summaryrefslogtreecommitdiffstats
path: root/platform/linuxbsd
diff options
context:
space:
mode:
Diffstat (limited to 'platform/linuxbsd')
-rw-r--r--platform/linuxbsd/SCsub3
-rw-r--r--platform/linuxbsd/detect.py62
-rw-r--r--platform/linuxbsd/export/export.cpp2
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp113
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h8
-rw-r--r--platform/linuxbsd/godot_linuxbsd.cpp12
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp16
-rw-r--r--platform/linuxbsd/os_linuxbsd.h2
-rw-r--r--platform/linuxbsd/pck_embed.ld10
-rw-r--r--platform/linuxbsd/pck_embed.legacy.ld10
-rw-r--r--platform/linuxbsd/wayland/SCsub208
-rw-r--r--platform/linuxbsd/wayland/detect_prime_egl.cpp231
-rw-r--r--platform/linuxbsd/wayland/detect_prime_egl.h65
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp1404
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.h295
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c453
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h175
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c607
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h231
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c89
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h42
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c67
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h34
-rw-r--r--platform/linuxbsd/wayland/egl_manager_wayland.cpp66
-rw-r--r--platform/linuxbsd/wayland/egl_manager_wayland.h53
-rw-r--r--platform/linuxbsd/wayland/key_mapping_xkb.cpp411
-rw-r--r--platform/linuxbsd/wayland/key_mapping_xkb.h65
-rw-r--r--platform/linuxbsd/wayland/vulkan_context_wayland.cpp59
-rw-r--r--platform/linuxbsd/wayland/vulkan_context_wayland.h52
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp4049
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.h949
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp70
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h3
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.cpp22
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.h2
35 files changed, 9873 insertions, 67 deletions
diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub
index 4dd74ff9d0..a3ce773ac2 100644
--- a/platform/linuxbsd/SCsub
+++ b/platform/linuxbsd/SCsub
@@ -19,6 +19,9 @@ if env["use_sowrap"]:
if env["x11"]:
common_linuxbsd += SConscript("x11/SCsub")
+if env["wayland"]:
+ common_linuxbsd += SConscript("wayland/SCsub")
+
if env["speechd"]:
common_linuxbsd.append("tts_linux.cpp")
if env["use_sowrap"]:
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py
index eaaaad82b9..94784f2da9 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -47,6 +47,8 @@ def get_opts():
BoolVariable("fontconfig", "Use fontconfig for system fonts support", True),
BoolVariable("udev", "Use udev for gamepad connection callbacks", True),
BoolVariable("x11", "Enable X11 display", True),
+ BoolVariable("wayland", "Enable Wayland display", True),
+ BoolVariable("libdecor", "Enable libdecor support", True),
BoolVariable("touch", "Enable touch events", True),
BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
]
@@ -204,6 +206,11 @@ def configure(env: "Environment"):
if env["use_sowrap"]:
env.Append(CPPDEFINES=["SOWRAP_ENABLED"])
+ if env["wayland"]:
+ if os.system("wayland-scanner -v 2>/dev/null") != 0:
+ print("wayland-scanner not found. Disabling Wayland support.")
+ env["wayland"] = False
+
if env["touch"]:
env.Append(CPPDEFINES=["TOUCH_ENABLED"])
@@ -364,9 +371,13 @@ def configure(env: "Environment"):
env.ParseConfig("pkg-config xkbcommon --cflags --libs")
env.Append(CPPDEFINES=["XKB_ENABLED"])
else:
- print(
- "Warning: libxkbcommon development libraries not found. Disabling dead key composition and key label support."
- )
+ if env["wayland"]:
+ print("Error: libxkbcommon development libraries required by Wayland not found. Aborting.")
+ sys.exit(255)
+ else:
+ print(
+ "Warning: libxkbcommon development libraries not found. Disabling dead key composition and key label support."
+ )
else:
env.Append(CPPDEFINES=["XKB_ENABLED"])
@@ -433,6 +444,33 @@ def configure(env: "Environment"):
env.ParseConfig("pkg-config xi --cflags --libs")
env.Append(CPPDEFINES=["X11_ENABLED"])
+ if env["wayland"]:
+ if not env["use_sowrap"]:
+ if os.system("pkg-config --exists libdecor-0"):
+ print("Warning: libdecor development libraries not found. Disabling client-side decorations.")
+ env["libdecor"] = False
+ else:
+ env.ParseConfig("pkg-config libdecor-0 --cflags --libs")
+ if os.system("pkg-config --exists wayland-client"):
+ print("Error: Wayland client library not found. Aborting.")
+ sys.exit(255)
+ env.ParseConfig("pkg-config wayland-client --cflags --libs")
+ if os.system("pkg-config --exists wayland-cursor"):
+ print("Error: Wayland cursor library not found. Aborting.")
+ sys.exit(255)
+ env.ParseConfig("pkg-config wayland-cursor --cflags --libs")
+ if os.system("pkg-config --exists wayland-egl"):
+ print("Error: Wayland EGL library not found. Aborting.")
+ sys.exit(255)
+ env.ParseConfig("pkg-config wayland-egl --cflags --libs")
+
+ if env["libdecor"]:
+ env.Append(CPPDEFINES=["LIBDECOR_ENABLED"])
+
+ env.Prepend(CPPPATH=["#platform/linuxbsd", "#thirdparty/linuxbsd_headers/wayland/"])
+ env.Append(CPPDEFINES=["WAYLAND_ENABLED"])
+ env.Append(LIBS=["rt"]) # Needed by glibc, used by _allocate_shm_file
+
if env["vulkan"]:
env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"])
if not env["use_volk"]:
@@ -458,24 +496,6 @@ def configure(env: "Environment"):
if env["execinfo"]:
env.Append(LIBS=["execinfo"])
- if not env.editor_build:
- import subprocess
- import re
-
- linker_version_str = subprocess.check_output(
- [env.subst(env["LINK"]), "-Wl,--version"] + env.subst(env["LINKFLAGS"])
- ).decode("utf-8")
- gnu_ld_version = re.search(r"^GNU ld [^$]*(\d+\.\d+)$", linker_version_str, re.MULTILINE)
- if not gnu_ld_version:
- print(
- "Warning: Creating export template binaries enabled for PCK embedding is currently only supported with GNU ld, not gold, LLD or mold."
- )
- else:
- if float(gnu_ld_version.group(1)) >= 2.30:
- env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.ld"])
- else:
- env.Append(LINKFLAGS=["-T", "platform/linuxbsd/pck_embed.legacy.ld"])
-
if platform.system() == "FreeBSD":
env.Append(LINKFLAGS=["-lkvm"])
diff --git a/platform/linuxbsd/export/export.cpp b/platform/linuxbsd/export/export.cpp
index f72c079d1d..a512714758 100644
--- a/platform/linuxbsd/export/export.cpp
+++ b/platform/linuxbsd/export/export.cpp
@@ -41,7 +41,7 @@ void register_linuxbsd_exporter_types() {
void register_linuxbsd_exporter() {
Ref<EditorExportPlatformLinuxBSD> platform;
platform.instantiate();
- platform->set_name("Linux/X11");
+ platform->set_name("Linux");
platform->set_os_name("Linux");
platform->set_chmod_flags(0755);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 3641f20c70..a3633e72b7 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -142,6 +142,54 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}
+void FreeDesktopPortalDesktop::append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options) {
+ DBusMessageIter dict_iter;
+ DBusMessageIter var_iter;
+ DBusMessageIter arr_iter;
+ const char *choices_key = "choices";
+
+ dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
+ dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &choices_key);
+ dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(ssa(ss)s)", &var_iter);
+ dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(ss(ss)s)", &arr_iter);
+
+ for (int i = 0; i < p_options.size(); i++) {
+ const Dictionary &item = p_options[i];
+ if (!item.has("name") || !item.has("values") || !item.has("default")) {
+ continue;
+ }
+ const String &name = item["name"];
+ const Vector<String> &options = item["values"];
+ int default_idx = item["default"];
+
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, name); // ID.
+ append_dbus_string(&struct_iter, name); // User visible name.
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(ss)", &array_iter);
+ for (int j = 0; j < options.size(); j++) {
+ dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ append_dbus_string(&array_struct_iter, itos(j));
+ append_dbus_string(&array_struct_iter, options[j]);
+ dbus_message_iter_close_container(&array_iter, &array_struct_iter);
+ }
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ if (options.is_empty()) {
+ append_dbus_string(&struct_iter, (default_idx) ? "true" : "false"); // Default selection.
+ } else {
+ append_dbus_string(&struct_iter, itos(default_idx)); // Default selection.
+ }
+
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
+ }
+ dbus_message_iter_close_container(&var_iter, &arr_iter);
+ dbus_message_iter_close_container(&dict_iter, &var_iter);
+ dbus_message_iter_close_container(p_iter, &dict_iter);
+}
+
void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
@@ -223,7 +271,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}
-bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
dbus_uint32_t resp_code;
@@ -262,6 +310,34 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
}
}
}
+ } else if (strcmp(key, "choices") == 0) { // a(ss) {
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
+ DBusMessageIter struct_iter;
+ dbus_message_iter_recurse(&var_iter, &struct_iter);
+ while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter opt_iter;
+ dbus_message_iter_recurse(&struct_iter, &opt_iter);
+ const char *opt_key = nullptr;
+ dbus_message_iter_get_basic(&opt_iter, &opt_key);
+ String opt_skey = String::utf8(opt_key);
+
+ dbus_message_iter_next(&opt_iter);
+ const char *opt_val = nullptr;
+ dbus_message_iter_get_basic(&opt_iter, &opt_val);
+ String opt_sval = String::utf8(opt_val);
+ if (opt_sval == "true") {
+ r_options[opt_skey] = true;
+ } else if (opt_sval == "false") {
+ r_options[opt_skey] = false;
+ } else {
+ r_options[opt_skey] = opt_sval.to_int();
+ }
+
+ if (!dbus_message_iter_next(&struct_iter)) {
+ break;
+ }
+ }
+ }
} else if (strcmp(key, "uris") == 0) { // as
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter uri_iter;
@@ -285,7 +361,7 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
return true;
}
-Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb) {
if (unsupported) {
return FAILED;
}
@@ -322,6 +398,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
fd.callback = p_callback;
fd.prev_focus = p_window_id;
fd.filter_names = filter_names;
+ fd.opt_in_cb = p_options_in_cb;
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
@@ -373,6 +450,8 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);
+
+ append_dbus_dict_options(&arr_iter, p_options);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
@@ -427,14 +506,25 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo
return OK;
}
-void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index) {
- Variant ret;
- Callable::CallError ce;
- const Variant *args[3] = { &p_status, &p_list, &p_index };
+void FreeDesktopPortalDesktop::_file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb) {
+ if (p_opt_in_cb) {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[4] = { &p_status, &p_list, &p_index, &p_options };
- p_callable.callp(args, 3, ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
- ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
+ p_callable.callp(args, 4, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 4, ce)));
+ }
+ } else {
+ Variant ret;
+ Callable::CallError ce;
+ const Variant *args[3] = { &p_status, &p_list, &p_index };
+
+ p_callable.callp(args, 3, ret, ce);
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_PRINT(vformat("Failed to execute file dialogs callback: %s.", Variant::get_callable_error_text(p_callable, args, 3, ce)));
+ }
}
}
@@ -458,11 +548,12 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
+ Dictionary options;
int index = 0;
- file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options);
if (fd.callback.is_valid()) {
- callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index);
+ callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb);
}
if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index 71e9812ea9..c9da387241 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -49,12 +49,13 @@ private:
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
+ static void append_dbus_dict_options(DBusMessageIter *p_iter, const TypedArray<Dictionary> &p_options);
static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
- static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index, Dictionary &r_options);
- void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index);
+ void _file_dialog_callback(const Callable &p_callable, const Variant &p_status, const Variant &p_list, const Variant &p_index, const Variant &p_options, bool p_opt_in_cb);
struct FileDialogData {
Vector<String> filter_names;
@@ -62,6 +63,7 @@ private:
DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
String path;
+ bool opt_in_cb = false;
};
Mutex file_dialog_mutex;
@@ -77,7 +79,7 @@ public:
bool is_supported() { return !unsupported; }
- Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
+ Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback, bool p_options_in_cb);
// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp
index efad9c8594..a2b6fbeb25 100644
--- a/platform/linuxbsd/godot_linuxbsd.cpp
+++ b/platform/linuxbsd/godot_linuxbsd.cpp
@@ -41,6 +41,18 @@
#include <sys/resource.h>
#endif
+// For export templates, add a section; the exporter will patch it to enclose
+// the data appended to the executable (bundled PCK).
+#if !defined(TOOLS_ENABLED) && defined(__GNUC__)
+static const char dummy[8] __attribute__((section("pck"), used)) = { 0 };
+
+// Dummy function to prevent LTO from discarding "pck" section.
+extern "C" const char *pck_section_dummy_call() __attribute__((used));
+extern "C" const char *pck_section_dummy_call() {
+ return &dummy[0];
+}
+#endif
+
int main(int argc, char *argv[]) {
#if defined(SANITIZERS_ENABLED)
// Note: Set stack size to be at least 30 MB (vs 8 MB default) to avoid overflow, address sanitizer can increase stack usage up to 3 times.
diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp
index aed8574902..f9e1aca742 100644
--- a/platform/linuxbsd/os_linuxbsd.cpp
+++ b/platform/linuxbsd/os_linuxbsd.cpp
@@ -39,6 +39,10 @@
#include "x11/display_server_x11.h"
#endif
+#ifdef WAYLAND_ENABLED
+#include "wayland/display_server_wayland.h"
+#endif
+
#include "modules/modules_enabled.gen.h" // For regex.
#ifdef MODULE_REGEX_ENABLED
#include "modules/regex/regex.h"
@@ -123,6 +127,14 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) {
}
}
+int OS_LinuxBSD::get_low_processor_usage_mode_sleep_usec() const {
+ if (DisplayServer::get_singleton() == nullptr || DisplayServer::get_singleton()->get_name() != "Wayland" || is_in_low_processor_usage_mode()) {
+ return OS::get_low_processor_usage_mode_sleep_usec();
+ }
+
+ return 500; // Roughly 2000 FPS, improves frame time when emulating VSync.
+}
+
void OS_LinuxBSD::initialize() {
crash_handler.initialize();
@@ -1166,6 +1178,10 @@ OS_LinuxBSD::OS_LinuxBSD() {
DisplayServerX11::register_x11_driver();
#endif
+#ifdef WAYLAND_ENABLED
+ DisplayServerWayland::register_wayland_driver();
+#endif
+
#ifdef FONTCONFIG_ENABLED
#ifdef SOWRAP_ENABLED
#ifdef DEBUG_ENABLED
diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h
index 6917ea5ae7..6ea2fc8e94 100644
--- a/platform/linuxbsd/os_linuxbsd.h
+++ b/platform/linuxbsd/os_linuxbsd.h
@@ -127,6 +127,8 @@ public:
virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+ virtual int get_low_processor_usage_mode_sleep_usec() const override;
+
virtual bool _check_internal_feature_support(const String &p_feature) override;
void run();
diff --git a/platform/linuxbsd/pck_embed.ld b/platform/linuxbsd/pck_embed.ld
deleted file mode 100644
index 57a1994043..0000000000
--- a/platform/linuxbsd/pck_embed.ld
+++ /dev/null
@@ -1,10 +0,0 @@
-SECTIONS
-{
- /* Add a zero-sized section; the exporter will patch it to enclose the data appended to the executable (embedded PCK) */
- pck 0 (INFO) :
- {
- /* binutils >= 2.30 allow it being zero-sized, but needs something between the braces to keep the section */
- . = ALIGN(8);
- }
-}
-INSERT AFTER .rodata;
diff --git a/platform/linuxbsd/pck_embed.legacy.ld b/platform/linuxbsd/pck_embed.legacy.ld
deleted file mode 100644
index a23013ba7a..0000000000
--- a/platform/linuxbsd/pck_embed.legacy.ld
+++ /dev/null
@@ -1,10 +0,0 @@
-SECTIONS
-{
- /* The exporter will patch this section to enclose the data appended to the executable (embedded PCK) */
- pck 0 (INFO) : AT ( ADDR (.rodata) + SIZEOF (.rodata) )
- {
- /* binutils < 2.30 need some actual content for the linker not to discard the section */
- BYTE(0);
- }
-}
-INSERT AFTER .rodata;
diff --git a/platform/linuxbsd/wayland/SCsub b/platform/linuxbsd/wayland/SCsub
new file mode 100644
index 0000000000..99b7349dbe
--- /dev/null
+++ b/platform/linuxbsd/wayland/SCsub
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+
+Import("env")
+
+# TODO: Add warning to headers and code about their autogenerated status.
+if env["use_sowrap"]:
+ # We have to implement separate builders for so wrappers as the
+ # autogenerated Wayland protocol wrapper must include them instead of the
+ # native libraries.
+
+ WAYLAND_BUILDERS_SOWRAP = {
+ "WAYLAND_API_HEADER": Builder(
+ action=Action(
+ "wayland-scanner -c client-header < ${SOURCE} | sed 's:wayland-client-core\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}",
+ 'Generating Wayland client header: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ "WAYLAND_API_CODE": Builder(
+ action=Action(
+ "wayland-scanner -c private-code < ${SOURCE} | sed 's:wayland-util\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}",
+ 'Generating Wayland protocol marshaling code: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ }
+ env.Append(BUILDERS=WAYLAND_BUILDERS_SOWRAP)
+else:
+ WAYLAND_BUILDERS = {
+ "WAYLAND_API_HEADER": Builder(
+ action=Action(
+ "wayland-scanner -c client-header < ${SOURCE} > ${TARGET}",
+ 'Generating Wayland client header: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ "WAYLAND_API_CODE": Builder(
+ action=Action(
+ "wayland-scanner -c private-code < ${SOURCE} > ${TARGET}",
+ 'Generating Wayland protocol marshaling code: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ }
+ env.Append(BUILDERS=WAYLAND_BUILDERS)
+
+env.WAYLAND_API_HEADER(target="protocol/wayland.gen.h", source="#thirdparty/wayland/protocol/wayland.xml")
+env.WAYLAND_API_CODE(target="protocol/wayland.gen.c", source="#thirdparty/wayland/protocol/wayland.xml")
+
+env.WAYLAND_API_HEADER(
+ target="protocol/viewporter.gen.h", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"
+)
+env.WAYLAND_API_CODE(
+ target="protocol/viewporter.gen.c", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/fractional_scale.gen.h",
+ source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml",
+)
+env.WAYLAND_API_CODE(
+ target="protocol/fractional_scale.gen.c",
+ source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_shell.gen.h", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_shell.gen.c", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_decoration.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_decoration.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_activation.gen.h",
+ source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_activation.gen.c",
+ source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/relative_pointer.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/relative_pointer.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/pointer_constraints.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/pointer_constraints.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/pointer_gestures.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/pointer_gestures.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/primary_selection.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/primary_selection.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/idle_inhibit.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/idle_inhibit.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/tablet.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/tablet.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_foreign.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_foreign.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml",
+)
+
+source_files = [
+ "protocol/wayland.gen.c",
+ "protocol/viewporter.gen.c",
+ "protocol/fractional_scale.gen.c",
+ "protocol/xdg_shell.gen.c",
+ "protocol/xdg_foreign.gen.c",
+ "protocol/xdg_decoration.gen.c",
+ "protocol/xdg_activation.gen.c",
+ "protocol/relative_pointer.gen.c",
+ "protocol/pointer_constraints.gen.c",
+ "protocol/pointer_gestures.gen.c",
+ "protocol/primary_selection.gen.c",
+ "protocol/idle_inhibit.gen.c",
+ "protocol/tablet.gen.c",
+ "display_server_wayland.cpp",
+ "wayland_thread.cpp",
+ "key_mapping_xkb.cpp",
+ "detect_prime_egl.cpp",
+]
+
+if env["use_sowrap"]:
+ source_files.append(
+ [
+ "dynwrappers/wayland-cursor-so_wrap.c",
+ "dynwrappers/wayland-client-core-so_wrap.c",
+ "dynwrappers/wayland-egl-core-so_wrap.c",
+ ]
+ )
+
+ if env["libdecor"]:
+ source_files.append("dynwrappers/libdecor-so_wrap.c")
+
+
+if env["vulkan"]:
+ source_files.append("vulkan_context_wayland.cpp")
+
+if env["opengl3"]:
+ source_files.append("egl_manager_wayland.cpp")
+
+objects = []
+
+for source_file in source_files:
+ objects.append(env.Object(source_file))
+
+Return("objects")
diff --git a/platform/linuxbsd/wayland/detect_prime_egl.cpp b/platform/linuxbsd/wayland/detect_prime_egl.cpp
new file mode 100644
index 0000000000..4bee32ae3a
--- /dev/null
+++ b/platform/linuxbsd/wayland/detect_prime_egl.cpp
@@ -0,0 +1,231 @@
+/**************************************************************************/
+/* detect_prime_egl.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifdef GLES3_ENABLED
+#ifdef EGL_ENABLED
+
+#include "detect_prime_egl.h"
+
+#include "core/string/print_string.h"
+#include "core/string/ustring.h"
+
+#include <stdlib.h>
+
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#include "thirdparty/glad/glad/gl.h"
+#else
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/glcorearb.h>
+#endif // GLAD_ENABLED
+
+#include <cstring>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+// To prevent shadowing warnings.
+#undef glGetString
+
+// Runs inside a child. Exiting will not quit the engine.
+void DetectPrimeEGL::create_context() {
+#if defined(GLAD_ENABLED)
+ if (!gladLoaderLoadEGL(nullptr)) {
+ print_verbose("Unable to load EGL, GPU detection skipped.");
+ quick_exit(1);
+ }
+#endif
+
+ EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ EGLConfig egl_config;
+ EGLContext egl_context = EGL_NO_CONTEXT;
+
+ eglInitialize(egl_display, NULL, NULL);
+
+#if defined(GLAD_ENABLED)
+ if (!gladLoaderLoadEGL(egl_display)) {
+ print_verbose("Unable to load EGL, GPU detection skipped.");
+ quick_exit(1);
+ }
+#endif
+
+ eglBindAPI(EGL_OPENGL_API);
+
+ EGLint attribs[] = {
+ EGL_RED_SIZE,
+ 1,
+ EGL_BLUE_SIZE,
+ 1,
+ EGL_GREEN_SIZE,
+ 1,
+ EGL_DEPTH_SIZE,
+ 24,
+ EGL_NONE,
+ };
+
+ EGLint config_count = 0;
+ eglChooseConfig(egl_display, attribs, &egl_config, 1, &config_count);
+
+ EGLint context_attribs[] = {
+ EGL_CONTEXT_MAJOR_VERSION, 3,
+ EGL_CONTEXT_MINOR_VERSION, 3,
+ EGL_NONE
+ };
+
+ egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs);
+ if (egl_context == EGL_NO_CONTEXT) {
+ print_verbose("Unable to create an EGL context, GPU detection skipped.");
+ quick_exit(1);
+ }
+
+ eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
+}
+
+int DetectPrimeEGL::detect_prime() {
+ pid_t p;
+ int priorities[4] = {};
+ String vendors[4];
+ String renderers[4];
+
+ for (int i = 0; i < 4; ++i) {
+ vendors[i] = "Unknown";
+ renderers[i] = "Unknown";
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ int fdset[2];
+
+ if (pipe(fdset) == -1) {
+ print_verbose("Failed to pipe(), using default GPU");
+ return 0;
+ }
+
+ // Fork so the driver initialization can crash without taking down the engine.
+ p = fork();
+
+ if (p > 0) {
+ // Main thread
+
+ int stat_loc = 0;
+ char string[201];
+ string[200] = '\0';
+
+ close(fdset[1]);
+
+ waitpid(p, &stat_loc, 0);
+
+ if (!stat_loc) {
+ // No need to do anything complicated here. Anything less than
+ // PIPE_BUF will be delivered in one read() call.
+ // Leave it 'Unknown' otherwise.
+ if (read(fdset[0], string, sizeof(string) - 1) > 0) {
+ vendors[i] = string;
+ renderers[i] = string + strlen(string) + 1;
+ }
+ }
+
+ close(fdset[0]);
+ } else {
+ // In child, exit() here will not quit the engine.
+
+ // Prevent false leak reports as we will not be properly
+ // cleaning up these processes, and fork() makes a copy
+ // of all globals.
+ CoreGlobals::leak_reporting_enabled = false;
+
+ char string[201];
+
+ close(fdset[0]);
+
+ setenv("DRI_PRIME", itos(i).utf8().ptr(), 1);
+
+ create_context();
+
+ PFNGLGETSTRINGPROC glGetString = (PFNGLGETSTRINGPROC)eglGetProcAddress("glGetString");
+ const char *vendor = (const char *)glGetString(GL_VENDOR);
+ const char *renderer = (const char *)glGetString(GL_RENDERER);
+
+ unsigned int vendor_len = strlen(vendor) + 1;
+ unsigned int renderer_len = strlen(renderer) + 1;
+
+ if (vendor_len + renderer_len >= sizeof(string)) {
+ renderer_len = 200 - vendor_len;
+ }
+
+ memcpy(&string, vendor, vendor_len);
+ memcpy(&string[vendor_len], renderer, renderer_len);
+
+ if (write(fdset[1], string, vendor_len + renderer_len) == -1) {
+ print_verbose("Couldn't write vendor/renderer string.");
+ }
+ close(fdset[1]);
+
+ // The function quick_exit() is used because exit() will call destructors on static objects copied by fork().
+ // These objects will be freed anyway when the process finishes execution.
+ quick_exit(0);
+ }
+ }
+
+ int preferred = 0;
+ int priority = 0;
+
+ if (vendors[0] == vendors[1]) {
+ print_verbose("Only one GPU found, using default.");
+ return 0;
+ }
+
+ for (int i = 3; i >= 0; --i) {
+ const Vendor *v = vendor_map;
+ while (v->glxvendor) {
+ if (v->glxvendor == vendors[i]) {
+ priorities[i] = v->priority;
+
+ if (v->priority >= priority) {
+ priority = v->priority;
+ preferred = i;
+ }
+ }
+ ++v;
+ }
+ }
+
+ print_verbose("Found renderers:");
+ for (int i = 0; i < 4; ++i) {
+ print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i]));
+ }
+
+ print_verbose("Using renderer: " + renderers[preferred]);
+ return preferred;
+}
+
+#endif // EGL_ENABLED
+#endif // GLES3_ENABLED
diff --git a/platform/linuxbsd/wayland/detect_prime_egl.h b/platform/linuxbsd/wayland/detect_prime_egl.h
new file mode 100644
index 0000000000..26351b0dce
--- /dev/null
+++ b/platform/linuxbsd/wayland/detect_prime_egl.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* detect_prime_egl.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. */
+/**************************************************************************/
+
+#ifndef DETECT_PRIME_EGL_H
+#define DETECT_PRIME_EGL_H
+
+#ifdef GLES3_ENABLED
+#ifdef EGL_ENABLED
+
+class DetectPrimeEGL {
+private:
+ struct Vendor {
+ const char *glxvendor = nullptr;
+ int priority = 0;
+ };
+
+ static constexpr Vendor vendor_map[] = {
+ { "Advanced Micro Devices, Inc.", 30 },
+ { "AMD", 30 },
+ { "NVIDIA Corporation", 30 },
+ { "X.Org", 30 },
+ { "Intel Open Source Technology Center", 20 },
+ { "Intel", 20 },
+ { "nouveau", 10 },
+ { "Mesa Project", 0 },
+ { nullptr, 0 }
+ };
+
+ static void create_context();
+
+public:
+ static int detect_prime();
+};
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+
+#endif // DETECT_PRIME_EGL_H
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
new file mode 100644
index 0000000000..02b715056a
--- /dev/null
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -0,0 +1,1404 @@
+/**************************************************************************/
+/* display_server_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "display_server_wayland.h"
+
+#ifdef WAYLAND_ENABLED
+
+#define WAYLAND_DISPLAY_SERVER_DEBUG_LOGS_ENABLED
+#ifdef WAYLAND_DISPLAY_SERVER_DEBUG_LOGS_ENABLED
+#define DEBUG_LOG_WAYLAND(...) print_verbose(__VA_ARGS__)
+#else
+#define DEBUG_LOG_WAYLAND(...)
+#endif
+
+#ifdef VULKAN_ENABLED
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#endif
+
+#ifdef GLES3_ENABLED
+#include "detect_prime_egl.h"
+#include "drivers/gles3/rasterizer_gles3.h"
+#endif
+
+String DisplayServerWayland::_get_app_id_from_context(Context p_context) {
+ String app_id;
+
+ switch (p_context) {
+ case CONTEXT_EDITOR: {
+ app_id = "org.godotengine.Editor";
+ } break;
+
+ case CONTEXT_PROJECTMAN: {
+ app_id = "org.godotengine.ProjectManager";
+ } break;
+
+ case CONTEXT_ENGINE:
+ default: {
+ String config_name = GLOBAL_GET("application/config/name");
+ if (config_name.length() != 0) {
+ app_id = config_name;
+ } else {
+ app_id = "org.godotengine.Godot";
+ }
+ }
+ }
+
+ return app_id;
+}
+
+void DisplayServerWayland::_send_window_event(WindowEvent p_event) {
+ WindowData &wd = main_window;
+
+ if (wd.window_event_callback.is_valid()) {
+ Variant event = int(p_event);
+ wd.window_event_callback.call(event);
+ }
+}
+
+void DisplayServerWayland::dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerWayland *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerWayland::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ Callable callable = main_window.input_event_callback;
+ if (callable.is_valid()) {
+ callable.call(p_event);
+ }
+}
+
+void DisplayServerWayland::_resize_window(const Size2i &p_size) {
+ WindowData &wd = main_window;
+
+ wd.rect.size = p_size;
+
+#ifdef RD_ENABLED
+ if (wd.visible && context_rd) {
+ context_rd->window_resize(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (wd.visible && egl_manager) {
+ wl_egl_window_resize(wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height, 0, 0);
+ }
+#endif
+
+ if (wd.rect_changed_callback.is_valid()) {
+ wd.rect_changed_callback.call(wd.rect);
+ }
+}
+
+void DisplayServerWayland::_show_window() {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ DEBUG_LOG_WAYLAND("Showing window.");
+
+ // Showing this window will reset its mode with whatever the compositor
+ // reports. We'll save the mode beforehand so that we can reapply it later.
+ // TODO: Fix/Port/Move/Whatever to `WaylandThread` APIs.
+ WindowMode setup_mode = wd.mode;
+
+ wayland_thread.window_create(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
+ wayland_thread.window_set_min_size(MAIN_WINDOW_ID, wd.min_size);
+ wayland_thread.window_set_max_size(MAIN_WINDOW_ID, wd.max_size);
+ wayland_thread.window_set_app_id(MAIN_WINDOW_ID, _get_app_id_from_context(context));
+ wayland_thread.window_set_borderless(MAIN_WINDOW_ID, window_get_flag(WINDOW_FLAG_BORDERLESS));
+
+ // NOTE: The XDG shell protocol is built in a way that causes the window to
+ // be immediately shown as soon as a valid buffer is assigned to it. Hence,
+ // the only acceptable way of implementing window showing is to move the
+ // graphics context window creation logic here.
+#ifdef RD_ENABLED
+ if (context_rd) {
+ union {
+#ifdef VULKAN_ENABLED
+ VulkanContextWayland::WindowPlatformData vulkan;
+#endif
+ } wpd;
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ wpd.vulkan.surface = wayland_thread.window_get_wl_surface(wd.id);
+ wpd.vulkan.display = wayland_thread.get_wl_display();
+ }
+#endif
+ Error err = context_rd->window_create(wd.id, wd.vsync_mode, wd.rect.size.width, wd.rect.size.height, &wpd);
+ ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", context_rd->get_api_name()));
+
+ emulate_vsync = (context_rd->get_vsync_mode(wd.id) == DisplayServer::VSYNC_ENABLED);
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames using MAILBOX.");
+ context_rd->set_vsync_mode(wd.id, DisplayServer::VSYNC_MAILBOX);
+ }
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id);
+ wd.wl_egl_window = wl_egl_window_create(wl_surface, wd.rect.size.width, wd.rect.size.height);
+
+ Error err = egl_manager->window_create(MAIN_WINDOW_ID, wayland_thread.get_wl_display(), wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height);
+ ERR_FAIL_COND_MSG(err == ERR_CANT_CREATE, "Can't show a GLES3 window.");
+
+ window_set_vsync_mode(wd.vsync_mode, MAIN_WINDOW_ID);
+ }
+#endif
+ // NOTE: The public window-handling methods might depend on this flag being
+ // set. Ensure to not make any of these calls before this assignment.
+ wd.visible = true;
+
+ // Actually try to apply the window's mode now that it's visible.
+ window_set_mode(setup_mode);
+
+ wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
+ }
+}
+// Interface methods.
+
+bool DisplayServerWayland::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_MOUSE:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_SWAP_BUFFERS:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_CLIPBOARD_PRIMARY:
+#ifdef DBUS_ENABLED
+ case FEATURE_NATIVE_DIALOG:
+#endif
+ case FEATURE_HIDPI: {
+ return true;
+ } break;
+
+ default: {
+ return false;
+ }
+ }
+}
+
+String DisplayServerWayland::get_name() const {
+ return "Wayland";
+}
+
+#ifdef SPEECHD_ENABLED
+
+bool DisplayServerWayland::tts_is_speaking() const {
+ ERR_FAIL_NULL_V(tts, false);
+ return tts->is_speaking();
+}
+
+bool DisplayServerWayland::tts_is_paused() const {
+ ERR_FAIL_NULL_V(tts, false);
+ return tts->is_paused();
+}
+
+TypedArray<Dictionary> DisplayServerWayland::tts_get_voices() const {
+ ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
+ return tts->get_voices();
+}
+
+void DisplayServerWayland::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_NULL(tts);
+ tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
+}
+
+void DisplayServerWayland::tts_pause() {
+ ERR_FAIL_NULL(tts);
+ tts->pause();
+}
+
+void DisplayServerWayland::tts_resume() {
+ ERR_FAIL_NULL(tts);
+ tts->resume();
+}
+
+void DisplayServerWayland::tts_stop() {
+ ERR_FAIL_NULL(tts);
+ tts->stop();
+}
+
+#endif
+
+#ifdef DBUS_ENABLED
+
+bool DisplayServerWayland::is_dark_mode_supported() const {
+ return portal_desktop->is_supported();
+}
+
+bool DisplayServerWayland::is_dark_mode() const {
+ switch (portal_desktop->get_appearance_color_scheme()) {
+ case 1:
+ // Prefers dark theme.
+ return true;
+ case 2:
+ // Prefers light theme.
+ return false;
+ default:
+ // Preference unknown.
+ return false;
+ }
+}
+
+Error DisplayServerWayland::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ WindowID window_id = MAIN_WINDOW_ID;
+ // TODO: Use window IDs for multiwindow support.
+
+ WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
+ return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerWayland::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ WindowID window_id = MAIN_WINDOW_ID;
+ // TODO: Use window IDs for multiwindow support.
+
+ WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
+ return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
+}
+
+#endif
+
+void DisplayServerWayland::mouse_set_mode(MouseMode p_mode) {
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ bool show_cursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
+
+ if (show_cursor) {
+ if (custom_cursors.has(cursor_shape)) {
+ wayland_thread.cursor_set_custom_shape(cursor_shape);
+ } else {
+ wayland_thread.cursor_set_shape(cursor_shape);
+ }
+ } else {
+ wayland_thread.cursor_hide();
+ }
+
+ WaylandThread::PointerConstraint constraint = WaylandThread::PointerConstraint::NONE;
+
+ switch (p_mode) {
+ case DisplayServer::MOUSE_MODE_CAPTURED: {
+ constraint = WaylandThread::PointerConstraint::LOCKED;
+ } break;
+
+ case DisplayServer::MOUSE_MODE_CONFINED:
+ case DisplayServer::MOUSE_MODE_CONFINED_HIDDEN: {
+ constraint = WaylandThread::PointerConstraint::CONFINED;
+ } break;
+
+ default: {
+ }
+ }
+
+ wayland_thread.pointer_set_constraint(constraint);
+
+ mouse_mode = p_mode;
+}
+
+DisplayServerWayland::MouseMode DisplayServerWayland::mouse_get_mode() const {
+ return mouse_mode;
+}
+
+// NOTE: This is hacked together (and not guaranteed to work in the first place)
+// as for some reason the there's no proper way to ask the compositor to warp
+// the pointer, although, at the time of writing, there's a proposal for a
+// proper protocol for this. See:
+// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/158
+void DisplayServerWayland::warp_mouse(const Point2i &p_to) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WaylandThread::PointerConstraint old_constraint = wayland_thread.pointer_get_constraint();
+
+ wayland_thread.pointer_set_constraint(WaylandThread::PointerConstraint::LOCKED);
+ wayland_thread.pointer_set_hint(p_to);
+
+ wayland_thread.pointer_set_constraint(old_constraint);
+}
+
+Point2i DisplayServerWayland::mouse_get_position() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't properly implement this method by design.
+ // This is the best we can do unfortunately.
+ return Input::get_singleton()->get_mouse_position();
+
+ return Point2i();
+}
+
+BitField<MouseButtonMask> DisplayServerWayland::mouse_get_button_state() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // Are we sure this is the only way? This seems sus.
+ // TODO: Handle tablets properly.
+ //mouse_button_mask.set_flag(MouseButtonMask((int64_t)wls.current_seat->tablet_tool_data.pressed_button_mask));
+
+ return wayland_thread.pointer_get_button_mask();
+}
+
+// NOTE: According to the Wayland specification, this method will only do
+// anything if the user has interacted with the application by sending a
+// "recent enough" input event.
+// TODO: Add this limitation to the documentation.
+void DisplayServerWayland::clipboard_set(const String &p_text) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.selection_set_text(p_text);
+}
+
+String DisplayServerWayland::clipboard_get() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<uint8_t> data;
+
+ const String text_mimes[] = {
+ "text/plain;charset=utf-8",
+ "text/plain",
+ };
+
+ for (String mime : text_mimes) {
+ if (wayland_thread.selection_has_mime(mime)) {
+ print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime));
+ data = wayland_thread.selection_get_mime(mime);
+ break;
+ }
+ }
+
+ return String::utf8((const char *)data.ptr(), data.size());
+}
+
+Ref<Image> DisplayServerWayland::clipboard_get_image() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Ref<Image> image;
+ image.instantiate();
+
+ Error err = OK;
+
+ // TODO: Fallback to next media type on missing module or parse error.
+ if (wayland_thread.selection_has_mime("image/png")) {
+ err = image->load_png_from_buffer(wayland_thread.selection_get_mime("image/png"));
+ } else if (wayland_thread.selection_has_mime("image/jpeg")) {
+ err = image->load_jpg_from_buffer(wayland_thread.selection_get_mime("image/jpeg"));
+ } else if (wayland_thread.selection_has_mime("image/webp")) {
+ err = image->load_webp_from_buffer(wayland_thread.selection_get_mime("image/webp"));
+ } else if (wayland_thread.selection_has_mime("image/svg+xml")) {
+ err = image->load_svg_from_buffer(wayland_thread.selection_get_mime("image/svg+xml"));
+ } else if (wayland_thread.selection_has_mime("image/bmp")) {
+ err = image->load_bmp_from_buffer(wayland_thread.selection_get_mime("image/bmp"));
+ } else if (wayland_thread.selection_has_mime("image/x-tga")) {
+ err = image->load_tga_from_buffer(wayland_thread.selection_get_mime("image/x-tga"));
+ } else if (wayland_thread.selection_has_mime("image/x-targa")) {
+ err = image->load_tga_from_buffer(wayland_thread.selection_get_mime("image/x-targa"));
+ } else if (wayland_thread.selection_has_mime("image/ktx")) {
+ err = image->load_ktx_from_buffer(wayland_thread.selection_get_mime("image/ktx"));
+ }
+
+ ERR_FAIL_COND_V(err != OK, Ref<Image>());
+
+ return image;
+}
+
+void DisplayServerWayland::clipboard_set_primary(const String &p_text) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.primary_set_text(p_text);
+}
+
+String DisplayServerWayland::clipboard_get_primary() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<uint8_t> data;
+
+ const String text_mimes[] = {
+ "text/plain;charset=utf-8",
+ "text/plain",
+ };
+
+ for (String mime : text_mimes) {
+ if (wayland_thread.primary_has_mime(mime)) {
+ print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime));
+ wayland_thread.primary_get_mime(mime);
+ break;
+ }
+ }
+
+ return String::utf8((const char *)data.ptr(), data.size());
+}
+
+int DisplayServerWayland::get_screen_count() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+ return wayland_thread.get_screen_count();
+}
+
+int DisplayServerWayland::get_primary_screen() const {
+ // AFAIK Wayland doesn't allow knowing (nor we care) about which screen is
+ // primary.
+ return 0;
+}
+
+Point2i DisplayServerWayland::screen_get_position(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).position;
+}
+
+Size2i DisplayServerWayland::screen_get_size(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).size;
+}
+
+Rect2i DisplayServerWayland::screen_get_usable_rect(int p_screen) const {
+ // Unsupported on wayland.
+ return Rect2i(Point2i(), screen_get_size(p_screen));
+}
+
+int DisplayServerWayland::screen_get_dpi(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ const WaylandThread::ScreenData &data = wayland_thread.screen_get_data(p_screen);
+
+ int width_mm = data.physical_size.width;
+ int height_mm = data.physical_size.height;
+
+ double xdpi = (width_mm ? data.size.width / (double)width_mm * 25.4 : 0);
+ double ydpi = (height_mm ? data.size.height / (double)height_mm * 25.4 : 0);
+
+ if (xdpi || ydpi) {
+ return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
+ }
+
+ // Could not get DPI.
+ return 96;
+}
+
+float DisplayServerWayland::screen_get_scale(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).scale;
+}
+
+float DisplayServerWayland::screen_get_refresh_rate(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).refresh_rate;
+}
+
+void DisplayServerWayland::screen_set_keep_on(bool p_enable) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (screen_is_kept_on() == p_enable) {
+ return;
+ }
+
+#ifdef DBUS_ENABLED
+ if (screensaver) {
+ if (p_enable) {
+ screensaver->inhibit();
+ } else {
+ screensaver->uninhibit();
+ }
+
+ screensaver_inhibited = p_enable;
+ }
+#endif
+}
+
+bool DisplayServerWayland::screen_is_kept_on() const {
+#ifdef DBUS_ENABLED
+ return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID) || screensaver_inhibited;
+#endif
+
+ return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID);
+}
+
+Vector<DisplayServer::WindowID> DisplayServerWayland::get_window_list() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<int> ret;
+ ret.push_back(MAIN_WINDOW_ID);
+
+ return ret;
+}
+
+int64_t DisplayServerWayland::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return (int64_t)wayland_thread.get_wl_display();
+ } break;
+
+ case WINDOW_HANDLE: {
+ return (int64_t)wayland_thread.window_get_wl_surface(p_window);
+ } break;
+
+ case WINDOW_VIEW: {
+ return 0; // Not supported.
+ } break;
+
+#ifdef GLES3_ENABLED
+ case OPENGL_CONTEXT: {
+ if (egl_manager) {
+ return (int64_t)egl_manager->get_context(p_window);
+ }
+ return 0;
+ } break;
+#endif // GLES3_ENABLED
+
+ default: {
+ return 0;
+ } break;
+ }
+}
+
+DisplayServer::WindowID DisplayServerWayland::get_window_at_screen_position(const Point2i &p_position) const {
+ // Standard Wayland APIs don't support this.
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerWayland::window_attach_instance_id(ObjectID p_instance, WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.instance_id = p_instance;
+}
+
+ObjectID DisplayServerWayland::window_get_attached_instance_id(WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.instance_id;
+}
+
+void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ wd.title = p_title;
+
+ wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
+}
+
+void DisplayServerWayland::window_set_mouse_passthrough(const Vector<Vector2> &p_region, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_mouse_passthrough region %s", p_region));
+}
+
+void DisplayServerWayland::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.rect_changed_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.window_event_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_input_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.input_event_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_input_text_callback(const Callable &p_callable, WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.input_text_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.drop_files_callback = p_callable;
+}
+
+int DisplayServerWayland::window_get_current_screen(DisplayServer::WindowID p_window_id) const {
+ // Standard Wayland APIs don't support getting the screen of a window.
+ return 0;
+}
+
+void DisplayServerWayland::window_set_current_screen(int p_screen, DisplayServer::WindowID p_window_id) {
+ // Standard Wayland APIs don't support setting the screen of a window.
+}
+
+Point2i DisplayServerWayland::window_get_position(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't know the position of toplevels with the standard protocol.
+ return Point2i();
+}
+
+Point2i DisplayServerWayland::window_get_position_with_decorations(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't know the position of toplevels with the standard protocol, nor can
+ // we get information about the decorations, at least with SSDs.
+ return Point2i();
+}
+
+void DisplayServerWayland::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window_id) {
+ // Unsupported with toplevels.
+}
+
+void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("window max size set to %s", p_size));
+
+ if (p_size.x < 0 || p_size.y < 0) {
+ ERR_FAIL_MSG("Maximum window size can't be negative!");
+ }
+
+ WindowData &wd = main_window;
+
+ // FIXME: Is `p_size.x < wd.min_size.x || p_size.y < wd.min_size.y` == `p_size < wd.min_size`?
+ if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+ ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+
+ wd.max_size = p_size;
+
+ wayland_thread.window_set_max_size(MAIN_WINDOW_ID, p_size);
+}
+
+Size2i DisplayServerWayland::window_get_max_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.max_size;
+}
+
+void DisplayServerWayland::gl_window_make_current(DisplayServer::WindowID p_window_id) {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->window_make_current(MAIN_WINDOW_ID);
+ }
+#endif
+}
+
+void DisplayServerWayland::window_set_transient(WindowID p_window_id, WindowID p_parent) {
+ // Currently unsupported.
+}
+
+void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("window minsize set to %s", p_size));
+
+ WindowData &wd = main_window;
+
+ if (p_size.x < 0 || p_size.y < 0) {
+ ERR_FAIL_MSG("Minimum window size can't be negative!");
+ }
+
+ // FIXME: Is `p_size.x > wd.max_size.x || p_size.y > wd.max_size.y` == `p_size > wd.max_size`?
+ if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+ ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+
+ wd.min_size = p_size;
+
+ wayland_thread.window_set_min_size(MAIN_WINDOW_ID, p_size);
+}
+
+Size2i DisplayServerWayland::window_get_min_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.min_size;
+}
+
+void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ // The XDG spec doesn't allow non-interactive resizes.
+}
+
+Size2i DisplayServerWayland::window_get_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.rect.size;
+}
+
+Size2i DisplayServerWayland::window_get_size_with_decorations(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // I don't think there's a way of actually knowing the size of the window
+ // decoration in Wayland, at least in the case of SSDs, nor that it would be
+ // that useful in this case. We'll just return the main window's size.
+ return main_window.rect.size;
+}
+
+void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ return;
+ }
+
+ wayland_thread.window_try_set_mode(p_window_id, p_mode);
+}
+
+DisplayServer::WindowMode DisplayServerWayland::window_get_mode(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ const WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ return WINDOW_MODE_WINDOWED;
+ }
+
+ return wayland_thread.window_get_mode(p_window_id);
+}
+
+bool DisplayServerWayland::window_is_maximize_allowed(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.window_can_set_mode(p_window_id, WINDOW_MODE_MAXIMIZED);
+}
+
+void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ DEBUG_LOG_WAYLAND(vformat("Window set flag %d", p_flag));
+
+ switch (p_flag) {
+ case WINDOW_FLAG_BORDERLESS: {
+ wayland_thread.window_set_borderless(MAIN_WINDOW_ID, p_enabled);
+ } break;
+
+ default: {
+ }
+ }
+
+ if (p_enabled) {
+ wd.flags |= 1 << p_flag;
+ } else {
+ wd.flags &= ~(1 << p_flag);
+ }
+}
+
+bool DisplayServerWayland::window_get_flag(WindowFlags p_flag, DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.flags & (1 << p_flag);
+}
+
+void DisplayServerWayland::window_request_attention(DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND("Requested attention.");
+
+ wayland_thread.window_request_attention(MAIN_WINDOW_ID);
+}
+
+void DisplayServerWayland::window_move_to_foreground(DisplayServer::WindowID p_window_id) {
+ // Standard Wayland APIs don't support this.
+}
+
+bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const {
+ return wayland_thread.pointer_get_pointed_window_id() == p_window_id;
+}
+
+bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const {
+ return frame;
+}
+
+bool DisplayServerWayland::can_any_window_draw() const {
+ return frame;
+}
+
+void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_active active %s", p_active ? "true" : "false"));
+}
+
+void DisplayServerWayland::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_position pos %s window %d", p_pos, p_window_id));
+}
+
+// NOTE: While Wayland is supposed to be tear-free, wayland-protocols version
+// 1.30 added a protocol for allowing async flips which is supposed to be
+// handled by drivers such as Vulkan. We can then just ask to disable v-sync and
+// hope for the best. See: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/6394f0b4f3be151076f10a845a2fb131eeb56706
+void DisplayServerWayland::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+#ifdef RD_ENABLED
+ if (context_rd) {
+ context_rd->set_vsync_mode(p_window_id, p_vsync_mode);
+
+ emulate_vsync = (context_rd->get_vsync_mode(p_window_id) == DisplayServer::VSYNC_ENABLED);
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames using MAILBOX.");
+ context_rd->set_vsync_mode(p_window_id, DisplayServer::VSYNC_MAILBOX);
+ }
+ }
+#endif // VULKAN_ENABLED
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
+
+ emulate_vsync = egl_manager->is_using_vsync();
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames with swap delay 0.");
+ egl_manager->set_use_vsync(false);
+ }
+ }
+#endif // GLES3_ENABLED
+}
+
+DisplayServer::VSyncMode DisplayServerWayland::window_get_vsync_mode(DisplayServer::WindowID p_window_id) const {
+ if (emulate_vsync) {
+ return DisplayServer::VSYNC_ENABLED;
+ }
+
+#ifdef VULKAN_ENABLED
+ if (context_rd) {
+ return context_rd->get_vsync_mode(p_window_id);
+ }
+#endif // VULKAN_ENABLED
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ return egl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
+ }
+#endif // GLES3_ENABLED
+
+ return DisplayServer::VSYNC_ENABLED;
+}
+
+void DisplayServerWayland::cursor_set_shape(CursorShape p_shape) {
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_shape == cursor_shape) {
+ return;
+ }
+
+ cursor_shape = p_shape;
+
+ if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+ // Hidden.
+ return;
+ }
+
+ if (custom_cursors.has(p_shape)) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ } else {
+ wayland_thread.cursor_set_shape(p_shape);
+ }
+}
+
+DisplayServerWayland::CursorShape DisplayServerWayland::cursor_get_shape() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return cursor_shape;
+}
+
+void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ bool visible = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);
+
+ if (p_cursor.is_valid()) {
+ HashMap<CursorShape, CustomCursor>::Iterator cursor_c = custom_cursors.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->value.rid == p_cursor->get_rid() && cursor_c->value.hotspot == p_hotspot) {
+ // We have a cached cursor. Nice.
+ if (visible) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ }
+
+ return;
+ }
+
+ // We're changing this cursor; we'll have to rebuild it.
+ custom_cursors.erase(p_shape);
+ wayland_thread.cursor_shape_clear_custom_image(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ ERR_FAIL_COND(!texture.is_valid());
+ Size2i texture_size;
+
+ Ref<AtlasTexture> atlas_texture = texture;
+
+ if (atlas_texture.is_valid()) {
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+
+ // NOTE: The Wayland protocol says nothing about cursor size limits, yet if
+ // the texture is larger than 256x256 it won't show at least on sway.
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+ ERR_FAIL_COND(texture_size.height == 0 || texture_size.width == 0);
+
+ Ref<Image> image = texture->get_image();
+ ERR_FAIL_COND(!image.is_valid());
+
+ if (image->is_compressed()) {
+ image = image->duplicate(true);
+ Error err = image->decompress();
+ ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
+ }
+
+ CustomCursor &cursor = custom_cursors[p_shape];
+
+ cursor.rid = p_cursor->get_rid();
+ cursor.hotspot = p_hotspot;
+
+ wayland_thread.cursor_shape_set_custom_image(p_shape, image, p_hotspot);
+
+ if (visible) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ }
+ } else {
+ // Clear cache and reset to default system cursor.
+ if (cursor_shape == p_shape && visible) {
+ wayland_thread.cursor_set_shape(p_shape);
+ }
+
+ if (custom_cursors.has(p_shape)) {
+ custom_cursors.erase(p_shape);
+ }
+
+ wayland_thread.cursor_shape_clear_custom_image(p_shape);
+ }
+}
+
+int DisplayServerWayland::keyboard_get_layout_count() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_layout_count();
+}
+
+int DisplayServerWayland::keyboard_get_current_layout() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_current_layout_index();
+}
+
+void DisplayServerWayland::keyboard_set_current_layout(int p_index) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.keyboard_set_current_layout_index(p_index);
+}
+
+String DisplayServerWayland::keyboard_get_layout_language(int p_index) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // xkbcommon exposes only the layout's name, which looks like it overlaps with
+ // its language.
+ return wayland_thread.keyboard_get_layout_name(p_index);
+}
+
+String DisplayServerWayland::keyboard_get_layout_name(int p_index) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_layout_name(p_index);
+}
+
+Key DisplayServerWayland::keyboard_get_keycode_from_physical(Key p_keycode) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Key key = wayland_thread.keyboard_get_key_from_physical(p_keycode);
+
+ // If not found, fallback to QWERTY.
+ // This should match the behavior of the event pump.
+ if (key == Key::NONE) {
+ return p_keycode;
+ }
+
+ if (key >= Key::A + 32 && key <= Key::Z + 32) {
+ key -= 'a' - 'A';
+ }
+
+ // Make it consistent with the keys returned by `Input`.
+ if (key == Key::BACKTAB) {
+ key = Key::TAB;
+ }
+
+ return key;
+}
+
+void DisplayServerWayland::process_events() {
+ wayland_thread.mutex.lock();
+
+ while (wayland_thread.has_message()) {
+ Ref<WaylandThread::Message> msg = wayland_thread.pop_message();
+
+ Ref<WaylandThread::WindowRectMessage> winrect_msg = msg;
+ if (winrect_msg.is_valid()) {
+ _resize_window(winrect_msg->rect.size);
+ }
+
+ Ref<WaylandThread::WindowEventMessage> winev_msg = msg;
+ if (winev_msg.is_valid()) {
+ _send_window_event(winev_msg->event);
+
+ if (winev_msg->event == WINDOW_EVENT_FOCUS_IN) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+ } else if (winev_msg->event == WINDOW_EVENT_FOCUS_OUT) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+ }
+ }
+
+ Ref<WaylandThread::InputEventMessage> inputev_msg = msg;
+ if (inputev_msg.is_valid()) {
+ Input::get_singleton()->parse_input_event(inputev_msg->event);
+ }
+
+ Ref<WaylandThread::DropFilesEventMessage> dropfiles_msg = msg;
+ if (dropfiles_msg.is_valid()) {
+ WindowData wd = main_window;
+
+ if (wd.drop_files_callback.is_valid()) {
+ wd.drop_files_callback.call(dropfiles_msg->files);
+ }
+ }
+ }
+
+ wayland_thread.keyboard_echo_keys();
+
+ frame = wayland_thread.get_reset_frame();
+
+ wayland_thread.mutex.unlock();
+
+ Input::get_singleton()->flush_buffered_events();
+}
+
+void DisplayServerWayland::release_rendering_thread() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->release_current();
+ }
+#endif
+}
+
+void DisplayServerWayland::make_rendering_thread() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->make_current();
+ }
+#endif
+}
+
+void DisplayServerWayland::swap_buffers() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->swap_buffers();
+ }
+#endif
+}
+
+void DisplayServerWayland::set_context(Context p_context) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("Setting context %d.", p_context));
+
+ context = p_context;
+
+ String app_id = _get_app_id_from_context(p_context);
+ wayland_thread.window_set_app_id(MAIN_WINDOW_ID, app_id);
+}
+
+Vector<String> DisplayServerWayland::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#ifdef VULKAN_ENABLED
+ drivers.push_back("vulkan");
+#endif
+
+#ifdef GLES3_ENABLED
+ drivers.push_back("opengl3");
+#endif
+
+ return drivers;
+}
+
+DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerWayland(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ERR_PRINT("Can't create the Wayland display server.");
+ memdelete(ds);
+
+ return nullptr;
+ }
+ return ds;
+}
+
+DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+#ifdef GLES3_ENABLED
+#ifdef SOWRAP_ENABLED
+#ifdef DEBUG_ENABLED
+ int dylibloader_verbose = 1;
+#else
+ int dylibloader_verbose = 0;
+#endif // DEBUG_ENABLED
+#endif // SOWRAP_ENABLED
+#endif // GLES3_ENABLED
+
+ r_error = ERR_UNAVAILABLE;
+
+ Error thread_err = wayland_thread.init();
+
+ if (thread_err != OK) {
+ r_error = thread_err;
+ ERR_FAIL_MSG("Could not initialize the Wayland thread.");
+ }
+
+ // Input.
+ Input::get_singleton()->set_event_dispatch_function(dispatch_input_events);
+
+#ifdef SPEECHD_ENABLED
+ // Init TTS
+ tts = memnew(TTS_Linux);
+#endif
+
+ rendering_driver = p_rendering_driver;
+
+#ifdef RD_ENABLED
+#ifdef VULKAN_ENABLED
+ if (p_rendering_driver == "vulkan") {
+ context_rd = memnew(VulkanContextWayland);
+ }
+#endif
+
+ if (context_rd) {
+ if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Could not initialize %s", context_rd->get_api_name()));
+ memdelete(context_rd);
+ context_rd = nullptr;
+ r_error = ERR_CANT_CREATE;
+ return;
+ }
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (p_rendering_driver == "opengl3") {
+ if (getenv("DRI_PRIME") == nullptr) {
+ int prime_idx = -1;
+
+ if (getenv("PRIMUS_DISPLAY") ||
+ getenv("PRIMUS_libGLd") ||
+ getenv("PRIMUS_libGLa") ||
+ getenv("PRIMUS_libGL") ||
+ getenv("PRIMUS_LOAD_GLOBAL") ||
+ getenv("BUMBLEBEE_SOCKET") ||
+ getenv("__NV_PRIME_RENDER_OFFLOAD")) {
+ print_verbose("Optirun/primusrun detected. Skipping GPU detection");
+ prime_idx = 0;
+ }
+
+ // Some tools use fake libGL libraries and have them override the real one using
+ // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its
+ // runtime and includes system `/lib` and `/lib64`... so ignore Steam.
+ if (prime_idx == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {
+ String ld_library_path(getenv("LD_LIBRARY_PATH"));
+ Vector<String> libraries = ld_library_path.split(":");
+
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
+ FileAccess::exists(libraries[i] + "/libGL.so")) {
+ print_verbose("Custom libGL override detected. Skipping GPU detection");
+ prime_idx = 0;
+ }
+ }
+ }
+
+ if (prime_idx == -1) {
+ print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
+ prime_idx = DetectPrimeEGL::detect_prime();
+ }
+
+ if (prime_idx) {
+ print_line(vformat("Found discrete GPU, setting DRI_PRIME=%d to use it.", prime_idx));
+ print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
+ setenv("DRI_PRIME", itos(prime_idx).utf8().ptr(), 1);
+ }
+ }
+
+ egl_manager = memnew(EGLManagerWayland);
+
+#ifdef SOWRAP_ENABLED
+ if (initialize_wayland_egl(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland EGL library.");
+ return;
+ }
+#endif // SOWRAP_ENABLED
+
+ if (egl_manager->initialize() != OK) {
+ memdelete(egl_manager);
+ egl_manager = nullptr;
+ r_error = ERR_CANT_CREATE;
+ ERR_FAIL_MSG("Could not initialize GLES3.");
+ }
+
+ RasterizerGLES3::make_current(true);
+ }
+#endif // GLES3_ENABLED
+
+ cursor_set_shape(CURSOR_BUSY);
+
+ WindowData &wd = main_window;
+
+ wd.id = MAIN_WINDOW_ID;
+ wd.mode = p_mode;
+ wd.flags = p_flags;
+ wd.vsync_mode = p_vsync_mode;
+ wd.rect.size = p_resolution;
+ wd.title = "Godot";
+
+ _show_window();
+
+#ifdef RD_ENABLED
+ if (context_rd) {
+ rendering_device = memnew(RenderingDevice);
+ rendering_device->initialize(context_rd);
+
+ RendererCompositorRD::make_current();
+ }
+#endif
+
+#ifdef DBUS_ENABLED
+ portal_desktop = memnew(FreeDesktopPortalDesktop);
+ screensaver = memnew(FreeDesktopScreenSaver);
+#endif
+
+ screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
+
+ r_error = OK;
+}
+
+DisplayServerWayland::~DisplayServerWayland() {
+ // TODO: Multiwindow support.
+ if (main_window.visible) {
+#ifdef VULKAN_ENABLED
+ if (context_rd) {
+ context_rd->window_destroy(MAIN_WINDOW_ID);
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->window_destroy(MAIN_WINDOW_ID);
+ }
+#endif
+ }
+
+#ifdef GLES3_ENABLED
+ if (main_window.wl_egl_window) {
+ wl_egl_window_destroy(main_window.wl_egl_window);
+ }
+#endif
+
+ wayland_thread.destroy();
+
+ // Destroy all drivers.
+#ifdef RD_ENABLED
+ if (rendering_device) {
+ rendering_device->finalize();
+ memdelete(rendering_device);
+ }
+
+ if (context_rd) {
+ memdelete(context_rd);
+ }
+#endif
+
+#ifdef SPEECHD_ENABLED
+ if (tts) {
+ memdelete(tts);
+ }
+#endif
+
+#ifdef DBUS_ENABLED
+ if (portal_desktop) {
+ memdelete(portal_desktop);
+ memdelete(screensaver);
+ }
+#endif
+}
+
+void DisplayServerWayland::register_wayland_driver() {
+ register_create_function("wayland", create_func, get_rendering_drivers_func);
+}
+
+#endif //WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h
new file mode 100644
index 0000000000..3e7f3c4cb4
--- /dev/null
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -0,0 +1,295 @@
+/**************************************************************************/
+/* display_server_wayland.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. */
+/**************************************************************************/
+
+#ifndef DISPLAY_SERVER_WAYLAND_H
+#define DISPLAY_SERVER_WAYLAND_H
+
+#ifdef WAYLAND_ENABLED
+
+#include "wayland/wayland_thread.h"
+
+#ifdef RD_ENABLED
+#include "servers/rendering/rendering_device.h"
+
+#ifdef VULKAN_ENABLED
+#include "wayland/vulkan_context_wayland.h"
+#endif
+
+#endif //RD_ENABLED
+
+#ifdef GLES3_ENABLED
+#include "wayland/egl_manager_wayland.h"
+#endif
+
+#if defined(SPEECHD_ENABLED)
+#include "tts_linux.h"
+#endif
+
+#ifdef DBUS_ENABLED
+#include "freedesktop_portal_desktop.h"
+#include "freedesktop_screensaver.h"
+#endif
+
+#include "core/config/project_settings.h"
+#include "core/input/input.h"
+#include "scene/resources/atlas_texture.h"
+#include "scene/resources/texture.h"
+#include "servers/display_server.h"
+
+#include <limits.h>
+#include <stdio.h>
+
+#undef CursorShape
+
+class DisplayServerWayland : public DisplayServer {
+ // No need to register with GDCLASS, it's platform-specific and nothing is added.
+ struct WindowData {
+ WindowID id;
+
+ Rect2i rect;
+ Size2i max_size;
+ Size2i min_size;
+
+ Rect2i safe_rect;
+
+#ifdef GLES3_ENABLED
+ struct wl_egl_window *wl_egl_window = nullptr;
+#endif
+
+ // Flags whether we have allocated a buffer through the video drivers.
+ bool visible = false;
+
+ DisplayServer::VSyncMode vsync_mode = VSYNC_ENABLED;
+
+ uint32_t flags = 0;
+
+ DisplayServer::WindowMode mode = WINDOW_MODE_WINDOWED;
+
+ Callable rect_changed_callback;
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable drop_files_callback;
+ Callable input_text_callback;
+
+ String title;
+ ObjectID instance_id;
+ };
+
+ struct CustomCursor {
+ RID rid;
+ Point2i hotspot;
+ };
+
+ CursorShape cursor_shape = CURSOR_ARROW;
+ DisplayServer::MouseMode mouse_mode = DisplayServer::MOUSE_MODE_VISIBLE;
+
+ HashMap<CursorShape, CustomCursor> custom_cursors;
+
+ WindowData main_window;
+ WaylandThread wayland_thread;
+
+ Context context;
+
+ bool frame = false;
+ bool emulate_vsync = false;
+
+ String rendering_driver;
+
+#ifdef RD_ENABLED
+ ApiContextRD *context_rd = nullptr;
+ RenderingDevice *rendering_device = nullptr;
+#endif
+
+#ifdef GLES3_ENABLED
+ EGLManagerWayland *egl_manager = nullptr;
+#endif
+
+#ifdef SPEECHD_ENABLED
+ TTS_Linux *tts = nullptr;
+#endif
+
+#if DBUS_ENABLED
+ FreeDesktopPortalDesktop *portal_desktop = nullptr;
+
+ FreeDesktopScreenSaver *screensaver = nullptr;
+ bool screensaver_inhibited = false;
+#endif
+ static String _get_app_id_from_context(Context p_context);
+
+ void _send_window_event(WindowEvent p_event);
+
+ static void dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+ void _resize_window(const Size2i &p_size);
+
+ virtual void _show_window();
+
+public:
+ virtual bool has_feature(Feature p_feature) const override;
+
+ virtual String get_name() const override;
+
+#ifdef SPEECHD_ENABLED
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+#endif
+
+#ifdef DBUS_ENABLED
+ virtual bool is_dark_mode_supported() const override;
+ virtual bool is_dark_mode() const override;
+
+ virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
+#endif
+
+ virtual void mouse_set_mode(MouseMode p_mode) override;
+ virtual MouseMode mouse_get_mode() const override;
+
+ virtual void warp_mouse(const Point2i &p_to) override;
+ virtual Point2i mouse_get_position() const override;
+ virtual BitField<MouseButtonMask> mouse_get_button_state() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+ virtual Ref<Image> clipboard_get_image() const override;
+ virtual void clipboard_set_primary(const String &p_text) override;
+ virtual String clipboard_get_primary() const override;
+
+ virtual int get_screen_count() const override;
+ virtual int get_primary_screen() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual Point2i window_get_position_with_decorations(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override;
+
+ virtual void window_set_transient(WindowID p_window_id, WindowID p_parent) override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_size_with_decorations(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_move_to_foreground(WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_can_draw(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_window_id) const override;
+
+ virtual void cursor_set_shape(CursorShape p_shape) override;
+ virtual CursorShape cursor_get_shape() const override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) 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;
+ virtual String keyboard_get_layout_language(int p_index) const override;
+ virtual String keyboard_get_layout_name(int p_index) const override;
+ virtual Key keyboard_get_keycode_from_physical(Key p_keycode) const override;
+
+ virtual void process_events() override;
+
+ virtual void release_rendering_thread() override;
+ virtual void make_rendering_thread() override;
+ virtual void swap_buffers() override;
+
+ virtual void set_context(Context p_context) override;
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ static void register_wayland_driver();
+
+ DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerWayland();
+};
+
+#endif // WAYLAND_ENABLED
+
+#endif // DISPLAY_SERVER_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c
new file mode 100644
index 0000000000..eaf43215ce
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c
@@ -0,0 +1,453 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./generate-wrapper.py 0.3 on 2022-12-12 10:55:19
+// flags: ./generate-wrapper.py --include /usr/include/libdecor-0/libdecor.h --sys-include <libdecor-0/libdecor.h> --soname libdecor-0.so.0 --init-name libdecor --output-header libdecor-so_wrap.h --output-implementation libdecor-so_wrap.c --omit-prefix wl_
+//
+// EDIT: This has been handpatched to properly report the pointer type of the window_state argument of libdecor_configuration_get_window_state.
+#include <stdint.h>
+
+#define libdecor_unref libdecor_unref_dylibloader_orig_libdecor
+#define libdecor_new libdecor_new_dylibloader_orig_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_orig_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_orig_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_orig_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_orig_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_orig_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_orig_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_orig_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_orig_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_orig_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_orig_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_orig_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_orig_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_orig_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_orig_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_orig_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_orig_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_orig_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_orig_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_orig_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_orig_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_orig_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_orig_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_orig_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_orig_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_orig_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_orig_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_orig_libdecor
+#include <libdecor-0/libdecor.h>
+#undef libdecor_unref
+#undef libdecor_new
+#undef libdecor_get_fd
+#undef libdecor_dispatch
+#undef libdecor_decorate
+#undef libdecor_frame_ref
+#undef libdecor_frame_unref
+#undef libdecor_frame_set_visibility
+#undef libdecor_frame_is_visible
+#undef libdecor_frame_set_parent
+#undef libdecor_frame_set_title
+#undef libdecor_frame_get_title
+#undef libdecor_frame_set_app_id
+#undef libdecor_frame_set_capabilities
+#undef libdecor_frame_unset_capabilities
+#undef libdecor_frame_has_capability
+#undef libdecor_frame_show_window_menu
+#undef libdecor_frame_popup_grab
+#undef libdecor_frame_popup_ungrab
+#undef libdecor_frame_translate_coordinate
+#undef libdecor_frame_set_min_content_size
+#undef libdecor_frame_set_max_content_size
+#undef libdecor_frame_resize
+#undef libdecor_frame_move
+#undef libdecor_frame_commit
+#undef libdecor_frame_set_minimized
+#undef libdecor_frame_set_maximized
+#undef libdecor_frame_unset_maximized
+#undef libdecor_frame_set_fullscreen
+#undef libdecor_frame_unset_fullscreen
+#undef libdecor_frame_is_floating
+#undef libdecor_frame_close
+#undef libdecor_frame_map
+#undef libdecor_frame_get_xdg_surface
+#undef libdecor_frame_get_xdg_toplevel
+#undef libdecor_state_new
+#undef libdecor_state_free
+#undef libdecor_configuration_get_content_size
+#undef libdecor_configuration_get_window_state
+#include <dlfcn.h>
+#include <stdio.h>
+void (*libdecor_unref_dylibloader_wrapper_libdecor)(struct libdecor*);
+struct libdecor* (*libdecor_new_dylibloader_wrapper_libdecor)(struct wl_display*,struct libdecor_interface*);
+int (*libdecor_get_fd_dylibloader_wrapper_libdecor)(struct libdecor*);
+int (*libdecor_dispatch_dylibloader_wrapper_libdecor)(struct libdecor*, int);
+struct libdecor_frame* (*libdecor_decorate_dylibloader_wrapper_libdecor)(struct libdecor*,struct wl_surface*,struct libdecor_frame_interface*, void*);
+void (*libdecor_frame_ref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_unref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_visibility_dylibloader_wrapper_libdecor)(struct libdecor_frame*, bool);
+bool (*libdecor_frame_is_visible_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_parent_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_frame*);
+void (*libdecor_frame_set_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+const char* (*libdecor_frame_get_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_app_id_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+void (*libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+bool (*libdecor_frame_has_capability_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+void (*libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t, int, int);
+void (*libdecor_frame_popup_grab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int, int*, int*);
+void (*libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+void (*libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+void (*libdecor_frame_resize_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t,enum libdecor_resize_edge);
+void (*libdecor_frame_move_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t);
+void (*libdecor_frame_commit_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_state*,struct libdecor_configuration*);
+void (*libdecor_frame_set_minimized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_output*);
+void (*libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+bool (*libdecor_frame_is_floating_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_close_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_map_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct xdg_surface* (*libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct xdg_toplevel* (*libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct libdecor_state* (*libdecor_state_new_dylibloader_wrapper_libdecor)( int, int);
+void (*libdecor_state_free_dylibloader_wrapper_libdecor)(struct libdecor_state*);
+bool (*libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,struct libdecor_frame*, int*, int*);
+bool (*libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,enum libdecor_window_state*);
+int initialize_libdecor(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libdecor-0.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// libdecor_unref
+ *(void **) (&libdecor_unref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_new
+ *(void **) (&libdecor_new_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_new");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_get_fd
+ *(void **) (&libdecor_get_fd_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_get_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_dispatch
+ *(void **) (&libdecor_dispatch_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_dispatch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_decorate
+ *(void **) (&libdecor_decorate_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_decorate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_ref
+ *(void **) (&libdecor_frame_ref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_ref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unref
+ *(void **) (&libdecor_frame_unref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_visibility
+ *(void **) (&libdecor_frame_set_visibility_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_visibility");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_is_visible
+ *(void **) (&libdecor_frame_is_visible_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_is_visible");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_parent
+ *(void **) (&libdecor_frame_set_parent_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_parent");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_title
+ *(void **) (&libdecor_frame_set_title_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_title");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_title
+ *(void **) (&libdecor_frame_get_title_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_title");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_app_id
+ *(void **) (&libdecor_frame_set_app_id_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_app_id");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_capabilities
+ *(void **) (&libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_capabilities");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_capabilities
+ *(void **) (&libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_capabilities");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_has_capability
+ *(void **) (&libdecor_frame_has_capability_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_has_capability");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_show_window_menu
+ *(void **) (&libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_show_window_menu");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_popup_grab
+ *(void **) (&libdecor_frame_popup_grab_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_popup_grab");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_popup_ungrab
+ *(void **) (&libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_popup_ungrab");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_translate_coordinate
+ *(void **) (&libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_translate_coordinate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_min_content_size
+ *(void **) (&libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_min_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_max_content_size
+ *(void **) (&libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_max_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_resize
+ *(void **) (&libdecor_frame_resize_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_resize");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_move
+ *(void **) (&libdecor_frame_move_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_move");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_commit
+ *(void **) (&libdecor_frame_commit_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_commit");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_minimized
+ *(void **) (&libdecor_frame_set_minimized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_minimized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_maximized
+ *(void **) (&libdecor_frame_set_maximized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_maximized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_maximized
+ *(void **) (&libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_maximized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_fullscreen
+ *(void **) (&libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_fullscreen");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_fullscreen
+ *(void **) (&libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_fullscreen");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_is_floating
+ *(void **) (&libdecor_frame_is_floating_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_is_floating");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_close
+ *(void **) (&libdecor_frame_close_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_close");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_map
+ *(void **) (&libdecor_frame_map_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_map");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_xdg_surface
+ *(void **) (&libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_xdg_surface");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_xdg_toplevel
+ *(void **) (&libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_xdg_toplevel");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_state_new
+ *(void **) (&libdecor_state_new_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_state_new");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_state_free
+ *(void **) (&libdecor_state_free_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_state_free");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_configuration_get_content_size
+ *(void **) (&libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_configuration_get_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_configuration_get_window_state
+ *(void **) (&libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_configuration_get_window_state");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h
new file mode 100644
index 0000000000..bf3f520008
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h
@@ -0,0 +1,175 @@
+#ifndef DYLIBLOAD_WRAPPER_LIBDECOR
+#define DYLIBLOAD_WRAPPER_LIBDECOR
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./generate-wrapper.py 0.3 on 2022-12-12 10:55:19
+// flags: ./generate-wrapper.py --include /usr/include/libdecor-0/libdecor.h --sys-include <libdecor-0/libdecor.h> --soname libdecor-0.so.0 --init-name libdecor --output-header libdecor-so_wrap.h --output-implementation libdecor-so_wrap.c --omit-prefix wl_
+//
+// EDIT: This has been handpatched to properly report the pointer type of the window_state argument of libdecor_configuration_get_window_state.
+#include <stdint.h>
+
+#define libdecor_unref libdecor_unref_dylibloader_orig_libdecor
+#define libdecor_new libdecor_new_dylibloader_orig_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_orig_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_orig_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_orig_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_orig_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_orig_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_orig_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_orig_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_orig_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_orig_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_orig_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_orig_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_orig_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_orig_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_orig_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_orig_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_orig_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_orig_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_orig_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_orig_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_orig_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_orig_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_orig_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_orig_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_orig_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_orig_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_orig_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_orig_libdecor
+#include <libdecor-0/libdecor.h>
+#undef libdecor_unref
+#undef libdecor_new
+#undef libdecor_get_fd
+#undef libdecor_dispatch
+#undef libdecor_decorate
+#undef libdecor_frame_ref
+#undef libdecor_frame_unref
+#undef libdecor_frame_set_visibility
+#undef libdecor_frame_is_visible
+#undef libdecor_frame_set_parent
+#undef libdecor_frame_set_title
+#undef libdecor_frame_get_title
+#undef libdecor_frame_set_app_id
+#undef libdecor_frame_set_capabilities
+#undef libdecor_frame_unset_capabilities
+#undef libdecor_frame_has_capability
+#undef libdecor_frame_show_window_menu
+#undef libdecor_frame_popup_grab
+#undef libdecor_frame_popup_ungrab
+#undef libdecor_frame_translate_coordinate
+#undef libdecor_frame_set_min_content_size
+#undef libdecor_frame_set_max_content_size
+#undef libdecor_frame_resize
+#undef libdecor_frame_move
+#undef libdecor_frame_commit
+#undef libdecor_frame_set_minimized
+#undef libdecor_frame_set_maximized
+#undef libdecor_frame_unset_maximized
+#undef libdecor_frame_set_fullscreen
+#undef libdecor_frame_unset_fullscreen
+#undef libdecor_frame_is_floating
+#undef libdecor_frame_close
+#undef libdecor_frame_map
+#undef libdecor_frame_get_xdg_surface
+#undef libdecor_frame_get_xdg_toplevel
+#undef libdecor_state_new
+#undef libdecor_state_free
+#undef libdecor_configuration_get_content_size
+#undef libdecor_configuration_get_window_state
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define libdecor_unref libdecor_unref_dylibloader_wrapper_libdecor
+#define libdecor_new libdecor_new_dylibloader_wrapper_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_wrapper_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_wrapper_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_wrapper_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_wrapper_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_wrapper_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_wrapper_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_wrapper_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_wrapper_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_wrapper_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_wrapper_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_wrapper_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_wrapper_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_wrapper_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor
+extern void (*libdecor_unref_dylibloader_wrapper_libdecor)(struct libdecor*);
+extern struct libdecor* (*libdecor_new_dylibloader_wrapper_libdecor)(struct wl_display*,struct libdecor_interface*);
+extern int (*libdecor_get_fd_dylibloader_wrapper_libdecor)(struct libdecor*);
+extern int (*libdecor_dispatch_dylibloader_wrapper_libdecor)(struct libdecor*, int);
+extern struct libdecor_frame* (*libdecor_decorate_dylibloader_wrapper_libdecor)(struct libdecor*,struct wl_surface*,struct libdecor_frame_interface*, void*);
+extern void (*libdecor_frame_ref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_unref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_visibility_dylibloader_wrapper_libdecor)(struct libdecor_frame*, bool);
+extern bool (*libdecor_frame_is_visible_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_parent_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_frame*);
+extern void (*libdecor_frame_set_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern const char* (*libdecor_frame_get_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_app_id_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern void (*libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern bool (*libdecor_frame_has_capability_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern void (*libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t, int, int);
+extern void (*libdecor_frame_popup_grab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int, int*, int*);
+extern void (*libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+extern void (*libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+extern void (*libdecor_frame_resize_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t,enum libdecor_resize_edge);
+extern void (*libdecor_frame_move_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t);
+extern void (*libdecor_frame_commit_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_state*,struct libdecor_configuration*);
+extern void (*libdecor_frame_set_minimized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_output*);
+extern void (*libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern bool (*libdecor_frame_is_floating_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_close_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_map_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct xdg_surface* (*libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct xdg_toplevel* (*libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct libdecor_state* (*libdecor_state_new_dylibloader_wrapper_libdecor)( int, int);
+extern void (*libdecor_state_free_dylibloader_wrapper_libdecor)(struct libdecor_state*);
+extern bool (*libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,struct libdecor_frame*, int*, int*);
+extern bool (*libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,enum libdecor_window_state*);
+int initialize_libdecor(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
new file mode 100644
index 0000000000..0cd8e56a77
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
@@ -0,0 +1,607 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:36:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h" --soname libwayland-client.so.0 --init-name wayland_client --output-header platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
+//
+// NOTE: This has been hand-patched to workaround some issues.
+#include <stdint.h>
+
+#define wl_list_init wl_list_init_dylibloader_orig_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_orig_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_orig_wayland_client
+#define wl_list_length wl_list_length_dylibloader_orig_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_orig_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_orig_wayland_client
+#define wl_array_init wl_array_init_dylibloader_orig_wayland_client
+#define wl_array_release wl_array_release_dylibloader_orig_wayland_client
+#define wl_array_add wl_array_add_dylibloader_orig_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_orig_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_orig_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_orig_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_orig_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_orig_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_orig_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_orig_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_orig_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_orig_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_orig_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_orig_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_orig_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_orig_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_orig_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_orig_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_orig_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_orig_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_orig_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_orig_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_orig_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_orig_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_orig_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_orig_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_orig_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_orig_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_orig_wayland_client
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h"
+#undef wl_list_init
+#undef wl_list_insert
+#undef wl_list_remove
+#undef wl_list_length
+#undef wl_list_empty
+#undef wl_list_insert_list
+#undef wl_array_init
+#undef wl_array_release
+#undef wl_array_add
+#undef wl_array_copy
+#undef wl_event_queue_destroy
+#undef wl_proxy_marshal_flags
+#undef wl_proxy_marshal_array_flags
+#undef wl_proxy_marshal
+#undef wl_proxy_marshal_array
+#undef wl_proxy_create
+#undef wl_proxy_create_wrapper
+#undef wl_proxy_wrapper_destroy
+#undef wl_proxy_marshal_constructor
+#undef wl_proxy_marshal_constructor_versioned
+#undef wl_proxy_marshal_array_constructor
+#undef wl_proxy_marshal_array_constructor_versioned
+#undef wl_proxy_destroy
+#undef wl_proxy_add_listener
+#undef wl_proxy_get_listener
+#undef wl_proxy_add_dispatcher
+#undef wl_proxy_set_user_data
+#undef wl_proxy_get_user_data
+#undef wl_proxy_get_version
+#undef wl_proxy_get_id
+#undef wl_proxy_set_tag
+#undef wl_proxy_get_tag
+#undef wl_proxy_get_class
+#undef wl_proxy_set_queue
+#undef wl_display_connect
+#undef wl_display_connect_to_fd
+#undef wl_display_disconnect
+#undef wl_display_get_fd
+#undef wl_display_dispatch
+#undef wl_display_dispatch_queue
+#undef wl_display_dispatch_queue_pending
+#undef wl_display_dispatch_pending
+#undef wl_display_get_error
+#undef wl_display_get_protocol_error
+#undef wl_display_flush
+#undef wl_display_roundtrip_queue
+#undef wl_display_roundtrip
+#undef wl_display_create_queue
+#undef wl_display_prepare_read_queue
+#undef wl_display_prepare_read
+#undef wl_display_cancel_read
+#undef wl_display_read_events
+#undef wl_log_set_handler_client
+#include <dlfcn.h>
+#include <stdio.h>
+void (*wl_list_init_dylibloader_wrapper_wayland_client)(struct wl_list*);
+void (*wl_list_insert_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+void (*wl_list_remove_dylibloader_wrapper_wayland_client)(struct wl_list*);
+int (*wl_list_length_dylibloader_wrapper_wayland_client)(struct wl_list*);
+int (*wl_list_empty_dylibloader_wrapper_wayland_client)(struct wl_list*);
+void (*wl_list_insert_list_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+void (*wl_array_init_dylibloader_wrapper_wayland_client)(struct wl_array*);
+void (*wl_array_release_dylibloader_wrapper_wayland_client)(struct wl_array*);
+void* (*wl_array_add_dylibloader_wrapper_wayland_client)(struct wl_array*, size_t);
+int (*wl_array_copy_dylibloader_wrapper_wayland_client)(struct wl_array*,struct wl_array*);
+void (*wl_event_queue_destroy_dylibloader_wrapper_wayland_client)(struct wl_event_queue*);
+struct wl_proxy* (*wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,...);
+struct wl_proxy* (*wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,union wl_argument);
+void (*wl_proxy_marshal_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,...);
+void (*wl_proxy_marshal_array_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument);
+struct wl_proxy* (*wl_proxy_create_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const struct wl_interface*);
+void* (*wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client)( void*);
+void (*wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client)( void*);
+struct wl_proxy* (*wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*,...);
+struct wl_proxy* (*wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t,...);
+struct wl_proxy* (*wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*);
+struct wl_proxy* (*wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*, uint32_t);
+void (*wl_proxy_destroy_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+int (*wl_proxy_add_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void(**)(void), void*);
+const void* (*wl_proxy_get_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+int (*wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client)(struct wl_proxy*, wl_dispatcher_func_t,const void*, void*);
+void (*wl_proxy_set_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void*);
+void* (*wl_proxy_get_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+uint32_t (*wl_proxy_get_version_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+uint32_t (*wl_proxy_get_id_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+void (*wl_proxy_set_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const char**);
+const char** (*wl_proxy_get_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+const char* (*wl_proxy_get_class_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+void (*wl_proxy_set_queue_dylibloader_wrapper_wayland_client)(struct wl_proxy*,struct wl_event_queue*);
+struct wl_display* (*wl_display_connect_dylibloader_wrapper_wayland_client)(const char*);
+struct wl_display* (*wl_display_connect_to_fd_dylibloader_wrapper_wayland_client)( int);
+void (*wl_display_disconnect_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_get_fd_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_dispatch_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_dispatch_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_dispatch_pending_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_get_error_dylibloader_wrapper_wayland_client)(struct wl_display*);
+uint32_t (*wl_display_get_protocol_error_dylibloader_wrapper_wayland_client)(struct wl_display*,const struct wl_interface**, uint32_t*);
+int (*wl_display_flush_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_roundtrip_dylibloader_wrapper_wayland_client)(struct wl_display*);
+struct wl_event_queue* (*wl_display_create_queue_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_prepare_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+void (*wl_display_cancel_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_read_events_dylibloader_wrapper_wayland_client)(struct wl_display*);
+void (*wl_log_set_handler_client_dylibloader_wrapper_wayland_client)( wl_log_func_t);
+int initialize_wayland_client(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-client.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_list_init
+ *(void **) (&wl_list_init_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_init");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_insert
+ *(void **) (&wl_list_insert_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_insert");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_remove
+ *(void **) (&wl_list_remove_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_remove");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_length
+ *(void **) (&wl_list_length_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_length");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_empty
+ *(void **) (&wl_list_empty_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_empty");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_insert_list
+ *(void **) (&wl_list_insert_list_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_insert_list");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_init
+ *(void **) (&wl_array_init_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_init");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_release
+ *(void **) (&wl_array_release_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_release");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_add
+ *(void **) (&wl_array_add_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_add");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_copy
+ *(void **) (&wl_array_copy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_copy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_event_queue_destroy
+ *(void **) (&wl_event_queue_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_event_queue_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_flags
+ *(void **) (&wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_flags");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_flags
+ *(void **) (&wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_flags");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal
+ *(void **) (&wl_proxy_marshal_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array
+ *(void **) (&wl_proxy_marshal_array_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_create
+ *(void **) (&wl_proxy_create_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_create");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_create_wrapper
+ *(void **) (&wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_create_wrapper");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_wrapper_destroy
+ *(void **) (&wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_wrapper_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_constructor
+ *(void **) (&wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_constructor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_constructor_versioned
+ *(void **) (&wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_constructor_versioned");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_constructor
+ *(void **) (&wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_constructor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_constructor_versioned
+ *(void **) (&wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_constructor_versioned");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_destroy
+ *(void **) (&wl_proxy_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_add_listener
+ *(void **) (&wl_proxy_add_listener_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_add_listener");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_listener
+ *(void **) (&wl_proxy_get_listener_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_listener");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_add_dispatcher
+ *(void **) (&wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_add_dispatcher");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_user_data
+ *(void **) (&wl_proxy_set_user_data_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_user_data");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_user_data
+ *(void **) (&wl_proxy_get_user_data_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_user_data");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_version
+ *(void **) (&wl_proxy_get_version_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_version");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_id
+ *(void **) (&wl_proxy_get_id_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_id");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_tag
+ *(void **) (&wl_proxy_set_tag_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_tag");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_tag
+ *(void **) (&wl_proxy_get_tag_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_tag");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_class
+ *(void **) (&wl_proxy_get_class_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_class");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_queue
+ *(void **) (&wl_proxy_set_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_connect
+ *(void **) (&wl_display_connect_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_connect");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_connect_to_fd
+ *(void **) (&wl_display_connect_to_fd_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_connect_to_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_disconnect
+ *(void **) (&wl_display_disconnect_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_disconnect");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_fd
+ *(void **) (&wl_display_get_fd_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch
+ *(void **) (&wl_display_dispatch_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_queue
+ *(void **) (&wl_display_dispatch_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_queue_pending
+ *(void **) (&wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_queue_pending");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_pending
+ *(void **) (&wl_display_dispatch_pending_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_pending");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_error
+ *(void **) (&wl_display_get_error_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_error");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_protocol_error
+ *(void **) (&wl_display_get_protocol_error_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_protocol_error");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_flush
+ *(void **) (&wl_display_flush_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_flush");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_roundtrip_queue
+ *(void **) (&wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_roundtrip_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_roundtrip
+ *(void **) (&wl_display_roundtrip_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_roundtrip");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_create_queue
+ *(void **) (&wl_display_create_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_create_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_prepare_read_queue
+ *(void **) (&wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_prepare_read_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_prepare_read
+ *(void **) (&wl_display_prepare_read_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_prepare_read");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_cancel_read
+ *(void **) (&wl_display_cancel_read_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_cancel_read");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_read_events
+ *(void **) (&wl_display_read_events_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_read_events");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_log_set_handler_client
+ *(void **) (&wl_log_set_handler_client_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_log_set_handler_client");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h
new file mode 100644
index 0000000000..86c51573a5
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h
@@ -0,0 +1,231 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_CLIENT
+#define DYLIBLOAD_WRAPPER_WAYLAND_CLIENT
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:36:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h" --soname libwayland-client.so.0 --init-name wayland_client --output-header platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
+//
+// NOTE: This has been hand-patched to workaround some issues.
+#include <stdint.h>
+
+#define wl_list_init wl_list_init_dylibloader_orig_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_orig_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_orig_wayland_client
+#define wl_list_length wl_list_length_dylibloader_orig_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_orig_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_orig_wayland_client
+#define wl_array_init wl_array_init_dylibloader_orig_wayland_client
+#define wl_array_release wl_array_release_dylibloader_orig_wayland_client
+#define wl_array_add wl_array_add_dylibloader_orig_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_orig_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_orig_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_orig_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_orig_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_orig_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_orig_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_orig_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_orig_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_orig_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_orig_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_orig_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_orig_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_orig_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_orig_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_orig_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_orig_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_orig_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_orig_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_orig_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_orig_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_orig_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_orig_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_orig_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_orig_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_orig_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_orig_wayland_client
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h"
+#undef wl_list_init
+#undef wl_list_insert
+#undef wl_list_remove
+#undef wl_list_length
+#undef wl_list_empty
+#undef wl_list_insert_list
+#undef wl_array_init
+#undef wl_array_release
+#undef wl_array_add
+#undef wl_array_copy
+#undef wl_event_queue_destroy
+#undef wl_proxy_marshal_flags
+#undef wl_proxy_marshal_array_flags
+#undef wl_proxy_marshal
+#undef wl_proxy_marshal_array
+#undef wl_proxy_create
+#undef wl_proxy_create_wrapper
+#undef wl_proxy_wrapper_destroy
+#undef wl_proxy_marshal_constructor
+#undef wl_proxy_marshal_constructor_versioned
+#undef wl_proxy_marshal_array_constructor
+#undef wl_proxy_marshal_array_constructor_versioned
+#undef wl_proxy_destroy
+#undef wl_proxy_add_listener
+#undef wl_proxy_get_listener
+#undef wl_proxy_add_dispatcher
+#undef wl_proxy_set_user_data
+#undef wl_proxy_get_user_data
+#undef wl_proxy_get_version
+#undef wl_proxy_get_id
+#undef wl_proxy_set_tag
+#undef wl_proxy_get_tag
+#undef wl_proxy_get_class
+#undef wl_proxy_set_queue
+#undef wl_display_connect
+#undef wl_display_connect_to_fd
+#undef wl_display_disconnect
+#undef wl_display_get_fd
+#undef wl_display_dispatch
+#undef wl_display_dispatch_queue
+#undef wl_display_dispatch_queue_pending
+#undef wl_display_dispatch_pending
+#undef wl_display_get_error
+#undef wl_display_get_protocol_error
+#undef wl_display_flush
+#undef wl_display_roundtrip_queue
+#undef wl_display_roundtrip
+#undef wl_display_create_queue
+#undef wl_display_prepare_read_queue
+#undef wl_display_prepare_read
+#undef wl_display_cancel_read
+#undef wl_display_read_events
+#undef wl_log_set_handler_client
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_list_init wl_list_init_dylibloader_wrapper_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_wrapper_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_wrapper_wayland_client
+#define wl_list_length wl_list_length_dylibloader_wrapper_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_wrapper_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_wrapper_wayland_client
+#define wl_array_init wl_array_init_dylibloader_wrapper_wayland_client
+#define wl_array_release wl_array_release_dylibloader_wrapper_wayland_client
+#define wl_array_add wl_array_add_dylibloader_wrapper_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_wrapper_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_wrapper_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_wrapper_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_wrapper_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_wrapper_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_wrapper_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_wrapper_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_wrapper_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_wrapper_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_wrapper_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_wrapper_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_wrapper_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_wrapper_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_wrapper_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_wrapper_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_wrapper_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_wrapper_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_wrapper_wayland_client
+extern void (*wl_list_init_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern void (*wl_list_insert_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+extern void (*wl_list_remove_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern int (*wl_list_length_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern int (*wl_list_empty_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern void (*wl_list_insert_list_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+extern void (*wl_array_init_dylibloader_wrapper_wayland_client)(struct wl_array*);
+extern void (*wl_array_release_dylibloader_wrapper_wayland_client)(struct wl_array*);
+extern void* (*wl_array_add_dylibloader_wrapper_wayland_client)(struct wl_array*, size_t);
+extern int (*wl_array_copy_dylibloader_wrapper_wayland_client)(struct wl_array*,struct wl_array*);
+extern void (*wl_event_queue_destroy_dylibloader_wrapper_wayland_client)(struct wl_event_queue*);
+extern struct wl_proxy* (*wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,...);
+extern struct wl_proxy* (*wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,union wl_argument);
+extern void (*wl_proxy_marshal_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,...);
+extern void (*wl_proxy_marshal_array_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument);
+extern struct wl_proxy* (*wl_proxy_create_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const struct wl_interface*);
+extern void* (*wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client)( void*);
+extern void (*wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client)( void*);
+extern struct wl_proxy* (*wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*,...);
+extern struct wl_proxy* (*wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t,...);
+extern struct wl_proxy* (*wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*);
+extern struct wl_proxy* (*wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*, uint32_t);
+extern void (*wl_proxy_destroy_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern int (*wl_proxy_add_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void(**)(void), void*);
+extern const void* (*wl_proxy_get_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern int (*wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client)(struct wl_proxy*, wl_dispatcher_func_t,const void*, void*);
+extern void (*wl_proxy_set_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void*);
+extern void* (*wl_proxy_get_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern uint32_t (*wl_proxy_get_version_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern uint32_t (*wl_proxy_get_id_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern void (*wl_proxy_set_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const char**);
+extern const char** (*wl_proxy_get_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern const char* (*wl_proxy_get_class_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern void (*wl_proxy_set_queue_dylibloader_wrapper_wayland_client)(struct wl_proxy*,struct wl_event_queue*);
+extern struct wl_display* (*wl_display_connect_dylibloader_wrapper_wayland_client)(const char*);
+extern struct wl_display* (*wl_display_connect_to_fd_dylibloader_wrapper_wayland_client)( int);
+extern void (*wl_display_disconnect_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_get_fd_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_dispatch_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_dispatch_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_dispatch_pending_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_get_error_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern uint32_t (*wl_display_get_protocol_error_dylibloader_wrapper_wayland_client)(struct wl_display*,const struct wl_interface**, uint32_t*);
+extern int (*wl_display_flush_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_roundtrip_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern struct wl_event_queue* (*wl_display_create_queue_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_prepare_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern void (*wl_display_cancel_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_read_events_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern void (*wl_log_set_handler_client_dylibloader_wrapper_wayland_client)( wl_log_func_t);
+int initialize_wayland_client(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
new file mode 100644
index 0000000000..61ccfbf4f9
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
@@ -0,0 +1,89 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:46:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h" --soname libwayland-cursor.so.0 --init-name wayland_cursor --output-header platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_orig_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_orig_wayland_cursor
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h"
+#undef wl_cursor_theme_load
+#undef wl_cursor_theme_destroy
+#undef wl_cursor_theme_get_cursor
+#undef wl_cursor_image_get_buffer
+#undef wl_cursor_frame
+#undef wl_cursor_frame_and_duration
+#include <dlfcn.h>
+#include <stdio.h>
+struct wl_cursor_theme* (*wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor)(const char*, int,struct wl_shm*);
+void (*wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*);
+struct wl_cursor* (*wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*,const char*);
+struct wl_buffer* (*wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_image*);
+int (*wl_cursor_frame_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t);
+int (*wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t, uint32_t*);
+int initialize_wayland_cursor(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-cursor.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_cursor_theme_load
+ *(void **) (&wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_load");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_theme_destroy
+ *(void **) (&wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_theme_get_cursor
+ *(void **) (&wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_get_cursor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_image_get_buffer
+ *(void **) (&wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_image_get_buffer");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_frame
+ *(void **) (&wl_cursor_frame_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_frame");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_frame_and_duration
+ *(void **) (&wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_frame_and_duration");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h
new file mode 100644
index 0000000000..43520e74a1
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h
@@ -0,0 +1,42 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_CURSOR
+#define DYLIBLOAD_WRAPPER_WAYLAND_CURSOR
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:46:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h" --soname libwayland-cursor.so.0 --init-name wayland_cursor --output-header platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_orig_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_orig_wayland_cursor
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h"
+#undef wl_cursor_theme_load
+#undef wl_cursor_theme_destroy
+#undef wl_cursor_theme_get_cursor
+#undef wl_cursor_image_get_buffer
+#undef wl_cursor_frame
+#undef wl_cursor_frame_and_duration
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor
+extern struct wl_cursor_theme* (*wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor)(const char*, int,struct wl_shm*);
+extern void (*wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*);
+extern struct wl_cursor* (*wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*,const char*);
+extern struct wl_buffer* (*wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_image*);
+extern int (*wl_cursor_frame_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t);
+extern int (*wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t, uint32_t*);
+int initialize_wayland_cursor(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
new file mode 100644
index 0000000000..122241d241
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
@@ -0,0 +1,67 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:49:37
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h" --soname libwayland-egl.so.1 --init-name wayland_egl --output-header platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_egl_window_create wl_egl_window_create_dylibloader_orig_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_orig_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_orig_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_orig_wayland_egl
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h"
+#undef wl_egl_window_create
+#undef wl_egl_window_destroy
+#undef wl_egl_window_resize
+#undef wl_egl_window_get_attached_size
+#include <dlfcn.h>
+#include <stdio.h>
+struct wl_egl_window* (*wl_egl_window_create_dylibloader_wrapper_wayland_egl)(struct wl_surface*, int, int);
+void (*wl_egl_window_destroy_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*);
+void (*wl_egl_window_resize_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int, int, int, int);
+void (*wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int*, int*);
+int initialize_wayland_egl(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-egl.so.1", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_egl_window_create
+ *(void **) (&wl_egl_window_create_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_create");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_destroy
+ *(void **) (&wl_egl_window_destroy_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_resize
+ *(void **) (&wl_egl_window_resize_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_resize");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_get_attached_size
+ *(void **) (&wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_get_attached_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h
new file mode 100644
index 0000000000..c2643f973f
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h
@@ -0,0 +1,34 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_EGL
+#define DYLIBLOAD_WRAPPER_WAYLAND_EGL
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:49:37
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h" --soname libwayland-egl.so.1 --init-name wayland_egl --output-header platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_egl_window_create wl_egl_window_create_dylibloader_orig_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_orig_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_orig_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_orig_wayland_egl
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h"
+#undef wl_egl_window_create
+#undef wl_egl_window_destroy
+#undef wl_egl_window_resize
+#undef wl_egl_window_get_attached_size
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_egl_window_create wl_egl_window_create_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl
+extern struct wl_egl_window* (*wl_egl_window_create_dylibloader_wrapper_wayland_egl)(struct wl_surface*, int, int);
+extern void (*wl_egl_window_destroy_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*);
+extern void (*wl_egl_window_resize_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int, int, int, int);
+extern void (*wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int*, int*);
+int initialize_wayland_egl(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/egl_manager_wayland.cpp b/platform/linuxbsd/wayland/egl_manager_wayland.cpp
new file mode 100644
index 0000000000..6cf24277a0
--- /dev/null
+++ b/platform/linuxbsd/wayland/egl_manager_wayland.cpp
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* egl_manager_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "egl_manager_wayland.h"
+
+#ifdef WAYLAND_ENABLED
+#ifdef EGL_ENABLED
+#ifdef GLES3_ENABLED
+
+const char *EGLManagerWayland::_get_platform_extension_name() const {
+ return "EGL_KHR_platform_wayland";
+}
+
+EGLenum EGLManagerWayland::_get_platform_extension_enum() const {
+ return EGL_PLATFORM_WAYLAND_KHR;
+}
+
+EGLenum EGLManagerWayland::_get_platform_api_enum() const {
+ return EGL_OPENGL_API;
+}
+
+Vector<EGLAttrib> EGLManagerWayland::_get_platform_display_attributes() const {
+ return Vector<EGLAttrib>();
+}
+
+Vector<EGLint> EGLManagerWayland::_get_platform_context_attribs() const {
+ Vector<EGLint> ret;
+ ret.push_back(EGL_CONTEXT_MAJOR_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_CONTEXT_MINOR_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_NONE);
+
+ return ret;
+}
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+#endif // WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/egl_manager_wayland.h b/platform/linuxbsd/wayland/egl_manager_wayland.h
new file mode 100644
index 0000000000..551c126760
--- /dev/null
+++ b/platform/linuxbsd/wayland/egl_manager_wayland.h
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* egl_manager_wayland.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. */
+/**************************************************************************/
+
+#ifndef EGL_MANAGER_WAYLAND_H
+#define EGL_MANAGER_WAYLAND_H
+
+#ifdef WAYLAND_ENABLED
+#ifdef EGL_ENABLED
+#ifdef GLES3_ENABLED
+
+#include "drivers/egl/egl_manager.h"
+
+class EGLManagerWayland : public EGLManager {
+public:
+ virtual const char *_get_platform_extension_name() const override;
+ virtual EGLenum _get_platform_extension_enum() const override;
+ virtual EGLenum _get_platform_api_enum() const override;
+ virtual Vector<EGLAttrib> _get_platform_display_attributes() const override;
+ virtual Vector<EGLint> _get_platform_context_attribs() const override;
+};
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+#endif // WAYLAND_ENABLED
+
+#endif // EGL_MANAGER_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/key_mapping_xkb.cpp b/platform/linuxbsd/wayland/key_mapping_xkb.cpp
new file mode 100644
index 0000000000..bd1a1e3835
--- /dev/null
+++ b/platform/linuxbsd/wayland/key_mapping_xkb.cpp
@@ -0,0 +1,411 @@
+/**************************************************************************/
+/* key_mapping_xkb.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "key_mapping_xkb.h"
+
+void KeyMappingXKB::initialize() {
+ // XKB keycode to Godot Key map.
+
+ xkb_keycode_map[XKB_KEY_Escape] = Key::ESCAPE;
+ xkb_keycode_map[XKB_KEY_Tab] = Key::TAB;
+ xkb_keycode_map[XKB_KEY_ISO_Left_Tab] = Key::BACKTAB;
+ xkb_keycode_map[XKB_KEY_BackSpace] = Key::BACKSPACE;
+ xkb_keycode_map[XKB_KEY_Return] = Key::ENTER;
+ xkb_keycode_map[XKB_KEY_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_Clear] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_Pause] = Key::PAUSE;
+ xkb_keycode_map[XKB_KEY_Print] = Key::PRINT;
+ xkb_keycode_map[XKB_KEY_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_Left] = Key::LEFT;
+ xkb_keycode_map[XKB_KEY_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_Prior] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_Next] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_Shift_L] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Shift_R] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Shift_Lock] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Control_L] = Key::CTRL;
+ xkb_keycode_map[XKB_KEY_Control_R] = Key::CTRL;
+ xkb_keycode_map[XKB_KEY_Meta_L] = Key::META;
+ xkb_keycode_map[XKB_KEY_Meta_R] = Key::META;
+ xkb_keycode_map[XKB_KEY_Alt_L] = Key::ALT;
+ xkb_keycode_map[XKB_KEY_Alt_R] = Key::ALT;
+ xkb_keycode_map[XKB_KEY_Caps_Lock] = Key::CAPSLOCK;
+ xkb_keycode_map[XKB_KEY_Num_Lock] = Key::NUMLOCK;
+ xkb_keycode_map[XKB_KEY_Scroll_Lock] = Key::SCROLLLOCK;
+ xkb_keycode_map[XKB_KEY_less] = Key::QUOTELEFT;
+ xkb_keycode_map[XKB_KEY_grave] = Key::SECTION;
+ xkb_keycode_map[XKB_KEY_Super_L] = Key::META;
+ xkb_keycode_map[XKB_KEY_Super_R] = Key::META;
+ xkb_keycode_map[XKB_KEY_Menu] = Key::MENU;
+ xkb_keycode_map[XKB_KEY_Hyper_L] = Key::HYPER;
+ xkb_keycode_map[XKB_KEY_Hyper_R] = Key::HYPER;
+ xkb_keycode_map[XKB_KEY_Help] = Key::HELP;
+ xkb_keycode_map[XKB_KEY_KP_Space] = Key::SPACE;
+ xkb_keycode_map[XKB_KEY_KP_Tab] = Key::TAB;
+ xkb_keycode_map[XKB_KEY_KP_Enter] = Key::KP_ENTER;
+ xkb_keycode_map[XKB_KEY_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_Left] = Key::LEFT;
+ xkb_keycode_map[XKB_KEY_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_Prior] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_Next] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_Begin] = Key::CLEAR;
+ xkb_keycode_map[XKB_KEY_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_KP_Equal] = Key::EQUAL;
+ xkb_keycode_map[XKB_KEY_KP_Separator] = Key::COMMA;
+ xkb_keycode_map[XKB_KEY_KP_Decimal] = Key::KP_PERIOD;
+ xkb_keycode_map[XKB_KEY_KP_Multiply] = Key::KP_MULTIPLY;
+ xkb_keycode_map[XKB_KEY_KP_Divide] = Key::KP_DIVIDE;
+ xkb_keycode_map[XKB_KEY_KP_Subtract] = Key::KP_SUBTRACT;
+ xkb_keycode_map[XKB_KEY_KP_Add] = Key::KP_ADD;
+ xkb_keycode_map[XKB_KEY_KP_0] = Key::KP_0;
+ xkb_keycode_map[XKB_KEY_KP_1] = Key::KP_1;
+ xkb_keycode_map[XKB_KEY_KP_2] = Key::KP_2;
+ xkb_keycode_map[XKB_KEY_KP_3] = Key::KP_3;
+ xkb_keycode_map[XKB_KEY_KP_4] = Key::KP_4;
+ xkb_keycode_map[XKB_KEY_KP_5] = Key::KP_5;
+ xkb_keycode_map[XKB_KEY_KP_6] = Key::KP_6;
+ xkb_keycode_map[XKB_KEY_KP_7] = Key::KP_7;
+ xkb_keycode_map[XKB_KEY_KP_8] = Key::KP_8;
+ xkb_keycode_map[XKB_KEY_KP_9] = Key::KP_9;
+ // Same keys but with numlock off.
+ xkb_keycode_map[XKB_KEY_KP_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_KP_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_KP_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_KP_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_KP_Page_Down] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_KP_Left] = Key::LEFT;
+ // X11 documents this (numpad 5) as "begin of line" but no toolkit seems to interpret it this way.
+ // On Windows this is emitting Key::Clear so for consistency it will be mapped to Key::Clear
+ xkb_keycode_map[XKB_KEY_KP_Begin] = Key::CLEAR;
+ xkb_keycode_map[XKB_KEY_KP_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_KP_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_KP_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_KP_Page_Up] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_F1] = Key::F1;
+ xkb_keycode_map[XKB_KEY_F2] = Key::F2;
+ xkb_keycode_map[XKB_KEY_F3] = Key::F3;
+ xkb_keycode_map[XKB_KEY_F4] = Key::F4;
+ xkb_keycode_map[XKB_KEY_F5] = Key::F5;
+ xkb_keycode_map[XKB_KEY_F6] = Key::F6;
+ xkb_keycode_map[XKB_KEY_F7] = Key::F7;
+ xkb_keycode_map[XKB_KEY_F8] = Key::F8;
+ xkb_keycode_map[XKB_KEY_F9] = Key::F9;
+ xkb_keycode_map[XKB_KEY_F10] = Key::F10;
+ xkb_keycode_map[XKB_KEY_F11] = Key::F11;
+ xkb_keycode_map[XKB_KEY_F12] = Key::F12;
+ xkb_keycode_map[XKB_KEY_F13] = Key::F13;
+ xkb_keycode_map[XKB_KEY_F14] = Key::F14;
+ xkb_keycode_map[XKB_KEY_F15] = Key::F15;
+ xkb_keycode_map[XKB_KEY_F16] = Key::F16;
+ xkb_keycode_map[XKB_KEY_F17] = Key::F17;
+ xkb_keycode_map[XKB_KEY_F18] = Key::F18;
+ xkb_keycode_map[XKB_KEY_F19] = Key::F19;
+ xkb_keycode_map[XKB_KEY_F20] = Key::F20;
+ xkb_keycode_map[XKB_KEY_F21] = Key::F21;
+ xkb_keycode_map[XKB_KEY_F22] = Key::F22;
+ xkb_keycode_map[XKB_KEY_F23] = Key::F23;
+ xkb_keycode_map[XKB_KEY_F24] = Key::F24;
+ xkb_keycode_map[XKB_KEY_F25] = Key::F25;
+ xkb_keycode_map[XKB_KEY_F26] = Key::F26;
+ xkb_keycode_map[XKB_KEY_F27] = Key::F27;
+ xkb_keycode_map[XKB_KEY_F28] = Key::F28;
+ xkb_keycode_map[XKB_KEY_F29] = Key::F29;
+ xkb_keycode_map[XKB_KEY_F30] = Key::F30;
+ xkb_keycode_map[XKB_KEY_F31] = Key::F31;
+ xkb_keycode_map[XKB_KEY_F32] = Key::F32;
+ xkb_keycode_map[XKB_KEY_F33] = Key::F33;
+ xkb_keycode_map[XKB_KEY_F34] = Key::F34;
+ xkb_keycode_map[XKB_KEY_F35] = Key::F35;
+ xkb_keycode_map[XKB_KEY_yen] = Key::YEN;
+ xkb_keycode_map[XKB_KEY_section] = Key::SECTION;
+ // Media keys.
+ xkb_keycode_map[XKB_KEY_XF86Back] = Key::BACK;
+ xkb_keycode_map[XKB_KEY_XF86Forward] = Key::FORWARD;
+ xkb_keycode_map[XKB_KEY_XF86Stop] = Key::STOP;
+ xkb_keycode_map[XKB_KEY_XF86Refresh] = Key::REFRESH;
+ xkb_keycode_map[XKB_KEY_XF86Favorites] = Key::FAVORITES;
+ xkb_keycode_map[XKB_KEY_XF86OpenURL] = Key::OPENURL;
+ xkb_keycode_map[XKB_KEY_XF86HomePage] = Key::HOMEPAGE;
+ xkb_keycode_map[XKB_KEY_XF86Search] = Key::SEARCH;
+ xkb_keycode_map[XKB_KEY_XF86AudioLowerVolume] = Key::VOLUMEDOWN;
+ xkb_keycode_map[XKB_KEY_XF86AudioMute] = Key::VOLUMEMUTE;
+ xkb_keycode_map[XKB_KEY_XF86AudioRaiseVolume] = Key::VOLUMEUP;
+ xkb_keycode_map[XKB_KEY_XF86AudioPlay] = Key::MEDIAPLAY;
+ xkb_keycode_map[XKB_KEY_XF86AudioStop] = Key::MEDIASTOP;
+ xkb_keycode_map[XKB_KEY_XF86AudioPrev] = Key::MEDIAPREVIOUS;
+ xkb_keycode_map[XKB_KEY_XF86AudioNext] = Key::MEDIANEXT;
+ xkb_keycode_map[XKB_KEY_XF86AudioRecord] = Key::MEDIARECORD;
+ xkb_keycode_map[XKB_KEY_XF86Standby] = Key::STANDBY;
+ // Launch keys.
+ xkb_keycode_map[XKB_KEY_XF86Mail] = Key::LAUNCHMAIL;
+ xkb_keycode_map[XKB_KEY_XF86AudioMedia] = Key::LAUNCHMEDIA;
+ xkb_keycode_map[XKB_KEY_XF86MyComputer] = Key::LAUNCH0;
+ xkb_keycode_map[XKB_KEY_XF86Calculator] = Key::LAUNCH1;
+ xkb_keycode_map[XKB_KEY_XF86Launch0] = Key::LAUNCH2;
+ xkb_keycode_map[XKB_KEY_XF86Launch1] = Key::LAUNCH3;
+ xkb_keycode_map[XKB_KEY_XF86Launch2] = Key::LAUNCH4;
+ xkb_keycode_map[XKB_KEY_XF86Launch3] = Key::LAUNCH5;
+ xkb_keycode_map[XKB_KEY_XF86Launch4] = Key::LAUNCH6;
+ xkb_keycode_map[XKB_KEY_XF86Launch5] = Key::LAUNCH7;
+ xkb_keycode_map[XKB_KEY_XF86Launch6] = Key::LAUNCH8;
+ xkb_keycode_map[XKB_KEY_XF86Launch7] = Key::LAUNCH9;
+ xkb_keycode_map[XKB_KEY_XF86Launch8] = Key::LAUNCHA;
+ xkb_keycode_map[XKB_KEY_XF86Launch9] = Key::LAUNCHB;
+ xkb_keycode_map[XKB_KEY_XF86LaunchA] = Key::LAUNCHC;
+ xkb_keycode_map[XKB_KEY_XF86LaunchB] = Key::LAUNCHD;
+ xkb_keycode_map[XKB_KEY_XF86LaunchC] = Key::LAUNCHE;
+ xkb_keycode_map[XKB_KEY_XF86LaunchD] = Key::LAUNCHF;
+
+ // Scancode to Godot Key map.
+ scancode_map[0x09] = Key::ESCAPE;
+ scancode_map[0x0A] = Key::KEY_1;
+ scancode_map[0x0B] = Key::KEY_2;
+ scancode_map[0x0C] = Key::KEY_3;
+ scancode_map[0x0D] = Key::KEY_4;
+ scancode_map[0x0E] = Key::KEY_5;
+ scancode_map[0x0F] = Key::KEY_6;
+ scancode_map[0x10] = Key::KEY_7;
+ scancode_map[0x11] = Key::KEY_8;
+ scancode_map[0x12] = Key::KEY_9;
+ scancode_map[0x13] = Key::KEY_0;
+ scancode_map[0x14] = Key::MINUS;
+ scancode_map[0x15] = Key::EQUAL;
+ scancode_map[0x16] = Key::BACKSPACE;
+ scancode_map[0x17] = Key::TAB;
+ scancode_map[0x18] = Key::Q;
+ scancode_map[0x19] = Key::W;
+ scancode_map[0x1A] = Key::E;
+ scancode_map[0x1B] = Key::R;
+ scancode_map[0x1C] = Key::T;
+ scancode_map[0x1D] = Key::Y;
+ scancode_map[0x1E] = Key::U;
+ scancode_map[0x1F] = Key::I;
+ scancode_map[0x20] = Key::O;
+ scancode_map[0x21] = Key::P;
+ scancode_map[0x22] = Key::BRACELEFT;
+ scancode_map[0x23] = Key::BRACERIGHT;
+ scancode_map[0x24] = Key::ENTER;
+ scancode_map[0x25] = Key::CTRL; // Left
+ scancode_map[0x26] = Key::A;
+ scancode_map[0x27] = Key::S;
+ scancode_map[0x28] = Key::D;
+ scancode_map[0x29] = Key::F;
+ scancode_map[0x2A] = Key::G;
+ scancode_map[0x2B] = Key::H;
+ scancode_map[0x2C] = Key::J;
+ scancode_map[0x2D] = Key::K;
+ scancode_map[0x2E] = Key::L;
+ scancode_map[0x2F] = Key::SEMICOLON;
+ scancode_map[0x30] = Key::APOSTROPHE;
+ scancode_map[0x31] = Key::SECTION;
+ scancode_map[0x32] = Key::SHIFT; // Left
+ scancode_map[0x33] = Key::BACKSLASH;
+ scancode_map[0x34] = Key::Z;
+ scancode_map[0x35] = Key::X;
+ scancode_map[0x36] = Key::C;
+ scancode_map[0x37] = Key::V;
+ scancode_map[0x38] = Key::B;
+ scancode_map[0x39] = Key::N;
+ scancode_map[0x3A] = Key::M;
+ scancode_map[0x3B] = Key::COMMA;
+ scancode_map[0x3C] = Key::PERIOD;
+ scancode_map[0x3D] = Key::SLASH;
+ scancode_map[0x3E] = Key::SHIFT; // Right
+ scancode_map[0x3F] = Key::KP_MULTIPLY;
+ scancode_map[0x40] = Key::ALT; // Left
+ scancode_map[0x41] = Key::SPACE;
+ scancode_map[0x42] = Key::CAPSLOCK;
+ scancode_map[0x43] = Key::F1;
+ scancode_map[0x44] = Key::F2;
+ scancode_map[0x45] = Key::F3;
+ scancode_map[0x46] = Key::F4;
+ scancode_map[0x47] = Key::F5;
+ scancode_map[0x48] = Key::F6;
+ scancode_map[0x49] = Key::F7;
+ scancode_map[0x4A] = Key::F8;
+ scancode_map[0x4B] = Key::F9;
+ scancode_map[0x4C] = Key::F10;
+ scancode_map[0x4D] = Key::NUMLOCK;
+ scancode_map[0x4E] = Key::SCROLLLOCK;
+ scancode_map[0x4F] = Key::KP_7;
+ scancode_map[0x50] = Key::KP_8;
+ scancode_map[0x51] = Key::KP_9;
+ scancode_map[0x52] = Key::KP_SUBTRACT;
+ scancode_map[0x53] = Key::KP_4;
+ scancode_map[0x54] = Key::KP_5;
+ scancode_map[0x55] = Key::KP_6;
+ scancode_map[0x56] = Key::KP_ADD;
+ scancode_map[0x57] = Key::KP_1;
+ scancode_map[0x58] = Key::KP_2;
+ scancode_map[0x59] = Key::KP_3;
+ scancode_map[0x5A] = Key::KP_0;
+ scancode_map[0x5B] = Key::KP_PERIOD;
+ //scancode_map[0x5C]
+ //scancode_map[0x5D] // Zenkaku Hankaku
+ scancode_map[0x5E] = Key::QUOTELEFT;
+ scancode_map[0x5F] = Key::F11;
+ scancode_map[0x60] = Key::F12;
+ //scancode_map[0x61] // Romaji
+ //scancode_map[0x62] // Katakana
+ //scancode_map[0x63] // Hiragana
+ //scancode_map[0x64] // Henkan
+ //scancode_map[0x65] // Hiragana Katakana
+ //scancode_map[0x66] // Muhenkan
+ scancode_map[0x67] = Key::COMMA; // KP_Separator
+ scancode_map[0x68] = Key::KP_ENTER;
+ scancode_map[0x69] = Key::CTRL; // Right
+ scancode_map[0x6A] = Key::KP_DIVIDE;
+ scancode_map[0x6B] = Key::PRINT;
+ scancode_map[0x6C] = Key::ALT; // Right
+ scancode_map[0x6D] = Key::ENTER;
+ scancode_map[0x6E] = Key::HOME;
+ scancode_map[0x6F] = Key::UP;
+ scancode_map[0x70] = Key::PAGEUP;
+ scancode_map[0x71] = Key::LEFT;
+ scancode_map[0x72] = Key::RIGHT;
+ scancode_map[0x73] = Key::END;
+ scancode_map[0x74] = Key::DOWN;
+ scancode_map[0x75] = Key::PAGEDOWN;
+ scancode_map[0x76] = Key::INSERT;
+ scancode_map[0x77] = Key::KEY_DELETE;
+ //scancode_map[0x78] // Macro
+ scancode_map[0x79] = Key::VOLUMEMUTE;
+ scancode_map[0x7A] = Key::VOLUMEDOWN;
+ scancode_map[0x7B] = Key::VOLUMEUP;
+ //scancode_map[0x7C] // Power
+ scancode_map[0x7D] = Key::EQUAL; // KP_Equal
+ //scancode_map[0x7E] // KP_PlusMinus
+ scancode_map[0x7F] = Key::PAUSE;
+ scancode_map[0x80] = Key::LAUNCH0;
+ scancode_map[0x81] = Key::COMMA; // KP_Comma
+ //scancode_map[0x82] // Hangul
+ //scancode_map[0x83] // Hangul_Hanja
+ scancode_map[0x84] = Key::YEN;
+ scancode_map[0x85] = Key::META; // Left
+ scancode_map[0x86] = Key::META; // Right
+ scancode_map[0x87] = Key::MENU;
+
+ scancode_map[0xA6] = Key::BACK; // On Chromebooks
+ scancode_map[0xA7] = Key::FORWARD; // On Chromebooks
+
+ scancode_map[0xB5] = Key::REFRESH; // On Chromebooks
+
+ scancode_map[0xBF] = Key::F13;
+ scancode_map[0xC0] = Key::F14;
+ scancode_map[0xC1] = Key::F15;
+ scancode_map[0xC2] = Key::F16;
+ scancode_map[0xC3] = Key::F17;
+ scancode_map[0xC4] = Key::F18;
+ scancode_map[0xC5] = Key::F19;
+ scancode_map[0xC6] = Key::F20;
+ scancode_map[0xC7] = Key::F21;
+ scancode_map[0xC8] = Key::F22;
+ scancode_map[0xC9] = Key::F23;
+ scancode_map[0xCA] = Key::F24;
+ scancode_map[0xCB] = Key::F25;
+ scancode_map[0xCC] = Key::F26;
+ scancode_map[0xCD] = Key::F27;
+ scancode_map[0xCE] = Key::F28;
+ scancode_map[0xCF] = Key::F29;
+ scancode_map[0xD0] = Key::F30;
+ scancode_map[0xD1] = Key::F31;
+ scancode_map[0xD2] = Key::F32;
+ scancode_map[0xD3] = Key::F33;
+ scancode_map[0xD4] = Key::F34;
+ scancode_map[0xD5] = Key::F35;
+
+ // Godot to scancode map.
+ for (const KeyValue<unsigned int, Key> &E : scancode_map) {
+ scancode_map_inv[E.value] = E.key;
+ }
+
+ // Scancode to physical location map.
+ // Ctrl.
+ location_map[0x25] = KeyLocation::LEFT;
+ location_map[0x69] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x32] = KeyLocation::LEFT;
+ location_map[0x3E] = KeyLocation::RIGHT;
+ // Alt.
+ location_map[0x40] = KeyLocation::LEFT;
+ location_map[0x6C] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x85] = KeyLocation::LEFT;
+ location_map[0x86] = KeyLocation::RIGHT;
+}
+
+Key KeyMappingXKB::get_keycode(xkb_keycode_t p_keysym) {
+ if (p_keysym >= 0x20 && p_keysym < 0x7E) { // ASCII, maps 1-1
+ if (p_keysym > 0x60 && p_keysym < 0x7B) { // Lowercase ASCII.
+ return (Key)(p_keysym - 32);
+ } else {
+ return (Key)p_keysym;
+ }
+ }
+
+ const Key *key = xkb_keycode_map.getptr(p_keysym);
+ if (key) {
+ return *key;
+ }
+ return Key::NONE;
+}
+
+Key KeyMappingXKB::get_scancode(unsigned int p_code) {
+ const Key *key = scancode_map.getptr(p_code);
+ if (key) {
+ return *key;
+ }
+
+ return Key::NONE;
+}
+
+xkb_keycode_t KeyMappingXKB::get_xkb_keycode(Key p_key) {
+ const unsigned int *key = scancode_map_inv.getptr(p_key);
+ if (key) {
+ return *key;
+ }
+ return 0x00;
+}
+
+KeyLocation KeyMappingXKB::get_location(unsigned int p_code) {
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/linuxbsd/wayland/key_mapping_xkb.h b/platform/linuxbsd/wayland/key_mapping_xkb.h
new file mode 100644
index 0000000000..306a8f25b5
--- /dev/null
+++ b/platform/linuxbsd/wayland/key_mapping_xkb.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* key_mapping_xkb.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. */
+/**************************************************************************/
+
+#ifndef KEY_MAPPING_XKB_H
+#define KEY_MAPPING_XKB_H
+
+#include "core/os/keyboard.h"
+#include "core/templates/hash_map.h"
+
+#ifdef SOWRAP_ENABLED
+#include "xkbcommon-so_wrap.h"
+#else
+#include <xkbcommon/xkbcommon.h>
+#endif // SOWRAP_ENABLED
+
+class KeyMappingXKB {
+ struct HashMapHasherKeys {
+ static _FORCE_INLINE_ uint32_t hash(Key p_key) { return hash_fmix32(static_cast<uint32_t>(p_key)); }
+ static _FORCE_INLINE_ uint32_t hash(unsigned p_key) { return hash_fmix32(p_key); }
+ };
+
+ static inline HashMap<xkb_keycode_t, Key, HashMapHasherKeys> xkb_keycode_map;
+ static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
+ static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
+ static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
+
+ KeyMappingXKB(){};
+
+public:
+ static void initialize();
+
+ static Key get_keycode(xkb_keysym_t p_keysym);
+ static xkb_keycode_t get_xkb_keycode(Key p_keycode);
+ static Key get_scancode(unsigned int p_code);
+ static KeyLocation get_location(unsigned int p_code);
+};
+
+#endif // KEY_MAPPING_XKB_H
diff --git a/platform/linuxbsd/wayland/vulkan_context_wayland.cpp b/platform/linuxbsd/wayland/vulkan_context_wayland.cpp
new file mode 100644
index 0000000000..b3f28a1678
--- /dev/null
+++ b/platform/linuxbsd/wayland/vulkan_context_wayland.cpp
@@ -0,0 +1,59 @@
+/**************************************************************************/
+/* vulkan_context_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "vulkan_context_wayland.h"
+
+#ifdef VULKAN_ENABLED
+
+#ifdef USE_VOLK
+#include <volk.h>
+#else
+#include <vulkan/vulkan.h>
+#endif
+
+const char *VulkanContextWayland::_get_platform_surface_extension() const {
+ return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
+}
+
+Error VulkanContextWayland::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) {
+ const WindowPlatformData *wpd = (const WindowPlatformData *)p_platform_data;
+
+ VkWaylandSurfaceCreateInfoKHR createInfo = {};
+ createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
+ createInfo.display = wpd->display;
+ createInfo.surface = wpd->surface;
+
+ VkSurfaceKHR surface = VK_NULL_HANDLE;
+ VkResult err = vkCreateWaylandSurfaceKHR(get_instance(), &createInfo, nullptr, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+ return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height);
+}
+
+#endif // VULKAN_ENABLED
diff --git a/platform/linuxbsd/wayland/vulkan_context_wayland.h b/platform/linuxbsd/wayland/vulkan_context_wayland.h
new file mode 100644
index 0000000000..b0a7d1ff87
--- /dev/null
+++ b/platform/linuxbsd/wayland/vulkan_context_wayland.h
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* vulkan_context_wayland.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. */
+/**************************************************************************/
+
+#ifndef VULKAN_CONTEXT_WAYLAND_H
+#define VULKAN_CONTEXT_WAYLAND_H
+
+#ifdef VULKAN_ENABLED
+
+#include "drivers/vulkan/vulkan_context.h"
+
+class VulkanContextWayland : public VulkanContext {
+ const char *_get_platform_surface_extension() const override final;
+
+public:
+ struct WindowPlatformData {
+ struct wl_display *display;
+ struct wl_surface *surface;
+ };
+
+ Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) override final;
+};
+
+#endif // VULKAN_ENABLED
+
+#endif // VULKAN_CONTEXT_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
new file mode 100644
index 0000000000..0e9c3fb776
--- /dev/null
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -0,0 +1,4049 @@
+/**************************************************************************/
+/* wayland_thread.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "wayland_thread.h"
+
+#ifdef WAYLAND_ENABLED
+
+// FIXME: Does this cause issues with *BSDs?
+#include <linux/input-event-codes.h>
+
+// For the actual polling thread.
+#include <poll.h>
+
+// For shared memory buffer creation.
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+// Fix the wl_array_for_each macro to work with C++. This is based on the
+// original from `wayland-util.h` in the Wayland client library.
+#undef wl_array_for_each
+#define wl_array_for_each(pos, array) \
+ for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++)
+
+#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED
+#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED
+#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__)
+#else
+#define DEBUG_LOG_WAYLAND_THREAD(...)
+#endif
+
+// Read the content pointed by fd into a Vector<uint8_t>.
+Vector<uint8_t> WaylandThread::_read_fd(int fd) {
+ // This is pretty much an arbitrary size.
+ uint32_t chunk_size = 2048;
+
+ LocalVector<uint8_t> data;
+ data.resize(chunk_size);
+
+ uint32_t bytes_read = 0;
+
+ while (true) {
+ ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size);
+ if (last_bytes_read < 0) {
+ ERR_PRINT(vformat("Read error %d.", errno));
+
+ data.clear();
+ break;
+ }
+
+ if (last_bytes_read == 0) {
+ // We're done, we've reached the EOF.
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read));
+
+ close(fd);
+
+ data.resize(bytes_read);
+ break;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read));
+
+ bytes_read += last_bytes_read;
+
+ // Increase the buffer size by one chunk in preparation of the next read.
+ data.resize(bytes_read + chunk_size);
+ }
+
+ return data;
+}
+
+// Based on the wayland book's shared memory boilerplate (PD/CC0).
+// See: https://wayland-book.com/surfaces/shared-memory.html
+int WaylandThread::_allocate_shm_file(size_t size) {
+ int retries = 100;
+
+ do {
+ // Generate a random name.
+ char name[] = "/wl_shm-godot-XXXXXX";
+ for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) {
+ name[i] = Math::random('A', 'Z');
+ }
+
+ // Try to open a shared memory object with that name.
+ int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0) {
+ // Success, unlink its name as we just need the file descriptor.
+ shm_unlink(name);
+
+ // Resize the file to the requested length.
+ int ret;
+ do {
+ ret = ftruncate(fd, size);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+ }
+
+ retries--;
+ } while (retries > 0 && errno == EEXIST);
+
+ return -1;
+}
+
+// Return the content of a wl_data_offer.
+Vector<uint8_t> WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) {
+ if (!p_offer) {
+ return Vector<uint8_t>();
+ }
+
+ int fds[2];
+ if (pipe(fds) == 0) {
+ wl_data_offer_receive(p_offer, p_mime, fds[1]);
+
+ // Let the compositor know about the pipe.
+ // NOTE: It's important to just flush and not roundtrip here as we would risk
+ // running some cleanup event, like for example `wl_data_device::leave`. We're
+ // going to wait for the message anyways as the read will probably block if
+ // the compositor doesn't read from the other end of the pipe.
+ wl_display_flush(p_display);
+
+ // Close the write end of the pipe, which we don't need and would otherwise
+ // just stall our next `read`s.
+ close(fds[1]);
+
+ return _read_fd(fds[0]);
+ }
+
+ return Vector<uint8_t>();
+}
+
+// Read the content of a wp_primary_selection_offer.
+Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) {
+ if (!p_offer) {
+ return Vector<uint8_t>();
+ }
+
+ int fds[2];
+ if (pipe(fds) == 0) {
+ // This function expects to return a string, so we can only ask for a MIME of
+ // "text/plain"
+ zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);
+
+ // Wait for the compositor to know about the pipe.
+ wl_display_roundtrip(p_display);
+
+ // Close the write end of the pipe, which we don't need and would otherwise
+ // just stall our next `read`s.
+ close(fds[1]);
+
+ return _read_fd(fds[0]);
+ }
+
+ return Vector<uint8_t>();
+}
+
+// Sets up an `InputEventKey` and returns whether it has any meaningful value.
+bool WaylandThread::_seat_state_configure_key_event(SeatState &p_ss, Ref<InputEventKey> p_event, xkb_keycode_t p_keycode, bool p_pressed) {
+ // TODO: Handle keys that release multiple symbols?
+ Key keycode = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(p_ss.xkb_state, p_keycode));
+ Key physical_keycode = KeyMappingXKB::get_scancode(p_keycode);
+ KeyLocation key_location = KeyMappingXKB::get_location(p_keycode);
+
+ if (physical_keycode == Key::NONE) {
+ return false;
+ }
+
+ if (keycode == Key::NONE) {
+ keycode = physical_keycode;
+ }
+
+ if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
+ keycode -= 'a' - 'A';
+ }
+
+ p_event->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ p_event->set_shift_pressed(p_ss.shift_pressed);
+ p_event->set_ctrl_pressed(p_ss.ctrl_pressed);
+ p_event->set_alt_pressed(p_ss.alt_pressed);
+ p_event->set_meta_pressed(p_ss.meta_pressed);
+
+ p_event->set_pressed(p_pressed);
+ p_event->set_keycode(keycode);
+ p_event->set_physical_keycode(physical_keycode);
+ p_event->set_location(key_location);
+
+ uint32_t unicode = xkb_state_key_get_utf32(p_ss.xkb_state, p_keycode);
+
+ if (unicode != 0) {
+ p_event->set_key_label(fix_key_label(unicode, keycode));
+ } else {
+ p_event->set_key_label(keycode);
+ }
+
+ if (p_pressed) {
+ p_event->set_unicode(fix_unicode(unicode));
+ }
+
+ // Taken from DisplayServerX11.
+ if (p_event->get_keycode() == Key::BACKTAB) {
+ // Make it consistent across platforms.
+ p_event->set_keycode(Key::TAB);
+ p_event->set_physical_keycode(Key::TAB);
+ p_event->set_shift_pressed(true);
+ }
+
+ return true;
+}
+
+void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {
+ if (p_seat == wl_seat_current) {
+ return;
+ }
+
+ SeatState *old_state = wl_seat_get_seat_state(wl_seat_current);
+
+ if (old_state) {
+ seat_state_unlock_pointer(old_state);
+ }
+
+ SeatState *new_state = wl_seat_get_seat_state(p_seat);
+ seat_state_unlock_pointer(new_state);
+
+ wl_seat_current = p_seat;
+ pointer_set_constraint(pointer_constraint);
+}
+
+// Returns whether it loaded the theme or not.
+bool WaylandThread::_load_cursor_theme(int p_cursor_size) {
+ if (wl_cursor_theme) {
+ wl_cursor_theme_destroy(wl_cursor_theme);
+ wl_cursor_theme = nullptr;
+
+ current_wl_cursor = nullptr;
+ }
+
+ if (cursor_theme_name.is_empty()) {
+ cursor_theme_name = "default";
+ }
+
+ print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size));
+
+ wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm);
+
+ ERR_FAIL_NULL_V_MSG(wl_cursor_theme, false, "Can't load any cursor theme.");
+
+ static const char *cursor_names[] = {
+ "left_ptr",
+ "xterm",
+ "hand2",
+ "cross",
+ "watch",
+ "left_ptr_watch",
+ "fleur",
+ "dnd-move",
+ "crossed_circle",
+ "v_double_arrow",
+ "h_double_arrow",
+ "size_bdiag",
+ "size_fdiag",
+ "move",
+ "row_resize",
+ "col_resize",
+ "question_arrow"
+ };
+
+ static const char *cursor_names_fallback[] = {
+ nullptr,
+ nullptr,
+ "pointer",
+ "cross",
+ "wait",
+ "progress",
+ "grabbing",
+ "hand1",
+ "forbidden",
+ "ns-resize",
+ "ew-resize",
+ "fd_double_arrow",
+ "bd_double_arrow",
+ "fleur",
+ "sb_v_double_arrow",
+ "sb_h_double_arrow",
+ "help"
+ };
+
+ for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {
+ struct wl_cursor *cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names[i]);
+
+ if (!cursor && cursor_names_fallback[i]) {
+ cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names_fallback[i]);
+ }
+
+ if (cursor && cursor->image_count > 0) {
+ wl_cursors[i] = cursor;
+ } else {
+ wl_cursors[i] = nullptr;
+ print_verbose("Failed loading cursor: " + String(cursor_names[i]));
+ }
+ }
+
+ return true;
+}
+
+void WaylandThread::_update_scale(int p_scale) {
+ if (p_scale <= cursor_scale) {
+ return;
+ }
+
+ print_verbose(vformat("Bumping cursor scale to %d", p_scale));
+
+ // There's some display that's bigger than the cache, let's update it.
+ cursor_scale = p_scale;
+
+ if (wl_cursor_theme == nullptr) {
+ // Ugh. Either we're still initializing (this must've been called from the
+ // first roundtrips) or we had some error while doing so. We'll trust that it
+ // will be updated for us if needed.
+ return;
+ }
+
+ int cursor_size = unscaled_cursor_size * p_scale;
+
+ if (_load_cursor_theme(cursor_size)) {
+ cursor_set_shape(last_cursor_shape);
+ }
+}
+
+void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) {
+ RegistryState *registry = (RegistryState *)data;
+ ERR_FAIL_NULL(registry);
+
+ if (strcmp(interface, wl_shm_interface.name) == 0) {
+ registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);
+ registry->wl_shm_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) {
+ registry->wl_exporter = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1);
+ registry->wl_exporter_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4);
+ registry->wl_compositor_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
+ registry->wl_subcompositor = (struct wl_subcompositor *)wl_registry_bind(wl_registry, name, &wl_subcompositor_interface, 1);
+ registry->wl_subcompositor_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
+ registry->wl_data_device_manager = (struct wl_data_device_manager *)wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3);
+ registry->wl_data_device_manager_name = name;
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_device == nullptr) {
+ ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
+ wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
+ }
+ }
+ return;
+ }
+
+ if (strcmp(interface, wl_output_interface.name) == 0) {
+ struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, 2);
+ wl_proxy_tag_godot((struct wl_proxy *)wl_output);
+
+ registry->wl_outputs.push_back(wl_output);
+
+ ScreenState *ss = memnew(ScreenState);
+ ss->wl_output_name = name;
+ ss->wayland_thread = registry->wayland_thread;
+
+ wl_proxy_tag_godot((struct wl_proxy *)wl_output);
+ wl_output_add_listener(wl_output, &wl_output_listener, ss);
+ return;
+ }
+
+ if (strcmp(interface, wl_seat_interface.name) == 0) {
+ struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, 5);
+ wl_proxy_tag_godot((struct wl_proxy *)wl_seat);
+
+ SeatState *ss = memnew(SeatState);
+ ss->wl_seat = wl_seat;
+ ss->wl_seat_name = name;
+
+ ss->registry = registry;
+ ss->wayland_thread = registry->wayland_thread;
+
+ // Some extra stuff depends on other globals. We'll initialize them if the
+ // globals are already there, otherwise we'll have to do that once and if they
+ // get announced.
+ //
+ // NOTE: Don't forget to also bind/destroy with the respective global.
+ if (!ss->wl_data_device && registry->wl_data_device_manager) {
+ // Clipboard & DnD.
+ ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
+ wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
+ }
+
+ if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
+ // Primary selection.
+ ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
+ zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {
+ // Tablet.
+ ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
+ zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
+ }
+#endif
+
+ registry->wl_seats.push_back(wl_seat);
+
+ wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);
+
+ if (registry->wayland_thread->wl_seat_current == nullptr) {
+ registry->wayland_thread->_set_current_seat(wl_seat);
+ }
+
+ return;
+ }
+
+ if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(5, (int)version)));
+ registry->xdg_wm_base_name = name;
+
+ xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr);
+ return;
+ }
+
+ if (strcmp(interface, wp_viewporter_interface.name) == 0) {
+ registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1);
+ registry->wp_viewporter_name = name;
+ }
+
+ if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
+ registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);
+ registry->wp_fractional_scale_manager_name = name;
+
+ // NOTE: We're not mapping the fractional scale object here because this is
+ // supposed to be a "startup global". If for some reason this isn't true (who
+ // knows), add a conditional branch for creating the add-on object.
+ }
+
+ if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
+ registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);
+ registry->xdg_decoration_manager_name = name;
+ return;
+ }
+
+ if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {
+ registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1);
+ registry->xdg_activation_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {
+ registry->wp_primary_selection_device_manager = (struct zwp_primary_selection_device_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1);
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
+ ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
+ zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
+ }
+ }
+ }
+
+ if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
+ registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1);
+ registry->wp_relative_pointer_manager_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {
+ registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1);
+ registry->wp_pointer_constraints_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {
+ registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1);
+ registry->wp_pointer_gestures_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
+ registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);
+ registry->wp_idle_inhibit_manager_name = name;
+ return;
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {
+ registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);
+ registry->wp_tablet_manager_name = name;
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
+ zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
+ }
+
+ return;
+ }
+#endif
+}
+
+void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
+ RegistryState *registry = (RegistryState *)data;
+ ERR_FAIL_NULL(registry);
+
+ if (name == registry->wl_shm_name) {
+ if (registry->wl_shm) {
+ wl_shm_destroy(registry->wl_shm);
+ registry->wl_shm = nullptr;
+ }
+
+ registry->wl_shm_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_exporter_name) {
+ if (registry->wl_exporter) {
+ zxdg_exporter_v1_destroy(registry->wl_exporter);
+ registry->wl_exporter = nullptr;
+ }
+
+ registry->wl_exporter_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_compositor_name) {
+ if (registry->wl_compositor) {
+ wl_compositor_destroy(registry->wl_compositor);
+ registry->wl_compositor = nullptr;
+ }
+
+ registry->wl_compositor_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_subcompositor_name) {
+ if (registry->wl_subcompositor) {
+ wl_subcompositor_destroy(registry->wl_subcompositor);
+ registry->wl_subcompositor = nullptr;
+ }
+
+ registry->wl_subcompositor_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_data_device_manager_name) {
+ if (registry->wl_data_device_manager) {
+ wl_data_device_manager_destroy(registry->wl_data_device_manager);
+ registry->wl_data_device_manager = nullptr;
+ }
+
+ registry->wl_data_device_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ ss->wl_data_device = nullptr;
+ }
+
+ ss->wl_data_device = nullptr;
+ }
+
+ return;
+ }
+
+ if (name == registry->xdg_wm_base_name) {
+ if (registry->xdg_wm_base) {
+ xdg_wm_base_destroy(registry->xdg_wm_base);
+ registry->xdg_wm_base = nullptr;
+ }
+
+ registry->xdg_wm_base_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_viewporter_name) {
+ WindowState *ws = &registry->wayland_thread->main_window;
+
+ if (registry->wp_viewporter) {
+ wp_viewporter_destroy(registry->wp_viewporter);
+ registry->wp_viewporter = nullptr;
+ }
+
+ if (ws->wp_viewport) {
+ wp_viewport_destroy(ws->wp_viewport);
+ ws->wp_viewport = nullptr;
+ }
+
+ registry->wp_viewporter_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_fractional_scale_manager_name) {
+ WindowState *ws = &registry->wayland_thread->main_window;
+
+ if (registry->wp_fractional_scale_manager) {
+ wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
+ registry->wp_fractional_scale_manager = nullptr;
+ }
+
+ if (ws->wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(ws->wp_fractional_scale);
+ ws->wp_fractional_scale = nullptr;
+ }
+
+ registry->wp_fractional_scale_manager_name = 0;
+ }
+
+ if (name == registry->xdg_decoration_manager_name) {
+ if (registry->xdg_decoration_manager) {
+ zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager);
+ registry->xdg_decoration_manager = nullptr;
+ }
+
+ registry->xdg_decoration_manager_name = 0;
+
+ return;
+ }
+
+ if (name == registry->xdg_activation_name) {
+ if (registry->xdg_activation) {
+ xdg_activation_v1_destroy(registry->xdg_activation);
+ registry->xdg_activation = nullptr;
+ }
+
+ registry->xdg_activation_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_primary_selection_device_manager_name) {
+ if (registry->wp_primary_selection_device_manager) {
+ zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager);
+ registry->wp_primary_selection_device_manager = nullptr;
+ }
+
+ registry->wp_primary_selection_device_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_primary_selection_device) {
+ zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device);
+ ss->wp_primary_selection_device = nullptr;
+ }
+
+ if (ss->wp_primary_selection_source) {
+ zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
+ ss->wp_primary_selection_source = nullptr;
+ }
+
+ if (ss->wp_primary_selection_offer) {
+ memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
+ zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
+ ss->wp_primary_selection_offer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_relative_pointer_manager_name) {
+ if (registry->wp_relative_pointer_manager) {
+ zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager);
+ registry->wp_relative_pointer_manager = nullptr;
+ }
+
+ registry->wp_relative_pointer_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_pointer_constraints_name) {
+ if (registry->wp_pointer_constraints) {
+ zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints);
+ registry->wp_pointer_constraints = nullptr;
+ }
+
+ registry->wp_pointer_constraints_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ ss->wp_locked_pointer = nullptr;
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ ss->wp_confined_pointer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_pointer_gestures_name) {
+ if (registry->wp_pointer_gestures) {
+ zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures);
+ }
+
+ registry->wp_pointer_gestures = nullptr;
+ registry->wp_pointer_gestures_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_pointer_gesture_pinch) {
+ zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch);
+ ss->wp_pointer_gesture_pinch = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_idle_inhibit_manager_name) {
+ if (registry->wp_idle_inhibit_manager) {
+ zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager);
+ registry->wp_idle_inhibit_manager = nullptr;
+ }
+
+ registry->wp_idle_inhibit_manager_name = 0;
+
+ return;
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (name == registry->wp_tablet_manager_name) {
+ if (registry->wp_tablet_manager) {
+ zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);
+ registry->wp_tablet_manager = nullptr;
+ }
+
+ registry->wp_tablet_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
+
+ while (it) {
+ zwp_tablet_tool_v2_destroy(it->get());
+ ss->tablet_tools.erase(it);
+
+ it = it->next();
+ }
+ }
+
+ return;
+ }
+#endif
+
+ {
+ // Iterate through all of the seats to find if any got removed.
+ List<struct wl_seat *>::Element *it = registry->wl_seats.front();
+ while (it) {
+ struct wl_seat *wl_seat = it->get();
+
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_seat_name == name) {
+ if (wl_seat) {
+ wl_seat_destroy(wl_seat);
+ }
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (ss->wp_tablet_seat) {
+ zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
+
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+ }
+
+ // Let's destroy all tools.
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+
+ memdelete(ss);
+ registry->wl_seats.erase(it);
+#endif
+ return;
+ }
+
+ it = it->next();
+ }
+ }
+
+ {
+ // Iterate through all of the outputs to find if any got removed.
+ // FIXME: This is a very bruteforce approach.
+ List<struct wl_output *>::Element *it = registry->wl_outputs.front();
+ while (it) {
+ // Iterate through all of the screens to find if any got removed.
+ struct wl_output *wl_output = it->get();
+ ERR_FAIL_NULL(wl_output);
+
+ ScreenState *ss = wl_output_get_screen_state(wl_output);
+
+ if (ss->wl_output_name == name) {
+ registry->wl_outputs.erase(it);
+
+ memdelete(ss);
+ wl_output_destroy(wl_output);
+
+ return;
+ }
+
+ it = it->next();
+ }
+ }
+}
+
+void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
+ if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
+ // This won't have the right data bound to it. Not worth it and would probably
+ // just break everything.
+ return;
+ }
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output));
+
+ ws->wl_outputs.insert(wl_output);
+
+ // Workaround for buffer scaling as there's no guaranteed way of knowing the
+ // preferred scale.
+ // TODO: Skip this branch for newer `wl_surface`s once we add support for
+ // `wl_surface::preferred_buffer_scale`
+ if (ws->preferred_fractional_scale == 0) {
+ window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
+ }
+}
+
+void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {
+ wl_callback_destroy(wl_callback);
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+ ERR_FAIL_NULL(ws->wayland_thread);
+ ERR_FAIL_NULL(ws->wl_surface);
+
+ ws->wayland_thread->set_frame();
+
+ ws->frame_callback = wl_surface_frame(ws->wl_surface),
+ wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
+ wl_surface_commit(ws->wl_surface);
+
+ if (ws->wl_surface && ws->buffer_scale_changed) {
+ // NOTE: We're only now setting the buffer scale as the idea is to get this
+ // data committed together with the new frame, all by the rendering driver.
+ // This is important because we might otherwise set an invalid combination of
+ // buffer size and scale (e.g. odd size and 2x scale). We're pretty much
+ // guaranteed to get a proper buffer in the next render loop as the rescaling
+ // method also informs the engine of a "window rect change", triggering
+ // rendering if needed.
+ wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));
+ }
+
+ // NOTE: Remember to set here also other buffer-dependent states (e.g. opaque
+ // region) if used, to be as close as possible to an atomic surface update.
+ // Ideally we'd only have one surface commit, but it's not really doable given
+ // the current state of things.
+}
+
+void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
+ if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
+ // This won't have the right data bound to it. Not worth it and would probably
+ // just break everything.
+ return;
+ }
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->wl_outputs.erase(wl_output);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output));
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) {
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) {
+}
+
+void WaylandThread::_wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.position.x = x;
+
+ ss->pending_data.position.x = x;
+ ss->pending_data.position.y = y;
+
+ ss->pending_data.physical_size.width = physical_width;
+ ss->pending_data.physical_size.height = physical_height;
+
+ ss->pending_data.make.parse_utf8(make);
+ ss->pending_data.model.parse_utf8(model);
+}
+
+void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.size.width = width;
+ ss->pending_data.size.height = height;
+
+ ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1;
+}
+
+void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->data = ss->pending_data;
+
+ ss->wayland_thread->_update_scale(ss->data.scale);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output));
+}
+
+void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.scale = factor;
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor));
+}
+
+void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) {
+}
+
+void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) {
+}
+
+void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height));
+}
+
+void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ // Expect the window to be in windowed mode. The mode will get overridden if
+ // the compositor reports otherwise.
+ ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ uint32_t *state = nullptr;
+ wl_array_for_each(state, states) {
+ switch (*state) {
+ case XDG_TOPLEVEL_STATE_MAXIMIZED: {
+ ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
+ } break;
+
+ case XDG_TOPLEVEL_STATE_FULLSCREEN: {
+ ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
+ } break;
+
+ default: {
+ // We don't care about the other states (for now).
+ } break;
+ }
+ }
+
+ if (width != 0 && height != 0) {
+ window_state_update_size(ws, width, height);
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));
+}
+
+void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
+ ws->wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) {
+}
+
+void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->can_maximize = false;
+ ws->can_fullscreen = false;
+ ws->can_minimize = false;
+
+ uint32_t *capability = nullptr;
+ wl_array_for_each(capability, capabilities) {
+ switch (*capability) {
+ case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {
+ ws->can_maximize = true;
+ } break;
+ case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {
+ ws->can_fullscreen = true;
+ } break;
+
+ case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {
+ ws->can_minimize = true;
+ } break;
+
+ default: {
+ } break;
+ }
+ }
+}
+
+void WaylandThread::_xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->exported_handle = vformat("wayland:%s", String::utf8(handle));
+}
+
+void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) {
+ if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {
+#ifdef LIBDECOR_ENABLED
+ WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!");
+#else
+ WARN_PRINT_ONCE("Native client side decorations are not yet supported!");
+#endif // LIBDECOR_ENABLED
+ }
+}
+
+#ifdef LIBDECOR_ENABLED
+void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) {
+ ERR_PRINT(vformat("libdecor error %d: %s", error, message));
+}
+
+// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure
+// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything,
+// forcing us to do stuff like this.
+void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) {
+ WindowState *ws = (WindowState *)user_data;
+ ERR_FAIL_NULL(ws);
+
+ int width = 0;
+ int height = 0;
+
+ ws->pending_libdecor_configuration = configuration;
+
+ if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
+ // The configuration doesn't have a size. We'll use the one already set in the window.
+ width = ws->rect.size.width;
+ height = ws->rect.size.height;
+ }
+
+ ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size.");
+
+ libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE;
+
+ // Expect the window to be in windowed mode. The mode will get overridden if
+ // the compositor reports otherwise.
+ ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ if (libdecor_configuration_get_window_state(configuration, &window_state)) {
+ if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
+ ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
+ }
+
+ if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
+ ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
+ }
+ }
+
+ window_state_update_size(ws, width, height);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect));
+}
+
+void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) {
+ WindowState *ws = (WindowState *)user_data;
+ ERR_FAIL_NULL(ws);
+
+ Ref<WindowEventMessage> winevent_msg;
+ winevent_msg.instantiate();
+ winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
+
+ ws->wayland_thread->push_message(winevent_msg);
+
+ DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close");
+}
+
+void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) {
+ // We're skipping this as we don't really care about libdecor's commit for
+ // atomicity reasons. See `_frame_wl_callback_on_done` for more info.
+
+ DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit");
+}
+
+void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) {
+}
+#endif // LIBDECOR_ENABLED
+
+void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
+ SeatState *ss = (SeatState *)data;
+
+ ERR_FAIL_NULL(ss);
+
+ // TODO: Handle touch.
+
+ // Pointer handling.
+ if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
+ ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);
+ ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
+ wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
+ wl_surface_commit(ss->cursor_surface);
+
+ ss->wl_pointer = wl_seat_get_pointer(wl_seat);
+ wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
+
+ if (ss->registry->wp_relative_pointer_manager) {
+ ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
+ zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
+ }
+
+ if (ss->registry->wp_pointer_gestures) {
+ ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);
+ zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);
+ }
+
+ // TODO: Constrain new pointers if the global mouse mode is constrained.
+ } else {
+ if (ss->cursor_frame_callback) {
+ // Just in case. I got bitten by weird race-like conditions already.
+ wl_callback_set_user_data(ss->cursor_frame_callback, nullptr);
+
+ wl_callback_destroy(ss->cursor_frame_callback);
+ ss->cursor_frame_callback = nullptr;
+ }
+
+ if (ss->cursor_surface) {
+ wl_surface_destroy(ss->cursor_surface);
+ ss->cursor_surface = nullptr;
+ }
+
+ if (ss->wl_pointer) {
+ wl_pointer_destroy(ss->wl_pointer);
+ ss->wl_pointer = nullptr;
+ }
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ ss->wp_confined_pointer = nullptr;
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ ss->wp_locked_pointer = nullptr;
+ }
+ }
+
+ // Keyboard handling.
+ if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
+ ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ ERR_FAIL_NULL(ss->xkb_context);
+
+ ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);
+ } else {
+ if (ss->xkb_context) {
+ xkb_context_unref(ss->xkb_context);
+ ss->xkb_context = nullptr;
+ }
+
+ if (ss->wl_keyboard) {
+ wl_keyboard_destroy(ss->wl_keyboard);
+ ss->wl_keyboard = nullptr;
+ }
+ }
+}
+
+void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) {
+}
+
+void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) {
+ wl_callback_destroy(wl_callback);
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->cursor_time_ms = time_ms;
+
+ ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
+ wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
+ wl_surface_commit(ss->cursor_surface);
+
+ seat_state_update_cursor(ss);
+}
+
+void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ return;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD("Pointing window.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ERR_FAIL_NULL(ss->cursor_surface);
+ ss->pointer_enter_serial = serial;
+ ss->pointed_surface = surface;
+ ss->last_pointed_surface = surface;
+
+ seat_state_update_cursor(ss);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+
+ ss->wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
+ if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ return;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD("Left window.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->pointed_surface = nullptr;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ // TODO: Scale only when sending the Wayland message.
+ pd.position.x = wl_fixed_to_int(surface_x);
+ pd.position.y = wl_fixed_to_int(surface_y);
+
+ pd.position = scale_vector2i(pd.position, window_state_get_scale_factor(ws));
+
+ pd.motion_time = time;
+}
+
+void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ MouseButton button_pressed = MouseButton::NONE;
+
+ switch (button) {
+ case BTN_LEFT:
+ button_pressed = MouseButton::LEFT;
+ break;
+
+ case BTN_MIDDLE:
+ button_pressed = MouseButton::MIDDLE;
+ break;
+
+ case BTN_RIGHT:
+ button_pressed = MouseButton::RIGHT;
+ break;
+
+ case BTN_EXTRA:
+ button_pressed = MouseButton::MB_XBUTTON1;
+ break;
+
+ case BTN_SIDE:
+ button_pressed = MouseButton::MB_XBUTTON2;
+ break;
+
+ default: {
+ }
+ }
+
+ MouseButtonMask mask = mouse_button_to_mask(button_pressed);
+
+ if (state & WL_POINTER_BUTTON_STATE_PRESSED) {
+ pd.pressed_button_mask.set_flag(mask);
+ pd.last_button_pressed = button_pressed;
+ pd.double_click_begun = true;
+ } else {
+ pd.pressed_button_mask.clear_flag(mask);
+ }
+
+ pd.button_time = time;
+ pd.button_serial = serial;
+}
+
+void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ switch (axis) {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL: {
+ pd.scroll_vector.y = wl_fixed_to_double(value);
+ } break;
+
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
+ pd.scroll_vector.x = wl_fixed_to_double(value);
+ } break;
+ }
+
+ pd.button_time = time;
+}
+
+void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ wayland_thread->_set_current_seat(ss->wl_seat);
+
+ PointerData &old_pd = ss->pointer_data;
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {
+ Ref<InputEventMouseMotion> mm;
+ mm.instantiate();
+
+ // Set all pressed modifiers.
+ mm->set_shift_pressed(ss->shift_pressed);
+ mm->set_ctrl_pressed(ss->ctrl_pressed);
+ mm->set_alt_pressed(ss->alt_pressed);
+ mm->set_meta_pressed(ss->meta_pressed);
+
+ mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mm->set_button_mask(pd.pressed_button_mask);
+ mm->set_position(pd.position);
+ mm->set_global_position(pd.position);
+
+ Vector2i pos_delta = pd.position - old_pd.position;
+
+ if (old_pd.relative_motion_time != pd.relative_motion_time) {
+ uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;
+
+ mm->set_relative(pd.relative_motion);
+ mm->set_velocity((Vector2)pos_delta / time_delta);
+ } else {
+ // The spec includes the possibility of having motion events without an
+ // associated relative motion event. If that's the case, fallback to a
+ // simple delta of the position. The captured mouse won't report the
+ // relative speed anymore though.
+ uint32_t time_delta = pd.motion_time - old_pd.motion_time;
+
+ mm->set_relative(pd.position - old_pd.position);
+ mm->set_velocity((Vector2)pos_delta / time_delta);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mm;
+
+ wayland_thread->push_message(msg);
+ }
+
+ if (pd.discrete_scroll_vector - old_pd.discrete_scroll_vector != Vector2i()) {
+ // This is a discrete scroll (eg. from a scroll wheel), so we'll just emit
+ // scroll wheel buttons.
+ if (pd.scroll_vector.y != 0) {
+ MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP;
+ pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
+ }
+
+ if (pd.scroll_vector.x != 0) {
+ MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT;
+ pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
+ }
+ } else {
+ if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) {
+ // This is a continuous scroll, so we'll emit a pan gesture.
+ Ref<InputEventPanGesture> pg;
+ pg.instantiate();
+
+ // Set all pressed modifiers.
+ pg->set_shift_pressed(ss->shift_pressed);
+ pg->set_ctrl_pressed(ss->ctrl_pressed);
+ pg->set_alt_pressed(ss->alt_pressed);
+ pg->set_meta_pressed(ss->meta_pressed);
+
+ pg->set_position(pd.position);
+
+ pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ pg->set_delta(pd.scroll_vector);
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = pg;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+
+ if (old_pd.pressed_button_mask != pd.pressed_button_mask) {
+ BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((uint32_t)old_pd.pressed_button_mask ^ (uint32_t)pd.pressed_button_mask);
+
+ const MouseButton buttons_to_test[] = {
+ MouseButton::LEFT,
+ MouseButton::MIDDLE,
+ MouseButton::RIGHT,
+ MouseButton::WHEEL_UP,
+ MouseButton::WHEEL_DOWN,
+ MouseButton::WHEEL_LEFT,
+ MouseButton::WHEEL_RIGHT,
+ MouseButton::MB_XBUTTON1,
+ MouseButton::MB_XBUTTON2,
+ };
+
+ for (MouseButton test_button : buttons_to_test) {
+ MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
+ if (pressed_mask_delta.has_flag(test_button_mask)) {
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+
+ // Set all pressed modifiers.
+ mb->set_shift_pressed(ss->shift_pressed);
+ mb->set_ctrl_pressed(ss->ctrl_pressed);
+ mb->set_alt_pressed(ss->alt_pressed);
+ mb->set_meta_pressed(ss->meta_pressed);
+
+ mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mb->set_position(pd.position);
+ mb->set_global_position(pd.position);
+
+ if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {
+ // If this is a discrete scroll, specify how many "clicks" it did for this
+ // pointer frame.
+ mb->set_factor(abs(pd.discrete_scroll_vector.y));
+ }
+
+ if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) {
+ // If this is a discrete scroll, specify how many "clicks" it did for this
+ // pointer frame.
+ mb->set_factor(abs(pd.discrete_scroll_vector.x));
+ }
+
+ mb->set_button_mask(pd.pressed_button_mask);
+
+ mb->set_button_index(test_button);
+ mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask));
+
+ // We have to set the last position pressed here as we can't take for
+ // granted what the individual events might have seen due to them not having
+ // a garaunteed order.
+ if (mb->is_pressed()) {
+ pd.last_pressed_position = pd.position;
+ }
+
+ if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position).distance_to(Vector2(pd.last_pressed_position)) < 5) {
+ pd.double_click_begun = false;
+ mb->set_double_click(true);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mb;
+
+ wayland_thread->push_message(msg);
+
+ // Send an event resetting immediately the wheel key.
+ // Wayland specification defines axis_stop events as optional and says to
+ // treat all axis events as unterminated. As such, we have to manually do
+ // it ourselves.
+ if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) {
+ // FIXME: This is ugly, I can't find a clean way to clone an InputEvent.
+ // This works for now, despite being horrible.
+ Ref<InputEventMouseButton> wh_up;
+ wh_up.instantiate();
+
+ wh_up->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ wh_up->set_position(pd.position);
+ wh_up->set_global_position(pd.position);
+
+ // We have to unset the button to avoid it getting stuck.
+ pd.pressed_button_mask.clear_flag(test_button_mask);
+ wh_up->set_button_mask(pd.pressed_button_mask);
+
+ wh_up->set_button_index(test_button);
+ wh_up->set_pressed(false);
+
+ Ref<InputEventMessage> msg_up;
+ msg_up.instantiate();
+ msg_up->event = wh_up;
+ wayland_thread->push_message(msg_up);
+ }
+ }
+ }
+ }
+
+ // Reset the scroll vectors as we already handled them.
+ pd.scroll_vector = Vector2();
+ pd.discrete_scroll_vector = Vector2();
+
+ // Update the data all getters read. Wayland's specification requires us to do
+ // this, since all pointer actions are sent in individual events.
+ old_pd = pd;
+}
+
+void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ ss->pointer_data_buffer.scroll_type = axis_source;
+}
+
+void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {
+}
+
+void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ pd.discrete_scroll_vector.y = discrete;
+ }
+
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ pd.discrete_scroll_vector.x = discrete;
+ }
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {
+}
+
+void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {
+ ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->keymap_buffer) {
+ // We have already a mapped buffer, so we unmap it. There's no need to reset
+ // its pointer or size, as we're gonna set them below.
+ munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
+ ss->keymap_buffer = nullptr;
+ }
+
+ ss->keymap_buffer = (const char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ss->keymap_buffer_size = size;
+
+ xkb_keymap_unref(ss->xkb_keymap);
+ ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer,
+ XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ xkb_state_unref(ss->xkb_state);
+ ss->xkb_state = xkb_state_new(ss->xkb_keymap);
+}
+
+void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ wayland_thread->_set_current_seat(ss->wl_seat);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->repeating_keycode = XKB_KEYCODE_INVALID;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ // We have to add 8 to the scancode to get an XKB-compatible keycode.
+ xkb_keycode_t xkb_keycode = key + 8;
+
+ bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED;
+
+ if (pressed) {
+ if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) {
+ ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec();
+ ss->repeating_keycode = xkb_keycode;
+ }
+
+ ss->last_key_pressed_serial = serial;
+ } else if (ss->repeating_keycode == xkb_keycode) {
+ ss->repeating_keycode = XKB_KEYCODE_INVALID;
+ }
+
+ Ref<InputEventKey> k;
+ k.instantiate();
+
+ if (!_seat_state_configure_key_event(*ss, k, xkb_keycode, pressed)) {
+ return;
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+ msg->event = k;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
+ 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->current_layout_index = group;
+}
+
+void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->repeat_key_delay_msec = 1000 / rate;
+ ss->repeat_start_delay_msec = delay;
+}
+
+// NOTE: Don't forget to `memfree` the offer's state.
+void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
+ wl_proxy_tag_godot((struct wl_proxy *)id);
+ wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState));
+}
+
+void WaylandThread::_wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->dnd_enter_serial = serial;
+ ss->wl_data_offer_dnd = id;
+
+ // Godot only supports DnD file copying for now.
+ wl_data_offer_accept(id, serial, "text/uri-list");
+ wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
+}
+
+void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_offer_dnd) {
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
+ wl_data_offer_destroy(ss->wl_data_offer_dnd);
+ ss->wl_data_offer_dnd = nullptr;
+ }
+}
+
+void WaylandThread::_wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
+}
+
+void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd);
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ Ref<DropFilesEventMessage> msg;
+ msg.instantiate();
+
+ Vector<uint8_t> list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);
+
+ msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false);
+ for (int i = 0; i < msg->files.size(); i++) {
+ msg->files.write[i] = msg->files[i].replace("file://", "").uri_decode();
+ }
+
+ wayland_thread->push_message(msg);
+
+ wl_data_offer_finish(ss->wl_data_offer_dnd);
+ }
+
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
+ wl_data_offer_destroy(ss->wl_data_offer_dnd);
+ ss->wl_data_offer_dnd = nullptr;
+}
+
+void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_offer_selection) {
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection));
+ wl_data_offer_destroy(ss->wl_data_offer_selection);
+ }
+
+ ss->wl_data_offer_selection = id;
+}
+
+void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) {
+ OfferState *os = (OfferState *)data;
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ os->mime_types.insert(String::utf8(mime_type));
+ }
+}
+
+void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {
+}
+
+void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {
+}
+
+void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) {
+}
+
+void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ Vector<uint8_t> *data_to_send = nullptr;
+
+ if (wl_data_source == ss->wl_data_source_selection) {
+ data_to_send = &ss->selection_data;
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection.");
+ }
+
+ if (data_to_send) {
+ ssize_t written_bytes = 0;
+
+ bool valid_mime = false;
+
+ if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {
+ valid_mime = true;
+ } else if (strcmp(mime_type, "text/plain") == 0) {
+ valid_mime = true;
+ }
+
+ if (valid_mime) {
+ written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
+ }
+
+ if (written_bytes > 0) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
+ } else if (written_bytes == 0) {
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
+ } else {
+ ERR_PRINT(vformat("Clipboard: write error %d.", errno));
+ }
+ }
+
+ close(fd);
+}
+
+void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ wl_data_source_destroy(wl_data_source);
+
+ if (wl_data_source == ss->wl_data_source_selection) {
+ ss->wl_data_source_selection = nullptr;
+ ss->selection_data.clear();
+
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program.");
+ return;
+ }
+}
+
+void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) {
+}
+
+void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) {
+}
+
+void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {
+}
+
+void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->preferred_fractional_scale = (double)scale / 120;
+
+ window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
+}
+
+void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ pd.relative_motion.x = wl_fixed_to_double(dx);
+ pd.relative_motion.y = wl_fixed_to_double(dy);
+
+ pd.relative_motion_time = uptime_lo;
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (fingers == 2) {
+ ss->old_pinch_scale = wl_fixed_from_int(1);
+ ss->active_gesture = Gesture::MAGNIFY;
+ }
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (ss->active_gesture == Gesture::MAGNIFY) {
+ Ref<InputEventMagnifyGesture> mg;
+ mg.instantiate();
+
+ mg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ mg->set_shift_pressed(ss->shift_pressed);
+ mg->set_ctrl_pressed(ss->ctrl_pressed);
+ mg->set_alt_pressed(ss->alt_pressed);
+ mg->set_meta_pressed(ss->meta_pressed);
+
+ mg->set_position(pd.position);
+
+ wl_fixed_t scale_delta = scale - ss->old_pinch_scale;
+ mg->set_factor(1 + wl_fixed_to_double(scale_delta));
+
+ Ref<InputEventMessage> magnify_msg;
+ magnify_msg.instantiate();
+ magnify_msg->event = mg;
+
+ // Since Wayland allows only one gesture at a time and godot instead expects
+ // both of them, we'll have to create two separate input events: one for
+ // magnification and one for panning.
+
+ Ref<InputEventPanGesture> pg;
+ pg.instantiate();
+
+ pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ pg->set_shift_pressed(ss->shift_pressed);
+ pg->set_ctrl_pressed(ss->ctrl_pressed);
+ pg->set_alt_pressed(ss->alt_pressed);
+ pg->set_meta_pressed(ss->meta_pressed);
+
+ pg->set_position(pd.position);
+ pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));
+
+ Ref<InputEventMessage> pan_msg;
+ pan_msg.instantiate();
+ pan_msg->event = pg;
+
+ wayland_thread->push_message(magnify_msg);
+ wayland_thread->push_message(pan_msg);
+
+ ss->old_pinch_scale = scale;
+ }
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->active_gesture = Gesture::NONE;
+}
+
+// NOTE: Don't forget to `memfree` the offer's state.
+void WaylandThread::_wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer) {
+ wl_proxy_tag_godot((struct wl_proxy *)offer);
+ zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState));
+}
+
+void WaylandThread::_wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_primary_selection_offer) {
+ memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
+ zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
+ }
+
+ ss->wp_primary_selection_offer = id;
+}
+
+void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type) {
+ OfferState *os = (OfferState *)data;
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ os->mime_types.insert(String::utf8(mime_type));
+ }
+}
+
+void WaylandThread::_wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ Vector<uint8_t> *data_to_send = nullptr;
+
+ if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
+ data_to_send = &ss->primary_data;
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection.");
+ }
+
+ if (data_to_send) {
+ ssize_t written_bytes = 0;
+
+ if (strcmp(mime_type, "text/plain") == 0) {
+ written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
+ }
+
+ if (written_bytes > 0) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
+ } else if (written_bytes == 0) {
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
+ } else {
+ ERR_PRINT(vformat("Clipboard: write error %d.", errno));
+ }
+ }
+
+ close(fd);
+}
+
+void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
+ zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
+ ss->wp_primary_selection_source = nullptr;
+
+ ss->primary_data.clear();
+
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program.");
+ return;
+ }
+}
+
+void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tablet %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tools.push_back(id);
+
+ zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, ss);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tool %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on pad %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on type %d", (size_t)zwp_tablet_tool_v2, tool_type));
+}
+
+void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware serial %x%x", (size_t)zwp_tablet_tool_v2, hardware_serial_hi, hardware_serial_lo));
+}
+
+void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware id wacom hardware id %x%x", (size_t)zwp_tablet_tool_v2, hardware_id_hi, hardware_id_lo));
+}
+
+void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (capability == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {
+ ss->tablet_tool_data_buffer.is_eraser = true;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on capability %d", (size_t)zwp_tablet_tool_v2, capability));
+}
+
+void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on done", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
+
+ while (it) {
+ struct zwp_tablet_tool_v2 *tool = it->get();
+
+ if (tool == zwp_tablet_tool_v2) {
+ zwp_tablet_tool_v2_destroy(tool);
+ ss->tablet_tools.erase(it);
+ break;
+ }
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on removed", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->tablet_tool_data_buffer.in_proximity = true;
+
+ ss->pointer_enter_serial = serial;
+ ss->pointed_surface = surface;
+ ss->last_pointed_surface = surface;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+ wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window.");
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity in serial %d tablet %x surface %x", (size_t)zwp_tablet_tool_v2, serial, (size_t)tablet, (size_t)surface));
+}
+
+void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->pointed_surface = nullptr;
+ ss->tablet_tool_data_buffer.in_proximity = false;
+
+ DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window.");
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity out", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.touching = true;
+ td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));
+ td.last_button_pressed = MouseButton::LEFT;
+ td.double_click_begun = true;
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on down serial %x", (size_t)zwp_tablet_tool_v2, serial));
+}
+
+void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.touching = false;
+ td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on up", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
+ double scale_factor = window_state_get_scale_factor(ws);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.position = scale_vector2i(td.position, scale_factor);
+}
+
+void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tool_data_buffer.pressure = pressure;
+}
+
+void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance) {
+ // Unsupported
+}
+
+void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tool_data_buffer.tilt.x = wl_fixed_to_double(tilt_x);
+ ss->tablet_tool_data_buffer.tilt.y = wl_fixed_to_double(tilt_y);
+}
+
+void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees) {
+ // Unsupported.
+}
+
+void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position) {
+ // Unsupported.
+}
+
+void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) {
+ // TODO
+}
+
+void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ MouseButton mouse_button = MouseButton::NONE;
+
+ if (button == BTN_STYLUS) {
+ mouse_button = MouseButton::LEFT;
+ }
+
+ if (button == BTN_STYLUS2) {
+ mouse_button = MouseButton::RIGHT;
+ }
+
+ if (mouse_button != MouseButton::NONE) {
+ MouseButtonMask mask = mouse_button_to_mask(mouse_button);
+
+ if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) {
+ td.pressed_button_mask.set_flag(mask);
+ td.last_button_pressed = mouse_button;
+ td.double_click_begun = true;
+ } else {
+ td.pressed_button_mask.clear_flag(mask);
+ }
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+ }
+}
+
+void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ TabletToolData &old_td = ss->tablet_tool_data;
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {
+ Ref<InputEventMouseMotion> mm;
+ mm.instantiate();
+
+ mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ mm->set_shift_pressed(ss->shift_pressed);
+ mm->set_ctrl_pressed(ss->ctrl_pressed);
+ mm->set_alt_pressed(ss->alt_pressed);
+ mm->set_meta_pressed(ss->meta_pressed);
+
+ mm->set_button_mask(td.pressed_button_mask);
+
+ mm->set_position(td.position);
+ mm->set_global_position(td.position);
+
+ // NOTE: The Godot API expects normalized values and we store them raw,
+ // straight from the compositor, so we have to normalize them here.
+
+ // According to the tablet proto spec, tilt is expressed in degrees relative
+ // to the Z axis of the tablet, so it shouldn't go over 90 degrees, I think.
+ // TODO: Investigate whether the tilt can go over 90 degrees (it shouldn't).
+ mm->set_tilt(td.tilt / 90);
+
+ // The tablet proto spec explicitly says that pressure is defined as a value
+ // between 0 to 65535.
+ mm->set_pressure(td.pressure / (float)65535);
+
+ // FIXME: Tool handling is broken.
+ mm->set_pen_inverted(td.is_eraser);
+
+ mm->set_relative(td.position - old_td.position);
+
+ // FIXME: Stop doing this to calculate speed.
+ // FIXME2: It has been done, port this from the pointer logic once this works again.
+ Input::get_singleton()->set_mouse_position(td.position);
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
+
+ Ref<InputEventMessage> inputev_msg;
+ inputev_msg.instantiate();
+
+ inputev_msg->event = mm;
+
+ wayland_thread->push_message(inputev_msg);
+ }
+
+ if (old_td.pressed_button_mask != td.pressed_button_mask) {
+ BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((int64_t)old_td.pressed_button_mask ^ (int64_t)td.pressed_button_mask);
+
+ for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {
+ MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
+
+ if (pressed_mask_delta.has_flag(test_button_mask)) {
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+
+ // Set all pressed modifiers.
+ mb->set_shift_pressed(ss->shift_pressed);
+ mb->set_ctrl_pressed(ss->ctrl_pressed);
+ mb->set_alt_pressed(ss->alt_pressed);
+ mb->set_meta_pressed(ss->meta_pressed);
+
+ mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mb->set_position(td.position);
+ mb->set_global_position(td.position);
+
+ mb->set_button_mask(td.pressed_button_mask);
+ mb->set_button_index(test_button);
+ mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask));
+
+ // We have to set the last position pressed here as we can't take for
+ // granted what the individual events might have seen due to them not having
+ // a garaunteed order.
+ if (mb->is_pressed()) {
+ td.last_pressed_position = td.position;
+ }
+
+ if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position).distance_to(Vector2(old_td.last_pressed_position)) < 5) {
+ td.double_click_begun = false;
+ mb->set_double_click(true);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mb;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+ }
+
+ old_td = td;
+}
+
+void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+ ERR_FAIL_NULL(ws->wayland_thread);
+ ERR_FAIL_NULL(ws->wl_surface);
+
+ xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface);
+ xdg_activation_token_v1_destroy(xdg_activation_token);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));
+}
+
+// NOTE: This must be started after a valid wl_display is loaded.
+void WaylandThread::_poll_events_thread(void *p_data) {
+ ThreadData *data = (ThreadData *)p_data;
+ ERR_FAIL_NULL(data);
+ ERR_FAIL_NULL(data->wl_display);
+
+ struct pollfd poll_fd;
+ poll_fd.fd = wl_display_get_fd(data->wl_display);
+ poll_fd.events = POLLIN | POLLHUP;
+
+ while (true) {
+ // Empty the event queue while it's full.
+ while (wl_display_prepare_read(data->wl_display) != 0) {
+ // We aren't using wl_display_dispatch(), instead "manually" handling events
+ // through wl_display_dispatch_pending so that we can use a global mutex and
+ // be sure that this and the main thread won't race over stuff, as long as
+ // the main thread locks it too.
+ //
+ // Note that the main thread can still call wl_display_roundtrip as that
+ // method directly handles all events, effectively bypassing this polling
+ // loop and thus the mutex locking, avoiding a deadlock.
+ MutexLock mutex_lock(data->mutex);
+
+ if (wl_display_dispatch_pending(data->wl_display) == -1) {
+ // Oh no. We'll check and handle any display error below.
+ break;
+ }
+ }
+
+ int werror = wl_display_get_error(data->wl_display);
+
+ if (werror) {
+ if (werror == EPROTO) {
+ struct wl_interface *wl_interface = nullptr;
+ uint32_t id = 0;
+
+ int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);
+ CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
+ } else {
+ CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
+ }
+ }
+
+ wl_display_flush(data->wl_display);
+
+ // Wait for the event file descriptor to have new data.
+ poll(&poll_fd, 1, -1);
+
+ if (data->thread_done.is_set()) {
+ wl_display_cancel_read(data->wl_display);
+ break;
+ }
+
+ if (poll_fd.revents | POLLIN) {
+ // Load the queues with fresh new data.
+ wl_display_read_events(data->wl_display);
+ } else {
+ // Oh well... Stop signaling that we want to read.
+ wl_display_cancel_read(data->wl_display);
+ }
+
+ // The docs advise to redispatch unconditionally and it looks like that if we
+ // don't do this we can't catch protocol errors, which is bad.
+ MutexLock mutex_lock(data->mutex);
+ wl_display_dispatch_pending(data->wl_display);
+ }
+}
+
+struct wl_display *WaylandThread::get_wl_display() const {
+ return wl_display;
+}
+
+// NOTE: Stuff like libdecor can (and will) register foreign proxies which
+// aren't formatted as we like. This method is needed to detect whether a proxy
+// has our tag. Also, be careful! The proxy has to be manually tagged or it
+// won't be recognized.
+bool WaylandThread::wl_proxy_is_godot(struct wl_proxy *p_proxy) {
+ ERR_FAIL_NULL_V(p_proxy, false);
+
+ return wl_proxy_get_tag(p_proxy) == &proxy_tag;
+}
+
+void WaylandThread::wl_proxy_tag_godot(struct wl_proxy *p_proxy) {
+ ERR_FAIL_NULL(p_proxy);
+
+ wl_proxy_set_tag(p_proxy, &proxy_tag);
+}
+
+// Returns the wl_surface's `WindowState`, otherwise `nullptr`.
+// NOTE: This will fail if the surface isn't tagged as ours.
+WaylandThread::WindowState *WaylandThread::wl_surface_get_window_state(struct wl_surface *p_surface) {
+ if (p_surface && wl_proxy_is_godot((wl_proxy *)p_surface)) {
+ return (WindowState *)wl_surface_get_user_data(p_surface);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_outputs's `ScreenState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::ScreenState *WaylandThread::wl_output_get_screen_state(struct wl_output *p_output) {
+ if (p_output && wl_proxy_is_godot((wl_proxy *)p_output)) {
+ return (ScreenState *)wl_output_get_user_data(p_output);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_seat's `SeatState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *p_seat) {
+ if (p_seat && wl_proxy_is_godot((wl_proxy *)p_seat)) {
+ return (SeatState *)wl_seat_get_user_data(p_seat);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {
+ if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
+ return (OfferState *)wl_data_offer_get_user_data(p_offer);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer) {
+ if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
+ return (OfferState *)zwp_primary_selection_offer_v1_get_user_data(p_offer);
+ }
+
+ 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) {
+ ERR_FAIL_NULL_V(p_ws, 1);
+
+ if (p_ws->preferred_fractional_scale > 0) {
+ // We're scaling fractionally. Per spec, the buffer scale is always 1.
+ return 1;
+ }
+
+ if (p_ws->wl_outputs.is_empty()) {
+ DEBUG_LOG_WAYLAND_THREAD("Window has no output associated, returning buffer scale of 1.");
+ return 1;
+ }
+
+ // TODO: Cache value?
+ int max_size = 1;
+
+ // ================================ IMPORTANT =================================
+ // NOTE: Due to a Godot limitation, we can't really rescale the whole UI yet.
+ // Because of this reason, all platforms have resorted to forcing the highest
+ // scale possible of a system on any window, despite of what screen it's onto.
+ // On this backend everything's already in place for dynamic window scale
+ // handling, but in the meantime we'll just select the biggest _global_ output.
+ // To restore dynamic scale selection, simply iterate over `p_ws->wl_outputs`
+ // instead.
+ for (struct wl_output *wl_output : p_ws->registry->wl_outputs) {
+ ScreenState *ss = wl_output_get_screen_state(wl_output);
+
+ if (ss && ss->pending_data.scale > max_size) {
+ // NOTE: For some mystical reason, wl_output.done is emitted _after_ windows
+ // get resized but the scale event gets sent _before_ that. I'm still leaning
+ // towards the idea that rescaling when a window gets a resolution change is a
+ // pretty good approach, but this means that we'll have to use the screen data
+ // before it's "committed".
+ // FIXME: Use the committed data. Somehow.
+ max_size = ss->pending_data.scale;
+ }
+ }
+
+ return max_size;
+}
+
+double WaylandThread::window_state_get_scale_factor(WindowState *p_ws) {
+ ERR_FAIL_NULL_V(p_ws, 1);
+
+ if (p_ws->fractional_scale > 0) {
+ // The fractional scale amount takes priority.
+ return p_ws->fractional_scale;
+ }
+
+ return p_ws->buffer_scale;
+}
+
+void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {
+ ERR_FAIL_NULL(p_ws);
+
+ int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);
+ bool using_fractional = p_ws->preferred_fractional_scale > 0;
+
+ // If neither is true we no-op.
+ bool scale_changed = false;
+ bool size_changed = false;
+
+ if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {
+ p_ws->rect.size.width = p_width;
+ p_ws->rect.size.height = p_height;
+
+ size_changed = true;
+ }
+
+ if (using_fractional && p_ws->fractional_scale != p_ws->preferred_fractional_scale) {
+ p_ws->fractional_scale = p_ws->preferred_fractional_scale;
+ scale_changed = true;
+ }
+
+ if (p_ws->buffer_scale != preferred_buffer_scale) {
+ // The buffer scale is always important, even if we use frac scaling.
+ p_ws->buffer_scale = preferred_buffer_scale;
+ p_ws->buffer_scale_changed = true;
+
+ if (!using_fractional) {
+ // We don't bother updating everything else if it's turned on though.
+ scale_changed = true;
+ }
+ }
+
+ if (p_ws->wl_surface && (size_changed || scale_changed)) {
+ if (p_ws->wp_viewport) {
+ wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);
+ }
+
+ if (p_ws->xdg_surface) {
+ xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height);
+ }
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (p_ws->libdecor_frame) {
+ struct libdecor_state *state = libdecor_state_new(p_width, p_height);
+ libdecor_frame_commit(p_ws->libdecor_frame, state, p_ws->pending_libdecor_configuration);
+ libdecor_state_free(state);
+ p_ws->pending_libdecor_configuration = nullptr;
+ }
+#endif
+
+ if (size_changed || scale_changed) {
+ Size2i scaled_size = scale_vector2i(p_ws->rect.size, window_state_get_scale_factor(p_ws));
+
+ if (using_fractional) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (fractional scale x%f).", p_ws->rect.size, scaled_size, p_ws->fractional_scale));
+ } else {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (buffer scale x%d).", p_ws->rect.size, scaled_size, p_ws->buffer_scale));
+ }
+
+ // FIXME: Actually resize the hint instead of centering it.
+ p_ws->wayland_thread->pointer_set_hint(scaled_size / 2);
+
+ Ref<WindowRectMessage> rect_msg;
+ rect_msg.instantiate();
+ rect_msg->rect = p_ws->rect;
+ rect_msg->rect.size = scaled_size;
+ p_ws->wayland_thread->push_message(rect_msg);
+ }
+
+ if (scale_changed) {
+ Ref<WindowEventMessage> dpi_msg;
+ dpi_msg.instantiate();
+ dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;
+ p_ws->wayland_thread->push_message(dpi_msg);
+ }
+}
+
+// Scales a vector according to wp_fractional_scale's rules, where coordinates
+// must be scaled with away from zero half-rounding.
+Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {
+ // This snippet is tiny, I know, but this is done a lot.
+ int x = round(p_vector.x * p_amount);
+ int y = round(p_vector.y * p_amount);
+
+ return Vector2i(x, y);
+}
+
+void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(p_ss->wp_locked_pointer);
+ p_ss->wp_locked_pointer = nullptr;
+ }
+
+ if (p_ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);
+ p_ss->wp_confined_pointer = nullptr;
+ }
+}
+
+void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (registry.wp_pointer_constraints == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_locked_pointer == nullptr) {
+ struct wl_surface *locked_surface = p_ss->last_pointed_surface;
+
+ 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, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
+}
+
+void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {
+ if (p_ss->wp_locked_pointer == nullptr) {
+ return;
+ }
+
+ zwp_locked_pointer_v1_set_cursor_position_hint(p_ss->wp_locked_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y));
+}
+
+void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (registry.wp_pointer_constraints == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_confined_pointer == nullptr) {
+ struct wl_surface *confined_surface = p_ss->last_pointed_surface;
+
+ if (confined_surface == nullptr) {
+ confined_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
+ }
+
+ ERR_FAIL_NULL(confined_surface);
+
+ p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
+}
+
+void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+ ERR_FAIL_NULL(p_ss->wayland_thread);
+
+ if (p_ss->wl_pointer && p_ss->cursor_surface) {
+ // NOTE: Those values are valid by default and will hide the cursor when
+ // unchanged, which happens when both the current custom cursor and the
+ // current wl_cursor are `nullptr`.
+ struct wl_buffer *cursor_buffer = nullptr;
+ uint32_t hotspot_x = 0;
+ uint32_t hotspot_y = 0;
+ int scale = 1;
+
+ CustomCursor *custom_cursor = p_ss->wayland_thread->current_custom_cursor;
+ struct wl_cursor *wl_cursor = p_ss->wayland_thread->current_wl_cursor;
+
+ if (custom_cursor) {
+ cursor_buffer = custom_cursor->wl_buffer;
+ hotspot_x = custom_cursor->hotspot.x;
+ hotspot_y = custom_cursor->hotspot.y;
+
+ // We can't really reasonably scale custom cursors, so we'll let the
+ // compositor do it for us (badly).
+ scale = 1;
+ } else if (wl_cursor) {
+ int frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);
+
+ struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];
+
+ scale = p_ss->wayland_thread->cursor_scale;
+
+ cursor_buffer = wl_cursor_image_get_buffer(wl_cursor_image);
+
+ // As the surface's buffer is scaled (thus the surface is smaller) and the
+ // hotspot must be expressed in surface-local coordinates, we need to scale
+ // them down accordingly.
+ hotspot_x = wl_cursor_image->hotspot_x / scale;
+ hotspot_y = wl_cursor_image->hotspot_y / scale;
+ }
+
+ wl_pointer_set_cursor(p_ss->wl_pointer, p_ss->pointer_enter_serial, p_ss->cursor_surface, hotspot_x, hotspot_y);
+ wl_surface_set_buffer_scale(p_ss->cursor_surface, scale);
+ wl_surface_attach(p_ss->cursor_surface, cursor_buffer, 0, 0);
+ wl_surface_damage_buffer(p_ss->cursor_surface, 0, 0, INT_MAX, INT_MAX);
+
+ wl_surface_commit(p_ss->cursor_surface);
+ }
+}
+
+void WaylandThread::seat_state_echo_keys(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_keyboard == nullptr) {
+ return;
+ }
+
+ // TODO: Comment and document out properly this block of code.
+ // In short, this implements key repeating.
+ if (p_ss->repeat_key_delay_msec && p_ss->repeating_keycode != XKB_KEYCODE_INVALID) {
+ uint64_t current_ticks = OS::get_singleton()->get_ticks_msec();
+ uint64_t delayed_start_ticks = p_ss->last_repeat_start_msec + p_ss->repeat_start_delay_msec;
+
+ if (p_ss->last_repeat_msec < delayed_start_ticks) {
+ p_ss->last_repeat_msec = delayed_start_ticks;
+ }
+
+ if (current_ticks >= delayed_start_ticks) {
+ uint64_t ticks_delta = current_ticks - p_ss->last_repeat_msec;
+
+ int keys_amount = (ticks_delta / p_ss->repeat_key_delay_msec);
+
+ for (int i = 0; i < keys_amount; i++) {
+ Ref<InputEventKey> k;
+ k.instantiate();
+
+ if (!_seat_state_configure_key_event(*p_ss, k, p_ss->repeating_keycode, true)) {
+ continue;
+ }
+
+ k->set_echo(true);
+
+ Input::get_singleton()->parse_input_event(k);
+ }
+
+ p_ss->last_repeat_msec += ticks_delta - (ticks_delta % p_ss->repeat_key_delay_msec);
+ }
+ }
+}
+
+void WaylandThread::push_message(Ref<Message> message) {
+ messages.push_back(message);
+}
+
+bool WaylandThread::has_message() {
+ return messages.front() != nullptr;
+}
+
+Ref<WaylandThread::Message> WaylandThread::pop_message() {
+ if (messages.front() != nullptr) {
+ Ref<Message> msg = messages.front()->get();
+ messages.pop_front();
+ return msg;
+ }
+
+ // This method should only be called if `has_messages` returns true but if
+ // that isn't the case we'll just return an invalid `Ref`. After all, due to
+ // its `InputEvent`-like interface, we still have to dynamically cast and check
+ // the `Ref`'s validity anyways.
+ return Ref<Message>();
+}
+
+void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
+ // TODO: Implement multi-window support.
+ WindowState &ws = main_window;
+
+ ws.registry = &registry;
+ ws.wayland_thread = this;
+
+ ws.rect.size.width = p_width;
+ ws.rect.size.height = p_height;
+
+ ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
+ wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
+ wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);
+
+ if (registry.wp_viewporter) {
+ ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);
+
+ if (registry.wp_fractional_scale_manager) {
+ ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);
+ wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);
+ }
+ }
+
+ bool decorated = false;
+
+#ifdef LIBDECOR_ENABLED
+ if (!decorated && libdecor_context) {
+ ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws);
+ libdecor_frame_map(ws.libdecor_frame);
+
+ decorated = true;
+ }
+#endif
+
+ if (!decorated) {
+ // libdecor has failed loading or is disabled, we shall handle xdg_toplevel
+ // creation and decoration ourselves (and by decorating for now I just mean
+ // asking for SSDs and hoping for the best).
+ ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);
+ xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);
+
+ ws.xdg_toplevel = xdg_surface_get_toplevel(ws.xdg_surface);
+ xdg_toplevel_add_listener(ws.xdg_toplevel, &xdg_toplevel_listener, &ws);
+
+ if (registry.xdg_decoration_manager) {
+ ws.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(registry.xdg_decoration_manager, ws.xdg_toplevel);
+ zxdg_toplevel_decoration_v1_add_listener(ws.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &ws);
+
+ decorated = true;
+ }
+ }
+
+ ws.frame_callback = wl_surface_frame(ws.wl_surface);
+ wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
+
+ // NOTE: This commit is only called once to start the whole frame callback
+ // "loop".
+ wl_surface_commit(ws.wl_surface);
+
+ if (registry.wl_exporter) {
+ ws.xdg_exported = zxdg_exporter_v1_export(registry.wl_exporter, ws.wl_surface);
+ zxdg_exported_v1_add_listener(ws.xdg_exported, &xdg_exported_listener, &ws);
+ }
+
+ // Wait for the surface to be configured before continuing.
+ wl_display_roundtrip(wl_display);
+}
+
+struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.wl_surface;
+}
+
+void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ Vector2i logical_max_size = p_size / window_state_get_scale_factor(&ws);
+
+ if (ws.wl_surface && ws.xdg_toplevel) {
+ xdg_toplevel_set_max_size(ws.xdg_toplevel, logical_max_size.width, logical_max_size.height);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_max_content_size(ws.libdecor_frame, logical_max_size.width, logical_max_size.height);
+ }
+
+ // FIXME: I'm not sure whether we have to commit the surface for this to apply.
+#endif
+}
+
+void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ Size2i logical_min_size = p_size / window_state_get_scale_factor(&ws);
+
+ if (ws.wl_surface && ws.xdg_toplevel) {
+ xdg_toplevel_set_min_size(ws.xdg_toplevel, logical_min_size.width, logical_min_size.height);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_min_content_size(ws.libdecor_frame, logical_min_size.width, logical_min_size.height);
+ }
+
+ // FIXME: I'm not sure whether we have to commit the surface for this to apply.
+#endif
+}
+
+bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ switch (p_window_mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Looks like it's guaranteed.
+ return true;
+ };
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_MINIMIZE);
+ }
+#endif // LIBDECOR_ENABLED
+
+ return ws.can_minimize;
+ };
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ // NOTE: libdecor doesn't seem to have a maximize capability query?
+ // The fact that there's a fullscreen one makes me suspicious.
+ return ws.can_maximize;
+ };
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN: {
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN);
+ }
+#endif // LIBDECOR_ENABLED
+
+ return ws.can_fullscreen;
+ };
+
+ case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
+ // I'm not really sure but from what I can find Wayland doesn't really have
+ // the concept of exclusive fullscreen.
+ // TODO: Discuss whether to fallback to regular fullscreen or not.
+ return false;
+ };
+ }
+
+ return false;
+}
+
+void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (ws.mode == p_window_mode) {
+ return;
+ }
+
+ // Don't waste time with hidden windows and whatnot. Behave like it worked.
+#ifdef LIBDECOR_ENABLED
+ if ((!ws.wl_surface || !ws.xdg_toplevel) && !ws.libdecor_frame) {
+#else
+ if (!ws.wl_surface || !ws.xdg_toplevel) {
+#endif // LIBDECOR_ENABLED
+ ws.mode = p_window_mode;
+ return;
+ }
+
+ // Return back to a windowed state so that we can apply what the user asked.
+ switch (ws.mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Do nothing.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+ // We can't do much according to the xdg_shell protocol. I have no idea
+ // whether this implies that we should return or who knows what. For now
+ // we'll do nothing.
+ // TODO: Test this properly.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ // Try to unmaximize. This isn't garaunteed to work actually, so we'll have
+ // to check whether something changed.
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_unset_maximized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_unset_maximized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN:
+ case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
+ // Same thing as above, unset fullscreen and check later if it worked.
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_unset_fullscreen(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_unset_fullscreen(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+ }
+
+ // Wait for a configure event and hope that something changed.
+ wl_display_roundtrip(wl_display);
+
+ if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {
+ // The compositor refused our "normalization" request. It'd be useless or
+ // unpredictable to attempt setting a new state. We're done.
+ return;
+ }
+
+ // Ask the compositor to set the state indicated by the new mode.
+ switch (p_window_mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Do nothing. We're already windowed.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+ if (!window_can_set_mode(p_window_id, p_window_mode)) {
+ // Minimization is special (read below). Better not mess with it if the
+ // compositor explicitly announces that it doesn't support it.
+ break;
+ }
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_minimized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_minimized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ // We have no way to actually detect this state, so we'll have to report it
+ // manually to the engine (hoping that it worked). In the worst case it'll
+ // get reset by the next configure event.
+ ws.mode = DisplayServer::WINDOW_MODE_MINIMIZED;
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_maximized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_maximized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN: {
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_fullscreen(ws.xdg_toplevel, nullptr);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_fullscreen(ws.libdecor_frame, nullptr);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ default: {
+ } break;
+ }
+}
+
+void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (ws.xdg_toplevel_decoration) {
+ if (p_borderless) {
+ // We implement borderless windows by simply asking the compositor to let
+ // us handle decorations (we don't).
+ zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
+ } else {
+ zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ }
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ bool visible_current = libdecor_frame_is_visible(ws.libdecor_frame);
+ bool visible_target = !p_borderless;
+
+ // NOTE: We have to do this otherwise we trip on a libdecor bug where it's
+ // possible to destroy the frame more than once, by setting the visibility
+ // to false multiple times and thus crashing.
+ if (visible_current != visible_target) {
+ print_verbose(vformat("Setting libdecor frame visibility to %d", visible_target));
+ libdecor_frame_set_visibility(ws.libdecor_frame, visible_target);
+ }
+ }
+#endif // LIBDECOR_ENABLED
+}
+
+void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_title(ws.libdecor_frame, p_title.utf8());
+ }
+#endif // LIBDECOR_ENABLE
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_title(ws.xdg_toplevel, p_title.utf8());
+ }
+}
+
+void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_app_id(ws.libdecor_frame, p_app_id.utf8());
+ return;
+ }
+#endif // LIBDECOR_ENABLED
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_app_id(ws.xdg_toplevel, p_app_id.utf8());
+ return;
+ }
+}
+
+DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.mode;
+}
+
+void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (registry.xdg_activation) {
+ // Window attention requests are done through the XDG activation protocol.
+ xdg_activation_token_v1 *xdg_activation_token = xdg_activation_v1_get_activation_token(registry.xdg_activation);
+ xdg_activation_token_v1_add_listener(xdg_activation_token, &xdg_activation_token_listener, &ws);
+ xdg_activation_token_v1_commit(xdg_activation_token);
+ }
+}
+
+void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (p_enable) {
+ if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {
+ ERR_FAIL_NULL(ws.wl_surface);
+ ws.wp_idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(ws.registry->wp_idle_inhibit_manager, ws.wl_surface);
+ }
+ } else {
+ if (ws.wp_idle_inhibitor) {
+ zwp_idle_inhibitor_v1_destroy(ws.wp_idle_inhibitor);
+ ws.wp_idle_inhibitor = nullptr;
+ }
+ }
+}
+
+bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.wp_idle_inhibitor != nullptr;
+}
+
+WaylandThread::ScreenData WaylandThread::screen_get_data(int p_screen) const {
+ ERR_FAIL_INDEX_V(p_screen, registry.wl_outputs.size(), ScreenData());
+
+ return wl_output_get_screen_state(registry.wl_outputs[p_screen])->data;
+}
+
+int WaylandThread::get_screen_count() const {
+ return registry.wl_outputs.size();
+}
+
+DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+
+ if (ws) {
+ return ws->id;
+ }
+ }
+
+ return DisplayServer::INVALID_WINDOW_ID;
+}
+
+void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ seat_state_unlock_pointer(ss);
+
+ if (p_constraint == PointerConstraint::LOCKED) {
+ seat_state_lock_pointer(ss);
+ } else if (p_constraint == PointerConstraint::CONFINED) {
+ seat_state_confine_pointer(ss);
+ }
+ }
+
+ pointer_constraint = p_constraint;
+}
+
+void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (!ss) {
+ return;
+ }
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+
+ int hint_x = 0;
+ int hint_y = 0;
+
+ if (ws) {
+ // NOTE: It looks like it's not really recommended to convert from
+ // "godot-space" to "wayland-space" and in general I received mixed feelings
+ // discussing about this. I'm not really sure about the maths behind this but,
+ // oh well, we're setting a cursor hint. ¯\_(ツ)_/¯
+ // See: https://oftc.irclog.whitequark.org/wayland/2023-08-23#1692756914-1692816818
+ hint_x = round(p_hint.x / window_state_get_scale_factor(ws));
+ hint_y = round(p_hint.y / window_state_get_scale_factor(ws));
+ }
+
+ if (ss) {
+ seat_state_set_hint(ss, hint_x, hint_y);
+ }
+}
+
+WaylandThread::PointerConstraint WaylandThread::pointer_get_constraint() const {
+ return pointer_constraint;
+}
+
+BitField<MouseButtonMask> WaylandThread::pointer_get_button_mask() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ return ss->pointer_data.pressed_button_mask;
+ }
+
+ return BitField<MouseButtonMask>();
+}
+
+Error WaylandThread::init() {
+#ifdef SOWRAP_ENABLED
+#ifdef DEBUG_ENABLED
+ int dylibloader_verbose = 1;
+#else
+ int dylibloader_verbose = 0;
+#endif // DEBUG_ENABLED
+
+ if (initialize_wayland_client(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland client library.");
+ return ERR_CANT_CREATE;
+ }
+
+ if (initialize_wayland_cursor(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland cursor library.");
+ return ERR_CANT_CREATE;
+ }
+
+ if (initialize_xkbcommon(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the XKBcommon library.");
+ return ERR_CANT_CREATE;
+ }
+#endif // SOWRAP_ENABLED
+
+ KeyMappingXKB::initialize();
+
+ wl_display = wl_display_connect(nullptr);
+ ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");
+
+ thread_data.wl_display = wl_display;
+
+ events_thread.start(_poll_events_thread, &thread_data);
+
+ wl_registry = wl_display_get_registry(wl_display);
+
+ ERR_FAIL_NULL_V_MSG(wl_registry, ERR_UNAVAILABLE, "Can't obtain the Wayland registry global.");
+
+ registry.wayland_thread = this;
+
+ wl_registry_add_listener(wl_registry, &wl_registry_listener, &registry);
+
+ // Wait for registry to get notified from the compositor.
+ wl_display_roundtrip(wl_display);
+
+ ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_subcompositor, ERR_UNAVAILABLE, "Can't obtain the Wayland subcompositor global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_data_device_manager, ERR_UNAVAILABLE, "Can't obtain the Wayland data device manager global.");
+ ERR_FAIL_NULL_V_MSG(registry.wp_pointer_constraints, ERR_UNAVAILABLE, "Can't obtain the Wayland pointer constraints 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) {
+#ifdef LIBDECOR_ENABLED
+ 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.");
+#endif // LIBDECOR_ENABLED
+ }
+
+ if (!registry.xdg_activation) {
+ WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");
+ }
+
+#ifndef DBUS_ENABLED
+ if (!registry.wp_idle_inhibit_manager) {
+ WARN_PRINT("Can't obtain the idle inhibition manager. The screen might turn off even after calling screen_set_keep_on()!");
+ }
+#endif // DBUS_ENABLED
+
+ // Wait for seat capabilities.
+ wl_display_roundtrip(wl_display);
+
+#ifdef LIBDECOR_ENABLED
+ bool libdecor_found = true;
+
+#ifdef SOWRAP_ENABLED
+ if (initialize_libdecor(dylibloader_verbose) != 0) {
+ libdecor_found = false;
+ }
+#endif // SOWRAP_ENABLED
+
+ if (libdecor_found) {
+ libdecor_context = libdecor_new(wl_display, (struct libdecor_interface *)&libdecor_interface);
+ } else {
+ print_verbose("libdecor not found. Client-side decorations disabled.");
+ }
+#endif // LIBDECOR_ENABLED
+
+ cursor_theme_name = OS::get_singleton()->get_environment("XCURSOR_THEME");
+
+ unscaled_cursor_size = OS::get_singleton()->get_environment("XCURSOR_SIZE").to_int();
+ if (unscaled_cursor_size <= 0) {
+ print_verbose("Detected invalid cursor size preference, defaulting to 24.");
+ unscaled_cursor_size = 24;
+ }
+
+ // NOTE: The scale is useful here as it might've been updated by _update_scale.
+ bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale);
+
+ if (!cursor_theme_loaded) {
+ return ERR_CANT_CREATE;
+ }
+
+ // Update the cursor.
+ cursor_set_shape(DisplayServer::CURSOR_ARROW);
+
+ initialized = true;
+ return OK;
+}
+
+void WaylandThread::cursor_hide() {
+ current_wl_cursor = nullptr;
+ current_custom_cursor = nullptr;
+
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ ERR_FAIL_NULL(ss);
+ seat_state_update_cursor(ss);
+}
+
+void WaylandThread::cursor_set_shape(DisplayServer::CursorShape p_cursor_shape) {
+ if (!wl_cursors[p_cursor_shape]) {
+ return;
+ }
+
+ // The point of this method is make the current cursor a "plain" shape and, as
+ // the custom cursor overrides what gets set, we have to clear it too.
+ current_custom_cursor = nullptr;
+
+ current_wl_cursor = wl_cursors[p_cursor_shape];
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ seat_state_update_cursor(ss);
+ }
+
+ last_cursor_shape = p_cursor_shape;
+}
+
+void WaylandThread::cursor_set_custom_shape(DisplayServer::CursorShape p_cursor_shape) {
+ ERR_FAIL_COND(!custom_cursors.has(p_cursor_shape));
+
+ current_custom_cursor = &custom_cursors[p_cursor_shape];
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ seat_state_update_cursor(ss);
+ }
+
+ last_cursor_shape = p_cursor_shape;
+}
+
+void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot) {
+ ERR_FAIL_COND(!p_image.is_valid());
+
+ Size2i image_size = p_image->get_size();
+
+ // NOTE: The stride is the width of the image in bytes.
+ unsigned int image_stride = image_size.width * 4;
+ unsigned int data_size = image_stride * image_size.height;
+
+ // We need a shared memory object file descriptor in order to create a
+ // wl_buffer through wl_shm.
+ int fd = WaylandThread::_allocate_shm_file(data_size);
+ ERR_FAIL_COND(fd == -1);
+
+ CustomCursor &cursor = custom_cursors[p_cursor_shape];
+ cursor.hotspot = p_hotspot;
+
+ if (cursor.buffer_data) {
+ // Clean up the old buffer data.
+ munmap(cursor.buffer_data, cursor.buffer_data_size);
+ }
+
+ cursor.buffer_data = (uint32_t *)mmap(NULL, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (cursor.wl_buffer) {
+ // Clean up the old Wayland buffer.
+ wl_buffer_destroy(cursor.wl_buffer);
+ }
+
+ // Create the Wayland buffer.
+ struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(registry.wl_shm, fd, image_size.height * data_size);
+ // TODO: Make sure that WL_SHM_FORMAT_ARGB8888 format is supported. It
+ // technically isn't garaunteed to be supported, but I think that'd be a
+ // pretty unlikely thing to stumble upon.
+ cursor.wl_buffer = wl_shm_pool_create_buffer(wl_shm_pool, 0, image_size.width, image_size.height, image_stride, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(wl_shm_pool);
+
+ // Fill the cursor buffer with the image data.
+ for (unsigned int index = 0; index < (unsigned int)(image_size.width * image_size.height); index++) {
+ int row_index = floor(index / image_size.width);
+ int column_index = (index % int(image_size.width));
+
+ cursor.buffer_data[index] = p_image->get_pixel(column_index, row_index).to_argb32();
+
+ // Wayland buffers, unless specified, require associated alpha, so we'll just
+ // associate the alpha in-place.
+ uint8_t *pixel_data = (uint8_t *)&cursor.buffer_data[index];
+ pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;
+ pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;
+ pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;
+ }
+}
+
+void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape) {
+ if (custom_cursors.has(p_cursor_shape)) {
+ CustomCursor cursor = custom_cursors[p_cursor_shape];
+ custom_cursors.erase(p_cursor_shape);
+
+ current_custom_cursor = nullptr;
+
+ if (cursor.wl_buffer) {
+ wl_buffer_destroy(cursor.wl_buffer);
+ }
+
+ if (cursor.buffer_data) {
+ munmap(cursor.buffer_data, cursor.buffer_data_size);
+ }
+ }
+}
+
+int WaylandThread::keyboard_get_layout_count() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_keymap) {
+ return xkb_keymap_num_layouts(ss->xkb_keymap);
+ }
+
+ return 0;
+}
+
+int WaylandThread::keyboard_get_current_layout_index() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ return ss->current_layout_index;
+ }
+
+ return 0;
+}
+
+void WaylandThread::keyboard_set_current_layout_index(int p_index) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ ss->current_layout_index = p_index;
+ }
+}
+
+String WaylandThread::keyboard_get_layout_name(int p_index) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_keymap) {
+ String ret;
+ ret.parse_utf8(xkb_keymap_layout_get_name(ss->xkb_keymap, p_index));
+
+ return ret;
+ }
+
+ return "";
+}
+
+Key WaylandThread::keyboard_get_key_from_physical(Key p_key) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_state) {
+ xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(p_key);
+ return KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode));
+ }
+
+ return Key::NONE;
+}
+
+void WaylandThread::keyboard_echo_keys() {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ seat_state_echo_keys(ss);
+ }
+}
+
+void WaylandThread::selection_set_text(const String &p_text) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (registry.wl_data_device_manager == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available.");
+ }
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, current seat not set.");
+ return;
+ }
+
+ if (ss->wl_data_device == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, seat doesn't have wl_data_device.");
+ }
+
+ ss->selection_data = p_text.to_utf8_buffer();
+
+ if (ss->wl_data_source_selection == nullptr) {
+ ss->wl_data_source_selection = wl_data_device_manager_create_data_source(registry.wl_data_device_manager);
+ wl_data_source_add_listener(ss->wl_data_source_selection, &wl_data_source_listener, ss);
+ wl_data_source_offer(ss->wl_data_source_selection, "text/plain;charset=utf-8");
+ wl_data_source_offer(ss->wl_data_source_selection, "text/plain");
+ }
+
+ // TODO: Implement a good way of getting the latest serial from the user.
+ wl_data_device_set_selection(ss->wl_data_device, ss->wl_data_source_selection, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
+
+ // Wait for the message to get to the server before continuing, otherwise the
+ // clipboard update might come with a delay.
+ wl_display_roundtrip(wl_display);
+}
+
+bool WaylandThread::selection_has_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return false;
+ }
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
+ if (!os) {
+ return false;
+ }
+
+ return os->mime_types.has(p_mime);
+}
+
+Vector<uint8_t> WaylandThread::selection_get_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return Vector<uint8_t>();
+ }
+
+ if (ss->wl_data_source_selection) {
+ // We have a source so the stuff we're pasting is ours. We'll have to pass the
+ // data directly or we'd stall waiting for Godot (ourselves) to send us the
+ // data :P
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
+ ERR_FAIL_NULL_V(os, Vector<uint8_t>());
+
+ if (os->mime_types.has(p_mime)) {
+ // All righty, we're offering this type. Let's just return the data as is.
+ return ss->selection_data;
+ }
+
+ // ... we don't offer that type. Oh well.
+ return Vector<uint8_t>();
+ }
+
+ return _wl_data_offer_read(wl_display, p_mime.utf8(), ss->wl_data_offer_selection);
+}
+
+bool WaylandThread::primary_has_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return false;
+ }
+
+ OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
+ if (!os) {
+ return false;
+ }
+
+ return os->mime_types.has(p_mime);
+}
+
+Vector<uint8_t> WaylandThread::primary_get_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get primary, current seat not set.");
+ return Vector<uint8_t>();
+ }
+
+ if (ss->wp_primary_selection_source) {
+ // We have a source so the stuff we're pasting is ours. We'll have to pass the
+ // data directly or we'd stall waiting for Godot (ourselves) to send us the
+ // data :P
+
+ OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
+ ERR_FAIL_NULL_V(os, Vector<uint8_t>());
+
+ if (os->mime_types.has(p_mime)) {
+ // All righty, we're offering this type. Let's just return the data as is.
+ return ss->selection_data;
+ }
+
+ // ... we don't offer that type. Oh well.
+ return Vector<uint8_t>();
+ }
+
+ return _wp_primary_selection_offer_read(wl_display, p_mime.utf8(), ss->wp_primary_selection_offer);
+}
+
+void WaylandThread::primary_set_text(const String &p_text) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (registry.wp_primary_selection_device_manager == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available");
+ return;
+ }
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, current seat not set.");
+ return;
+ }
+
+ ss->primary_data = p_text.to_utf8_buffer();
+
+ if (ss->wp_primary_selection_source == nullptr) {
+ ss->wp_primary_selection_source = zwp_primary_selection_device_manager_v1_create_source(registry.wp_primary_selection_device_manager);
+ zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss);
+ zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8");
+ zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain");
+ }
+
+ // TODO: Implement a good way of getting the latest serial from the user.
+ zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
+
+ // Wait for the message to get to the server before continuing, otherwise the
+ // clipboard update might come with a delay.
+ wl_display_roundtrip(wl_display);
+}
+
+void WaylandThread::set_frame() {
+ frame = true;
+}
+
+bool WaylandThread::get_reset_frame() {
+ bool old_frame = frame;
+ frame = false;
+
+ return old_frame;
+}
+
+void WaylandThread::destroy() {
+ if (!initialized) {
+ return;
+ }
+
+ if (wl_display && events_thread.is_started()) {
+ thread_data.thread_done.set();
+
+ // By sending a roundtrip message we're unblocking the polling thread so that
+ // it can realize that it's done and also handle every event that's left.
+ wl_display_roundtrip(wl_display);
+
+ events_thread.wait_to_finish();
+ }
+
+ if (main_window.wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(main_window.wp_fractional_scale);
+ }
+
+ if (main_window.wp_viewport) {
+ wp_viewport_destroy(main_window.wp_viewport);
+ }
+
+ if (main_window.frame_callback) {
+ wl_callback_destroy(main_window.frame_callback);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (main_window.libdecor_frame) {
+ libdecor_frame_close(main_window.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+
+ if (main_window.xdg_toplevel) {
+ xdg_toplevel_destroy(main_window.xdg_toplevel);
+ }
+
+ if (main_window.xdg_surface) {
+ xdg_surface_destroy(main_window.xdg_surface);
+ }
+
+ if (main_window.wl_surface) {
+ wl_surface_destroy(main_window.wl_surface);
+ }
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ wl_seat_destroy(wl_seat);
+
+ xkb_context_unref(ss->xkb_context);
+ xkb_state_unref(ss->xkb_state);
+ xkb_keymap_unref(ss->xkb_keymap);
+
+ if (ss->wl_keyboard) {
+ wl_keyboard_destroy(ss->wl_keyboard);
+ }
+
+ if (ss->keymap_buffer) {
+ munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
+ }
+
+ if (ss->wl_pointer) {
+ wl_pointer_destroy(ss->wl_pointer);
+ }
+
+ if (ss->cursor_frame_callback) {
+ // We don't need to set a null userdata for safety as the thread is done.
+ wl_callback_destroy(ss->cursor_frame_callback);
+ }
+
+ if (ss->cursor_surface) {
+ wl_surface_destroy(ss->cursor_surface);
+ }
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ }
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (ss->wp_tablet_seat) {
+ zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
+ }
+#endif
+
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+
+ memdelete(ss);
+ }
+
+ for (struct wl_output *wl_output : registry.wl_outputs) {
+ ERR_FAIL_NULL(wl_output);
+
+ memdelete(wl_output_get_screen_state(wl_output));
+ wl_output_destroy(wl_output);
+ }
+
+ if (wl_cursor_theme) {
+ wl_cursor_theme_destroy(wl_cursor_theme);
+ }
+
+ if (registry.wp_idle_inhibit_manager) {
+ zwp_idle_inhibit_manager_v1_destroy(registry.wp_idle_inhibit_manager);
+ }
+
+ if (registry.wp_pointer_constraints) {
+ zwp_pointer_constraints_v1_destroy(registry.wp_pointer_constraints);
+ }
+
+ if (registry.wp_pointer_gestures) {
+ zwp_pointer_gestures_v1_destroy(registry.wp_pointer_gestures);
+ }
+
+ if (registry.wp_relative_pointer_manager) {
+ zwp_relative_pointer_manager_v1_destroy(registry.wp_relative_pointer_manager);
+ }
+
+ if (registry.xdg_activation) {
+ xdg_activation_v1_destroy(registry.xdg_activation);
+ }
+
+ if (registry.xdg_decoration_manager) {
+ zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);
+ }
+
+ if (registry.wp_fractional_scale_manager) {
+ wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);
+ }
+
+ if (registry.wp_viewporter) {
+ wp_viewporter_destroy(registry.wp_viewporter);
+ }
+
+ if (registry.xdg_wm_base) {
+ xdg_wm_base_destroy(registry.xdg_wm_base);
+ }
+
+ if (registry.wl_exporter) {
+ zxdg_exporter_v1_destroy(registry.wl_exporter);
+ }
+
+ if (registry.wl_shm) {
+ wl_shm_destroy(registry.wl_shm);
+ }
+
+ if (registry.wl_subcompositor) {
+ wl_subcompositor_destroy(registry.wl_subcompositor);
+ }
+
+ if (registry.wl_compositor) {
+ wl_compositor_destroy(registry.wl_compositor);
+ }
+
+ if (wl_registry) {
+ wl_registry_destroy(wl_registry);
+ }
+
+ if (wl_display) {
+ wl_display_disconnect(wl_display);
+ }
+}
+
+#endif // WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
new file mode 100644
index 0000000000..86033c1a09
--- /dev/null
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -0,0 +1,949 @@
+/**************************************************************************/
+/* wayland_thread.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. */
+/**************************************************************************/
+
+#ifndef WAYLAND_THREAD_H
+#define WAYLAND_THREAD_H
+
+#ifdef WAYLAND_ENABLED
+
+#include "key_mapping_xkb.h"
+
+#ifdef SOWRAP_ENABLED
+#include "wayland/dynwrappers/wayland-client-core-so_wrap.h"
+#include "wayland/dynwrappers/wayland-cursor-so_wrap.h"
+#include "wayland/dynwrappers/wayland-egl-core-so_wrap.h"
+#include "xkbcommon-so_wrap.h"
+#else
+#include <wayland-client-core.h>
+#include <wayland-cursor.h>
+#include <xkbcommon/xkbcommon.h>
+#endif // SOWRAP_ENABLED
+
+// These must go after the Wayland client include to work properly.
+#include "wayland/protocol/idle_inhibit.gen.h"
+#include "wayland/protocol/primary_selection.gen.h"
+// These three protocol headers name wl_pointer method arguments as `pointer`,
+// which is the same name as X11's pointer typedef. This trips some very
+// annoying shadowing warnings. A `#define` works around this issue.
+#define pointer wl_pointer
+#include "wayland/protocol/pointer_constraints.gen.h"
+#include "wayland/protocol/pointer_gestures.gen.h"
+#include "wayland/protocol/relative_pointer.gen.h"
+#undef pointer
+#include "wayland/protocol/fractional_scale.gen.h"
+#include "wayland/protocol/tablet.gen.h"
+#include "wayland/protocol/viewporter.gen.h"
+#include "wayland/protocol/wayland.gen.h"
+#include "wayland/protocol/xdg_activation.gen.h"
+#include "wayland/protocol/xdg_decoration.gen.h"
+#include "wayland/protocol/xdg_foreign.gen.h"
+#include "wayland/protocol/xdg_shell.gen.h"
+
+#ifdef LIBDECOR_ENABLED
+#ifdef SOWRAP_ENABLED
+#include "dynwrappers/libdecor-so_wrap.h"
+#else
+#include <libdecor-0/libdecor.h>
+#endif // SOWRAP_ENABLED
+#endif // LIBDECOR_ENABLED
+
+#include "core/os/thread.h"
+#include "servers/display_server.h"
+
+class WaylandThread {
+public:
+ // Messages used for exchanging information between Godot's and Wayland's thread.
+ class Message : public RefCounted {
+ public:
+ Message() {}
+ virtual ~Message() = default;
+ };
+
+ // Message data for window rect changes.
+ class WindowRectMessage : public Message {
+ public:
+ // NOTE: This is in "scaled" terms. For example, if there's a 1920x1080 rect
+ // with a scale factor of 2, the actual value of `rect` will be 3840x2160.
+ Rect2i rect;
+ };
+
+ class WindowEventMessage : public Message {
+ public:
+ DisplayServer::WindowEvent event;
+ };
+
+ class InputEventMessage : public Message {
+ public:
+ Ref<InputEvent> event;
+ };
+
+ class DropFilesEventMessage : public Message {
+ public:
+ Vector<String> files;
+ };
+
+ struct RegistryState {
+ WaylandThread *wayland_thread;
+
+ // Core Wayland globals.
+ struct wl_shm *wl_shm = nullptr;
+ uint32_t wl_shm_name = 0;
+
+ struct wl_compositor *wl_compositor = nullptr;
+ uint32_t wl_compositor_name = 0;
+
+ struct wl_subcompositor *wl_subcompositor = nullptr;
+ uint32_t wl_subcompositor_name = 0;
+
+ struct wl_data_device_manager *wl_data_device_manager = nullptr;
+ uint32_t wl_data_device_manager_name = 0;
+
+ List<struct wl_output *> wl_outputs;
+ List<struct wl_seat *> wl_seats;
+
+ // xdg-shell globals.
+
+ struct xdg_wm_base *xdg_wm_base = nullptr;
+ uint32_t xdg_wm_base_name = 0;
+
+ struct zxdg_exporter_v1 *wl_exporter = nullptr;
+ uint32_t wl_exporter_name = 0;
+
+ // wayland-protocols globals.
+
+ struct wp_viewporter *wp_viewporter = nullptr;
+ uint32_t wp_viewporter_name = 0;
+
+ struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager = nullptr;
+ uint32_t wp_fractional_scale_manager_name = 0;
+
+ struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr;
+ uint32_t xdg_decoration_manager_name = 0;
+
+ struct xdg_activation_v1 *xdg_activation = nullptr;
+ uint32_t xdg_activation_name = 0;
+
+ struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr;
+ uint32_t wp_primary_selection_device_manager_name = 0;
+
+ struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr;
+ uint32_t wp_relative_pointer_manager_name = 0;
+
+ struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
+ uint32_t wp_pointer_constraints_name = 0;
+
+ struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
+ uint32_t wp_pointer_gestures_name = 0;
+
+ struct zwp_idle_inhibit_manager_v1 *wp_idle_inhibit_manager = nullptr;
+ uint32_t wp_idle_inhibit_manager_name = 0;
+
+ struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr;
+ uint32_t wp_tablet_manager_name = 0;
+ };
+
+ // General Wayland-specific states. Shouldn't be accessed directly.
+ // TODO: Make private?
+
+ struct WindowState {
+ DisplayServer::WindowID id;
+
+ Rect2i rect;
+ DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ // These are true by default as it isn't guaranteed that we'll find an
+ // xdg-shell implementation with wm_capabilities available. If and once we
+ // receive a wm_capabilities event these will get reset and updated with
+ // whatever the compositor says.
+ bool can_minimize = false;
+ bool can_maximize = false;
+ bool can_fullscreen = false;
+
+ HashSet<struct wl_output *> wl_outputs;
+
+ // NOTE: If for whatever reason this callback is destroyed _while_ the event
+ // thread is still running, it might be a good idea to set its user data to
+ // `nullptr`. From some initial testing of mine, it looks like it might still
+ // be called even after being destroyed, pointing to probably invalid window
+ // data by then and segfaulting hard.
+ struct wl_callback *frame_callback = nullptr;
+
+ struct wl_surface *wl_surface = nullptr;
+ struct xdg_surface *xdg_surface = nullptr;
+ struct xdg_toplevel *xdg_toplevel = nullptr;
+
+ struct wp_viewport *wp_viewport = nullptr;
+ struct wp_fractional_scale_v1 *wp_fractional_scale = nullptr;
+ struct zxdg_exported_v1 *xdg_exported = nullptr;
+
+ String exported_handle;
+
+ // Currently applied buffer scale.
+ int buffer_scale = 1;
+
+ // Buffer scale must be applied right before rendering but _after_ committing
+ // everything else or otherwise we might have an inconsistent state (e.g.
+ // double scale and odd resolution). This flag assists with that; when set,
+ // on the next frame, we'll commit whatever is set in `buffer_scale`.
+ bool buffer_scale_changed = false;
+
+ // NOTE: The preferred buffer scale is currently only dynamically calculated.
+ // It can be accessed by calling `window_state_get_preferred_buffer_scale`.
+
+ // Override used by the fractional scale add-on object. If less or equal to 0
+ // (default) then the normal output-based scale is used instead.
+ double fractional_scale = 0;
+
+ // What the compositor is recommending us.
+ double preferred_fractional_scale = 0;
+
+ struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr;
+
+ struct zwp_idle_inhibitor_v1 *wp_idle_inhibitor = nullptr;
+
+#ifdef LIBDECOR_ENABLED
+ // If this is null the xdg_* variables must be set and vice-versa. This way we
+ // can handle this mess gracefully enough to hopefully being able of getting
+ // rid of this cleanly once we have our own CSDs.
+ struct libdecor_frame *libdecor_frame = nullptr;
+ struct libdecor_configuration *pending_libdecor_configuration = nullptr;
+#endif
+
+ RegistryState *registry;
+ WaylandThread *wayland_thread;
+ };
+
+ // "High level" Godot-side screen data.
+ struct ScreenData {
+ // Geometry data.
+ Point2i position;
+
+ String make;
+ String model;
+
+ Size2i size;
+ Size2i physical_size;
+
+ float refresh_rate = -1;
+ int scale = 1;
+ };
+
+ struct ScreenState {
+ uint32_t wl_output_name = 0;
+
+ ScreenData pending_data;
+ ScreenData data;
+
+ WaylandThread *wayland_thread;
+ };
+
+ enum class Gesture {
+ NONE,
+ MAGNIFY,
+ };
+
+ enum class PointerConstraint {
+ NONE,
+ LOCKED,
+ CONFINED,
+ };
+
+ struct PointerData {
+ Point2i position;
+ uint32_t motion_time = 0;
+
+ // Relative motion has its own optional event and so needs its own time.
+ Vector2 relative_motion;
+ uint32_t relative_motion_time = 0;
+
+ BitField<MouseButtonMask> pressed_button_mask;
+
+ MouseButton last_button_pressed = MouseButton::NONE;
+ Point2i last_pressed_position;
+
+ // This is needed to check for a new double click every time.
+ bool double_click_begun = false;
+
+ uint32_t button_time = 0;
+ uint32_t button_serial = 0;
+
+ uint32_t scroll_type = WL_POINTER_AXIS_SOURCE_WHEEL;
+
+ // The amount "scrolled" in pixels, in each direction.
+ Vector2 scroll_vector;
+
+ // The amount of scroll "clicks" in each direction.
+ Vector2i discrete_scroll_vector;
+
+ uint32_t pinch_scale = 1;
+ };
+
+ struct TabletToolData {
+ Point2i position;
+ Vector2i tilt;
+ uint32_t pressure = 0;
+
+ BitField<MouseButtonMask> pressed_button_mask;
+
+ MouseButton last_button_pressed = MouseButton::NONE;
+ Point2i last_pressed_position;
+
+ bool double_click_begun = false;
+
+ // Note: the protocol doesn't have it (I guess that this isn't really meant to
+ // be used as a mouse...), but we'll hack one in with the current ticks.
+ uint64_t button_time = 0;
+
+ bool is_eraser = false;
+
+ bool in_proximity = false;
+ bool touching = false;
+ };
+
+ struct OfferState {
+ HashSet<String> mime_types;
+ };
+
+ struct SeatState {
+ RegistryState *registry = nullptr;
+
+ WaylandThread *wayland_thread = nullptr;
+
+ struct wl_seat *wl_seat = nullptr;
+ uint32_t wl_seat_name = 0;
+
+ // Pointer.
+ struct wl_pointer *wl_pointer = nullptr;
+
+ uint32_t pointer_enter_serial = 0;
+
+ struct wl_surface *pointed_surface = nullptr;
+ struct wl_surface *last_pointed_surface = nullptr;
+
+ struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr;
+ struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
+ struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
+
+ struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr;
+
+ // NOTE: According to the wp_pointer_gestures protocol specification, there
+ // can be only one active gesture at a time.
+ Gesture active_gesture = Gesture::NONE;
+
+ // Used for delta calculations.
+ // NOTE: The wp_pointer_gestures protocol keeps track of the total scale of
+ // the pinch gesture, while godot instead wants its delta.
+ wl_fixed_t old_pinch_scale = 0;
+
+ struct wl_surface *cursor_surface = nullptr;
+ struct wl_callback *cursor_frame_callback = nullptr;
+ uint32_t cursor_time_ms = 0;
+
+ // This variable is needed to buffer all pointer changes until a
+ // wl_pointer.frame event, as per Wayland's specification. Everything is
+ // first set in `data_buffer` and then `data` is set with its contents on
+ // an input frame event. All methods should generally read from
+ // `pointer_data` and write to `data_buffer`.
+ PointerData pointer_data_buffer;
+ PointerData pointer_data;
+
+ // Keyboard.
+ struct wl_keyboard *wl_keyboard = nullptr;
+
+ struct xkb_context *xkb_context = nullptr;
+ struct xkb_keymap *xkb_keymap = nullptr;
+ struct xkb_state *xkb_state = nullptr;
+
+ const char *keymap_buffer = nullptr;
+ uint32_t keymap_buffer_size = 0;
+
+ xkb_layout_index_t current_layout_index = 0;
+
+ int32_t repeat_key_delay_msec = 0;
+ int32_t repeat_start_delay_msec = 0;
+
+ xkb_keycode_t repeating_keycode = XKB_KEYCODE_INVALID;
+ uint64_t last_repeat_start_msec = 0;
+ uint64_t last_repeat_msec = 0;
+
+ bool shift_pressed = false;
+ bool ctrl_pressed = false;
+ bool alt_pressed = false;
+ bool meta_pressed = false;
+
+ uint32_t last_key_pressed_serial = 0;
+
+ struct wl_data_device *wl_data_device = nullptr;
+
+ // Drag and drop.
+ struct wl_data_offer *wl_data_offer_dnd = nullptr;
+ uint32_t dnd_enter_serial = 0;
+
+ // Clipboard.
+ struct wl_data_source *wl_data_source_selection = nullptr;
+ Vector<uint8_t> selection_data;
+
+ struct wl_data_offer *wl_data_offer_selection = nullptr;
+
+ // Primary selection.
+ struct zwp_primary_selection_device_v1 *wp_primary_selection_device = nullptr;
+
+ struct zwp_primary_selection_source_v1 *wp_primary_selection_source = nullptr;
+ Vector<uint8_t> primary_data;
+
+ struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer = nullptr;
+
+ // Tablet.
+ struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr;
+
+ List<struct zwp_tablet_tool_v2 *> tablet_tools;
+
+ TabletToolData tablet_tool_data_buffer;
+ TabletToolData tablet_tool_data;
+ };
+
+ struct CustomCursor {
+ struct wl_buffer *wl_buffer = nullptr;
+ uint32_t *buffer_data = nullptr;
+ uint32_t buffer_data_size = 0;
+
+ RID rid;
+ Point2i hotspot;
+ };
+
+private:
+ struct ThreadData {
+ SafeFlag thread_done;
+ Mutex mutex;
+
+ struct wl_display *wl_display = nullptr;
+ };
+
+ // FIXME: Is this the right thing to do?
+ inline static const char *proxy_tag = "godot";
+
+ Thread events_thread;
+ ThreadData thread_data;
+
+ WindowState main_window;
+
+ List<Ref<Message>> messages;
+
+ String cursor_theme_name;
+ int unscaled_cursor_size = 24;
+
+ // NOTE: Regarding screen scale handling, the cursor cache is currently
+ // "static", by which I mean that we try to change it as little as possible and
+ // thus will be as big as the largest screen. This is mainly due to the fact
+ // that doing it dynamically doesn't look like it's worth it to me currently,
+ // especially as usually screen scales don't change continuously.
+ int cursor_scale = 1;
+
+ struct wl_cursor_theme *wl_cursor_theme = nullptr;
+ struct wl_cursor *wl_cursors[DisplayServer::CURSOR_MAX] = {};
+
+ HashMap<DisplayServer::CursorShape, CustomCursor> custom_cursors;
+
+ struct wl_cursor *current_wl_cursor = nullptr;
+ struct CustomCursor *current_custom_cursor = nullptr;
+
+ DisplayServer::CursorShape last_cursor_shape = DisplayServer::CURSOR_ARROW;
+
+ PointerConstraint pointer_constraint = PointerConstraint::NONE;
+
+ struct wl_display *wl_display = nullptr;
+ struct wl_registry *wl_registry = nullptr;
+
+ struct wl_seat *wl_seat_current = nullptr;
+
+ bool frame = true;
+
+ RegistryState registry;
+
+ bool initialized = false;
+
+#ifdef LIBDECOR_ENABLED
+ struct libdecor *libdecor_context = nullptr;
+#endif // LIBDECOR_ENABLED
+
+ // Main polling method.
+ static void _poll_events_thread(void *p_data);
+
+ // Core Wayland event handlers.
+ static void _wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version);
+ static void _wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name);
+
+ static void _wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output);
+ static void _wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output);
+ static void _wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor);
+ static void _wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform);
+
+ static void _frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data);
+
+ static void _wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform);
+ static void _wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh);
+ static void _wl_output_on_done(void *data, struct wl_output *wl_output);
+ static void _wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor);
+ static void _wl_output_on_name(void *data, struct wl_output *wl_output, const char *name);
+ static void _wl_output_on_description(void *data, struct wl_output *wl_output, const char *description);
+
+ static void _wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities);
+ static void _wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name);
+
+ static void _cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms);
+
+ static void _wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
+ static void _wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface);
+ static void _wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y);
+ static void _wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
+ static void _wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
+ static void _wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer);
+ static void _wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source);
+ static void _wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis);
+ static void _wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete);
+ static void _wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120);
+ static void _wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction);
+
+ static void _wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size);
+ static void _wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
+ static void _wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface);
+ static void _wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
+ static void _wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
+ static void _wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay);
+
+ static void _wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
+ static void _wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id);
+ static void _wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device);
+ static void _wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y);
+ static void _wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device);
+ static void _wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
+
+ static void _wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type);
+ static void _wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions);
+ static void _wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action);
+
+ static void _wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type);
+ static void _wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd);
+ static void _wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action);
+
+ // xdg-shell event handlers.
+ static void _xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial);
+
+ static void _xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
+
+ static void _xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states);
+ static void _xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel);
+ static void _xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height);
+ static void _xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities);
+
+ // wayland-protocols event handlers.
+ static void _wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
+
+ static void _wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer_v1, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel);
+
+ static void _wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers);
+ static void _wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation);
+ static void _wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled);
+
+ static void _wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer);
+ static void _wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id);
+
+ static void _wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type);
+
+ static void _wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd);
+ static void _wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1);
+
+ static void _wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id);
+ static void _wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id);
+ static void _wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id);
+
+ static void _wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type);
+ static void _wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo);
+ static void _wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo);
+ static void _wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability);
+ static void _wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface);
+ static void _wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial);
+ static void _wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y);
+ static void _wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure);
+ static void _wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance);
+ static void _wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y);
+ static void _wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees);
+ static void _wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position);
+ static void _wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks);
+ static void _wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state);
+ static void _wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time);
+
+ static void _xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode);
+
+ static void _xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle);
+
+ static void _xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token);
+
+ // Core Wayland event listeners.
+ static constexpr struct wl_registry_listener wl_registry_listener = {
+ .global = _wl_registry_on_global,
+ .global_remove = _wl_registry_on_global_remove,
+ };
+
+ static constexpr struct wl_surface_listener wl_surface_listener = {
+ .enter = _wl_surface_on_enter,
+ .leave = _wl_surface_on_leave,
+ .preferred_buffer_scale = _wl_surface_on_preferred_buffer_scale,
+ .preferred_buffer_transform = _wl_surface_on_preferred_buffer_transform,
+ };
+
+ static constexpr struct wl_callback_listener frame_wl_callback_listener {
+ .done = _frame_wl_callback_on_done,
+ };
+
+ static constexpr struct wl_output_listener wl_output_listener = {
+ .geometry = _wl_output_on_geometry,
+ .mode = _wl_output_on_mode,
+ .done = _wl_output_on_done,
+ .scale = _wl_output_on_scale,
+ .name = _wl_output_on_name,
+ .description = _wl_output_on_description,
+ };
+
+ static constexpr struct wl_seat_listener wl_seat_listener = {
+ .capabilities = _wl_seat_on_capabilities,
+ .name = _wl_seat_on_name,
+ };
+
+ static constexpr struct wl_callback_listener cursor_frame_callback_listener {
+ .done = _cursor_frame_callback_on_done,
+ };
+
+ static constexpr struct wl_pointer_listener wl_pointer_listener = {
+ .enter = _wl_pointer_on_enter,
+ .leave = _wl_pointer_on_leave,
+ .motion = _wl_pointer_on_motion,
+ .button = _wl_pointer_on_button,
+ .axis = _wl_pointer_on_axis,
+ .frame = _wl_pointer_on_frame,
+ .axis_source = _wl_pointer_on_axis_source,
+ .axis_stop = _wl_pointer_on_axis_stop,
+ .axis_discrete = _wl_pointer_on_axis_discrete,
+ .axis_value120 = _wl_pointer_on_axis_value120,
+ .axis_relative_direction = _wl_pointer_on_axis_relative_direction,
+ };
+
+ static constexpr struct wl_keyboard_listener wl_keyboard_listener = {
+ .keymap = _wl_keyboard_on_keymap,
+ .enter = _wl_keyboard_on_enter,
+ .leave = _wl_keyboard_on_leave,
+ .key = _wl_keyboard_on_key,
+ .modifiers = _wl_keyboard_on_modifiers,
+ .repeat_info = _wl_keyboard_on_repeat_info,
+ };
+
+ static constexpr struct wl_data_device_listener wl_data_device_listener = {
+ .data_offer = _wl_data_device_on_data_offer,
+ .enter = _wl_data_device_on_enter,
+ .leave = _wl_data_device_on_leave,
+ .motion = _wl_data_device_on_motion,
+ .drop = _wl_data_device_on_drop,
+ .selection = _wl_data_device_on_selection,
+ };
+
+ static constexpr struct wl_data_offer_listener wl_data_offer_listener = {
+ .offer = _wl_data_offer_on_offer,
+ .source_actions = _wl_data_offer_on_source_actions,
+ .action = _wl_data_offer_on_action,
+ };
+
+ static constexpr struct wl_data_source_listener wl_data_source_listener = {
+ .target = _wl_data_source_on_target,
+ .send = _wl_data_source_on_send,
+ .cancelled = _wl_data_source_on_cancelled,
+ .dnd_drop_performed = _wl_data_source_on_dnd_drop_performed,
+ .dnd_finished = _wl_data_source_on_dnd_finished,
+ .action = _wl_data_source_on_action,
+ };
+
+ // xdg-shell event listeners.
+ static constexpr struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = _xdg_wm_base_on_ping,
+ };
+
+ static constexpr struct xdg_surface_listener xdg_surface_listener = {
+ .configure = _xdg_surface_on_configure,
+ };
+
+ static constexpr struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = _xdg_toplevel_on_configure,
+ .close = _xdg_toplevel_on_close,
+ .configure_bounds = _xdg_toplevel_on_configure_bounds,
+ .wm_capabilities = _xdg_toplevel_on_wm_capabilities,
+ };
+
+ // wayland-protocols event listeners.
+ static constexpr struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
+ .preferred_scale = _wp_fractional_scale_on_preferred_scale,
+ };
+
+ static constexpr struct zwp_relative_pointer_v1_listener wp_relative_pointer_listener = {
+ .relative_motion = _wp_relative_pointer_on_relative_motion,
+ };
+
+ static constexpr struct zwp_pointer_gesture_pinch_v1_listener wp_pointer_gesture_pinch_listener = {
+ .begin = _wp_pointer_gesture_pinch_on_begin,
+ .update = _wp_pointer_gesture_pinch_on_update,
+ .end = _wp_pointer_gesture_pinch_on_end,
+ };
+
+ static constexpr struct zwp_primary_selection_device_v1_listener wp_primary_selection_device_listener = {
+ .data_offer = _wp_primary_selection_device_on_data_offer,
+ .selection = _wp_primary_selection_device_on_selection,
+ };
+
+ static constexpr struct zwp_primary_selection_offer_v1_listener wp_primary_selection_offer_listener = {
+ .offer = _wp_primary_selection_offer_on_offer,
+ };
+
+ static constexpr struct zwp_primary_selection_source_v1_listener wp_primary_selection_source_listener = {
+ .send = _wp_primary_selection_source_on_send,
+ .cancelled = _wp_primary_selection_source_on_cancelled,
+ };
+
+ static constexpr struct zwp_tablet_seat_v2_listener wp_tablet_seat_listener = {
+ .tablet_added = _wp_tablet_seat_on_tablet_added,
+ .tool_added = _wp_tablet_seat_on_tool_added,
+ .pad_added = _wp_tablet_seat_on_pad_added,
+ };
+
+ static constexpr struct zwp_tablet_tool_v2_listener wp_tablet_tool_listener = {
+ .type = _wp_tablet_tool_on_type,
+ .hardware_serial = _wp_tablet_tool_on_hardware_serial,
+ .hardware_id_wacom = _wp_tablet_tool_on_hardware_id_wacom,
+ .capability = _wp_tablet_tool_on_capability,
+ .done = _wp_tablet_tool_on_done,
+ .removed = _wp_tablet_tool_on_removed,
+ .proximity_in = _wp_tablet_tool_on_proximity_in,
+ .proximity_out = _wp_tablet_tool_on_proximity_out,
+ .down = _wp_tablet_tool_on_down,
+ .up = _wp_tablet_tool_on_up,
+ .motion = _wp_tablet_tool_on_motion,
+ .pressure = _wp_tablet_tool_on_pressure,
+ .distance = _wp_tablet_tool_on_distance,
+ .tilt = _wp_tablet_tool_on_tilt,
+ .rotation = _wp_tablet_tool_on_rotation,
+ .slider = _wp_tablet_tool_on_slider,
+ .wheel = _wp_tablet_tool_on_wheel,
+ .button = _wp_tablet_tool_on_button,
+ .frame = _wp_tablet_tool_on_frame,
+ };
+
+ static constexpr struct zxdg_exported_v1_listener xdg_exported_listener = {
+ .handle = _xdg_exported_on_exported
+ };
+
+ static constexpr struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
+ .configure = _xdg_toplevel_decoration_on_configure,
+ };
+
+ static constexpr struct xdg_activation_token_v1_listener xdg_activation_token_listener = {
+ .done = _xdg_activation_token_on_done,
+ };
+
+#ifdef LIBDECOR_ENABLED
+ // libdecor event handlers.
+ static void libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message);
+
+ static void libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data);
+
+ static void libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data);
+
+ static void libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data);
+
+ static void libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data);
+
+ // libdecor event listeners.
+ static constexpr struct libdecor_interface libdecor_interface = {
+ .error = libdecor_on_error,
+ .reserved0 = nullptr,
+ .reserved1 = nullptr,
+ .reserved2 = nullptr,
+ .reserved3 = nullptr,
+ .reserved4 = nullptr,
+ .reserved5 = nullptr,
+ .reserved6 = nullptr,
+ .reserved7 = nullptr,
+ .reserved8 = nullptr,
+ .reserved9 = nullptr,
+ };
+
+ static constexpr struct libdecor_frame_interface libdecor_frame_interface = {
+ .configure = libdecor_frame_on_configure,
+ .close = libdecor_frame_on_close,
+ .commit = libdecor_frame_on_commit,
+ .dismiss_popup = libdecor_frame_on_dismiss_popup,
+ .reserved0 = nullptr,
+ .reserved1 = nullptr,
+ .reserved2 = nullptr,
+ .reserved3 = nullptr,
+ .reserved4 = nullptr,
+ .reserved5 = nullptr,
+ .reserved6 = nullptr,
+ .reserved7 = nullptr,
+ .reserved8 = nullptr,
+ .reserved9 = nullptr,
+ };
+#endif // LIBDECOR_ENABLED
+
+ static Vector<uint8_t> _read_fd(int fd);
+ static int _allocate_shm_file(size_t size);
+
+ static Vector<uint8_t> _wl_data_offer_read(struct wl_display *wl_display, const char *p_mime, struct wl_data_offer *wl_data_offer);
+ static Vector<uint8_t> _wp_primary_selection_offer_read(struct wl_display *wl_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer);
+
+ static void _seat_state_set_current(WaylandThread::SeatState &p_ss);
+ static bool _seat_state_configure_key_event(WaylandThread::SeatState &p_seat, Ref<InputEventKey> p_event, xkb_keycode_t p_keycode, bool p_pressed);
+
+ static void _wayland_state_update_cursor();
+
+ void _set_current_seat(struct wl_seat *p_seat);
+
+ bool _load_cursor_theme(int p_cursor_size);
+
+ void _update_scale(int p_scale);
+
+public:
+ Mutex &mutex = thread_data.mutex;
+
+ struct wl_display *get_wl_display() const;
+
+ // Core Wayland utilities for integrating with our own data structures.
+ static bool wl_proxy_is_godot(struct wl_proxy *p_proxy);
+ static void wl_proxy_tag_godot(struct wl_proxy *p_proxy);
+
+ static WindowState *wl_surface_get_window_state(struct wl_surface *p_surface);
+ static ScreenState *wl_output_get_screen_state(struct wl_output *p_output);
+ static SeatState *wl_seat_get_seat_state(struct wl_seat *p_seat);
+ static OfferState *wl_data_offer_get_offer_state(struct wl_data_offer *p_offer);
+
+ static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer);
+
+ 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);
+ void seat_state_confine_pointer(SeatState *p_ss);
+
+ static void seat_state_update_cursor(SeatState *p_ss);
+
+ void seat_state_echo_keys(SeatState *p_ss);
+
+ static int window_state_get_preferred_buffer_scale(WindowState *p_ws);
+ static double window_state_get_scale_factor(WindowState *p_ws);
+ static void window_state_update_size(WindowState *p_ws, int p_width, int p_height);
+
+ static Vector2i scale_vector2i(const Vector2i &p_vector, double p_amount);
+
+ void push_message(Ref<Message> message);
+ bool has_message();
+ Ref<Message> pop_message();
+
+ void window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height);
+
+ struct wl_surface *window_get_wl_surface(DisplayServer::WindowID p_window_id) const;
+
+ void window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size);
+ void window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size);
+
+ bool window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const;
+ void window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode);
+ DisplayServer::WindowMode window_get_mode(DisplayServer::WindowID p_window_id) const;
+
+ void window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless);
+ void window_set_title(DisplayServer::WindowID p_window_id, const String &p_title);
+ void window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id);
+
+ bool window_is_focused(DisplayServer::WindowID p_window_id);
+
+ // Optional - requires xdg_activation_v1
+ void window_request_attention(DisplayServer::WindowID p_window_id);
+
+ // Optional - require idle_inhibit_unstable_v1
+ void window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable);
+ bool window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const;
+
+ ScreenData screen_get_data(int p_screen) const;
+ int get_screen_count() const;
+
+ void pointer_set_constraint(PointerConstraint p_constraint);
+ void pointer_set_hint(const Point2i &p_hint);
+ PointerConstraint pointer_get_constraint() const;
+ DisplayServer::WindowID pointer_get_pointed_window_id() const;
+ BitField<MouseButtonMask> pointer_get_button_mask() const;
+
+ void cursor_hide();
+ void cursor_set_shape(DisplayServer::CursorShape p_cursor_shape);
+
+ void cursor_set_custom_shape(DisplayServer::CursorShape p_cursor_shape);
+ void cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot);
+ void cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape);
+
+ int keyboard_get_layout_count() const;
+ int keyboard_get_current_layout_index() const;
+ void keyboard_set_current_layout_index(int p_index);
+ String keyboard_get_layout_name(int p_index) const;
+
+ Key keyboard_get_key_from_physical(Key p_key) const;
+
+ void keyboard_echo_keys();
+
+ bool selection_has_mime(const String &p_mime) const;
+ Vector<uint8_t> selection_get_mime(const String &p_mime) const;
+
+ void selection_set_text(const String &p_text);
+
+ // Optional primary support - requires wp_primary_selection_unstable_v1
+ bool primary_has_mime(const String &p_mime) const;
+ Vector<uint8_t> primary_get_mime(const String &p_mime) const;
+
+ void primary_set_text(const String &p_text);
+
+ void set_frame();
+ bool get_reset_frame();
+
+ Error init();
+ void destroy();
+};
+
+#endif // WAYLAND_ENABLED
+
+#endif // WAYLAND_THREAD_H
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index d8a81266d0..93d528bab6 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -372,7 +372,18 @@ Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_
}
String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
- return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ WindowID window_id = last_focused_window;
+
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
}
#endif
@@ -2010,8 +2021,8 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {
- if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) {
- XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup && _window_focus_check()) {
+ _set_input_focus(wd_parent.x11_window, RevertToPointerRoot);
}
}
} else {
@@ -2950,8 +2961,8 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win
XWindowAttributes xwa;
XSync(x11_display, False);
XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
- if (xwa.map_state == IsViewable) {
- XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime);
+ if (xwa.map_state == IsViewable && _window_focus_check()) {
+ _set_input_focus(wd.x11_xim_window, RevertToParent);
}
XSetICFocus(wd.xic);
} else {
@@ -3501,6 +3512,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
bool keypress = xkeyevent->type == KeyPress;
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
keycode -= 'a' - 'A';
@@ -3538,6 +3550,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
k->set_unicode(fix_unicode(tmp[i]));
}
+ k->set_location(key_location);
+
k->set_echo(false);
if (k->get_keycode() == Key::BACKTAB) {
@@ -3563,6 +3577,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
+
/* Phase 3, obtain a unicode character from the keysym */
// KeyMappingX11 also translates keysym to unicode.
@@ -3662,6 +3678,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
if (keypress) {
k->set_unicode(fix_unicode(unicode));
}
+
+ k->set_location(key_location);
+
k->set_echo(p_echo);
if (k->get_keycode() == Key::BACKTAB) {
@@ -4024,6 +4043,18 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev
}
}
+void DisplayServerX11::_set_input_focus(Window p_window, int p_revert_to) {
+ Window focused_window;
+ int focus_ret_state;
+ XGetInputFocus(x11_display, &focused_window, &focus_ret_state);
+
+ // Only attempt to change focus if the window isn't already focused, in order to
+ // prevent issues with Godot stealing input focus with alternative window managers.
+ if (p_window != focused_window) {
+ XSetInputFocus(x11_display, p_window, p_revert_to, CurrentTime);
+ }
+}
+
void DisplayServerX11::_poll_events_thread(void *ud) {
DisplayServerX11 *display_server = static_cast<DisplayServerX11 *>(ud);
display_server->_poll_events();
@@ -4233,6 +4264,22 @@ bool DisplayServerX11::mouse_process_popups() {
return closed;
}
+bool DisplayServerX11::_window_focus_check() {
+ Window focused_window;
+ int focus_ret_state;
+ XGetInputFocus(x11_display, &focused_window, &focus_ret_state);
+
+ bool has_focus = false;
+ for (const KeyValue<int, DisplayServerX11::WindowData> &wid : windows) {
+ if (wid.value.x11_window == focused_window) {
+ has_focus = true;
+ break;
+ }
+ }
+
+ return has_focus;
+}
+
void DisplayServerX11::process_events() {
_THREAD_SAFE_METHOD_
@@ -4504,8 +4551,8 @@ void DisplayServerX11::process_events() {
// Set focus when menu window is started.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
// Have we failed to set fullscreen while the window was unmapped?
@@ -4680,8 +4727,8 @@ void DisplayServerX11::process_events() {
// Set focus when menu window is re-used.
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
- if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup && _window_focus_check()) {
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
_window_changed(&event);
@@ -4725,7 +4772,7 @@ void DisplayServerX11::process_events() {
// RevertToPointerRoot is used to make sure we don't lose all focus in case
// a subwindow and its parent are both destroyed.
if (!wd.no_focus && !wd.is_popup) {
- XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);
+ _set_input_focus(wd.x11_window, RevertToPointerRoot);
}
uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;
@@ -6033,10 +6080,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (context_rd) {
if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Could not initialize %s", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
r_error = ERR_CANT_CREATE;
- ERR_FAIL_MSG(vformat("Could not initialize %s", context_rd->get_api_name()));
+ return;
}
driver_found = true;
}
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 378d8bb407..da4085772a 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -354,10 +354,12 @@ class DisplayServerX11 : public DisplayServer {
Context context = CONTEXT_ENGINE;
WindowID _get_focused_window_or_popup() const;
+ bool _window_focus_check();
void _send_window_event(const WindowData &wd, WindowEvent p_event);
static void _dispatch_input_events(const Ref<InputEvent> &p_event);
void _dispatch_input_event(const Ref<InputEvent> &p_event);
+ void _set_input_focus(Window p_window, int p_revert_to);
mutable Mutex events_mutex;
Thread events_thread;
@@ -400,6 +402,7 @@ public:
virtual bool is_dark_mode() const override;
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
#endif
virtual void mouse_set_mode(MouseMode p_mode) override;
diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp
index c0e6b91d57..b589a2a573 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.cpp
+++ b/platform/linuxbsd/x11/key_mapping_x11.cpp
@@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() {
xkeysym_unicode_map[0x13BD] = 0x0153;
xkeysym_unicode_map[0x13BE] = 0x0178;
xkeysym_unicode_map[0x20AC] = 0x20AC;
+
+ // Scancode to physical location map.
+ // Ctrl.
+ location_map[0x25] = KeyLocation::LEFT;
+ location_map[0x69] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x32] = KeyLocation::LEFT;
+ location_map[0x3E] = KeyLocation::RIGHT;
+ // Alt.
+ location_map[0x40] = KeyLocation::LEFT;
+ location_map[0x6C] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x85] = KeyLocation::LEFT;
+ location_map[0x86] = KeyLocation::RIGHT;
}
Key KeyMappingX11::get_keycode(KeySym p_keysym) {
@@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) {
}
return 0;
}
+
+KeyLocation KeyMappingX11::get_location(unsigned int p_code) {
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h
index ae8fd67f27..a51ee5f48e 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.h
+++ b/platform/linuxbsd/x11/key_mapping_x11.h
@@ -54,6 +54,7 @@ class KeyMappingX11 {
static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map;
+ static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
KeyMappingX11() {}
@@ -64,6 +65,7 @@ public:
static unsigned int get_xlibcode(Key p_keysym);
static Key get_scancode(unsigned int p_code);
static char32_t get_unicode_from_keysym(KeySym p_keysym);
+ static KeyLocation get_location(unsigned int p_code);
};
#endif // KEY_MAPPING_X11_H