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