diff options
Diffstat (limited to 'platform/linuxbsd')
-rw-r--r-- | platform/linuxbsd/detect.py | 3 | ||||
-rw-r--r-- | platform/linuxbsd/export/export_plugin.cpp | 5 | ||||
-rw-r--r-- | platform/linuxbsd/export/export_plugin.h | 2 | ||||
-rw-r--r-- | platform/linuxbsd/export/run_icon.svg | 2 | ||||
-rw-r--r-- | platform/linuxbsd/freedesktop_portal_desktop.cpp | 350 | ||||
-rw-r--r-- | platform/linuxbsd/freedesktop_portal_desktop.h | 30 | ||||
-rw-r--r-- | platform/linuxbsd/joypad_linux.cpp | 61 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.cpp | 21 | ||||
-rw-r--r-- | platform/linuxbsd/os_linuxbsd.h | 2 | ||||
-rw-r--r-- | platform/linuxbsd/x11/display_server_x11.cpp | 139 | ||||
-rw-r--r-- | platform/linuxbsd/x11/display_server_x11.h | 8 | ||||
-rw-r--r-- | platform/linuxbsd/x11/gl_manager_x11.h | 8 |
12 files changed, 545 insertions, 86 deletions
diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index ce743fbf8a..a723bb5d58 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -291,6 +291,9 @@ def configure(env: "Environment"): # No pkgconfig file so far, hardcode expected lib name. env.Append(LIBS=["embree3"]) + if not env["builtin_openxr"]: + env.ParseConfig("pkg-config openxr --cflags --libs") + if env["fontconfig"]: if not env["use_sowrap"]: if os.system("pkg-config --exists fontconfig") == 0: # 0 means found diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index f74bdf3516..40151b1a02 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -521,11 +521,10 @@ EditorExportPlatformLinuxBSD::EditorExportPlatformLinuxBSD() { Ref<Image> img = memnew(Image); const bool upsample = !Math::is_equal_approx(Math::round(EDSCALE), EDSCALE); - ImageLoaderSVG img_loader; - img_loader.create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); + ImageLoaderSVG::create_image_from_string(img, _linuxbsd_logo_svg, EDSCALE, upsample, false); set_logo(ImageTexture::create_from_image(img)); - img_loader.create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); + ImageLoaderSVG::create_image_from_string(img, _linuxbsd_run_icon_svg, EDSCALE, upsample, false); run_icon = ImageTexture::create_from_image(img); #endif diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index cef714e86e..21bd81ed2f 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -34,7 +34,7 @@ #include "core/io/file_access.h" #include "editor/editor_settings.h" #include "editor/export/editor_export_platform_pc.h" -#include "scene/resources/texture.h" +#include "scene/resources/image_texture.h" class EditorExportPlatformLinuxBSD : public EditorExportPlatformPC { GDCLASS(EditorExportPlatformLinuxBSD, EditorExportPlatformPC); diff --git a/platform/linuxbsd/export/run_icon.svg b/platform/linuxbsd/export/run_icon.svg index 56465a0df3..ad58bcd5c7 100644 --- a/platform/linuxbsd/export/run_icon.svg +++ b/platform/linuxbsd/export/run_icon.svg @@ -1 +1 @@ -<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M7.941 13.966a3.62 3.62 0 0 1-.096.444 2.129 2.129 0 0 1-.31.668c.15.01.305.01.464.003.16.008.314.007.465-.003a2.129 2.129 0 0 1-.31-.668 3.62 3.62 0 0 1-.097-.444l-.058.001-.058-.001z" fill="#333" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/><path d="M10.688 10.793c-.297.005-.441.299-.707.33-.328.038-.533-.34-.996-.058-.463.283-.862 3.528.212 3.97 1.074.442 3.072-2.146 2.942-2.673-.13-.527-.542-.403-.747-.66-.206-.258 0-.666-.47-.86a.588.588 0 0 0-.234-.05zm-5.411 0a.62.62 0 0 0-.199.05c-.47.193-.264.601-.47.859-.205.257-.618.133-.748.66s1.867 3.115 2.942 2.673c1.074-.442.674-3.687.211-3.97-.463-.283-.668.096-.995.058-.277-.032-.42-.349-.741-.33z" fill="#f4bb37" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/><path d="M8 .914c-1.386 0-2.2.845-2.353 1.985-.153 1.14.094 1.348-.29 2.515s-2.103 3.168-2.063 5.013c.012.575.078 1.072.194 1.507a1.25 1.25 0 0 1 .503-.47 4.37 4.37 0 0 1 .204-.09c.004-.03.019-.098.046-.23.038-.182.183-.467.43-.654a4.773 4.773 0 0 1-.006-.172c-.029-1.431 1.45-2.982 1.723-3.888.272-.905.154-.998.199-1.223.045-.225.218-.487.468-.696a.11.11 0 0 1 .07-.028c.228-.003.456.826.897.827.44 0 .67-1.01.923-.799.25.21.423.471.468.696.045.225-.073.318.199 1.223.272.906 1.75 2.457 1.722 3.888-.001.058-.004.114-.007.17a1.2 1.2 0 0 1 .432.656c.027.132.042.2.046.23.027.01.085.037.204.092.153.07.36.236.502.47.115-.435.183-.933.195-1.509.04-1.845-1.681-3.846-2.065-5.013-.383-1.167-.135-1.376-.288-2.515C10.2 1.759 9.385.914 7.999.914Z" fill="#333" style="stroke-width:.472092;fill:#e0e0e0;fill-opacity:1"/></svg> +<svg height="16" width="16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M7.941 13.966a3.62 3.62 0 0 1-.096.444 2.129 2.129 0 0 1-.31.668c.15.01.305.01.464.003.16.008.314.007.465-.003a2.129 2.129 0 0 1-.31-.668 3.62 3.62 0 0 1-.097-.444zM8 .914c-1.386 0-2.2.845-2.353 1.985-.153 1.14.094 1.348-.29 2.515s-2.103 3.168-2.063 5.013c.012.575.078 1.072.194 1.507a1.25 1.25 0 0 1 .503-.47 4.37 4.37 0 0 1 .204-.09c.004-.03.019-.098.046-.23.038-.182.183-.467.43-.654a4.773 4.773 0 0 1-.006-.172c-.029-1.431 1.45-2.982 1.723-3.888.272-.905.154-.998.199-1.223.045-.225.218-.487.468-.696.253-.211.483.798.945.799.462 0 .692-1.01.945-.799.25.21.423.471.468.696.045.225-.073.318.199 1.223.272.906 1.75 2.457 1.722 3.888a4.773 4.773 0 0 0-.007.17 1.2 1.2 0 0 1 .432.656c.027.132.042.2.046.23.027.01.085.037.204.092.153.07.36.236.502.47.115-.435.183-.933.195-1.509.04-1.845-1.681-3.846-2.065-5.013-.383-1.167-.135-1.376-.288-2.515C10.2 1.759 9.385.914 7.999.914z" fill="#333" style="fill:#e0e0e0;fill-opacity:1"/><path d="M10.688 10.793c-.297.005-.441.299-.707.33-.328.038-.533-.34-.996-.058-.463.283-.862 3.528.212 3.97 1.074.442 3.072-2.146 2.942-2.673-.13-.527-.542-.403-.747-.66-.206-.258 0-.666-.47-.86a.588.588 0 0 0-.234-.05zm-5.411 0a.62.62 0 0 0-.199.05c-.47.193-.264.601-.47.859-.205.257-.618.133-.748.66s1.867 3.115 2.942 2.673c1.074-.442.674-3.687.211-3.97-.463-.283-.668.096-.995.058-.277-.032-.42-.349-.741-.33z" fill="#f4bb37" style="fill:#e0e0e0;fill-opacity:1"/></svg> diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index c3cb0f411d..91c14e0e91 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -32,6 +32,7 @@ #ifdef DBUS_ENABLED +#include "core/crypto/crypto_core.h" #include "core/error/error_macros.h" #include "core/os/os.h" #include "core/string/ustring.h" @@ -43,12 +44,15 @@ #include <dbus/dbus.h> #endif +#include <unistd.h> + #define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop" #define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop" #define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings" +#define BUS_INTERFACE_FILE_CHOOSER "org.freedesktop.portal.FileChooser" -static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { +bool FreeDesktopPortalDesktop::try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { DBusMessageIter iter[3]; dbus_message_iter_init(p_reply_message, &iter[0]); @@ -80,11 +84,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { - dbus_error_free(&error); - unsupported = true; if (OS::get_singleton()->is_stdout_verbose()) { - ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message); + ERR_PRINT(vformat("Error opening D-Bus connection: %s", error.message)); } + dbus_error_free(&error); + unsupported = true; return false; } @@ -100,11 +104,11 @@ bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { - dbus_error_free(&error); - dbus_connection_unref(bus); if (OS::get_singleton()->is_stdout_verbose()) { - ERR_PRINT(String() + "Error on D-Bus communication: " + error.message); + ERR_PRINT(vformat("Error on D-Bus communication: %s", error.message)); } + dbus_error_free(&error); + dbus_connection_unref(bus); return false; } @@ -126,6 +130,317 @@ uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() { return value; } +static const char *cs_empty = ""; + +void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const String &p_string) { + CharString cs = p_string.utf8(); + const char *cs_ptr = cs.ptr(); + if (cs_ptr) { + dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_ptr); + } else { + dbus_message_iter_append_basic(p_iter, DBUS_TYPE_STRING, &cs_empty); + } +} + +void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + DBusMessageIter arr_iter; + const char *filters_key = "filters"; + + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key); + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter); + dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter); + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() == 2) { + 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, tokens[0]); + + dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); + dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); + { + const unsigned nil = 0; + dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil); + } + append_dbus_string(&array_struct_iter, tokens[1]); + dbus_message_iter_close_container(&array_iter, &array_struct_iter); + dbus_message_iter_close_container(&struct_iter, &array_iter); + 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_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + append_dbus_string(&dict_iter, p_key); + + if (p_as_byte_array) { + DBusMessageIter arr_iter; + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "ay", &var_iter); + dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "y", &arr_iter); + CharString cs = p_value.utf8(); + const char *cs_ptr = cs.get_data(); + do { + dbus_message_iter_append_basic(&arr_iter, DBUS_TYPE_BYTE, cs_ptr); + } while (*cs_ptr++); + dbus_message_iter_close_container(&var_iter, &arr_iter); + } else { + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "s", &var_iter); + append_dbus_string(&var_iter, p_value); + } + + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + +void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value) { + DBusMessageIter dict_iter; + DBusMessageIter var_iter; + dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter); + append_dbus_string(&dict_iter, p_key); + + dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "b", &var_iter); + { + int val = p_value; + dbus_message_iter_append_basic(&var_iter, DBUS_TYPE_BOOLEAN, &val); + } + + dbus_message_iter_close_container(&dict_iter, &var_iter); + dbus_message_iter_close_container(p_iter, &dict_iter); +} + +bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) { + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false); + + dbus_uint32_t resp_code; + dbus_message_iter_get_basic(p_iter, &resp_code); + if (resp_code != 0) { + r_cancel = true; + } else { + r_cancel = false; + ERR_FAIL_COND_V(!dbus_message_iter_next(p_iter), false); + ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_ARRAY, false); + + DBusMessageIter dict_iter; + dbus_message_iter_recurse(p_iter, &dict_iter); + while (dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter iter; + dbus_message_iter_recurse(&dict_iter, &iter); + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING) { + const char *key; + dbus_message_iter_get_basic(&iter, &key); + dbus_message_iter_next(&iter); + + DBusMessageIter var_iter; + dbus_message_iter_recurse(&iter, &var_iter); + if (strcmp(key, "uris") == 0) { + if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) { + DBusMessageIter uri_iter; + dbus_message_iter_recurse(&var_iter, &uri_iter); + while (dbus_message_iter_get_arg_type(&uri_iter) == DBUS_TYPE_STRING) { + const char *value; + dbus_message_iter_get_basic(&uri_iter, &value); + r_urls.push_back(String::utf8(value).trim_prefix("file://").uri_decode()); + if (!dbus_message_iter_next(&uri_iter)) { + break; + } + } + } + } + } + if (!dbus_message_iter_next(&dict_iter)) { + break; + } + } + } + return true; +} + +Error FreeDesktopPortalDesktop::file_dialog_show(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) { + if (unsupported) { + return FAILED; + } + + DBusError err; + dbus_error_init(&err); + + // Open connection and add signal handler. + FileDialogData fd; + fd.callback = p_callback; + + CryptoCore::RandomGenerator rng; + ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator."); + uint8_t uuid[64]; + Error rng_err = rng.get_random_bytes(uuid, 64); + ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token."); + + fd.connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to open DBus connection: %s", err.message)); + dbus_error_free(&err); + unsupported = true; + return FAILED; + } + + String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(fd.connection)); + String token = String::hex_encode_buffer(uuid, 64); + String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token); + + fd.path = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name); + dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + + // Generate FileChooser message. + const char *method = nullptr; + switch (p_mode) { + case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: { + method = "SaveFile"; + } break; + case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY: + case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE: + case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR: + case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: { + method = "OpenFile"; + } break; + } + + DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method); + { + DBusMessageIter iter; + dbus_message_iter_init_append(message, &iter); + + append_dbus_string(&iter, p_xid); + append_dbus_string(&iter, p_title); + + DBusMessageIter arr_iter; + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr_iter); + + append_dbus_dict_string(&arr_iter, "handle_token", token); + 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, p_filters); + 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); + } + + dbus_message_iter_close_container(&iter, &arr_iter); + } + + DBusMessage *reply = dbus_connection_send_with_reply_and_block(fd.connection, message, DBUS_TIMEOUT_INFINITE, &err); + dbus_message_unref(message); + + if (!reply || dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to send DBus message: %s", err.message)); + dbus_error_free(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_connection_unref(fd.connection); + return FAILED; + } + + // Update signal path. + { + DBusMessageIter iter; + if (dbus_message_iter_init(reply, &iter)) { + if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_OBJECT_PATH) { + const char *new_path = nullptr; + dbus_message_iter_get_basic(&iter, &new_path); + if (String::utf8(new_path) != path) { + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + fd.path = String::utf8(new_path); + dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + if (dbus_error_is_set(&err)) { + ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + return FAILED; + } + } + } + } + } + dbus_message_unref(reply); + + MutexLock lock(file_dialog_mutex); + file_dialogs.push_back(fd); + + return OK; +} + +void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) { + FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud; + + while (!portal->file_dialog_thread_abort.is_set()) { + { + MutexLock lock(portal->file_dialog_mutex); + for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) { + bool remove = false; + { + FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; + if (fd.connection) { + while (true) { + DBusMessage *msg = dbus_connection_pop_message(fd.connection); + if (!msg) { + break; + } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + DBusMessageIter iter; + if (dbus_message_iter_init(msg, &iter)) { + bool cancel = false; + Vector<String> uris; + file_chooser_parse_response(&iter, cancel, uris); + + if (fd.callback.is_valid()) { + Variant v_status = !cancel; + Variant v_files = uris; + Variant *v_args[2] = { &v_status, &v_files }; + fd.callback.call_deferredp((const Variant **)&v_args, 2); + } + } + dbus_message_unref(msg); + + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + remove = true; + break; + } + dbus_message_unref(msg); + } + dbus_connection_read_write(fd.connection, 0); + } + } + if (remove) { + portal->file_dialogs.remove_at(i); + } + } + } + usleep(50000); + } +} + FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { #ifdef SOWRAP_ENABLED #ifdef DEBUG_ENABLED @@ -153,6 +468,27 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { print_verbose("PortalDesktop: Unsupported DBus library version!"); unsupported = true; } + + if (!unsupported) { + file_dialog_thread_abort.clear(); + file_dialog_thread.start(FreeDesktopPortalDesktop::_thread_file_dialog_monitor, this); + } +} + +FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() { + file_dialog_thread_abort.set(); + if (file_dialog_thread.is_started()) { + file_dialog_thread.wait_to_finish(); + } + for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) { + if (fd.connection) { + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_error_free(&err); + dbus_connection_unref(fd.connection); + } + } } #endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index 1520ab3a1f..a9b83b3844 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -33,20 +33,48 @@ #ifdef DBUS_ENABLED -#include <stdint.h> +#include "core/os/thread.h" +#include "servers/display_server.h" + +struct DBusMessage; +struct DBusConnection; +struct DBusMessageIter; class FreeDesktopPortalDesktop { private: bool unsupported = false; + static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value); // Read a setting from org.freekdesktop.portal.Settings 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_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters); + 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, bool &r_cancel, Vector<String> &r_urls); + + struct FileDialogData { + DBusConnection *connection = nullptr; + Callable callback; + String path; + }; + + Mutex file_dialog_mutex; + Vector<FileDialogData> file_dialogs; + Thread file_dialog_thread; + SafeFlag file_dialog_thread_abort; + + static void _thread_file_dialog_monitor(void *p_ud); + public: FreeDesktopPortalDesktop(); + ~FreeDesktopPortalDesktop(); bool is_supported() { return !unsupported; } + Error file_dialog_show(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); + // Retrieve the system's preferred color scheme. // 0: No preference or unknown. // 1: Prefer dark appearance. diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 71c03898c8..827c567785 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -56,6 +56,14 @@ static const char *ignore_str = "/dev/input/js"; #endif +// On Linux with Steam Input Xbox 360 devices have an index appended to their device name, this index is +// the Steam Input gamepad index +#define VALVE_GAMEPAD_NAME_PREFIX "Microsoft X-Box 360 pad " +// IDs used by Steam Input virtual controllers. +// See https://partner.steamgames.com/doc/features/steam_controller/steam_input_gamepad_emulation_bestpractices +#define VALVE_GAMEPAD_VID 0x28DE +#define VALVE_GAMEPAD_PID 0x11FF + JoypadLinux::Joypad::~Joypad() { for (int i = 0; i < MAX_ABS; i++) { if (abs_info[i]) { @@ -74,31 +82,9 @@ void JoypadLinux::Joypad::reset() { events.clear(); } -#ifdef UDEV_ENABLED -// This function is derived from SDL: -// https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45 -static bool detect_sandbox() { - if (access("/.flatpak-info", F_OK) == 0) { - return true; - } - - // For Snap, we check multiple variables because they might be set for - // unrelated reasons. This is the same thing WebKitGTK does. - if (OS::get_singleton()->has_environment("SNAP") && OS::get_singleton()->has_environment("SNAP_NAME") && OS::get_singleton()->has_environment("SNAP_REVISION")) { - return true; - } - - if (access("/run/host/container-manager", F_OK) == 0) { - return true; - } - - return false; -} -#endif // UDEV_ENABLED - JoypadLinux::JoypadLinux(Input *in) { #ifdef UDEV_ENABLED - if (detect_sandbox()) { + if (OS::get_singleton()->is_sandboxed()) { // Linux binaries in sandboxes / containers need special handling because // libudev doesn't work there. So we need to fallback to manual parsing // of /dev/input in such case. @@ -393,6 +379,16 @@ void JoypadLinux::open_joypad(const char *p_path) { return; } + uint16_t vendor = BSWAP16(inpid.vendor); + uint16_t product = BSWAP16(inpid.product); + uint16_t version = BSWAP16(inpid.version); + + if (input->should_ignore_device(vendor, product)) { + // This can be true in cases where Steam is passing information into the game to ignore + // original gamepads when using virtual rebindings (See SteamInput). + return; + } + MutexLock lock(joypads_mutex[joy_num]); Joypad &joypad = joypads[joy_num]; joypad.reset(); @@ -401,12 +397,23 @@ void JoypadLinux::open_joypad(const char *p_path) { setup_joypad_properties(joypad); sprintf(uid, "%04x%04x", BSWAP16(inpid.bustype), 0); if (inpid.vendor && inpid.product && inpid.version) { - uint16_t vendor = BSWAP16(inpid.vendor); - uint16_t product = BSWAP16(inpid.product); - uint16_t version = BSWAP16(inpid.version); + Dictionary joypad_info; + joypad_info["vendor_id"] = inpid.vendor; + joypad_info["product_id"] = inpid.product; + joypad_info["raw_name"] = name; sprintf(uid + String(uid).length(), "%04x%04x%04x%04x%04x%04x", vendor, 0, product, 0, version, 0); - input->joy_connection_changed(joy_num, true, name, uid); + + if (inpid.vendor == VALVE_GAMEPAD_VID && inpid.product == VALVE_GAMEPAD_PID) { + if (name.begins_with(VALVE_GAMEPAD_NAME_PREFIX)) { + String idx_str = name.substr(strlen(VALVE_GAMEPAD_NAME_PREFIX)); + if (idx_str.is_valid_int()) { + joypad_info["steam_input_index"] = idx_str.to_int(); + } + } + } + + input->joy_connection_changed(joy_num, true, name, uid, joypad_info); } else { String uidname = uid; int uidlen = MIN(name.length(), 11); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 14d02a73c8..d22d398a67 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -164,6 +164,27 @@ String OS_LinuxBSD::get_processor_name() const { ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string.")); } +bool OS_LinuxBSD::is_sandboxed() const { + // This function is derived from SDL: + // https://github.com/libsdl-org/SDL/blob/main/src/core/linux/SDL_sandbox.c#L28-L45 + + if (access("/.flatpak-info", F_OK) == 0) { + return true; + } + + // For Snap, we check multiple variables because they might be set for + // unrelated reasons. This is the same thing WebKitGTK does. + if (has_environment("SNAP") && has_environment("SNAP_NAME") && has_environment("SNAP_REVISION")) { + return true; + } + + if (access("/run/host/container-manager", F_OK) == 0) { + return true; + } + + return false; +} + void OS_LinuxBSD::finalize() { if (main_loop) { memdelete(main_loop); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 007b90b82b..6917ea5ae7 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -123,6 +123,8 @@ public: virtual String get_unique_id() const override; virtual String get_processor_name() const override; + virtual bool is_sandboxed() const override; + virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; virtual bool _check_internal_feature_support(const String &p_feature) override; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index d9e18720dc..f38a9dd278 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -40,7 +40,7 @@ #include "core/string/print_string.h" #include "core/string/ustring.h" #include "main/main.h" -#include "scene/resources/texture.h" +#include "scene/resources/atlas_texture.h" #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" @@ -122,6 +122,9 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_HIDPI: case FEATURE_ICON: +#ifdef DBUS_ENABLED + case FEATURE_NATIVE_DIALOG: +#endif //case FEATURE_NATIVE_ICON: case FEATURE_SWAP_BUFFERS: #ifdef DBUS_ENABLED @@ -360,6 +363,17 @@ bool DisplayServerX11::is_dark_mode() const { } } +Error DisplayServerX11::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 = _get_focused_window_or_popup(); + + 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(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback); +} + #endif void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { @@ -379,7 +393,11 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { if (show_cursor && !previously_shown) { WindowID window_id = get_window_at_screen_position(mouse_get_position()); - if (window_id != INVALID_WINDOW_ID) { + if (window_id != INVALID_WINDOW_ID && window_mouseover_id != window_id) { + if (window_mouseover_id != INVALID_WINDOW_ID) { + _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = window_id; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } @@ -744,7 +762,7 @@ int DisplayServerX11::get_screen_count() const { // Using Xinerama Extension int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) { XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); XFree(xsi); } else { @@ -756,7 +774,7 @@ int DisplayServerX11::get_screen_count() const { int DisplayServerX11::get_primary_screen() const { int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) { return 0; } else { return XDefaultScreen(x11_display); @@ -809,7 +827,7 @@ Rect2i DisplayServerX11::_screen_get_rect(int p_screen) const { // Using Xinerama Extension. int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) { int count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &count); @@ -1244,7 +1262,7 @@ Ref<Image> DisplayServerX11::screen_get_image(int p_screen) const { XImage *image = nullptr; int event_base, error_base; - if (XineramaQueryExtension(x11_display, &event_base, &error_base)) { + if (xinerama_ext_ok && XineramaQueryExtension(x11_display, &event_base, &error_base)) { int xin_count; XineramaScreenInfo *xsi = XineramaQueryScreens(x11_display, &xin_count); if (p_screen < xin_count) { @@ -1449,6 +1467,17 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); + if (window_mouseover_id == p_id) { + window_mouseover_id = INVALID_WINDOW_ID; + _send_window_event(windows[p_id], WINDOW_EVENT_MOUSE_EXIT); + } + + window_set_rect_changed_callback(Callable(), p_id); + window_set_window_event_callback(Callable(), p_id); + window_set_input_event_callback(Callable(), p_id); + window_set_input_text_callback(Callable(), p_id); + window_set_drop_files_callback(Callable(), p_id); + while (wd.transient_children.size()) { window_set_transient(*wd.transient_children.begin(), INVALID_WINDOW_ID); } @@ -1580,6 +1609,7 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_reg void DisplayServerX11::_update_window_mouse_passthrough(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); + ERR_FAIL_COND(!xshaped_ext_ok); const Vector<Vector2> region_path = windows[p_window].mpath; @@ -2096,9 +2126,10 @@ bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_a bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { const WindowData &wd = windows[p_window]; - // Using ICCCM -- Inter-Client Communication Conventions Manual - Atom property = XInternAtom(x11_display, "WM_STATE", True); - if (property == None) { + // Using EWMH instead of ICCCM, might work better for Wayland users. + Atom property = XInternAtom(x11_display, "_NET_WM_STATE", True); + Atom hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", True); + if (property == None || hidden == None) { return false; } @@ -2106,7 +2137,7 @@ bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { int format; unsigned long len; unsigned long remaining; - unsigned char *data = nullptr; + Atom *atoms = nullptr; int result = XGetWindowProperty( x11_display, @@ -2115,20 +2146,21 @@ bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { 0, 32, False, - AnyPropertyType, + XA_ATOM, &type, &format, &len, &remaining, - &data); + (unsigned char **)&atoms); - if (result == Success && data) { - long *state = (long *)data; - if (state[0] == WM_IconicState) { - XFree(data); - return true; + if (result == Success && atoms) { + for (unsigned int i = 0; i < len; i++) { + if (atoms[i] == hidden) { + XFree(atoms); + return true; + } } - XFree(data); + XFree(atoms); } return false; @@ -2980,6 +3012,30 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { return (Key)(key | modifiers); } +Key DisplayServerX11::keyboard_get_label_from_physical(Key p_keycode) const { + Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; + Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; + unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, keyboard_get_current_layout(), 0); + if (is_ascii_lower_case(xkeysym)) { + xkeysym -= ('a' - 'A'); + } + + Key key = KeyMappingX11::get_keycode(xkeysym); +#ifdef XKB_ENABLED + if (xkb_loaded_v08p) { + String keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(xkeysym))); + key = fix_key_label(keysym[0], KeyMappingX11::get_keycode(xkeysym)); + } +#endif + + // If not found, fallback to QWERTY. + // This should match the behavior of the event pump + if (key == Key::NONE) { + return p_keycode; + } + return (Key)(key | modifiers); +} DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { Atom actual_type = None; int actual_format = 0; @@ -3927,7 +3983,7 @@ bool DisplayServerX11::mouse_process_popups() { // Find top popup to close. while (E) { // Popup window area. - Rect2i win_rect = Rect2i(window_get_position(E->get()), window_get_size(E->get())); + Rect2i win_rect = Rect2i(window_get_position_with_decorations(E->get()), window_get_size_with_decorations(E->get())); // Area of the parent window, which responsible for opening sub-menu. Rect2i safe_rect = window_get_popup_safe_rect(E->get()); if (win_rect.has_point(pos)) { @@ -4261,7 +4317,8 @@ void DisplayServerX11::process_events() { break; } - if (!mouse_mode_grab) { + if (!mouse_mode_grab && window_mouseover_id == window_id) { + window_mouseover_id = INVALID_WINDOW_ID; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); } @@ -4273,7 +4330,11 @@ void DisplayServerX11::process_events() { break; } - if (!mouse_mode_grab) { + if (!mouse_mode_grab && window_mouseover_id != window_id) { + if (window_mouseover_id != INVALID_WINDOW_ID) { + _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); + } + window_mouseover_id = window_id; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER); } } break; @@ -4880,6 +4941,8 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False); if (p_icon.is_valid()) { + ERR_FAIL_COND(p_icon->get_width() <= 0 || p_icon->get_height() <= 0); + Ref<Image> img = p_icon->duplicate(); img->convert(Image::FORMAT_RGBA8); @@ -5429,13 +5492,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } if (initialize_xinerama(dylibloader_verbose) != 0) { - r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Can't load Xinerama dynamically."); + xinerama_ext_ok = false; } if (initialize_xrandr(dylibloader_verbose) != 0) { - r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Can't load Xrandr dynamically."); + xrandr_ext_ok = false; } if (initialize_xrender(dylibloader_verbose) != 0) { @@ -5505,42 +5566,36 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode return; } - { + if (xshaped_ext_ok) { int version_major = 0; int version_minor = 0; int rc = XShapeQueryVersion(x11_display, &version_major, &version_minor); print_verbose(vformat("Xshape %d.%d detected.", version_major, version_minor)); if (rc != 1 || version_major < 1) { - ERR_PRINT("Unsupported Xshape library version."); - r_error = ERR_UNAVAILABLE; - XCloseDisplay(x11_display); - return; + xshaped_ext_ok = false; + print_verbose("Unsupported Xshape library version."); } } - { + if (xinerama_ext_ok) { int version_major = 0; int version_minor = 0; int rc = XineramaQueryVersion(x11_display, &version_major, &version_minor); print_verbose(vformat("Xinerama %d.%d detected.", version_major, version_minor)); if (rc != 1 || version_major < 1) { - ERR_PRINT("Unsupported Xinerama library version."); - r_error = ERR_UNAVAILABLE; - XCloseDisplay(x11_display); - return; + xinerama_ext_ok = false; + print_verbose("Unsupported Xinerama library version."); } } - { + if (xrandr_ext_ok) { int version_major = 0; int version_minor = 0; int rc = XRRQueryVersion(x11_display, &version_major, &version_minor); print_verbose(vformat("Xrandr %d.%d detected.", version_major, version_minor)); if (rc != 1 || (version_major == 1 && version_minor < 3) || (version_major < 1)) { - ERR_PRINT("Unsupported Xrandr library version."); - r_error = ERR_UNAVAILABLE; - XCloseDisplay(x11_display); - return; + xrandr_ext_ok = false; + print_verbose("Unsupported Xrandr library version."); } } @@ -5606,7 +5661,9 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (!xrandr_handle) { fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); } - } else { + } + + if (xrandr_handle) { XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor); if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) { xrr_get_monitors = (xrr_get_monitors_t)dlsym(xrandr_handle, "XRRGetMonitors"); diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index 180362923b..71beddce76 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -225,6 +225,7 @@ class DisplayServerX11 : public DisplayServer { List<WindowID> popup_list; + WindowID window_mouseover_id = INVALID_WINDOW_ID; WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; @@ -322,7 +323,9 @@ class DisplayServerX11 : public DisplayServer { xrr_get_monitors_t xrr_get_monitors = nullptr; xrr_free_monitors_t xrr_free_monitors = nullptr; void *xrandr_handle = nullptr; - Bool xrandr_ext_ok; + bool xrandr_ext_ok = true; + bool xinerama_ext_ok = true; + bool xshaped_ext_ok = true; struct Property { unsigned char *data; @@ -390,6 +393,8 @@ public: #if defined(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; #endif virtual void mouse_set_mode(MouseMode p_mode) override; @@ -502,6 +507,7 @@ public: 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 Key keyboard_get_label_from_physical(Key p_keycode) const override; virtual void process_events() override; diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h index 59e20fec45..d3a25506a8 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.h +++ b/platform/linuxbsd/x11/gl_manager_x11.h @@ -74,17 +74,17 @@ private: }; struct GLDisplay { - GLDisplay() { context = nullptr; } + GLDisplay() {} ~GLDisplay(); GLManager_X11_Private *context = nullptr; - ::Display *x11_display; - XVisualInfo x_vi; + ::Display *x11_display = nullptr; + XVisualInfo x_vi = {}; }; // just for convenience, window and display struct struct XWinDisp { ::Window x11_window; - ::Display *x11_display; + ::Display *x11_display = nullptr; } _x_windisp; LocalVector<GLWindow> _windows; |