summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/android_input_handler.cpp1
-rw-r--r--platform/android/android_keys_utils.cpp9
-rw-r--r--platform/android/android_keys_utils.h20
-rw-r--r--platform/android/export/export.cpp2
-rw-r--r--platform/android/export/export_plugin.cpp64
-rw-r--r--platform/android/export/export_plugin.h2
-rw-r--r--platform/android/java/app/AndroidManifest.xml1
-rw-r--r--platform/android/java/app/assetPacks/installTime/build.gradle4
-rw-r--r--platform/android/java/app/build.gradle43
-rw-r--r--platform/android/java/app/config.gradle23
-rw-r--r--platform/android/java/app/settings.gradle4
-rw-r--r--platform/android/java/build.gradle101
-rw-r--r--platform/android/java/editor/build.gradle17
-rw-r--r--platform/android/java/editor/src/main/AndroidManifest.xml1
-rw-r--r--platform/android/java/gradle/wrapper/gradle-wrapper.properties3
-rw-r--r--platform/android/java/lib/AndroidManifest.xml1
-rw-r--r--platform/android/java/lib/build.gradle8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotGestureHandler.kt20
-rw-r--r--platform/android/java/nativeSrcsConfigs/AndroidManifest.xml2
-rw-r--r--platform/android/java/nativeSrcsConfigs/build.gradle2
-rw-r--r--platform/android/java/settings.gradle5
-rw-r--r--platform/ios/display_server_ios.h2
-rw-r--r--platform/ios/display_server_ios.mm3
-rw-r--r--platform/ios/export/export_plugin.cpp368
-rw-r--r--platform/ios/export/export_plugin.h11
-rw-r--r--platform/ios/key_mapping_ios.h1
-rw-r--r--platform/ios/key_mapping_ios.mm20
-rw-r--r--platform/ios/keyboard_input_view.mm8
-rw-r--r--platform/ios/view_controller.mm10
-rw-r--r--platform/linuxbsd/SCsub3
-rw-r--r--platform/linuxbsd/detect.py44
-rw-r--r--platform/linuxbsd/export/export.cpp2
-rw-r--r--platform/linuxbsd/export/export_plugin.cpp2
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp113
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h8
-rw-r--r--platform/linuxbsd/os_linuxbsd.cpp16
-rw-r--r--platform/linuxbsd/os_linuxbsd.h2
-rw-r--r--platform/linuxbsd/wayland/SCsub208
-rw-r--r--platform/linuxbsd/wayland/detect_prime_egl.cpp231
-rw-r--r--platform/linuxbsd/wayland/detect_prime_egl.h65
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.cpp1404
-rw-r--r--platform/linuxbsd/wayland/display_server_wayland.h295
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c453
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h175
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c607
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h231
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c89
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h42
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c67
-rw-r--r--platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h34
-rw-r--r--platform/linuxbsd/wayland/egl_manager_wayland.cpp66
-rw-r--r--platform/linuxbsd/wayland/egl_manager_wayland.h53
-rw-r--r--platform/linuxbsd/wayland/key_mapping_xkb.cpp411
-rw-r--r--platform/linuxbsd/wayland/key_mapping_xkb.h65
-rw-r--r--platform/linuxbsd/wayland/vulkan_context_wayland.cpp59
-rw-r--r--platform/linuxbsd/wayland/vulkan_context_wayland.h52
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.cpp4037
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.h943
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp72
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h5
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.cpp22
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.h2
-rw-r--r--platform/macos/SCsub1
-rw-r--r--platform/macos/detect.py41
-rw-r--r--platform/macos/display_server_macos.h6
-rw-r--r--platform/macos/display_server_macos.mm275
-rw-r--r--platform/macos/export/export_plugin.cpp2
-rw-r--r--platform/macos/godot_content_view.mm29
-rw-r--r--platform/macos/godot_main_macos.mm14
-rw-r--r--platform/macos/godot_open_save_delegate.h66
-rw-r--r--platform/macos/godot_open_save_delegate.mm250
-rw-r--r--platform/macos/key_mapping_macos.h1
-rw-r--r--platform/macos/key_mapping_macos.mm24
-rw-r--r--platform/macos/os_macos.mm24
-rw-r--r--platform/web/.eslintrc.html.js2
-rw-r--r--platform/web/SCsub4
-rw-r--r--platform/web/audio_driver_web.cpp52
-rw-r--r--platform/web/audio_driver_web.h51
-rw-r--r--platform/web/detect.py24
-rw-r--r--platform/web/display_server_web.cpp3
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/doc_classes/EditorExportPlatformWeb.xml4
-rw-r--r--platform/web/dom_keys.inc21
-rw-r--r--platform/web/emscripten_helpers.py41
-rw-r--r--platform/web/export/export_plugin.cpp28
-rw-r--r--platform/web/export/export_plugin.h5
-rw-r--r--platform/web/js/engine/features.js22
-rw-r--r--platform/web/js/libs/audio.worklet.js4
-rw-r--r--platform/windows/SCsub30
-rw-r--r--platform/windows/detect.py89
-rw-r--r--platform/windows/display_server_windows.cpp313
-rw-r--r--platform/windows/display_server_windows.h6
-rw-r--r--platform/windows/export/export_plugin.cpp2
-rw-r--r--platform/windows/godot.natvis149
-rw-r--r--platform/windows/godot_res.rc14
-rw-r--r--platform/windows/godot_res_wrap.rc14
-rw-r--r--platform/windows/key_mapping_windows.cpp23
-rw-r--r--platform/windows/key_mapping_windows.h1
-rw-r--r--platform/windows/msvs.py20
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 = &registry->wayland_thread->main_window;
+
+ if (registry->wp_viewporter) {
+ wp_viewporter_destroy(registry->wp_viewporter);
+ registry->wp_viewporter = nullptr;
+ }
+
+ if (ws->wp_viewport) {
+ wp_viewport_destroy(ws->wp_viewport);
+ ws->wp_viewport = nullptr;
+ }
+
+ registry->wp_viewporter_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_fractional_scale_manager_name) {
+ WindowState *ws = &registry->wayland_thread->main_window;
+
+ if (registry->wp_fractional_scale_manager) {
+ wp_fractional_scale_manager_v1_destroy(registry->wp_fractional_scale_manager);
+ registry->wp_fractional_scale_manager = nullptr;
+ }
+
+ if (ws->wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(ws->wp_fractional_scale);
+ ws->wp_fractional_scale = nullptr;
+ }
+
+ registry->wp_fractional_scale_manager_name = 0;
+ }
+
+ if (name == registry->xdg_decoration_manager_name) {
+ if (registry->xdg_decoration_manager) {
+ zxdg_decoration_manager_v1_destroy(registry->xdg_decoration_manager);
+ registry->xdg_decoration_manager = nullptr;
+ }
+
+ registry->xdg_decoration_manager_name = 0;
+
+ return;
+ }
+
+ if (name == registry->xdg_activation_name) {
+ if (registry->xdg_activation) {
+ xdg_activation_v1_destroy(registry->xdg_activation);
+ registry->xdg_activation = nullptr;
+ }
+
+ registry->xdg_activation_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_primary_selection_device_manager_name) {
+ if (registry->wp_primary_selection_device_manager) {
+ zwp_primary_selection_device_manager_v1_destroy(registry->wp_primary_selection_device_manager);
+ registry->wp_primary_selection_device_manager = nullptr;
+ }
+
+ registry->wp_primary_selection_device_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_primary_selection_device) {
+ zwp_primary_selection_device_v1_destroy(ss->wp_primary_selection_device);
+ ss->wp_primary_selection_device = nullptr;
+ }
+
+ if (ss->wp_primary_selection_source) {
+ zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
+ ss->wp_primary_selection_source = nullptr;
+ }
+
+ if (ss->wp_primary_selection_offer) {
+ memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
+ zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
+ ss->wp_primary_selection_offer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_relative_pointer_manager_name) {
+ if (registry->wp_relative_pointer_manager) {
+ zwp_relative_pointer_manager_v1_destroy(registry->wp_relative_pointer_manager);
+ registry->wp_relative_pointer_manager = nullptr;
+ }
+
+ registry->wp_relative_pointer_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_pointer_constraints_name) {
+ if (registry->wp_pointer_constraints) {
+ zwp_pointer_constraints_v1_destroy(registry->wp_pointer_constraints);
+ registry->wp_pointer_constraints = nullptr;
+ }
+
+ registry->wp_pointer_constraints_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ ss->wp_locked_pointer = nullptr;
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ ss->wp_confined_pointer = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_pointer_gestures_name) {
+ if (registry->wp_pointer_gestures) {
+ zwp_pointer_gestures_v1_destroy(registry->wp_pointer_gestures);
+ }
+
+ registry->wp_pointer_gestures = nullptr;
+ registry->wp_pointer_gestures_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_pointer_gesture_pinch) {
+ zwp_pointer_gesture_pinch_v1_destroy(ss->wp_pointer_gesture_pinch);
+ ss->wp_pointer_gesture_pinch = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (name == registry->wp_idle_inhibit_manager_name) {
+ if (registry->wp_idle_inhibit_manager) {
+ zwp_idle_inhibit_manager_v1_destroy(registry->wp_idle_inhibit_manager);
+ registry->wp_idle_inhibit_manager = nullptr;
+ }
+
+ registry->wp_idle_inhibit_manager_name = 0;
+
+ return;
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (name == registry->wp_tablet_manager_name) {
+ if (registry->wp_tablet_manager) {
+ zwp_tablet_manager_v2_destroy(registry->wp_tablet_manager);
+ registry->wp_tablet_manager = nullptr;
+ }
+
+ registry->wp_tablet_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
+
+ while (it) {
+ zwp_tablet_tool_v2_destroy(it->get());
+ ss->tablet_tools.erase(it);
+
+ it = it->next();
+ }
+ }
+
+ return;
+ }
+#endif
+
+ {
+ // Iterate through all of the seats to find if any got removed.
+ List<struct wl_seat *>::Element *it = registry->wl_seats.front();
+ while (it) {
+ struct wl_seat *wl_seat = it->get();
+
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_seat_name == name) {
+ if (wl_seat) {
+ wl_seat_destroy(wl_seat);
+ }
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (ss->wp_tablet_seat) {
+ zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
+
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+ }
+
+ // Let's destroy all tools.
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+
+ memdelete(ss);
+ registry->wl_seats.erase(it);
+#endif
+ return;
+ }
+
+ it = it->next();
+ }
+ }
+
+ {
+ // Iterate through all of the outputs to find if any got removed.
+ // FIXME: This is a very bruteforce approach.
+ List<struct wl_output *>::Element *it = registry->wl_outputs.front();
+ while (it) {
+ // Iterate through all of the screens to find if any got removed.
+ struct wl_output *wl_output = it->get();
+ ERR_FAIL_NULL(wl_output);
+
+ ScreenState *ss = wl_output_get_screen_state(wl_output);
+
+ if (ss->wl_output_name == name) {
+ registry->wl_outputs.erase(it);
+
+ memdelete(ss);
+ wl_output_destroy(wl_output);
+
+ return;
+ }
+
+ it = it->next();
+ }
+ }
+}
+
+void WaylandThread::_wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
+ if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
+ // This won't have the right data bound to it. Not worth it and would probably
+ // just break everything.
+ return;
+ }
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Window entered output %x.\n", (size_t)wl_output));
+
+ ws->wl_outputs.insert(wl_output);
+
+ // Workaround for buffer scaling as there's no guaranteed way of knowing the
+ // preferred scale.
+ // TODO: Skip this branch for newer `wl_surface`s once we add support for
+ // `wl_surface::preferred_buffer_scale`
+ if (ws->preferred_fractional_scale == 0) {
+ window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
+ }
+}
+
+void WaylandThread::_frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data) {
+ wl_callback_destroy(wl_callback);
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+ ERR_FAIL_NULL(ws->wayland_thread);
+ ERR_FAIL_NULL(ws->wl_surface);
+
+ ws->wayland_thread->set_frame();
+
+ ws->frame_callback = wl_surface_frame(ws->wl_surface),
+ wl_callback_add_listener(ws->frame_callback, &frame_wl_callback_listener, ws);
+ wl_surface_commit(ws->wl_surface);
+
+ if (ws->wl_surface && ws->buffer_scale_changed) {
+ // NOTE: We're only now setting the buffer scale as the idea is to get this
+ // data committed together with the new frame, all by the rendering driver.
+ // This is important because we might otherwise set an invalid combination of
+ // buffer size and scale (e.g. odd size and 2x scale). We're pretty much
+ // guaranteed to get a proper buffer in the next render loop as the rescaling
+ // method also informs the engine of a "window rect change", triggering
+ // rendering if needed.
+ wl_surface_set_buffer_scale(ws->wl_surface, window_state_get_preferred_buffer_scale(ws));
+ }
+
+ // NOTE: Remember to set here also other buffer-dependent states (e.g. opaque
+ // region) if used, to be as close as possible to an atomic surface update.
+ // Ideally we'd only have one surface commit, but it's not really doable given
+ // the current state of things.
+}
+
+void WaylandThread::_wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) {
+ if (!wl_output || !wl_proxy_is_godot((struct wl_proxy *)wl_output)) {
+ // This won't have the right data bound to it. Not worth it and would probably
+ // just break everything.
+ return;
+ }
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->wl_outputs.erase(wl_output);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Window left output %x.\n", (size_t)wl_output));
+}
+
+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 = &registry;
+ ws.wayland_thread = this;
+
+ ws.rect.size.width = p_width;
+ ws.rect.size.height = p_height;
+
+ ws.wl_surface = wl_compositor_create_surface(registry.wl_compositor);
+ wl_proxy_tag_godot((struct wl_proxy *)ws.wl_surface);
+ wl_surface_add_listener(ws.wl_surface, &wl_surface_listener, &ws);
+
+ if (registry.wp_viewporter) {
+ ws.wp_viewport = wp_viewporter_get_viewport(registry.wp_viewporter, ws.wl_surface);
+
+ if (registry.wp_fractional_scale_manager) {
+ ws.wp_fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(registry.wp_fractional_scale_manager, ws.wl_surface);
+ wp_fractional_scale_v1_add_listener(ws.wp_fractional_scale, &wp_fractional_scale_listener, &ws);
+ }
+ }
+
+ bool decorated = false;
+
+#ifdef LIBDECOR_ENABLED
+ if (!decorated && libdecor_context) {
+ ws.libdecor_frame = libdecor_decorate(libdecor_context, ws.wl_surface, (struct libdecor_frame_interface *)&libdecor_frame_interface, &ws);
+ libdecor_frame_map(ws.libdecor_frame);
+
+ decorated = true;
+ }
+#endif
+
+ if (!decorated) {
+ // libdecor has failed loading or is disabled, we shall handle xdg_toplevel
+ // creation and decoration ourselves (and by decorating for now I just mean
+ // asking for SSDs and hoping for the best).
+ ws.xdg_surface = xdg_wm_base_get_xdg_surface(registry.xdg_wm_base, ws.wl_surface);
+ xdg_surface_add_listener(ws.xdg_surface, &xdg_surface_listener, &ws);
+
+ ws.xdg_toplevel = xdg_surface_get_toplevel(ws.xdg_surface);
+ xdg_toplevel_add_listener(ws.xdg_toplevel, &xdg_toplevel_listener, &ws);
+
+ if (registry.xdg_decoration_manager) {
+ ws.xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(registry.xdg_decoration_manager, ws.xdg_toplevel);
+ zxdg_toplevel_decoration_v1_add_listener(ws.xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, &ws);
+
+ decorated = true;
+ }
+ }
+
+ ws.frame_callback = wl_surface_frame(ws.wl_surface);
+ wl_callback_add_listener(ws.frame_callback, &frame_wl_callback_listener, &ws);
+
+ // NOTE: This commit is only called once to start the whole frame callback
+ // "loop".
+ wl_surface_commit(ws.wl_surface);
+
+ if (registry.wl_exporter) {
+ ws.xdg_exported = zxdg_exporter_v1_export(registry.wl_exporter, ws.wl_surface);
+ zxdg_exported_v1_add_listener(ws.xdg_exported, &xdg_exported_listener, &ws);
+ }
+
+ // Wait for the surface to be configured before continuing.
+ wl_display_roundtrip(wl_display);
+}
+
+struct wl_surface *WaylandThread::window_get_wl_surface(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.wl_surface;
+}
+
+void WaylandThread::window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ Vector2i logical_max_size = p_size / window_state_get_scale_factor(&ws);
+
+ if (ws.wl_surface && ws.xdg_toplevel) {
+ xdg_toplevel_set_max_size(ws.xdg_toplevel, logical_max_size.width, logical_max_size.height);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_max_content_size(ws.libdecor_frame, logical_max_size.width, logical_max_size.height);
+ }
+
+ // FIXME: I'm not sure whether we have to commit the surface for this to apply.
+#endif
+}
+
+void WaylandThread::window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ Size2i logical_min_size = p_size / window_state_get_scale_factor(&ws);
+
+ if (ws.wl_surface && ws.xdg_toplevel) {
+ xdg_toplevel_set_min_size(ws.xdg_toplevel, logical_min_size.width, logical_min_size.height);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_min_content_size(ws.libdecor_frame, logical_min_size.width, logical_min_size.height);
+ }
+
+ // FIXME: I'm not sure whether we have to commit the surface for this to apply.
+#endif
+}
+
+bool WaylandThread::window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ switch (p_window_mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Looks like it's guaranteed.
+ return true;
+ };
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_MINIMIZE);
+ }
+#endif // LIBDECOR_ENABLED
+
+ return ws.can_minimize;
+ };
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ // NOTE: libdecor doesn't seem to have a maximize capability query?
+ // The fact that there's a fullscreen one makes me suspicious.
+ return ws.can_maximize;
+ };
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN: {
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ return libdecor_frame_has_capability(ws.libdecor_frame, LIBDECOR_ACTION_FULLSCREEN);
+ }
+#endif // LIBDECOR_ENABLED
+
+ return ws.can_fullscreen;
+ };
+
+ case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
+ // I'm not really sure but from what I can find Wayland doesn't really have
+ // the concept of exclusive fullscreen.
+ // TODO: Discuss whether to fallback to regular fullscreen or not.
+ return false;
+ };
+ }
+
+ return false;
+}
+
+void WaylandThread::window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (ws.mode == p_window_mode) {
+ return;
+ }
+
+ // Don't waste time with hidden windows and whatnot. Behave like it worked.
+#ifdef LIBDECOR_ENABLED
+ if ((!ws.wl_surface || !ws.xdg_toplevel) && !ws.libdecor_frame) {
+#else
+ if (!ws.wl_surface || !ws.xdg_toplevel) {
+#endif // LIBDECOR_ENABLED
+ ws.mode = p_window_mode;
+ return;
+ }
+
+ // Return back to a windowed state so that we can apply what the user asked.
+ switch (ws.mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Do nothing.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+ // We can't do much according to the xdg_shell protocol. I have no idea
+ // whether this implies that we should return or who knows what. For now
+ // we'll do nothing.
+ // TODO: Test this properly.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ // Try to unmaximize. This isn't garaunteed to work actually, so we'll have
+ // to check whether something changed.
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_unset_maximized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_unset_maximized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN:
+ case DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN: {
+ // Same thing as above, unset fullscreen and check later if it worked.
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_unset_fullscreen(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_unset_fullscreen(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+ }
+
+ // Wait for a configure event and hope that something changed.
+ wl_display_roundtrip(wl_display);
+
+ if (ws.mode != DisplayServer::WINDOW_MODE_WINDOWED) {
+ // The compositor refused our "normalization" request. It'd be useless or
+ // unpredictable to attempt setting a new state. We're done.
+ return;
+ }
+
+ // Ask the compositor to set the state indicated by the new mode.
+ switch (p_window_mode) {
+ case DisplayServer::WINDOW_MODE_WINDOWED: {
+ // Do nothing. We're already windowed.
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MINIMIZED: {
+ if (!window_can_set_mode(p_window_id, p_window_mode)) {
+ // Minimization is special (read below). Better not mess with it if the
+ // compositor explicitly announces that it doesn't support it.
+ break;
+ }
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_minimized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_minimized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ // We have no way to actually detect this state, so we'll have to report it
+ // manually to the engine (hoping that it worked). In the worst case it'll
+ // get reset by the next configure event.
+ ws.mode = DisplayServer::WINDOW_MODE_MINIMIZED;
+ } break;
+
+ case DisplayServer::WINDOW_MODE_MAXIMIZED: {
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_maximized(ws.xdg_toplevel);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_maximized(ws.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ case DisplayServer::WINDOW_MODE_FULLSCREEN: {
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_fullscreen(ws.xdg_toplevel, nullptr);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_fullscreen(ws.libdecor_frame, nullptr);
+ }
+#endif // LIBDECOR_ENABLED
+ } break;
+
+ default: {
+ } break;
+ }
+}
+
+void WaylandThread::window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (ws.xdg_toplevel_decoration) {
+ if (p_borderless) {
+ // We implement borderless windows by simply asking the compositor to let
+ // us handle decorations (we don't).
+ zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
+ } else {
+ zxdg_toplevel_decoration_v1_set_mode(ws.xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
+ }
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ bool visible_current = libdecor_frame_is_visible(ws.libdecor_frame);
+ bool visible_target = !p_borderless;
+
+ // NOTE: We have to do this otherwise we trip on a libdecor bug where it's
+ // possible to destroy the frame more than once, by setting the visibility
+ // to false multiple times and thus crashing.
+ if (visible_current != visible_target) {
+ print_verbose(vformat("Setting libdecor frame visibility to %d", visible_target));
+ libdecor_frame_set_visibility(ws.libdecor_frame, visible_target);
+ }
+ }
+#endif // LIBDECOR_ENABLED
+}
+
+void WaylandThread::window_set_title(DisplayServer::WindowID p_window_id, const String &p_title) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_title(ws.libdecor_frame, p_title.utf8());
+ }
+#endif // LIBDECOR_ENABLE
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_title(ws.xdg_toplevel, p_title.utf8());
+ }
+}
+
+void WaylandThread::window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+#ifdef LIBDECOR_ENABLED
+ if (ws.libdecor_frame) {
+ libdecor_frame_set_app_id(ws.libdecor_frame, p_app_id.utf8());
+ return;
+ }
+#endif // LIBDECOR_ENABLED
+
+ if (ws.xdg_toplevel) {
+ xdg_toplevel_set_app_id(ws.xdg_toplevel, p_app_id.utf8());
+ return;
+ }
+}
+
+DisplayServer::WindowMode WaylandThread::window_get_mode(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.mode;
+}
+
+void WaylandThread::window_request_attention(DisplayServer::WindowID p_window_id) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (registry.xdg_activation) {
+ // Window attention requests are done through the XDG activation protocol.
+ xdg_activation_token_v1 *xdg_activation_token = xdg_activation_v1_get_activation_token(registry.xdg_activation);
+ xdg_activation_token_v1_add_listener(xdg_activation_token, &xdg_activation_token_listener, &ws);
+ xdg_activation_token_v1_commit(xdg_activation_token);
+ }
+}
+
+void WaylandThread::window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable) {
+ // TODO: Use window IDs for multiwindow support.
+ WindowState &ws = main_window;
+
+ if (p_enable) {
+ if (ws.registry->wp_idle_inhibit_manager && !ws.wp_idle_inhibitor) {
+ ERR_FAIL_NULL(ws.wl_surface);
+ ws.wp_idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(ws.registry->wp_idle_inhibit_manager, ws.wl_surface);
+ }
+ } else {
+ if (ws.wp_idle_inhibitor) {
+ zwp_idle_inhibitor_v1_destroy(ws.wp_idle_inhibitor);
+ ws.wp_idle_inhibitor = nullptr;
+ }
+ }
+}
+
+bool WaylandThread::window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const {
+ // TODO: Use window IDs for multiwindow support.
+ const WindowState &ws = main_window;
+
+ return ws.wp_idle_inhibitor != nullptr;
+}
+
+WaylandThread::ScreenData WaylandThread::screen_get_data(int p_screen) const {
+ ERR_FAIL_INDEX_V(p_screen, registry.wl_outputs.size(), ScreenData());
+
+ return wl_output_get_screen_state(registry.wl_outputs[p_screen])->data;
+}
+
+int WaylandThread::get_screen_count() const {
+ return registry.wl_outputs.size();
+}
+
+DisplayServer::WindowID WaylandThread::pointer_get_pointed_window_id() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+
+ if (ws) {
+ return ws->id;
+ }
+ }
+
+ return DisplayServer::INVALID_WINDOW_ID;
+}
+
+void WaylandThread::pointer_set_constraint(PointerConstraint p_constraint) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ seat_state_unlock_pointer(ss);
+
+ if (p_constraint == PointerConstraint::LOCKED) {
+ seat_state_lock_pointer(ss);
+ } else if (p_constraint == PointerConstraint::CONFINED) {
+ seat_state_confine_pointer(ss);
+ }
+ }
+
+ pointer_constraint = p_constraint;
+}
+
+void WaylandThread::pointer_set_hint(const Point2i &p_hint) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (!ss) {
+ return;
+ }
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+
+ int hint_x = 0;
+ int hint_y = 0;
+
+ if (ws) {
+ // NOTE: It looks like it's not really recommended to convert from
+ // "godot-space" to "wayland-space" and in general I received mixed feelings
+ // discussing about this. I'm not really sure about the maths behind this but,
+ // oh well, we're setting a cursor hint. ¯\_(ツ)_/¯
+ // See: https://oftc.irclog.whitequark.org/wayland/2023-08-23#1692756914-1692816818
+ hint_x = round(p_hint.x / window_state_get_scale_factor(ws));
+ hint_y = round(p_hint.y / window_state_get_scale_factor(ws));
+ }
+
+ if (ss) {
+ seat_state_set_hint(ss, hint_x, hint_y);
+ }
+}
+
+WaylandThread::PointerConstraint WaylandThread::pointer_get_constraint() const {
+ return pointer_constraint;
+}
+
+BitField<MouseButtonMask> WaylandThread::pointer_get_button_mask() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ return ss->pointer_data.pressed_button_mask;
+ }
+
+ return BitField<MouseButtonMask>();
+}
+
+Error WaylandThread::init() {
+#ifdef SOWRAP_ENABLED
+#ifdef DEBUG_ENABLED
+ int dylibloader_verbose = 1;
+#else
+ int dylibloader_verbose = 0;
+#endif // DEBUG_ENABLED
+
+ if (initialize_wayland_client(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland client library.");
+ return ERR_CANT_CREATE;
+ }
+
+ if (initialize_wayland_cursor(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland cursor library.");
+ return ERR_CANT_CREATE;
+ }
+
+ if (initialize_xkbcommon(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the XKBcommon library.");
+ return ERR_CANT_CREATE;
+ }
+#endif // SOWRAP_ENABLED
+
+ KeyMappingXKB::initialize();
+
+ wl_display = wl_display_connect(nullptr);
+ ERR_FAIL_NULL_V_MSG(wl_display, ERR_CANT_CREATE, "Can't connect to a Wayland display.");
+
+ thread_data.wl_display = wl_display;
+
+ events_thread.start(_poll_events_thread, &thread_data);
+
+ wl_registry = wl_display_get_registry(wl_display);
+
+ ERR_FAIL_NULL_V_MSG(wl_registry, ERR_UNAVAILABLE, "Can't obtain the Wayland registry global.");
+
+ registry.wayland_thread = this;
+
+ wl_registry_add_listener(wl_registry, &wl_registry_listener, &registry);
+
+ // Wait for registry to get notified from the compositor.
+ wl_display_roundtrip(wl_display);
+
+ ERR_FAIL_NULL_V_MSG(registry.wl_shm, ERR_UNAVAILABLE, "Can't obtain the Wayland shared memory global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_compositor, ERR_UNAVAILABLE, "Can't obtain the Wayland compositor global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_subcompositor, ERR_UNAVAILABLE, "Can't obtain the Wayland subcompositor global.");
+ ERR_FAIL_NULL_V_MSG(registry.wl_data_device_manager, ERR_UNAVAILABLE, "Can't obtain the Wayland data device manager global.");
+ ERR_FAIL_NULL_V_MSG(registry.wp_pointer_constraints, ERR_UNAVAILABLE, "Can't obtain the Wayland pointer constraints global.");
+ ERR_FAIL_NULL_V_MSG(registry.xdg_wm_base, ERR_UNAVAILABLE, "Can't obtain the Wayland XDG shell global.");
+
+ if (!registry.xdg_decoration_manager) {
+#ifdef LIBDECOR_ENABLED
+ WARN_PRINT("Can't obtain the XDG decoration manager. Libdecor will be used for drawing CSDs, if available.");
+#else
+ WARN_PRINT("Can't obtain the XDG decoration manager. Decorations won't show up.");
+#endif // LIBDECOR_ENABLED
+ }
+
+ if (!registry.xdg_activation) {
+ WARN_PRINT("Can't obtain the XDG activation global. Attention requesting won't work!");
+ }
+
+#ifndef DBUS_ENABLED
+ if (!registry.wp_idle_inhibit_manager) {
+ WARN_PRINT("Can't obtain the idle inhibition manager. The screen might turn off even after calling screen_set_keep_on()!");
+ }
+#endif // DBUS_ENABLED
+
+ // Wait for seat capabilities.
+ wl_display_roundtrip(wl_display);
+
+#ifdef LIBDECOR_ENABLED
+ bool libdecor_found = true;
+
+#ifdef SOWRAP_ENABLED
+ if (initialize_libdecor(dylibloader_verbose) != 0) {
+ libdecor_found = false;
+ }
+#endif // SOWRAP_ENABLED
+
+ if (libdecor_found) {
+ libdecor_context = libdecor_new(wl_display, (struct libdecor_interface *)&libdecor_interface);
+ } else {
+ print_verbose("libdecor not found. Client-side decorations disabled.");
+ }
+#endif // LIBDECOR_ENABLED
+
+ cursor_theme_name = OS::get_singleton()->get_environment("XCURSOR_THEME");
+
+ unscaled_cursor_size = OS::get_singleton()->get_environment("XCURSOR_SIZE").to_int();
+ if (unscaled_cursor_size <= 0) {
+ print_verbose("Detected invalid cursor size preference, defaulting to 24.");
+ unscaled_cursor_size = 24;
+ }
+
+ // NOTE: The scale is useful here as it might've been updated by _update_scale.
+ bool cursor_theme_loaded = _load_cursor_theme(unscaled_cursor_size * cursor_scale);
+
+ if (!cursor_theme_loaded) {
+ return ERR_CANT_CREATE;
+ }
+
+ // Update the cursor.
+ cursor_set_shape(DisplayServer::CURSOR_ARROW);
+
+ initialized = true;
+ return OK;
+}
+
+void WaylandThread::cursor_hide() {
+ current_wl_cursor = nullptr;
+ current_custom_cursor = nullptr;
+
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ ERR_FAIL_NULL(ss);
+ seat_state_update_cursor(ss);
+}
+
+void WaylandThread::cursor_set_shape(DisplayServer::CursorShape p_cursor_shape) {
+ if (!wl_cursors[p_cursor_shape]) {
+ return;
+ }
+
+ // The point of this method is make the current cursor a "plain" shape and, as
+ // the custom cursor overrides what gets set, we have to clear it too.
+ current_custom_cursor = nullptr;
+
+ current_wl_cursor = wl_cursors[p_cursor_shape];
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ seat_state_update_cursor(ss);
+ }
+
+ last_cursor_shape = p_cursor_shape;
+}
+
+void WaylandThread::cursor_set_custom_shape(DisplayServer::CursorShape p_cursor_shape) {
+ ERR_FAIL_COND(!custom_cursors.has(p_cursor_shape));
+
+ current_custom_cursor = &custom_cursors[p_cursor_shape];
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ seat_state_update_cursor(ss);
+ }
+
+ last_cursor_shape = p_cursor_shape;
+}
+
+void WaylandThread::cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot) {
+ ERR_FAIL_COND(!p_image.is_valid());
+
+ Size2i image_size = p_image->get_size();
+
+ // NOTE: The stride is the width of the image in bytes.
+ unsigned int image_stride = image_size.width * 4;
+ unsigned int data_size = image_stride * image_size.height;
+
+ // We need a shared memory object file descriptor in order to create a
+ // wl_buffer through wl_shm.
+ int fd = WaylandThread::_allocate_shm_file(data_size);
+ ERR_FAIL_COND(fd == -1);
+
+ CustomCursor &cursor = custom_cursors[p_cursor_shape];
+ cursor.hotspot = p_hotspot;
+
+ if (cursor.buffer_data) {
+ // Clean up the old buffer data.
+ munmap(cursor.buffer_data, cursor.buffer_data_size);
+ }
+
+ cursor.buffer_data = (uint32_t *)mmap(NULL, data_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (cursor.wl_buffer) {
+ // Clean up the old Wayland buffer.
+ wl_buffer_destroy(cursor.wl_buffer);
+ }
+
+ // Create the Wayland buffer.
+ struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(registry.wl_shm, fd, image_size.height * data_size);
+ // TODO: Make sure that WL_SHM_FORMAT_ARGB8888 format is supported. It
+ // technically isn't garaunteed to be supported, but I think that'd be a
+ // pretty unlikely thing to stumble upon.
+ cursor.wl_buffer = wl_shm_pool_create_buffer(wl_shm_pool, 0, image_size.width, image_size.height, image_stride, WL_SHM_FORMAT_ARGB8888);
+ wl_shm_pool_destroy(wl_shm_pool);
+
+ // Fill the cursor buffer with the image data.
+ for (unsigned int index = 0; index < (unsigned int)(image_size.width * image_size.height); index++) {
+ int row_index = floor(index / image_size.width);
+ int column_index = (index % int(image_size.width));
+
+ cursor.buffer_data[index] = p_image->get_pixel(column_index, row_index).to_argb32();
+
+ // Wayland buffers, unless specified, require associated alpha, so we'll just
+ // associate the alpha in-place.
+ uint8_t *pixel_data = (uint8_t *)&cursor.buffer_data[index];
+ pixel_data[0] = pixel_data[0] * pixel_data[3] / 255;
+ pixel_data[1] = pixel_data[1] * pixel_data[3] / 255;
+ pixel_data[2] = pixel_data[2] * pixel_data[3] / 255;
+ }
+}
+
+void WaylandThread::cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape) {
+ if (custom_cursors.has(p_cursor_shape)) {
+ CustomCursor cursor = custom_cursors[p_cursor_shape];
+ custom_cursors.erase(p_cursor_shape);
+
+ current_custom_cursor = nullptr;
+
+ if (cursor.wl_buffer) {
+ wl_buffer_destroy(cursor.wl_buffer);
+ }
+
+ if (cursor.buffer_data) {
+ munmap(cursor.buffer_data, cursor.buffer_data_size);
+ }
+ }
+}
+
+int WaylandThread::keyboard_get_layout_count() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_keymap) {
+ return xkb_keymap_num_layouts(ss->xkb_keymap);
+ }
+
+ return 0;
+}
+
+int WaylandThread::keyboard_get_current_layout_index() const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ return ss->current_layout_index;
+ }
+
+ return 0;
+}
+
+void WaylandThread::keyboard_set_current_layout_index(int p_index) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ ss->current_layout_index = p_index;
+ }
+}
+
+String WaylandThread::keyboard_get_layout_name(int p_index) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_keymap) {
+ String ret;
+ ret.parse_utf8(xkb_keymap_layout_get_name(ss->xkb_keymap, p_index));
+
+ return ret;
+ }
+
+ return "";
+}
+
+Key WaylandThread::keyboard_get_key_from_physical(Key p_key) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss && ss->xkb_state) {
+ xkb_keycode_t xkb_keycode = KeyMappingXKB::get_xkb_keycode(p_key);
+ return KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(ss->xkb_state, xkb_keycode));
+ }
+
+ return Key::NONE;
+}
+
+void WaylandThread::keyboard_echo_keys() {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss) {
+ seat_state_echo_keys(ss);
+ }
+}
+
+void WaylandThread::selection_set_text(const String &p_text) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (registry.wl_data_device_manager == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, wl_data_device_manager global not available.");
+ }
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, current seat not set.");
+ return;
+ }
+
+ if (ss->wl_data_device == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set selection, seat doesn't have wl_data_device.");
+ }
+
+ ss->selection_data = p_text.to_utf8_buffer();
+
+ if (ss->wl_data_source_selection == nullptr) {
+ ss->wl_data_source_selection = wl_data_device_manager_create_data_source(registry.wl_data_device_manager);
+ wl_data_source_add_listener(ss->wl_data_source_selection, &wl_data_source_listener, ss);
+ wl_data_source_offer(ss->wl_data_source_selection, "text/plain;charset=utf-8");
+ wl_data_source_offer(ss->wl_data_source_selection, "text/plain");
+ }
+
+ // TODO: Implement a good way of getting the latest serial from the user.
+ wl_data_device_set_selection(ss->wl_data_device, ss->wl_data_source_selection, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
+
+ // Wait for the message to get to the server before continuing, otherwise the
+ // clipboard update might come with a delay.
+ wl_display_roundtrip(wl_display);
+}
+
+bool WaylandThread::selection_has_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return false;
+ }
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
+ if (!os) {
+ return false;
+ }
+
+ return os->mime_types.has(p_mime);
+}
+
+Vector<uint8_t> WaylandThread::selection_get_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return Vector<uint8_t>();
+ }
+
+ if (ss->wl_data_source_selection) {
+ // We have a source so the stuff we're pasting is ours. We'll have to pass the
+ // data directly or we'd stall waiting for Godot (ourselves) to send us the
+ // data :P
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_selection);
+ ERR_FAIL_NULL_V(os, Vector<uint8_t>());
+
+ if (os->mime_types.has(p_mime)) {
+ // All righty, we're offering this type. Let's just return the data as is.
+ return ss->selection_data;
+ }
+
+ // ... we don't offer that type. Oh well.
+ return Vector<uint8_t>();
+ }
+
+ return _wl_data_offer_read(wl_display, p_mime.utf8(), ss->wl_data_offer_selection);
+}
+
+bool WaylandThread::primary_has_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get selection, current seat not set.");
+ return false;
+ }
+
+ OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
+ if (!os) {
+ return false;
+ }
+
+ return os->mime_types.has(p_mime);
+}
+
+Vector<uint8_t> WaylandThread::primary_get_mime(const String &p_mime) const {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't get primary, current seat not set.");
+ return Vector<uint8_t>();
+ }
+
+ if (ss->wp_primary_selection_source) {
+ // We have a source so the stuff we're pasting is ours. We'll have to pass the
+ // data directly or we'd stall waiting for Godot (ourselves) to send us the
+ // data :P
+
+ OfferState *os = wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer);
+ ERR_FAIL_NULL_V(os, Vector<uint8_t>());
+
+ if (os->mime_types.has(p_mime)) {
+ // All righty, we're offering this type. Let's just return the data as is.
+ return ss->selection_data;
+ }
+
+ // ... we don't offer that type. Oh well.
+ return Vector<uint8_t>();
+ }
+
+ return _wp_primary_selection_offer_read(wl_display, p_mime.utf8(), ss->wp_primary_selection_offer);
+}
+
+void WaylandThread::primary_set_text(const String &p_text) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat_current);
+
+ if (registry.wp_primary_selection_device_manager == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, protocol not available");
+ return;
+ }
+
+ if (ss == nullptr) {
+ DEBUG_LOG_WAYLAND_THREAD("Couldn't set primary, current seat not set.");
+ return;
+ }
+
+ ss->primary_data = p_text.to_utf8_buffer();
+
+ if (ss->wp_primary_selection_source == nullptr) {
+ ss->wp_primary_selection_source = zwp_primary_selection_device_manager_v1_create_source(registry.wp_primary_selection_device_manager);
+ zwp_primary_selection_source_v1_add_listener(ss->wp_primary_selection_source, &wp_primary_selection_source_listener, ss);
+ zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain;charset=utf-8");
+ zwp_primary_selection_source_v1_offer(ss->wp_primary_selection_source, "text/plain");
+ }
+
+ // TODO: Implement a good way of getting the latest serial from the user.
+ zwp_primary_selection_device_v1_set_selection(ss->wp_primary_selection_device, ss->wp_primary_selection_source, MAX(ss->pointer_data.button_serial, ss->last_key_pressed_serial));
+
+ // Wait for the message to get to the server before continuing, otherwise the
+ // clipboard update might come with a delay.
+ wl_display_roundtrip(wl_display);
+}
+
+void WaylandThread::set_frame() {
+ frame = true;
+}
+
+bool WaylandThread::get_reset_frame() {
+ bool old_frame = frame;
+ frame = false;
+
+ return old_frame;
+}
+
+void WaylandThread::destroy() {
+ if (!initialized) {
+ return;
+ }
+
+ if (wl_display && events_thread.is_started()) {
+ thread_data.thread_done.set();
+
+ // By sending a roundtrip message we're unblocking the polling thread so that
+ // it can realize that it's done and also handle every event that's left.
+ wl_display_roundtrip(wl_display);
+
+ events_thread.wait_to_finish();
+ }
+
+ if (main_window.wp_fractional_scale) {
+ wp_fractional_scale_v1_destroy(main_window.wp_fractional_scale);
+ }
+
+ if (main_window.wp_viewport) {
+ wp_viewport_destroy(main_window.wp_viewport);
+ }
+
+ if (main_window.frame_callback) {
+ wl_callback_destroy(main_window.frame_callback);
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (main_window.libdecor_frame) {
+ libdecor_frame_close(main_window.libdecor_frame);
+ }
+#endif // LIBDECOR_ENABLED
+
+ if (main_window.xdg_toplevel) {
+ xdg_toplevel_destroy(main_window.xdg_toplevel);
+ }
+
+ if (main_window.xdg_surface) {
+ xdg_surface_destroy(main_window.xdg_surface);
+ }
+
+ if (main_window.wl_surface) {
+ wl_surface_destroy(main_window.wl_surface);
+ }
+
+ for (struct wl_seat *wl_seat : registry.wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ wl_seat_destroy(wl_seat);
+
+ xkb_context_unref(ss->xkb_context);
+ xkb_state_unref(ss->xkb_state);
+ xkb_keymap_unref(ss->xkb_keymap);
+
+ if (ss->wl_keyboard) {
+ wl_keyboard_destroy(ss->wl_keyboard);
+ }
+
+ if (ss->keymap_buffer) {
+ munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
+ }
+
+ if (ss->wl_pointer) {
+ wl_pointer_destroy(ss->wl_pointer);
+ }
+
+ if (ss->cursor_frame_callback) {
+ // We don't need to set a null userdata for safety as the thread is done.
+ wl_callback_destroy(ss->cursor_frame_callback);
+ }
+
+ if (ss->cursor_surface) {
+ wl_surface_destroy(ss->cursor_surface);
+ }
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ }
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (ss->wp_tablet_seat) {
+ zwp_tablet_seat_v2_destroy(ss->wp_tablet_seat);
+ }
+#endif
+
+ for (struct zwp_tablet_tool_v2 *tool : ss->tablet_tools) {
+ zwp_tablet_tool_v2_destroy(tool);
+ }
+
+ memdelete(ss);
+ }
+
+ for (struct wl_output *wl_output : registry.wl_outputs) {
+ ERR_FAIL_NULL(wl_output);
+
+ memdelete(wl_output_get_screen_state(wl_output));
+ wl_output_destroy(wl_output);
+ }
+
+ if (wl_cursor_theme) {
+ wl_cursor_theme_destroy(wl_cursor_theme);
+ }
+
+ if (registry.wp_idle_inhibit_manager) {
+ zwp_idle_inhibit_manager_v1_destroy(registry.wp_idle_inhibit_manager);
+ }
+
+ if (registry.wp_pointer_constraints) {
+ zwp_pointer_constraints_v1_destroy(registry.wp_pointer_constraints);
+ }
+
+ if (registry.wp_pointer_gestures) {
+ zwp_pointer_gestures_v1_destroy(registry.wp_pointer_gestures);
+ }
+
+ if (registry.wp_relative_pointer_manager) {
+ zwp_relative_pointer_manager_v1_destroy(registry.wp_relative_pointer_manager);
+ }
+
+ if (registry.xdg_activation) {
+ xdg_activation_v1_destroy(registry.xdg_activation);
+ }
+
+ if (registry.xdg_decoration_manager) {
+ zxdg_decoration_manager_v1_destroy(registry.xdg_decoration_manager);
+ }
+
+ if (registry.wp_fractional_scale_manager) {
+ wp_fractional_scale_manager_v1_destroy(registry.wp_fractional_scale_manager);
+ }
+
+ if (registry.wp_viewporter) {
+ wp_viewporter_destroy(registry.wp_viewporter);
+ }
+
+ if (registry.xdg_wm_base) {
+ xdg_wm_base_destroy(registry.xdg_wm_base);
+ }
+
+ if (registry.wl_exporter) {
+ zxdg_exporter_v1_destroy(registry.wl_exporter);
+ }
+
+ if (registry.wl_shm) {
+ wl_shm_destroy(registry.wl_shm);
+ }
+
+ if (registry.wl_subcompositor) {
+ wl_subcompositor_destroy(registry.wl_subcompositor);
+ }
+
+ if (registry.wl_compositor) {
+ wl_compositor_destroy(registry.wl_compositor);
+ }
+
+ if (wl_registry) {
+ wl_registry_destroy(wl_registry);
+ }
+
+ if (wl_display) {
+ wl_display_disconnect(wl_display);
+ }
+}
+
+#endif // WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h
new file mode 100644
index 0000000000..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&lt;*&gt;">
<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&lt;*&gt;">
+ <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 &amp;&amp; _p->variant_map.head_element ? _p->variant_map.num_elements : 0</Item>
+ <LinkedListItems>
+ <Size>_p &amp;&amp; _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&lt;*&gt;">
<Expand>
<Item Name="[size]">count</Item>
@@ -32,23 +64,85 @@
</Expand>
</Type>
- <Type Name="HashMap&lt;*,*&gt;">
+ <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&lt;StringName&gt;" 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 &amp;&amp; _data->cname">{_data->cname,s8b}</DisplayString>
+ <DisplayString Condition="_data &amp;&amp; !_data->cname">{_data->name,s32b}</DisplayString>
+ <DisplayString Condition="!_data">[empty]</DisplayString>
+ <StringView Condition="_data &amp;&amp; _data->cname">_data->cname,s8b</StringView>
+ <StringView Condition="_data &amp;&amp; !_data->cname">_data->name,s32b</StringView>
+ </Type>
+
+ <Type Name="HashMapElement&lt;*,*&gt;">
+ <DisplayString>{{Key = {($T1 *) &amp;data.key} Value = {($T2 *) &amp;data.value}}}</DisplayString>
+ <Expand>
+ <Item Name="[key]">($T1 *) &amp;data.key</Item>
+ <Item Name="[value]">($T2 *) &amp;data.value</Item>
+ </Expand>
+ </Type>
+
+ <!-- elements displayed by index -->
+ <Type Name="HashMap&lt;*,*,*,*,*&gt;" 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&lt;*,*,*,*,*&gt;" 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&lt;*,*&gt;" IncludeView="MapHelper">
+ <DisplayString>{value}</DisplayString>
+ </Type>
+
+ <Type Name="HashMapElement&lt;*,*&gt;" IncludeView="MapHelper">
+ <DisplayString>{data.value}</DisplayString>
+ <Expand>
+ <Item Name="[key]" >($T1 *) &amp;data.key</Item>
+ <Item Name="[value]">($T2 *) &amp;data.value</Item>
+ </Expand>
+ </Type>
+
<Type Name="VMap&lt;*,*&gt;">
<Expand>
- <Item Condition="_cowdata._ptr" Name="[size]">*(reinterpret_cast&lt;int*&gt;(_cowdata._ptr) - 1)</Item>
+ <Item Condition="_cowdata._ptr" Name="[size]">*(reinterpret_cast&lt;long long*&gt;(_cowdata._ptr) - 1)</Item>
<ArrayItems Condition="_cowdata._ptr">
- <Size>*(reinterpret_cast&lt;int*&gt;(_cowdata._ptr) - 1)</Size>
+ <Size>*(reinterpret_cast&lt;long long*&gt;(_cowdata._ptr) - 1)</Size>
<ValuePointer>reinterpret_cast&lt;VMap&lt;$T1,$T2&gt;::Pair*&gt;(_cowdata._ptr)</ValuePointer>
</ArrayItems>
</Expand>
@@ -58,11 +152,6 @@
<DisplayString Condition="dynamic_cast&lt;CallableCustomMethodPointerBase*&gt;(key.custom)">{dynamic_cast&lt;CallableCustomMethodPointerBase*&gt;(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&lt;ObjData*&gt;(&amp;_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&lt;const Variant::PackedArrayRef&lt;unsigned char&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_INT32_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;int&gt;*&gt;(_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&lt;PackedInt64Array *&gt;(&amp;_data.packed_array[1])}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_FLOAT32_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;float&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_FLOAT64_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;double&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_STRING_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;String&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_VECTOR2_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector2&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_VECTOR3_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector3&gt;*&gt;(_data.packed_array)->array}</DisplayString>
<DisplayString Condition="type == Variant::PACKED_COLOR_ARRAY">{reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Color&gt;*&gt;(_data.packed_array)->array}</DisplayString>
+ <DisplayString Condition="type &lt; 0 || type >= Variant::VARIANT_MAX">[INVALID]</DisplayString>
<StringView Condition="type == Variant::STRING &amp;&amp; ((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&lt;ObjData*&gt;(&amp;_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&lt;const Variant::PackedArrayRef&lt;unsigned char&gt;*&gt;(_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&lt;const Variant::PackedArrayRef&lt;int&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_INT64_ARRAY">*reinterpret_cast&lt;PackedInt64Array *&gt;(&amp;_data.packed_array[1])</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT32_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;float&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_FLOAT64_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;double&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_STRING_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;String&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR2_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector2&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_VECTOR3_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Vector3&gt;*&gt;(_data.packed_array)->array</Item>
+ <Item Name="[value]" Condition="type == Variant::PACKED_COLOR_ARRAY">reinterpret_cast&lt;const Variant::PackedArrayRef&lt;Color&gt;*&gt;(_data.packed_array)->array</Item>
+
</Expand>
</Type>
@@ -148,10 +239,10 @@
</Type>
<Type Name="StringName">
- <DisplayString Condition="_data &amp;&amp; _data->cname">{_data->cname}</DisplayString>
+ <DisplayString Condition="_data &amp;&amp; _data->cname">{_data->cname,na}</DisplayString>
<DisplayString Condition="_data &amp;&amp; !_data->cname">{_data->name,s32}</DisplayString>
<DisplayString Condition="!_data">[empty]</DisplayString>
- <StringView Condition="_data &amp;&amp; _data->cname">_data->cname</StringView>
+ <StringView Condition="_data &amp;&amp; _data->cname">_data->cname,na</StringView>
<StringView Condition="_data &amp;&amp; !_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 &quot;plat=$(PlatformTarget)&quot;",
+ "(if &quot;$(PlatformTarget)&quot;==&quot;x64&quot; (set &quot;plat=x86_amd64&quot;))",
+ f"call &quot;{batch_file}&quot; !plat!",
+ ]