diff options
Diffstat (limited to 'platform/linuxbsd')
38 files changed, 9960 insertions, 126 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 59cc6e7962..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,8 +444,35 @@ 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"]) + env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: env.ParseConfig("pkg-config vulkan --cflags --libs") if not env["builtin_glslang"]: @@ -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/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 64efcffae3..773b124c6a 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -36,9 +36,9 @@ #include "core/config/project_settings.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" -#include "editor/editor_scale.h" #include "editor/editor_string_names.h" #include "editor/export/editor_export.h" +#include "editor/themes/editor_scale.h" #include "modules/modules_enabled.gen.h" // For svg. #ifdef MODULE_SVG_ENABLED diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 6a5b5b8064..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; @@ -162,7 +210,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, append_dbus_string(&struct_iter, p_filter_names[i]); dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); - String flt = p_filter_exts[i]; + const String &flt = p_filter_exts[i]; int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_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 d22d398a67..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(); @@ -316,7 +328,7 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const { continue; } String device_class = columns[1].trim_suffix(":"); - String vendor_device_id_mapping = columns[2]; + const String &vendor_device_id_mapping = columns[2]; #ifdef MODULE_REGEX_ENABLED if (regex_id_format.search(vendor_device_id_mapping).is_null()) { @@ -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 = ®istry->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 = ®istry->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 = ®istry; + 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, ®istry); + + // 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 bbb4b04508..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 @@ -1707,9 +1718,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { window_set_transient(p_id, INVALID_WINDOW_ID); } -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(p_id); +#if defined(RD_ENABLED) + if (context_rd) { + context_rd->window_destroy(p_id); } #endif #ifdef GLES3_ENABLED @@ -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 { @@ -2233,9 +2244,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { } // Keep rendering context window size in sync -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(p_window, xwa.width, xwa.height); +#if defined(RD_ENABLED) + if (context_rd) { + context_rd->window_resize(p_window, xwa.width, xwa.height); } #endif #if defined(GLES3_ENABLED) @@ -2891,10 +2902,15 @@ void DisplayServerX11::window_move_to_foreground(WindowID p_window) { XFlush(x11_display); } +DisplayServerX11::WindowID DisplayServerX11::get_focused_window() const { + return last_focused_window; +} + bool DisplayServerX11::window_is_focused(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), false); + const WindowData &wd = windows[p_window]; return wd.focused; @@ -2945,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 { @@ -3496,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'; @@ -3533,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) { @@ -3558,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. @@ -3657,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) { @@ -3875,7 +3899,7 @@ void DisplayServerX11::_xim_preedit_draw_callback(::XIM xim, ::XPointer client_d ds->im_selection = Point2i(); } - OS_Unix::get_singleton()->get_main_loop()->call_deferred(SNAME("notification"), MainLoop::NOTIFICATION_OS_IME_UPDATE); + callable_mp((Object *)OS_Unix::get_singleton()->get_main_loop(), &Object::notification).call_deferred(MainLoop::NOTIFICATION_OS_IME_UPDATE, false); } } @@ -3945,9 +3969,9 @@ void DisplayServerX11::_window_changed(XEvent *event) { wd.position = new_rect.position; wd.size = new_rect.size; -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); +#if defined(RD_ENABLED) + if (context_rd) { + context_rd->window_resize(window_id, wd.size.width, wd.size.height); } #endif #if defined(GLES3_ENABLED) @@ -4019,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(); @@ -4228,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_ @@ -4499,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? @@ -4675,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); @@ -4720,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; @@ -5244,9 +5296,9 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); +#if defined(RD_ENABLED) + if (context_rd) { + context_rd->set_vsync_mode(p_window, p_vsync_mode); } #endif @@ -5262,9 +5314,9 @@ void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mo DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_window) const { _THREAD_SAFE_METHOD_ -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); +#if defined(RD_ENABLED) + if (context_rd) { + return context_rd->get_vsync_mode(p_window); } #endif #if defined(GLES3_ENABLED) @@ -5606,10 +5658,21 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V _update_size_hints(id); -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(id, p_vsync_mode, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan window"); +#if defined(RD_ENABLED) + if (context_rd) { + union { +#ifdef VULKAN_ENABLED + VulkanContextX11::WindowPlatformData vulkan; +#endif + } wpd; +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + wpd.vulkan.window = wd.x11_window; + wpd.vulkan.display = x11_display; + } +#endif + Error err = context_rd->window_create(id, p_vsync_mode, win_rect.size.width, win_rect.size.height, &wpd); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s window", context_rd->get_api_name())); } #endif #ifdef GLES3_ENABLED @@ -6008,14 +6071,20 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode rendering_driver = p_rendering_driver; bool driver_found = false; +#if defined(RD_ENABLED) #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextX11); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; + context_rd = memnew(VulkanContextX11); + } +#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; - ERR_FAIL_MSG("Could not initialize Vulkan"); + return; } driver_found = true; } @@ -6124,11 +6193,10 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } show_window(main_window); -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - //temporary - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); +#if defined(RD_ENABLED) + if (context_rd) { + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(context_rd); RendererCompositorRD::make_current(); } @@ -6302,9 +6370,9 @@ DisplayServerX11::~DisplayServerX11() { //destroy all windows for (KeyValue<WindowID, WindowData> &E : windows) { -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(E.key); +#if defined(RD_ENABLED) + if (context_rd) { + context_rd->window_destroy(E.key); } #endif #ifdef GLES3_ENABLED @@ -6346,16 +6414,16 @@ DisplayServerX11::~DisplayServerX11() { #endif //destroy drivers -#if defined(VULKAN_ENABLED) - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + if (rendering_device) { + rendering_device->finalize(); + memdelete(rendering_device); + rendering_device = nullptr; } - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; + if (context_rd) { + memdelete(context_rd); + context_rd = nullptr; } #endif diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index a8d134a6c7..da4085772a 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -57,10 +57,12 @@ #include "x11/gl_manager_x11_egl.h" #endif +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" + #if defined(VULKAN_ENABLED) #include "x11/vulkan_context_x11.h" - -#include "drivers/vulkan/rendering_device_vulkan.h" +#endif #endif #if defined(DBUS_ENABLED) @@ -141,9 +143,9 @@ class DisplayServerX11 : public DisplayServer { GLManager_X11 *gl_manager = nullptr; GLManagerEGL_X11 *gl_manager_egl = nullptr; #endif -#if defined(VULKAN_ENABLED) - VulkanContextX11 *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + ApiContextRD *context_rd = nullptr; + RenderingDevice *rendering_device = nullptr; #endif #if defined(DBUS_ENABLED) @@ -352,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; @@ -398,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; @@ -489,6 +494,8 @@ public: virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual WindowID get_focused_window() const override; + virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; virtual bool can_any_window_draw() const override; diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp index 0f709872cb..b589a2a573 100644 --- a/platform/linuxbsd/x11/key_mapping_x11.cpp +++ b/platform/linuxbsd/x11/key_mapping_x11.cpp @@ -88,7 +88,6 @@ void KeyMappingX11::initialize() { xkeysym_map[XK_KP_Equal] = Key::EQUAL; xkeysym_map[XK_KP_Separator] = Key::COMMA; xkeysym_map[XK_KP_Decimal] = Key::KP_PERIOD; - xkeysym_map[XK_KP_Delete] = Key::KP_PERIOD; xkeysym_map[XK_KP_Multiply] = Key::KP_MULTIPLY; xkeysym_map[XK_KP_Divide] = Key::KP_DIVIDE; xkeysym_map[XK_KP_Subtract] = Key::KP_SUBTRACT; @@ -105,6 +104,7 @@ void KeyMappingX11::initialize() { xkeysym_map[XK_KP_9] = Key::KP_9; // Same keys but with numlock off. xkeysym_map[XK_KP_Insert] = Key::INSERT; + xkeysym_map[XK_KP_Delete] = Key::KEY_DELETE; xkeysym_map[XK_KP_End] = Key::END; xkeysym_map[XK_KP_Down] = Key::DOWN; xkeysym_map[XK_KP_Page_Down] = Key::PAGEDOWN; @@ -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 diff --git a/platform/linuxbsd/x11/vulkan_context_x11.cpp b/platform/linuxbsd/x11/vulkan_context_x11.cpp index d240480f61..3eee1706b0 100644 --- a/platform/linuxbsd/x11/vulkan_context_x11.cpp +++ b/platform/linuxbsd/x11/vulkan_context_x11.cpp @@ -42,15 +42,15 @@ const char *VulkanContextX11::_get_platform_surface_extension() const { return VK_KHR_XLIB_SURFACE_EXTENSION_NAME; } -Error VulkanContextX11::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::Window p_window, Display *p_display, int p_width, int p_height) { - VkXlibSurfaceCreateInfoKHR createInfo; +Error VulkanContextX11::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; + + VkXlibSurfaceCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.dpy = p_display; - createInfo.window = p_window; + createInfo.dpy = wpd->display; + createInfo.window = wpd->window; - VkSurfaceKHR surface; + VkSurfaceKHR surface = VK_NULL_HANDLE; VkResult err = vkCreateXlibSurfaceKHR(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); diff --git a/platform/linuxbsd/x11/vulkan_context_x11.h b/platform/linuxbsd/x11/vulkan_context_x11.h index 294fdc710e..2390326b44 100644 --- a/platform/linuxbsd/x11/vulkan_context_x11.h +++ b/platform/linuxbsd/x11/vulkan_context_x11.h @@ -38,10 +38,14 @@ #include <X11/Xlib.h> class VulkanContextX11 : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; + virtual const char *_get_platform_surface_extension() const override final; public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, ::Window p_window, Display *p_display, int p_width, int p_height); + struct WindowPlatformData { + ::Window window; + Display *display; + }; + virtual 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; VulkanContextX11(); ~VulkanContextX11(); |