diff options
Diffstat (limited to 'platform')
162 files changed, 14108 insertions, 2724 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index 97262cf148..31bc7c25b0 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -23,7 +23,7 @@ android_files = [ "android_keys_utils.cpp", "display_server_android.cpp", "plugin/godot_plugin_jni.cpp", - "vulkan_context_android.cpp", + "rendering_context_driver_vulkan_android.cpp", ] env_android = env.Clone() diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index bd194478d9..373dd399e4 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod ev->set_physical_keycode(physical_keycode); ev->set_key_label(fix_key_label(p_key_label, keycode)); ev->set_unicode(fix_unicode(unicode)); + ev->set_location(godot_location_from_android_code(p_physical_keycode)); ev->set_pressed(p_pressed); ev->set_echo(p_echo); @@ -206,6 +207,7 @@ void AndroidInputHandler::process_touch_event(int p_event, int p_pointer, const ev->set_index(touch[i].id); ev->set_position(p_points[idx].pos); ev->set_relative(p_points[idx].pos - touch[i].pos); + ev->set_relative_screen_position(ev->get_relative()); Input::get_singleton()->parse_input_event(ev); touch.write[i].pos = p_points[idx].pos; } @@ -305,6 +307,7 @@ void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_an ev->set_position(p_event_pos); ev->set_global_position(p_event_pos); ev->set_relative(p_event_pos - hover_prev_pos); + ev->set_relative_screen_position(ev->get_relative()); Input::get_singleton()->parse_input_event(ev); hover_prev_pos = p_event_pos; } break; @@ -341,10 +344,12 @@ void AndroidInputHandler::process_mouse_event(int p_event_action, int p_event_an ev->set_position(hover_prev_pos); ev->set_global_position(hover_prev_pos); ev->set_relative(p_event_pos); + ev->set_relative_screen_position(p_event_pos); } else { ev->set_position(p_event_pos); ev->set_global_position(p_event_pos); ev->set_relative(p_event_pos - hover_prev_pos); + ev->set_relative_screen_position(ev->get_relative()); mouse_event_info.pos = p_event_pos; hover_prev_pos = p_event_pos; } diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp index f50437e82a..83ee98e8bc 100644 --- a/platform/android/android_keys_utils.cpp +++ b/platform/android/android_keys_utils.cpp @@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) { } return Key::UNKNOWN; } + +KeyLocation godot_location_from_android_code(unsigned int p_code) { + for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) { + if (android_godot_location_pairs[i].android_code == p_code) { + return android_godot_location_pairs[i].godot_code; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h index 5cf5628a8b..77c0911f2b 100644 --- a/platform/android/android_keys_utils.h +++ b/platform/android/android_keys_utils.h @@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = { Key godot_code_from_android_code(unsigned int p_code); +// Key location determination. +struct AndroidGodotLocationPair { + unsigned int android_code = 0; + KeyLocation godot_code = KeyLocation::UNSPECIFIED; +}; + +static AndroidGodotLocationPair android_godot_location_pairs[] = { + { AKEYCODE_ALT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT }, + { AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_CTRL_LEFT, KeyLocation::LEFT }, + { AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_META_LEFT, KeyLocation::LEFT }, + { AKEYCODE_META_RIGHT, KeyLocation::RIGHT }, + { AKEYCODE_MAX, KeyLocation::UNSPECIFIED } +}; + +KeyLocation godot_location_from_android_code(unsigned int p_code); + #endif // ANDROID_KEYS_UTILS_H diff --git a/platform/android/detect.py b/platform/android/detect.py index a417ef454b..8976e218b3 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -28,6 +28,7 @@ def get_opts(): "android-" + str(get_min_target_api()), ), BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False), + BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False), ] @@ -200,7 +201,7 @@ def configure(env: "Environment"): env.Append(LIBS=["OpenSLES", "EGL", "android", "log", "z", "dl"]) if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: env.Append(LIBS=["vulkan"]) diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 9529e0e683..b06164246e 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -37,11 +37,13 @@ #include "core/config/project_settings.h" -#if defined(VULKAN_ENABLED) -#include "vulkan_context_android.h" - -#include "drivers/vulkan/rendering_device_vulkan.h" +#if defined(RD_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#include "servers/rendering/rendering_device.h" + +#if defined(VULKAN_ENABLED) +#include "rendering_context_driver_vulkan_android.h" +#endif #endif #ifdef GLES3_ENABLED @@ -486,9 +488,6 @@ Vector<String> DisplayServerAndroid::get_rendering_drivers_func() { #ifdef GLES3_ENABLED drivers.push_back("opengl3"); #endif -#ifdef D3D12_ENABLED - drivers.push_back("d3d12"); -#endif #ifdef VULKAN_ENABLED drivers.push_back("vulkan"); #endif @@ -518,20 +517,41 @@ void DisplayServerAndroid::register_android_driver() { } void DisplayServerAndroid::reset_window() { -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); - ERR_FAIL_NULL(native_window); +#if defined(RD_ENABLED) + if (rendering_context) { + if (rendering_device) { + rendering_device->screen_free(MAIN_WINDOW_ID); + } + + VSyncMode last_vsync_mode = rendering_context->window_get_vsync_mode(MAIN_WINDOW_ID); + rendering_context->window_destroy(MAIN_WINDOW_ID); - ERR_FAIL_NULL(context_vulkan); - VSyncMode last_vsync_mode = context_vulkan->get_vsync_mode(MAIN_WINDOW_ID); - context_vulkan->window_destroy(MAIN_WINDOW_ID); + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanAndroid::WindowPlatformData vulkan; +#endif + } wpd; +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); + ERR_FAIL_NULL(native_window); + wpd.vulkan.window = native_window; + } +#endif + + if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) { + ERR_PRINT(vformat("Failed to reset %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; + return; + } Size2i display_size = OS_Android::get_singleton()->get_display_size(); - if (context_vulkan->window_create(native_window, last_vsync_mode, display_size.width, display_size.height) != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - ERR_FAIL_MSG("Failed to reset Vulkan window."); + rendering_context->window_set_size(MAIN_WINDOW_ID, display_size.width, display_size.height); + rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, last_vsync_mode); + + if (rendering_device) { + rendering_device->screen_create(MAIN_WINDOW_ID); } } #endif @@ -554,30 +574,51 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis } #endif -#if defined(VULKAN_ENABLED) - context_vulkan = nullptr; - rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + rendering_context = nullptr; + rendering_device = nullptr; +#if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); - ERR_FAIL_NULL(native_window); - - context_vulkan = memnew(VulkanContextAndroid); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - ERR_FAIL_MSG("Failed to initialize Vulkan context"); + rendering_context = memnew(RenderingContextDriverVulkanAndroid); + } +#endif + + if (rendering_context) { + if (rendering_context->initialize() != OK) { + ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; + return; } - Size2i display_size = OS_Android::get_singleton()->get_display_size(); - if (context_vulkan->window_create(native_window, p_vsync_mode, display_size.width, display_size.height) != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - ERR_FAIL_MSG("Failed to create Vulkan window."); + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanAndroid::WindowPlatformData vulkan; +#endif + } wpd; +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + ANativeWindow *native_window = OS_Android::get_singleton()->get_native_window(); + ERR_FAIL_NULL(native_window); + wpd.vulkan.window = native_window; + } +#endif + + if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) { + ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; + return; } - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); + Size2i display_size = OS_Android::get_singleton()->get_display_size(); + rendering_context->window_set_size(MAIN_WINDOW_ID, display_size.width, display_size.height); + rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode); + + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -590,16 +631,12 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis } DisplayServerAndroid::~DisplayServerAndroid() { -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - } - - if (context_vulkan) { - memdelete(context_vulkan); - } +#if defined(RD_ENABLED) + if (rendering_device) { + memdelete(rendering_device); + } + if (rendering_context) { + memdelete(rendering_context); } #endif } @@ -690,17 +727,17 @@ void DisplayServerAndroid::cursor_set_custom_image(const Ref<Resource> &p_cursor } void DisplayServerAndroid::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif } DisplayServer::VSyncMode DisplayServerAndroid::window_get_vsync_mode(WindowID p_window) const { -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); +#if defined(RD_ENABLED) + if (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif return DisplayServer::VSYNC_ENABLED; diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 54912212dc..b425b2d9ae 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -33,9 +33,9 @@ #include "servers/display_server.h" -#if defined(VULKAN_ENABLED) -class VulkanContextAndroid; -class RenderingDeviceVulkan; +#if defined(RD_ENABLED) +class RenderingContextDriver; +class RenderingDevice; #endif class DisplayServerAndroid : public DisplayServer { @@ -72,9 +72,9 @@ class DisplayServerAndroid : public DisplayServer { CursorShape cursor_shape = CursorShape::CURSOR_ARROW; -#if defined(VULKAN_ENABLED) - VulkanContextAndroid *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + RenderingContextDriver *rendering_context = nullptr; + RenderingDevice *rendering_device = nullptr; #endif ObjectID window_attached_instance_id; diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index dae968378b..a6f92158f9 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -119,7 +119,10 @@ If [code]true[/code], package signing is enabled. </member> <member name="package/unique_name" type="String" setter="" getter=""> - Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]). + Unique application identifier in a reverse-DNS format. The reverse DNS format should preferably match a domain name you control, but this is not strictly required. For instance, if you own [code]example.com[/code], your package unique name should preferably be of the form [code]com.example.mygame[/code]. This identifier can only contain lowercase alphanumeric characters ([code]a-z[/code], and [code]0-9[/code]), underscores ([code]_[/code]), and periods ([code].[/code]). Each component of the reverse DNS format must start with a letter: for instance, [code]com.example.8game[/code] is not valid. + If [code]$genname[/code] is present in the value, it will be replaced by the project name converted to lowercase. If there are invalid characters in the project name, they will be stripped. If all characters in the project name are stripped, [code]$genname[/code] is replaced by [code]noname[/code]. + [b]Note:[/b] Changing the package name will cause the package to be considered as a new package, with its own installation and data paths. The new package won't be usable to update existing installations. + [b]Note:[/b] When publishing to Google Play, the package name must be [i]globally[/i] unique. This means no other apps published on Google Play must be using the same package name as yours. Otherwise, you'll be prevented from publishing your app on Google Play. </member> <member name="permissions/access_checkin_properties" type="bool" setter="" getter=""> Allows read/write access to the "properties" table in the checkin database. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_CHECKIN_PROPERTIES]ACCESS_CHECKIN_PROPERTIES[/url]. diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index ee1ec2790d..138714634f 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -42,12 +42,15 @@ void register_android_exporter_types() { void register_android_exporter() { #ifndef ANDROID_ENABLED + EDITOR_DEF("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME")); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/android_sdk_path", OS::get_singleton()->get_environment("ANDROID_HOME")); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/android_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("export/android/debug_keystore", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks")); EDITOR_DEF("export/android/debug_keystore_user", "androiddebugkey"); EDITOR_DEF("export/android/debug_keystore_pass", "android"); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD)); EDITOR_DEF("export/android/force_system_user", false); EDITOR_DEF("export/android/shutdown_adb_on_exit", true); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 10d54e8d97..459f5a5983 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -45,9 +45,9 @@ #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" -#include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/import/resource_importer_texture_settings.h" +#include "editor/themes/editor_scale.h" #include "main/splash.gen.h" #include "scene/resources/image_texture.h" @@ -255,7 +255,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal static const int OPENGL_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk' static const int VULKAN_MIN_SDK_VERSION = 24; -static const int DEFAULT_TARGET_SDK_VERSION = 33; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' +static const int DEFAULT_TARGET_SDK_VERSION = 34; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk' #ifndef ANDROID_ENABLED void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) { @@ -1111,7 +1111,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } for (int i = 0; i < feature_names.size(); i++) { - String feature_name = feature_names[i]; + const String &feature_name = feature_names[i]; bool feature_required = feature_required_list[i]; int feature_version = feature_versions[i]; bool has_version_attribute = feature_version != -1; @@ -1605,7 +1605,11 @@ void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> & print_verbose("Loading regular icon from " + path); if (path.is_empty() || ImageLoader::load_image(path, icon) != OK) { print_verbose("- falling back to project icon: " + project_icon_path); - ImageLoader::load_image(project_icon_path, icon); + if (!project_icon_path.is_empty()) { + ImageLoader::load_image(project_icon_path, icon); + } else { + ERR_PRINT("No project icon specified. Please specify one in the Project Settings under Application -> Config -> Icon"); + } } // Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). @@ -1823,10 +1827,10 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/debug_password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release", PROPERTY_HINT_GLOBAL_FILE, "*.keystore,*.jks", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_PASSWORD, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); @@ -2111,8 +2115,17 @@ Ref<Texture2D> EditorExportPlatformAndroid::get_run_icon() const { return run_icon; } +String EditorExportPlatformAndroid::get_java_path() { + String exe_ext; + if (OS::get_singleton()->get_name() == "Windows") { + exe_ext = ".exe"; + } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + return java_sdk_path.path_join("bin/java" + exe_ext); +} + String EditorExportPlatformAndroid::get_adb_path() { - String exe_ext = ""; + String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".exe"; } @@ -2124,13 +2137,13 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ if (p_target_sdk == -1) { p_target_sdk = DEFAULT_TARGET_SDK_VERSION; } - String exe_ext = ""; + String exe_ext; if (OS::get_singleton()->get_name() == "Windows") { exe_ext = ".bat"; } String apksigner_command_name = "apksigner" + exe_ext; String sdk_path = EDITOR_GET("export/android/android_sdk_path"); - String apksigner_path = ""; + String apksigner_path; Error errn; String build_tools_dir = sdk_path.path_join("build-tools"); @@ -2229,6 +2242,54 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ return apksigner_path; } +static bool has_valid_keystore_credentials(String &r_error_str, const String &p_keystore, const String &p_username, const String &p_password, const String &p_type) { + String output; + List<String> args; + args.push_back("-list"); + args.push_back("-keystore"); + args.push_back(p_keystore); + args.push_back("-storepass"); + args.push_back(p_password); + args.push_back("-alias"); + args.push_back(p_username); + Error error = OS::get_singleton()->execute("keytool", args, &output, nullptr, true); + String keytool_error = "keytool error:"; + bool valid = output.substr(0, keytool_error.length()) != keytool_error; + + if (error != OK) { + r_error_str = TTR("Error: There was a problem validating the keystore username and password"); + return false; + } + if (!valid) { + r_error_str = TTR(p_type + " Username and/or Password is invalid for the given " + p_type + " Keystore"); + return false; + } + r_error_str = ""; + return true; +} + +bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error) { + String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH); + String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER); + String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS); + String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH); + String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); + String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); + + bool valid = true; + if (!dk.is_empty() && !dk_user.is_empty() && !dk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, dk, dk_user, dk_password, "Debug"); + r_error += err; + } + if (!rk.is_empty() && !rk_user.is_empty() && !rk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, rk, rk_user, rk_password, "Release"); + r_error += err; + } + return valid; +} + bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { String err; bool valid = false; @@ -2329,6 +2390,32 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito err += TTR("Release keystore incorrectly configured in the export preset.") + "\n"; } + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + if (java_sdk_path.is_empty()) { + err += TTR("A valid Java SDK path is required in Editor Settings.") + "\n"; + valid = false; + } else { + // Validate the given path by checking that `java` is present under the `bin` directory. + Error errn; + // Check for the bin directory. + Ref<DirAccess> da = DirAccess::open(java_sdk_path.path_join("bin"), &errn); + if (errn != OK) { + err += TTR("Invalid Java SDK path in Editor Settings."); + err += TTR("Missing 'bin' directory!"); + err += "\n"; + valid = false; + } else { + // Check for the `java` command. + String java_path = get_java_path(); + if (!FileAccess::exists(java_path)) { + err += TTR("Unable to find 'java' command using the Java SDK path."); + err += TTR("Please check the Java SDK directory specified in Editor Settings."); + err += "\n"; + valid = false; + } + } + } + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); if (sdk_path.is_empty()) { err += TTR("A valid Android SDK path is required in Editor Settings.") + "\n"; @@ -2842,6 +2929,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!")); return ERR_UNCONFIGURED; } + String err_string; + if (!has_valid_username_and_password(p_preset, err_string)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR(err_string)); + return ERR_UNCONFIGURED; + } if (use_gradle_build) { print_verbose("Starting gradle build..."); @@ -2861,6 +2953,13 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } } const String assets_directory = get_assets_directory(p_preset, export_format); + String java_sdk_path = EDITOR_GET("export/android/java_sdk_path"); + if (java_sdk_path.is_empty()) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Java SDK path must be configured in Editor Settings at 'export/android/java_sdk_path'.")); + return ERR_UNCONFIGURED; + } + print_verbose("Java sdk path: " + java_sdk_path); + String sdk_path = EDITOR_GET("export/android/android_sdk_path"); if (sdk_path.is_empty()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'.")); @@ -2911,8 +3010,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP print_verbose("Storing command line flags..."); store_file_at_path(assets_directory + "/_cl_", command_line_flags); + print_verbose("Updating JAVA_HOME environment to " + java_sdk_path); + OS::get_singleton()->set_environment("JAVA_HOME", java_sdk_path); + print_verbose("Updating ANDROID_HOME environment to " + sdk_path); - OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required + OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); String build_command; #ifdef WINDOWS_ENABLED @@ -2975,6 +3077,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String combined_android_dependencies_maven_repos = String("|").join(android_dependencies_maven_repos); List<String> cmdline; + cmdline.push_back("validateJavaVersion"); if (clean_build_required) { cmdline.push_back("clean"); } diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index a2d0417c5d..c282055fba 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -226,8 +226,11 @@ public: static String get_apksigner_path(int p_target_sdk = -1, bool p_check_executes = false); + static String get_java_path(); + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; + static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error); virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 1249f2219f..f56eda4694 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -121,6 +121,75 @@ uint8_t FileAccessAndroid::get_8() const { return byte; } +uint16_t FileAccessAndroid::get_16() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint16_t bytes = 0; + int r = AAsset_read(asset, &bytes, 2); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP16(bytes); + } + + return bytes; +} + +uint32_t FileAccessAndroid::get_32() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint32_t bytes = 0; + int r = AAsset_read(asset, &bytes, 4); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP32(bytes); + } + + return bytes; +} + +uint64_t FileAccessAndroid::get_64() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint64_t bytes = 0; + int r = AAsset_read(asset, &bytes, 8); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP64(bytes); + } + + return bytes; +} + uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); @@ -151,6 +220,18 @@ void FileAccessAndroid::store_8(uint8_t p_dest) { ERR_FAIL(); } +void FileAccessAndroid::store_16(uint16_t p_dest) { + ERR_FAIL(); +} + +void FileAccessAndroid::store_32(uint32_t p_dest) { + ERR_FAIL(); +} + +void FileAccessAndroid::store_64(uint64_t p_dest) { + ERR_FAIL(); +} + bool FileAccessAndroid::file_exists(const String &p_path) { String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) { diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 3aa4ca98fc..ec613b6687 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -66,12 +66,18 @@ public: virtual bool eof_reached() const override; // reading passed EOF virtual uint8_t get_8() const override; // get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; // get last error virtual void flush() override; virtual void store_8(uint8_t p_dest) override; // store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual bool file_exists(const String &p_path) override; // return true if a file exists diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index beea73fd61..46d9728632 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -181,6 +181,36 @@ uint8_t FileAccessFilesystemJAndroid::get_8() const { return byte; } +uint16_t FileAccessFilesystemJAndroid::get_16() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint16_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2); + if (big_endian) { + bytes = BSWAP16(bytes); + } + return bytes; +} + +uint32_t FileAccessFilesystemJAndroid::get_32() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint32_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4); + if (big_endian) { + bytes = BSWAP32(bytes); + } + return bytes; +} + +uint64_t FileAccessFilesystemJAndroid::get_64() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint64_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8); + if (big_endian) { + bytes = BSWAP64(bytes); + } + return bytes; +} + String FileAccessFilesystemJAndroid::get_line() const { ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use."); @@ -250,6 +280,27 @@ void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) { store_buffer(&p_dest, 1); } +void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) { + if (big_endian) { + p_dest = BSWAP16(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2); +} + +void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) { + if (big_endian) { + p_dest = BSWAP32(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4); +} + +void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) { + if (big_endian) { + p_dest = BSWAP64(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8); +} + void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) { if (_file_write) { ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 0c3f8d7259..f33aa64ebe 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -77,6 +77,9 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF virtual uint8_t get_8() const override; ///< get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual String get_line() const override; ///< get a line virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; @@ -84,6 +87,9 @@ public: virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_path) override; ///< return true if a file exists diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 079f629b12..4abc6548bf 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - package="com.godot.game" android:versionCode="1" android:versionName="1.0" android:installLocation="auto" > diff --git a/platform/android/java/app/assetPacks/installTime/build.gradle b/platform/android/java/app/assetPacks/installTime/build.gradle index b06faac374..46fa046ed4 100644 --- a/platform/android/java/app/assetPacks/installTime/build.gradle +++ b/platform/android/java/app/assetPacks/installTime/build.gradle @@ -1,4 +1,6 @@ -apply plugin: 'com.android.asset-pack' +plugins { + id 'com.android.asset-pack' +} assetPack { packName = "installTime" // Directory name for the asset pack diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 01b148aeef..f084c60209 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -1,17 +1,4 @@ // Gradle build config for Godot Engine's Android port. -buildscript { - apply from: 'config.gradle' - - repositories { - google() - mavenCentral() - } - dependencies { - classpath libraries.androidGradlePlugin - classpath libraries.kotlinGradlePlugin - } -} - plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' @@ -23,6 +10,8 @@ allprojects { repositories { google() mavenCentral() + gradlePluginPortal() + maven { url "https://plugins.gradle.org/m2/" } // Godot user plugins custom maven repos String[] mavenRepos = getGodotPluginsMavenRepos() @@ -42,8 +31,7 @@ configurations { } dependencies { - implementation libraries.kotlinStdLib - implementation libraries.androidxFragment + implementation "androidx.fragment:fragment:$versions.fragmentVersion" if (rootProject.findProject(":lib")) { implementation project(":lib") @@ -88,6 +76,8 @@ android { assetPacks = [":assetPacks:installTime"] + namespace = 'com.godot.game' + defaultConfig { // The default ignore pattern for the 'assets' directory includes hidden files and directories which are used by Godot projects. aaptOptions { @@ -241,3 +231,26 @@ task copyAndRenameReleaseAab(type: Copy) { into getExportPath() rename "build-release.aab", getExportFilename() } + +/** + * Used to validate the version of the Java SDK used for the Godot gradle builds. + */ +task validateJavaVersion { + if (JavaVersion.current() != versions.javaVersion) { + throw new GradleException("Invalid Java version ${JavaVersion.current()}. Version ${versions.javaVersion} is the required Java version for Godot gradle builds.") + } +} + +/* +When they're scheduled to run, the copy*AARToAppModule tasks generate dependencies for the 'app' +module, so we're ensuring the ':app:preBuild' task is set to run after those tasks. + */ +if (rootProject.tasks.findByPath("copyDebugAARToAppModule") != null) { + preBuild.mustRunAfter(rootProject.tasks.named("copyDebugAARToAppModule")) +} +if (rootProject.tasks.findByPath("copyDevAARToAppModule") != null) { + preBuild.mustRunAfter(rootProject.tasks.named("copyDevAARToAppModule")) +} +if (rootProject.tasks.findByPath("copyReleaseAARToAppModule") != null) { + preBuild.mustRunAfter(rootProject.tasks.named("copyReleaseAARToAppModule")) +} diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index a91e7bc7ce..7224765f28 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,27 +1,20 @@ ext.versions = [ - androidGradlePlugin: '7.2.1', - compileSdk : 33, + androidGradlePlugin: '8.2.0', + compileSdk : 34, // Also update 'platform/android/export/export_plugin.cpp#OPENGL_MIN_SDK_VERSION' minSdk : 21, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' - targetSdk : 33, - buildTools : '33.0.2', - kotlinVersion : '1.7.0', - fragmentVersion : '1.3.6', - nexusPublishVersion: '1.1.0', - javaVersion : 17, + targetSdk : 34, + buildTools : '34.0.0', + kotlinVersion : '1.9.20', + fragmentVersion : '1.6.2', + nexusPublishVersion: '1.3.0', + javaVersion : JavaVersion.VERSION_17, // Also update 'platform/android/detect.py#get_ndk_version()' when this is updated. ndkVersion : '23.2.8568313' ] -ext.libraries = [ - androidGradlePlugin: "com.android.tools.build:gradle:$versions.androidGradlePlugin", - kotlinGradlePlugin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlinVersion", - kotlinStdLib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlinVersion", - androidxFragment : "androidx.fragment:fragment:$versions.fragmentVersion", -] - ext.getExportPackageName = { -> // Retrieve the app id from the project property set by the Godot build command. String appId = project.hasProperty("export_package_name") ? project.property("export_package_name") : "" diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle index b4524a3f60..dcac44e393 100644 --- a/platform/android/java/app/settings.gradle +++ b/platform/android/java/app/settings.gradle @@ -7,8 +7,10 @@ pluginManagement { id 'org.jetbrains.kotlin.android' version versions.kotlinVersion } repositories { - gradlePluginPortal() google() + mavenCentral() + gradlePluginPortal() + maven { url "https://plugins.gradle.org/m2/" } } } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index f94454e2a7..c609b33ef4 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -1,18 +1,3 @@ -buildscript { - apply from: 'app/config.gradle' - - repositories { - google() - mavenCentral() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath libraries.androidGradlePlugin - classpath libraries.kotlinGradlePlugin - classpath 'io.github.gradle-nexus:publish-plugin:1.3.0' - } -} - plugins { id 'io.github.gradle-nexus.publish-plugin' } @@ -31,6 +16,8 @@ allprojects { repositories { google() mavenCentral() + gradlePluginPortal() + maven { url "https://plugins.gradle.org/m2/" } } } @@ -173,10 +160,21 @@ task zipGradleBuild(type: Zip) { destinationDirectory = file(binDir) } +/** + * Returns true if the scons build tasks responsible for generating the Godot native shared + * libraries should be excluded. + */ +def excludeSconsBuildTasks() { + return !isAndroidStudio() && !project.hasProperty("generateNativeLibs") +} + +/** + * Generates the list of build tasks that should be excluded from the build process.\ + */ def templateExcludedBuildTask() { // We exclude these gradle tasks so we can run the scons command manually. def excludedTasks = [] - if (!isAndroidStudio()) { + if (excludeSconsBuildTasks()) { logger.lifecycle("Excluding Android studio build tasks") for (String flavor : supportedFlavors) { String[] supportedBuildTypes = supportedFlavorsBuildTypes[flavor] @@ -190,23 +188,42 @@ def templateExcludedBuildTask() { return excludedTasks } -def templateBuildTasks() { +/** + * Generates the build tasks for the given flavor + * @param flavor Must be one of the supported flavors ('template' / 'editor') + */ +def generateBuildTasks(String flavor = "template") { + if (!supportedFlavors.contains(flavor)) { + throw new GradleException("Invalid build flavor: $flavor") + } + def tasks = [] - // Only build the apks and aar files for which we have native shared libraries. - for (String target : supportedFlavorsBuildTypes["template"]) { - File targetLibs = new File("lib/libs/" + target) - if (targetLibs != null + // Only build the apks and aar files for which we have native shared libraries unless we intend + // to run the scons build tasks. + boolean excludeSconsBuildTasks = excludeSconsBuildTasks() + boolean isTemplate = flavor == "template" + String libsDir = isTemplate ? "lib/libs/" : "lib/libs/tools/" + for (String target : supportedFlavorsBuildTypes[flavor]) { + File targetLibs = new File(libsDir + target) + if (!excludeSconsBuildTasks || (targetLibs != null && targetLibs.isDirectory() && targetLibs.listFiles() != null - && targetLibs.listFiles().length > 0) { + && targetLibs.listFiles().length > 0)) { String capitalizedTarget = target.capitalize() - // Copy the generated aar library files to the build directory. - tasks += "copy" + capitalizedTarget + "AARToAppModule" - // Copy the generated aar library files to the bin directory. - tasks += "copy" + capitalizedTarget + "AARToBin" - // Copy the prebuilt binary templates to the bin directory. - tasks += "copy" + capitalizedTarget + "BinaryToBin" + if (isTemplate) { + // Copy the generated aar library files to the build directory. + tasks += "copy${capitalizedTarget}AARToAppModule" + // Copy the generated aar library files to the bin directory. + tasks += "copy${capitalizedTarget}AARToBin" + // Copy the prebuilt binary templates to the bin directory. + tasks += "copy${capitalizedTarget}BinaryToBin" + } else { + // Copy the generated editor apk to the bin directory. + tasks += "copyEditor${capitalizedTarget}ApkToBin" + // Copy the generated editor aab to the bin directory. + tasks += "copyEditor${capitalizedTarget}AabToBin" + } } else { logger.lifecycle("No native shared libs for target $target. Skipping build.") } @@ -265,27 +282,13 @@ task copyEditorDevAabToBin(type: Copy) { /** * Generate the Godot Editor Android apk. * - * Note: The Godot 'tools' shared libraries must have been generated (via scons) prior to running - * this gradle task. The task will only build the apk(s) for which the shared libraries is - * available. + * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries + * must have been generated (via scons) prior to running this gradle task. + * The task will only build the apk(s) for which the shared libraries is available. */ task generateGodotEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - - def tasks = [] - - for (String target : supportedFlavorsBuildTypes["editor"]) { - File targetLibs = new File("lib/libs/tools/" + target) - if (targetLibs != null - && targetLibs.isDirectory() - && targetLibs.listFiles() != null - && targetLibs.listFiles().length > 0) { - tasks += "copyEditor${target.capitalize()}ApkToBin" - tasks += "copyEditor${target.capitalize()}AabToBin" - } - } - - dependsOn = tasks + dependsOn = generateBuildTasks("editor") } /** @@ -293,7 +296,7 @@ task generateGodotEditor { */ task generateGodotTemplates { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = templateBuildTasks() + dependsOn = generateBuildTasks("template") finalizedBy 'zipGradleBuild' } @@ -303,10 +306,10 @@ task generateGodotTemplates { */ task generateDevTemplate { // add parameter to set symbols to true - gradle.startParameter.projectProperties += [doNotStrip: true] + gradle.startParameter.projectProperties += [doNotStrip: "true"] gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = templateBuildTasks() + dependsOn = generateBuildTasks("template") finalizedBy 'zipGradleBuild' } diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 38034aa47c..0f7ffeecae 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -2,14 +2,14 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'base' } dependencies { - implementation libraries.kotlinStdLib - implementation libraries.androidxFragment + implementation "androidx.fragment:fragment:$versions.fragmentVersion" implementation project(":lib") - implementation "androidx.window:window:1.0.0" + implementation "androidx.window:window:1.2.0" } ext { @@ -81,6 +81,8 @@ android { buildToolsVersion versions.buildTools ndkVersion versions.ndkVersion + namespace = "org.godotengine.editor" + defaultConfig { // The 'applicationId' suffix allows to install Godot 3.x(v3) and 4.x(v4) on the same device applicationId "org.godotengine.editor.v4" @@ -90,7 +92,10 @@ android { targetSdkVersion versions.targetSdk missingDimensionStrategy 'products', 'editor' - setProperty("archivesBaseName", "android_editor") + } + + base { + archivesName = "android_editor" } compileOptions { @@ -111,6 +116,10 @@ android { } } + buildFeatures { + buildConfig = true + } + buildTypes { dev { initWith debug diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index 1405b6c737..78dcddac0e 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - package="org.godotengine.editor" android:installLocation="auto"> <supports-screens diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 0d7017ae71..caf64bc933 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -91,6 +91,10 @@ open class GodotEditor : GodotActivity() { private val commandLineParams = ArrayList<String>() override fun onCreate(savedInstanceState: Bundle?) { + // We exclude certain permissions from the set we request at startup, as they'll be + // requested on demand based on use-cases. + PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) Log.d(TAG, "Received parameters ${params.contentToString()}") updateCommandLineParams(params) diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index aa991fceae..471fefaf90 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Wed Jan 17 12:08:26 PST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/platform/android/java/lib/AndroidManifest.xml b/platform/android/java/lib/AndroidManifest.xml index f03a1dd47a..8240843876 100644 --- a/platform/android/java/lib/AndroidManifest.xml +++ b/platform/android/java/lib/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="org.godotengine.godot" android:versionCode="1" android:versionName="1.0"> diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 4340250ad3..61ae0cd58a 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -10,8 +10,7 @@ ext { apply from: "../scripts/publish-module.gradle" dependencies { - implementation libraries.kotlinStdLib - implementation libraries.androidxFragment + implementation "androidx.fragment:fragment:$versions.fragmentVersion" } def pathToRootDir = "../../../../" @@ -39,6 +38,11 @@ android { jvmTarget = versions.javaVersion } + buildFeatures { + aidl = true + buildConfig = true + } + buildTypes { dev { initWith debug diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 217e7a2b60..da86e67c7d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -484,6 +484,14 @@ class Godot(private val context: Context) : SensorEventListener { return containerLayout } + fun onStart(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityStarted() + } + fun onResume(host: GodotHost) { if (host != primaryHost) { return @@ -528,6 +536,14 @@ class Godot(private val context: Context) : SensorEventListener { } } + fun onStop(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityStopped() + } + fun onDestroy(primaryHost: GodotHost) { if (this.primaryHost != primaryHost) { return diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index a60f6e997e..e01c5481d5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -30,7 +30,6 @@ package org.godotengine.godot -import android.Manifest import android.app.Activity import android.content.Intent import android.content.pm.PackageManager @@ -65,10 +64,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { private set override fun onCreate(savedInstanceState: Bundle?) { - // We exclude certain permissions from the set we request at startup, as they'll be - // requested on demand based on use-cases. - PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) - super.onCreate(savedInstanceState) setContentView(R.layout.godot_app_layout) @@ -156,7 +151,8 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { super.onRequestPermissionsResult(requestCode, permissions, grantResults) godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) { + // Logging the result of permission requests + if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE || requestCode == PermissionsUtil.REQUEST_SINGLE_PERMISSION_REQ_CODE) { Log.d(TAG, "Received permissions request result..") for (i in permissions.indices) { val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index f1c029e7a1..643c9a658e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -271,6 +271,32 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } @Override + public void onStop() { + super.onStop(); + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.disconnect(getActivity()); + } + return; + } + + godot.onStop(this); + } + + @Override + public void onStart() { + super.onStart(); + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.connect(getActivity()); + } + return; + } + + godot.onStart(this); + } + + @Override public void onResume() { super.onResume(); if (!godot.isInitialized()) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 52350c12a6..81043ce782 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -114,12 +114,30 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Override public void onActivityPaused() { - onPause(); + queueEvent(() -> { + GodotLib.focusout(); + // Pause the renderer + godotRenderer.onActivityPaused(); + }); + } + + @Override + public void onActivityStopped() { + pauseGLThread(); } @Override public void onActivityResumed() { - onResume(); + queueEvent(() -> { + // Resume the renderer + godotRenderer.onActivityResumed(); + GodotLib.focusin(); + }); + } + + @Override + public void onActivityStarted() { + resumeGLThread(); } @Override @@ -283,26 +301,4 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView /* Set the renderer responsible for frame rendering */ setRenderer(godotRenderer); } - - @Override - public void onResume() { - super.onResume(); - - queueEvent(() -> { - // Resume the renderer - godotRenderer.onActivityResumed(); - GodotLib.focusin(); - }); - } - - @Override - public void onPause() { - super.onPause(); - - queueEvent(() -> { - GodotLib.focusout(); - // Pause the renderer - godotRenderer.onActivityPaused(); - }); - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index edcd9c4d1f..4b51bd778d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -178,12 +178,10 @@ public class GodotIO { } public int[] getDisplaySafeArea() { - DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - Display display = activity.getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getRealSize(size); + Rect rect = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); - int[] result = { 0, 0, size.x, size.y }; + int[] result = { rect.left, rect.top, rect.right, rect.bottom }; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); DisplayCutout cutout = insets.getDisplayCutout(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index ebf3a6b2fb..5b2f9f57c7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -47,8 +47,13 @@ public interface GodotRenderView { void queueOnRenderThread(Runnable event); void onActivityPaused(); + + void onActivityStopped(); + void onActivityResumed(); + void onActivityStarted(); + void onBackPressed(); GodotInputHandler getInputHandler(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 48708152be..a1ee9bd6b4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -92,12 +92,30 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Override public void onActivityPaused() { - onPause(); + queueOnVkThread(() -> { + GodotLib.focusout(); + // Pause the renderer + mRenderer.onVkPause(); + }); + } + + @Override + public void onActivityStopped() { + pauseRenderThread(); + } + + @Override + public void onActivityStarted() { + resumeRenderThread(); } @Override public void onActivityResumed() { - onResume(); + queueOnVkThread(() -> { + // Resume the renderer + mRenderer.onVkResume(); + GodotLib.focusin(); + }); } @Override @@ -211,26 +229,4 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV } return super.onResolvePointerIcon(me, pointerIndex); } - - @Override - public void onResume() { - super.onResume(); - - queueOnVkThread(() -> { - // Resume the renderer - mRenderer.onVkResume(); - GodotLib.focusin(); - }); - } - - @Override - public void onPause() { - super.onPause(); - - queueOnVkThread(() -> { - GodotLib.focusout(); - // Pause the renderer - mRenderer.onVkPause(); - }); - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java index 56397bb2c2..ef97aaeab9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java @@ -122,8 +122,8 @@ import javax.microedition.khronos.opengles.GL10; * <p> * <h3>Activity Life-cycle</h3> * A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients - * are required to call {@link #onPause()} when the activity stops and - * {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to + * are required to call {@link #pauseGLThread()} when the activity stops and + * {@link #resumeGLThread()} when the activity starts. These calls allow GLSurfaceView to * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate * the OpenGL display. * <p> @@ -339,8 +339,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * setRenderer is called: * <ul> * <li>{@link #getRenderMode()} - * <li>{@link #onPause()} - * <li>{@link #onResume()} + * <li>{@link #pauseGLThread()} + * <li>{@link #resumeGLThread()} * <li>{@link #queueEvent(Runnable)} * <li>{@link #requestRender()} * <li>{@link #setRenderMode(int)} @@ -568,6 +568,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } + // -- GODOT start -- /** * Pause the rendering thread, optionally tearing down the EGL context * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}. @@ -578,22 +579,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * * Must not be called before a renderer has been set. */ - public void onPause() { + protected final void pauseGLThread() { mGLThread.onPause(); } /** * Resumes the rendering thread, re-creating the OpenGL context if necessary. It - * is the counterpart to {@link #onPause()}. + * is the counterpart to {@link #pauseGLThread()}. * * This method should typically be called in * {@link android.app.Activity#onStart Activity.onStart}. * * Must not be called before a renderer has been set. */ - public void onResume() { + protected final void resumeGLThread() { mGLThread.onResume(); } + // -- GODOT end -- /** * Queue a runnable to be run on the GL rendering thread. This can be used diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt index e26c9d39c2..89fbb9f580 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt @@ -210,21 +210,23 @@ internal class GodotGestureHandler : SimpleOnGestureListener(), OnScaleGestureLi } override fun onScroll( - originEvent: MotionEvent, + originEvent: MotionEvent?, terminusEvent: MotionEvent, distanceX: Float, distanceY: Float ): Boolean { if (scaleInProgress) { if (dragInProgress) { - // Cancel the drag - GodotInputHandler.handleMotionEvent( - originEvent.source, - MotionEvent.ACTION_CANCEL, - originEvent.buttonState, - originEvent.x, - originEvent.y - ) + if (originEvent != null) { + // Cancel the drag + GodotInputHandler.handleMotionEvent( + originEvent.source, + MotionEvent.ACTION_CANCEL, + originEvent.buttonState, + originEvent.x, + originEvent.y + ) + } dragInProgress = false } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 9a82204467..737b4ac20b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -56,9 +56,9 @@ import java.util.Set; public final class PermissionsUtil { private static final String TAG = PermissionsUtil.class.getSimpleName(); - static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; - static final int REQUEST_CAMERA_PERMISSION = 2; - static final int REQUEST_VIBRATE_PERMISSION = 3; + public static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + public static final int REQUEST_CAMERA_PERMISSION = 2; + public static final int REQUEST_VIBRATE_PERMISSION = 3; public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001; public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002; public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002; @@ -70,7 +70,7 @@ public final class PermissionsUtil { * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a> * @param permissionName the name of the requested permission. * @param activity the caller activity for this method. - * @return true/false. "true" if permission was granted otherwise returns "false". + * @return true/false. "true" if permission is already granted, "false" if a permission request was dispatched. */ public static boolean requestPermission(String permissionName, Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { @@ -124,7 +124,7 @@ public final class PermissionsUtil { /** * Request dangerous permissions which are defined in the Android manifest file from the user. * @param activity the caller activity for this method. - * @return true/false. "true" if all permissions were granted otherwise returns "false". + * @return true/false. "true" if all permissions were already granted, returns "false" if permissions requests were dispatched. */ public static boolean requestManifestPermissions(Activity activity) { return requestManifestPermissions(activity, null); @@ -134,7 +134,7 @@ public final class PermissionsUtil { * Request dangerous permissions which are defined in the Android manifest file from the user. * @param activity the caller activity for this method. * @param excludes Set of permissions to exclude from the request - * @return true/false. "true" if all permissions were granted otherwise returns "false". + * @return true/false. "true" if all permissions were already granted, returns "false" if permissions requests were dispatched. */ public static boolean requestManifestPermissions(Activity activity, @Nullable Set<String> excludes) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { @@ -235,7 +235,7 @@ public final class PermissionsUtil { /** * Check if the given permission is in the AndroidManifest.xml file. * @param context the caller context for this method. - * @param permission the permession to look for in the manifest file. + * @param permission the permission to look for in the manifest file. * @return "true" if the permission is in the manifest file of the activity, "false" otherwise. */ public static boolean hasManifestPermission(Context context, String permission) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt index 3828004198..791b425444 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -99,7 +99,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf * * Must not be called before a [VkRenderer] has been set. */ - open fun onResume() { + protected fun resumeRenderThread() { vkThread.onResume() } @@ -108,7 +108,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf * * Must not be called before a [VkRenderer] has been set. */ - open fun onPause() { + protected fun pauseRenderThread() { vkThread.onPause() } diff --git a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml index dc180375d5..8072ee00db 100644 --- a/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml +++ b/platform/android/java/nativeSrcsConfigs/AndroidManifest.xml @@ -1,2 +1,2 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest package="org.godotengine.godot" /> +<manifest /> diff --git a/platform/android/java/nativeSrcsConfigs/build.gradle b/platform/android/java/nativeSrcsConfigs/build.gradle index 5e810ae1ba..a728241181 100644 --- a/platform/android/java/nativeSrcsConfigs/build.gradle +++ b/platform/android/java/nativeSrcsConfigs/build.gradle @@ -9,6 +9,8 @@ android { buildToolsVersion versions.buildTools ndkVersion versions.ndkVersion + namespace = "org.godotengine.godot" + defaultConfig { minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index 466ffebf22..3137e74244 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -5,12 +5,15 @@ pluginManagement { plugins { id 'com.android.application' version versions.androidGradlePlugin id 'com.android.library' version versions.androidGradlePlugin + id 'com.android.asset-pack' version versions.androidGradlePlugin id 'org.jetbrains.kotlin.android' version versions.kotlinVersion id 'io.github.gradle-nexus.publish-plugin' version versions.nexusPublishVersion } repositories { - gradlePluginPortal() google() + mavenCentral() + gradlePluginPortal() + maven { url "https://plugins.gradle.org/m2/" } } } diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 50075ed3f5..08e792cc04 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -484,7 +484,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv * env->DeleteLocalRef(jobj); } - MessageQueue::get_singleton()->push_callp(obj, str_method, argptrs, count); + Callable(obj, str_method).call_deferredp(argptrs, count); } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) { diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 4e401e633e..3c950bb1b1 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -334,7 +334,7 @@ void GodotJavaWrapper::vibrate(int p_duration_ms) { } } -int GodotJavaWrapper::create_new_godot_instance(List<String> args) { +int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) { if (_create_new_godot_instance) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, 0); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 52043c6027..93998021a9 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -104,7 +104,7 @@ public: void init_input_devices(); void vibrate(int p_duration_ms); String get_input_fallback_mapping(); - int create_new_godot_instance(List<String> args); + int create_new_godot_instance(const List<String> &args); void begin_benchmark_measure(const String &p_context, const String &p_label); void end_benchmark_measure(const String &p_context, const String &p_label); void dump_benchmark(const String &benchmark_file); diff --git a/platform/android/vulkan_context_android.cpp b/platform/android/rendering_context_driver_vulkan_android.cpp index 01e6d14438..9232126b04 100644 --- a/platform/android/vulkan_context_android.cpp +++ b/platform/android/rendering_context_driver_vulkan_android.cpp @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_android.cpp */ +/* rendering_context_driver_vulkan_android.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "vulkan_context_android.h" +#include "rendering_context_driver_vulkan_android.h" #ifdef VULKAN_ENABLED @@ -38,32 +38,32 @@ #include <vulkan/vulkan.h> #endif -const char *VulkanContextAndroid::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanAndroid::_get_platform_surface_extension() const { return VK_KHR_ANDROID_SURFACE_EXTENSION_NAME; } -Error VulkanContextAndroid::window_create(ANativeWindow *p_window, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height) { - VkAndroidSurfaceCreateInfoKHR createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.window = p_window; +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanAndroid::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); - VkSurfaceKHR surface; - VkResult err = vkCreateAndroidSurfaceKHR(get_instance(), &createInfo, nullptr, &surface); - if (err != VK_SUCCESS) { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "vkCreateAndroidSurfaceKHR failed with error " + itos(err)); - } + VkAndroidSurfaceCreateInfoKHR create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; + create_info.window = wpd->window; - return _window_create(DisplayServer::MAIN_WINDOW_ID, p_vsync_mode, surface, p_width, p_height); + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateAndroidSurfaceKHR(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); } -bool VulkanContextAndroid::_use_validation_layers() { - uint32_t count = 0; - _get_preferred_validation_layers(&count, nullptr); +bool RenderingContextDriverVulkanAndroid::_use_validation_layers() const { + TightLocalVector<const char *> layer_names; + Error err = _find_validation_layers(layer_names); // On Android, we use validation layers automatically if they were explicitly linked with the app. - return count > 0; + return (err == OK) && !layer_names.is_empty(); } #endif // VULKAN_ENABLED diff --git a/platform/android/rendering_context_driver_vulkan_android.h b/platform/android/rendering_context_driver_vulkan_android.h new file mode 100644 index 0000000000..a2a42eef24 --- /dev/null +++ b/platform/android/rendering_context_driver_vulkan_android.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* rendering_context_driver_vulkan_android.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 RENDERING_CONTEXT_DRIVER_VULKAN_ANDROID_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_ANDROID_H + +#ifdef VULKAN_ENABLED + +#include "drivers/vulkan/rendering_context_driver_vulkan.h" + +struct ANativeWindow; + +class RenderingContextDriverVulkanAndroid : public RenderingContextDriverVulkan { +private: + virtual const char *_get_platform_surface_extension() const override final; + +protected: + SurfaceID surface_create(const void *p_platform_data) override final; + bool _use_validation_layers() const override final; + +public: + struct WindowPlatformData { + ANativeWindow *window; + }; + + RenderingContextDriverVulkanAndroid() = default; + ~RenderingContextDriverVulkanAndroid() override = default; +}; + +#endif // VULKAN_ENABLED + +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_ANDROID_H diff --git a/platform/ios/SCsub b/platform/ios/SCsub index 18ba6617af..5a57f3840b 100644 --- a/platform/ios/SCsub +++ b/platform/ios/SCsub @@ -2,6 +2,62 @@ Import("env") +import os, json +from platform_methods import run_in_subprocess, architectures, lipo, get_build_version, detect_mvk +import subprocess +import shutil + + +def generate_bundle(target, source, env): + bin_dir = Dir("#bin").abspath + + # Template bundle. + app_prefix = "godot." + env["platform"] + rel_prefix = "libgodot." + env["platform"] + "." + "template_release" + dbg_prefix = "libgodot." + env["platform"] + "." + "template_debug" + if env.dev_build: + app_prefix += ".dev" + rel_prefix += ".dev" + dbg_prefix += ".dev" + if env["precision"] == "double": + app_prefix += ".double" + rel_prefix += ".double" + dbg_prefix += ".double" + + # Lipo template libraries. + rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + ".a") + dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + ".a") + rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + env.extra_suffix + ".a") + dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + env.extra_suffix + ".a") + + # Assemble Xcode project bundle. + app_dir = Dir("#bin/ios_xcode").abspath + templ = Dir("#misc/dist/ios_xcode").abspath + if os.path.exists(app_dir): + shutil.rmtree(app_dir) + shutil.copytree(templ, app_dir) + if rel_target_bin != "": + shutil.copy(rel_target_bin, app_dir + "/libgodot.ios.release.xcframework/ios-arm64/libgodot.a") + if dbg_target_bin != "": + shutil.copy(dbg_target_bin, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a") + if rel_target_bin_sim != "": + shutil.copy( + rel_target_bin_sim, app_dir + "/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a" + ) + if dbg_target_bin_sim != "": + shutil.copy( + dbg_target_bin_sim, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a" + ) + mvk_path = detect_mvk(env, "ios-arm64") + if mvk_path != "": + shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework") + + # ZIP Xcode project bundle. + zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath + shutil.make_archive(zip_dir, "zip", root_dir=app_dir) + shutil.rmtree(app_dir) + + ios_lib = [ "godot_ios.mm", "os_ios.mm", @@ -9,7 +65,7 @@ ios_lib = [ "app_delegate.mm", "view_controller.mm", "ios.mm", - "vulkan_context_ios.mm", + "rendering_context_driver_vulkan_ios.mm", "display_server_ios.mm", "joypad_ios.mm", "godot_view.mm", @@ -20,6 +76,7 @@ ios_lib = [ "device_metrics.m", "keyboard_input_view.mm", "key_mapping_ios.mm", + "ios_terminal_logger.mm", ] env_ios = env.Clone() @@ -41,3 +98,8 @@ def combine_libs(target=None, source=None, env=None): combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs) + +if env["generate_bundle"]: + generate_bundle_command = env.Command("generate_bundle", [], generate_bundle) + command = env.AlwaysBuild(generate_bundle_command) + env.Depends(command, [combine_command]) diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 26d81c8ed6..f8468e3d9e 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -23,6 +23,7 @@ def get_opts(): from SCons.Variables import BoolVariable return [ + ("vulkan_sdk_path", "Path to the Vulkan SDK", ""), ( "IOS_TOOLCHAIN_PATH", "Path to iOS toolchain", @@ -31,6 +32,7 @@ def get_opts(): ("IOS_SDK_PATH", "Path to the iOS SDK", ""), BoolVariable("ios_simulator", "Build for iOS Simulator", False), ("ios_triple", "Triple for ios toolchain", ""), + BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False), ] @@ -152,7 +154,7 @@ def configure(env: "Environment"): env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED", "COREAUDIO_ENABLED"]) if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if env["opengl3"]: env.Append(CPPDEFINES=["GLES3_ENABLED", "GLES_SILENCE_DEPRECATION"]) diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index be4ea1e6ab..3efd2498d4 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -34,11 +34,12 @@ #include "core/input/input.h" #include "servers/display_server.h" -#if defined(VULKAN_ENABLED) -#import "vulkan_context_ios.h" - -#include "drivers/vulkan/rendering_device_vulkan.h" +#if defined(RD_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" +#include "servers/rendering/rendering_device.h" + +#if defined(VULKAN_ENABLED) +#import "rendering_context_driver_vulkan_ios.h" #ifdef USE_VOLK #include <volk.h> @@ -46,6 +47,7 @@ #include <vulkan/vulkan.h> #endif #endif // VULKAN_ENABLED +#endif // RD_ENABLED #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" @@ -59,9 +61,9 @@ class DisplayServerIOS : public DisplayServer { _THREAD_SAFE_CLASS_ -#if defined(VULKAN_ENABLED) - VulkanContextIOS *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + RenderingContextDriver *rendering_context = nullptr; + RenderingDevice *rendering_device = nullptr; #endif id tts = nullptr; @@ -117,7 +119,7 @@ public: // MARK: Keyboard - void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed); + void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location); bool is_keyboard_active() const; // MARK: Motion diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 60da16ae8c..c371f2777c 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -62,34 +62,52 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode tts = [[TTS_IOS alloc] init]; } -#if defined(VULKAN_ENABLED) - context_vulkan = nullptr; - rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + rendering_context = nullptr; + rendering_device = nullptr; - if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextIOS); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - ERR_FAIL_MSG("Failed to initialize Vulkan context"); - } + CALayer *layer = nullptr; - CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanIOS::WindowPlatformData vulkan; +#endif + } wpd; +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; if (!layer) { ERR_FAIL_MSG("Failed to create iOS Vulkan rendering layer."); } + wpd.vulkan.layer_ptr = &layer; + rendering_context = memnew(RenderingContextDriverVulkanIOS); + } +#endif - Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); - if (context_vulkan->window_create(MAIN_WINDOW_ID, p_vsync_mode, layer, size.width, size.height) != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; + if (rendering_context) { + if (rendering_context->initialize() != OK) { + ERR_PRINT(vformat("Failed to initialize %s context", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; + return; + } + + if (rendering_context->window_create(MAIN_WINDOW_ID, &wpd) != OK) { + ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Failed to create Vulkan window."); + return; } - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); + Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); + rendering_context->window_set_size(MAIN_WINDOW_ID, size.width, size.height); + rendering_context->window_set_vsync_mode(MAIN_WINDOW_ID, p_vsync_mode); + + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -116,17 +134,17 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode } DisplayServerIOS::~DisplayServerIOS() { -#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->screen_free(MAIN_WINDOW_ID); + memdelete(rendering_device); + rendering_device = nullptr; } - if (context_vulkan) { - context_vulkan->window_destroy(MAIN_WINDOW_ID); - memdelete(context_vulkan); - context_vulkan = nullptr; + if (rendering_context) { + rendering_context->window_destroy(MAIN_WINDOW_ID); + memdelete(rendering_context); + rendering_context = nullptr; } #endif } @@ -222,6 +240,7 @@ void DisplayServerIOS::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x ev->set_tilt(p_tilt); ev->set_position(Vector2(p_x, p_y)); ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); + ev->set_relative_screen_position(ev->get_relative()); perform_event(ev); } @@ -235,7 +254,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) { // MARK: Keyboard -void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) { +void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) { Ref<InputEventKey> ev; ev.instantiate(); ev->set_echo(false); @@ -258,6 +277,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph ev->set_key_label(p_unshifted); ev->set_physical_keycode(p_physical); ev->set_unicode(fix_unicode(p_char)); + ev->set_location(p_location); perform_event(ev); } @@ -697,9 +717,9 @@ bool DisplayServerIOS::screen_is_kept_on() const { void DisplayServerIOS::resize_window(CGSize viewSize) { Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_size(MAIN_WINDOW_ID, size.x, size.y); } #endif @@ -709,18 +729,18 @@ void DisplayServerIOS::resize_window(CGSize viewSize) { void DisplayServerIOS::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 (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif } DisplayServer::VSyncMode DisplayServerIOS::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 (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif return DisplayServer::VSYNC_ENABLED; diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index ecae6d721e..35ef6d6a78 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -10,6 +10,13 @@ <link title="iOS plugins documentation index">$DOCS_URL/tutorials/platform/ios/index.html</link> </tutorials> <members> + <member name="application/additional_plist_content" type="String" setter="" getter=""> + Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.: + [codeblock] + <key>key_name</key> + <string>value</string> + [/codeblock] + </member> <member name="application/app_store_team_id" type="String" setter="" getter=""> Apple Team ID, unique 10-character string. To locate your Team ID check "Membership details" section in your Apple developer account dashboard, or "Organizational Unit" of your code signing certificate. See [url=https://developer.apple.com/help/account/manage-your-team/locate-your-team-id]Locate your Team ID[/url]. </member> @@ -34,8 +41,8 @@ <member name="application/icon_interpolation" type="int" setter="" getter=""> Interpolation method used to resize application icon. </member> - <member name="application/launch_screens_interpolation" type="int" setter="" getter=""> - Interpolation method used to resize launch screen images. + <member name="application/min_ios_version" type="String" setter="" getter=""> + Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). </member> <member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter=""> UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. @@ -63,6 +70,14 @@ <member name="capabilities/access_wifi" type="bool" setter="" getter=""> If [code]true[/code], networking features related to Wi-Fi access are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. </member> + <member name="capabilities/performance_a12" type="bool" setter="" getter=""> + Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features). + Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer. + </member> + <member name="capabilities/performance_gaming_tier" type="bool" setter="" getter=""> + Requires the graphics performance and features of the A17 Pro and later chips. + Enabling this option limits supported devices to: iPhone 15 Pro and newer. + </member> <member name="capabilities/push_notifications" type="bool" setter="" getter=""> If [code]true[/code], push notifications are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. </member> @@ -108,39 +123,6 @@ <member name="icons/spotlight_80x80" type="String" setter="" getter=""> Spotlight icon file on iPad and iPhone (2x DPI). If left empty, it will fallback to [member ProjectSettings.application/config/icon]. See [url=https://developer.apple.com/design/human-interface-guidelines/foundations/app-icons]App icons[/url]. </member> - <member name="landscape_launch_screens/ipad_1024x768" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="landscape_launch_screens/ipad_2048x1536" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="landscape_launch_screens/iphone_2208x1242" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="landscape_launch_screens/iphone_2436x1125" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/ipad_768x1024" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/ipad_1536x2048" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/iphone_640x960" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/iphone_640x1136" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/iphone_750x1334" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/iphone_1125x2436" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> - <member name="portrait_launch_screens/iphone_1242x2208" type="String" setter="" getter=""> - Application launch screen image file. If left empty, it will fallback to [member ProjectSettings.application/boot_splash/image]. - </member> <member name="privacy/camera_usage_description" type="String" setter="" getter=""> A message displayed when requesting access to the device's camera (in English). </member> @@ -174,9 +156,6 @@ <member name="storyboard/use_custom_bg_color" type="bool" setter="" getter=""> If [code]true[/code], [member storyboard/custom_bg_color] is used as a launch screen background color, otherwise [code]application/boot_splash/bg_color[/code] project setting is used. </member> - <member name="storyboard/use_launch_screen_storyboard" type="bool" setter="" getter=""> - If [code]true[/code], storyboard launch screen is used instead of launch screen images. - </member> <member name="user_data/accessible_from_files_app" type="bool" setter="" getter=""> If [code]true[/code], the app "Documents" folder can be accessed via "Files" app. See [url=https://developer.apple.com/documentation/bundleresources/information_property_list/lssupportsopeningdocumentsinplace]LSSupportsOpeningDocumentsInPlace[/url]. </member> diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index b478759e45..d35819c34d 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -34,14 +34,15 @@ #include "run_icon_svg.gen.h" #include "core/io/json.h" +#include "core/io/plist.h" #include "core/string/translation.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/import/resource_importer_texture_settings.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/themes/editor_scale.h" #include "modules/modules_enabled.gen.h" // For mono and svg. #ifdef MODULE_SVG_ENABLED @@ -105,29 +106,6 @@ static const IconInfo icon_infos[] = { { PNAME("icons/notification_60x60"), "iphone", "Icon-60.png", "60", "3x", "20x20", false } }; -struct LoadingScreenInfo { - const char *preset_key; - const char *export_name; - int width = 0; - int height = 0; - bool rotate = false; -}; - -static const LoadingScreenInfo loading_screen_infos[] = { - { PNAME("landscape_launch_screens/iphone_2436x1125"), "Default-Landscape-X.png", 2436, 1125, false }, - { PNAME("landscape_launch_screens/iphone_2208x1242"), "Default-Landscape-736h@3x.png", 2208, 1242, false }, - { PNAME("landscape_launch_screens/ipad_1024x768"), "Default-Landscape.png", 1024, 768, false }, - { PNAME("landscape_launch_screens/ipad_2048x1536"), "Default-Landscape@2x.png", 2048, 1536, false }, - - { PNAME("portrait_launch_screens/iphone_640x960"), "Default-480h@2x.png", 640, 960, false }, - { PNAME("portrait_launch_screens/iphone_640x1136"), "Default-568h@2x.png", 640, 1136, false }, - { PNAME("portrait_launch_screens/iphone_750x1334"), "Default-667h@2x.png", 750, 1334, false }, - { PNAME("portrait_launch_screens/iphone_1125x2436"), "Default-Portrait-X.png", 1125, 2436, false }, - { PNAME("portrait_launch_screens/ipad_768x1024"), "Default-Portrait.png", 768, 1024, false }, - { PNAME("portrait_launch_screens/ipad_1536x2048"), "Default-Portrait@2x.png", 1536, 2048, false }, - { PNAME("portrait_launch_screens/iphone_1242x2208"), "Default-Portrait-736h@3x.png", 1242, 2208, false } -}; - String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { if (p_preset) { if (p_name == "application/app_store_team_id") { @@ -147,12 +125,6 @@ String EditorExportPlatformIOS::get_export_option_warning(const EditorExportPres } bool EditorExportPlatformIOS::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { - if (p_preset) { - bool sb = p_preset->get("storyboard/use_launch_screen_storyboard"); - if (!sb && p_option != "storyboard/use_launch_screen_storyboard" && p_option.begins_with("storyboard/")) { - return false; - } - } return true; } @@ -181,8 +153,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), "12.0")); + + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/launch_screens_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false)); @@ -217,6 +192,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); @@ -235,16 +212,11 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, icon_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); } } - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale to Fit,Scale to Fill,Scale"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color())); - - for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, loading_screen_infos[i].preset_key, PROPERTY_HINT_FILE, "*.png,*.jpg,*.jpeg"), "")); - } } void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug) { @@ -262,8 +234,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ }; String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); - bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); - bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); + bool dbg_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_debug", ENV_IOS_PROFILE_UUID_DEBUG).operator String().is_empty() || (dbg_sign_id != "iPhone Developer" && dbg_sign_id != "iPhone Distribution"); + bool rel_manual = !p_preset->get_or_env("application/provisioning_profile_uuid_release", ENV_IOS_PROFILE_UUID_RELEASE).operator String().is_empty() || (rel_sign_id != "iPhone Developer" && rel_sign_id != "iPhone Distribution"); String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -287,6 +259,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; + } else if (lines[i].find("$min_version") != -1) { + strnew += lines[i].replace("$min_version", p_preset->get("application/min_ios_version")) + "\n"; } else if (lines[i].find("$signature") != -1) { strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; } else if (lines[i].find("$team_id") != -1) { @@ -359,7 +333,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { capabilities_list.push_back("wifi"); } - + if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) { + capabilities_list.push_back("iphone-performance-gaming-tier"); + } + if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) { + capabilities_list.push_back("iphone-ipad-minimum-performance-a12"); + } for (int idx = 0; idx < capabilities_list.size(); idx++) { capabilities += "<string>" + capabilities_list[idx] + "</string>\n"; } @@ -413,29 +392,20 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ String description = p_preset->get("privacy/photolibrary_usage_description"); strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; } else if (lines[i].find("$plist_launch_screen_name") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : ""; + String value = "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>"; strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n"; } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : ""; + String value = "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };"; strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n"; } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : ""; + String value = "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */,"; strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n"; } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : ""; + String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */,"; strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n"; } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : ""; + String value = "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };"; strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n"; - } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) { - bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); - String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;"; - strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n"; } else if (lines[i].find("$launch_screen_image_mode") != -1) { int image_scale_mode = p_preset->get("storyboard/image_scale_mode"); String value; @@ -783,94 +753,6 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor return OK; } -Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { - Ref<DirAccess> da = DirAccess::open(p_dest_dir); - ERR_FAIL_COND_V_MSG(da.is_null(), ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); - - for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { - LoadingScreenInfo info = loading_screen_infos[i]; - String loading_screen_file = p_preset->get(info.preset_key); - - Color boot_bg_color = GLOBAL_GET("application/boot_splash/bg_color"); - String boot_logo_path = GLOBAL_GET("application/boot_splash/image"); - bool boot_logo_scale = GLOBAL_GET("application/boot_splash/fullsize"); - - if (loading_screen_file.size() > 0) { - // Load custom loading screens, and resize if required. - Ref<Image> img = memnew(Image); - Error err = ImageLoader::load_image(loading_screen_file, img); - if (err != OK) { - ERR_PRINT("Invalid loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "'."); - return ERR_UNCONFIGURED; - } - if (img->get_width() != info.width || img->get_height() != info.height) { - WARN_PRINT("Loading screen (" + String(info.preset_key) + "): '" + loading_screen_file + "' has incorrect size (" + String::num_int64(img->get_width()) + "x" + String::num_int64(img->get_height()) + ") and was automatically resized to " + String::num_int64(info.width) + "x" + String::num_int64(info.height) + "."); - float aspect_ratio = (float)img->get_width() / (float)img->get_height(); - if (boot_logo_scale) { - if (info.height * aspect_ratio <= info.width) { - img->resize(info.height * aspect_ratio, info.height, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } else { - img->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } - } - Ref<Image> new_img = Image::create_empty(info.width, info.height, false, Image::FORMAT_RGBA8); - new_img->fill(boot_bg_color); - _blend_and_rotate(new_img, img, false); - err = new_img->save_png(p_dest_dir + info.export_name); - } else { - err = da->copy(loading_screen_file, p_dest_dir + info.export_name); - } - if (err) { - String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'."; - ERR_PRINT(err_str.utf8().get_data()); - return err; - } - } else { - // Generate loading screen from the splash screen - Ref<Image> img = Image::create_empty(info.width, info.height, false, Image::FORMAT_RGBA8); - img->fill(boot_bg_color); - - Ref<Image> img_bs; - - if (boot_logo_path.length() > 0) { - img_bs = Ref<Image>(memnew(Image)); - ImageLoader::load_image(boot_logo_path, img_bs); - } - if (!img_bs.is_valid()) { - img_bs = Ref<Image>(memnew(Image(boot_splash_png))); - } - if (img_bs.is_valid()) { - float aspect_ratio = (float)img_bs->get_width() / (float)img_bs->get_height(); - if (info.rotate) { - if (boot_logo_scale) { - if (info.width * aspect_ratio <= info.height) { - img_bs->resize(info.width * aspect_ratio, info.width, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } else { - img_bs->resize(info.height, info.height / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } - } - } else { - if (boot_logo_scale) { - if (info.height * aspect_ratio <= info.width) { - img_bs->resize(info.height * aspect_ratio, info.height, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } else { - img_bs->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); - } - } - } - _blend_and_rotate(img, img_bs, info.rotate); - } - Error err = img->save_png(p_dest_dir + info.export_name); - if (err) { - String err_str = String("Failed to export loading screen (") + info.preset_key + ") from splash screen."; - WARN_PRINT(err_str.utf8().get_data()); - } - } - } - - return OK; -} - Error EditorExportPlatformIOS::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHandler p_handler, void *p_userdata) { Vector<String> dirs; String current_dir = p_da->get_current_dir(); @@ -986,7 +868,183 @@ struct ExportLibsData { String dest_dir; }; -void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { +void EditorExportPlatformIOS::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const { + Ref<PList> plist; + plist.instantiate(); + plist->load_file(p_path.path_join("Info.plist")); + Ref<PListNode> root_node = plist->get_root(); + if (root_node.is_null()) { + return; + } + Dictionary root = root_node->get_value(); + if (!root.has("AvailableLibraries")) { + return; + } + Ref<PListNode> libs_node = root["AvailableLibraries"]; + if (libs_node.is_null()) { + return; + } + Array libs = libs_node->get_value(); + r_total_libs = libs.size(); + for (int j = 0; j < libs.size(); j++) { + Ref<PListNode> lib_node = libs[j]; + if (lib_node.is_null()) { + return; + } + Dictionary lib = lib_node->get_value(); + if (lib.has("BinaryPath")) { + Ref<PListNode> path_node = lib["BinaryPath"]; + if (path_node.is_valid()) { + String path = path_node->get_value(); + if (path.ends_with(".a")) { + r_static_libs++; + } + if (path.ends_with(".dylib")) { + r_dylibs++; + } + if (path.ends_with(".framework")) { + r_frameworks++; + } + } + } + } +} + +Error EditorExportPlatformIOS::_convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const { + print_line("Converting to .framework", p_source, " -> ", p_destination); + + Ref<DirAccess> da = DirAccess::create_for_path(p_source); + if (da.is_null()) { + return ERR_CANT_OPEN; + } + + Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (filesystem_da.is_null()) { + return ERR_CANT_OPEN; + } + + if (!filesystem_da->dir_exists(p_destination)) { + Error make_dir_err = filesystem_da->make_dir_recursive(p_destination); + if (make_dir_err) { + return make_dir_err; + } + } + + String asset = p_source.ends_with("/") ? p_source.left(p_source.length() - 1) : p_source; + if (asset.ends_with(".xcframework")) { + Ref<PList> plist; + plist.instantiate(); + plist->load_file(p_source.path_join("Info.plist")); + Ref<PListNode> root_node = plist->get_root(); + if (root_node.is_null()) { + return ERR_CANT_OPEN; + } + Dictionary root = root_node->get_value(); + if (!root.has("AvailableLibraries")) { + return ERR_CANT_OPEN; + } + Ref<PListNode> libs_node = root["AvailableLibraries"]; + if (libs_node.is_null()) { + return ERR_CANT_OPEN; + } + Array libs = libs_node->get_value(); + for (int j = 0; j < libs.size(); j++) { + Ref<PListNode> lib_node = libs[j]; + if (lib_node.is_null()) { + return ERR_CANT_OPEN; + } + Dictionary lib = lib_node->get_value(); + if (lib.has("BinaryPath") && lib.has("LibraryPath") && lib.has("LibraryIdentifier")) { + Ref<PListNode> bpath_node = lib["BinaryPath"]; + Ref<PListNode> lpath_node = lib["LibraryPath"]; + Ref<PListNode> lid_node = lib["LibraryIdentifier"]; + if (bpath_node.is_valid() && lpath_node.is_valid() && lid_node.is_valid()) { + String binary_path = bpath_node->get_value(); + String library_identifier = lid_node->get_value(); + + String file_name = binary_path.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + bpath_node->data_string = framework_name.utf8(); + lpath_node->data_string = framework_name.utf8(); + if (!filesystem_da->dir_exists(p_destination.path_join(library_identifier))) { + filesystem_da->make_dir_recursive(p_destination.path_join(library_identifier)); + } + _convert_to_framework(p_source.path_join(library_identifier).path_join(binary_path), p_destination.path_join(library_identifier).path_join(framework_name), p_id); + if (lib.has("DebugSymbolsPath")) { + Ref<PListNode> dpath_node = lib["DebugSymbolsPath"]; + if (dpath_node.is_valid()) { + String dpath = dpath_node->get_value(); + if (da->dir_exists(p_source.path_join(library_identifier).path_join(dpath))) { + da->copy_dir(p_source.path_join(library_identifier).path_join(dpath), p_destination.path_join(library_identifier).path_join("dSYMs")); + } + } + } + } + } + } + String info_plist = plist->save_text(); + + Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { + f->store_string(info_plist); + } + } else { + String file_name = p_destination.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + da->copy(p_source, p_destination.path_join(file_name)); + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List<String> install_name_args; + install_name_args.push_back("-id"); + install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name)); + install_name_args.push_back(p_destination.path_join(file_name)); + + OS::get_singleton()->execute("install_name_tool", install_name_args); + } + + // Creating Info.plist + { + String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + " <dict>\n" + " <key>CFBundleShortVersionString</key>\n" + " <string>1.0</string>\n" + " <key>CFBundleIdentifier</key>\n" + " <string>$id.framework.$name</string>\n" + " <key>CFBundleName</key>\n" + " <string>$name</string>\n" + " <key>CFBundleExecutable</key>\n" + " <string>$name</string>\n" + " <key>DTPlatformName</key>\n" + " <string>iphoneos</string>\n" + " <key>CFBundleInfoDictionaryVersion</key>\n" + " <string>6.0</string>\n" + " <key>CFBundleVersion</key>\n" + " <string>1</string>\n" + " <key>CFBundlePackageType</key>\n" + " <string>FMWK</string>\n" + " <key>MinimumOSVersion</key>\n" + " <string>12.0</string>\n" + " </dict>\n" + "</plist>"; + + String info_plist = info_plist_format.replace("$id", p_id).replace("$name", file_name); + + Ref<FileAccess> f = FileAccess::open(p_destination.path_join("Info.plist"), FileAccess::WRITE); + if (f.is_valid()) { + f->store_string(info_plist); + } + } + } + + return OK; +} + +void EditorExportPlatformIOS::_add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { // that is just a random number, we just need Godot IDs not to clash with // existing IDs in the project. PbxId current_id = { 0x58938401, 0, 0 }; @@ -1011,7 +1069,12 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese String type; if (asset.exported_path.ends_with(".framework")) { - if (asset.should_embed) { + int total_libs = 0; + int static_libs = 0; + int dylibs = 0; + int frameworks = 0; + _check_xcframework_content(p_out_dir.path_join(asset.exported_path), total_libs, static_libs, dylibs, frameworks); + if (asset.should_embed && (static_libs != total_libs)) { additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; framework_id = (++current_id).str(); pbx_embeded_frameworks += framework_id + ",\n"; @@ -1074,12 +1137,12 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese } } -Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { +Error EditorExportPlatformIOS::_copy_asset(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { String binary_name = p_out_dir.get_file().get_basename(); Ref<DirAccess> da = DirAccess::create_for_path(p_asset); if (da.is_null()) { - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't create directory: " + p_asset + "."); + ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open directory: " + p_asset + "."); } bool file_exists = da->file_exists(p_asset); bool dir_exists = da->dir_exists(p_asset); @@ -1093,7 +1156,8 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String String destination; String asset_path; - bool create_framework = false; + Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); if (p_is_framework && asset.ends_with(".dylib")) { // For iOS we need to turn .dylib into .framework @@ -1113,10 +1177,23 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String asset_path = asset_path.path_join(framework_name); destination_dir = p_out_dir.path_join(asset_path); destination = destination_dir.path_join(file_name); - create_framework = true; - } else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) { - asset_path = String("dylibs").path_join(base_dir); + // Convert to framework and copy. + Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); + if (err) { + return err; + } + } else if (p_is_framework && asset.ends_with(".xcframework")) { + // For iOS we need to turn .dylib inside .xcframework + // into .framework to be able to send application to AppStore + + int total_libs = 0; + int static_libs = 0; + int dylibs = 0; + int frameworks = 0; + _check_xcframework_content(p_asset, total_libs, static_libs, dylibs, frameworks); + + asset_path = String("dylibs").path_join(base_dir); String file_name; if (!p_custom_file_name) { @@ -1128,8 +1205,29 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String asset_path = asset_path.path_join(file_name); destination_dir = p_out_dir.path_join(asset_path); destination = destination_dir; - } else { - asset_path = base_dir; + + if (dylibs > 0) { + // Convert to framework and copy. + Error err = _convert_to_framework(p_asset, destination, p_preset->get("application/bundle_identifier")); + if (err) { + return err; + } + } else { + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } + } + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + } + } else if (p_is_framework && asset.ends_with(".framework")) { + // Framework. + asset_path = String("dylibs").path_join(base_dir); String file_name; @@ -1139,99 +1237,67 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String file_name = *p_custom_file_name; } - destination_dir = p_out_dir.path_join(asset_path); asset_path = asset_path.path_join(file_name); - destination = p_out_dir.path_join(asset_path); - } - - Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V_MSG(filesystem_da.is_null(), ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); + destination_dir = p_out_dir.path_join(asset_path); + destination = destination_dir; - if (!filesystem_da->dir_exists(destination_dir)) { - Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); - if (make_dir_err) { - return make_dir_err; + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; + } } - } - - Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); - if (err) { - return err; - } - if (asset_path.ends_with("/")) { - asset_path = asset_path.left(asset_path.length() - 1); - } - IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed }; - r_exported_assets.push_back(exported_asset); + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } + } else { + // Unknown resource. + asset_path = base_dir; - if (create_framework) { String file_name; if (!p_custom_file_name) { - file_name = p_asset.get_basename().get_file(); + file_name = p_asset.get_file(); } else { file_name = *p_custom_file_name; } - String framework_name = file_name + ".framework"; - - // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib - { - List<String> install_name_args; - install_name_args.push_back("-id"); - install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name)); - install_name_args.push_back(destination); - - OS::get_singleton()->execute("install_name_tool", install_name_args); - } - - // Creating Info.plist - { - String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" - "<plist version=\"1.0\">\n" - "<dict>\n" - "<key>CFBundleShortVersionString</key>\n" - "<string>1.0</string>\n" - "<key>CFBundleIdentifier</key>\n" - "<string>com.gdextension.framework.$name</string>\n" - "<key>CFBundleName</key>\n" - "<string>$name</string>\n" - "<key>CFBundleExecutable</key>\n" - "<string>$name</string>\n" - "<key>DTPlatformName</key>\n" - "<string>iphoneos</string>\n" - "<key>CFBundleInfoDictionaryVersion</key>\n" - "<string>6.0</string>\n" - "<key>CFBundleVersion</key>\n" - "<string>1</string>\n" - "<key>CFBundlePackageType</key>\n" - "<string>FMWK</string>\n" - "<key>MinimumOSVersion</key>\n" - "<string>10.0</string>\n" - "</dict>\n" - "</plist>"; - - String info_plist = info_plist_format.replace("$name", file_name); + destination_dir = p_out_dir.path_join(asset_path); + asset_path = asset_path.path_join(file_name); + destination = p_out_dir.path_join(asset_path); - Ref<FileAccess> f = FileAccess::open(destination_dir.path_join("Info.plist"), FileAccess::WRITE); - if (f.is_valid()) { - f->store_string(info_plist); + // Copy as is. + if (!filesystem_da->dir_exists(destination_dir)) { + Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); + if (make_dir_err) { + return make_dir_err; } } + Error err = dir_exists ? da->copy_dir(p_asset, destination) : da->copy(p_asset, destination); + if (err) { + return err; + } } + if (asset_path.ends_with("/")) { + asset_path = asset_path.left(asset_path.length() - 1); + } + IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed }; + r_exported_assets.push_back(exported_asset); + return OK; } -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { +Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { - String asset = p_assets[f_idx]; + const String &asset = p_assets[f_idx]; if (asset.begins_with("res://")) { - Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); + Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); } else if (ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) { - Error err = _copy_asset(p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets); + Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); } else { // either SDK-builtin or already a part of the export template @@ -1243,26 +1309,26 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir return OK; } -Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) { +Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) { Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); for (int i = 0; i < export_plugins.size(); i++) { Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks(); - Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets); + Error err = _export_additional_assets(p_preset, p_out_dir, linked_frameworks, true, false, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks(); - err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets); + err = _export_additional_assets(p_preset, p_out_dir, embedded_frameworks, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs(); for (int j = 0; j < project_static_libs.size(); j++) { project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project } - err = _export_additional_assets(p_out_dir, project_static_libs, true, false, r_exported_assets); + err = _export_additional_assets(p_preset, p_out_dir, project_static_libs, true, false, r_exported_assets); ERR_FAIL_COND_V(err, err); Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); - err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets); + err = _export_additional_assets(p_preset, p_out_dir, ios_bundle_files, false, false, r_exported_assets); ERR_FAIL_COND_V(err, err); } @@ -1270,7 +1336,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir for (int i = 0; i < p_libraries.size(); ++i) { library_paths.push_back(p_libraries[i].path); } - Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets); + Error err = _export_additional_assets(p_preset, p_out_dir, library_paths, true, true, r_exported_assets); ERR_FAIL_COND_V(err, err); return OK; @@ -1315,7 +1381,7 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> String plugin_binary_result_file = plugin.binary.get_file(); // We shouldn't embed .xcframework that contains static libraries. // Static libraries are not embedded anyway. - err = _copy_asset(dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); + err = _copy_asset(p_preset, dest_dir, plugin_main_binary, &plugin_binary_result_file, true, false, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); // Adding dependencies. @@ -1440,15 +1506,15 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> // Export files { // Export linked plugin dependency - err = _export_additional_assets(dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); + err = _export_additional_assets(p_preset, dest_dir, plugin_linked_dependencies, true, false, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); // Export embedded plugin dependency - err = _export_additional_assets(dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); + err = _export_additional_assets(p_preset, dest_dir, plugin_embedded_dependencies, true, true, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); // Export plugin files - err = _export_additional_assets(dest_dir, plugin_files, false, false, r_exported_assets); + err = _export_additional_assets(p_preset, dest_dir, plugin_files, false, false, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); } @@ -1619,6 +1685,8 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres false }; + config_data.plist_content += p_preset->get("application/additional_plist_content").operator String() + "\n"; + Vector<IOSExportAsset> assets; Ref<DirAccess> tmp_app_path = DirAccess::create_for_path(dest_dir); @@ -1824,9 +1892,6 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } { - bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); - - String launch_image_path = binary_dir + "/Images.xcassets/LaunchImage.launchimage/"; String splash_image_path = binary_dir + "/Images.xcassets/SplashImage.imageset/"; Ref<DirAccess> launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -1835,30 +1900,11 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres return ERR_CANT_CREATE; } - if (use_storyboard) { - print_line("Using Launch Storyboard"); + print_line("Exporting launch screen storyboard"); - if (launch_screen_da->change_dir(launch_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(launch_image_path); - } - - err = _export_loading_screen_file(p_preset, splash_image_path); + err = _export_loading_screen_file(p_preset, splash_image_path); + if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), splash_image_path, err)); - } else { - print_line("Using Launch Images"); - - const String launch_screen_path = binary_dir + "/Launch Screen.storyboard"; - - launch_screen_da->remove(launch_screen_path); - - if (launch_screen_da->change_dir(splash_image_path) == OK) { - launch_screen_da->erase_contents_recursive(); - launch_screen_da->remove(splash_image_path); - } - - err = _export_loading_screen_images(p_preset, launch_image_path); - add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Failed to create a file at path \"%s\" with code %d."), launch_image_path, err)); } } @@ -1867,8 +1913,8 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres } print_line("Exporting additional assets"); - _export_additional_assets(binary_dir, libraries, assets); - _add_assets_to_project(p_preset, project_file_data, assets); + _export_additional_assets(p_preset, binary_dir, libraries, assets); + _add_assets_to_project(dest_dir, p_preset, project_file_data, assets); String project_file_name = binary_dir + ".xcodeproj/project.pbxproj"; { Ref<FileAccess> f = FileAccess::open(project_file_name, FileAccess::WRITE); @@ -2010,6 +2056,26 @@ bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExp valid = dvalid || rvalid; r_missing_templates = !valid; + const String &additional_plist_content = p_preset->get("application/additional_plist_content"); + if (!additional_plist_content.is_empty()) { + const String &plist = vformat("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" + "<plist version=\"1.0\">" + "<dict>\n" + "%s\n" + "</dict>\n" + "</plist>\n", + additional_plist_content); + + String plist_err; + Ref<PList> plist_parser; + plist_parser.instantiate(); + if (!plist_parser->load_string(plist, plist_err)) { + err += TTR("Invalid additional PList content: ") + plist_err + "\n"; + valid = false; + } + } + if (!err.is_empty()) { r_error = err; } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index fd2243bcda..edbe566dab 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -127,17 +127,19 @@ class EditorExportPlatformIOS : public EditorExportPlatform { String _get_linker_flags(); String _get_cpp_code(); void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug); - Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir); Vector<ExportArchitecture> _get_supported_architectures() const; Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const; - void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); - Error _copy_asset(const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); - Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); + void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const; + Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const; + + void _add_assets_to_project(const String &p_out_dir, const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets); + Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); + Error _copy_asset(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const String &p_asset, const String *p_custom_file_name, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets); + Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug); Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick); diff --git a/platform/ios/vulkan_context_ios.h b/platform/ios/ios_terminal_logger.h index 58dad4aad6..7f0bc37a07 100644 --- a/platform/ios/vulkan_context_ios.h +++ b/platform/ios/ios_terminal_logger.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_ios.h */ +/* ios_terminal_logger.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef VULKAN_CONTEXT_IOS_H -#define VULKAN_CONTEXT_IOS_H +#ifndef IOS_TERMINAL_LOGGER_H +#define IOS_TERMINAL_LOGGER_H -#ifdef VULKAN_ENABLED +#ifdef IOS_ENABLED -#include "drivers/vulkan/vulkan_context.h" - -#import <UIKit/UIKit.h> - -class VulkanContextIOS : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; +#include "core/io/logger.h" +class IOSTerminalLogger : public StdLogger { public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height); - - VulkanContextIOS(); - ~VulkanContextIOS(); + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; }; -#endif // VULKAN_ENABLED +#endif // IOS_ENABLED -#endif // VULKAN_CONTEXT_IOS_H +#endif // IOS_TERMINAL_LOGGER_H diff --git a/platform/ios/ios_terminal_logger.mm b/platform/ios/ios_terminal_logger.mm new file mode 100644 index 0000000000..b4c9821cdc --- /dev/null +++ b/platform/ios/ios_terminal_logger.mm @@ -0,0 +1,74 @@ +/**************************************************************************/ +/* ios_terminal_logger.mm */ +/**************************************************************************/ +/* 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 "ios_terminal_logger.h" + +#ifdef IOS_ENABLED + +#include <os/log.h> + +void IOSTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { + if (!should_log(true)) { + return; + } + + const char *err_details; + if (p_rationale && p_rationale[0]) { + err_details = p_rationale; + } else { + err_details = p_code; + } + + switch (p_type) { + case ERR_WARNING: + os_log_info(OS_LOG_DEFAULT, + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + break; + case ERR_SCRIPT: + os_log_error(OS_LOG_DEFAULT, + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + break; + case ERR_SHADER: + os_log_error(OS_LOG_DEFAULT, + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + break; + case ERR_ERROR: + default: + os_log_error(OS_LOG_DEFAULT, + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + break; + } +} + +#endif // IOS_ENABLED diff --git a/platform/ios/key_mapping_ios.h b/platform/ios/key_mapping_ios.h index 6cc61175bb..8874da3024 100644 --- a/platform/ios/key_mapping_ios.h +++ b/platform/ios/key_mapping_ios.h @@ -41,6 +41,7 @@ class KeyMappingIOS { public: static void initialize(); static Key remap_key(CFIndex p_keycode); + static KeyLocation key_location(CFIndex p_keycode); }; #endif // KEY_MAPPING_IOS_H diff --git a/platform/ios/key_mapping_ios.mm b/platform/ios/key_mapping_ios.mm index d2c84884d1..61f28aa84b 100644 --- a/platform/ios/key_mapping_ios.mm +++ b/platform/ios/key_mapping_ios.mm @@ -38,6 +38,7 @@ struct HashMapHasherKeys { }; HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map; +HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingIOS::initialize() { if (@available(iOS 13.4, *)) { @@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() { keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard. keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU; keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA; + + location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT; + location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT; + location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT; } } @@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) { } return Key::NONE; } + +KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) { + if (@available(iOS 13.4, *)) { + const KeyLocation *location = location_map.getptr(p_keycode); + if (location) { + return *location; + } + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm index bc6eb63ed5..8b614662b7 100644 --- a/platform/ios/keyboard_input_view.mm +++ b/platform/ios/keyboard_input_view.mm @@ -116,8 +116,8 @@ - (void)deleteText:(NSInteger)charactersToDelete { for (int i = 0; i < charactersToDelete; i++) { - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } @@ -137,8 +137,8 @@ key = Key::SPACE; } - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true); - DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED); + DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED); } } diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index 9dc5e11497..2d985a6c0b 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -41,10 +41,12 @@ #include "servers/audio_server.h" #include "servers/rendering/renderer_compositor.h" -#if defined(VULKAN_ENABLED) -#import "vulkan_context_ios.h" +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" -#include "drivers/vulkan/rendering_device_vulkan.h" +#if defined(VULKAN_ENABLED) +#import "rendering_context_driver_vulkan_ios.h" +#endif #endif class OS_IOS : public OS_Unix { diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index 16ac3acbec..6ac21fa9c8 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -35,6 +35,7 @@ #import "app_delegate.h" #import "display_server_ios.h" #import "godot_view.h" +#import "ios_terminal_logger.h" #import "view_controller.h" #include "core/config/project_settings.h" @@ -50,15 +51,17 @@ #import <dlfcn.h> #include <sys/sysctl.h> -#if defined(VULKAN_ENABLED) +#if defined(RD_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" - #import <QuartzCore/CAMetalLayer.h> + +#if defined(VULKAN_ENABLED) #ifdef USE_VOLK #include <volk.h> #else #include <vulkan/vulkan.h> #endif +#endif // VULKAN_ENABLED #endif // Initialization order between compilation units is not guaranteed, @@ -103,12 +106,7 @@ OS_IOS::OS_IOS() { main_loop = nullptr; Vector<Logger *> loggers; - loggers.push_back(memnew(SyslogLogger)); -#ifdef DEBUG_ENABLED - // it seems iOS app's stdout/stderr is only obtainable if you launch it from - // Xcode - loggers.push_back(memnew(StdLogger)); -#endif + loggers.push_back(memnew(IOSTerminalLogger)); _set_logger(memnew(CompositeLogger(loggers))); AudioDriverManager::add_driver(&audio_driver); @@ -255,6 +253,8 @@ Error OS_IOS::open_dynamic_library(const String p_path, void *&p_library_handle, path = get_framework_executable(get_executable_path().get_base_dir().path_join("Frameworks").path_join(p_path.get_file().get_basename() + ".framework")); } + ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); diff --git a/platform/android/vulkan_context_android.h b/platform/ios/rendering_context_driver_vulkan_ios.h index f253149ef6..0778993a05 100644 --- a/platform/android/vulkan_context_android.h +++ b/platform/ios/rendering_context_driver_vulkan_ios.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_android.h */ +/* rendering_context_driver_vulkan_ios.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,28 +28,31 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef VULKAN_CONTEXT_ANDROID_H -#define VULKAN_CONTEXT_ANDROID_H +#ifndef RENDERING_CONTEXT_DRIVER_VULKAN_IOS_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_IOS_H #ifdef VULKAN_ENABLED -#include "drivers/vulkan/vulkan_context.h" +#include "drivers/vulkan/rendering_context_driver_vulkan.h" -struct ANativeWindow; +#import <UIKit/UIKit.h> -class VulkanContextAndroid : public VulkanContext { - virtual const char *_get_platform_surface_extension() const override; +class RenderingContextDriverVulkanIOS : public RenderingContextDriverVulkan { +private: + virtual const char *_get_platform_surface_extension() const override final; -public: - Error window_create(ANativeWindow *p_window, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height); +protected: + SurfaceID surface_create(const void *p_platform_data) override final; - VulkanContextAndroid() = default; - ~VulkanContextAndroid() override = default; +public: + struct WindowPlatformData { + CALayer *const *layer_ptr; + }; -protected: - bool _use_validation_layers() override; + RenderingContextDriverVulkanIOS(); + ~RenderingContextDriverVulkanIOS(); }; #endif // VULKAN_ENABLED -#endif // VULKAN_CONTEXT_ANDROID_H +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_IOS_H diff --git a/platform/ios/vulkan_context_ios.mm b/platform/ios/rendering_context_driver_vulkan_ios.mm index 56f1894e06..7e9c3e0e44 100644 --- a/platform/ios/vulkan_context_ios.mm +++ b/platform/ios/rendering_context_driver_vulkan_ios.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_ios.mm */ +/* rendering_context_driver_vulkan_ios.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#import "vulkan_context_ios.h" +#import "rendering_context_driver_vulkan_ios.h" #ifdef VULKAN_ENABLED @@ -38,27 +38,32 @@ #include <vulkan/vulkan.h> #endif -const char *VulkanContextIOS::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanIOS::_get_platform_surface_extension() const { return VK_MVK_IOS_SURFACE_EXTENSION_NAME; } -Error VulkanContextIOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, CALayer *p_metal_layer, int p_width, int p_height) { - VkIOSSurfaceCreateInfoMVK createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.pView = (__bridge const void *)p_metal_layer; +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanIOS::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); - VkSurfaceKHR surface; - VkResult err = - vkCreateIOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); - ERR_FAIL_COND_V(err, ERR_CANT_CREATE); + VkIOSSurfaceCreateInfoMVK create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK; + create_info.pView = (__bridge const void *)(*wpd->layer_ptr); - return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height); + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateIOSSurfaceMVK(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); } -VulkanContextIOS::VulkanContextIOS() {} +RenderingContextDriverVulkanIOS::RenderingContextDriverVulkanIOS() { + // Does nothing. +} -VulkanContextIOS::~VulkanContextIOS() {} +RenderingContextDriverVulkanIOS::~RenderingContextDriverVulkanIOS() { + // Does nothing. +} #endif // VULKAN_ENABLED diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm index 1f55670b68..6f6c04c2c8 100644 --- a/platform/ios/view_controller.mm +++ b/platform/ios/view_controller.mm @@ -78,13 +78,15 @@ us = u32lbl[0]; } + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + if (!u32text.is_empty() && !u32text.begins_with("UIKey")) { for (int i = 0; i < u32text.length(); i++) { const char32_t c = u32text[i]; - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } else { - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true); + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location); } } } @@ -110,7 +112,9 @@ us = u32lbl[0]; } - DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false); + KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode); + + DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location); } } } 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..cab45b7672 --- /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( + r"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( + r"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( + r"wayland-scanner -c client-header < ${SOURCE} > ${TARGET}", + 'Generating Wayland client header: "${TARGET}"', + ), + single_source=True, + ), + "WAYLAND_API_CODE": Builder( + action=Action( + r"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("rendering_context_driver_vulkan_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..b8a10ea6b9 --- /dev/null +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -0,0 +1,1411 @@ +/**************************************************************************/ +/* 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 && rendering_context) { + rendering_context->window_set_size(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 (rendering_context) { + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanWayland::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 = rendering_context->window_create(wd.id, &wpd); + ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", rendering_driver)); + + rendering_context->window_set_size(wd.id, wd.rect.size.width, wd.rect.size.height); + rendering_context->window_set_vsync_mode(wd.id, wd.vsync_mode); + + emulate_vsync = (rendering_context->window_get_vsync_mode(wd.id) == DisplayServer::VSYNC_ENABLED); + + if (emulate_vsync) { + print_verbose("VSYNC: manually throttling frames using MAILBOX."); + rendering_context->window_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 (rendering_context) { + rendering_context->window_set_vsync_mode(p_window_id, p_vsync_mode); + + emulate_vsync = (rendering_context->window_get_vsync_mode(p_window_id) == DisplayServer::VSYNC_ENABLED); + + if (emulate_vsync) { + print_verbose("VSYNC: manually throttling frames using MAILBOX."); + rendering_context->window_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 (rendering_context) { + return rendering_context->window_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") { + rendering_context = memnew(RenderingContextDriverVulkanWayland); + } +#endif + + if (rendering_context) { + if (rendering_context->initialize() != OK) { + ERR_PRINT(vformat("Could not initialize %s", p_rendering_driver)); + memdelete(rendering_context); + rendering_context = 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 (rendering_context) { + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); + + 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 (rendering_device) { + rendering_device->screen_free(MAIN_WINDOW_ID); + } + + if (rendering_context) { + rendering_context->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) { + memdelete(rendering_device); + } + + if (rendering_context) { + memdelete(rendering_context); + } +#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..58c5dab586 --- /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/rendering_context_driver_vulkan_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 + RenderingContextDriver *rendering_context = 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/rendering_context_driver_vulkan_wayland.cpp b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp new file mode 100644 index 0000000000..c874c45a8a --- /dev/null +++ b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.cpp @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* rendering_context_driver_vulkan_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. */ +/**************************************************************************/ + +#ifdef VULKAN_ENABLED + +#include "rendering_context_driver_vulkan_wayland.h" + +#ifdef USE_VOLK +#include <volk.h> +#else +#include <vulkan/vulkan.h> +#endif + +const char *RenderingContextDriverVulkanWayland::_get_platform_surface_extension() const { + return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME; +} + +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanWayland::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); + + VkWaylandSurfaceCreateInfoKHR create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + create_info.display = wpd->display; + create_info.surface = wpd->surface; + + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateWaylandSurfaceKHR(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); +} + +RenderingContextDriverVulkanWayland::RenderingContextDriverVulkanWayland() { + // Does nothing. +} + +RenderingContextDriverVulkanWayland::~RenderingContextDriverVulkanWayland() { + // Does nothing. +} + +#endif // VULKAN_ENABLED diff --git a/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.h b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.h new file mode 100644 index 0000000000..dfebca1890 --- /dev/null +++ b/platform/linuxbsd/wayland/rendering_context_driver_vulkan_wayland.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* rendering_context_driver_vulkan_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 RENDERING_CONTEXT_DRIVER_VULKAN_WAYLAND_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_WAYLAND_H + +#ifdef VULKAN_ENABLED + +#include "drivers/vulkan/rendering_context_driver_vulkan.h" + +class RenderingContextDriverVulkanWayland : public RenderingContextDriverVulkan { +private: + virtual const char *_get_platform_surface_extension() const override final; + +protected: + SurfaceID surface_create(const void *p_platform_data) override final; + +public: + struct WindowPlatformData { + struct wl_display *display; + struct wl_surface *surface; + }; + + RenderingContextDriverVulkanWayland(); + ~RenderingContextDriverVulkanWayland(); +}; + +#endif // VULKAN_ENABLED + +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_WAYLAND_H diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp new file mode 100644 index 0000000000..ae1d96a3b1 --- /dev/null +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -0,0 +1,4053 @@ +/**************************************************************************/ +/* 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); + } + mm->set_relative_screen_position(mm->get_relative()); + mm->set_screen_velocity(mm->get_velocity()); + + 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); + mm->set_relative_screen_position(mm->get_relative()); + + // 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()); + mm->set_screen_velocity(mm->get_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/SCsub b/platform/linuxbsd/x11/SCsub index bbfaaf10d1..75fe584ad5 100644 --- a/platform/linuxbsd/x11/SCsub +++ b/platform/linuxbsd/x11/SCsub @@ -21,7 +21,7 @@ if env["use_sowrap"]: ) if env["vulkan"]: - source_files.append("vulkan_context_x11.cpp") + source_files.append("rendering_context_driver_vulkan_x11.cpp") if env["opengl3"]: env.Append(CPPDEFINES=["GLAD_GLX_NO_X11"]) diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index e1d842422c..b838e4b870 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 @@ -1659,7 +1670,11 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, V window_set_flag(WindowFlags(i), true, id); } } - +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_create(id); + } +#endif return id; } @@ -1707,9 +1722,13 @@ 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 (rendering_device) { + rendering_device->screen_free(p_id); + } + + if (rendering_context) { + rendering_context->window_destroy(p_id); } #endif #ifdef GLES3_ENABLED @@ -2010,8 +2029,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 +2252,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 (rendering_context) { + rendering_context->window_set_size(p_window, xwa.width, xwa.height); } #endif #if defined(GLES3_ENABLED) @@ -2891,10 +2910,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 +2969,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 +3520,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 +3558,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 +3585,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 +3686,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 +3907,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 +3977,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 (rendering_context) { + rendering_context->window_set_size(window_id, wd.size.width, wd.size.height); } #endif #if defined(GLES3_ENABLED) @@ -4019,6 +4051,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 +4272,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_ @@ -4472,6 +4532,7 @@ void DisplayServerX11::process_events() { sd->set_index(index); sd->set_position(pos); sd->set_relative(pos - curr_pos_elem->value); + sd->set_relative_screen_position(sd->get_relative()); Input::get_singleton()->parse_input_event(sd); curr_pos_elem->value = pos; @@ -4499,8 +4560,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 +4736,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 +4781,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; @@ -4893,8 +4954,10 @@ void DisplayServerX11::process_events() { mm->set_position(pos); mm->set_global_position(pos); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); mm->set_relative(rel); + mm->set_relative_screen_position(rel); last_mouse_pos = pos; @@ -5244,9 +5307,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 (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif @@ -5262,9 +5325,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 (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif #if defined(GLES3_ENABLED) @@ -5606,10 +5669,24 @@ 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 (rendering_context) { + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanX11::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 = rendering_context->window_create(id, &wpd); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s window", rendering_driver)); + + rendering_context->window_set_size(id, win_rect.size.width, win_rect.size.height); + rendering_context->window_set_vsync_mode(id, p_vsync_mode); } #endif #ifdef GLES3_ENABLED @@ -6008,14 +6085,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; + rendering_context = memnew(RenderingContextDriverVulkanX11); + } +#endif + + if (rendering_context) { + if (rendering_context->initialize() != OK) { + ERR_PRINT(vformat("Could not initialize %s", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; r_error = ERR_CANT_CREATE; - ERR_FAIL_MSG("Could not initialize Vulkan"); + return; } driver_found = true; } @@ -6108,7 +6191,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (p_screen == SCREEN_OF_MAIN_WINDOW) { p_screen = SCREEN_PRIMARY; } - window_position = screen_get_position(p_screen) + (screen_get_size(p_screen) - p_resolution) / 2; + Rect2i scr_rect = screen_get_usable_rect(p_screen); + window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; } WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution)); @@ -6123,11 +6207,11 @@ 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 (rendering_context) { + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -6301,9 +6385,13 @@ 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 (rendering_device) { + rendering_device->screen_free(E.key); + } + + if (rendering_context) { + rendering_context->window_destroy(E.key); } #endif #ifdef GLES3_ENABLED @@ -6345,16 +6433,15 @@ 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) { + memdelete(rendering_device); + rendering_device = nullptr; } - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; + if (rendering_context) { + memdelete(rendering_context); + rendering_context = nullptr; } #endif diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index a8d134a6c7..7c094d6a41 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(VULKAN_ENABLED) -#include "x11/vulkan_context_x11.h" +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" -#include "drivers/vulkan/rendering_device_vulkan.h" +#if defined(VULKAN_ENABLED) +#include "x11/rendering_context_driver_vulkan_x11.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) + RenderingContextDriver *rendering_context = 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/rendering_context_driver_vulkan_x11.cpp index d240480f61..bf44062266 100644 --- a/platform/linuxbsd/x11/vulkan_context_x11.cpp +++ b/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.cpp @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_x11.cpp */ +/* rendering_context_driver_vulkan_x11.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,7 +30,7 @@ #ifdef VULKAN_ENABLED -#include "vulkan_context_x11.h" +#include "rendering_context_driver_vulkan_x11.h" #ifdef USE_VOLK #include <volk.h> @@ -38,28 +38,33 @@ #include <vulkan/vulkan.h> #endif -const char *VulkanContextX11::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanX11::_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; - createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.dpy = p_display; - createInfo.window = p_window; +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanX11::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); - VkSurfaceKHR surface; - 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); + VkXlibSurfaceCreateInfoKHR create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + create_info.dpy = wpd->display; + create_info.window = wpd->window; + + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateXlibSurfaceKHR(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); } -VulkanContextX11::VulkanContextX11() { +RenderingContextDriverVulkanX11::RenderingContextDriverVulkanX11() { + // Does nothing. } -VulkanContextX11::~VulkanContextX11() { +RenderingContextDriverVulkanX11::~RenderingContextDriverVulkanX11() { + // Does nothing. } #endif // VULKAN_ENABLED diff --git a/platform/linuxbsd/x11/vulkan_context_x11.h b/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.h index 294fdc710e..d525b69ec7 100644 --- a/platform/linuxbsd/x11/vulkan_context_x11.h +++ b/platform/linuxbsd/x11/rendering_context_driver_vulkan_x11.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_x11.h */ +/* rendering_context_driver_vulkan_x11.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,32 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef VULKAN_CONTEXT_X11_H -#define VULKAN_CONTEXT_X11_H +#ifndef RENDERING_CONTEXT_DRIVER_VULKAN_X11_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_X11_H #ifdef VULKAN_ENABLED -#include "drivers/vulkan/vulkan_context.h" +#include "drivers/vulkan/rendering_context_driver_vulkan.h" #include <X11/Xlib.h> -class VulkanContextX11 : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; +class RenderingContextDriverVulkanX11 : public RenderingContextDriverVulkan { +private: + virtual const char *_get_platform_surface_extension() const override final; + +protected: + SurfaceID surface_create(const void *p_platform_data) 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; + }; - VulkanContextX11(); - ~VulkanContextX11(); + RenderingContextDriverVulkanX11(); + ~RenderingContextDriverVulkanX11(); }; #endif // VULKAN_ENABLED -#endif // VULKAN_CONTEXT_X11_H +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_X11_H diff --git a/platform/macos/SCsub b/platform/macos/SCsub index 30202e5de7..08783ee14a 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -2,8 +2,99 @@ Import("env") -from platform_methods import run_in_subprocess +import os, json +from platform_methods import run_in_subprocess, architectures, lipo, get_build_version import platform_macos_builders +import subprocess +import shutil + + +def generate_bundle(target, source, env): + bin_dir = Dir("#bin").abspath + + if env.editor_build: + # Editor bundle. + prefix = "godot." + env["platform"] + "." + env["target"] + if env.dev_build: + prefix += ".dev" + if env["precision"] == "double": + prefix += ".double" + + # Lipo editor executable. + target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix) + + # Assemble .app bundle and update version info. + app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath + templ = Dir("#misc/dist/macos_tools.app").abspath + if os.path.exists(app_dir): + shutil.rmtree(app_dir) + shutil.copytree(templ, app_dir, ignore=shutil.ignore_patterns("Contents/Info.plist")) + if not os.path.isdir(app_dir + "/Contents/MacOS"): + os.mkdir(app_dir + "/Contents/MacOS") + if target_bin != "": + shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot") + version = get_build_version(False) + short_version = get_build_version(True) + with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin: + with open(app_dir + "/Contents/Info.plist", "wt") as fout: + for line in fin: + line = line.replace("$version", version) + line = line.replace("$short_version", short_version) + fout.write(line) + + # Sign .app bundle. + if env["bundle_sign_identity"] != "": + sign_command = [ + "codesign", + "-s", + env["bundle_sign_identity"], + "--deep", + "--force", + "--options=runtime", + "--entitlements", + ] + if env.dev_build: + sign_command += [Dir("#misc/dist/macos").abspath + "/editor_debug.entitlements"] + else: + sign_command += [Dir("#misc/dist/macos").abspath + "/editor.entitlements"] + sign_command += [app_dir] + subprocess.run(sign_command) + else: + # Template bundle. + app_prefix = "godot." + env["platform"] + rel_prefix = "godot." + env["platform"] + "." + "template_release" + dbg_prefix = "godot." + env["platform"] + "." + "template_debug" + if env.dev_build: + app_prefix += ".dev" + rel_prefix += ".dev" + dbg_prefix += ".dev" + if env["precision"] == "double": + app_prefix += ".double" + rel_prefix += ".double" + dbg_prefix += ".double" + + # Lipo template executables. + rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix) + dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix) + + # Assemble .app bundle. + app_dir = Dir("#bin/macos_template.app").abspath + templ = Dir("#misc/dist/macos_template.app").abspath + if os.path.exists(app_dir): + shutil.rmtree(app_dir) + shutil.copytree(templ, app_dir) + if not os.path.isdir(app_dir + "/Contents/MacOS"): + os.mkdir(app_dir + "/Contents/MacOS") + if rel_target_bin != "": + shutil.copy(rel_target_bin, app_dir + "/Contents/MacOS/godot_macos_release.universal") + if dbg_target_bin != "": + shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal") + + # ZIP .app bundle. + zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath + shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app") + shutil.rmtree(app_dir) + files = [ "os_macos.mm", @@ -14,16 +105,18 @@ files = [ "display_server_macos.mm", "godot_button_view.mm", "godot_content_view.mm", + "godot_status_item.mm", "godot_window_delegate.mm", "godot_window.mm", "key_mapping_macos.mm", "godot_main_macos.mm", "godot_menu_delegate.mm", "godot_menu_item.mm", + "godot_open_save_delegate.mm", "dir_access_macos.mm", "tts_macos.mm", "joypad_macos.cpp", - "vulkan_context_macos.mm", + "rendering_context_driver_vulkan_macos.mm", "gl_manager_macos_angle.mm", "gl_manager_macos_legacy.mm", ] @@ -32,3 +125,8 @@ prog = env.add_program("#bin/godot", files) if env["debug_symbols"] and env["separate_debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos)) + +if env["generate_bundle"]: + generate_bundle_command = env.Command("generate_bundle", [], generate_bundle) + command = env.AlwaysBuild(generate_bundle_command) + env.Depends(command, [prog]) diff --git a/platform/macos/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm index 7c0cab0210..c370422bfa 100644 --- a/platform/macos/crash_handler_macos.mm +++ b/platform/macos/crash_handler_macos.mm @@ -75,6 +75,7 @@ static void handle_crash(int sig) { signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGILL, SIG_DFL); + signal(SIGTRAP, SIG_DFL); if (OS::get_singleton() == nullptr) { abort(); @@ -193,6 +194,7 @@ void CrashHandler::disable() { signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGILL, SIG_DFL); + signal(SIGTRAP, SIG_DFL); #endif disabled = true; @@ -203,5 +205,6 @@ void CrashHandler::initialize() { signal(SIGSEGV, handle_crash); signal(SIGFPE, handle_crash); signal(SIGILL, handle_crash); + signal(SIGTRAP, handle_crash); #endif } diff --git a/platform/macos/detect.py b/platform/macos/detect.py index b41d2141fb..54eeb833fa 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -1,7 +1,7 @@ import os import sys from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang -from platform_methods import detect_arch +from platform_methods import detect_arch, detect_mvk from typing import TYPE_CHECKING @@ -33,6 +33,12 @@ def get_opts(): BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False), ("angle_libs", "Path to the ANGLE static libraries", ""), + ( + "bundle_sign_identity", + "The 'Full Name', 'Common Name' or SHA-1 hash of the signing identity used to sign editor .app bundle.", + "-", + ), + BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False), ] @@ -53,37 +59,6 @@ def get_flags(): ] -def get_mvk_sdk_path(): - def int_or_zero(i): - try: - return int(i) - except: - return 0 - - def ver_parse(a): - return [int_or_zero(i) for i in a.split(".")] - - dirname = os.path.expanduser("~/VulkanSDK") - if not os.path.exists(dirname): - return "" - - ver_file = "0.0.0.0" - ver_num = ver_parse(ver_file) - - files = os.listdir(dirname) - for file in files: - if os.path.isdir(os.path.join(dirname, file)): - ver_comp = ver_parse(file) - lib_name = os.path.join( - os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/libMoltenVK.a" - ) - if os.path.isfile(lib_name) and ver_comp > ver_num: - ver_num = ver_comp - ver_file = file - - return os.path.join(os.path.join(dirname, ver_file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/") - - def configure(env: "Environment"): # Validate arch. supported_arches = ["x86_64", "arm64"] @@ -121,12 +96,12 @@ def configure(env: "Environment"): env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.13"]) cc_version = get_compiler_version(env) - cc_version_major = cc_version["major"] - cc_version_minor = cc_version["minor"] + cc_version_major = cc_version["apple_major"] + cc_version_minor = cc_version["apple_minor"] vanilla = is_vanilla_clang(env) # Workaround for Xcode 15 linker bug. - if not vanilla and cc_version_major == 15 and cc_version_minor == 0: + if not vanilla and cc_version_major == 1500 and cc_version_minor == 0: env.Prepend(LINKFLAGS=["-ld_classic"]) env.Append(CCFLAGS=["-fobjc-arc"]) @@ -260,30 +235,15 @@ def configure(env: "Environment"): env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"]) if not env["use_volk"]: env.Append(LINKFLAGS=["-lMoltenVK"]) - mvk_found = False - - mvk_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"] - if env["vulkan_sdk_path"] != "": - mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"])) - mvk_list.insert( - 0, - os.path.join( - os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/" - ), - ) - - for mvk_path in mvk_list: - if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): - mvk_found = True - print("MoltenVK found at: " + mvk_path) - env.Append(LINKFLAGS=["-L" + mvk_path]) - break + mvk_path = detect_mvk(env, "macos-arm64_x86_64") - if not mvk_found: + if mvk_path != "": + env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")]) + else: print( "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path." ) diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index a19e02eb21..10c8abe663 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -39,11 +39,13 @@ #include "gl_manager_macos_legacy.h" #endif // GLES3_ENABLED -#if defined(VULKAN_ENABLED) -#include "vulkan_context_macos.h" +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" -#include "drivers/vulkan/rendering_device_vulkan.h" +#if defined(VULKAN_ENABLED) +#include "rendering_context_driver_vulkan_macos.h" #endif // VULKAN_ENABLED +#endif // RD_ENABLED #define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. @@ -73,6 +75,7 @@ public: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; }; struct WindowData { @@ -133,9 +136,9 @@ private: GLManagerLegacy_MacOS *gl_manager_legacy = nullptr; GLManagerANGLE_MacOS *gl_manager_angle = nullptr; #endif -#if defined(VULKAN_ENABLED) - VulkanContextMacOS *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#if defined(RD_ENABLED) + RenderingContextDriver *rendering_context = nullptr; + RenderingDevice *rendering_device = nullptr; #endif String rendering_driver; @@ -198,6 +201,14 @@ private: HashMap<WindowID, WindowData> windows; + struct IndicatorData { + id view; + id item; + }; + + IndicatorID indicator_id_counter = 0; + HashMap<IndicatorID, IndicatorData> indicators; + IOPMAssertionID screen_keep_on_assertion = kIOPMNullAssertionID; struct MenuCall { @@ -232,6 +243,8 @@ private: int _get_system_menu_count(const NSMenu *p_menu) const; NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out); + 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, bool p_options_in_cb); + public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); @@ -343,6 +356,7 @@ public: virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) 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; virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; @@ -426,6 +440,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; @@ -478,6 +494,12 @@ public: virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref<Image> &p_icon) override; + virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override; + virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override; + virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; + virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual void delete_status_indicator(IndicatorID p_id) override; + static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error); static Vector<String> get_rendering_drivers_func(); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 742b552e13..99d4554a42 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -34,6 +34,8 @@ #include "godot_content_view.h" #include "godot_menu_delegate.h" #include "godot_menu_item.h" +#include "godot_open_save_delegate.h" +#include "godot_status_item.h" #include "godot_window.h" #include "godot_window_delegate.h" #include "key_mapping_macos.h" @@ -53,7 +55,7 @@ #include "drivers/gles3/rasterizer_gles3.h" #endif -#if defined(VULKAN_ENABLED) +#if defined(RD_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #endif @@ -159,6 +161,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod defer:NO]; ERR_FAIL_NULL_V_MSG(wd.window_object, INVALID_WINDOW_ID, "Can't create a window"); [wd.window_object setWindowID:window_id_counter]; + [wd.window_object setReleasedWhenClosed:NO]; wd.window_view = [[GodotContentView alloc] init]; ERR_FAIL_NULL_V_MSG(wd.window_view, INVALID_WINDOW_ID, "Can't create a window view"); @@ -192,10 +195,23 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod [layer setBackgroundColor:bg_color.CGColor]; } -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); +#if defined(RD_ENABLED) + if (rendering_context) { + union { +#ifdef VULKAN_ENABLED + RenderingContextDriverVulkanMacOS::WindowPlatformData vulkan; +#endif + } wpd; +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + wpd.vulkan.view_ptr = &wd.window_view; + } +#endif + Error err = rendering_context->window_create(window_id_counter, &wpd); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, vformat("Can't create a %s context", rendering_driver)); + + rendering_context->window_set_size(window_id_counter, p_rect.size.width, p_rect.size.height); + rendering_context->window_set_vsync_mode(window_id_counter, p_vsync_mode); } #endif #if defined(GLES3_ENABLED) @@ -245,11 +261,6 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod gl_manager_angle->window_resize(id, wd.size.width, wd.size.height); } #endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(id, wd.size.width, wd.size.height); - } -#endif return id; } @@ -467,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); k->set_unicode(ke.unicode); + k->set_location(ke.location); _push_input(k); } else { @@ -495,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() { k->set_keycode(ke.keycode); k->set_physical_keycode(ke.physical_keycode); k->set_key_label(ke.key_label); + k->set_location(ke.location); if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { k->set_unicode(key_event_buffer[i + 1].unicode); @@ -776,9 +789,13 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) { gl_manager_legacy->window_destroy(p_window); } #endif -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(p_window); +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_free(p_window); + } + + if (rendering_context) { + rendering_context->window_destroy(p_window); } #endif windows.erase(p_window); @@ -786,6 +803,11 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) { } void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) { +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_size(p_window, p_width, p_height); + } +#endif #if defined(GLES3_ENABLED) if (gl_manager_legacy) { gl_manager_legacy->window_resize(p_window, p_width, p_height); @@ -794,11 +816,6 @@ void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_hei gl_manager_angle->window_resize(p_window, p_width, p_height); } #endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(p_window, p_width, p_height); - } -#endif } bool DisplayServerMacOS::has_feature(Feature p_feature) const { @@ -822,6 +839,7 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const { case FEATURE_TEXT_TO_SPEECH: case FEATURE_EXTEND_TO_TITLE: case FEATURE_SCREEN_CAPTURE: + case FEATURE_STATUS_INDICATOR: return true; default: { } @@ -1662,6 +1680,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + NSMenu *sub_menu = [menu_item submenu]; + if (sub_menu) { + [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + } } } } @@ -2069,139 +2091,37 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect return OK; } -@interface FileDialogDropdown : NSObject { - NSSavePanel *dialog; - NSMutableArray *allowed_types; - int cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types; -- (void)popupAction:(id)sender; -- (int)getIndex; - -@end - -@implementation FileDialogDropdown - -- (int)getIndex { - return cur_index; -} - -- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types { - if ((self = [super init])) { - dialog = p_dialog; - allowed_types = p_allowed_types; - cur_index = 0; - } - return self; -} - -- (void)popupAction:(id)sender { - NSUInteger index = [sender indexOfSelectedItem]; - if (index < [allowed_types count]) { - [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; - cur_index = index; - } else { - [dialog setAllowedFileTypes:@[]]; - cur_index = -1; - } +Error DisplayServerMacOS::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) { + return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); } -@end - -FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) { - NSView *group = [[NSView alloc] initWithFrame:NSZeroRect]; - group.translatesAutoresizingMaskIntoConstraints = NO; - - NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; - label.translatesAutoresizingMaskIntoConstraints = NO; - if (@available(macOS 10.14, *)) { - label.textColor = NSColor.secondaryLabelColor; - } - if (@available(macOS 11.10, *)) { - label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; - } - [group addSubview:label]; - - NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; - popup.translatesAutoresizingMaskIntoConstraints = NO; - - NSMutableArray *allowed_types = [[NSMutableArray alloc] init]; - bool allow_other = false; - for (int i = 0; i < p_filters.size(); i++) { - Vector<String> tokens = p_filters[i].split(";"); - if (tokens.size() >= 1) { - String flt = tokens[0].strip_edges(); - int filter_slice_count = flt.get_slice_count(","); - - NSMutableArray *type_filters = [[NSMutableArray alloc] init]; - for (int j = 0; j < filter_slice_count; j++) { - String str = (flt.get_slice(",", j).strip_edges()); - if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { - allow_other = true; - } else if (!str.is_empty()) { - [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; - } - } - - if ([type_filters count] > 0) { - NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; - [allowed_types addObject:type_filters]; - [popup addItemWithTitle:name_str]; - } - } - } - FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types]; - popup.target = handler; - popup.action = @selector(popupAction:); - - [group addSubview:popup]; - - NSView *view = [[NSView alloc] initWithFrame:NSZeroRect]; - view.translatesAutoresizingMaskIntoConstraints = NO; - [view addSubview:group]; - - NSMutableArray *constraints = [NSMutableArray array]; - [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]]; - [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]]; - [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]]; - [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]]; - [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]]; - [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]]; - [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]]; - [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]]; - [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]]; - [NSLayoutConstraint activateConstraints:constraints]; - - [p_panel setAllowsOtherFileTypes:allow_other]; - if ([allowed_types count] > 0) { - [p_panel setAccessoryView:view]; - [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]]; - } - - return handler; +Error DisplayServerMacOS::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) { + return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true); } -Error DisplayServerMacOS::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) { +Error DisplayServerMacOS::_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, bool p_options_in_cb) { _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()]; - FileDialogDropdown *handler = nullptr; - WindowID prev_focus = last_focused_window; + GodotOpenSaveDelegate *panel_delegate = [[GodotOpenSaveDelegate alloc] init]; + if (p_root.length() > 0) { + [panel_delegate setRootPath:p_root]; + } Callable callback = p_callback; // Make a copy for async completion handler. if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2238,30 +2158,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & url.parse_utf8([[[panel URL] path] UTF8String]); files.push_back(url); if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.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(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } @@ -2273,13 +2223,14 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setDirectoryURL:[NSURL fileURLWithPath:url]]; - handler = _make_accessory_view(panel, p_filters); + [panel_delegate makeAccessoryView:panel filters:p_filters options:p_options]; [panel setExtensionHidden:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)]; [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)]; [panel setShowsHiddenFiles:p_show_hidden]; + [panel setDelegate:panel_delegate]; if (p_filename != "") { NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()]; [panel setNameFieldStringValue:fileurl]; @@ -2323,30 +2274,60 @@ Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String & files.push_back(url); } if (!callback.is_null()) { - Variant v_result = true; - Variant v_files = files; - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.callp(args, 3, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = files; + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } else { if (!callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = [handler getIndex]; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - callback.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(callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant v_opt = [panel_delegate getSelection]; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = [panel_delegate getIndex]; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(callback, args, 3, ce))); + } } } } @@ -2873,7 +2854,11 @@ DisplayServer::WindowID DisplayServerMacOS::create_sub_window(WindowMode p_mode, window_set_flag(WindowFlags(i), true, id); } } - +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_create(id); + } +#endif return id; } @@ -3713,6 +3698,10 @@ bool DisplayServerMacOS::window_is_focused(WindowID p_window) const { return wd.focused; } +DisplayServerMacOS::WindowID DisplayServerMacOS::get_focused_window() const { + return last_focused_window; +} + bool DisplayServerMacOS::window_can_draw(WindowID p_window) const { return windows[p_window].is_visible; } @@ -3830,8 +3819,8 @@ void DisplayServerMacOS::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_ } #endif #if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + if (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif } @@ -3847,8 +3836,8 @@ DisplayServer::VSyncMode DisplayServerMacOS::window_get_vsync_mode(WindowID p_wi } #endif #if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); + if (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif return DisplayServer::VSYNC_ENABLED; @@ -4309,6 +4298,124 @@ void DisplayServerMacOS::set_icon(const Ref<Image> &p_icon) { } } +DisplayServer::IndicatorID DisplayServerMacOS::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { + NSImage *nsimg = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon->duplicate(); + img->convert(Image::FORMAT_RGBA8); + + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nullptr + pixelsWide:img->get_width() + pixelsHigh:img->get_height() + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:img->get_width() * 4 + bitsPerPixel:32]; + if (imgrep) { + uint8_t *pixels = [imgrep bitmapData]; + + int len = img->get_width() * img->get_height(); + const uint8_t *r = img->get_data().ptr(); + + /* Premultiply the alpha channel */ + for (int i = 0; i < len; i++) { + uint8_t alpha = r[i * 4 + 3]; + pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); + pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); + pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); + pixels[i * 4 + 3] = alpha; + } + + nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; + if (nsimg) { + [nsimg addRepresentation:imgrep]; + } + } + } + + IndicatorData idat; + + idat.item = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength]; + idat.view = [[GodotStatusItemView alloc] init]; + + [idat.view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; + [idat.view setImage:nsimg]; + [idat.view setCallback:p_callback]; + [idat.item setView:idat.view]; + + IndicatorID iid = indicator_id_counter++; + indicators[iid] = idat; + + return iid; +} + +void DisplayServerMacOS::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NSImage *nsimg = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon->duplicate(); + img->convert(Image::FORMAT_RGBA8); + + NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes:nullptr + pixelsWide:img->get_width() + pixelsHigh:img->get_height() + bitsPerSample:8 + samplesPerPixel:4 + hasAlpha:YES + isPlanar:NO + colorSpaceName:NSDeviceRGBColorSpace + bytesPerRow:img->get_width() * 4 + bitsPerPixel:32]; + if (imgrep) { + uint8_t *pixels = [imgrep bitmapData]; + + int len = img->get_width() * img->get_height(); + const uint8_t *r = img->get_data().ptr(); + + /* Premultiply the alpha channel */ + for (int i = 0; i < len; i++) { + uint8_t alpha = r[i * 4 + 3]; + pixels[i * 4 + 0] = (uint8_t)(((uint16_t)r[i * 4 + 0] * alpha) / 255); + pixels[i * 4 + 1] = (uint8_t)(((uint16_t)r[i * 4 + 1] * alpha) / 255); + pixels[i * 4 + 2] = (uint8_t)(((uint16_t)r[i * 4 + 2] * alpha) / 255); + pixels[i * 4 + 3] = alpha; + } + + nsimg = [[NSImage alloc] initWithSize:NSMakeSize(img->get_width(), img->get_height())]; + if (nsimg) { + [nsimg addRepresentation:imgrep]; + } + } + } + + [indicators[p_id].view setImage:nsimg]; +} + +void DisplayServerMacOS::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) { + ERR_FAIL_COND(!indicators.has(p_id)); + + [indicators[p_id].view setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; +} + +void DisplayServerMacOS::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { + ERR_FAIL_COND(!indicators.has(p_id)); + + [indicators[p_id].view setCallback:p_callback]; +} + +void DisplayServerMacOS::delete_status_indicator(IndicatorID p_id) { + ERR_FAIL_COND(!indicators.has(p_id)); + + [[NSStatusBar systemStatusBar] removeStatusItem:indicators[p_id].item]; + indicators.erase(p_id); +} + DisplayServer *DisplayServerMacOS::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error) { DisplayServer *ds = memnew(DisplayServerMacOS(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, r_error)); if (r_error != OK) { @@ -4648,14 +4755,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM } } #endif +#if defined(RD_ENABLED) #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextMacOS); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; + rendering_context = memnew(RenderingContextDriverVulkanMacOS); + } +#endif + + if (rendering_context) { + if (rendering_context->initialize() != OK) { + memdelete(rendering_context); + rendering_context = nullptr; r_error = ERR_CANT_CREATE; - ERR_FAIL_MSG("Could not initialize Vulkan"); + ERR_FAIL_MSG("Could not initialize " + rendering_driver); } } #endif @@ -4667,7 +4779,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM if (p_screen == SCREEN_OF_MAIN_WINDOW) { p_screen = SCREEN_PRIMARY; } - window_position = screen_get_position(p_screen) + (screen_get_size(p_screen) - p_resolution) / 2; + Rect2i scr_rect = screen_get_usable_rect(p_screen); + window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; } WindowID main_window = _create_window(p_mode, p_vsync_mode, Rect2i(window_position, p_resolution)); @@ -4688,10 +4801,11 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM RasterizerGLES3::make_current(false); } #endif -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -4706,6 +4820,11 @@ DisplayServerMacOS::~DisplayServerMacOS() { screen_keep_on_assertion = kIOPMNullAssertionID; } + // Destroy all status indicators. + for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) { + [[NSStatusBar systemStatusBar] removeStatusItem:E->value.item]; + } + // Destroy all windows. for (HashMap<WindowID, WindowData>::Iterator E = windows.begin(); E;) { HashMap<WindowID, WindowData>::Iterator F = E; @@ -4725,16 +4844,15 @@ DisplayServerMacOS::~DisplayServerMacOS() { gl_manager_angle = nullptr; } #endif -#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) { + memdelete(rendering_device); + rendering_device = nullptr; } - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; + if (rendering_context) { + memdelete(rendering_context); + rendering_context = nullptr; } #endif diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 85fefe65c0..506b0dffb8 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -10,6 +10,13 @@ <link title="Running Godot apps on macOS">$DOCS_URL/tutorials//export/running_on_macos.html</link> </tutorials> <members> + <member name="application/additional_plist_content" type="String" setter="" getter=""> + Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.: + [codeblock] + <key>key_name</key> + <string>value</string> + [/codeblock] + </member> <member name="application/app_category" type="String" setter="" getter=""> Application category for the App Store. </member> diff --git a/platform/macos/export/codesign.cpp b/platform/macos/export/codesign.cpp index 2b8898e6a1..082c0abea7 100644 --- a/platform/macos/export/codesign.cpp +++ b/platform/macos/export/codesign.cpp @@ -32,8 +32,8 @@ #include "lipo.h" #include "macho.h" -#include "plist.h" +#include "core/io/plist.h" #include "core/os/os.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" diff --git a/platform/macos/export/codesign.h b/platform/macos/export/codesign.h index 3e61751a96..49d53b376e 100644 --- a/platform/macos/export/codesign.h +++ b/platform/macos/export/codesign.h @@ -41,11 +41,10 @@ // - Requirements code generator is not implemented (only hard-coded requirements for the ad-hoc signing is supported). // - RFC5652/CMS blob generation is not implemented, supports ad-hoc signing only. -#include "plist.h" - #include "core/crypto/crypto_core.h" #include "core/io/dir_access.h" #include "core/io/file_access.h" +#include "core/io/plist.h" #include "core/object/ref_counted.h" #include "modules/modules_enabled.gen.h" // For regex. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 6f9db1427b..7ed78db6f8 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -37,13 +37,14 @@ #include "run_icon_svg.gen.h" #include "core/io/image_loader.h" +#include "core/io/plist.h" #include "core/string/translation.h" #include "drivers/png/png_driver_common.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" -#include "editor/editor_scale.h" #include "editor/editor_string_names.h" #include "editor/import/resource_importer_texture_settings.h" +#include "editor/themes/editor_scale.h" #include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For svg and regex. @@ -388,6 +389,8 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/additional_plist_content", PROPERTY_HINT_MULTILINE_TEXT), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/platform_build"), "14C18")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_version"), "13.1")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "xcode/sdk_build"), "22C55")); @@ -672,6 +675,8 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n"; } else if (lines[i].find("$highres") != -1) { strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n"; + } else if (lines[i].find("$additional_plist_content") != -1) { + strnew += lines[i].replace("$additional_plist_content", p_preset->get("application/additional_plist_content")) + "\n"; } else if (lines[i].find("$platfbuild") != -1) { strnew += lines[i].replace("$platfbuild", p_preset->get("xcode/platform_build")) + "\n"; } else if (lines[i].find("$sdkver") != -1) { @@ -2051,13 +2056,17 @@ bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorE String architecture = p_preset->get("binary_format/architecture"); if (architecture == "universal" || architecture == "x86_64") { if (!ResourceImporterTextureSettings::should_import_s3tc_bptc()) { + err += TTR("Cannot export for universal or x86_64 if S3TC BPTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import S3TC BPTC).") + "\n"; valid = false; } - } else if (architecture == "arm64") { + } + if (architecture == "universal" || architecture == "arm64") { if (!ResourceImporterTextureSettings::should_import_etc2_astc()) { + err += TTR("Cannot export for universal or arm64 if ETC2 ASTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import ETC2 ASTC).") + "\n"; valid = false; } - } else { + } + if (architecture != "universal" && architecture != "x86_64" && architecture != "arm64") { ERR_PRINT("Invalid architecture"); } @@ -2091,6 +2100,26 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor }; } + const String &additional_plist_content = p_preset->get("application/additional_plist_content"); + if (!additional_plist_content.is_empty()) { + const String &plist = vformat("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" + "<plist version=\"1.0\">" + "<dict>\n" + "%s\n" + "</dict>\n" + "</plist>\n", + additional_plist_content); + + String plist_err; + Ref<PList> plist_parser; + plist_parser.instantiate(); + if (!plist_parser->load_string(plist, plist_err)) { + err += TTR("Invalid additional PList content: ") + plist_err + "\n"; + valid = false; + } + } + List<ExportOption> options; get_export_options(&options); for (const EditorExportPlatform::ExportOption &E : options) { diff --git a/platform/macos/export/plist.cpp b/platform/macos/export/plist.cpp deleted file mode 100644 index f494c58fc9..0000000000 --- a/platform/macos/export/plist.cpp +++ /dev/null @@ -1,848 +0,0 @@ -/**************************************************************************/ -/* plist.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 "plist.h" - -PList::PLNodeType PListNode::get_type() const { - return data_type; -} - -Variant PListNode::get_value() const { - switch (data_type) { - case PList::PL_NODE_TYPE_NIL: { - return Variant(); - } break; - case PList::PL_NODE_TYPE_STRING: { - return String::utf8(data_string.get_data()); - } break; - case PList::PL_NODE_TYPE_ARRAY: { - Array arr; - for (const Ref<PListNode> &E : data_array) { - arr.push_back(E); - } - return arr; - } break; - case PList::PL_NODE_TYPE_DICT: { - Dictionary dict; - for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { - dict[E.key] = E.value; - } - return dict; - } break; - case PList::PL_NODE_TYPE_BOOLEAN: { - return data_bool; - } break; - case PList::PL_NODE_TYPE_INTEGER: { - return data_int; - } break; - case PList::PL_NODE_TYPE_REAL: { - return data_real; - } break; - case PList::PL_NODE_TYPE_DATA: { - int strlen = data_string.length(); - - size_t arr_len = 0; - Vector<uint8_t> buf; - { - buf.resize(strlen / 4 * 3 + 1); - uint8_t *w = buf.ptrw(); - - ERR_FAIL_COND_V(CryptoCore::b64_decode(&w[0], buf.size(), &arr_len, (unsigned char *)data_string.get_data(), strlen) != OK, Vector<uint8_t>()); - } - buf.resize(arr_len); - return buf; - } break; - case PList::PL_NODE_TYPE_DATE: { - return String(data_string.get_data()); - } break; - } - return Variant(); -} - -Ref<PListNode> PListNode::new_node(const Variant &p_value) { - Ref<PListNode> node; - node.instantiate(); - - switch (p_value.get_type()) { - case Variant::NIL: { - node->data_type = PList::PL_NODE_TYPE_NIL; - } break; - case Variant::BOOL: { - node->data_type = PList::PL_NODE_TYPE_BOOLEAN; - node->data_bool = p_value; - } break; - case Variant::INT: { - node->data_type = PList::PL_NODE_TYPE_INTEGER; - node->data_int = p_value; - } break; - case Variant::FLOAT: { - node->data_type = PList::PL_NODE_TYPE_REAL; - node->data_real = p_value; - } break; - case Variant::STRING_NAME: - case Variant::STRING: { - node->data_type = PList::PL_NODE_TYPE_STRING; - node->data_string = p_value.operator String().utf8(); - } break; - case Variant::DICTIONARY: { - node->data_type = PList::PL_NODE_TYPE_DICT; - Dictionary dict = p_value; - const Variant *next = dict.next(nullptr); - while (next) { - Ref<PListNode> sub_node = dict[*next]; - ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid dictionary element, should be PListNode."); - node->data_dict[*next] = sub_node; - next = dict.next(next); - } - } break; - case Variant::ARRAY: { - node->data_type = PList::PL_NODE_TYPE_ARRAY; - Array ar = p_value; - for (int i = 0; i < ar.size(); i++) { - Ref<PListNode> sub_node = ar[i]; - ERR_FAIL_COND_V_MSG(sub_node.is_null(), Ref<PListNode>(), "Invalid array element, should be PListNode."); - node->data_array.push_back(sub_node); - } - } break; - case Variant::PACKED_BYTE_ARRAY: { - node->data_type = PList::PL_NODE_TYPE_DATA; - PackedByteArray buf = p_value; - node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8(); - } break; - default: { - ERR_FAIL_V_MSG(Ref<PListNode>(), "Unsupported data type."); - } break; - } - return node; -} - -Ref<PListNode> PListNode::new_array() { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_ARRAY; - return node; -} - -Ref<PListNode> PListNode::new_dict() { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; - return node; -} - -Ref<PListNode> PListNode::new_string(const String &p_string) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_STRING; - node->data_string = p_string.utf8(); - return node; -} - -Ref<PListNode> PListNode::new_data(const String &p_string) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATA; - node->data_string = p_string.utf8(); - return node; -} - -Ref<PListNode> PListNode::new_date(const String &p_string) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_DATE; - node->data_string = p_string.utf8(); - node->data_real = (double)Time::get_singleton()->get_unix_time_from_datetime_string(p_string) - 978307200.0; - return node; -} - -Ref<PListNode> PListNode::new_bool(bool p_bool) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_BOOLEAN; - node->data_bool = p_bool; - return node; -} - -Ref<PListNode> PListNode::new_int(int64_t p_int) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_INTEGER; - node->data_int = p_int; - return node; -} - -Ref<PListNode> PListNode::new_real(double p_real) { - Ref<PListNode> node = memnew(PListNode()); - ERR_FAIL_COND_V(node.is_null(), Ref<PListNode>()); - node->data_type = PList::PLNodeType::PL_NODE_TYPE_REAL; - node->data_real = p_real; - return node; -} - -bool PListNode::push_subnode(const Ref<PListNode> &p_node, const String &p_key) { - ERR_FAIL_COND_V(p_node.is_null(), false); - if (data_type == PList::PLNodeType::PL_NODE_TYPE_DICT) { - ERR_FAIL_COND_V(p_key.is_empty(), false); - ERR_FAIL_COND_V(data_dict.has(p_key), false); - data_dict[p_key] = p_node; - return true; - } else if (data_type == PList::PLNodeType::PL_NODE_TYPE_ARRAY) { - data_array.push_back(p_node); - return true; - } else { - ERR_FAIL_V_MSG(false, "PList: Invalid parent node type, should be DICT or ARRAY."); - } -} - -size_t PListNode::get_asn1_size(uint8_t p_len_octets) const { - // Get size of all data, excluding type and size information. - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - return 0; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATA: - case PList::PLNodeType::PL_NODE_TYPE_DATE: { - ERR_FAIL_V_MSG(0, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - return data_string.length(); - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - return 1; - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - return 4; - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - size_t size = 0; - for (int i = 0; i < data_array.size(); i++) { - size += 1 + _asn1_size_len(p_len_octets) + data_array[i]->get_asn1_size(p_len_octets); - } - return size; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - size_t size = 0; - - for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { - size += 1 + _asn1_size_len(p_len_octets); // Sequence. - size += 1 + _asn1_size_len(p_len_octets) + E.key.utf8().length(); //Key. - size += 1 + _asn1_size_len(p_len_octets) + E.value->get_asn1_size(p_len_octets); // Value. - } - return size; - } break; - default: { - return 0; - } break; - } -} - -int PListNode::_asn1_size_len(uint8_t p_len_octets) { - if (p_len_octets > 1) { - return p_len_octets + 1; - } else { - return 1; - } -} - -void PListNode::store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const { - uint32_t size = get_asn1_size(p_len_octets); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (size >> i * 8) & 0xFF; - p_stream.push_back(x); - } -} - -bool PListNode::store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const { - // Convert to binary ASN1 stream. - bool valid = true; - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - // Nothing to store. - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATE: - case PList::PLNodeType::PL_NODE_TYPE_DATA: { - ERR_FAIL_V_MSG(false, "PList: DATE and DATA nodes are not supported by ASN.1 serialization."); - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - p_stream.push_back(0x0C); - store_asn1_size(p_stream, p_len_octets); - for (int i = 0; i < data_string.size(); i++) { - p_stream.push_back(data_string[i]); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - p_stream.push_back(0x01); - store_asn1_size(p_stream, p_len_octets); - if (data_bool) { - p_stream.push_back(0x01); - } else { - p_stream.push_back(0x00); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { - p_stream.push_back(0x02); - store_asn1_size(p_stream, p_len_octets); - for (int i = 4; i >= 0; i--) { - uint8_t x = (data_int >> i * 8) & 0xFF; - p_stream.push_back(x); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - p_stream.push_back(0x03); - store_asn1_size(p_stream, p_len_octets); - for (int i = 4; i >= 0; i--) { - uint8_t x = (data_int >> i * 8) & 0xFF; - p_stream.push_back(x); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - p_stream.push_back(0x30); // Sequence. - store_asn1_size(p_stream, p_len_octets); - for (int i = 0; i < data_array.size(); i++) { - valid = valid && data_array[i]->store_asn1(p_stream, p_len_octets); - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - p_stream.push_back(0x31); // Set. - store_asn1_size(p_stream, p_len_octets); - for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { - CharString cs = E.key.utf8(); - uint32_t size = cs.length(); - - // Sequence. - p_stream.push_back(0x30); - uint32_t seq_size = 2 * (1 + _asn1_size_len(p_len_octets)) + size + E.value->get_asn1_size(p_len_octets); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (seq_size >> i * 8) & 0xFF; - p_stream.push_back(x); - } - // Key. - p_stream.push_back(0x0C); - if (p_len_octets > 1) { - p_stream.push_back(0x80 + p_len_octets); - } - for (int i = p_len_octets - 1; i >= 0; i--) { - uint8_t x = (size >> i * 8) & 0xFF; - p_stream.push_back(x); - } - for (uint32_t i = 0; i < size; i++) { - p_stream.push_back(cs[i]); - } - // Value. - valid = valid && E.value->store_asn1(p_stream, p_len_octets); - } - } break; - } - return valid; -} - -void PListNode::store_text(String &p_stream, uint8_t p_indent) const { - // Convert to text XML stream. - switch (data_type) { - case PList::PLNodeType::PL_NODE_TYPE_NIL: { - // Nothing to store. - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATA: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<data>\n"; - p_stream += String("\t").repeat(p_indent); - p_stream += data_string + "\n"; - p_stream += String("\t").repeat(p_indent); - p_stream += "</data>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DATE: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<date>"; - p_stream += data_string; - p_stream += "</date>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_STRING: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<string>"; - p_stream += String::utf8(data_string); - p_stream += "</string>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_BOOLEAN: { - p_stream += String("\t").repeat(p_indent); - if (data_bool) { - p_stream += "<true/>\n"; - } else { - p_stream += "<false/>\n"; - } - } break; - case PList::PLNodeType::PL_NODE_TYPE_INTEGER: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<integer>"; - p_stream += itos(data_int); - p_stream += "</integer>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_REAL: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<real>"; - p_stream += rtos(data_real); - p_stream += "</real>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_ARRAY: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<array>\n"; - for (int i = 0; i < data_array.size(); i++) { - data_array[i]->store_text(p_stream, p_indent + 1); - } - p_stream += String("\t").repeat(p_indent); - p_stream += "</array>\n"; - } break; - case PList::PLNodeType::PL_NODE_TYPE_DICT: { - p_stream += String("\t").repeat(p_indent); - p_stream += "<dict>\n"; - for (const KeyValue<String, Ref<PListNode>> &E : data_dict) { - p_stream += String("\t").repeat(p_indent + 1); - p_stream += "<key>"; - p_stream += E.key; - p_stream += "</key>\n"; - E.value->store_text(p_stream, p_indent + 1); - } - p_stream += String("\t").repeat(p_indent); - p_stream += "</dict>\n"; - } break; - } -} - -/*************************************************************************/ - -PList::PList() { - root = PListNode::new_dict(); -} - -PList::PList(const String &p_string) { - load_string(p_string); -} - -uint64_t PList::read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size) { - uint64_t pos = p_file->get_position(); - uint64_t ret = 0; - switch (p_size) { - case 1: { - ret = p_file->get_8(); - } break; - case 2: { - ret = BSWAP16(p_file->get_16()); - } break; - case 3: { - ret = BSWAP32(p_file->get_32() & 0x00FFFFFF); - } break; - case 4: { - ret = BSWAP32(p_file->get_32()); - } break; - case 5: { - ret = BSWAP64(p_file->get_64() & 0x000000FFFFFFFFFF); - } break; - case 6: { - ret = BSWAP64(p_file->get_64() & 0x0000FFFFFFFFFFFF); - } break; - case 7: { - ret = BSWAP64(p_file->get_64() & 0x00FFFFFFFFFFFFFF); - } break; - case 8: { - ret = BSWAP64(p_file->get_64()); - } break; - default: { - ret = 0; - } - } - p_file->seek(pos + p_size); - - return ret; -} - -Ref<PListNode> PList::read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx) { - Ref<PListNode> node; - node.instantiate(); - - uint64_t ot_off = trailer.offset_table_start + p_offset_idx * trailer.offset_size; - p_file->seek(ot_off); - uint64_t marker_off = read_bplist_var_size_int(p_file, trailer.offset_size); - ERR_FAIL_COND_V_MSG(marker_off == 0, Ref<PListNode>(), "Invalid marker size."); - - p_file->seek(marker_off); - uint8_t marker = p_file->get_8(); - uint8_t marker_type = marker & 0xF0; - uint64_t marker_size = marker & 0x0F; - - switch (marker_type) { - case 0x00: { - if (marker_size == 0x00) { - node->data_type = PL_NODE_TYPE_NIL; - } else if (marker_size == 0x08) { - node->data_type = PL_NODE_TYPE_BOOLEAN; - node->data_bool = false; - } else if (marker_size == 0x09) { - node->data_type = PL_NODE_TYPE_BOOLEAN; - node->data_bool = true; - } else { - ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid nil/bool marker value."); - } - } break; - case 0x10: { - node->data_type = PL_NODE_TYPE_INTEGER; - node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size))); - } break; - case 0x20: { - node->data_type = PL_NODE_TYPE_REAL; - node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, pow(2, marker_size))); - } break; - case 0x30: { - node->data_type = PL_NODE_TYPE_DATE; - node->data_int = BSWAP64(p_file->get_64()); - node->data_string = Time::get_singleton()->get_datetime_string_from_unix_time(node->data_real + 978307200.0).utf8(); - } break; - case 0x40: { - if (marker_size == 0x0F) { - uint8_t ext = p_file->get_8() & 0xF; - marker_size = read_bplist_var_size_int(p_file, pow(2, ext)); - } - node->data_type = PL_NODE_TYPE_DATA; - PackedByteArray buf; - buf.resize(marker_size + 1); - p_file->get_buffer(reinterpret_cast<uint8_t *>(buf.ptrw()), marker_size); - node->data_string = CryptoCore::b64_encode_str(buf.ptr(), buf.size()).utf8(); - } break; - case 0x50: { - if (marker_size == 0x0F) { - uint8_t ext = p_file->get_8() & 0xF; - marker_size = read_bplist_var_size_int(p_file, pow(2, ext)); - } - node->data_type = PL_NODE_TYPE_STRING; - node->data_string.resize(marker_size + 1); - p_file->get_buffer(reinterpret_cast<uint8_t *>(node->data_string.ptrw()), marker_size); - } break; - case 0x60: { - if (marker_size == 0x0F) { - uint8_t ext = p_file->get_8() & 0xF; - marker_size = read_bplist_var_size_int(p_file, pow(2, ext)); - } - Char16String cs16; - cs16.resize(marker_size + 1); - for (uint64_t i = 0; i < marker_size; i++) { - cs16[i] = BSWAP16(p_file->get_16()); - } - node->data_type = PL_NODE_TYPE_STRING; - node->data_string = String::utf16(cs16.ptr(), cs16.length()).utf8(); - } break; - case 0x80: { - node->data_type = PL_NODE_TYPE_INTEGER; - node->data_int = static_cast<int64_t>(read_bplist_var_size_int(p_file, marker_size + 1)); - } break; - case 0xA0: - case 0xC0: { - if (marker_size == 0x0F) { - uint8_t ext = p_file->get_8() & 0xF; - marker_size = read_bplist_var_size_int(p_file, pow(2, ext)); - } - uint64_t pos = p_file->get_position(); - - node->data_type = PL_NODE_TYPE_ARRAY; - for (uint64_t i = 0; i < marker_size; i++) { - p_file->seek(pos + trailer.ref_size * i); - uint64_t ref = read_bplist_var_size_int(p_file, trailer.ref_size); - - Ref<PListNode> element = read_bplist_obj(p_file, ref); - ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>()); - node->data_array.push_back(element); - } - } break; - case 0xD0: { - if (marker_size == 0x0F) { - uint8_t ext = p_file->get_8() & 0xF; - marker_size = read_bplist_var_size_int(p_file, pow(2, ext)); - } - uint64_t pos = p_file->get_position(); - - node->data_type = PL_NODE_TYPE_DICT; - for (uint64_t i = 0; i < marker_size; i++) { - p_file->seek(pos + trailer.ref_size * i); - uint64_t key_ref = read_bplist_var_size_int(p_file, trailer.ref_size); - - p_file->seek(pos + trailer.ref_size * (i + marker_size)); - uint64_t obj_ref = read_bplist_var_size_int(p_file, trailer.ref_size); - - Ref<PListNode> element_key = read_bplist_obj(p_file, key_ref); - ERR_FAIL_COND_V(element_key.is_null() || element_key->data_type != PL_NODE_TYPE_STRING, Ref<PListNode>()); - Ref<PListNode> element = read_bplist_obj(p_file, obj_ref); - ERR_FAIL_COND_V(element.is_null(), Ref<PListNode>()); - node->data_dict[String::utf8(element_key->data_string.ptr(), element_key->data_string.length())] = element; - } - } break; - default: { - ERR_FAIL_V_MSG(Ref<PListNode>(), "Invalid marker type."); - } - } - return node; -} - -bool PList::load_file(const String &p_filename) { - root = Ref<PListNode>(); - - Ref<FileAccess> fb = FileAccess::open(p_filename, FileAccess::READ); - if (fb.is_null()) { - return false; - } - - unsigned char magic[8]; - fb->get_buffer(magic, 8); - - if (String((const char *)magic, 8) == "bplist00") { - fb->seek_end(-26); - trailer.offset_size = fb->get_8(); - trailer.ref_size = fb->get_8(); - trailer.object_num = BSWAP64(fb->get_64()); - trailer.root_offset_idx = BSWAP64(fb->get_64()); - trailer.offset_table_start = BSWAP64(fb->get_64()); - root = read_bplist_obj(fb, trailer.root_offset_idx); - - return root.is_valid(); - } else { - // Load text plist. - Error err; - Vector<uint8_t> array = FileAccess::get_file_as_bytes(p_filename, &err); - ERR_FAIL_COND_V(err != OK, false); - - String ret; - ret.parse_utf8((const char *)array.ptr(), array.size()); - return load_string(ret); - } -} - -bool PList::load_string(const String &p_string) { - root = Ref<PListNode>(); - - int pos = 0; - bool in_plist = false; - bool done_plist = false; - List<Ref<PListNode>> stack; - String key; - while (pos >= 0) { - int open_token_s = p_string.find("<", pos); - if (open_token_s == -1) { - ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. No tags found."); - } - int open_token_e = p_string.find(">", open_token_s); - pos = open_token_e; - - String token = p_string.substr(open_token_s + 1, open_token_e - open_token_s - 1); - if (token.is_empty()) { - ERR_FAIL_V_MSG(false, "PList: Invalid token name."); - } - String value; - if (token[0] == '?' || token[0] == '!') { // Skip <?xml ... ?> and <!DOCTYPE ... > - int end_token_e = p_string.find(">", open_token_s); - pos = end_token_e; - continue; - } - - if (token.find("plist", 0) == 0) { - in_plist = true; - continue; - } - - if (token == "/plist") { - done_plist = true; - break; - } - - if (!in_plist) { - ERR_FAIL_V_MSG(false, "PList: Node outside of <plist> tag."); - } - - if (token == "dict") { - if (!stack.is_empty()) { - // Add subnode end enter it. - Ref<PListNode> dict = PListNode::new_dict(); - dict->data_type = PList::PLNodeType::PL_NODE_TYPE_DICT; - if (!stack.back()->get()->push_subnode(dict, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - stack.push_back(dict); - } else { - // Add root node. - if (!root.is_null()) { - ERR_FAIL_V_MSG(false, "PList: Root node already set."); - } - Ref<PListNode> dict = PListNode::new_dict(); - stack.push_back(dict); - root = dict; - } - continue; - } - - if (token == "/dict") { - // Exit current dict. - if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_DICT) { - ERR_FAIL_V_MSG(false, "PList: Mismatched </dict> tag."); - } - stack.pop_back(); - continue; - } - - if (token == "array") { - if (!stack.is_empty()) { - // Add subnode end enter it. - Ref<PListNode> arr = PListNode::new_array(); - if (!stack.back()->get()->push_subnode(arr, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - stack.push_back(arr); - } else { - // Add root node. - if (!root.is_null()) { - ERR_FAIL_V_MSG(false, "PList: Root node already set."); - } - Ref<PListNode> arr = PListNode::new_array(); - stack.push_back(arr); - root = arr; - } - continue; - } - - if (token == "/array") { - // Exit current array. - if (stack.is_empty() || stack.back()->get()->data_type != PList::PLNodeType::PL_NODE_TYPE_ARRAY) { - ERR_FAIL_V_MSG(false, "PList: Mismatched </array> tag."); - } - stack.pop_back(); - continue; - } - - if (token[token.length() - 1] == '/') { - token = token.substr(0, token.length() - 1); - } else { - int end_token_s = p_string.find("</", pos); - if (end_token_s == -1) { - ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> tag.", token)); - } - int end_token_e = p_string.find(">", end_token_s); - pos = end_token_e; - String end_token = p_string.substr(end_token_s + 2, end_token_e - end_token_s - 2); - if (end_token != token) { - ERR_FAIL_V_MSG(false, vformat("PList: Mismatched <%s> and <%s> token pair.", token, end_token)); - } - value = p_string.substr(open_token_e + 1, end_token_s - open_token_e - 1); - } - if (token == "key") { - key = value; - } else { - Ref<PListNode> var = nullptr; - if (token == "true") { - var = PListNode::new_bool(true); - } else if (token == "false") { - var = PListNode::new_bool(false); - } else if (token == "integer") { - var = PListNode::new_int(value.to_int()); - } else if (token == "real") { - var = PListNode::new_real(value.to_float()); - } else if (token == "string") { - var = PListNode::new_string(value); - } else if (token == "data") { - var = PListNode::new_data(value); - } else if (token == "date") { - var = PListNode::new_date(value); - } else { - ERR_FAIL_V_MSG(false, "PList: Invalid value type."); - } - if (stack.is_empty() || !stack.back()->get()->push_subnode(var, key)) { - ERR_FAIL_V_MSG(false, "PList: Can't push subnode, invalid parent type."); - } - } - } - if (!stack.is_empty() || !done_plist) { - ERR_FAIL_V_MSG(false, "PList: Unexpected end of data. Root node is not closed."); - } - return true; -} - -PackedByteArray PList::save_asn1() const { - if (root == nullptr) { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node."); - } - size_t size = root->get_asn1_size(1); - uint8_t len_octets = 0; - if (size < 0x80) { - len_octets = 1; - } else { - size = root->get_asn1_size(2); - if (size < 0xFFFF) { - len_octets = 2; - } else { - size = root->get_asn1_size(3); - if (size < 0xFFFFFF) { - len_octets = 3; - } else { - size = root->get_asn1_size(4); - if (size < 0xFFFFFFFF) { - len_octets = 4; - } else { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: Data is too big for ASN.1 serializer, should be < 4 GiB."); - } - } - } - } - - PackedByteArray ret; - if (!root->store_asn1(ret, len_octets)) { - ERR_FAIL_V_MSG(PackedByteArray(), "PList: ASN.1 serializer error."); - } - return ret; -} - -String PList::save_text() const { - if (root == nullptr) { - ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node."); - } - - String ret; - ret += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - ret += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"; - ret += "<plist version=\"1.0\">\n"; - - root->store_text(ret, 0); - - ret += "</plist>\n\n"; - return ret; -} - -Ref<PListNode> PList::get_root() { - return root; -} diff --git a/platform/macos/export/plist.h b/platform/macos/export/plist.h deleted file mode 100644 index 28b02e4eb7..0000000000 --- a/platform/macos/export/plist.h +++ /dev/null @@ -1,128 +0,0 @@ -/**************************************************************************/ -/* plist.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 MACOS_PLIST_H -#define MACOS_PLIST_H - -// Property list file format (application/x-plist) parser, property list ASN-1 serialization. - -#include "core/crypto/crypto_core.h" -#include "core/io/file_access.h" -#include "core/os/time.h" - -class PListNode; - -class PList : public RefCounted { - friend class PListNode; - -public: - enum PLNodeType { - PL_NODE_TYPE_NIL, - PL_NODE_TYPE_STRING, - PL_NODE_TYPE_ARRAY, - PL_NODE_TYPE_DICT, - PL_NODE_TYPE_BOOLEAN, - PL_NODE_TYPE_INTEGER, - PL_NODE_TYPE_REAL, - PL_NODE_TYPE_DATA, - PL_NODE_TYPE_DATE, - }; - -private: - struct PListTrailer { - uint8_t offset_size; - uint8_t ref_size; - uint64_t object_num; - uint64_t root_offset_idx; - uint64_t offset_table_start; - }; - - PListTrailer trailer; - Ref<PListNode> root; - - uint64_t read_bplist_var_size_int(Ref<FileAccess> p_file, uint8_t p_size); - Ref<PListNode> read_bplist_obj(Ref<FileAccess> p_file, uint64_t p_offset_idx); - -public: - PList(); - PList(const String &p_string); - - bool load_file(const String &p_filename); - bool load_string(const String &p_string); - - PackedByteArray save_asn1() const; - String save_text() const; - - Ref<PListNode> get_root(); -}; - -/*************************************************************************/ - -class PListNode : public RefCounted { - static int _asn1_size_len(uint8_t p_len_octets); - -public: - PList::PLNodeType data_type = PList::PLNodeType::PL_NODE_TYPE_NIL; - - CharString data_string; - Vector<Ref<PListNode>> data_array; - HashMap<String, Ref<PListNode>> data_dict; - union { - int64_t data_int; - bool data_bool; - double data_real; - }; - - PList::PLNodeType get_type() const; - Variant get_value() const; - - static Ref<PListNode> new_node(const Variant &p_value); - static Ref<PListNode> new_array(); - static Ref<PListNode> new_dict(); - static Ref<PListNode> new_string(const String &p_string); - static Ref<PListNode> new_data(const String &p_string); - static Ref<PListNode> new_date(const String &p_string); - static Ref<PListNode> new_bool(bool p_bool); - static Ref<PListNode> new_int(int64_t p_int); - static Ref<PListNode> new_real(double p_real); - - bool push_subnode(const Ref<PListNode> &p_node, const String &p_key = ""); - - size_t get_asn1_size(uint8_t p_len_octets) const; - - void store_asn1_size(PackedByteArray &p_stream, uint8_t p_len_octets) const; - bool store_asn1(PackedByteArray &p_stream, uint8_t p_len_octets) const; - void store_text(String &p_stream, uint8_t p_indent) const; - - PListNode() {} - ~PListNode() {} -}; - -#endif // MACOS_PLIST_H diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm index a1925195b8..c1de8ade58 100644 --- a/platform/macos/godot_application_delegate.mm +++ b/platform/macos/godot_application_delegate.mm @@ -35,6 +35,10 @@ @implementation GodotApplicationDelegate +- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { + return YES; +} + - (void)forceUnbundledWindowActivationHackStep1 { // Step 1: Switch focus to macOS SystemUIServer process. // Required to perform step 2, TransformProcessType will fail if app is already the in focus. diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm index 139411249c..f6f054c1e6 100644 --- a/platform/macos/godot_content_view.mm +++ b/platform/macos/godot_content_view.mm @@ -448,8 +448,10 @@ } mm->set_global_position(wd.mouse_pos); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale(); mm->set_relative(relativeMotion); + mm->set_relative_screen_position(relativeMotion); ds->get_key_modifier_state([event modifierFlags], mm); Input::get_singleton()->parse_input_event(mm); @@ -576,21 +578,23 @@ String u32text; u32text.parse_utf16(text.ptr(), text.length()); + DisplayServerMacOS::KeyEvent ke; + ke.window_id = window_id; + ke.macos_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); + ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); + ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); + ke.raw = true; + + if (u32text.is_empty()) { + ke.unicode = 0; + ds->push_to_key_event_buffer(ke); + } for (int i = 0; i < u32text.length(); i++) { const char32_t codepoint = u32text[i]; - - DisplayServerMacOS::KeyEvent ke; - - ke.window_id = window_id; - ke.macos_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], false); - ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); - ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = fix_unicode(codepoint); - ke.raw = true; - ds->push_to_key_event_buffer(ke); } } else { @@ -604,6 +608,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = false; ds->push_to_key_event_buffer(ke); @@ -669,6 +674,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key(key); ke.key_label = KeyMappingMacOS::remap_key(key, mod, true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location(key); ds->push_to_key_event_buffer(ke); } @@ -696,6 +702,7 @@ ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]); ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true); ke.unicode = 0; + ke.location = KeyMappingMacOS::translate_location([event keyCode]); ke.raw = true; ds->push_to_key_event_buffer(ke); diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index 58263471b0..3959fb686c 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -65,7 +65,9 @@ int main(int argc, char **argv) { // We must override main when testing is enabled. TEST_MAIN_OVERRIDE - err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); + @autoreleasepool { + err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); + } if (err == ERR_HELP) { // Returned by --help and --version, so success. return 0; @@ -73,11 +75,17 @@ int main(int argc, char **argv) { return 255; } - if (Main::start()) { + bool ok; + @autoreleasepool { + ok = Main::start(); + } + if (ok) { os.run(); // It is actually the OS that decides how to run. } - Main::cleanup(); + @autoreleasepool { + Main::cleanup(); + } return os.get_exit_code(); } diff --git a/platform/macos/godot_open_save_delegate.h b/platform/macos/godot_open_save_delegate.h new file mode 100644 index 0000000000..8857ef1fa9 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* godot_open_save_delegate.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 GODOT_OPEN_SAVE_DELEGATE_H +#define GODOT_OPEN_SAVE_DELEGATE_H + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#include "core/templates/hash_map.h" +#include "core/variant/typed_array.h" +#include "core/variant/variant.h" + +@interface GodotOpenSaveDelegate : NSObject <NSOpenSavePanelDelegate> { + NSSavePanel *dialog; + NSMutableArray *allowed_types; + + HashMap<int, String> ctr_ids; + Dictionary options; + int cur_index; + int ctr_id; + + String root; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options; +- (void)setFileTypes:(NSMutableArray *)p_allowed_types; +- (void)popupOptionAction:(id)p_sender; +- (void)popupCheckAction:(id)p_sender; +- (void)popupFileAction:(id)p_sender; +- (int)getIndex; +- (Dictionary)getSelection; +- (int)setDefaultInt:(const String &)p_name value:(int)p_value; +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value; +- (void)setRootPath:(const String &)p_root_path; + +@end + +#endif // GODOT_OPEN_SAVE_DELEGATE_H diff --git a/platform/macos/godot_open_save_delegate.mm b/platform/macos/godot_open_save_delegate.mm new file mode 100644 index 0000000000..306015d644 --- /dev/null +++ b/platform/macos/godot_open_save_delegate.mm @@ -0,0 +1,250 @@ +/**************************************************************************/ +/* godot_open_save_delegate.mm */ +/**************************************************************************/ +/* 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 "godot_open_save_delegate.h" + +@implementation GodotOpenSaveDelegate + +- (instancetype)init { + self = [super init]; + if ((self = [super init])) { + dialog = nullptr; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + root = String(); + } + return self; +} + +- (void)makeAccessoryView:(NSSavePanel *)p_panel filters:(const Vector<String> &)p_filters options:(const TypedArray<Dictionary> &)p_options { + dialog = p_panel; + + NSMutableArray *constraints = [NSMutableArray array]; + + NSView *base_view = [[NSView alloc] initWithFrame:NSZeroRect]; + base_view.translatesAutoresizingMaskIntoConstraints = NO; + + NSGridView *view = [NSGridView gridViewWithNumberOfColumns:2 rows:0]; + view.translatesAutoresizingMaskIntoConstraints = NO; + view.columnSpacing = 10; + view.rowSpacing = 10; + view.rowAlignment = NSGridRowAlignmentLastBaseline; + + int option_count = 0; + + 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> &values = item["values"]; + int default_idx = item["default"]; + + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:name.utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSView *popup = nullptr; + if (values.is_empty()) { + NSButton *popup_check = [NSButton checkboxWithTitle:@"" target:self action:@selector(popupCheckAction:)]; + int tag = [self setDefaultBool:name value:(bool)default_idx]; + popup_check.state = (default_idx) ? NSControlStateValueOn : NSControlStateValueOff; + popup_check.tag = tag; + popup = popup_check; + } else { + NSPopUpButton *popup_list = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + for (int i = 0; i < values.size(); i++) { + [popup_list addItemWithTitle:[NSString stringWithUTF8String:values[i].utf8().get_data()]]; + } + int tag = [self setDefaultInt:name value:default_idx]; + [popup_list selectItemAtIndex:default_idx]; + popup_list.tag = tag; + popup_list.target = self; + popup_list.action = @selector(popupOptionAction:); + popup = popup_list; + } + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + + option_count++; + } + + NSMutableArray *new_allowed_types = [[NSMutableArray alloc] init]; + bool allow_other = false; + { + NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]]; + if (@available(macOS 10.14, *)) { + label.textColor = NSColor.secondaryLabelColor; + } + if (@available(macOS 11.10, *)) { + label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; + } + + NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]; + if (p_filters.is_empty()) { + [popup addItemWithTitle:@"All Files"]; + } + + for (int i = 0; i < p_filters.size(); i++) { + Vector<String> tokens = p_filters[i].split(";"); + if (tokens.size() >= 1) { + String flt = tokens[0].strip_edges(); + int filter_slice_count = flt.get_slice_count(","); + + NSMutableArray *type_filters = [[NSMutableArray alloc] init]; + for (int j = 0; j < filter_slice_count; j++) { + String str = (flt.get_slice(",", j).strip_edges()); + if (str.strip_edges() == "*.*" || str.strip_edges() == "*") { + allow_other = true; + } else if (!str.is_empty()) { + [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]]; + } + } + + if ([type_filters count] > 0) { + NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()]; + [new_allowed_types addObject:type_filters]; + [popup addItemWithTitle:name_str]; + } + } + } + [self setFileTypes:new_allowed_types]; + popup.target = self; + popup.action = @selector(popupFileAction:); + + [view addRowWithViews:[NSArray arrayWithObjects:label, popup, nil]]; + } + + [base_view addSubview:view]; + [constraints addObject:[view.topAnchor constraintEqualToAnchor:base_view.topAnchor constant:10]]; + [constraints addObject:[base_view.bottomAnchor constraintEqualToAnchor:view.bottomAnchor constant:10]]; + [constraints addObject:[base_view.centerXAnchor constraintEqualToAnchor:view.centerXAnchor constant:10]]; + [NSLayoutConstraint activateConstraints:constraints]; + + [p_panel setAllowsOtherFileTypes:allow_other]; + if (option_count > 0 || [new_allowed_types count] > 0) { + [p_panel setAccessoryView:base_view]; + } + if ([new_allowed_types count] > 0) { + [p_panel setAllowedFileTypes:[new_allowed_types objectAtIndex:0]]; + } +} + +- (int)getIndex { + return cur_index; +} + +- (Dictionary)getSelection { + return options; +} + +- (int)setDefaultInt:(const String &)p_name value:(int)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (int)setDefaultBool:(const String &)p_name value:(bool)p_value { + int cid = ctr_id++; + options[p_name] = p_value; + ctr_ids[cid] = p_name; + + return cid; +} + +- (void)setFileTypes:(NSMutableArray *)p_allowed_types { + allowed_types = p_allowed_types; +} + +- (instancetype)initWithDialog:(NSSavePanel *)p_dialog { + if ((self = [super init])) { + dialog = p_dialog; + cur_index = 0; + ctr_id = 1; + allowed_types = nullptr; + } + return self; +} + +- (void)popupCheckAction:(id)p_sender { + NSButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = ([btn state] == NSControlStateValueOn); + } +} + +- (void)popupOptionAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn && ctr_ids.has(btn.tag)) { + options[ctr_ids[btn.tag]] = (int)[btn indexOfSelectedItem]; + } +} + +- (void)popupFileAction:(id)p_sender { + NSPopUpButton *btn = p_sender; + if (btn) { + NSUInteger index = [btn indexOfSelectedItem]; + if (allowed_types && index < [allowed_types count]) { + [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]]; + cur_index = index; + } else { + [dialog setAllowedFileTypes:@[]]; + cur_index = -1; + } + } +} + +- (void)setRootPath:(const String &)p_root_path { + root = p_root_path; +} + +- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError *_Nullable *)outError { + if (root.is_empty()) { + return YES; + } + + NSString *ns_path = url.URLByStandardizingPath.URLByResolvingSymlinksInPath.path; + String path = String::utf8([ns_path UTF8String]).simplify_path(); + if (!path.begins_with(root.simplify_path())) { + return NO; + } + + return YES; +} + +@end diff --git a/platform/macos/vulkan_context_macos.h b/platform/macos/godot_status_item.h index ab019384e2..1827baa9bd 100644 --- a/platform/macos/vulkan_context_macos.h +++ b/platform/macos/godot_status_item.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_macos.h */ +/* godot_status_item.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,24 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef VULKAN_CONTEXT_MACOS_H -#define VULKAN_CONTEXT_MACOS_H +#ifndef GODOT_STATUS_ITEM_H +#define GODOT_STATUS_ITEM_H -#ifdef VULKAN_ENABLED - -#include "drivers/vulkan/vulkan_context.h" +#include "core/input/input_enums.h" +#include "core/variant/callable.h" #import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> -class VulkanContextMacOS : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; - -public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height); +@interface GodotStatusItemView : NSView { + NSImage *image; + Callable cb; +} - VulkanContextMacOS(); - ~VulkanContextMacOS(); -}; +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index; +- (void)setImage:(NSImage *)image; +- (void)setCallback:(const Callable &)callback; -#endif // VULKAN_ENABLED +@end -#endif // VULKAN_CONTEXT_MACOS_H +#endif // GODOT_STATUS_ITEM_H diff --git a/platform/macos/godot_status_item.mm b/platform/macos/godot_status_item.mm new file mode 100644 index 0000000000..71ed0a0f71 --- /dev/null +++ b/platform/macos/godot_status_item.mm @@ -0,0 +1,101 @@ +/**************************************************************************/ +/* godot_status_item.mm */ +/**************************************************************************/ +/* 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 "godot_status_item.h" + +#include "display_server_macos.h" + +@implementation GodotStatusItemView + +- (id)init { + self = [super init]; + image = nullptr; + return self; +} + +- (void)setImage:(NSImage *)newImage { + image = newImage; + [self setNeedsDisplayInRect:self.frame]; +} + +- (void)setCallback:(const Callable &)callback { + cb = callback; +} + +- (void)drawRect:(NSRect)rect { + if (image) { + [image drawInRect:rect]; + } +} + +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index { + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (!ds) { + return; + } + + if (cb.is_valid()) { + Variant v_button = index; + Variant v_pos = ds->mouse_get_position(); + Variant *v_args[2] = { &v_button, &v_pos }; + Variant ret; + Callable::CallError ce; + cb.callp((const Variant **)&v_args, 2, ret, ce); + } +} + +- (void)mouseDown:(NSEvent *)event { + [super mouseDown:event]; + if (([event modifierFlags] & NSEventModifierFlagControl)) { + [self processMouseEvent:event index:MouseButton::RIGHT]; + } else { + [self processMouseEvent:event index:MouseButton::LEFT]; + } +} + +- (void)rightMouseDown:(NSEvent *)event { + [super rightMouseDown:event]; + + [self processMouseEvent:event index:MouseButton::RIGHT]; +} + +- (void)otherMouseDown:(NSEvent *)event { + [super otherMouseDown:event]; + + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2]; + } +} + +@end diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h index 1bda4eb406..f5b0ff8d02 100644 --- a/platform/macos/key_mapping_macos.h +++ b/platform/macos/key_mapping_macos.h @@ -45,6 +45,7 @@ public: static Key translate_key(unsigned int p_key); static unsigned int unmap_key(Key p_key); static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode); + static KeyLocation translate_location(unsigned int p_key); // Mapping for menu shortcuts. static String keycode_get_native_string(Key p_keycode); diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm index db3fa4e02d..b5e72048e7 100644 --- a/platform/macos/key_mapping_macos.mm +++ b/platform/macos/key_mapping_macos.mm @@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys; HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map; HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv; HashMap<Key, char32_t, HashMapHasherKeys> keycode_map; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingMacOS::initialize() { numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal @@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() { keycode_map[Key::BAR] = '|'; keycode_map[Key::BRACERIGHT] = '}'; keycode_map[Key::ASCIITILDE] = '~'; + + // Keysym -> physical location. + // Ctrl. + location_map[0x3b] = KeyLocation::LEFT; + location_map[0x3e] = KeyLocation::RIGHT; + // Shift. + location_map[0x38] = KeyLocation::LEFT; + location_map[0x3c] = KeyLocation::RIGHT; + // Alt/Option. + location_map[0x3a] = KeyLocation::LEFT; + location_map[0x3d] = KeyLocation::RIGHT; + // Meta/Command (yes, right < left). + location_map[0x36] = KeyLocation::RIGHT; + location_map[0x37] = KeyLocation::LEFT; } bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) { @@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_ } } +// Translates a macOS keycode to a Godot key location. +KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) { + const KeyLocation *location = location_map.getptr(p_key); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} + String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) { const char32_t *key = keycode_map.getptr(p_keycode); if (key) { diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 29dff683d5..000215ac46 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -230,6 +230,8 @@ Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handl path = get_framework_executable(get_executable_path().get_base_dir().path_join("../Frameworks").path_join(p_path.get_file())); } + ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); + p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror())); @@ -354,8 +356,11 @@ Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) { Error OS_MacOS::shell_open(String p_uri) { NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()]; NSURL *uri = [[NSURL alloc] initWithString:string]; - // Escape special characters in filenames if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) { + // No scheme set, assume "file://" and escape special characters. + if (!p_uri.begins_with("file://")) { + string = [NSString stringWithUTF8String:("file://" + p_uri).utf8().get_data()]; + } uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; } [[NSWorkspace sharedWorkspace] openURL:uri]; @@ -753,21 +758,25 @@ void OS_MacOS::run() { return; } - main_loop->initialize(); + @autoreleasepool { + main_loop->initialize(); + } bool quit = false; while (!quit) { - @try { - if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); // Get rid of pending events. - } - joypad_macos->process_joypads(); + @autoreleasepool { + @try { + if (DisplayServer::get_singleton()) { + DisplayServer::get_singleton()->process_events(); // Get rid of pending events. + } + joypad_macos->process_joypads(); - if (Main::iteration()) { - quit = true; + if (Main::iteration()) { + quit = true; + } + } @catch (NSException *exception) { + ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); } - } @catch (NSException *exception) { - ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String)); } } diff --git a/platform/macos/rendering_context_driver_vulkan_macos.h b/platform/macos/rendering_context_driver_vulkan_macos.h new file mode 100644 index 0000000000..bbc67581db --- /dev/null +++ b/platform/macos/rendering_context_driver_vulkan_macos.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* rendering_context_driver_vulkan_macos.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 RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H + +#ifdef VULKAN_ENABLED + +#include "drivers/vulkan/rendering_context_driver_vulkan.h" + +#import <AppKit/AppKit.h> + +class RenderingContextDriverVulkanMacOS : public RenderingContextDriverVulkan { +private: + virtual const char *_get_platform_surface_extension() const override final; + +protected: + SurfaceID surface_create(const void *p_platform_data) override final; + +public: + struct WindowPlatformData { + const id *view_ptr; + }; + + RenderingContextDriverVulkanMacOS(); + ~RenderingContextDriverVulkanMacOS(); +}; + +#endif // VULKAN_ENABLED + +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_MACOS_H diff --git a/platform/macos/vulkan_context_macos.mm b/platform/macos/rendering_context_driver_vulkan_macos.mm index 46a2d9f86a..e0f8bf9e67 100644 --- a/platform/macos/vulkan_context_macos.mm +++ b/platform/macos/rendering_context_driver_vulkan_macos.mm @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_macos.mm */ +/* rendering_context_driver_vulkan_macos.mm */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "vulkan_context_macos.h" +#include "rendering_context_driver_vulkan_macos.h" #ifdef VULKAN_ENABLED @@ -38,27 +38,32 @@ #include <vulkan/vulkan.h> #endif -const char *VulkanContextMacOS::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanMacOS::_get_platform_surface_extension() const { return VK_MVK_MACOS_SURFACE_EXTENSION_NAME; } -Error VulkanContextMacOS::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, id p_window, int p_width, int p_height) { - VkMacOSSurfaceCreateInfoMVK createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.pView = (__bridge const void *)p_window; +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanMacOS::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); - VkSurfaceKHR surface; - VkResult err = vkCreateMacOSSurfaceMVK(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); + VkMacOSSurfaceCreateInfoMVK create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; + create_info.pView = (__bridge const void *)(*wpd->view_ptr); + + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateMacOSSurfaceMVK(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); } -VulkanContextMacOS::VulkanContextMacOS() { +RenderingContextDriverVulkanMacOS::RenderingContextDriverVulkanMacOS() { + // Does nothing. } -VulkanContextMacOS::~VulkanContextMacOS() { +RenderingContextDriverVulkanMacOS::~RenderingContextDriverVulkanMacOS() { + // Does nothing. } #endif // VULKAN_ENABLED diff --git a/platform/web/.eslintrc.html.js b/platform/web/.eslintrc.html.js index 5cb8de360a..8c9a3d83da 100644 --- a/platform/web/.eslintrc.html.js +++ b/platform/web/.eslintrc.html.js @@ -15,5 +15,7 @@ module.exports = { "Godot": true, "Engine": true, "$GODOT_CONFIG": true, + "$GODOT_THREADS_ENABLED": true, + "___GODOT_THREADS_ENABLED___": true, }, }; diff --git a/platform/web/.eslintrc.sw.js b/platform/web/.eslintrc.sw.js new file mode 100644 index 0000000000..cba9ed8001 --- /dev/null +++ b/platform/web/.eslintrc.sw.js @@ -0,0 +1,14 @@ +module.exports = { + "extends": [ + "./.eslintrc.js", + ], + "rules": { + "no-restricted-globals": 0, + }, + "globals": { + "onClientMessage": true, + "___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___": true, + "___GODOT_CACHE___": true, + "___GODOT_OPT_CACHE___": true, + }, +}; diff --git a/platform/web/SCsub b/platform/web/SCsub index 1af0642554..3e0cc9ac4a 100644 --- a/platform/web/SCsub +++ b/platform/web/SCsub @@ -13,7 +13,7 @@ if "serve" in COMMAND_LINE_TARGETS or "run" in COMMAND_LINE_TARGETS: except Exception: print("GODOT_WEB_TEST_PORT must be a valid integer") sys.exit(255) - serve(env.Dir("#bin/.web_zip").abspath, port, "run" in COMMAND_LINE_TARGETS) + serve(env.Dir(env.GetTemplateZipPath()).abspath, port, "run" in COMMAND_LINE_TARGETS) sys.exit(0) web_files = [ @@ -95,7 +95,7 @@ engine = [ "js/engine/engine.js", ] externs = [env.File("#platform/web/js/engine/engine.externs.js")] -js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs) +js_engine = env.CreateEngineFile("#bin/godot${PROGSUFFIX}.engine.js", engine, externs, env["threads"]) env.Depends(js_engine, externs) wrap_list = [ diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp index 1298d28ebf..ec3c22bf7c 100644 --- a/platform/web/audio_driver_web.cpp +++ b/platform/web/audio_driver_web.cpp @@ -30,6 +30,8 @@ #include "audio_driver_web.h" +#include "godot_audio.h" + #include "core/config/project_settings.h" #include <emscripten.h> @@ -184,6 +186,8 @@ Error AudioDriverWeb::input_stop() { return OK; } +#ifdef THREADS_ENABLED + /// AudioWorkletNode implementation (threads) void AudioDriverWorklet::_audio_thread_func(void *p_data) { AudioDriverWorklet *driver = static_cast<AudioDriverWorklet *>(p_data); @@ -245,3 +249,51 @@ void AudioDriverWorklet::finish_driver() { quit = true; // Ask thread to quit. thread.wait_to_finish(); } + +#else // No threads. + +/// AudioWorkletNode implementation (no threads) +AudioDriverWorklet *AudioDriverWorklet::singleton = nullptr; + +Error AudioDriverWorklet::create(int &p_buffer_size, int p_channels) { + if (!godot_audio_has_worklet()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_worklet_create(p_channels); +} + +void AudioDriverWorklet::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + _audio_driver_process(); + godot_audio_worklet_start_no_threads(p_out_buf, p_out_buf_size, &_process_callback, p_in_buf, p_in_buf_size, &_capture_callback); +} + +void AudioDriverWorklet::_process_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton(); + driver->_audio_driver_process(p_pos, p_samples); +} + +void AudioDriverWorklet::_capture_callback(int p_pos, int p_samples) { + AudioDriverWorklet *driver = AudioDriverWorklet::get_singleton(); + driver->_audio_driver_capture(p_pos, p_samples); +} + +/// ScriptProcessorNode implementation +AudioDriverScriptProcessor *AudioDriverScriptProcessor::singleton = nullptr; + +void AudioDriverScriptProcessor::_process_callback() { + AudioDriverScriptProcessor::get_singleton()->_audio_driver_capture(); + AudioDriverScriptProcessor::get_singleton()->_audio_driver_process(); +} + +Error AudioDriverScriptProcessor::create(int &p_buffer_samples, int p_channels) { + if (!godot_audio_has_script_processor()) { + return ERR_UNAVAILABLE; + } + return (Error)godot_audio_script_create(&p_buffer_samples, p_channels); +} + +void AudioDriverScriptProcessor::start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) { + godot_audio_script_start(p_in_buf, p_in_buf_size, p_out_buf, p_out_buf_size, &_process_callback); +} + +#endif // THREADS_ENABLED diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h index 12a61746c3..df88d0a94c 100644 --- a/platform/web/audio_driver_web.h +++ b/platform/web/audio_driver_web.h @@ -90,6 +90,7 @@ public: AudioDriverWeb() {} }; +#ifdef THREADS_ENABLED class AudioDriverWorklet : public AudioDriverWeb { private: enum { @@ -120,4 +121,54 @@ public: virtual void unlock() override; }; +#else + +class AudioDriverWorklet : public AudioDriverWeb { +private: + static void _process_callback(int p_pos, int p_samples); + static void _capture_callback(int p_pos, int p_samples); + + static AudioDriverWorklet *singleton; + +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + +public: + virtual const char *get_name() const override { + return "AudioWorklet"; + } + + virtual void lock() override {} + virtual void unlock() override {} + + static AudioDriverWorklet *get_singleton() { return singleton; } + + AudioDriverWorklet() { singleton = this; } +}; + +class AudioDriverScriptProcessor : public AudioDriverWeb { +private: + static void _process_callback(); + + static AudioDriverScriptProcessor *singleton; + +protected: + virtual Error create(int &p_buffer_size, int p_output_channels) override; + virtual void start(float *p_out_buf, int p_out_buf_size, float *p_in_buf, int p_in_buf_size) override; + virtual void finish_driver() override; + +public: + virtual const char *get_name() const override { return "ScriptProcessor"; } + + virtual void lock() override {} + virtual void unlock() override {} + + static AudioDriverScriptProcessor *get_singleton() { return singleton; } + + AudioDriverScriptProcessor() { singleton = this; } +}; + +#endif // THREADS_ENABLED + #endif // AUDIO_DRIVER_WEB_H diff --git a/platform/web/detect.py b/platform/web/detect.py index 579eaaff03..bbe1634dfa 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -8,6 +8,7 @@ from emscripten_helpers import ( add_js_pre, add_js_externs, create_template_zip, + get_template_zip_path, ) from methods import get_compiler_version from SCons.Util import WhereIs @@ -30,6 +31,9 @@ def get_opts(): return [ ("initial_memory", "Initial WASM memory (in MiB)", 32), + # Matches default values from before Emscripten 3.1.27. New defaults are too low for Godot. + ("stack_size", "WASM stack size (in KiB)", 5120), + ("default_pthread_stack_size", "WASM pthread default stack size (in KiB)", 2048), BoolVariable("use_assertions", "Use Emscripten runtime assertions", False), BoolVariable("use_ubsan", "Use Emscripten undefined behavior sanitizer (UBSAN)", False), BoolVariable("use_asan", "Use Emscripten address sanitizer (ASAN)", False), @@ -158,11 +162,14 @@ def configure(env: "Environment"): # Add method that joins/compiles our Engine files. env.AddMethod(create_engine_file, "CreateEngineFile") + # Add method for getting the final zip path + env.AddMethod(get_template_zip_path, "GetTemplateZipPath") + # Add method for creating the final zip file env.AddMethod(create_template_zip, "CreateTemplateZip") # Closure compiler extern and support for ecmascript specs (const, let, etc). - env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2020" + env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2021" env["CC"] = "emcc" env["CXX"] = "em++" @@ -193,37 +200,47 @@ def configure(env: "Environment"): env.Prepend(CPPPATH=["#platform/web"]) env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) - if cc_semver >= (3, 1, 25): - env.Append(LINKFLAGS=["-s", "STACK_SIZE=5MB"]) - else: - env.Append(LINKFLAGS=["-s", "TOTAL_STACK=5MB"]) - if env["opengl3"]: env.AppendUnique(CPPDEFINES=["GLES3_ENABLED"]) # This setting just makes WebGL 2 APIs available, it does NOT disable WebGL 1. env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"]) # Allow use to take control of swapping WebGL buffers. env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"]) + # Breaking change since emscripten 3.1.51 + # https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md#3151---121323 + if cc_semver >= (3, 1, 51): + # Enables the use of *glGetProcAddress() + env.Append(LINKFLAGS=["-s", "GL_ENABLE_GET_PROC_ADDRESS=1"]) if env["javascript_eval"]: env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) - # Thread support (via SharedArrayBuffer). - env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) - env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=2MB"]) - env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) - env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) + stack_size_opt = "STACK_SIZE" if cc_semver >= (3, 1, 25) else "TOTAL_STACK" + env.Append(LINKFLAGS=["-s", "%s=%sKB" % (stack_size_opt, env["stack_size"])]) + + if env["threads"]: + # Thread support (via SharedArrayBuffer). + env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) + env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "DEFAULT_PTHREAD_STACK_SIZE=%sKB" % env["default_pthread_stack_size"]]) + env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) + env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) + elif env["proxy_to_pthread"]: + print('"threads=no" support requires "proxy_to_pthread=no", disabling proxy to pthread.') + env["proxy_to_pthread"] = False if env["lto"] != "none": # Workaround https://github.com/emscripten-core/emscripten/issues/19781. if cc_semver >= (3, 1, 42) and cc_semver < (3, 1, 46): env.Append(LINKFLAGS=["-Wl,-u,scalbnf"]) + # Workaround https://github.com/emscripten-core/emscripten/issues/16836. + if cc_semver >= (3, 1, 47): + env.Append(LINKFLAGS=["-Wl,-u,_emscripten_run_callback_on_thread"]) if env["dlink_enabled"]: if env["proxy_to_pthread"]: - print("GDExtension support requires proxy_to_pthread=no, disabling") + print("GDExtension support requires proxy_to_pthread=no, disabling proxy to pthread.") env["proxy_to_pthread"] = False if cc_semver < (3, 1, 14): diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index b4a190d47e..bc4c0d22f0 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false); Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true); + KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data()); DisplayServerWeb::KeyEvent ke; @@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin ke.physical_keycode = scancode; ke.key_label = fix_key_label(c, keycode); ke.unicode = fix_unicode(c); + ke.location = location; ke.mod = p_modifiers; if (ds->key_event_pos >= ds->key_event_buffer.size()) { @@ -326,7 +328,9 @@ void DisplayServerWeb::_mouse_move_callback(double p_x, double p_y, double p_rel ev->set_global_position(pos); ev->set_relative(Vector2(p_rel_x, p_rel_y)); + ev->set_relative_screen_position(ev->get_relative()); ev->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + ev->set_screen_velocity(ev->get_velocity()); Input::get_singleton()->parse_input_event(ev); } @@ -705,6 +709,7 @@ void DisplayServerWeb::_touch_callback(int p_type, int p_count) { Point2 &prev = ds->touches[i]; ev->set_relative(ev->get_position() - prev); + ev->set_relative_screen_position(ev->get_relative()); prev = ev->get_position(); Input::get_singleton()->parse_input_event(ev); @@ -1383,6 +1388,7 @@ void DisplayServerWeb::process_events() { ev->set_physical_keycode(ke.physical_keycode); ev->set_key_label(ke.key_label); ev->set_unicode(ke.unicode); + ev->set_location(ke.location); if (ke.raw) { dom2godot_mod(ev, ke.mod, ke.keycode); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 140aef952b..682d10704f 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -95,6 +95,7 @@ private: Key physical_keycode = Key::NONE; Key key_label = Key::NONE; uint32_t unicode = 0; + KeyLocation location = KeyLocation::UNSPECIFIED; int mod = 0; }; diff --git a/platform/web/doc_classes/EditorExportPlatformWeb.xml b/platform/web/doc_classes/EditorExportPlatformWeb.xml index c4c4fd870b..75125e2708 100644 --- a/platform/web/doc_classes/EditorExportPlatformWeb.xml +++ b/platform/web/doc_classes/EditorExportPlatformWeb.xml @@ -4,6 +4,8 @@ Exporter for the Web. </brief_description> <description> + The Web exporter customizes how a web build is handled. In the editor's "Export" window, it is created when adding a new "Web" preset. + [b]Note:[/b] Godot on Web is rendered inside a [code]<canvas>[/code] tag. Normally, the canvas cannot be positioned or resized manually, but otherwise acts as the main [Window] of the application. </description> <tutorials> <link title="Exporting for the Web">$DOCS_URL/tutorials/export/exporting_for_web.html</link> @@ -11,45 +13,85 @@ </tutorials> <members> <member name="custom_template/debug" type="String" setter="" getter=""> - Path to the custom export template. If left empty, default template is used. + File path to the custom export template used for debug builds. If left empty, the default template is used. </member> <member name="custom_template/release" type="String" setter="" getter=""> - Path to the custom export template. If left empty, default template is used. + File path to the custom export template used for release builds. If left empty, the default template is used. </member> <member name="html/canvas_resize_policy" type="int" setter="" getter=""> - The canvas resize policy determines how the canvas should be resized by Godot. + Determines how the canvas should be resized by Godot. + - [b]None:[/b] The canvas is not automatically resized. + - [b]Project:[/b] The size of the canvas is dependent on the [ProjectSettings]. + - [b]Adaptive:[/b] The canvas is automatically resized to fit as much of the web page as possible. </member> <member name="html/custom_html_shell" type="String" setter="" getter=""> + The custom HTML page that wraps the exported web build. If left empty, the default HTML shell is used. + For more information, see the [url=$DOCS_URL/tutorials/platform/web/customizing_html5_shell.html]Customizing HTML5 Shell[/url] tutorial. </member> <member name="html/experimental_virtual_keyboard" type="bool" setter="" getter=""> + If [code]true[/code], embeds support for a virtual keyboard into the web page, which is shown when necessary on touchscreen devices. + [b]Warning:[/b] This feature is experimental and may be changed in a future release. </member> <member name="html/export_icon" type="bool" setter="" getter=""> + If [code]true[/code], the project icon will be used as the favicon for this application's web page. </member> <member name="html/focus_canvas_on_start" type="bool" setter="" getter=""> + If [code]true[/code], the canvas will be focused as soon as the application is loaded, if the browser window is already in focus. </member> <member name="html/head_include" type="String" setter="" getter=""> + Additional HTML tags to include inside the [code]<head>[/code], such as [code]<meta>[/code] tags. + [b]Note:[/b] You do not need to add a [code]<title>[/code] tag, as it is automatically included based on the project's name. </member> <member name="progressive_web_app/background_color" type="Color" setter="" getter=""> + The background color used behind the web application. </member> <member name="progressive_web_app/display" type="int" setter="" getter=""> + The [url=https://developer.mozilla.org/en-US/docs/Web/Manifest/display/]display mode[/url] to use for this progressive web application. Different browsers and platforms may not behave the same. + - [b]Fullscreen:[/b] Displays the app in fullscreen and hides all of the browser's UI elements. + - [b]Standalone:[/b] Displays the app in a separate window and hides all of the browser's UI elements. + - [b]Minimal UI:[/b] Displays the app in a separate window and only shows the browser's UI elements for navigation. + - [b]Browser:[/b] Displays the app as a normal web page. </member> <member name="progressive_web_app/enabled" type="bool" setter="" getter=""> + If [code]true[/code], turns this web build into a [url=https://en.wikipedia.org/wiki/Progressive_web_app]progressive web application[/url] (PWA). + </member> + <member name="progressive_web_app/ensure_cross_origin_isolation_headers" type="bool" setter="" getter=""> + When enabled, the progressive web app will make sure that each request has cross-origin isolation headers (COEP/COOP). + This can simplify the setup to serve the exported game. </member> <member name="progressive_web_app/icon_144x144" type="String" setter="" getter=""> + File path to the smallest icon for this web application. If not defined, defaults to the project icon. + [b]Note:[/b] If the icon is not 144x144, it will be automatically resized for the final build. </member> <member name="progressive_web_app/icon_180x180" type="String" setter="" getter=""> + File path to the small icon for this web application. If not defined, defaults to the project icon. + [b]Note:[/b] If the icon is not 180x180, it will be automatically resized for the final build. </member> <member name="progressive_web_app/icon_512x512" type="String" setter="" getter=""> + File path to the smallest icon for this web application. If not defined, defaults to the project icon. + [b]Note:[/b] If the icon is not 512x512, it will be automatically resized for the final build. </member> <member name="progressive_web_app/offline_page" type="String" setter="" getter=""> + The page to display, should the server hosting the page not be available. This page is saved in the client's machine. </member> <member name="progressive_web_app/orientation" type="int" setter="" getter=""> + The orientation to use when the web application is run through a mobile device. + - [b]Any:[/b] No orientation is forced. + - [b]Landscape:[/b] Forces a horizontal layout (wider than it is taller). + - [b]Portrait:[/b] Forces a vertical layout (taller than it is wider). </member> <member name="variant/extensions_support" type="bool" setter="" getter=""> + If [code]true[/code] enables [GDExtension] support for this web build. + </member> + <member name="variant/thread_support" type="bool" setter="" getter=""> + If enabled, the exported game will support threads. It requires [url=https://web.dev/articles/coop-coep]a "cross-origin isolated" website[/url], which can be difficult to setup and brings some limitations (e.g. not being able to communicate with third-party websites). + If disabled, the exported game will not support threads. As a result, it is more prone to performance and audio issues, but will only require to be run on a HTTPS website. </member> <member name="vram_texture_compression/for_desktop" type="bool" setter="" getter=""> + If [code]true[/code], allows textures to be optimized for desktop through the S3TC algorithm. </member> <member name="vram_texture_compression/for_mobile" type="bool" setter="" getter=""> + If [code]true[/code] allows textures to be optimized for mobile through the ETC2 algorithm. </member> </members> </class> diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc index cd94b779c0..b20a3a46b9 100644 --- a/platform/web/dom_keys.inc +++ b/platform/web/dom_keys.inc @@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b return Key::NONE; #undef DOM2GODOT } + +KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) { +#define DOM2GODOT(m_str, m_godot_code) \ + if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \ + return KeyLocation::m_godot_code; \ + } + + DOM2GODOT("AltLeft", LEFT); + DOM2GODOT("AltRight", RIGHT); + DOM2GODOT("ControlLeft", LEFT); + DOM2GODOT("ControlRight", RIGHT); + DOM2GODOT("MetaLeft", LEFT); + DOM2GODOT("MetaRight", RIGHT); + DOM2GODOT("OSLeft", LEFT); + DOM2GODOT("OSRight", RIGHT); + DOM2GODOT("ShiftLeft", LEFT); + DOM2GODOT("ShiftRight", RIGHT); + + return KeyLocation::UNSPECIFIED; +#undef DOM2GODOT +} diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py index ec33397842..3ba133c9a1 100644 --- a/platform/web/emscripten_helpers.py +++ b/platform/web/emscripten_helpers.py @@ -4,7 +4,12 @@ from SCons.Util import WhereIs def run_closure_compiler(target, source, env, for_signature): - closure_bin = os.path.join(os.path.dirname(WhereIs("emcc")), "node_modules", ".bin", "google-closure-compiler") + closure_bin = os.path.join( + os.path.dirname(WhereIs("emcc")), + "node_modules", + ".bin", + "google-closure-compiler", + ) cmd = [WhereIs("node"), closure_bin] cmd.extend(["--compilation_level", "ADVANCED_OPTIMIZATIONS"]) for f in env["JSEXTERNS"]: @@ -31,27 +36,29 @@ def get_build_version(): return v -def create_engine_file(env, target, source, externs): +def create_engine_file(env, target, source, externs, threads_enabled): if env["use_closure_compiler"]: return env.BuildJS(target, source, JSEXTERNS=externs) - return env.Textfile(target, [env.File(s) for s in source]) + subst_dict = {"___GODOT_THREADS_ENABLED": "true" if threads_enabled else "false"} + return env.Substfile(target=target, source=[env.File(s) for s in source], SUBST_DICT=subst_dict) def create_template_zip(env, js, wasm, worker, side): binary_name = "godot.editor" if env.editor_build else "godot" - zip_dir = env.Dir("#bin/.web_zip") + zip_dir = env.Dir(env.GetTemplateZipPath()) in_files = [ js, wasm, - worker, "#platform/web/js/libs/audio.worklet.js", ] out_files = [ zip_dir.File(binary_name + ".js"), zip_dir.File(binary_name + ".wasm"), - zip_dir.File(binary_name + ".worker.js"), zip_dir.File(binary_name + ".audio.worklet.js"), ] + if env["threads"]: + in_files.append(worker) + out_files.append(zip_dir.File(binary_name + ".worker.js")) # Dynamic linking (extensions) specific. if env["dlink_enabled"]: in_files.append(side) # Side wasm (contains the actual Godot code). @@ -65,18 +72,20 @@ def create_template_zip(env, js, wasm, worker, side): "godot.editor.html", "offline.html", "godot.editor.js", - "godot.editor.worker.js", "godot.editor.audio.worklet.js", "logo.svg", "favicon.png", ] + if env["threads"]: + cache.append("godot.editor.worker.js") opt_cache = ["godot.editor.wasm"] subst_dict = { - "@GODOT_VERSION@": get_build_version(), - "@GODOT_NAME@": "GodotEngine", - "@GODOT_CACHE@": json.dumps(cache), - "@GODOT_OPT_CACHE@": json.dumps(opt_cache), - "@GODOT_OFFLINE_PAGE@": "offline.html", + "___GODOT_VERSION___": get_build_version(), + "___GODOT_NAME___": "GodotEngine", + "___GODOT_CACHE___": json.dumps(cache), + "___GODOT_OPT_CACHE___": json.dumps(opt_cache), + "___GODOT_OFFLINE_PAGE___": "offline.html", + "___GODOT_THREADS_ENABLED___": "true" if env["threads"] else "false", } html = env.Substfile(target="#bin/godot${PROGSUFFIX}.html", source=html, SUBST_DICT=subst_dict) in_files.append(html) @@ -88,7 +97,9 @@ def create_template_zip(env, js, wasm, worker, side): out_files.append(zip_dir.File("favicon.png")) # PWA service_worker = env.Substfile( - target="#bin/godot${PROGSUFFIX}.service.worker.js", source=service_worker, SUBST_DICT=subst_dict + target="#bin/godot${PROGSUFFIX}.service.worker.js", + source=service_worker, + SUBST_DICT=subst_dict, ) in_files.append(service_worker) out_files.append(zip_dir.File("service.worker.js")) @@ -115,6 +126,10 @@ def create_template_zip(env, js, wasm, worker, side): ) +def get_template_zip_path(env): + return "#bin/.web_zip" + + def add_js_libraries(env, libraries): env.Append(JS_LIBS=env.File(libraries)) diff --git a/platform/web/export/editor_http_server.cpp b/platform/web/export/editor_http_server.cpp new file mode 100644 index 0000000000..9cf862eb1e --- /dev/null +++ b/platform/web/export/editor_http_server.cpp @@ -0,0 +1,255 @@ +/**************************************************************************/ +/* editor_http_server.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 "editor_http_server.h" + +void EditorHTTPServer::_server_thread_poll(void *data) { + EditorHTTPServer *web_server = static_cast<EditorHTTPServer *>(data); + while (!web_server->server_quit.is_set()) { + OS::get_singleton()->delay_usec(6900); + { + MutexLock lock(web_server->server_lock); + web_server->_poll(); + } + } +} + +void EditorHTTPServer::_clear_client() { + peer = Ref<StreamPeer>(); + tls = Ref<StreamPeerTLS>(); + tcp = Ref<StreamPeerTCP>(); + memset(req_buf, 0, sizeof(req_buf)); + time = 0; + req_pos = 0; +} + +void EditorHTTPServer::_set_internal_certs(Ref<Crypto> p_crypto) { + const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); + const String key_path = cache_path.path_join("html5_server.key"); + const String crt_path = cache_path.path_join("html5_server.crt"); + bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); + if (!regen) { + key = Ref<CryptoKey>(CryptoKey::create()); + cert = Ref<X509Certificate>(X509Certificate::create()); + if (key->load(key_path) != OK || cert->load(crt_path) != OK) { + regen = true; + } + } + if (regen) { + key = p_crypto->generate_rsa(2048); + key->save(key_path); + cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000"); + cert->save(crt_path); + } +} + +void EditorHTTPServer::_send_response() { + Vector<String> psa = String((char *)req_buf).split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); + + Vector<String> req = psa[0].split(" ", false); + ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); + + const int query_index = req[1].find_char('?'); + const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index); + + const String req_file = path.get_file(); + const String req_ext = path.get_extension(); + const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); + const String filepath = cache_path.path_join(req_file); + + if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { + String s = "HTTP/1.1 404 Not Found\r\n"; + s += "Connection: Close\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + return; + } + const String ctype = mimes[req_ext]; + + Ref<FileAccess> f = FileAccess::open(filepath, FileAccess::READ); + ERR_FAIL_COND(f.is_null()); + String s = "HTTP/1.1 200 OK\r\n"; + s += "Connection: Close\r\n"; + s += "Content-Type: " + ctype + "\r\n"; + s += "Access-Control-Allow-Origin: *\r\n"; + s += "Cross-Origin-Opener-Policy: same-origin\r\n"; + s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; + s += "Cache-Control: no-store, max-age=0\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + if (err != OK) { + ERR_FAIL(); + } + + while (true) { + uint8_t bytes[4096]; + uint64_t read = f->get_buffer(bytes, 4096); + if (read == 0) { + break; + } + err = peer->put_data(bytes, read); + if (err != OK) { + ERR_FAIL(); + } + } +} + +void EditorHTTPServer::_poll() { + if (!server->is_listening()) { + return; + } + if (tcp.is_null()) { + if (!server->is_connection_available()) { + return; + } + tcp = server->take_connection(); + peer = tcp; + time = OS::get_singleton()->get_ticks_usec(); + } + if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { + _clear_client(); + return; + } + if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { + return; + } + + if (use_tls) { + if (tls.is_null()) { + tls = Ref<StreamPeerTLS>(StreamPeerTLS::create()); + peer = tls; + if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) { + _clear_client(); + return; + } + } + tls->poll(); + if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { + // Still handshaking, keep waiting. + return; + } + if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { + _clear_client(); + return; + } + } + + while (true) { + char *r = (char *)req_buf; + int l = req_pos - 1; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + _send_response(); + _clear_client(); + return; + } + + int read = 0; + ERR_FAIL_COND(req_pos >= 4096); + Error err = peer->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) { + // Got an error + _clear_client(); + return; + } else if (read != 1) { + // Busy, wait next poll + return; + } + req_pos += read; + } +} + +void EditorHTTPServer::stop() { + server_quit.set(); + if (server_thread.is_started()) { + server_thread.wait_to_finish(); + } + if (server.is_valid()) { + server->stop(); + } + _clear_client(); +} + +Error EditorHTTPServer::listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { + MutexLock lock(server_lock); + if (server->is_listening()) { + return ERR_ALREADY_IN_USE; + } + use_tls = p_use_tls; + if (use_tls) { + Ref<Crypto> crypto = Crypto::create(); + if (crypto.is_null()) { + return ERR_UNAVAILABLE; + } + if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { + key = Ref<CryptoKey>(CryptoKey::create()); + Error err = key->load(p_tls_key); + ERR_FAIL_COND_V(err != OK, err); + cert = Ref<X509Certificate>(X509Certificate::create()); + err = cert->load(p_tls_cert); + ERR_FAIL_COND_V(err != OK, err); + } else { + _set_internal_certs(crypto); + } + } + Error err = server->listen(p_port, p_address); + if (err == OK) { + server_quit.clear(); + server_thread.start(_server_thread_poll, this); + } + return err; +} + +bool EditorHTTPServer::is_listening() const { + MutexLock lock(server_lock); + return server->is_listening(); +} + +EditorHTTPServer::EditorHTTPServer() { + mimes["html"] = "text/html"; + mimes["js"] = "application/javascript"; + mimes["json"] = "application/json"; + mimes["pck"] = "application/octet-stream"; + mimes["png"] = "image/png"; + mimes["svg"] = "image/svg"; + mimes["wasm"] = "application/wasm"; + server.instantiate(); + stop(); +} + +EditorHTTPServer::~EditorHTTPServer() { + stop(); +} diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h index 3f87288537..4437492b5a 100644 --- a/platform/web/export/editor_http_server.h +++ b/platform/web/export/editor_http_server.h @@ -51,199 +51,24 @@ private: uint8_t req_buf[4096]; int req_pos = 0; - void _clear_client() { - peer = Ref<StreamPeer>(); - tls = Ref<StreamPeerTLS>(); - tcp = Ref<StreamPeerTCP>(); - memset(req_buf, 0, sizeof(req_buf)); - time = 0; - req_pos = 0; - } + SafeFlag server_quit; + Mutex server_lock; + Thread server_thread; - void _set_internal_certs(Ref<Crypto> p_crypto) { - const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); - const String key_path = cache_path.path_join("html5_server.key"); - const String crt_path = cache_path.path_join("html5_server.crt"); - bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); - if (!regen) { - key = Ref<CryptoKey>(CryptoKey::create()); - cert = Ref<X509Certificate>(X509Certificate::create()); - if (key->load(key_path) != OK || cert->load(crt_path) != OK) { - regen = true; - } - } - if (regen) { - key = p_crypto->generate_rsa(2048); - key->save(key_path); - cert = p_crypto->generate_self_signed_certificate(key, "CN=godot-debug.local,O=A Game Dev,C=XXA", "20140101000000", "20340101000000"); - cert->save(crt_path); - } - } + void _clear_client(); + void _set_internal_certs(Ref<Crypto> p_crypto); + void _send_response(); + void _poll(); -public: - EditorHTTPServer() { - mimes["html"] = "text/html"; - mimes["js"] = "application/javascript"; - mimes["json"] = "application/json"; - mimes["pck"] = "application/octet-stream"; - mimes["png"] = "image/png"; - mimes["svg"] = "image/svg"; - mimes["wasm"] = "application/wasm"; - server.instantiate(); - stop(); - } - - void stop() { - server->stop(); - _clear_client(); - } - - Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert) { - use_tls = p_use_tls; - if (use_tls) { - Ref<Crypto> crypto = Crypto::create(); - if (crypto.is_null()) { - return ERR_UNAVAILABLE; - } - if (!p_tls_key.is_empty() && !p_tls_cert.is_empty()) { - key = Ref<CryptoKey>(CryptoKey::create()); - Error err = key->load(p_tls_key); - ERR_FAIL_COND_V(err != OK, err); - cert = Ref<X509Certificate>(X509Certificate::create()); - err = cert->load(p_tls_cert); - ERR_FAIL_COND_V(err != OK, err); - } else { - _set_internal_certs(crypto); - } - } - return server->listen(p_port, p_address); - } - - bool is_listening() const { - return server->is_listening(); - } - - void _send_response() { - Vector<String> psa = String((char *)req_buf).split("\r\n"); - int len = psa.size(); - ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); - - Vector<String> req = psa[0].split(" ", false); - ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code."); - - // Wrong protocol - ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - - const int query_index = req[1].find_char('?'); - const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index); + static void _server_thread_poll(void *data); - const String req_file = path.get_file(); - const String req_ext = path.get_extension(); - const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); - const String filepath = cache_path.path_join(req_file); - - if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { - String s = "HTTP/1.1 404 Not Found\r\n"; - s += "Connection: Close\r\n"; - s += "\r\n"; - CharString cs = s.utf8(); - peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); - return; - } - const String ctype = mimes[req_ext]; - - Ref<FileAccess> f = FileAccess::open(filepath, FileAccess::READ); - ERR_FAIL_COND(f.is_null()); - String s = "HTTP/1.1 200 OK\r\n"; - s += "Connection: Close\r\n"; - s += "Content-Type: " + ctype + "\r\n"; - s += "Access-Control-Allow-Origin: *\r\n"; - s += "Cross-Origin-Opener-Policy: same-origin\r\n"; - s += "Cross-Origin-Embedder-Policy: require-corp\r\n"; - s += "Cache-Control: no-store, max-age=0\r\n"; - s += "\r\n"; - CharString cs = s.utf8(); - Error err = peer->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); - if (err != OK) { - ERR_FAIL(); - } - - while (true) { - uint8_t bytes[4096]; - uint64_t read = f->get_buffer(bytes, 4096); - if (read == 0) { - break; - } - err = peer->put_data(bytes, read); - if (err != OK) { - ERR_FAIL(); - } - } - } - - void poll() { - if (!server->is_listening()) { - return; - } - if (tcp.is_null()) { - if (!server->is_connection_available()) { - return; - } - tcp = server->take_connection(); - peer = tcp; - time = OS::get_singleton()->get_ticks_usec(); - } - if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { - _clear_client(); - return; - } - if (tcp->get_status() != StreamPeerTCP::STATUS_CONNECTED) { - return; - } - - if (use_tls) { - if (tls.is_null()) { - tls = Ref<StreamPeerTLS>(StreamPeerTLS::create()); - peer = tls; - if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) { - _clear_client(); - return; - } - } - tls->poll(); - if (tls->get_status() == StreamPeerTLS::STATUS_HANDSHAKING) { - // Still handshaking, keep waiting. - return; - } - if (tls->get_status() != StreamPeerTLS::STATUS_CONNECTED) { - _clear_client(); - return; - } - } - - while (true) { - char *r = (char *)req_buf; - int l = req_pos - 1; - if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { - _send_response(); - _clear_client(); - return; - } +public: + EditorHTTPServer(); + ~EditorHTTPServer(); - int read = 0; - ERR_FAIL_COND(req_pos >= 4096); - Error err = peer->get_partial_data(&req_buf[req_pos], 1, read); - if (err != OK) { - // Got an error - _clear_client(); - return; - } else if (read != 1) { - // Busy, wait next poll - return; - } - req_pos += read; - } - } + void stop(); + Error listen(int p_port, IPAddress p_address, bool p_use_tls, String p_tls_key, String p_tls_cert); + bool is_listening() const; }; #endif // WEB_EDITOR_HTTP_SERVER_H diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index a70812cf5b..41c969b5f4 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -34,11 +34,11 @@ #include "run_icon_svg.gen.h" #include "core/config/project_settings.h" -#include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/export/editor_export.h" #include "editor/import/resource_importer_texture_settings.h" +#include "editor/themes/editor_scale.h" #include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For mono and svg. @@ -112,7 +112,7 @@ Error EditorExportPlatformWeb::_write_or_error(const uint8_t *p_content, int p_s return OK; } -void EditorExportPlatformWeb::_replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template) { +void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template) { String str_template = String::utf8(reinterpret_cast<const char *>(r_template.ptr()), r_template.size()); String out; Vector<String> lines = str_template.split("\n"); @@ -150,6 +150,7 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito config["executable"] = p_name; config["args"] = args; config["fileSizes"] = p_file_sizes; + config["ensureCrossOriginIsolationHeaders"] = (bool)p_preset->get("progressive_web_app/ensure_cross_origin_isolation_headers"); String head_include; if (p_preset->get("html/export_icon")) { @@ -169,6 +170,13 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito replaces["$GODOT_PROJECT_NAME"] = GLOBAL_GET("application/config/name"); replaces["$GODOT_HEAD_INCLUDE"] = head_include + custom_head_include; replaces["$GODOT_CONFIG"] = str_config; + + if (p_preset->get("variant/thread_support")) { + replaces["$GODOT_THREADS_ENABLED"] = "true"; + } else { + replaces["$GODOT_THREADS_ENABLED"] = "false"; + } + _replace_strings(replaces, p_html); } @@ -215,10 +223,12 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese const String dir = p_path.get_base_dir(); const String name = p_path.get_file().get_basename(); bool extensions = (bool)p_preset->get("variant/extensions_support"); + bool ensure_crossorigin_isolation_headers = (bool)p_preset->get("progressive_web_app/ensure_cross_origin_isolation_headers"); HashMap<String, String> replaces; - replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); - replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); - replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; + replaces["___GODOT_VERSION___"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); + replaces["___GODOT_NAME___"] = proj_name.substr(0, 16); + replaces["___GODOT_OFFLINE_PAGE___"] = name + ".offline.html"; + replaces["___GODOT_ENSURE_CROSSORIGIN_ISOLATION_HEADERS___"] = ensure_crossorigin_isolation_headers ? "true" : "false"; // Files cached during worker install. Array cache_files; @@ -231,7 +241,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese } cache_files.push_back(name + ".worker.js"); cache_files.push_back(name + ".audio.worklet.js"); - replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string(); + replaces["___GODOT_CACHE___"] = Variant(cache_files).to_json_string(); // Heavy files that are cached on demand. Array opt_cache_files; @@ -243,7 +253,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese opt_cache_files.push_back(p_shared_objects[i].path.get_file()); } } - replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); + replaces["___GODOT_OPT_CACHE___"] = Variant(opt_cache_files).to_json_string(); const String sw_path = dir.path_join(name + ".service.worker.js"); Vector<uint8_t> sw; @@ -335,6 +345,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type. + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), true)); // Thread support (i.e. run with or without COEP/COOP headers). r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer @@ -345,6 +356,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/focus_canvas_on_start"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/enabled"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/ensure_cross_origin_isolation_headers"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/offline_page", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/display", PROPERTY_HINT_ENUM, "Fullscreen,Standalone,Minimal UI,Browser"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "progressive_web_app/orientation", PROPERTY_HINT_ENUM, "Any,Landscape,Portrait"), 0)); @@ -377,10 +389,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp String err; bool valid = false; bool extensions = (bool)p_preset->get("variant/extensions_support"); + bool thread_support = (bool)p_preset->get("variant/thread_support"); // Look for export templates (first official, and if defined custom templates). - bool dvalid = exists_export_template(_get_template_name(extensions, true), &err); - bool rvalid = exists_export_template(_get_template_name(extensions, false), &err); + bool dvalid = exists_export_template(_get_template_name(extensions, thread_support, true), &err); + bool rvalid = exists_export_template(_get_template_name(extensions, thread_support, false), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -454,7 +467,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p template_path = template_path.strip_edges(); if (template_path.is_empty()) { bool extensions = (bool)p_preset->get("variant/extensions_support"); - template_path = find_export_template(_get_template_name(extensions, p_debug)); + bool thread_support = (bool)p_preset->get("variant/thread_support"); + template_path = find_export_template(_get_template_name(extensions, thread_support, p_debug)); } if (!template_path.is_empty() && !FileAccess::exists(template_path)) { @@ -574,7 +588,6 @@ bool EditorExportPlatformWeb::poll_export() { menu_options = preset.is_valid(); if (server->is_listening()) { if (menu_options == 0) { - MutexLock lock(server_lock); server->stop(); } else { menu_options += 1; @@ -593,7 +606,6 @@ int EditorExportPlatformWeb::get_options_count() const { Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { if (p_option == 1) { - MutexLock lock(server_lock); server->stop(); return OK; } @@ -643,12 +655,8 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int const String tls_cert = EDITOR_GET("export/web/tls_certificate"); // Restart server. - { - MutexLock lock(server_lock); - - server->stop(); - err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert); - } + server->stop(); + err = server->listen(bind_port, bind_ip, use_tls, tls_key, tls_cert); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err)); return err; @@ -664,21 +672,9 @@ Ref<Texture2D> EditorExportPlatformWeb::get_run_icon() const { return run_icon; } -void EditorExportPlatformWeb::_server_thread_poll(void *data) { - EditorExportPlatformWeb *ej = static_cast<EditorExportPlatformWeb *>(data); - while (!ej->server_quit) { - OS::get_singleton()->delay_usec(6900); - { - MutexLock lock(ej->server_lock); - ej->server->poll(); - } - } -} - EditorExportPlatformWeb::EditorExportPlatformWeb() { if (EditorNode::get_singleton()) { server.instantiate(); - server_thread.start(_server_thread_poll, this); #ifdef MODULE_SVG_ENABLED Ref<Image> img = memnew(Image); @@ -701,11 +697,4 @@ EditorExportPlatformWeb::EditorExportPlatformWeb() { } EditorExportPlatformWeb::~EditorExportPlatformWeb() { - if (server.is_valid()) { - server->stop(); - } - server_quit = true; - if (server_thread.is_started()) { - server_thread.wait_to_finish(); - } } diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 887000ac45..952d03cdb4 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -52,15 +52,15 @@ class EditorExportPlatformWeb : public EditorExportPlatform { int menu_options = 0; Ref<EditorHTTPServer> server; - bool server_quit = false; - Mutex server_lock; - Thread server_thread; - String _get_template_name(bool p_extension, bool p_debug) const { + String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const { String name = "web"; if (p_extension) { name += "_dlink"; } + if (!p_thread_support) { + name += "_nothreads"; + } if (p_debug) { name += "_debug.zip"; } else { @@ -90,14 +90,12 @@ class EditorExportPlatformWeb : public EditorExportPlatform { } Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); - void _replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template); + void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template); void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); - static void _server_thread_poll(void *data); - public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; diff --git a/platform/web/js/engine/config.js b/platform/web/js/engine/config.js index 0b6626968e..a7134da6fa 100644 --- a/platform/web/js/engine/config.js +++ b/platform/web/js/engine/config.js @@ -19,7 +19,7 @@ const EngineConfig = {}; // eslint-disable-line no-unused-vars const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars const cfg = /** @lends {InternalConfig.prototype} */ { /** - * Whether the unload the engine automatically after the instance is initialized. + * Whether to unload the engine automatically after the instance is initialized. * * @memberof EngineConfig * @default diff --git a/platform/web/js/engine/engine.js b/platform/web/js/engine/engine.js index 3d6720a2fc..7e24ad9ae2 100644 --- a/platform/web/js/engine/engine.js +++ b/platform/web/js/engine/engine.js @@ -179,9 +179,7 @@ const Engine = (function () { preloader.preloadedFiles.length = 0; // Clear memory me.rtenv['callMain'](me.config.args); initPromise = null; - if (me.config.serviceWorker && 'serviceWorker' in navigator) { - navigator.serviceWorker.register(me.config.serviceWorker); - } + me.installServiceWorker(); resolve(); }); }); @@ -242,6 +240,17 @@ const Engine = (function () { this.rtenv['request_quit'](); } }, + + /** + * Install the progressive-web app service worker. + * @returns {Promise} The service worker registration promise. + */ + installServiceWorker: function () { + if (this.config.serviceWorker && 'serviceWorker' in navigator) { + return navigator.serviceWorker.register(this.config.serviceWorker); + } + return Promise.resolve(); + }, }; Engine.prototype = proto; @@ -252,6 +261,7 @@ const Engine = (function () { Engine.prototype['startGame'] = Engine.prototype.startGame; Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; + Engine.prototype['installServiceWorker'] = Engine.prototype.installServiceWorker; // Also expose static methods as instance methods Engine.prototype['load'] = Engine.load; Engine.prototype['unload'] = Engine.unload; diff --git a/platform/web/js/engine/features.js b/platform/web/js/engine/features.js index b7c6c9d445..81bc82f3c6 100644 --- a/platform/web/js/engine/features.js +++ b/platform/web/js/engine/features.js @@ -72,8 +72,14 @@ const Features = { // eslint-disable-line no-unused-vars * * @returns {Array<string>} A list of human-readable missing features. * @function Engine.getMissingFeatures + * @typedef {{ threads: boolean }} SupportedFeatures + * @param {SupportedFeatures} supportedFeatures */ - getMissingFeatures: function () { + getMissingFeatures: function (supportedFeatures = {}) { + const { + threads: supportsThreads = true, + } = supportedFeatures; + const missing = []; if (!Features.isWebGLAvailable(2)) { missing.push('WebGL2 - Check web browser configuration and hardware support'); @@ -84,12 +90,16 @@ const Features = { // eslint-disable-line no-unused-vars if (!Features.isSecureContext()) { missing.push('Secure Context - Check web server configuration (use HTTPS)'); } - if (!Features.isCrossOriginIsolated()) { - missing.push('Cross Origin Isolation - Check web server configuration (send correct headers)'); - } - if (!Features.isSharedArrayBufferAvailable()) { - missing.push('SharedArrayBuffer - Check web server configuration (send correct headers)'); + + if (supportsThreads) { + if (!Features.isCrossOriginIsolated()) { + missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.'); + } + if (!Features.isSharedArrayBufferAvailable()) { + missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.'); + } } + // Audio is normally optional since we have a dummy fallback. return missing; }, diff --git a/platform/web/js/libs/audio.worklet.js b/platform/web/js/libs/audio.worklet.js index 89b581b3d6..3b94cab85c 100644 --- a/platform/web/js/libs/audio.worklet.js +++ b/platform/web/js/libs/audio.worklet.js @@ -167,7 +167,7 @@ class GodotProcessor extends AudioWorkletProcessor { GodotProcessor.write_input(this.input_buffer, input); this.input.write(this.input_buffer); } else { - this.port.postMessage('Input buffer is full! Skipping input frame.'); + // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer. } } const process_output = GodotProcessor.array_has_data(outputs); @@ -184,7 +184,7 @@ class GodotProcessor extends AudioWorkletProcessor { this.port.postMessage({ 'cmd': 'read', 'data': chunk }); } } else { - this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); + // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer. } } this.process_notify(); diff --git a/platform/web/package.json b/platform/web/package.json index 5da0e2a76b..4e17cd530b 100644 --- a/platform/web/package.json +++ b/platform/web/package.json @@ -5,18 +5,20 @@ "description": "Development and linting setup for Godot's Web platform code", "scripts": { "docs": "jsdoc --template js/jsdoc2rst/ js/engine/engine.js js/engine/config.js js/engine/features.js --destination ''", - "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools && npm run lint:html", + "lint": "npm run lint:engine && npm run lint:libs && npm run lint:modules && npm run lint:tools && npm run lint:sw && npm run lint:html", "lint:engine": "eslint \"js/engine/*.js\" --no-eslintrc -c .eslintrc.engine.js", + "lint:sw": "eslint \"../../misc/dist/html/service-worker.js\" --no-eslintrc -c .eslintrc.sw.js", "lint:libs": "eslint \"js/libs/*.js\" --no-eslintrc -c .eslintrc.libs.js", "lint:modules": "eslint \"../../modules/**/*.js\" --no-eslintrc -c .eslintrc.libs.js", "lint:tools": "eslint \"js/jsdoc2rst/**/*.js\" --no-eslintrc -c .eslintrc.engine.js", "lint:html": "eslint \"../../misc/dist/html/*.html\" --no-eslintrc -c .eslintrc.html.js", - "format": "npm run format:engine && npm run format:libs && npm run format:modules && npm run format:tools && npm run format:html", + "format": "npm run format:engine && npm run format:libs && npm run format:modules && npm run format:tools && format:sw && npm run format:html", "format:engine": "npm run lint:engine -- --fix", "format:libs": "npm run lint:libs -- --fix", "format:modules": "npm run lint:modules -- --fix", "format:tools": "npm run lint:tools -- --fix", - "format:html": "npm run lint:html -- --fix" + "format:html": "npm run lint:html -- --fix", + "format:sw": "npm run lint:sw -- --fix" }, "author": "Godot Engine contributors", "license": "MIT", diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 0549e408a7..34c8f8e7a1 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -3,9 +3,12 @@ Import("env") import os +from pathlib import Path from platform_methods import run_in_subprocess import platform_windows_builders +sources = [] + common_win = [ "godot_windows.cpp", "crash_handler_windows.cpp", @@ -15,23 +18,38 @@ common_win = [ "joypad_windows.cpp", "tts_windows.cpp", "windows_terminal_logger.cpp", - "vulkan_context_win.cpp", "gl_manager_windows_native.cpp", "gl_manager_windows_angle.cpp", "wgl_detect_version.cpp", + "rendering_context_driver_vulkan_windows.cpp", ] common_win_wrap = [ "console_wrapper_windows.cpp", ] + +def arrange_program_clean(prog): + """ + Given an SCons program, arrange for output files SCons doesn't know about + to be cleaned when SCons is called with --clean + """ + extensions_to_clean = [".ilk", ".exp", ".pdb", ".lib"] + for program in prog: + executable_stem = Path(program.name).stem + extra_files_to_clean = [f"#bin/{executable_stem}{extension}" for extension in extensions_to_clean] + Clean(prog, extra_files_to_clean) + + res_file = "godot_res.rc" res_target = "godot_res" + env["OBJSUFFIX"] res_obj = env.RES(res_target, res_file) -sources = common_win + res_obj +env.add_source_files(sources, common_win) +sources += res_obj prog = env.add_program("#bin/godot", sources, PROGSUFFIX=env["PROGSUFFIX"]) +arrange_program_clean(prog) # Build console wrapper app. if env["windows_subsystem"] == "gui": @@ -48,7 +66,9 @@ if env["windows_subsystem"] == "gui": env_wrap.Append(LIBS=["version"]) prog_wrap = env_wrap.add_program("#bin/godot", common_win_wrap + res_wrap_obj, PROGSUFFIX=env["PROGSUFFIX_WRAP"]) + arrange_program_clean(prog_wrap) env_wrap.Depends(prog_wrap, prog) + sources += common_win_wrap + res_wrap_obj # Microsoft Visual Studio Project Generation if env["vsproj"]: @@ -81,7 +101,7 @@ if env["d3d12"]: arch_bin_dir = "#bin/" + env["arch"] # DXC - if env["dxc_path"] != "": + if env["dxc_path"] != "" and os.path.exists(env["dxc_path"]): dxc_dll = "dxil.dll" # Whether this one is loaded from arch-specific directory or not can be determined at runtime. # Let's copy to both and let the user decide the distribution model. @@ -93,7 +113,7 @@ if env["d3d12"]: ) # Agility SDK - if env["agility_sdk_path"] != "": + if env["agility_sdk_path"] != "" and os.path.exists(env["agility_sdk_path"]): agility_dlls = ["D3D12Core.dll", "d3d12SDKLayers.dll"] # Whether these are loaded from arch-specific directory or not has to be known at build time. target_dir = arch_bin_dir if env["agility_sdk_multiarch"] else "#bin" @@ -105,7 +125,7 @@ if env["d3d12"]: ) # PIX - if env["pix_path"] != "": + if env["use_pix"]: pix_dll = "WinPixEventRuntime.dll" env.Command( "#bin/" + pix_dll, @@ -114,7 +134,9 @@ if env["d3d12"]: ) if not os.getenv("VCINSTALLDIR"): - if env["debug_symbols"] and env["separate_debug_symbols"]: + if env["debug_symbols"]: env.AddPostAction(prog, run_in_subprocess(platform_windows_builders.make_debug_mingw)) if env["windows_subsystem"] == "gui": env.AddPostAction(prog_wrap, run_in_subprocess(platform_windows_builders.make_debug_mingw)) + +env.platform_sources += sources diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 4acdc8e639..0619e62563 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -164,6 +164,22 @@ def get_opts(): mingw = os.getenv("MINGW_PREFIX", "") + # Direct3D 12 SDK dependencies folder. + d3d12_deps_folder = os.getenv("LOCALAPPDATA") + if d3d12_deps_folder: + d3d12_deps_folder = os.path.join(d3d12_deps_folder, "Godot", "build_deps") + else: + # Cross-compiling, the deps install script puts things in `bin`. + # Getting an absolute path to it is a bit hacky in Python. + try: + import inspect + + caller_frame = inspect.stack()[1] + caller_script_dir = os.path.dirname(os.path.abspath(caller_frame[1])) + d3d12_deps_folder = os.path.join(caller_script_dir, "bin", "build_deps") + except: # Give up. + d3d12_deps_folder = "" + return [ ("mingw_prefix", "MinGW prefix", mingw), # Targeted Windows version: 7 (and later), minimum supported version @@ -188,11 +204,32 @@ def get_opts(): BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False), ("angle_libs", "Path to the ANGLE static libraries", ""), # Direct3D 12 support. - ("mesa_libs", "Path to the MESA/NIR static libraries (required for D3D12)", ""), - ("dxc_path", "Path to the DirectX Shader Compiler distribution (required for D3D12)", ""), - ("agility_sdk_path", "Path to the Agility SDK distribution (optional for D3D12)", ""), - ("agility_sdk_multiarch", "Whether the Agility SDK DLLs will be stored in arch-specific subdirectories", False), - ("pix_path", "Path to the PIX runtime distribution (optional for D3D12)", ""), + ( + "mesa_libs", + "Path to the MESA/NIR static libraries (required for D3D12)", + os.path.join(d3d12_deps_folder, "mesa"), + ), + ( + "dxc_path", + "Path to the DirectX Shader Compiler distribution (required for D3D12)", + os.path.join(d3d12_deps_folder, "dxc"), + ), + ( + "agility_sdk_path", + "Path to the Agility SDK distribution (optional for D3D12)", + os.path.join(d3d12_deps_folder, "agility_sdk"), + ), + BoolVariable( + "agility_sdk_multiarch", + "Whether the Agility SDK DLLs will be stored in arch-specific subdirectories", + False, + ), + BoolVariable("use_pix", "Use PIX (Performance tuning and debugging for DirectX 12) runtime", False), + ( + "pix_path", + "Path to the PIX runtime distribution (optional for D3D12)", + os.path.join(d3d12_deps_folder, "pix"), + ), ] @@ -432,16 +469,22 @@ def configure_msvc(env, vcvars_msvc_config): LIBS += ["psapi", "dbghelp"] if env["vulkan"]: - env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED"]) + env.AppendUnique(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: LIBS += ["vulkan"] if env["d3d12"]: - if env["dxc_path"] == "": - print("The Direct3D 12 rendering driver requires dxc_path to be set.") + # Check whether we have d3d12 dependencies installed. + if not os.path.exists(env["mesa_libs"]): + print("The Direct3D 12 rendering driver requires dependencies to be installed.") + print(r"You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.") + print("See the documentation for more information:") + print( + "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html" + ) sys.exit(255) - env.AppendUnique(CPPDEFINES=["D3D12_ENABLED"]) + env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"]) LIBS += ["d3d12", "dxgi", "dxguid"] LIBS += ["version"] # Mesa dependency. @@ -449,18 +492,16 @@ def configure_msvc(env, vcvars_msvc_config): if env["target"] == "release_debug": env.Append(CXXFLAGS=["/bigobj"]) - arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" - # PIX - if env["pix_path"] != "": + if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): + env["use_pix"] = False + + if env["use_pix"]: + arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" + env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) LIBS += ["WinPixEventRuntime"] - # Mesa - if env["mesa_libs"] == "": - print("The Direct3D 12 rendering driver requires mesa_libs to be set.") - sys.exit(255) - env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) LIBS += ["libNIR.windows." + env["arch"]] @@ -653,29 +694,37 @@ def configure_mingw(env): env.Append(LIBS=["psapi", "dbghelp"]) if env["vulkan"]: - env.Append(CPPDEFINES=["VULKAN_ENABLED"]) + env.Append(CPPDEFINES=["VULKAN_ENABLED", "RD_ENABLED"]) if not env["use_volk"]: env.Append(LIBS=["vulkan"]) if env["d3d12"]: - env.AppendUnique(CPPDEFINES=["D3D12_ENABLED"]) - env.Append(LIBS=["d3d12", "dxgi", "dxguid"]) - env.Append(LIBS=["version"]) # Mesa dependency. + # Check whether we have d3d12 dependencies installed. + if not os.path.exists(env["mesa_libs"]): + print("The Direct3D 12 rendering driver requires dependencies to be installed.") + print(r"You can install them by running `python misc\scripts\install_d3d12_sdk_windows.py`.") + print("See the documentation for more information:") + print( + "https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_windows.html" + ) + sys.exit(255) - arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" + env.AppendUnique(CPPDEFINES=["D3D12_ENABLED", "RD_ENABLED"]) + env.Append(LIBS=["d3d12", "dxgi", "dxguid"]) # PIX - if env["pix_path"] != "": - print("PIX runtime is not supported with MinGW.") - sys.exit(255) + if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]): + env["use_pix"] = False - # Mesa - if env["mesa_libs"] == "": - print("The Direct3D 12 rendering driver requires mesa_libs to be set.") - sys.exit(255) + if env["use_pix"]: + arch_subdir = "arm64" if env["arch"] == "arm64" else "x64" + + env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir]) + env.Append(LIBS=["WinPixEventRuntime"]) env.Append(LIBPATH=[env["mesa_libs"] + "/bin"]) env.Append(LIBS=["libNIR.windows." + env["arch"]]) + env.Append(LIBS=["version"]) # Mesa dependency. if env["opengl3"]: env.Append(CPPDEFINES=["GLES3_ENABLED"]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 77dfff2e5d..e0bad55b20 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -39,6 +39,12 @@ #include "main/main.h" #include "scene/resources/atlas_texture.h" +#if defined(VULKAN_ENABLED) +#include "rendering_context_driver_vulkan_windows.h" +#endif +#if defined(D3D12_ENABLED) +#include "drivers/d3d12/rendering_context_driver_d3d12.h" +#endif #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" #endif @@ -52,6 +58,12 @@ #define DWMWA_USE_IMMERSIVE_DARK_MODE 20 #endif +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 +#define DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 19 +#endif + +#define WM_INDICATOR_CALLBACK_MESSAGE (WM_USER + 1) + #if defined(__GNUC__) // Workaround GCC warning from -Wcast-function-type. #define GetProcAddress (void *)GetProcAddress @@ -97,6 +109,7 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: case FEATURE_SCREEN_CAPTURE: + case FEATURE_STATUS_INDICATOR: return true; default: return false; @@ -165,20 +178,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) { use_raw_input = true; - RAWINPUTDEVICE rid[1] = {}; - rid[0].usUsagePage = 0x01; - rid[0].usUsage = 0x02; + RAWINPUTDEVICE rid[2] = {}; + rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE rid[0].dwFlags = 0; + rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD + rid[1].dwFlags = 0; + if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) { // Follow the defined window rid[0].hwndTarget = windows[p_target_window].hWnd; + rid[1].hwndTarget = windows[p_target_window].hWnd; } else { // Follow the keyboard focus rid[0].hwndTarget = 0; + rid[1].hwndTarget = 0; } - if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) { + if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) { // Registration failed. use_raw_input = false; } @@ -219,7 +238,142 @@ void DisplayServerWindows::tts_stop() { tts->stop(); } +// Silence warning due to a COM API weirdness. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +class FileDialogEventHandler : public IFileDialogEvents, public IFileDialogControlEvents { + LONG ref_count = 1; + int ctl_id = 1; + + HashMap<int, String> ctls; + Dictionary selected; + String root; + +public: + // IUnknown methods + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppv) { + static const QITAB qit[] = { +#ifdef __MINGW32__ + { &__uuidof(IFileDialogEvents), static_cast<decltype(qit[0].dwOffset)>(OFFSETOFCLASS(IFileDialogEvents, FileDialogEventHandler)) }, + { &__uuidof(IFileDialogControlEvents), static_cast<decltype(qit[0].dwOffset)>(OFFSETOFCLASS(IFileDialogControlEvents, FileDialogEventHandler)) }, +#else + QITABENT(FileDialogEventHandler, IFileDialogEvents), + QITABENT(FileDialogEventHandler, IFileDialogControlEvents), +#endif + { 0, 0 }, + }; + return QISearch(this, qit, riid, ppv); + } + + ULONG STDMETHODCALLTYPE AddRef() { + return InterlockedIncrement(&ref_count); + } + + ULONG STDMETHODCALLTYPE Release() { + long ref = InterlockedDecrement(&ref_count); + if (!ref) { + delete this; + } + return ref; + } + + // IFileDialogEvents methods + HRESULT STDMETHODCALLTYPE OnFileOk(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnFolderChange(IFileDialog *) { return S_OK; }; + + HRESULT STDMETHODCALLTYPE OnFolderChanging(IFileDialog *p_pfd, IShellItem *p_item) { + if (root.is_empty()) { + return S_OK; + } + + LPWSTR lpw_path = nullptr; + p_item->GetDisplayName(SIGDN_FILESYSPATH, &lpw_path); + if (!lpw_path) { + return S_FALSE; + } + String path = String::utf16((const char16_t *)lpw_path).simplify_path(); + if (!path.begins_with(root.simplify_path())) { + return S_FALSE; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnHelp(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnSelectionChange(IFileDialog *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnTypeChange(IFileDialog *pfd) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) { return S_OK; }; + + // IFileDialogControlEvents methods + HRESULT STDMETHODCALLTYPE OnItemSelected(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, DWORD p_item_idx) { + if (ctls.has(p_ctl_id)) { + selected[ctls[p_ctl_id]] = (int)p_item_idx; + } + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnButtonClicked(IFileDialogCustomize *, DWORD) { return S_OK; }; + HRESULT STDMETHODCALLTYPE OnCheckButtonToggled(IFileDialogCustomize *p_pfdc, DWORD p_ctl_id, BOOL p_checked) { + if (ctls.has(p_ctl_id)) { + selected[ctls[p_ctl_id]] = (bool)p_checked; + } + return S_OK; + } + HRESULT STDMETHODCALLTYPE OnControlActivating(IFileDialogCustomize *, DWORD) { return S_OK; }; + + Dictionary get_selected() { + return selected; + } + + void set_root(const String &p_root) { + root = p_root; + } + + void add_option(IFileDialogCustomize *p_pfdc, const String &p_name, const Vector<String> &p_options, int p_default) { + int gid = ctl_id++; + int cid = ctl_id++; + + if (p_options.size() == 0) { + // Add check box. + p_pfdc->StartVisualGroup(gid, L""); + p_pfdc->AddCheckButton(cid, (LPCWSTR)p_name.utf16().get_data(), p_default); + p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED); + p_pfdc->EndVisualGroup(); + selected[p_name] = (bool)p_default; + } else { + // Add combo box. + p_pfdc->StartVisualGroup(gid, (LPCWSTR)p_name.utf16().get_data()); + p_pfdc->AddComboBox(cid); + p_pfdc->SetControlState(cid, CDCS_VISIBLE | CDCS_ENABLED); + for (int i = 0; i < p_options.size(); i++) { + p_pfdc->AddControlItem(cid, i, (LPCWSTR)p_options[i].utf16().get_data()); + } + p_pfdc->SetSelectedControlItem(cid, p_default); + p_pfdc->EndVisualGroup(); + selected[p_name] = p_default; + } + ctls[cid] = p_name; + } + + virtual ~FileDialogEventHandler(){}; +}; + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + Error DisplayServerWindows::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) { + return _file_dialog_with_options_show(p_title, p_current_directory, String(), p_filename, p_show_hidden, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false); +} + +Error DisplayServerWindows::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) { + return _file_dialog_with_options_show(p_title, p_current_directory, p_root, p_filename, p_show_hidden, p_mode, p_filters, p_options, p_callback, true); +} + +Error DisplayServerWindows::_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, bool p_options_in_cb) { _THREAD_SAFE_METHOD_ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED); @@ -269,6 +423,31 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String hr = CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void **)&pfd); } if (SUCCEEDED(hr)) { + IFileDialogEvents *pfde = nullptr; + FileDialogEventHandler *event_handler = new FileDialogEventHandler(); + hr = event_handler->QueryInterface(IID_PPV_ARGS(&pfde)); + + DWORD cookie = 0; + hr = pfd->Advise(pfde, &cookie); + + IFileDialogCustomize *pfdc = nullptr; + hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc)); + + 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"]; + + event_handler->add_option(pfdc, name, options, default_idx); + } + event_handler->set_root(p_root); + + pfdc->Release(); + DWORD flags; pfd->GetOptions(&flags); if (p_mode == FILE_DIALOG_MODE_OPEN_FILES) { @@ -306,8 +485,18 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } hr = pfd->Show(windows[window_id].hWnd); + pfd->Unadvise(cookie); + + Dictionary options = event_handler->get_selected(); + + pfde->Release(); + event_handler->Release(); + UINT index = 0; pfd->GetFileTypeIndex(&index); + if (index > 0) { + index = index - 1; + } if (SUCCEEDED(hr)) { Vector<String> file_names; @@ -346,30 +535,60 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String } } if (!p_callback.is_null()) { - Variant v_result = true; - Variant v_files = file_names; - Variant v_index = index; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - p_callback.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_callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = true; + Variant v_files = file_names; + Variant v_index = index; + Variant v_opt = options; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + p_callback.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_callback, args, 4, ce))); + } + } else { + Variant v_result = true; + Variant v_files = file_names; + Variant v_index = index; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + p_callback.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_callback, args, 3, ce))); + } } } } else { if (!p_callback.is_null()) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = index; - Variant ret; - Callable::CallError ce; - const Variant *args[3] = { &v_result, &v_files, &v_index }; - - p_callback.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_callback, args, 3, ce))); + if (p_options_in_cb) { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = index; + Variant v_opt = options; + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &v_result, &v_files, &v_index, &v_opt }; + + p_callback.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_callback, args, 4, ce))); + } + } else { + Variant v_result = false; + Variant v_files = Vector<String>(); + Variant v_index = index; + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &v_result, &v_files, &v_index }; + + p_callback.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_callback, args, 3, ce))); + } } } } @@ -550,8 +769,7 @@ Ref<Image> DisplayServerWindows::clipboard_get_image() const { pba.append(rgbquad->rgbReserved); } } - image.instantiate(); - image->create_from_data(info->biWidth, info->biHeight, false, Image::Format::FORMAT_RGBA8, pba); + image = Image::create_from_data(info->biWidth, info->biHeight, false, Image::Format::FORMAT_RGBA8, pba); GlobalUnlock(mem); } @@ -687,6 +905,8 @@ typedef struct { } EnumRectData; typedef struct { + Vector<DISPLAYCONFIG_PATH_INFO> paths; + Vector<DISPLAYCONFIG_MODE_INFO> modes; int count; int screen; float rate; @@ -738,12 +958,30 @@ static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonit minfo.cbSize = sizeof(minfo); GetMonitorInfoW(hMonitor, &minfo); - DEVMODEW dm; - memset(&dm, 0, sizeof(dm)); - dm.dmSize = sizeof(dm); - EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + bool found = false; + for (const DISPLAYCONFIG_PATH_INFO &path : data->paths) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name; + memset(&source_name, 0, sizeof(source_name)); + source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source_name.header.size = sizeof(source_name); + source_name.header.adapterId = path.sourceInfo.adapterId; + source_name.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&source_name.header) == ERROR_SUCCESS) { + if (wcscmp(minfo.szDevice, source_name.viewGdiDeviceName) == 0 && path.targetInfo.refreshRate.Numerator != 0 && path.targetInfo.refreshRate.Denominator != 0) { + data->rate = (double)path.targetInfo.refreshRate.Numerator / (double)path.targetInfo.refreshRate.Denominator; + found = true; + break; + } + } + } + if (!found) { + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); - data->rate = dm.dmDisplayFrequency; + data->rate = dm.dmDisplayFrequency; + } } data->count++; @@ -932,7 +1170,19 @@ float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ p_screen = _get_screen_index(p_screen); - EnumRefreshRateData data = { 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + EnumRefreshRateData data = { Vector<DISPLAYCONFIG_PATH_INFO>(), Vector<DISPLAYCONFIG_MODE_INFO>(), 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + + uint32_t path_count = 0; + uint32_t mode_count = 0; + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) == ERROR_SUCCESS) { + data.paths.resize(path_count); + data.modes.resize(mode_count); + if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_count, data.paths.ptrw(), &mode_count, data.modes.ptrw(), nullptr) != ERROR_SUCCESS) { + data.paths.clear(); + data.modes.clear(); + } + } + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); return data.rate; } @@ -1049,6 +1299,11 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (mainwindow_icon) { SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon); } +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_create(window_id); + } +#endif return window_id; } @@ -1102,14 +1357,13 @@ void DisplayServerWindows::delete_sub_window(WindowID p_window) { window_set_transient(p_window, INVALID_WINDOW_ID); } -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(p_window); +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_free(p_window); } -#endif -#ifdef D3D12_ENABLED - if (context_d3d12) { - context_d3d12->window_destroy(p_window); + + if (rendering_context) { + rendering_context->window_destroy(p_window); } #endif #ifdef GLES3_ENABLED @@ -1539,14 +1793,9 @@ void DisplayServerWindows::window_set_size(const Size2i p_size, WindowID p_windo wd.width = w; wd.height = h; -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(p_window, w, h); - } -#endif -#if defined(D3D12_ENABLED) - if (context_d3d12) { - context_d3d12->window_resize(p_window, w, h); +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_size(p_window, w, h); } #endif #if defined(GLES3_ENABLED) @@ -1926,6 +2175,10 @@ bool DisplayServerWindows::window_is_focused(WindowID p_window) const { return wd.window_focused; } +DisplayServerWindows::WindowID DisplayServerWindows::get_focused_window() const { + return last_focused_window; +} + bool DisplayServerWindows::window_can_draw(WindowID p_window) const { _THREAD_SAFE_METHOD_ @@ -2592,17 +2845,177 @@ void DisplayServerWindows::set_icon(const Ref<Image> &p_icon) { } } -void DisplayServerWindows::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); +DisplayServer::IndicatorID DisplayServerWindows::create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) { + HICON hicon = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon; + if (img != icon) { + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + } + + int w = img->get_width(); + int h = img->get_height(); + + // Create temporary bitmap buffer. + int icon_len = 40 + h * w * 4; + Vector<BYTE> v; + v.resize(icon_len); + BYTE *icon_bmp = v.ptrw(); + + encode_uint32(40, &icon_bmp[0]); + encode_uint32(w, &icon_bmp[4]); + encode_uint32(h * 2, &icon_bmp[8]); + encode_uint16(1, &icon_bmp[12]); + encode_uint16(32, &icon_bmp[14]); + encode_uint32(BI_RGB, &icon_bmp[16]); + encode_uint32(w * h * 4, &icon_bmp[20]); + encode_uint32(0, &icon_bmp[24]); + encode_uint32(0, &icon_bmp[28]); + encode_uint32(0, &icon_bmp[32]); + encode_uint32(0, &icon_bmp[36]); + + uint8_t *wr = &icon_bmp[40]; + const uint8_t *r = img->get_data().ptr(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; + uint8_t *wpx = &wr[(i * w + j) * 4]; + wpx[0] = rpx[2]; + wpx[1] = rpx[1]; + wpx[2] = rpx[0]; + wpx[3] = rpx[3]; + } + } + + hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); } -#endif -#if defined(D3D12_ENABLED) - if (context_d3d12) { - context_d3d12->set_vsync_mode(p_window, p_vsync_mode); + IndicatorData idat; + idat.callback = p_callback; + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = indicator_id_counter; + ndat.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; + ndat.uCallbackMessage = WM_INDICATOR_CALLBACK_MESSAGE; + ndat.hIcon = hicon; + memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR)); + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_ADD, &ndat); + Shell_NotifyIconW(NIM_SETVERSION, &ndat); + + IndicatorID iid = indicator_id_counter++; + indicators[iid] = idat; + + return iid; +} + +void DisplayServerWindows::status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) { + ERR_FAIL_COND(!indicators.has(p_id)); + + HICON hicon = nullptr; + if (p_icon.is_valid() && p_icon->get_width() > 0 && p_icon->get_height() > 0) { + Ref<Image> img = p_icon; + if (img != icon) { + img = img->duplicate(); + img->convert(Image::FORMAT_RGBA8); + } + + int w = img->get_width(); + int h = img->get_height(); + + // Create temporary bitmap buffer. + int icon_len = 40 + h * w * 4; + Vector<BYTE> v; + v.resize(icon_len); + BYTE *icon_bmp = v.ptrw(); + + encode_uint32(40, &icon_bmp[0]); + encode_uint32(w, &icon_bmp[4]); + encode_uint32(h * 2, &icon_bmp[8]); + encode_uint16(1, &icon_bmp[12]); + encode_uint16(32, &icon_bmp[14]); + encode_uint32(BI_RGB, &icon_bmp[16]); + encode_uint32(w * h * 4, &icon_bmp[20]); + encode_uint32(0, &icon_bmp[24]); + encode_uint32(0, &icon_bmp[28]); + encode_uint32(0, &icon_bmp[32]); + encode_uint32(0, &icon_bmp[36]); + + uint8_t *wr = &icon_bmp[40]; + const uint8_t *r = img->get_data().ptr(); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + const uint8_t *rpx = &r[((h - i - 1) * w + j) * 4]; + uint8_t *wpx = &wr[(i * w + j) * 4]; + wpx[0] = rpx[2]; + wpx[1] = rpx[1]; + wpx[2] = rpx[0]; + wpx[3] = rpx[3]; + } + } + + hicon = CreateIconFromResource(icon_bmp, icon_len, TRUE, 0x00030000); + } + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uFlags = NIF_ICON; + ndat.hIcon = hicon; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_MODIFY, &ndat); +} + +void DisplayServerWindows::status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uFlags = NIF_TIP; + memcpy(ndat.szTip, p_tooltip.utf16().ptr(), MIN(p_tooltip.utf16().length(), 127) * sizeof(WCHAR)); + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_MODIFY, &ndat); +} + +void DisplayServerWindows::status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) { + ERR_FAIL_COND(!indicators.has(p_id)); + + indicators[p_id].callback = p_callback; +} + +void DisplayServerWindows::delete_status_indicator(IndicatorID p_id) { + ERR_FAIL_COND(!indicators.has(p_id)); + + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = p_id; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_DELETE, &ndat); + indicators.erase(p_id); +} + +void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_context->window_set_vsync_mode(p_window, p_vsync_mode); } #endif @@ -2618,15 +3031,9 @@ void DisplayServerWindows::window_set_vsync_mode(DisplayServer::VSyncMode p_vsyn DisplayServer::VSyncMode DisplayServerWindows::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); - } -#endif - -#if defined(D3D12_ENABLED) - if (context_d3d12) { - return context_d3d12->get_vsync_mode(p_window); +#if defined(RD_ENABLED) + if (rendering_context) { + return rendering_context->window_get_vsync_mode(p_window); } #endif @@ -2689,6 +3096,7 @@ void DisplayServerWindows::_drag_event(WindowID p_window, float p_x, float p_y, event->set_index(idx); event->set_position(Vector2(p_x, p_y)); event->set_relative(Vector2(p_x, p_y) - curr->get()); + event->set_relative_screen_position(event->get_relative()); Input::get_singleton()->parse_input_event(event); @@ -2949,6 +3357,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Process window messages. switch (uMsg) { + case WM_CREATE: { + if (is_dark_mode_supported() && dark_title_available) { + BOOL value = is_dark_mode(); + + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + SendMessageW(windows[window_id].hWnd, WM_PAINT, 0, 0); + } + } break; case WM_NCPAINT: { if (RenderingServer::get_singleton() && (windows[window_id].borderless || (windows[window_id].fullscreen && windows[window_id].multiwindow_fs))) { Color color = RenderingServer::get_singleton()->get_default_clear_color(); @@ -3081,14 +3497,14 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } } break; case WM_THEMECHANGED: { if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(windows[window_id].hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } } break; case WM_SYSCOMMAND: // Intercept system commands. @@ -3104,6 +3520,30 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } } break; + case WM_INDICATOR_CALLBACK_MESSAGE: { + if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN || lParam == WM_MBUTTONDOWN || lParam == WM_XBUTTONDOWN) { + IndicatorID iid = (IndicatorID)wParam; + MouseButton mb = MouseButton::LEFT; + if (lParam == WM_RBUTTONDOWN) { + mb = MouseButton::RIGHT; + } else if (lParam == WM_MBUTTONDOWN) { + mb = MouseButton::MIDDLE; + } else if (lParam == WM_XBUTTONDOWN) { + mb = MouseButton::MB_XBUTTON1; + } + if (indicators.has(iid)) { + if (indicators[iid].callback.is_valid()) { + Variant v_button = mb; + Variant v_pos = mouse_get_position(); + Variant *v_args[2] = { &v_button, &v_pos }; + Variant ret; + Callable::CallError ce; + indicators[iid].callback.callp((const Variant **)&v_args, 2, ret, ce); + } + } + return 0; + } + } break; case WM_CLOSE: // Did we receive a close message? { if (windows[window_id].focus_timer_id != 0U) { @@ -3127,7 +3567,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_INPUT: { - if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) { + if (!use_raw_input) { break; } @@ -3145,7 +3585,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA RAWINPUT *raw = (RAWINPUT *)lpb; - if (raw->header.dwType == RIM_TYPEMOUSE) { + if (raw->header.dwType == RIM_TYPEKEYBOARD) { + if (raw->data.keyboard.VKey == VK_SHIFT) { + // If multiple Shifts are held down at the same time, + // Windows natively only sends a KEYUP for the last one to be released. + if (raw->data.keyboard.Flags & RI_KEY_BREAK) { + if (GetAsyncKeyState(VK_SHIFT) < 0) { + // A Shift is released, but another Shift is still held + ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE); + + KeyEvent ke; + ke.shift = false; + ke.alt = alt_mem; + ke.control = control_mem; + ke.meta = meta_mem; + ke.uMsg = WM_KEYUP; + ke.window_id = window_id; + + ke.wParam = VK_SHIFT; + // data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift. + // Bit 30 -> key was previously down, bit 31 -> key is being released. + ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31; + key_event_buffer[key_event_pos++] = ke; + } + } + } + } else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) { Ref<InputEventMouseMotion> mm; mm.instantiate(); @@ -3168,6 +3633,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(c); mm->set_global_position(c); mm->set_velocity(Vector2(0, 0)); + mm->set_screen_velocity(Vector2(0, 0)); if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { mm->set_relative(Vector2(raw->data.mouse.lLastX, raw->data.mouse.lLastY)); @@ -3192,6 +3658,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA old_x = coords.x; old_y = coords.y; } + mm->set_relative_screen_position(mm->get_relative()); if ((windows[window_id].window_has_focus || windows[window_id].is_popup) && mm->get_relative() != Vector2()) { Input::get_singleton()->parse_input_event(mm); @@ -3279,6 +3746,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3287,6 +3755,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { @@ -3426,6 +3895,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3434,6 +3904,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { @@ -3545,6 +4016,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + mm->set_screen_velocity(mm->get_velocity()); if (old_invalid) { old_x = mm->get_position().x; @@ -3553,6 +4025,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; @@ -3788,19 +4261,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA rect_changed = true; } - // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed. - if (window_created && window.context_created) { -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(window_id, window.width, window.height); - } -#endif -#if defined(D3D12_ENABLED) - if (context_d3d12) { - context_d3d12->window_resize(window_id, window.width, window.height); - } -#endif +#if defined(RD_ENABLED) + if (rendering_context && window.context_created) { + // Note: Trigger resize event to update swapchains when window is minimized/restored, even if size is not changed. + rendering_context->window_set_size(window_id, window.width, window.height); } +#endif } if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) { @@ -3826,7 +4292,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Return here to prevent WM_MOVE and WM_SIZE from being sent // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks return 0; - } break; case WM_ENTERSIZEMOVE: { @@ -4158,6 +4623,7 @@ void DisplayServerWindows::_process_key_events() { } Key key_label = keycode; Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); + KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24)); static BYTE keyboard_state[256]; memset(keyboard_state, 0, 256); @@ -4184,6 +4650,7 @@ void DisplayServerWindows::_process_key_events() { } k->set_keycode(keycode); k->set_physical_keycode(physical_keycode); + k->set_location(location); k->set_key_label(key_label); if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) { @@ -4349,29 +4816,40 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (is_dark_mode_supported() && dark_title_available) { BOOL value = is_dark_mode(); - ::DwmSetWindowAttribute(wd.hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + ::DwmSetWindowAttribute(wd.hWnd, use_legacy_dark_mode_before_20H1 ? DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 : DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); } +#ifdef RD_ENABLED + if (rendering_context) { + union { #ifdef VULKAN_ENABLED - if (context_vulkan) { - if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - windows.erase(id); - ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window."); + RenderingContextDriverVulkanWindows::WindowPlatformData vulkan; +#endif +#ifdef D3D12_ENABLED + RenderingContextDriverD3D12::WindowPlatformData d3d12; +#endif + } wpd; +#ifdef VULKAN_ENABLED + if (rendering_driver == "vulkan") { + wpd.vulkan.window = wd.hWnd; + wpd.vulkan.instance = hInstance; } - wd.context_created = true; - } #endif - #ifdef D3D12_ENABLED - if (context_d3d12) { - if (context_d3d12->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) != OK) { - memdelete(context_d3d12); - context_d3d12 = nullptr; + if (rendering_driver == "d3d12") { + wpd.d3d12.window = wd.hWnd; + } +#endif + if (rendering_context->window_create(id, &wpd) != OK) { + ERR_PRINT(vformat("Failed to create %s window.", rendering_driver)); + memdelete(rendering_context); + rendering_context = nullptr; windows.erase(id); - ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create D3D12 Window."); + return INVALID_WINDOW_ID; } + + rendering_context->window_set_size(id, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top); + rendering_context->window_set_vsync_mode(id, p_vsync_mode); wd.context_created = true; } #endif @@ -4480,6 +4958,7 @@ WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; // UXTheme API. bool DisplayServerWindows::dark_title_available = false; +bool DisplayServerWindows::use_legacy_dark_mode_before_20H1 = false; bool DisplayServerWindows::ux_theme_available = false; ShouldAppsUseDarkModePtr DisplayServerWindows::ShouldAppsUseDarkMode = nullptr; GetImmersiveColorFromColorSetExPtr DisplayServerWindows::GetImmersiveColorFromColorSetEx = nullptr; @@ -4601,11 +5080,30 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98)); ux_theme_available = ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference; - if (os_ver.dwBuildNumber >= 22000) { + if (os_ver.dwBuildNumber >= 18363) { dark_title_available = true; + if (os_ver.dwBuildNumber < 19041) { + use_legacy_dark_mode_before_20H1 = true; + } } } + // Note: Windows Ink API for pen input, available on Windows 8+ only. + // Note: DPI conversion API, available on Windows 8.1+ only. + HMODULE user32_lib = LoadLibraryW(L"user32.dll"); + if (user32_lib) { + win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); + win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); + + winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; + } + + if (winink_available) { + tablet_drivers.push_back("winink"); + } + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { @@ -4622,21 +5120,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win tablet_drivers.push_back("wintab"); } - // Note: Windows Ink API for pen input, available on Windows 8+ only. - // Note: DPI conversion API, available on Windows 8.1+ only. - HMODULE user32_lib = LoadLibraryW(L"user32.dll"); - if (user32_lib) { - win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); - win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); - win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI"); - win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI"); - - winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo; - } - - if (winink_available) { - tablet_drivers.push_back("winink"); - } + tablet_drivers.push_back("dummy"); if (OS::get_singleton()->is_hidpi_allowed()) { HMODULE Shcore = LoadLibraryW(L"Shcore.dll"); @@ -4673,29 +5157,28 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win _register_raw_input_devices(INVALID_WINDOW_ID); +#if defined(RD_ENABLED) #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - context_vulkan = memnew(VulkanContextWindows); - if (context_vulkan->initialize() != OK) { - memdelete(context_vulkan); - context_vulkan = nullptr; - r_error = ERR_UNAVAILABLE; - return; - } + rendering_context = memnew(RenderingContextDriverVulkanWindows); } #endif #if defined(D3D12_ENABLED) if (rendering_driver == "d3d12") { - context_d3d12 = memnew(D3D12Context); - if (context_d3d12->initialize() != OK) { - memdelete(context_d3d12); - context_d3d12 = nullptr; + rendering_context = memnew(RenderingContextDriverD3D12); + } +#endif + + if (rendering_context) { + if (rendering_context->initialize() != OK) { + memdelete(rendering_context); + rendering_context = nullptr; r_error = ERR_UNAVAILABLE; return; } } #endif - // Init context and rendering device +// Init context and rendering device #if defined(GLES3_ENABLED) #if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) @@ -4761,7 +5244,8 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (p_screen == SCREEN_OF_MAIN_WINDOW) { p_screen = SCREEN_PRIMARY; } - window_position = screen_get_position(p_screen) + (screen_get_size(p_screen) - p_resolution) / 2; + Rect2i scr_rect = screen_get_usable_rect(p_screen); + window_position = scr_rect.position + (scr_rect.size - p_resolution) / 2; } WindowID main_window = _create_window(p_mode, p_vsync_mode, p_flags, Rect2i(window_position, p_resolution)); @@ -4777,18 +5261,11 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win show_window(MAIN_WINDOW_ID); -#if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { - rendering_device_vulkan = memnew(RenderingDeviceVulkan); - rendering_device_vulkan->initialize(context_vulkan); - - RendererCompositorRD::make_current(); - } -#endif -#if defined(D3D12_ENABLED) - if (rendering_driver == "d3d12") { - rendering_device_d3d12 = memnew(RenderingDeviceD3D12); - rendering_device_d3d12->initialize(context_d3d12); +#if defined(RD_ENABLED) + if (rendering_context) { + rendering_device = memnew(RenderingDevice); + rendering_device->initialize(rendering_context, MAIN_WINDOW_ID); + rendering_device->screen_create(MAIN_WINDOW_ID); RendererCompositorRD::make_current(); } @@ -4851,6 +5328,16 @@ DisplayServer *DisplayServerWindows::create_func(const String &p_rendering_drive "If you have recently updated your video card drivers, try rebooting.", executable_name), "Unable to initialize Vulkan video driver"); + } else if (p_rendering_driver == "d3d12") { + String executable_name = OS::get_singleton()->get_executable_path().get_file(); + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required DirectX 12 version.\n\n" + "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" + "You can enable the OpenGL 3 driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" + "If you have recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize DirectX 12 video driver"); } else { OS::get_singleton()->alert( "Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n" @@ -4872,6 +5359,18 @@ DisplayServerWindows::~DisplayServerWindows() { cursors_cache.clear(); + // Destroy all status indicators. + for (HashMap<IndicatorID, IndicatorData>::Iterator E = indicators.begin(); E;) { + NOTIFYICONDATAW ndat; + ZeroMemory(&ndat, sizeof(NOTIFYICONDATAW)); + ndat.cbSize = sizeof(NOTIFYICONDATAW); + ndat.hWnd = windows[MAIN_WINDOW_ID].hWnd; + ndat.uID = E->key; + ndat.uVersion = NOTIFYICON_VERSION; + + Shell_NotifyIconW(NIM_DELETE, &ndat); + } + if (mouse_monitor) { UnhookWindowsHookEx(mouse_monitor); } @@ -4889,14 +5388,13 @@ DisplayServerWindows::~DisplayServerWindows() { #endif if (windows.has(MAIN_WINDOW_ID)) { -#ifdef VULKAN_ENABLED - if (context_vulkan) { - context_vulkan->window_destroy(MAIN_WINDOW_ID); +#ifdef RD_ENABLED + if (rendering_device) { + rendering_device->screen_free(MAIN_WINDOW_ID); } -#endif -#ifdef D3D12_ENABLED - if (context_d3d12) { - context_d3d12->window_destroy(MAIN_WINDOW_ID); + + if (rendering_context) { + rendering_context->window_destroy(MAIN_WINDOW_ID); } #endif if (wintab_available && windows[MAIN_WINDOW_ID].wtctx) { @@ -4906,29 +5404,15 @@ DisplayServerWindows::~DisplayServerWindows() { DestroyWindow(windows[MAIN_WINDOW_ID].hWnd); } -#if defined(VULKAN_ENABLED) - if (rendering_device_vulkan) { - rendering_device_vulkan->finalize(); - memdelete(rendering_device_vulkan); - rendering_device_vulkan = nullptr; - } - - if (context_vulkan) { - memdelete(context_vulkan); - context_vulkan = nullptr; - } -#endif - -#if defined(D3D12_ENABLED) - if (rendering_device_d3d12) { - rendering_device_d3d12->finalize(); - memdelete(rendering_device_d3d12); - rendering_device_d3d12 = nullptr; +#ifdef RD_ENABLED + if (rendering_device) { + memdelete(rendering_device); + rendering_device = nullptr; } - if (context_d3d12) { - memdelete(context_d3d12); - context_d3d12 = nullptr; + if (rendering_context) { + memdelete(rendering_context); + rendering_context = nullptr; } #endif diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 1e61462e95..e66c533da5 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -52,14 +52,8 @@ #include "drivers/xaudio2/audio_driver_xaudio2.h" #endif -#if defined(VULKAN_ENABLED) -#include "vulkan_context_win.h" - -#include "drivers/vulkan/rendering_device_vulkan.h" -#endif - -#if defined(D3D12_ENABLED) -#include "drivers/d3d12/rendering_device_d3d12.h" +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" #endif #if defined(GLES3_ENABLED) @@ -293,6 +287,7 @@ class DisplayServerWindows : public DisplayServer { // UXTheme API static bool dark_title_available; + static bool use_legacy_dark_mode_before_20H1; static bool ux_theme_available; static ShouldAppsUseDarkModePtr ShouldAppsUseDarkMode; static GetImmersiveColorFromColorSetExPtr GetImmersiveColorFromColorSetEx; @@ -346,14 +341,9 @@ class DisplayServerWindows : public DisplayServer { GLManagerNative_Windows *gl_manager_native = nullptr; #endif -#if defined(VULKAN_ENABLED) - VulkanContextWindows *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - -#if defined(D3D12_ENABLED) - D3D12Context *context_d3d12 = nullptr; - RenderingDeviceD3D12 *rendering_device_d3d12 = nullptr; +#if defined(RD_ENABLED) + RenderingContextDriver *rendering_context = nullptr; + RenderingDevice *rendering_device = nullptr; #endif RBMap<int, Vector2> touch_state; @@ -457,6 +447,13 @@ class DisplayServerWindows : public DisplayServer { WNDPROC user_proc = nullptr; + struct IndicatorData { + Callable callback; + }; + + IndicatorID indicator_id_counter = 0; + HashMap<IndicatorID, IndicatorData> indicators; + void _send_window_event(const WindowData &wd, WindowEvent p_event); void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); @@ -501,6 +498,8 @@ class DisplayServerWindows : public DisplayServer { LRESULT _handle_early_window_message(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); Point2i _get_screens_origin() const; + 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, bool p_options_in_cb); + public: LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT MouseProc(int code, WPARAM wParam, LPARAM lParam); @@ -525,6 +524,7 @@ public: virtual Color get_accent_color() 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; virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; @@ -615,6 +615,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; @@ -660,6 +662,12 @@ public: virtual void set_native_icon(const String &p_filename) override; virtual void set_icon(const Ref<Image> &p_icon) override; + virtual IndicatorID create_status_indicator(const Ref<Image> &p_icon, const String &p_tooltip, const Callable &p_callback) override; + virtual void status_indicator_set_icon(IndicatorID p_id, const Ref<Image> &p_icon) override; + virtual void status_indicator_set_tooltip(IndicatorID p_id, const String &p_tooltip) override; + virtual void status_indicator_set_callback(IndicatorID p_id, const Callable &p_callback) override; + virtual void delete_status_indicator(IndicatorID p_id) 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 Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Error &r_error); diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 418f38c127..6cdd370bfe 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -37,9 +37,9 @@ #include "core/io/image_loader.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/windows/godot.natvis b/platform/windows/godot.natvis index 36b0919185..d049ed9a3d 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -2,14 +2,46 @@ <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> <Type Name="Vector<*>"> <Expand> - <Item Name="[size]">_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Item> + <Item Name="[size]">_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Item> <ArrayItems> - <Size>_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Size> - <ValuePointer>_cowdata._ptr</ValuePointer> + <Size>_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Size> + <ValuePointer>($T1 *) _cowdata._ptr</ValuePointer> </ArrayItems> </Expand> </Type> + <Type Name="Array"> + <Expand> + <Item Name="[size]">_p->array._cowdata._ptr ? (((const unsigned long long *)(_p->array._cowdata._ptr))[-1]) : 0</Item> + <ArrayItems> + <Size>_p->array._cowdata._ptr ? (((const unsigned long long *)(_p->array._cowdata._ptr))[-1]) : 0</Size> + <ValuePointer>(Variant *) _p->array._cowdata._ptr</ValuePointer> + </ArrayItems> + </Expand> + </Type> + + <Type Name="TypedArray<*>"> + <Expand> + <Item Name="[size]"> _p->array._cowdata._ptr ? (((const unsigned long long *)(_p->array._cowdata._ptr))[-1]) : 0</Item> + <ArrayItems> + <Size>_p->array._cowdata._ptr ? (((const unsigned long long *)(_p->array._cowdata._ptr))[-1]) : 0</Size> + <ValuePointer >(Variant *) _p->array._cowdata._ptr</ValuePointer> + </ArrayItems> + </Expand> + </Type> + + <Type Name="Dictionary"> + <Expand> + <Item Name="[size]">_p && _p->variant_map.head_element ? _p->variant_map.num_elements : 0</Item> + <LinkedListItems> + <Size>_p && _p->variant_map.head_element ? _p->variant_map.num_elements : 0</Size> + <HeadPointer>_p ? _p->variant_map.head_element : nullptr</HeadPointer> + <NextPointer>next</NextPointer> + <ValueNode Name="[{data.key}]">(*this),view(MapHelper)</ValueNode> + </LinkedListItems> + </Expand> + </Type> + <Type Name="LocalVector<*>"> <Expand> <Item Name="[size]">count</Item> @@ -32,23 +64,85 @@ </Expand> </Type> - <Type Name="HashMap<*,*>"> + <Type Name="NodePath"> + <DisplayString Condition="!data">[empty]</DisplayString> + <DisplayString Condition="!!data">{{[absolute] = {data->absolute} [path] = {data->path,view(NodePathHelper)} [subpath] = {data->subpath,view(NodePathHelper)}}}</DisplayString> + <Expand> + <Item Name="[path]">data->path,view(NodePathHelper)</Item> + <Item Name="[subpath]">data->subpath,view(NodePathHelper)</Item> + <Item Name="[absolute]">data->absolute</Item> + </Expand> + </Type> + + <Type Name="Vector<StringName>" IncludeView="NodePathHelper"> + <Expand> + <ArrayItems> + <Size>_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Size> + <ValuePointer>((StringName *)_cowdata._ptr),view(NodePathHelper)</ValuePointer> + </ArrayItems> + </Expand> + </Type> + + <Type Name="StringName" IncludeView="NodePathHelper"> + <DisplayString Condition="_data && _data->cname">{_data->cname,s8b}</DisplayString> + <DisplayString Condition="_data && !_data->cname">{_data->name,s32b}</DisplayString> + <DisplayString Condition="!_data">[empty]</DisplayString> + <StringView Condition="_data && _data->cname">_data->cname,s8b</StringView> + <StringView Condition="_data && !_data->cname">_data->name,s32b</StringView> + </Type> + + <Type Name="HashMapElement<*,*>"> + <DisplayString>{{Key = {($T1 *) &data.key} Value = {($T2 *) &data.value}}}</DisplayString> + <Expand> + <Item Name="[key]">($T1 *) &data.key</Item> + <Item Name="[value]">($T2 *) &data.value</Item> + </Expand> + </Type> + + <!-- elements displayed by index --> + <Type Name="HashMap<*,*,*,*,*>" Priority="Medium"> + <Expand> + <Item Name="[size]">head_element ? num_elements : 0</Item> + <LinkedListItems> + <Size>head_element ? num_elements : 0</Size> + <HeadPointer>head_element</HeadPointer> + <NextPointer>next</NextPointer> + <ValueNode>(*this)</ValueNode> + </LinkedListItems> + </Expand> + </Type> + + <!-- elements by key:value --> + <!-- show elements by index by specifying "ShowElementsByIndex"--> + <Type Name="HashMap<*,*,*,*,*>" ExcludeView="ShowElementsByIndex" Priority="MediumHigh"> <Expand> - <Item Name="[size]">num_elements</Item> + <Item Name="[size]">head_element ? num_elements : 0</Item> <LinkedListItems> - <Size>num_elements</Size> + <Size>head_element ? num_elements : 0</Size> <HeadPointer>head_element</HeadPointer> <NextPointer>next</NextPointer> - <ValueNode>data</ValueNode> + <ValueNode Name="[{data.key}]">(*this),view(MapHelper)</ValueNode> </LinkedListItems> </Expand> </Type> + <Type Name="KeyValue<*,*>" IncludeView="MapHelper"> + <DisplayString>{value}</DisplayString> + </Type> + + <Type Name="HashMapElement<*,*>" IncludeView="MapHelper"> + <DisplayString>{data.value}</DisplayString> + <Expand> + <Item Name="[key]" >($T1 *) &data.key</Item> + <Item Name="[value]">($T2 *) &data.value</Item> + </Expand> + </Type> + <Type Name="VMap<*,*>"> <Expand> - <Item Condition="_cowdata._ptr" Name="[size]">*(reinterpret_cast<int*>(_cowdata._ptr) - 1)</Item> + <Item Condition="_cowdata._ptr" Name="[size]">*(reinterpret_cast<long long*>(_cowdata._ptr) - 1)</Item> <ArrayItems Condition="_cowdata._ptr"> - <Size>*(reinterpret_cast<int*>(_cowdata._ptr) - 1)</Size> + <Size>*(reinterpret_cast<long long*>(_cowdata._ptr) - 1)</Size> <ValuePointer>reinterpret_cast<VMap<$T1,$T2>::Pair*>(_cowdata._ptr)</ValuePointer> </ArrayItems> </Expand> @@ -58,11 +152,6 @@ <DisplayString Condition="dynamic_cast<CallableCustomMethodPointerBase*>(key.custom)">{dynamic_cast<CallableCustomMethodPointerBase*>(key.custom)->text}</DisplayString> </Type> - <!-- requires PR 64364 - <Type Name="GDScriptThreadContext"> - <DisplayString Condition="_is_main == true">main thread {_debug_thread_id}</DisplayString> - </Type> - --> <Type Name="Variant"> <DisplayString Condition="type == Variant::NIL">nil</DisplayString> @@ -82,22 +171,22 @@ <DisplayString Condition="type == Variant::PLANE">{*(Plane *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::QUATERNION">{*(Quaternion *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::COLOR">{*(Color *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::STRING_NAME">{*(StringName *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::NODE_PATH">{*(NodePath *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::RID">{*(::RID *)_data._mem}</DisplayString> - <DisplayString Condition="type == Variant::OBJECT">{*(Object *)_data._mem}</DisplayString> + <DisplayString Condition="type == Variant::OBJECT">{*(*reinterpret_cast<ObjData*>(&_data._mem[0])).obj}</DisplayString> <DisplayString Condition="type == Variant::DICTIONARY">{*(Dictionary *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::ARRAY">{*(Array *)_data._mem}</DisplayString> <DisplayString Condition="type == Variant::PACKED_BYTE_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<unsigned char>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_INT32_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<int>*>(_data.packed_array)->array}</DisplayString> - <!-- broken, will show incorrect data - <DisplayString Condition="type == Variant::PACKED_INT64_ARRAY">{*(PackedInt64Array *)_data._mem}</DisplayString> - --> + <DisplayString Condition="type == Variant::PACKED_INT64_ARRAY">{*reinterpret_cast<PackedInt64Array *>(&_data.packed_array[1])}</DisplayString> <DisplayString Condition="type == Variant::PACKED_FLOAT32_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<float>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_FLOAT64_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<double>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_STRING_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<String>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_VECTOR2_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<Vector2>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_VECTOR3_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<Vector3>*>(_data.packed_array)->array}</DisplayString> <DisplayString Condition="type == Variant::PACKED_COLOR_ARRAY">{reinterpret_cast<const Variant::PackedArrayRef<Color>*>(_data.packed_array)->array}</DisplayString> + <DisplayString Condition="type < 0 || type >= Variant::VARIANT_MAX">[INVALID]</DisplayString> <StringView Condition="type == Variant::STRING && ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,s32</StringView> @@ -116,20 +205,22 @@ <Item Name="[value]" Condition="type == Variant::PLANE">*(Plane *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::QUATERNION">*(Quaternion *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::COLOR">*(Color *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::STRING_NAME">*(StringName *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::NODE_PATH">*(NodePath *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::RID">*(::RID *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::OBJECT">*(Object *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::OBJECT">*(*reinterpret_cast<ObjData*>(&_data._mem[0])).obj</Item> <Item Name="[value]" Condition="type == Variant::DICTIONARY">*(Dictionary *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::ARRAY">*(Array *)_data._mem</Item> <Item Name="[value]" Condition="type == Variant::PACKED_BYTE_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<unsigned char>*>(_data.packed_array)->array</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_INT32_ARRAY">*(PackedInt32Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_INT64_ARRAY">*(PackedInt64Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT32_ARRAY">*(PackedFloat32Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT64_ARRAY">*(PackedFloat64Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_STRING_ARRAY">*(PackedStringArray *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR2_ARRAY">*(PackedVector2Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR3_ARRAY">*(PackedVector3Array *)_data._mem</Item> - <Item Name="[value]" Condition="type == Variant::PACKED_COLOR_ARRAY">*(PackedColorArray *)_data._mem</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_INT32_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<int>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_INT64_ARRAY">*reinterpret_cast<PackedInt64Array *>(&_data.packed_array[1])</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT32_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<float>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT64_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<double>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_STRING_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<String>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR2_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<Vector2>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR3_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<Vector3>*>(_data.packed_array)->array</Item> + <Item Name="[value]" Condition="type == Variant::PACKED_COLOR_ARRAY">reinterpret_cast<const Variant::PackedArrayRef<Color>*>(_data.packed_array)->array</Item> + </Expand> </Type> @@ -148,10 +239,10 @@ </Type> <Type Name="StringName"> - <DisplayString Condition="_data && _data->cname">{_data->cname}</DisplayString> + <DisplayString Condition="_data && _data->cname">{_data->cname,na}</DisplayString> <DisplayString Condition="_data && !_data->cname">{_data->name,s32}</DisplayString> <DisplayString Condition="!_data">[empty]</DisplayString> - <StringView Condition="_data && _data->cname">_data->cname</StringView> + <StringView Condition="_data && _data->cname">_data->cname,na</StringView> <StringView Condition="_data && !_data->cname">_data->name,s32</StringView> </Type> diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc index 0593c8b069..8187c0c936 100644 --- a/platform/windows/godot_res.rc +++ b/platform/windows/godot_res.rc @@ -1,16 +1,12 @@ #include "core/version.h" -#ifndef _STR -#define _STR(m_x) #m_x -#define _MKSTR(m_x) _STR(m_x) -#endif GODOT_ICON ICON platform/windows/godot.ico 1 VERSIONINFO -FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -FILEOS 4 -FILETYPE 1 +FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +FILEOS 4 +FILETYPE 1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -21,7 +17,7 @@ BEGIN VALUE "FileVersion", VERSION_NUMBER VALUE "ProductName", VERSION_NAME VALUE "Licence", "MIT" - VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors" + VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors" VALUE "Info", "https://godotengine.org" VALUE "ProductVersion", VERSION_FULL_BUILD END diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc index 9dd29afe51..27ad26cbc5 100644 --- a/platform/windows/godot_res_wrap.rc +++ b/platform/windows/godot_res_wrap.rc @@ -1,16 +1,12 @@ #include "core/version.h" -#ifndef _STR -#define _STR(m_x) #m_x -#define _MKSTR(m_x) _STR(m_x) -#endif GODOT_ICON ICON platform/windows/godot_console.ico 1 VERSIONINFO -FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 -FILEOS 4 -FILETYPE 1 +FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 +FILEOS 4 +FILETYPE 1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -21,7 +17,7 @@ BEGIN VALUE "FileVersion", VERSION_NUMBER VALUE "ProductName", VERSION_NAME " (Console)" VALUE "Licence", "MIT" - VALUE "LegalCopyright", "Copyright (c) 2007-" _MKSTR(VERSION_YEAR) " Juan Linietsky, Ariel Manzur and contributors" + VALUE "LegalCopyright", "(c) 2007-present Juan Linietsky, Ariel Manzur and Godot Engine contributors" VALUE "Info", "https://godotengine.org" VALUE "ProductVersion", VERSION_FULL_BUILD END diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 8d63b1747e..f86ecd87fb 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -36,7 +36,7 @@ #include <stdio.h> // For export templates, add a section; the exporter will patch it to enclose -// the data appended to the executable (bundled PCK) +// the data appended to the executable (bundled PCK). #ifndef TOOLS_ENABLED #if defined _MSC_VER #pragma section("pck", read) @@ -45,7 +45,7 @@ __declspec(allocate("pck")) static char dummy[8] = { 0 }; // Dummy function to prevent LTO from discarding "pck" section. extern "C" char *__cdecl pck_section_dummy_call() { return &dummy[0]; -}; +} #if defined _AMD64_ #pragma comment(linker, "/include:pck_section_dummy_call") #elif defined _X86_ diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index b376854c0c..20905d0fe9 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map; HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv; HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext; +HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map; void KeyMappingWindows::initialize() { // VK_LBUTTON (0x01) @@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() { scansym_map_ext[0x6C] = Key::LAUNCHMAIL; scansym_map_ext[0x6D] = Key::LAUNCHMEDIA; scansym_map_ext[0x78] = Key::MEDIARECORD; + + // Scancode to physical location map. + // Shift. + location_map[0x2A] = KeyLocation::LEFT; + location_map[0x36] = KeyLocation::RIGHT; + // Meta. + location_map[0x5B] = KeyLocation::LEFT; + location_map[0x5C] = KeyLocation::RIGHT; + // Ctrl and Alt must be handled differently. } Key KeyMappingWindows::get_keysym(unsigned int p_code) { @@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) { p_code == VK_RIGHT || p_code == VK_DOWN; } + +KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) { + // Right- ctrl and alt have the same scancode as left, but are in the extended keys. + const Key *key = scansym_map.getptr(p_code); + if (key && (*key == Key::CTRL || *key == Key::ALT)) { + return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT; + } + const KeyLocation *location = location_map.getptr(p_code); + if (location) { + return *location; + } + return KeyLocation::UNSPECIFIED; +} diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index a98aa7ed68..e6f184a2cc 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -47,6 +47,7 @@ public: static unsigned int get_scancode(Key p_keycode); static Key get_scansym(unsigned int p_code, bool p_extended); static bool is_extended_key(unsigned int p_code); + static KeyLocation get_location(unsigned int p_code, bool p_extended); }; #endif // KEY_MAPPING_WINDOWS_H diff --git a/platform/windows/msvs.py b/platform/windows/msvs.py new file mode 100644 index 0000000000..2d5ebe811a --- /dev/null +++ b/platform/windows/msvs.py @@ -0,0 +1,20 @@ +import methods + + +# Tuples with the name of the arch that will be used in VS, mapped to our internal arch names. +# For Windows platforms, Win32 is what VS wants. For other platforms, it can be different. +def get_platforms(): + return [("Win32", "x86_32"), ("x64", "x86_64")] + + +def get_configurations(): + return ["editor", "template_debug", "template_release"] + + +def get_build_prefix(env): + batch_file = methods.find_visual_c_batch_file(env) + return [ + "set "plat=$(PlatformTarget)"", + "(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))", + f"call "{batch_file}" !plat!", + ] diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 1d3b80e21e..6ad616da85 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -360,6 +360,8 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han path = get_executable_path().get_base_dir().path_join(p_path.get_file()); } + ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND); + typedef DLL_DIRECTORY_COOKIE(WINAPI * PAddDllDirectory)(PCWSTR); typedef BOOL(WINAPI * PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE); @@ -1183,7 +1185,7 @@ Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_na if (FAILED(hr)) { continue; } - String fpath = String::utf16((const char16_t *)&file_path[0]); + String fpath = String::utf16((const char16_t *)&file_path[0]).replace("\\", "/"); WIN32_FIND_DATAW d; HANDLE fnd = FindFirstFileW((LPCWSTR)&file_path[0], &d); @@ -1262,7 +1264,7 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight, if (FAILED(hr)) { continue; } - String fpath = String::utf16((const char16_t *)&file_path[0]); + String fpath = String::utf16((const char16_t *)&file_path[0]).replace("\\", "/"); WIN32_FIND_DATAW d; HANDLE fnd = FindFirstFileW((LPCWSTR)&file_path[0], &d); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 02f062f2de..056696ae2f 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -46,14 +46,8 @@ #include "drivers/xaudio2/audio_driver_xaudio2.h" #endif -#if defined(VULKAN_ENABLED) -#include "vulkan_context_win.h" - -#include "drivers/vulkan/rendering_device_vulkan.h" -#endif - -#if defined(D3D12_ENABLED) -#include "drivers/d3d12/rendering_device_d3d12.h" +#if defined(RD_ENABLED) +#include "servers/rendering/rendering_device.h" #endif #include <io.h> diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index 51652fa814..652dc06acf 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -10,19 +10,21 @@ from platform_methods import subprocess_main def make_debug_mingw(target, source, env): - mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) - if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) - else: - os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) - if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0])) - else: - os.system("strip --strip-debug --strip-unneeded {0}".format(target[0])) - if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) - else: - os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) + # Force separate debug symbols if executable size is larger than 1.9 GB. + if env["separate_debug_symbols"] or os.stat(target[0]).st_size >= 2040109465: + mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) + else: + os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0])) + if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0])) + else: + os.system("strip --strip-debug --strip-unneeded {0}".format(target[0])) + if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): + os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) + else: + os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0])) if __name__ == "__main__": diff --git a/platform/windows/vulkan_context_win.cpp b/platform/windows/rendering_context_driver_vulkan_windows.cpp index a60055dbec..f968ffc1d7 100644 --- a/platform/windows/vulkan_context_win.cpp +++ b/platform/windows/rendering_context_driver_vulkan_windows.cpp @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_win.cpp */ +/* rendering_context_driver_vulkan_windows.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -30,7 +30,9 @@ #if defined(WINDOWS_ENABLED) && defined(VULKAN_ENABLED) -#include "vulkan_context_win.h" +#include "core/os/os.h" + +#include "rendering_context_driver_vulkan_windows.h" #ifdef USE_VOLK #include <volk.h> @@ -38,31 +40,36 @@ #include <vulkan/vulkan.h> #endif -const char *VulkanContextWindows::_get_platform_surface_extension() const { +const char *RenderingContextDriverVulkanWindows::_get_platform_surface_extension() const { return VK_KHR_WIN32_SURFACE_EXTENSION_NAME; } -Error VulkanContextWindows::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, HWND p_window, HINSTANCE p_instance, int p_width, int p_height) { - VkWin32SurfaceCreateInfoKHR createInfo; - createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; - createInfo.pNext = nullptr; - createInfo.flags = 0; - createInfo.hinstance = p_instance; - createInfo.hwnd = p_window; - VkSurfaceKHR surface; - VkResult err = vkCreateWin32SurfaceKHR(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); -} - -VulkanContextWindows::VulkanContextWindows() { +RenderingContextDriverVulkanWindows::RenderingContextDriverVulkanWindows() { // Workaround for Vulkan not working on setups with AMD integrated graphics + NVIDIA dedicated GPU (GH-57708). // This prevents using AMD integrated graphics with Vulkan entirely, but it allows the engine to start // even on outdated/broken driver setups. OS::get_singleton()->set_environment("DISABLE_LAYER_AMD_SWITCHABLE_GRAPHICS_1", "1"); } -VulkanContextWindows::~VulkanContextWindows() { +RenderingContextDriverVulkanWindows::~RenderingContextDriverVulkanWindows() { + // Does nothing. +} + +RenderingContextDriver::SurfaceID RenderingContextDriverVulkanWindows::surface_create(const void *p_platform_data) { + const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); + + VkWin32SurfaceCreateInfoKHR create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + create_info.hinstance = wpd->instance; + create_info.hwnd = wpd->window; + + VkSurfaceKHR vk_surface = VK_NULL_HANDLE; + VkResult err = vkCreateWin32SurfaceKHR(instance_get(), &create_info, nullptr, &vk_surface); + ERR_FAIL_COND_V(err != VK_SUCCESS, SurfaceID()); + + Surface *surface = memnew(Surface); + surface->vk_surface = vk_surface; + return SurfaceID(surface); } #endif // WINDOWS_ENABLED && VULKAN_ENABLED diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/rendering_context_driver_vulkan_windows.h index 01ae2031e7..34546c9ea6 100644 --- a/platform/windows/vulkan_context_win.h +++ b/platform/windows/rendering_context_driver_vulkan_windows.h @@ -1,5 +1,5 @@ /**************************************************************************/ -/* vulkan_context_win.h */ +/* rendering_context_driver_vulkan_windows.h */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,26 +28,33 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#ifndef VULKAN_CONTEXT_WIN_H -#define VULKAN_CONTEXT_WIN_H +#ifndef RENDERING_CONTEXT_DRIVER_VULKAN_WINDOWS_H +#define RENDERING_CONTEXT_DRIVER_VULKAN_WINDOWS_H #ifdef VULKAN_ENABLED -#include "drivers/vulkan/vulkan_context.h" +#include "drivers/vulkan/rendering_context_driver_vulkan.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> -class VulkanContextWindows : public VulkanContext { - virtual const char *_get_platform_surface_extension() const; +class RenderingContextDriverVulkanWindows : public RenderingContextDriverVulkan { +private: + const char *_get_platform_surface_extension() const override final; + +protected: + SurfaceID surface_create(const void *p_platform_data) override final; public: - Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, HWND p_window, HINSTANCE p_instance, int p_width, int p_height); + struct WindowPlatformData { + HWND window; + HINSTANCE instance; + }; - VulkanContextWindows(); - ~VulkanContextWindows(); + RenderingContextDriverVulkanWindows(); + ~RenderingContextDriverVulkanWindows() override final; }; #endif // VULKAN_ENABLED -#endif // VULKAN_CONTEXT_WIN_H +#endif // RENDERING_CONTEXT_DRIVER_VULKAN_WINDOWS_H |