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/display_server_android.cpp9
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml5
-rw-r--r--platform/android/export/export_plugin.cpp2
-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.gradle34
-rw-r--r--platform/android/java/app/config.gradle21
-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.mm9
-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/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.cpp4049
-rw-r--r--platform/linuxbsd/wayland/wayland_thread.h949
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp24
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h1
-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/crash_handler_macos.mm3
-rw-r--r--platform/macos/detect.py3
-rw-r--r--platform/macos/display_server_macos.h4
-rw-r--r--platform/macos/display_server_macos.mm275
-rw-r--r--platform/macos/godot_application_delegate.mm4
-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/detect.py10
-rw-r--r--platform/web/display_server_web.cpp3
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/dom_keys.inc21
-rw-r--r--platform/web/export/export_plugin.cpp4
-rw-r--r--platform/web/export/export_plugin.h2
-rw-r--r--platform/web/js/engine/config.js2
-rw-r--r--platform/windows/SCsub14
-rw-r--r--platform/windows/detect.py89
-rw-r--r--platform/windows/display_server_windows.cpp312
-rw-r--r--platform/windows/display_server_windows.h4
-rw-r--r--platform/windows/godot.natvis18
-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
89 files changed, 11008 insertions, 404 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/display_server_android.cpp b/platform/android/display_server_android.cpp
index 340823baac..4a9915379a 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -538,9 +538,10 @@ void DisplayServerAndroid::reset_window() {
#endif
if (context_rd->window_create(MAIN_WINDOW_ID, last_vsync_mode, display_size.width, display_size.height, &wpd) != OK) {
+ ERR_PRINT(vformat("Failed to reset %s window.", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
- ERR_FAIL_MSG(vformat("Failed to reset %s window.", context_rd->get_api_name()));
+ return;
}
}
#endif
@@ -575,9 +576,10 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
if (context_rd) {
if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Failed to initialize %s context", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
- ERR_FAIL_MSG(vformat("Failed to initialize %s context", context_rd->get_api_name()));
+ return;
}
Size2i display_size = OS_Android::get_singleton()->get_display_size();
@@ -596,9 +598,10 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis
#endif
if (context_rd->window_create(MAIN_WINDOW_ID, p_vsync_mode, display_size.width, display_size.height, &wpd) != OK) {
+ ERR_PRINT(vformat("Failed to create %s window.", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
- ERR_FAIL_MSG(vformat("Failed to create %s window.", context_rd->get_api_name()));
+ return;
}
rendering_device = memnew(RenderingDevice);
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index dae968378b..a6f92158f9 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -119,7 +119,10 @@
If [code]true[/code], package signing is enabled.
</member>
<member name="package/unique_name" type="String" setter="" getter="">
- Unique application identifier in a reverse-DNS format, can only contain alphanumeric characters ([code]A-Z[/code], [code]a-z[/code], and [code]0-9[/code]), hyphens ([code]-[/code]), and periods ([code].[/code]).
+ Unique application identifier in a reverse-DNS format. The reverse DNS format should preferably match a domain name you control, but this is not strictly required. For instance, if you own [code]example.com[/code], your package unique name should preferably be of the form [code]com.example.mygame[/code]. This identifier can only contain lowercase alphanumeric characters ([code]a-z[/code], and [code]0-9[/code]), underscores ([code]_[/code]), and periods ([code].[/code]). Each component of the reverse DNS format must start with a letter: for instance, [code]com.example.8game[/code] is not valid.
+ If [code]$genname[/code] is present in the value, it will be replaced by the project name converted to lowercase. If there are invalid characters in the project name, they will be stripped. If all characters in the project name are stripped, [code]$genname[/code] is replaced by [code]noname[/code].
+ [b]Note:[/b] Changing the package name will cause the package to be considered as a new package, with its own installation and data paths. The new package won't be usable to update existing installations.
+ [b]Note:[/b] When publishing to Google Play, the package name must be [i]globally[/i] unique. This means no other apps published on Google Play must be using the same package name as yours. Otherwise, you'll be prevented from publishing your app on Google Play.
</member>
<member name="permissions/access_checkin_properties" type="bool" setter="" getter="">
Allows read/write access to the "properties" table in the checkin database. See [url=https://developer.android.com/reference/android/Manifest.permission#ACCESS_CHECKIN_PROPERTIES]ACCESS_CHECKIN_PROPERTIES[/url].
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 5e42d8a967..459f5a5983 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -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) {
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 8a543a8550..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 {
@@ -250,3 +240,17 @@ task validateJavaVersion {
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 bf091098b4..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',
+ 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..2f2878bfc0 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -87,17 +87,19 @@ DisplayServerIOS::DisplayServerIOS(const String &p_rendering_driver, WindowMode
if (context_rd) {
if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Failed to initialize %s context", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
- ERR_FAIL_MSG(vformat("Failed to initialize %s context", context_rd->get_api_name()));
+ return;
}
Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
if (context_rd->window_create(MAIN_WINDOW_ID, p_vsync_mode, size.width, size.height, &wpd) != OK) {
+ ERR_PRINT(vformat("Failed to create %s window.", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
r_error = ERR_UNAVAILABLE;
- ERR_FAIL_MSG(vformat("Failed to create %s window.", context_rd->get_api_name()));
+ return;
}
rendering_device = memnew(RenderingDevice);
@@ -247,7 +249,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 +272,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/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 a5050c253c..94784f2da9 100644
--- a/platform/linuxbsd/detect.py
+++ b/platform/linuxbsd/detect.py
@@ -47,6 +47,8 @@ def get_opts():
BoolVariable("fontconfig", "Use fontconfig for system fonts support", True),
BoolVariable("udev", "Use udev for gamepad connection callbacks", True),
BoolVariable("x11", "Enable X11 display", True),
+ BoolVariable("wayland", "Enable Wayland display", True),
+ BoolVariable("libdecor", "Enable libdecor support", True),
BoolVariable("touch", "Enable touch events", True),
BoolVariable("execinfo", "Use libexecinfo on systems where glibc is not available", False),
]
@@ -204,6 +206,11 @@ def configure(env: "Environment"):
if env["use_sowrap"]:
env.Append(CPPDEFINES=["SOWRAP_ENABLED"])
+ if env["wayland"]:
+ if os.system("wayland-scanner -v 2>/dev/null") != 0:
+ print("wayland-scanner not found. Disabling Wayland support.")
+ env["wayland"] = False
+
if env["touch"]:
env.Append(CPPDEFINES=["TOUCH_ENABLED"])
@@ -364,9 +371,13 @@ def configure(env: "Environment"):
env.ParseConfig("pkg-config xkbcommon --cflags --libs")
env.Append(CPPDEFINES=["XKB_ENABLED"])
else:
- print(
- "Warning: libxkbcommon development libraries not found. Disabling dead key composition and key label support."
- )
+ if env["wayland"]:
+ print("Error: libxkbcommon development libraries required by Wayland not found. Aborting.")
+ sys.exit(255)
+ else:
+ print(
+ "Warning: libxkbcommon development libraries not found. Disabling dead key composition and key label support."
+ )
else:
env.Append(CPPDEFINES=["XKB_ENABLED"])
@@ -433,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/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..99b7349dbe
--- /dev/null
+++ b/platform/linuxbsd/wayland/SCsub
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+
+Import("env")
+
+# TODO: Add warning to headers and code about their autogenerated status.
+if env["use_sowrap"]:
+ # We have to implement separate builders for so wrappers as the
+ # autogenerated Wayland protocol wrapper must include them instead of the
+ # native libraries.
+
+ WAYLAND_BUILDERS_SOWRAP = {
+ "WAYLAND_API_HEADER": Builder(
+ action=Action(
+ "wayland-scanner -c client-header < ${SOURCE} | sed 's:wayland-client-core\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}",
+ 'Generating Wayland client header: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ "WAYLAND_API_CODE": Builder(
+ action=Action(
+ "wayland-scanner -c private-code < ${SOURCE} | sed 's:wayland-util\.h:../dynwrappers/wayland-client-core-so_wrap\.h:' > ${TARGET}",
+ 'Generating Wayland protocol marshaling code: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ }
+ env.Append(BUILDERS=WAYLAND_BUILDERS_SOWRAP)
+else:
+ WAYLAND_BUILDERS = {
+ "WAYLAND_API_HEADER": Builder(
+ action=Action(
+ "wayland-scanner -c client-header < ${SOURCE} > ${TARGET}",
+ 'Generating Wayland client header: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ "WAYLAND_API_CODE": Builder(
+ action=Action(
+ "wayland-scanner -c private-code < ${SOURCE} > ${TARGET}",
+ 'Generating Wayland protocol marshaling code: "${TARGET}"',
+ ),
+ single_source=True,
+ ),
+ }
+ env.Append(BUILDERS=WAYLAND_BUILDERS)
+
+env.WAYLAND_API_HEADER(target="protocol/wayland.gen.h", source="#thirdparty/wayland/protocol/wayland.xml")
+env.WAYLAND_API_CODE(target="protocol/wayland.gen.c", source="#thirdparty/wayland/protocol/wayland.xml")
+
+env.WAYLAND_API_HEADER(
+ target="protocol/viewporter.gen.h", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"
+)
+env.WAYLAND_API_CODE(
+ target="protocol/viewporter.gen.c", source="#thirdparty/wayland-protocols/stable/viewporter/viewporter.xml"
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/fractional_scale.gen.h",
+ source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml",
+)
+env.WAYLAND_API_CODE(
+ target="protocol/fractional_scale.gen.c",
+ source="#thirdparty/wayland-protocols/staging/fractional-scale/fractional-scale-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_shell.gen.h", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_shell.gen.c", source="#thirdparty/wayland-protocols/stable/xdg-shell/xdg-shell.xml"
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_decoration.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_decoration.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_activation.gen.h",
+ source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_activation.gen.c",
+ source="#thirdparty/wayland-protocols/staging/xdg-activation/xdg-activation-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/relative_pointer.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/relative_pointer.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/relative-pointer/relative-pointer-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/pointer_constraints.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/pointer_constraints.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/pointer_gestures.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/pointer_gestures.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/pointer-gestures/pointer-gestures-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/primary_selection.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/primary_selection.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/primary-selection/primary-selection-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/idle_inhibit.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/idle_inhibit.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/tablet.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/tablet.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/tablet/tablet-unstable-v2.xml",
+)
+
+env.WAYLAND_API_HEADER(
+ target="protocol/xdg_foreign.gen.h",
+ source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml",
+)
+
+env.WAYLAND_API_CODE(
+ target="protocol/xdg_foreign.gen.c",
+ source="#thirdparty/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml",
+)
+
+source_files = [
+ "protocol/wayland.gen.c",
+ "protocol/viewporter.gen.c",
+ "protocol/fractional_scale.gen.c",
+ "protocol/xdg_shell.gen.c",
+ "protocol/xdg_foreign.gen.c",
+ "protocol/xdg_decoration.gen.c",
+ "protocol/xdg_activation.gen.c",
+ "protocol/relative_pointer.gen.c",
+ "protocol/pointer_constraints.gen.c",
+ "protocol/pointer_gestures.gen.c",
+ "protocol/primary_selection.gen.c",
+ "protocol/idle_inhibit.gen.c",
+ "protocol/tablet.gen.c",
+ "display_server_wayland.cpp",
+ "wayland_thread.cpp",
+ "key_mapping_xkb.cpp",
+ "detect_prime_egl.cpp",
+]
+
+if env["use_sowrap"]:
+ source_files.append(
+ [
+ "dynwrappers/wayland-cursor-so_wrap.c",
+ "dynwrappers/wayland-client-core-so_wrap.c",
+ "dynwrappers/wayland-egl-core-so_wrap.c",
+ ]
+ )
+
+ if env["libdecor"]:
+ source_files.append("dynwrappers/libdecor-so_wrap.c")
+
+
+if env["vulkan"]:
+ source_files.append("vulkan_context_wayland.cpp")
+
+if env["opengl3"]:
+ source_files.append("egl_manager_wayland.cpp")
+
+objects = []
+
+for source_file in source_files:
+ objects.append(env.Object(source_file))
+
+Return("objects")
diff --git a/platform/linuxbsd/wayland/detect_prime_egl.cpp b/platform/linuxbsd/wayland/detect_prime_egl.cpp
new file mode 100644
index 0000000000..4bee32ae3a
--- /dev/null
+++ b/platform/linuxbsd/wayland/detect_prime_egl.cpp
@@ -0,0 +1,231 @@
+/**************************************************************************/
+/* detect_prime_egl.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifdef GLES3_ENABLED
+#ifdef EGL_ENABLED
+
+#include "detect_prime_egl.h"
+
+#include "core/string/print_string.h"
+#include "core/string/ustring.h"
+
+#include <stdlib.h>
+
+#ifdef GLAD_ENABLED
+#include "thirdparty/glad/glad/egl.h"
+#include "thirdparty/glad/glad/gl.h"
+#else
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/glcorearb.h>
+#endif // GLAD_ENABLED
+
+#include <cstring>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+// To prevent shadowing warnings.
+#undef glGetString
+
+// Runs inside a child. Exiting will not quit the engine.
+void DetectPrimeEGL::create_context() {
+#if defined(GLAD_ENABLED)
+ if (!gladLoaderLoadEGL(nullptr)) {
+ print_verbose("Unable to load EGL, GPU detection skipped.");
+ quick_exit(1);
+ }
+#endif
+
+ EGLDisplay egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ EGLConfig egl_config;
+ EGLContext egl_context = EGL_NO_CONTEXT;
+
+ eglInitialize(egl_display, NULL, NULL);
+
+#if defined(GLAD_ENABLED)
+ if (!gladLoaderLoadEGL(egl_display)) {
+ print_verbose("Unable to load EGL, GPU detection skipped.");
+ quick_exit(1);
+ }
+#endif
+
+ eglBindAPI(EGL_OPENGL_API);
+
+ EGLint attribs[] = {
+ EGL_RED_SIZE,
+ 1,
+ EGL_BLUE_SIZE,
+ 1,
+ EGL_GREEN_SIZE,
+ 1,
+ EGL_DEPTH_SIZE,
+ 24,
+ EGL_NONE,
+ };
+
+ EGLint config_count = 0;
+ eglChooseConfig(egl_display, attribs, &egl_config, 1, &config_count);
+
+ EGLint context_attribs[] = {
+ EGL_CONTEXT_MAJOR_VERSION, 3,
+ EGL_CONTEXT_MINOR_VERSION, 3,
+ EGL_NONE
+ };
+
+ egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs);
+ if (egl_context == EGL_NO_CONTEXT) {
+ print_verbose("Unable to create an EGL context, GPU detection skipped.");
+ quick_exit(1);
+ }
+
+ eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, egl_context);
+}
+
+int DetectPrimeEGL::detect_prime() {
+ pid_t p;
+ int priorities[4] = {};
+ String vendors[4];
+ String renderers[4];
+
+ for (int i = 0; i < 4; ++i) {
+ vendors[i] = "Unknown";
+ renderers[i] = "Unknown";
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ int fdset[2];
+
+ if (pipe(fdset) == -1) {
+ print_verbose("Failed to pipe(), using default GPU");
+ return 0;
+ }
+
+ // Fork so the driver initialization can crash without taking down the engine.
+ p = fork();
+
+ if (p > 0) {
+ // Main thread
+
+ int stat_loc = 0;
+ char string[201];
+ string[200] = '\0';
+
+ close(fdset[1]);
+
+ waitpid(p, &stat_loc, 0);
+
+ if (!stat_loc) {
+ // No need to do anything complicated here. Anything less than
+ // PIPE_BUF will be delivered in one read() call.
+ // Leave it 'Unknown' otherwise.
+ if (read(fdset[0], string, sizeof(string) - 1) > 0) {
+ vendors[i] = string;
+ renderers[i] = string + strlen(string) + 1;
+ }
+ }
+
+ close(fdset[0]);
+ } else {
+ // In child, exit() here will not quit the engine.
+
+ // Prevent false leak reports as we will not be properly
+ // cleaning up these processes, and fork() makes a copy
+ // of all globals.
+ CoreGlobals::leak_reporting_enabled = false;
+
+ char string[201];
+
+ close(fdset[0]);
+
+ setenv("DRI_PRIME", itos(i).utf8().ptr(), 1);
+
+ create_context();
+
+ PFNGLGETSTRINGPROC glGetString = (PFNGLGETSTRINGPROC)eglGetProcAddress("glGetString");
+ const char *vendor = (const char *)glGetString(GL_VENDOR);
+ const char *renderer = (const char *)glGetString(GL_RENDERER);
+
+ unsigned int vendor_len = strlen(vendor) + 1;
+ unsigned int renderer_len = strlen(renderer) + 1;
+
+ if (vendor_len + renderer_len >= sizeof(string)) {
+ renderer_len = 200 - vendor_len;
+ }
+
+ memcpy(&string, vendor, vendor_len);
+ memcpy(&string[vendor_len], renderer, renderer_len);
+
+ if (write(fdset[1], string, vendor_len + renderer_len) == -1) {
+ print_verbose("Couldn't write vendor/renderer string.");
+ }
+ close(fdset[1]);
+
+ // The function quick_exit() is used because exit() will call destructors on static objects copied by fork().
+ // These objects will be freed anyway when the process finishes execution.
+ quick_exit(0);
+ }
+ }
+
+ int preferred = 0;
+ int priority = 0;
+
+ if (vendors[0] == vendors[1]) {
+ print_verbose("Only one GPU found, using default.");
+ return 0;
+ }
+
+ for (int i = 3; i >= 0; --i) {
+ const Vendor *v = vendor_map;
+ while (v->glxvendor) {
+ if (v->glxvendor == vendors[i]) {
+ priorities[i] = v->priority;
+
+ if (v->priority >= priority) {
+ priority = v->priority;
+ preferred = i;
+ }
+ }
+ ++v;
+ }
+ }
+
+ print_verbose("Found renderers:");
+ for (int i = 0; i < 4; ++i) {
+ print_verbose("Renderer " + itos(i) + ": " + renderers[i] + " with priority: " + itos(priorities[i]));
+ }
+
+ print_verbose("Using renderer: " + renderers[preferred]);
+ return preferred;
+}
+
+#endif // EGL_ENABLED
+#endif // GLES3_ENABLED
diff --git a/platform/linuxbsd/wayland/detect_prime_egl.h b/platform/linuxbsd/wayland/detect_prime_egl.h
new file mode 100644
index 0000000000..26351b0dce
--- /dev/null
+++ b/platform/linuxbsd/wayland/detect_prime_egl.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* detect_prime_egl.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DETECT_PRIME_EGL_H
+#define DETECT_PRIME_EGL_H
+
+#ifdef GLES3_ENABLED
+#ifdef EGL_ENABLED
+
+class DetectPrimeEGL {
+private:
+ struct Vendor {
+ const char *glxvendor = nullptr;
+ int priority = 0;
+ };
+
+ static constexpr Vendor vendor_map[] = {
+ { "Advanced Micro Devices, Inc.", 30 },
+ { "AMD", 30 },
+ { "NVIDIA Corporation", 30 },
+ { "X.Org", 30 },
+ { "Intel Open Source Technology Center", 20 },
+ { "Intel", 20 },
+ { "nouveau", 10 },
+ { "Mesa Project", 0 },
+ { nullptr, 0 }
+ };
+
+ static void create_context();
+
+public:
+ static int detect_prime();
+};
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+
+#endif // DETECT_PRIME_EGL_H
diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp
new file mode 100644
index 0000000000..02b715056a
--- /dev/null
+++ b/platform/linuxbsd/wayland/display_server_wayland.cpp
@@ -0,0 +1,1404 @@
+/**************************************************************************/
+/* display_server_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "display_server_wayland.h"
+
+#ifdef WAYLAND_ENABLED
+
+#define WAYLAND_DISPLAY_SERVER_DEBUG_LOGS_ENABLED
+#ifdef WAYLAND_DISPLAY_SERVER_DEBUG_LOGS_ENABLED
+#define DEBUG_LOG_WAYLAND(...) print_verbose(__VA_ARGS__)
+#else
+#define DEBUG_LOG_WAYLAND(...)
+#endif
+
+#ifdef VULKAN_ENABLED
+#include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
+#endif
+
+#ifdef GLES3_ENABLED
+#include "detect_prime_egl.h"
+#include "drivers/gles3/rasterizer_gles3.h"
+#endif
+
+String DisplayServerWayland::_get_app_id_from_context(Context p_context) {
+ String app_id;
+
+ switch (p_context) {
+ case CONTEXT_EDITOR: {
+ app_id = "org.godotengine.Editor";
+ } break;
+
+ case CONTEXT_PROJECTMAN: {
+ app_id = "org.godotengine.ProjectManager";
+ } break;
+
+ case CONTEXT_ENGINE:
+ default: {
+ String config_name = GLOBAL_GET("application/config/name");
+ if (config_name.length() != 0) {
+ app_id = config_name;
+ } else {
+ app_id = "org.godotengine.Godot";
+ }
+ }
+ }
+
+ return app_id;
+}
+
+void DisplayServerWayland::_send_window_event(WindowEvent p_event) {
+ WindowData &wd = main_window;
+
+ if (wd.window_event_callback.is_valid()) {
+ Variant event = int(p_event);
+ wd.window_event_callback.call(event);
+ }
+}
+
+void DisplayServerWayland::dispatch_input_events(const Ref<InputEvent> &p_event) {
+ ((DisplayServerWayland *)(get_singleton()))->_dispatch_input_event(p_event);
+}
+
+void DisplayServerWayland::_dispatch_input_event(const Ref<InputEvent> &p_event) {
+ Callable callable = main_window.input_event_callback;
+ if (callable.is_valid()) {
+ callable.call(p_event);
+ }
+}
+
+void DisplayServerWayland::_resize_window(const Size2i &p_size) {
+ WindowData &wd = main_window;
+
+ wd.rect.size = p_size;
+
+#ifdef RD_ENABLED
+ if (wd.visible && context_rd) {
+ context_rd->window_resize(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (wd.visible && egl_manager) {
+ wl_egl_window_resize(wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height, 0, 0);
+ }
+#endif
+
+ if (wd.rect_changed_callback.is_valid()) {
+ wd.rect_changed_callback.call(wd.rect);
+ }
+}
+
+void DisplayServerWayland::_show_window() {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ DEBUG_LOG_WAYLAND("Showing window.");
+
+ // Showing this window will reset its mode with whatever the compositor
+ // reports. We'll save the mode beforehand so that we can reapply it later.
+ // TODO: Fix/Port/Move/Whatever to `WaylandThread` APIs.
+ WindowMode setup_mode = wd.mode;
+
+ wayland_thread.window_create(MAIN_WINDOW_ID, wd.rect.size.width, wd.rect.size.height);
+ wayland_thread.window_set_min_size(MAIN_WINDOW_ID, wd.min_size);
+ wayland_thread.window_set_max_size(MAIN_WINDOW_ID, wd.max_size);
+ wayland_thread.window_set_app_id(MAIN_WINDOW_ID, _get_app_id_from_context(context));
+ wayland_thread.window_set_borderless(MAIN_WINDOW_ID, window_get_flag(WINDOW_FLAG_BORDERLESS));
+
+ // NOTE: The XDG shell protocol is built in a way that causes the window to
+ // be immediately shown as soon as a valid buffer is assigned to it. Hence,
+ // the only acceptable way of implementing window showing is to move the
+ // graphics context window creation logic here.
+#ifdef RD_ENABLED
+ if (context_rd) {
+ union {
+#ifdef VULKAN_ENABLED
+ VulkanContextWayland::WindowPlatformData vulkan;
+#endif
+ } wpd;
+#ifdef VULKAN_ENABLED
+ if (rendering_driver == "vulkan") {
+ wpd.vulkan.surface = wayland_thread.window_get_wl_surface(wd.id);
+ wpd.vulkan.display = wayland_thread.get_wl_display();
+ }
+#endif
+ Error err = context_rd->window_create(wd.id, wd.vsync_mode, wd.rect.size.width, wd.rect.size.height, &wpd);
+ ERR_FAIL_COND_MSG(err != OK, vformat("Can't create a %s window", context_rd->get_api_name()));
+
+ emulate_vsync = (context_rd->get_vsync_mode(wd.id) == DisplayServer::VSYNC_ENABLED);
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames using MAILBOX.");
+ context_rd->set_vsync_mode(wd.id, DisplayServer::VSYNC_MAILBOX);
+ }
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ struct wl_surface *wl_surface = wayland_thread.window_get_wl_surface(wd.id);
+ wd.wl_egl_window = wl_egl_window_create(wl_surface, wd.rect.size.width, wd.rect.size.height);
+
+ Error err = egl_manager->window_create(MAIN_WINDOW_ID, wayland_thread.get_wl_display(), wd.wl_egl_window, wd.rect.size.width, wd.rect.size.height);
+ ERR_FAIL_COND_MSG(err == ERR_CANT_CREATE, "Can't show a GLES3 window.");
+
+ window_set_vsync_mode(wd.vsync_mode, MAIN_WINDOW_ID);
+ }
+#endif
+ // NOTE: The public window-handling methods might depend on this flag being
+ // set. Ensure to not make any of these calls before this assignment.
+ wd.visible = true;
+
+ // Actually try to apply the window's mode now that it's visible.
+ window_set_mode(setup_mode);
+
+ wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
+ }
+}
+// Interface methods.
+
+bool DisplayServerWayland::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ case FEATURE_MOUSE:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_CURSOR_SHAPE:
+ case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_SWAP_BUFFERS:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_CLIPBOARD_PRIMARY:
+#ifdef DBUS_ENABLED
+ case FEATURE_NATIVE_DIALOG:
+#endif
+ case FEATURE_HIDPI: {
+ return true;
+ } break;
+
+ default: {
+ return false;
+ }
+ }
+}
+
+String DisplayServerWayland::get_name() const {
+ return "Wayland";
+}
+
+#ifdef SPEECHD_ENABLED
+
+bool DisplayServerWayland::tts_is_speaking() const {
+ ERR_FAIL_NULL_V(tts, false);
+ return tts->is_speaking();
+}
+
+bool DisplayServerWayland::tts_is_paused() const {
+ ERR_FAIL_NULL_V(tts, false);
+ return tts->is_paused();
+}
+
+TypedArray<Dictionary> DisplayServerWayland::tts_get_voices() const {
+ ERR_FAIL_NULL_V(tts, TypedArray<Dictionary>());
+ return tts->get_voices();
+}
+
+void DisplayServerWayland::tts_speak(const String &p_text, const String &p_voice, int p_volume, float p_pitch, float p_rate, int p_utterance_id, bool p_interrupt) {
+ ERR_FAIL_NULL(tts);
+ tts->speak(p_text, p_voice, p_volume, p_pitch, p_rate, p_utterance_id, p_interrupt);
+}
+
+void DisplayServerWayland::tts_pause() {
+ ERR_FAIL_NULL(tts);
+ tts->pause();
+}
+
+void DisplayServerWayland::tts_resume() {
+ ERR_FAIL_NULL(tts);
+ tts->resume();
+}
+
+void DisplayServerWayland::tts_stop() {
+ ERR_FAIL_NULL(tts);
+ tts->stop();
+}
+
+#endif
+
+#ifdef DBUS_ENABLED
+
+bool DisplayServerWayland::is_dark_mode_supported() const {
+ return portal_desktop->is_supported();
+}
+
+bool DisplayServerWayland::is_dark_mode() const {
+ switch (portal_desktop->get_appearance_color_scheme()) {
+ case 1:
+ // Prefers dark theme.
+ return true;
+ case 2:
+ // Prefers light theme.
+ return false;
+ default:
+ // Preference unknown.
+ return false;
+ }
+}
+
+Error DisplayServerWayland::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ WindowID window_id = MAIN_WINDOW_ID;
+ // TODO: Use window IDs for multiwindow support.
+
+ WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
+ return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerWayland::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ WindowID window_id = MAIN_WINDOW_ID;
+ // TODO: Use window IDs for multiwindow support.
+
+ WaylandThread::WindowState *ws = wayland_thread.wl_surface_get_window_state(wayland_thread.window_get_wl_surface(window_id));
+ return portal_desktop->file_dialog_show(window_id, (ws ? ws->exported_handle : String()), p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
+}
+
+#endif
+
+void DisplayServerWayland::mouse_set_mode(MouseMode p_mode) {
+ if (p_mode == mouse_mode) {
+ return;
+ }
+
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ bool show_cursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED);
+
+ if (show_cursor) {
+ if (custom_cursors.has(cursor_shape)) {
+ wayland_thread.cursor_set_custom_shape(cursor_shape);
+ } else {
+ wayland_thread.cursor_set_shape(cursor_shape);
+ }
+ } else {
+ wayland_thread.cursor_hide();
+ }
+
+ WaylandThread::PointerConstraint constraint = WaylandThread::PointerConstraint::NONE;
+
+ switch (p_mode) {
+ case DisplayServer::MOUSE_MODE_CAPTURED: {
+ constraint = WaylandThread::PointerConstraint::LOCKED;
+ } break;
+
+ case DisplayServer::MOUSE_MODE_CONFINED:
+ case DisplayServer::MOUSE_MODE_CONFINED_HIDDEN: {
+ constraint = WaylandThread::PointerConstraint::CONFINED;
+ } break;
+
+ default: {
+ }
+ }
+
+ wayland_thread.pointer_set_constraint(constraint);
+
+ mouse_mode = p_mode;
+}
+
+DisplayServerWayland::MouseMode DisplayServerWayland::mouse_get_mode() const {
+ return mouse_mode;
+}
+
+// NOTE: This is hacked together (and not guaranteed to work in the first place)
+// as for some reason the there's no proper way to ask the compositor to warp
+// the pointer, although, at the time of writing, there's a proposal for a
+// proper protocol for this. See:
+// https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/158
+void DisplayServerWayland::warp_mouse(const Point2i &p_to) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WaylandThread::PointerConstraint old_constraint = wayland_thread.pointer_get_constraint();
+
+ wayland_thread.pointer_set_constraint(WaylandThread::PointerConstraint::LOCKED);
+ wayland_thread.pointer_set_hint(p_to);
+
+ wayland_thread.pointer_set_constraint(old_constraint);
+}
+
+Point2i DisplayServerWayland::mouse_get_position() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't properly implement this method by design.
+ // This is the best we can do unfortunately.
+ return Input::get_singleton()->get_mouse_position();
+
+ return Point2i();
+}
+
+BitField<MouseButtonMask> DisplayServerWayland::mouse_get_button_state() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // Are we sure this is the only way? This seems sus.
+ // TODO: Handle tablets properly.
+ //mouse_button_mask.set_flag(MouseButtonMask((int64_t)wls.current_seat->tablet_tool_data.pressed_button_mask));
+
+ return wayland_thread.pointer_get_button_mask();
+}
+
+// NOTE: According to the Wayland specification, this method will only do
+// anything if the user has interacted with the application by sending a
+// "recent enough" input event.
+// TODO: Add this limitation to the documentation.
+void DisplayServerWayland::clipboard_set(const String &p_text) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.selection_set_text(p_text);
+}
+
+String DisplayServerWayland::clipboard_get() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<uint8_t> data;
+
+ const String text_mimes[] = {
+ "text/plain;charset=utf-8",
+ "text/plain",
+ };
+
+ for (String mime : text_mimes) {
+ if (wayland_thread.selection_has_mime(mime)) {
+ print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime));
+ data = wayland_thread.selection_get_mime(mime);
+ break;
+ }
+ }
+
+ return String::utf8((const char *)data.ptr(), data.size());
+}
+
+Ref<Image> DisplayServerWayland::clipboard_get_image() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Ref<Image> image;
+ image.instantiate();
+
+ Error err = OK;
+
+ // TODO: Fallback to next media type on missing module or parse error.
+ if (wayland_thread.selection_has_mime("image/png")) {
+ err = image->load_png_from_buffer(wayland_thread.selection_get_mime("image/png"));
+ } else if (wayland_thread.selection_has_mime("image/jpeg")) {
+ err = image->load_jpg_from_buffer(wayland_thread.selection_get_mime("image/jpeg"));
+ } else if (wayland_thread.selection_has_mime("image/webp")) {
+ err = image->load_webp_from_buffer(wayland_thread.selection_get_mime("image/webp"));
+ } else if (wayland_thread.selection_has_mime("image/svg+xml")) {
+ err = image->load_svg_from_buffer(wayland_thread.selection_get_mime("image/svg+xml"));
+ } else if (wayland_thread.selection_has_mime("image/bmp")) {
+ err = image->load_bmp_from_buffer(wayland_thread.selection_get_mime("image/bmp"));
+ } else if (wayland_thread.selection_has_mime("image/x-tga")) {
+ err = image->load_tga_from_buffer(wayland_thread.selection_get_mime("image/x-tga"));
+ } else if (wayland_thread.selection_has_mime("image/x-targa")) {
+ err = image->load_tga_from_buffer(wayland_thread.selection_get_mime("image/x-targa"));
+ } else if (wayland_thread.selection_has_mime("image/ktx")) {
+ err = image->load_ktx_from_buffer(wayland_thread.selection_get_mime("image/ktx"));
+ }
+
+ ERR_FAIL_COND_V(err != OK, Ref<Image>());
+
+ return image;
+}
+
+void DisplayServerWayland::clipboard_set_primary(const String &p_text) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.primary_set_text(p_text);
+}
+
+String DisplayServerWayland::clipboard_get_primary() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<uint8_t> data;
+
+ const String text_mimes[] = {
+ "text/plain;charset=utf-8",
+ "text/plain",
+ };
+
+ for (String mime : text_mimes) {
+ if (wayland_thread.primary_has_mime(mime)) {
+ print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime));
+ wayland_thread.primary_get_mime(mime);
+ break;
+ }
+ }
+
+ return String::utf8((const char *)data.ptr(), data.size());
+}
+
+int DisplayServerWayland::get_screen_count() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+ return wayland_thread.get_screen_count();
+}
+
+int DisplayServerWayland::get_primary_screen() const {
+ // AFAIK Wayland doesn't allow knowing (nor we care) about which screen is
+ // primary.
+ return 0;
+}
+
+Point2i DisplayServerWayland::screen_get_position(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).position;
+}
+
+Size2i DisplayServerWayland::screen_get_size(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).size;
+}
+
+Rect2i DisplayServerWayland::screen_get_usable_rect(int p_screen) const {
+ // Unsupported on wayland.
+ return Rect2i(Point2i(), screen_get_size(p_screen));
+}
+
+int DisplayServerWayland::screen_get_dpi(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ const WaylandThread::ScreenData &data = wayland_thread.screen_get_data(p_screen);
+
+ int width_mm = data.physical_size.width;
+ int height_mm = data.physical_size.height;
+
+ double xdpi = (width_mm ? data.size.width / (double)width_mm * 25.4 : 0);
+ double ydpi = (height_mm ? data.size.height / (double)height_mm * 25.4 : 0);
+
+ if (xdpi || ydpi) {
+ return (xdpi + ydpi) / (xdpi && ydpi ? 2 : 1);
+ }
+
+ // Could not get DPI.
+ return 96;
+}
+
+float DisplayServerWayland::screen_get_scale(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).scale;
+}
+
+float DisplayServerWayland::screen_get_refresh_rate(int p_screen) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ return wayland_thread.screen_get_data(p_screen).refresh_rate;
+}
+
+void DisplayServerWayland::screen_set_keep_on(bool p_enable) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (screen_is_kept_on() == p_enable) {
+ return;
+ }
+
+#ifdef DBUS_ENABLED
+ if (screensaver) {
+ if (p_enable) {
+ screensaver->inhibit();
+ } else {
+ screensaver->uninhibit();
+ }
+
+ screensaver_inhibited = p_enable;
+ }
+#endif
+}
+
+bool DisplayServerWayland::screen_is_kept_on() const {
+#ifdef DBUS_ENABLED
+ return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID) || screensaver_inhibited;
+#endif
+
+ return wayland_thread.window_get_idle_inhibition(MAIN_WINDOW_ID);
+}
+
+Vector<DisplayServer::WindowID> DisplayServerWayland::get_window_list() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Vector<int> ret;
+ ret.push_back(MAIN_WINDOW_ID);
+
+ return ret;
+}
+
+int64_t DisplayServerWayland::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ switch (p_handle_type) {
+ case DISPLAY_HANDLE: {
+ return (int64_t)wayland_thread.get_wl_display();
+ } break;
+
+ case WINDOW_HANDLE: {
+ return (int64_t)wayland_thread.window_get_wl_surface(p_window);
+ } break;
+
+ case WINDOW_VIEW: {
+ return 0; // Not supported.
+ } break;
+
+#ifdef GLES3_ENABLED
+ case OPENGL_CONTEXT: {
+ if (egl_manager) {
+ return (int64_t)egl_manager->get_context(p_window);
+ }
+ return 0;
+ } break;
+#endif // GLES3_ENABLED
+
+ default: {
+ return 0;
+ } break;
+ }
+}
+
+DisplayServer::WindowID DisplayServerWayland::get_window_at_screen_position(const Point2i &p_position) const {
+ // Standard Wayland APIs don't support this.
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerWayland::window_attach_instance_id(ObjectID p_instance, WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.instance_id = p_instance;
+}
+
+ObjectID DisplayServerWayland::window_get_attached_instance_id(WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.instance_id;
+}
+
+void DisplayServerWayland::window_set_title(const String &p_title, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ wd.title = p_title;
+
+ wayland_thread.window_set_title(MAIN_WINDOW_ID, wd.title);
+}
+
+void DisplayServerWayland::window_set_mouse_passthrough(const Vector<Vector2> &p_region, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_mouse_passthrough region %s", p_region));
+}
+
+void DisplayServerWayland::window_set_rect_changed_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.rect_changed_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_window_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.window_event_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_input_event_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.input_event_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_input_text_callback(const Callable &p_callable, WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.input_text_callback = p_callable;
+}
+
+void DisplayServerWayland::window_set_drop_files_callback(const Callable &p_callable, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ main_window.drop_files_callback = p_callable;
+}
+
+int DisplayServerWayland::window_get_current_screen(DisplayServer::WindowID p_window_id) const {
+ // Standard Wayland APIs don't support getting the screen of a window.
+ return 0;
+}
+
+void DisplayServerWayland::window_set_current_screen(int p_screen, DisplayServer::WindowID p_window_id) {
+ // Standard Wayland APIs don't support setting the screen of a window.
+}
+
+Point2i DisplayServerWayland::window_get_position(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't know the position of toplevels with the standard protocol.
+ return Point2i();
+}
+
+Point2i DisplayServerWayland::window_get_position_with_decorations(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // We can't know the position of toplevels with the standard protocol, nor can
+ // we get information about the decorations, at least with SSDs.
+ return Point2i();
+}
+
+void DisplayServerWayland::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window_id) {
+ // Unsupported with toplevels.
+}
+
+void DisplayServerWayland::window_set_max_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("window max size set to %s", p_size));
+
+ if (p_size.x < 0 || p_size.y < 0) {
+ ERR_FAIL_MSG("Maximum window size can't be negative!");
+ }
+
+ WindowData &wd = main_window;
+
+ // FIXME: Is `p_size.x < wd.min_size.x || p_size.y < wd.min_size.y` == `p_size < wd.min_size`?
+ if ((p_size != Size2i()) && ((p_size.x < wd.min_size.x) || (p_size.y < wd.min_size.y))) {
+ ERR_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+
+ wd.max_size = p_size;
+
+ wayland_thread.window_set_max_size(MAIN_WINDOW_ID, p_size);
+}
+
+Size2i DisplayServerWayland::window_get_max_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.max_size;
+}
+
+void DisplayServerWayland::gl_window_make_current(DisplayServer::WindowID p_window_id) {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->window_make_current(MAIN_WINDOW_ID);
+ }
+#endif
+}
+
+void DisplayServerWayland::window_set_transient(WindowID p_window_id, WindowID p_parent) {
+ // Currently unsupported.
+}
+
+void DisplayServerWayland::window_set_min_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("window minsize set to %s", p_size));
+
+ WindowData &wd = main_window;
+
+ if (p_size.x < 0 || p_size.y < 0) {
+ ERR_FAIL_MSG("Minimum window size can't be negative!");
+ }
+
+ // FIXME: Is `p_size.x > wd.max_size.x || p_size.y > wd.max_size.y` == `p_size > wd.max_size`?
+ if ((p_size != Size2i()) && (wd.max_size != Size2i()) && ((p_size.x > wd.max_size.x) || (p_size.y > wd.max_size.y))) {
+ ERR_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+
+ wd.min_size = p_size;
+
+ wayland_thread.window_set_min_size(MAIN_WINDOW_ID, p_size);
+}
+
+Size2i DisplayServerWayland::window_get_min_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.min_size;
+}
+
+void DisplayServerWayland::window_set_size(const Size2i p_size, DisplayServer::WindowID p_window_id) {
+ // The XDG spec doesn't allow non-interactive resizes.
+}
+
+Size2i DisplayServerWayland::window_get_size(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.rect.size;
+}
+
+Size2i DisplayServerWayland::window_get_size_with_decorations(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // I don't think there's a way of actually knowing the size of the window
+ // decoration in Wayland, at least in the case of SSDs, nor that it would be
+ // that useful in this case. We'll just return the main window's size.
+ return main_window.rect.size;
+}
+
+void DisplayServerWayland::window_set_mode(WindowMode p_mode, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ return;
+ }
+
+ wayland_thread.window_try_set_mode(p_window_id, p_mode);
+}
+
+DisplayServer::WindowMode DisplayServerWayland::window_get_mode(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ const WindowData &wd = main_window;
+
+ if (!wd.visible) {
+ return WINDOW_MODE_WINDOWED;
+ }
+
+ return wayland_thread.window_get_mode(p_window_id);
+}
+
+bool DisplayServerWayland::window_is_maximize_allowed(DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.window_can_set_mode(p_window_id, WINDOW_MODE_MAXIMIZED);
+}
+
+void DisplayServerWayland::window_set_flag(WindowFlags p_flag, bool p_enabled, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ WindowData &wd = main_window;
+
+ DEBUG_LOG_WAYLAND(vformat("Window set flag %d", p_flag));
+
+ switch (p_flag) {
+ case WINDOW_FLAG_BORDERLESS: {
+ wayland_thread.window_set_borderless(MAIN_WINDOW_ID, p_enabled);
+ } break;
+
+ default: {
+ }
+ }
+
+ if (p_enabled) {
+ wd.flags |= 1 << p_flag;
+ } else {
+ wd.flags &= ~(1 << p_flag);
+ }
+}
+
+bool DisplayServerWayland::window_get_flag(WindowFlags p_flag, DisplayServer::WindowID p_window_id) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return main_window.flags & (1 << p_flag);
+}
+
+void DisplayServerWayland::window_request_attention(DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND("Requested attention.");
+
+ wayland_thread.window_request_attention(MAIN_WINDOW_ID);
+}
+
+void DisplayServerWayland::window_move_to_foreground(DisplayServer::WindowID p_window_id) {
+ // Standard Wayland APIs don't support this.
+}
+
+bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const {
+ return wayland_thread.pointer_get_pointed_window_id() == p_window_id;
+}
+
+bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const {
+ return frame;
+}
+
+bool DisplayServerWayland::can_any_window_draw() const {
+ return frame;
+}
+
+void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_active active %s", p_active ? "true" : "false"));
+}
+
+void DisplayServerWayland::window_set_ime_position(const Point2i &p_pos, DisplayServer::WindowID p_window_id) {
+ // TODO
+ DEBUG_LOG_WAYLAND(vformat("wayland stub window_set_ime_position pos %s window %d", p_pos, p_window_id));
+}
+
+// NOTE: While Wayland is supposed to be tear-free, wayland-protocols version
+// 1.30 added a protocol for allowing async flips which is supposed to be
+// handled by drivers such as Vulkan. We can then just ask to disable v-sync and
+// hope for the best. See: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/6394f0b4f3be151076f10a845a2fb131eeb56706
+void DisplayServerWayland::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, DisplayServer::WindowID p_window_id) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+#ifdef RD_ENABLED
+ if (context_rd) {
+ context_rd->set_vsync_mode(p_window_id, p_vsync_mode);
+
+ emulate_vsync = (context_rd->get_vsync_mode(p_window_id) == DisplayServer::VSYNC_ENABLED);
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames using MAILBOX.");
+ context_rd->set_vsync_mode(p_window_id, DisplayServer::VSYNC_MAILBOX);
+ }
+ }
+#endif // VULKAN_ENABLED
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
+
+ emulate_vsync = egl_manager->is_using_vsync();
+
+ if (emulate_vsync) {
+ print_verbose("VSYNC: manually throttling frames with swap delay 0.");
+ egl_manager->set_use_vsync(false);
+ }
+ }
+#endif // GLES3_ENABLED
+}
+
+DisplayServer::VSyncMode DisplayServerWayland::window_get_vsync_mode(DisplayServer::WindowID p_window_id) const {
+ if (emulate_vsync) {
+ return DisplayServer::VSYNC_ENABLED;
+ }
+
+#ifdef VULKAN_ENABLED
+ if (context_rd) {
+ return context_rd->get_vsync_mode(p_window_id);
+ }
+#endif // VULKAN_ENABLED
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ return egl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
+ }
+#endif // GLES3_ENABLED
+
+ return DisplayServer::VSYNC_ENABLED;
+}
+
+void DisplayServerWayland::cursor_set_shape(CursorShape p_shape) {
+ ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
+
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ if (p_shape == cursor_shape) {
+ return;
+ }
+
+ cursor_shape = p_shape;
+
+ if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) {
+ // Hidden.
+ return;
+ }
+
+ if (custom_cursors.has(p_shape)) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ } else {
+ wayland_thread.cursor_set_shape(p_shape);
+ }
+}
+
+DisplayServerWayland::CursorShape DisplayServerWayland::cursor_get_shape() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return cursor_shape;
+}
+
+void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ bool visible = (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED);
+
+ if (p_cursor.is_valid()) {
+ HashMap<CursorShape, CustomCursor>::Iterator cursor_c = custom_cursors.find(p_shape);
+
+ if (cursor_c) {
+ if (cursor_c->value.rid == p_cursor->get_rid() && cursor_c->value.hotspot == p_hotspot) {
+ // We have a cached cursor. Nice.
+ if (visible) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ }
+
+ return;
+ }
+
+ // We're changing this cursor; we'll have to rebuild it.
+ custom_cursors.erase(p_shape);
+ wayland_thread.cursor_shape_clear_custom_image(p_shape);
+ }
+
+ Ref<Texture2D> texture = p_cursor;
+ ERR_FAIL_COND(!texture.is_valid());
+ Size2i texture_size;
+
+ Ref<AtlasTexture> atlas_texture = texture;
+
+ if (atlas_texture.is_valid()) {
+ texture_size.width = atlas_texture->get_region().size.x;
+ texture_size.height = atlas_texture->get_region().size.y;
+ } else {
+ texture_size.width = texture->get_width();
+ texture_size.height = texture->get_height();
+ }
+
+ ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
+
+ // NOTE: The Wayland protocol says nothing about cursor size limits, yet if
+ // the texture is larger than 256x256 it won't show at least on sway.
+ ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
+ ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
+ ERR_FAIL_COND(texture_size.height == 0 || texture_size.width == 0);
+
+ Ref<Image> image = texture->get_image();
+ ERR_FAIL_COND(!image.is_valid());
+
+ if (image->is_compressed()) {
+ image = image->duplicate(true);
+ Error err = image->decompress();
+ ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
+ }
+
+ CustomCursor &cursor = custom_cursors[p_shape];
+
+ cursor.rid = p_cursor->get_rid();
+ cursor.hotspot = p_hotspot;
+
+ wayland_thread.cursor_shape_set_custom_image(p_shape, image, p_hotspot);
+
+ if (visible) {
+ wayland_thread.cursor_set_custom_shape(p_shape);
+ }
+ } else {
+ // Clear cache and reset to default system cursor.
+ if (cursor_shape == p_shape && visible) {
+ wayland_thread.cursor_set_shape(p_shape);
+ }
+
+ if (custom_cursors.has(p_shape)) {
+ custom_cursors.erase(p_shape);
+ }
+
+ wayland_thread.cursor_shape_clear_custom_image(p_shape);
+ }
+}
+
+int DisplayServerWayland::keyboard_get_layout_count() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_layout_count();
+}
+
+int DisplayServerWayland::keyboard_get_current_layout() const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_current_layout_index();
+}
+
+void DisplayServerWayland::keyboard_set_current_layout(int p_index) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ wayland_thread.keyboard_set_current_layout_index(p_index);
+}
+
+String DisplayServerWayland::keyboard_get_layout_language(int p_index) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ // xkbcommon exposes only the layout's name, which looks like it overlaps with
+ // its language.
+ return wayland_thread.keyboard_get_layout_name(p_index);
+}
+
+String DisplayServerWayland::keyboard_get_layout_name(int p_index) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ return wayland_thread.keyboard_get_layout_name(p_index);
+}
+
+Key DisplayServerWayland::keyboard_get_keycode_from_physical(Key p_keycode) const {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ Key key = wayland_thread.keyboard_get_key_from_physical(p_keycode);
+
+ // If not found, fallback to QWERTY.
+ // This should match the behavior of the event pump.
+ if (key == Key::NONE) {
+ return p_keycode;
+ }
+
+ if (key >= Key::A + 32 && key <= Key::Z + 32) {
+ key -= 'a' - 'A';
+ }
+
+ // Make it consistent with the keys returned by `Input`.
+ if (key == Key::BACKTAB) {
+ key = Key::TAB;
+ }
+
+ return key;
+}
+
+void DisplayServerWayland::process_events() {
+ wayland_thread.mutex.lock();
+
+ while (wayland_thread.has_message()) {
+ Ref<WaylandThread::Message> msg = wayland_thread.pop_message();
+
+ Ref<WaylandThread::WindowRectMessage> winrect_msg = msg;
+ if (winrect_msg.is_valid()) {
+ _resize_window(winrect_msg->rect.size);
+ }
+
+ Ref<WaylandThread::WindowEventMessage> winev_msg = msg;
+ if (winev_msg.is_valid()) {
+ _send_window_event(winev_msg->event);
+
+ if (winev_msg->event == WINDOW_EVENT_FOCUS_IN) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN);
+ }
+ } else if (winev_msg->event == WINDOW_EVENT_FOCUS_OUT) {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);
+ }
+ }
+ }
+
+ Ref<WaylandThread::InputEventMessage> inputev_msg = msg;
+ if (inputev_msg.is_valid()) {
+ Input::get_singleton()->parse_input_event(inputev_msg->event);
+ }
+
+ Ref<WaylandThread::DropFilesEventMessage> dropfiles_msg = msg;
+ if (dropfiles_msg.is_valid()) {
+ WindowData wd = main_window;
+
+ if (wd.drop_files_callback.is_valid()) {
+ wd.drop_files_callback.call(dropfiles_msg->files);
+ }
+ }
+ }
+
+ wayland_thread.keyboard_echo_keys();
+
+ frame = wayland_thread.get_reset_frame();
+
+ wayland_thread.mutex.unlock();
+
+ Input::get_singleton()->flush_buffered_events();
+}
+
+void DisplayServerWayland::release_rendering_thread() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->release_current();
+ }
+#endif
+}
+
+void DisplayServerWayland::make_rendering_thread() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->make_current();
+ }
+#endif
+}
+
+void DisplayServerWayland::swap_buffers() {
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->swap_buffers();
+ }
+#endif
+}
+
+void DisplayServerWayland::set_context(Context p_context) {
+ MutexLock mutex_lock(wayland_thread.mutex);
+
+ DEBUG_LOG_WAYLAND(vformat("Setting context %d.", p_context));
+
+ context = p_context;
+
+ String app_id = _get_app_id_from_context(p_context);
+ wayland_thread.window_set_app_id(MAIN_WINDOW_ID, app_id);
+}
+
+Vector<String> DisplayServerWayland::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#ifdef VULKAN_ENABLED
+ drivers.push_back("vulkan");
+#endif
+
+#ifdef GLES3_ENABLED
+ drivers.push_back("opengl3");
+#endif
+
+ return drivers;
+}
+
+DisplayServer *DisplayServerWayland::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Error &r_error) {
+ DisplayServer *ds = memnew(DisplayServerWayland(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_resolution, r_error));
+ if (r_error != OK) {
+ ERR_PRINT("Can't create the Wayland display server.");
+ memdelete(ds);
+
+ return nullptr;
+ }
+ return ds;
+}
+
+DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+#ifdef GLES3_ENABLED
+#ifdef SOWRAP_ENABLED
+#ifdef DEBUG_ENABLED
+ int dylibloader_verbose = 1;
+#else
+ int dylibloader_verbose = 0;
+#endif // DEBUG_ENABLED
+#endif // SOWRAP_ENABLED
+#endif // GLES3_ENABLED
+
+ r_error = ERR_UNAVAILABLE;
+
+ Error thread_err = wayland_thread.init();
+
+ if (thread_err != OK) {
+ r_error = thread_err;
+ ERR_FAIL_MSG("Could not initialize the Wayland thread.");
+ }
+
+ // Input.
+ Input::get_singleton()->set_event_dispatch_function(dispatch_input_events);
+
+#ifdef SPEECHD_ENABLED
+ // Init TTS
+ tts = memnew(TTS_Linux);
+#endif
+
+ rendering_driver = p_rendering_driver;
+
+#ifdef RD_ENABLED
+#ifdef VULKAN_ENABLED
+ if (p_rendering_driver == "vulkan") {
+ context_rd = memnew(VulkanContextWayland);
+ }
+#endif
+
+ if (context_rd) {
+ if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Could not initialize %s", context_rd->get_api_name()));
+ memdelete(context_rd);
+ context_rd = nullptr;
+ r_error = ERR_CANT_CREATE;
+ return;
+ }
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (p_rendering_driver == "opengl3") {
+ if (getenv("DRI_PRIME") == nullptr) {
+ int prime_idx = -1;
+
+ if (getenv("PRIMUS_DISPLAY") ||
+ getenv("PRIMUS_libGLd") ||
+ getenv("PRIMUS_libGLa") ||
+ getenv("PRIMUS_libGL") ||
+ getenv("PRIMUS_LOAD_GLOBAL") ||
+ getenv("BUMBLEBEE_SOCKET") ||
+ getenv("__NV_PRIME_RENDER_OFFLOAD")) {
+ print_verbose("Optirun/primusrun detected. Skipping GPU detection");
+ prime_idx = 0;
+ }
+
+ // Some tools use fake libGL libraries and have them override the real one using
+ // LD_LIBRARY_PATH, so we skip them. *But* Steam also sets LD_LIBRARY_PATH for its
+ // runtime and includes system `/lib` and `/lib64`... so ignore Steam.
+ if (prime_idx == -1 && getenv("LD_LIBRARY_PATH") && !getenv("STEAM_RUNTIME_LIBRARY_PATH")) {
+ String ld_library_path(getenv("LD_LIBRARY_PATH"));
+ Vector<String> libraries = ld_library_path.split(":");
+
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (FileAccess::exists(libraries[i] + "/libGL.so.1") ||
+ FileAccess::exists(libraries[i] + "/libGL.so")) {
+ print_verbose("Custom libGL override detected. Skipping GPU detection");
+ prime_idx = 0;
+ }
+ }
+ }
+
+ if (prime_idx == -1) {
+ print_verbose("Detecting GPUs, set DRI_PRIME in the environment to override GPU detection logic.");
+ prime_idx = DetectPrimeEGL::detect_prime();
+ }
+
+ if (prime_idx) {
+ print_line(vformat("Found discrete GPU, setting DRI_PRIME=%d to use it.", prime_idx));
+ print_line("Note: Set DRI_PRIME=0 in the environment to disable Godot from using the discrete GPU.");
+ setenv("DRI_PRIME", itos(prime_idx).utf8().ptr(), 1);
+ }
+ }
+
+ egl_manager = memnew(EGLManagerWayland);
+
+#ifdef SOWRAP_ENABLED
+ if (initialize_wayland_egl(dylibloader_verbose) != 0) {
+ WARN_PRINT("Can't load the Wayland EGL library.");
+ return;
+ }
+#endif // SOWRAP_ENABLED
+
+ if (egl_manager->initialize() != OK) {
+ memdelete(egl_manager);
+ egl_manager = nullptr;
+ r_error = ERR_CANT_CREATE;
+ ERR_FAIL_MSG("Could not initialize GLES3.");
+ }
+
+ RasterizerGLES3::make_current(true);
+ }
+#endif // GLES3_ENABLED
+
+ cursor_set_shape(CURSOR_BUSY);
+
+ WindowData &wd = main_window;
+
+ wd.id = MAIN_WINDOW_ID;
+ wd.mode = p_mode;
+ wd.flags = p_flags;
+ wd.vsync_mode = p_vsync_mode;
+ wd.rect.size = p_resolution;
+ wd.title = "Godot";
+
+ _show_window();
+
+#ifdef RD_ENABLED
+ if (context_rd) {
+ rendering_device = memnew(RenderingDevice);
+ rendering_device->initialize(context_rd);
+
+ RendererCompositorRD::make_current();
+ }
+#endif
+
+#ifdef DBUS_ENABLED
+ portal_desktop = memnew(FreeDesktopPortalDesktop);
+ screensaver = memnew(FreeDesktopScreenSaver);
+#endif
+
+ screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on"));
+
+ r_error = OK;
+}
+
+DisplayServerWayland::~DisplayServerWayland() {
+ // TODO: Multiwindow support.
+ if (main_window.visible) {
+#ifdef VULKAN_ENABLED
+ if (context_rd) {
+ context_rd->window_destroy(MAIN_WINDOW_ID);
+ }
+#endif
+
+#ifdef GLES3_ENABLED
+ if (egl_manager) {
+ egl_manager->window_destroy(MAIN_WINDOW_ID);
+ }
+#endif
+ }
+
+#ifdef GLES3_ENABLED
+ if (main_window.wl_egl_window) {
+ wl_egl_window_destroy(main_window.wl_egl_window);
+ }
+#endif
+
+ wayland_thread.destroy();
+
+ // Destroy all drivers.
+#ifdef RD_ENABLED
+ if (rendering_device) {
+ rendering_device->finalize();
+ memdelete(rendering_device);
+ }
+
+ if (context_rd) {
+ memdelete(context_rd);
+ }
+#endif
+
+#ifdef SPEECHD_ENABLED
+ if (tts) {
+ memdelete(tts);
+ }
+#endif
+
+#ifdef DBUS_ENABLED
+ if (portal_desktop) {
+ memdelete(portal_desktop);
+ memdelete(screensaver);
+ }
+#endif
+}
+
+void DisplayServerWayland::register_wayland_driver() {
+ register_create_function("wayland", create_func, get_rendering_drivers_func);
+}
+
+#endif //WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h
new file mode 100644
index 0000000000..3e7f3c4cb4
--- /dev/null
+++ b/platform/linuxbsd/wayland/display_server_wayland.h
@@ -0,0 +1,295 @@
+/**************************************************************************/
+/* display_server_wayland.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef DISPLAY_SERVER_WAYLAND_H
+#define DISPLAY_SERVER_WAYLAND_H
+
+#ifdef WAYLAND_ENABLED
+
+#include "wayland/wayland_thread.h"
+
+#ifdef RD_ENABLED
+#include "servers/rendering/rendering_device.h"
+
+#ifdef VULKAN_ENABLED
+#include "wayland/vulkan_context_wayland.h"
+#endif
+
+#endif //RD_ENABLED
+
+#ifdef GLES3_ENABLED
+#include "wayland/egl_manager_wayland.h"
+#endif
+
+#if defined(SPEECHD_ENABLED)
+#include "tts_linux.h"
+#endif
+
+#ifdef DBUS_ENABLED
+#include "freedesktop_portal_desktop.h"
+#include "freedesktop_screensaver.h"
+#endif
+
+#include "core/config/project_settings.h"
+#include "core/input/input.h"
+#include "scene/resources/atlas_texture.h"
+#include "scene/resources/texture.h"
+#include "servers/display_server.h"
+
+#include <limits.h>
+#include <stdio.h>
+
+#undef CursorShape
+
+class DisplayServerWayland : public DisplayServer {
+ // No need to register with GDCLASS, it's platform-specific and nothing is added.
+ struct WindowData {
+ WindowID id;
+
+ Rect2i rect;
+ Size2i max_size;
+ Size2i min_size;
+
+ Rect2i safe_rect;
+
+#ifdef GLES3_ENABLED
+ struct wl_egl_window *wl_egl_window = nullptr;
+#endif
+
+ // Flags whether we have allocated a buffer through the video drivers.
+ bool visible = false;
+
+ DisplayServer::VSyncMode vsync_mode = VSYNC_ENABLED;
+
+ uint32_t flags = 0;
+
+ DisplayServer::WindowMode mode = WINDOW_MODE_WINDOWED;
+
+ Callable rect_changed_callback;
+ Callable window_event_callback;
+ Callable input_event_callback;
+ Callable drop_files_callback;
+ Callable input_text_callback;
+
+ String title;
+ ObjectID instance_id;
+ };
+
+ struct CustomCursor {
+ RID rid;
+ Point2i hotspot;
+ };
+
+ CursorShape cursor_shape = CURSOR_ARROW;
+ DisplayServer::MouseMode mouse_mode = DisplayServer::MOUSE_MODE_VISIBLE;
+
+ HashMap<CursorShape, CustomCursor> custom_cursors;
+
+ WindowData main_window;
+ WaylandThread wayland_thread;
+
+ Context context;
+
+ bool frame = false;
+ bool emulate_vsync = false;
+
+ String rendering_driver;
+
+#ifdef RD_ENABLED
+ ApiContextRD *context_rd = nullptr;
+ RenderingDevice *rendering_device = nullptr;
+#endif
+
+#ifdef GLES3_ENABLED
+ EGLManagerWayland *egl_manager = nullptr;
+#endif
+
+#ifdef SPEECHD_ENABLED
+ TTS_Linux *tts = nullptr;
+#endif
+
+#if DBUS_ENABLED
+ FreeDesktopPortalDesktop *portal_desktop = nullptr;
+
+ FreeDesktopScreenSaver *screensaver = nullptr;
+ bool screensaver_inhibited = false;
+#endif
+ static String _get_app_id_from_context(Context p_context);
+
+ void _send_window_event(WindowEvent p_event);
+
+ static void dispatch_input_events(const Ref<InputEvent> &p_event);
+ void _dispatch_input_event(const Ref<InputEvent> &p_event);
+
+ void _resize_window(const Size2i &p_size);
+
+ virtual void _show_window();
+
+public:
+ virtual bool has_feature(Feature p_feature) const override;
+
+ virtual String get_name() const override;
+
+#ifdef SPEECHD_ENABLED
+ virtual bool tts_is_speaking() const override;
+ virtual bool tts_is_paused() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
+
+ virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
+ virtual void tts_pause() override;
+ virtual void tts_resume() override;
+ virtual void tts_stop() override;
+#endif
+
+#ifdef DBUS_ENABLED
+ virtual bool is_dark_mode_supported() const override;
+ virtual bool is_dark_mode() const override;
+
+ virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) override;
+ virtual Error file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) override;
+#endif
+
+ virtual void mouse_set_mode(MouseMode p_mode) override;
+ virtual MouseMode mouse_get_mode() const override;
+
+ virtual void warp_mouse(const Point2i &p_to) override;
+ virtual Point2i mouse_get_position() const override;
+ virtual BitField<MouseButtonMask> mouse_get_button_state() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+ virtual Ref<Image> clipboard_get_image() const override;
+ virtual void clipboard_set_primary(const String &p_text) override;
+ virtual String clipboard_get_primary() const override;
+
+ virtual int get_screen_count() const override;
+ virtual int get_primary_screen() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual Point2i window_get_position_with_decorations(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual void gl_window_make_current(DisplayServer::WindowID p_window_id) override;
+
+ virtual void window_set_transient(WindowID p_window_id, WindowID p_parent) override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_size_with_decorations(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_move_to_foreground(WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual bool window_is_focused(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_can_draw(WindowID p_window_id = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual void window_set_ime_active(const bool p_active, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual void window_set_ime_position(const Point2i &p_pos, WindowID p_window_id = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window_id = MAIN_WINDOW_ID) override;
+ virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_window_id) const override;
+
+ virtual void cursor_set_shape(CursorShape p_shape) override;
+ virtual CursorShape cursor_get_shape() const override;
+ virtual void cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) override;
+
+ virtual int keyboard_get_layout_count() const override;
+ virtual int keyboard_get_current_layout() const override;
+ virtual void keyboard_set_current_layout(int p_index) override;
+ virtual String keyboard_get_layout_language(int p_index) const override;
+ virtual String keyboard_get_layout_name(int p_index) const override;
+ virtual Key keyboard_get_keycode_from_physical(Key p_keycode) const override;
+
+ virtual void process_events() override;
+
+ virtual void release_rendering_thread() override;
+ virtual void make_rendering_thread() override;
+ virtual void swap_buffers() override;
+
+ virtual void set_context(Context p_context) override;
+
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Point2i *p_position, const Size2i &p_resolution, int p_screen, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ static void register_wayland_driver();
+
+ DisplayServerWayland(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerWayland();
+};
+
+#endif // WAYLAND_ENABLED
+
+#endif // DISPLAY_SERVER_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c
new file mode 100644
index 0000000000..eaf43215ce
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.c
@@ -0,0 +1,453 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./generate-wrapper.py 0.3 on 2022-12-12 10:55:19
+// flags: ./generate-wrapper.py --include /usr/include/libdecor-0/libdecor.h --sys-include <libdecor-0/libdecor.h> --soname libdecor-0.so.0 --init-name libdecor --output-header libdecor-so_wrap.h --output-implementation libdecor-so_wrap.c --omit-prefix wl_
+//
+// EDIT: This has been handpatched to properly report the pointer type of the window_state argument of libdecor_configuration_get_window_state.
+#include <stdint.h>
+
+#define libdecor_unref libdecor_unref_dylibloader_orig_libdecor
+#define libdecor_new libdecor_new_dylibloader_orig_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_orig_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_orig_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_orig_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_orig_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_orig_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_orig_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_orig_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_orig_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_orig_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_orig_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_orig_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_orig_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_orig_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_orig_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_orig_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_orig_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_orig_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_orig_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_orig_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_orig_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_orig_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_orig_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_orig_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_orig_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_orig_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_orig_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_orig_libdecor
+#include <libdecor-0/libdecor.h>
+#undef libdecor_unref
+#undef libdecor_new
+#undef libdecor_get_fd
+#undef libdecor_dispatch
+#undef libdecor_decorate
+#undef libdecor_frame_ref
+#undef libdecor_frame_unref
+#undef libdecor_frame_set_visibility
+#undef libdecor_frame_is_visible
+#undef libdecor_frame_set_parent
+#undef libdecor_frame_set_title
+#undef libdecor_frame_get_title
+#undef libdecor_frame_set_app_id
+#undef libdecor_frame_set_capabilities
+#undef libdecor_frame_unset_capabilities
+#undef libdecor_frame_has_capability
+#undef libdecor_frame_show_window_menu
+#undef libdecor_frame_popup_grab
+#undef libdecor_frame_popup_ungrab
+#undef libdecor_frame_translate_coordinate
+#undef libdecor_frame_set_min_content_size
+#undef libdecor_frame_set_max_content_size
+#undef libdecor_frame_resize
+#undef libdecor_frame_move
+#undef libdecor_frame_commit
+#undef libdecor_frame_set_minimized
+#undef libdecor_frame_set_maximized
+#undef libdecor_frame_unset_maximized
+#undef libdecor_frame_set_fullscreen
+#undef libdecor_frame_unset_fullscreen
+#undef libdecor_frame_is_floating
+#undef libdecor_frame_close
+#undef libdecor_frame_map
+#undef libdecor_frame_get_xdg_surface
+#undef libdecor_frame_get_xdg_toplevel
+#undef libdecor_state_new
+#undef libdecor_state_free
+#undef libdecor_configuration_get_content_size
+#undef libdecor_configuration_get_window_state
+#include <dlfcn.h>
+#include <stdio.h>
+void (*libdecor_unref_dylibloader_wrapper_libdecor)(struct libdecor*);
+struct libdecor* (*libdecor_new_dylibloader_wrapper_libdecor)(struct wl_display*,struct libdecor_interface*);
+int (*libdecor_get_fd_dylibloader_wrapper_libdecor)(struct libdecor*);
+int (*libdecor_dispatch_dylibloader_wrapper_libdecor)(struct libdecor*, int);
+struct libdecor_frame* (*libdecor_decorate_dylibloader_wrapper_libdecor)(struct libdecor*,struct wl_surface*,struct libdecor_frame_interface*, void*);
+void (*libdecor_frame_ref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_unref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_visibility_dylibloader_wrapper_libdecor)(struct libdecor_frame*, bool);
+bool (*libdecor_frame_is_visible_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_parent_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_frame*);
+void (*libdecor_frame_set_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+const char* (*libdecor_frame_get_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_app_id_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+void (*libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+bool (*libdecor_frame_has_capability_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+void (*libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t, int, int);
+void (*libdecor_frame_popup_grab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+void (*libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int, int*, int*);
+void (*libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+void (*libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+void (*libdecor_frame_resize_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t,enum libdecor_resize_edge);
+void (*libdecor_frame_move_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t);
+void (*libdecor_frame_commit_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_state*,struct libdecor_configuration*);
+void (*libdecor_frame_set_minimized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_output*);
+void (*libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+bool (*libdecor_frame_is_floating_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_close_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+void (*libdecor_frame_map_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct xdg_surface* (*libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct xdg_toplevel* (*libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+struct libdecor_state* (*libdecor_state_new_dylibloader_wrapper_libdecor)( int, int);
+void (*libdecor_state_free_dylibloader_wrapper_libdecor)(struct libdecor_state*);
+bool (*libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,struct libdecor_frame*, int*, int*);
+bool (*libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,enum libdecor_window_state*);
+int initialize_libdecor(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libdecor-0.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// libdecor_unref
+ *(void **) (&libdecor_unref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_new
+ *(void **) (&libdecor_new_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_new");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_get_fd
+ *(void **) (&libdecor_get_fd_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_get_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_dispatch
+ *(void **) (&libdecor_dispatch_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_dispatch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_decorate
+ *(void **) (&libdecor_decorate_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_decorate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_ref
+ *(void **) (&libdecor_frame_ref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_ref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unref
+ *(void **) (&libdecor_frame_unref_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_visibility
+ *(void **) (&libdecor_frame_set_visibility_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_visibility");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_is_visible
+ *(void **) (&libdecor_frame_is_visible_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_is_visible");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_parent
+ *(void **) (&libdecor_frame_set_parent_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_parent");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_title
+ *(void **) (&libdecor_frame_set_title_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_title");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_title
+ *(void **) (&libdecor_frame_get_title_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_title");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_app_id
+ *(void **) (&libdecor_frame_set_app_id_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_app_id");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_capabilities
+ *(void **) (&libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_capabilities");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_capabilities
+ *(void **) (&libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_capabilities");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_has_capability
+ *(void **) (&libdecor_frame_has_capability_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_has_capability");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_show_window_menu
+ *(void **) (&libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_show_window_menu");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_popup_grab
+ *(void **) (&libdecor_frame_popup_grab_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_popup_grab");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_popup_ungrab
+ *(void **) (&libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_popup_ungrab");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_translate_coordinate
+ *(void **) (&libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_translate_coordinate");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_min_content_size
+ *(void **) (&libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_min_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_max_content_size
+ *(void **) (&libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_max_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_resize
+ *(void **) (&libdecor_frame_resize_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_resize");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_move
+ *(void **) (&libdecor_frame_move_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_move");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_commit
+ *(void **) (&libdecor_frame_commit_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_commit");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_minimized
+ *(void **) (&libdecor_frame_set_minimized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_minimized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_maximized
+ *(void **) (&libdecor_frame_set_maximized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_maximized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_maximized
+ *(void **) (&libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_maximized");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_set_fullscreen
+ *(void **) (&libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_set_fullscreen");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_unset_fullscreen
+ *(void **) (&libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_unset_fullscreen");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_is_floating
+ *(void **) (&libdecor_frame_is_floating_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_is_floating");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_close
+ *(void **) (&libdecor_frame_close_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_close");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_map
+ *(void **) (&libdecor_frame_map_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_map");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_xdg_surface
+ *(void **) (&libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_xdg_surface");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_frame_get_xdg_toplevel
+ *(void **) (&libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_frame_get_xdg_toplevel");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_state_new
+ *(void **) (&libdecor_state_new_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_state_new");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_state_free
+ *(void **) (&libdecor_state_free_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_state_free");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_configuration_get_content_size
+ *(void **) (&libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_configuration_get_content_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// libdecor_configuration_get_window_state
+ *(void **) (&libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor) = dlsym(handle, "libdecor_configuration_get_window_state");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h
new file mode 100644
index 0000000000..bf3f520008
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/libdecor-so_wrap.h
@@ -0,0 +1,175 @@
+#ifndef DYLIBLOAD_WRAPPER_LIBDECOR
+#define DYLIBLOAD_WRAPPER_LIBDECOR
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ./generate-wrapper.py 0.3 on 2022-12-12 10:55:19
+// flags: ./generate-wrapper.py --include /usr/include/libdecor-0/libdecor.h --sys-include <libdecor-0/libdecor.h> --soname libdecor-0.so.0 --init-name libdecor --output-header libdecor-so_wrap.h --output-implementation libdecor-so_wrap.c --omit-prefix wl_
+//
+// EDIT: This has been handpatched to properly report the pointer type of the window_state argument of libdecor_configuration_get_window_state.
+#include <stdint.h>
+
+#define libdecor_unref libdecor_unref_dylibloader_orig_libdecor
+#define libdecor_new libdecor_new_dylibloader_orig_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_orig_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_orig_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_orig_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_orig_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_orig_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_orig_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_orig_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_orig_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_orig_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_orig_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_orig_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_orig_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_orig_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_orig_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_orig_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_orig_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_orig_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_orig_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_orig_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_orig_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_orig_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_orig_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_orig_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_orig_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_orig_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_orig_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_orig_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_orig_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_orig_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_orig_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_orig_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_orig_libdecor
+#include <libdecor-0/libdecor.h>
+#undef libdecor_unref
+#undef libdecor_new
+#undef libdecor_get_fd
+#undef libdecor_dispatch
+#undef libdecor_decorate
+#undef libdecor_frame_ref
+#undef libdecor_frame_unref
+#undef libdecor_frame_set_visibility
+#undef libdecor_frame_is_visible
+#undef libdecor_frame_set_parent
+#undef libdecor_frame_set_title
+#undef libdecor_frame_get_title
+#undef libdecor_frame_set_app_id
+#undef libdecor_frame_set_capabilities
+#undef libdecor_frame_unset_capabilities
+#undef libdecor_frame_has_capability
+#undef libdecor_frame_show_window_menu
+#undef libdecor_frame_popup_grab
+#undef libdecor_frame_popup_ungrab
+#undef libdecor_frame_translate_coordinate
+#undef libdecor_frame_set_min_content_size
+#undef libdecor_frame_set_max_content_size
+#undef libdecor_frame_resize
+#undef libdecor_frame_move
+#undef libdecor_frame_commit
+#undef libdecor_frame_set_minimized
+#undef libdecor_frame_set_maximized
+#undef libdecor_frame_unset_maximized
+#undef libdecor_frame_set_fullscreen
+#undef libdecor_frame_unset_fullscreen
+#undef libdecor_frame_is_floating
+#undef libdecor_frame_close
+#undef libdecor_frame_map
+#undef libdecor_frame_get_xdg_surface
+#undef libdecor_frame_get_xdg_toplevel
+#undef libdecor_state_new
+#undef libdecor_state_free
+#undef libdecor_configuration_get_content_size
+#undef libdecor_configuration_get_window_state
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define libdecor_unref libdecor_unref_dylibloader_wrapper_libdecor
+#define libdecor_new libdecor_new_dylibloader_wrapper_libdecor
+#define libdecor_get_fd libdecor_get_fd_dylibloader_wrapper_libdecor
+#define libdecor_dispatch libdecor_dispatch_dylibloader_wrapper_libdecor
+#define libdecor_decorate libdecor_decorate_dylibloader_wrapper_libdecor
+#define libdecor_frame_ref libdecor_frame_ref_dylibloader_wrapper_libdecor
+#define libdecor_frame_unref libdecor_frame_unref_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_visibility libdecor_frame_set_visibility_dylibloader_wrapper_libdecor
+#define libdecor_frame_is_visible libdecor_frame_is_visible_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_parent libdecor_frame_set_parent_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_title libdecor_frame_set_title_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_title libdecor_frame_get_title_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_app_id libdecor_frame_set_app_id_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_capabilities libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_capabilities libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor
+#define libdecor_frame_has_capability libdecor_frame_has_capability_dylibloader_wrapper_libdecor
+#define libdecor_frame_show_window_menu libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor
+#define libdecor_frame_popup_grab libdecor_frame_popup_grab_dylibloader_wrapper_libdecor
+#define libdecor_frame_popup_ungrab libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor
+#define libdecor_frame_translate_coordinate libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_min_content_size libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_max_content_size libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor
+#define libdecor_frame_resize libdecor_frame_resize_dylibloader_wrapper_libdecor
+#define libdecor_frame_move libdecor_frame_move_dylibloader_wrapper_libdecor
+#define libdecor_frame_commit libdecor_frame_commit_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_minimized libdecor_frame_set_minimized_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_maximized libdecor_frame_set_maximized_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_maximized libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor
+#define libdecor_frame_set_fullscreen libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor
+#define libdecor_frame_unset_fullscreen libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor
+#define libdecor_frame_is_floating libdecor_frame_is_floating_dylibloader_wrapper_libdecor
+#define libdecor_frame_close libdecor_frame_close_dylibloader_wrapper_libdecor
+#define libdecor_frame_map libdecor_frame_map_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_xdg_surface libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor
+#define libdecor_frame_get_xdg_toplevel libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor
+#define libdecor_state_new libdecor_state_new_dylibloader_wrapper_libdecor
+#define libdecor_state_free libdecor_state_free_dylibloader_wrapper_libdecor
+#define libdecor_configuration_get_content_size libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor
+#define libdecor_configuration_get_window_state libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor
+extern void (*libdecor_unref_dylibloader_wrapper_libdecor)(struct libdecor*);
+extern struct libdecor* (*libdecor_new_dylibloader_wrapper_libdecor)(struct wl_display*,struct libdecor_interface*);
+extern int (*libdecor_get_fd_dylibloader_wrapper_libdecor)(struct libdecor*);
+extern int (*libdecor_dispatch_dylibloader_wrapper_libdecor)(struct libdecor*, int);
+extern struct libdecor_frame* (*libdecor_decorate_dylibloader_wrapper_libdecor)(struct libdecor*,struct wl_surface*,struct libdecor_frame_interface*, void*);
+extern void (*libdecor_frame_ref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_unref_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_visibility_dylibloader_wrapper_libdecor)(struct libdecor_frame*, bool);
+extern bool (*libdecor_frame_is_visible_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_parent_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_frame*);
+extern void (*libdecor_frame_set_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern const char* (*libdecor_frame_get_title_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_app_id_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_set_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern void (*libdecor_frame_unset_capabilities_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern bool (*libdecor_frame_has_capability_dylibloader_wrapper_libdecor)(struct libdecor_frame*,enum libdecor_capabilities);
+extern void (*libdecor_frame_show_window_menu_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t, int, int);
+extern void (*libdecor_frame_popup_grab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_popup_ungrab_dylibloader_wrapper_libdecor)(struct libdecor_frame*,const char*);
+extern void (*libdecor_frame_translate_coordinate_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int, int*, int*);
+extern void (*libdecor_frame_set_min_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+extern void (*libdecor_frame_set_max_content_size_dylibloader_wrapper_libdecor)(struct libdecor_frame*, int, int);
+extern void (*libdecor_frame_resize_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t,enum libdecor_resize_edge);
+extern void (*libdecor_frame_move_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_seat*, uint32_t);
+extern void (*libdecor_frame_commit_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct libdecor_state*,struct libdecor_configuration*);
+extern void (*libdecor_frame_set_minimized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_unset_maximized_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_set_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*,struct wl_output*);
+extern void (*libdecor_frame_unset_fullscreen_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern bool (*libdecor_frame_is_floating_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_close_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern void (*libdecor_frame_map_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct xdg_surface* (*libdecor_frame_get_xdg_surface_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct xdg_toplevel* (*libdecor_frame_get_xdg_toplevel_dylibloader_wrapper_libdecor)(struct libdecor_frame*);
+extern struct libdecor_state* (*libdecor_state_new_dylibloader_wrapper_libdecor)( int, int);
+extern void (*libdecor_state_free_dylibloader_wrapper_libdecor)(struct libdecor_state*);
+extern bool (*libdecor_configuration_get_content_size_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,struct libdecor_frame*, int*, int*);
+extern bool (*libdecor_configuration_get_window_state_dylibloader_wrapper_libdecor)(struct libdecor_configuration*,enum libdecor_window_state*);
+int initialize_libdecor(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
new file mode 100644
index 0000000000..0cd8e56a77
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
@@ -0,0 +1,607 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:36:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h" --soname libwayland-client.so.0 --init-name wayland_client --output-header platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
+//
+// NOTE: This has been hand-patched to workaround some issues.
+#include <stdint.h>
+
+#define wl_list_init wl_list_init_dylibloader_orig_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_orig_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_orig_wayland_client
+#define wl_list_length wl_list_length_dylibloader_orig_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_orig_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_orig_wayland_client
+#define wl_array_init wl_array_init_dylibloader_orig_wayland_client
+#define wl_array_release wl_array_release_dylibloader_orig_wayland_client
+#define wl_array_add wl_array_add_dylibloader_orig_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_orig_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_orig_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_orig_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_orig_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_orig_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_orig_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_orig_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_orig_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_orig_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_orig_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_orig_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_orig_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_orig_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_orig_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_orig_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_orig_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_orig_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_orig_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_orig_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_orig_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_orig_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_orig_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_orig_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_orig_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_orig_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_orig_wayland_client
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h"
+#undef wl_list_init
+#undef wl_list_insert
+#undef wl_list_remove
+#undef wl_list_length
+#undef wl_list_empty
+#undef wl_list_insert_list
+#undef wl_array_init
+#undef wl_array_release
+#undef wl_array_add
+#undef wl_array_copy
+#undef wl_event_queue_destroy
+#undef wl_proxy_marshal_flags
+#undef wl_proxy_marshal_array_flags
+#undef wl_proxy_marshal
+#undef wl_proxy_marshal_array
+#undef wl_proxy_create
+#undef wl_proxy_create_wrapper
+#undef wl_proxy_wrapper_destroy
+#undef wl_proxy_marshal_constructor
+#undef wl_proxy_marshal_constructor_versioned
+#undef wl_proxy_marshal_array_constructor
+#undef wl_proxy_marshal_array_constructor_versioned
+#undef wl_proxy_destroy
+#undef wl_proxy_add_listener
+#undef wl_proxy_get_listener
+#undef wl_proxy_add_dispatcher
+#undef wl_proxy_set_user_data
+#undef wl_proxy_get_user_data
+#undef wl_proxy_get_version
+#undef wl_proxy_get_id
+#undef wl_proxy_set_tag
+#undef wl_proxy_get_tag
+#undef wl_proxy_get_class
+#undef wl_proxy_set_queue
+#undef wl_display_connect
+#undef wl_display_connect_to_fd
+#undef wl_display_disconnect
+#undef wl_display_get_fd
+#undef wl_display_dispatch
+#undef wl_display_dispatch_queue
+#undef wl_display_dispatch_queue_pending
+#undef wl_display_dispatch_pending
+#undef wl_display_get_error
+#undef wl_display_get_protocol_error
+#undef wl_display_flush
+#undef wl_display_roundtrip_queue
+#undef wl_display_roundtrip
+#undef wl_display_create_queue
+#undef wl_display_prepare_read_queue
+#undef wl_display_prepare_read
+#undef wl_display_cancel_read
+#undef wl_display_read_events
+#undef wl_log_set_handler_client
+#include <dlfcn.h>
+#include <stdio.h>
+void (*wl_list_init_dylibloader_wrapper_wayland_client)(struct wl_list*);
+void (*wl_list_insert_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+void (*wl_list_remove_dylibloader_wrapper_wayland_client)(struct wl_list*);
+int (*wl_list_length_dylibloader_wrapper_wayland_client)(struct wl_list*);
+int (*wl_list_empty_dylibloader_wrapper_wayland_client)(struct wl_list*);
+void (*wl_list_insert_list_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+void (*wl_array_init_dylibloader_wrapper_wayland_client)(struct wl_array*);
+void (*wl_array_release_dylibloader_wrapper_wayland_client)(struct wl_array*);
+void* (*wl_array_add_dylibloader_wrapper_wayland_client)(struct wl_array*, size_t);
+int (*wl_array_copy_dylibloader_wrapper_wayland_client)(struct wl_array*,struct wl_array*);
+void (*wl_event_queue_destroy_dylibloader_wrapper_wayland_client)(struct wl_event_queue*);
+struct wl_proxy* (*wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,...);
+struct wl_proxy* (*wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,union wl_argument);
+void (*wl_proxy_marshal_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,...);
+void (*wl_proxy_marshal_array_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument);
+struct wl_proxy* (*wl_proxy_create_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const struct wl_interface*);
+void* (*wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client)( void*);
+void (*wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client)( void*);
+struct wl_proxy* (*wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*,...);
+struct wl_proxy* (*wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t,...);
+struct wl_proxy* (*wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*);
+struct wl_proxy* (*wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*, uint32_t);
+void (*wl_proxy_destroy_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+int (*wl_proxy_add_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void(**)(void), void*);
+const void* (*wl_proxy_get_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+int (*wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client)(struct wl_proxy*, wl_dispatcher_func_t,const void*, void*);
+void (*wl_proxy_set_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void*);
+void* (*wl_proxy_get_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+uint32_t (*wl_proxy_get_version_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+uint32_t (*wl_proxy_get_id_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+void (*wl_proxy_set_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const char**);
+const char** (*wl_proxy_get_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+const char* (*wl_proxy_get_class_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+void (*wl_proxy_set_queue_dylibloader_wrapper_wayland_client)(struct wl_proxy*,struct wl_event_queue*);
+struct wl_display* (*wl_display_connect_dylibloader_wrapper_wayland_client)(const char*);
+struct wl_display* (*wl_display_connect_to_fd_dylibloader_wrapper_wayland_client)( int);
+void (*wl_display_disconnect_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_get_fd_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_dispatch_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_dispatch_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_dispatch_pending_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_get_error_dylibloader_wrapper_wayland_client)(struct wl_display*);
+uint32_t (*wl_display_get_protocol_error_dylibloader_wrapper_wayland_client)(struct wl_display*,const struct wl_interface**, uint32_t*);
+int (*wl_display_flush_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_roundtrip_dylibloader_wrapper_wayland_client)(struct wl_display*);
+struct wl_event_queue* (*wl_display_create_queue_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+int (*wl_display_prepare_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+void (*wl_display_cancel_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+int (*wl_display_read_events_dylibloader_wrapper_wayland_client)(struct wl_display*);
+void (*wl_log_set_handler_client_dylibloader_wrapper_wayland_client)( wl_log_func_t);
+int initialize_wayland_client(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-client.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_list_init
+ *(void **) (&wl_list_init_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_init");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_insert
+ *(void **) (&wl_list_insert_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_insert");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_remove
+ *(void **) (&wl_list_remove_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_remove");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_length
+ *(void **) (&wl_list_length_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_length");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_empty
+ *(void **) (&wl_list_empty_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_empty");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_list_insert_list
+ *(void **) (&wl_list_insert_list_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_list_insert_list");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_init
+ *(void **) (&wl_array_init_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_init");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_release
+ *(void **) (&wl_array_release_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_release");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_add
+ *(void **) (&wl_array_add_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_add");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_array_copy
+ *(void **) (&wl_array_copy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_array_copy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_event_queue_destroy
+ *(void **) (&wl_event_queue_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_event_queue_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_flags
+ *(void **) (&wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_flags");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_flags
+ *(void **) (&wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_flags");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal
+ *(void **) (&wl_proxy_marshal_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array
+ *(void **) (&wl_proxy_marshal_array_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_create
+ *(void **) (&wl_proxy_create_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_create");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_create_wrapper
+ *(void **) (&wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_create_wrapper");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_wrapper_destroy
+ *(void **) (&wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_wrapper_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_constructor
+ *(void **) (&wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_constructor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_constructor_versioned
+ *(void **) (&wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_constructor_versioned");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_constructor
+ *(void **) (&wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_constructor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_marshal_array_constructor_versioned
+ *(void **) (&wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_marshal_array_constructor_versioned");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_destroy
+ *(void **) (&wl_proxy_destroy_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_add_listener
+ *(void **) (&wl_proxy_add_listener_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_add_listener");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_listener
+ *(void **) (&wl_proxy_get_listener_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_listener");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_add_dispatcher
+ *(void **) (&wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_add_dispatcher");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_user_data
+ *(void **) (&wl_proxy_set_user_data_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_user_data");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_user_data
+ *(void **) (&wl_proxy_get_user_data_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_user_data");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_version
+ *(void **) (&wl_proxy_get_version_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_version");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_id
+ *(void **) (&wl_proxy_get_id_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_id");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_tag
+ *(void **) (&wl_proxy_set_tag_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_tag");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_tag
+ *(void **) (&wl_proxy_get_tag_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_tag");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_get_class
+ *(void **) (&wl_proxy_get_class_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_get_class");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_proxy_set_queue
+ *(void **) (&wl_proxy_set_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_proxy_set_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_connect
+ *(void **) (&wl_display_connect_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_connect");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_connect_to_fd
+ *(void **) (&wl_display_connect_to_fd_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_connect_to_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_disconnect
+ *(void **) (&wl_display_disconnect_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_disconnect");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_fd
+ *(void **) (&wl_display_get_fd_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_fd");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch
+ *(void **) (&wl_display_dispatch_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_queue
+ *(void **) (&wl_display_dispatch_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_queue_pending
+ *(void **) (&wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_queue_pending");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_dispatch_pending
+ *(void **) (&wl_display_dispatch_pending_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_dispatch_pending");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_error
+ *(void **) (&wl_display_get_error_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_error");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_get_protocol_error
+ *(void **) (&wl_display_get_protocol_error_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_get_protocol_error");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_flush
+ *(void **) (&wl_display_flush_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_flush");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_roundtrip_queue
+ *(void **) (&wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_roundtrip_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_roundtrip
+ *(void **) (&wl_display_roundtrip_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_roundtrip");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_create_queue
+ *(void **) (&wl_display_create_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_create_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_prepare_read_queue
+ *(void **) (&wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_prepare_read_queue");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_prepare_read
+ *(void **) (&wl_display_prepare_read_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_prepare_read");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_cancel_read
+ *(void **) (&wl_display_cancel_read_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_cancel_read");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_display_read_events
+ *(void **) (&wl_display_read_events_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_display_read_events");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_log_set_handler_client
+ *(void **) (&wl_log_set_handler_client_dylibloader_wrapper_wayland_client) = dlsym(handle, "wl_log_set_handler_client");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h
new file mode 100644
index 0000000000..86c51573a5
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h
@@ -0,0 +1,231 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_CLIENT
+#define DYLIBLOAD_WRAPPER_WAYLAND_CLIENT
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:36:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h" --soname libwayland-client.so.0 --init-name wayland_client --output-header platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-client-core-so_wrap.c
+//
+// NOTE: This has been hand-patched to workaround some issues.
+#include <stdint.h>
+
+#define wl_list_init wl_list_init_dylibloader_orig_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_orig_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_orig_wayland_client
+#define wl_list_length wl_list_length_dylibloader_orig_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_orig_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_orig_wayland_client
+#define wl_array_init wl_array_init_dylibloader_orig_wayland_client
+#define wl_array_release wl_array_release_dylibloader_orig_wayland_client
+#define wl_array_add wl_array_add_dylibloader_orig_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_orig_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_orig_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_orig_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_orig_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_orig_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_orig_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_orig_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_orig_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_orig_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_orig_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_orig_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_orig_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_orig_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_orig_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_orig_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_orig_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_orig_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_orig_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_orig_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_orig_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_orig_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_orig_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_orig_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_orig_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_orig_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_orig_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_orig_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_orig_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_orig_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_orig_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_orig_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_orig_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_orig_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_orig_wayland_client
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-client-core.h"
+#undef wl_list_init
+#undef wl_list_insert
+#undef wl_list_remove
+#undef wl_list_length
+#undef wl_list_empty
+#undef wl_list_insert_list
+#undef wl_array_init
+#undef wl_array_release
+#undef wl_array_add
+#undef wl_array_copy
+#undef wl_event_queue_destroy
+#undef wl_proxy_marshal_flags
+#undef wl_proxy_marshal_array_flags
+#undef wl_proxy_marshal
+#undef wl_proxy_marshal_array
+#undef wl_proxy_create
+#undef wl_proxy_create_wrapper
+#undef wl_proxy_wrapper_destroy
+#undef wl_proxy_marshal_constructor
+#undef wl_proxy_marshal_constructor_versioned
+#undef wl_proxy_marshal_array_constructor
+#undef wl_proxy_marshal_array_constructor_versioned
+#undef wl_proxy_destroy
+#undef wl_proxy_add_listener
+#undef wl_proxy_get_listener
+#undef wl_proxy_add_dispatcher
+#undef wl_proxy_set_user_data
+#undef wl_proxy_get_user_data
+#undef wl_proxy_get_version
+#undef wl_proxy_get_id
+#undef wl_proxy_set_tag
+#undef wl_proxy_get_tag
+#undef wl_proxy_get_class
+#undef wl_proxy_set_queue
+#undef wl_display_connect
+#undef wl_display_connect_to_fd
+#undef wl_display_disconnect
+#undef wl_display_get_fd
+#undef wl_display_dispatch
+#undef wl_display_dispatch_queue
+#undef wl_display_dispatch_queue_pending
+#undef wl_display_dispatch_pending
+#undef wl_display_get_error
+#undef wl_display_get_protocol_error
+#undef wl_display_flush
+#undef wl_display_roundtrip_queue
+#undef wl_display_roundtrip
+#undef wl_display_create_queue
+#undef wl_display_prepare_read_queue
+#undef wl_display_prepare_read
+#undef wl_display_cancel_read
+#undef wl_display_read_events
+#undef wl_log_set_handler_client
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_list_init wl_list_init_dylibloader_wrapper_wayland_client
+#define wl_list_insert wl_list_insert_dylibloader_wrapper_wayland_client
+#define wl_list_remove wl_list_remove_dylibloader_wrapper_wayland_client
+#define wl_list_length wl_list_length_dylibloader_wrapper_wayland_client
+#define wl_list_empty wl_list_empty_dylibloader_wrapper_wayland_client
+#define wl_list_insert_list wl_list_insert_list_dylibloader_wrapper_wayland_client
+#define wl_array_init wl_array_init_dylibloader_wrapper_wayland_client
+#define wl_array_release wl_array_release_dylibloader_wrapper_wayland_client
+#define wl_array_add wl_array_add_dylibloader_wrapper_wayland_client
+#define wl_array_copy wl_array_copy_dylibloader_wrapper_wayland_client
+#define wl_event_queue_destroy wl_event_queue_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_flags wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_flags wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal wl_proxy_marshal_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array wl_proxy_marshal_array_dylibloader_wrapper_wayland_client
+#define wl_proxy_create wl_proxy_create_dylibloader_wrapper_wayland_client
+#define wl_proxy_create_wrapper wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client
+#define wl_proxy_wrapper_destroy wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_constructor wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_constructor_versioned wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_constructor wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client
+#define wl_proxy_marshal_array_constructor_versioned wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client
+#define wl_proxy_destroy wl_proxy_destroy_dylibloader_wrapper_wayland_client
+#define wl_proxy_add_listener wl_proxy_add_listener_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_listener wl_proxy_get_listener_dylibloader_wrapper_wayland_client
+#define wl_proxy_add_dispatcher wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_user_data wl_proxy_set_user_data_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_user_data wl_proxy_get_user_data_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_version wl_proxy_get_version_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_id wl_proxy_get_id_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_tag wl_proxy_set_tag_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_tag wl_proxy_get_tag_dylibloader_wrapper_wayland_client
+#define wl_proxy_get_class wl_proxy_get_class_dylibloader_wrapper_wayland_client
+#define wl_proxy_set_queue wl_proxy_set_queue_dylibloader_wrapper_wayland_client
+#define wl_display_connect wl_display_connect_dylibloader_wrapper_wayland_client
+#define wl_display_connect_to_fd wl_display_connect_to_fd_dylibloader_wrapper_wayland_client
+#define wl_display_disconnect wl_display_disconnect_dylibloader_wrapper_wayland_client
+#define wl_display_get_fd wl_display_get_fd_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch wl_display_dispatch_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_queue wl_display_dispatch_queue_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_queue_pending wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client
+#define wl_display_dispatch_pending wl_display_dispatch_pending_dylibloader_wrapper_wayland_client
+#define wl_display_get_error wl_display_get_error_dylibloader_wrapper_wayland_client
+#define wl_display_get_protocol_error wl_display_get_protocol_error_dylibloader_wrapper_wayland_client
+#define wl_display_flush wl_display_flush_dylibloader_wrapper_wayland_client
+#define wl_display_roundtrip_queue wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client
+#define wl_display_roundtrip wl_display_roundtrip_dylibloader_wrapper_wayland_client
+#define wl_display_create_queue wl_display_create_queue_dylibloader_wrapper_wayland_client
+#define wl_display_prepare_read_queue wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client
+#define wl_display_prepare_read wl_display_prepare_read_dylibloader_wrapper_wayland_client
+#define wl_display_cancel_read wl_display_cancel_read_dylibloader_wrapper_wayland_client
+#define wl_display_read_events wl_display_read_events_dylibloader_wrapper_wayland_client
+#define wl_log_set_handler_client wl_log_set_handler_client_dylibloader_wrapper_wayland_client
+extern void (*wl_list_init_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern void (*wl_list_insert_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+extern void (*wl_list_remove_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern int (*wl_list_length_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern int (*wl_list_empty_dylibloader_wrapper_wayland_client)(struct wl_list*);
+extern void (*wl_list_insert_list_dylibloader_wrapper_wayland_client)(struct wl_list*,struct wl_list*);
+extern void (*wl_array_init_dylibloader_wrapper_wayland_client)(struct wl_array*);
+extern void (*wl_array_release_dylibloader_wrapper_wayland_client)(struct wl_array*);
+extern void* (*wl_array_add_dylibloader_wrapper_wayland_client)(struct wl_array*, size_t);
+extern int (*wl_array_copy_dylibloader_wrapper_wayland_client)(struct wl_array*,struct wl_array*);
+extern void (*wl_event_queue_destroy_dylibloader_wrapper_wayland_client)(struct wl_event_queue*);
+extern struct wl_proxy* (*wl_proxy_marshal_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,...);
+extern struct wl_proxy* (*wl_proxy_marshal_array_flags_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t, uint32_t,union wl_argument);
+extern void (*wl_proxy_marshal_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,...);
+extern void (*wl_proxy_marshal_array_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument);
+extern struct wl_proxy* (*wl_proxy_create_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const struct wl_interface*);
+extern void* (*wl_proxy_create_wrapper_dylibloader_wrapper_wayland_client)( void*);
+extern void (*wl_proxy_wrapper_destroy_dylibloader_wrapper_wayland_client)( void*);
+extern struct wl_proxy* (*wl_proxy_marshal_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*,...);
+extern struct wl_proxy* (*wl_proxy_marshal_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,const struct wl_interface*, uint32_t,...);
+extern struct wl_proxy* (*wl_proxy_marshal_array_constructor_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*);
+extern struct wl_proxy* (*wl_proxy_marshal_array_constructor_versioned_dylibloader_wrapper_wayland_client)(struct wl_proxy*, uint32_t,union wl_argument,const struct wl_interface*, uint32_t);
+extern void (*wl_proxy_destroy_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern int (*wl_proxy_add_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void(**)(void), void*);
+extern const void* (*wl_proxy_get_listener_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern int (*wl_proxy_add_dispatcher_dylibloader_wrapper_wayland_client)(struct wl_proxy*, wl_dispatcher_func_t,const void*, void*);
+extern void (*wl_proxy_set_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*, void*);
+extern void* (*wl_proxy_get_user_data_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern uint32_t (*wl_proxy_get_version_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern uint32_t (*wl_proxy_get_id_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern void (*wl_proxy_set_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*,const char**);
+extern const char** (*wl_proxy_get_tag_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern const char* (*wl_proxy_get_class_dylibloader_wrapper_wayland_client)(struct wl_proxy*);
+extern void (*wl_proxy_set_queue_dylibloader_wrapper_wayland_client)(struct wl_proxy*,struct wl_event_queue*);
+extern struct wl_display* (*wl_display_connect_dylibloader_wrapper_wayland_client)(const char*);
+extern struct wl_display* (*wl_display_connect_to_fd_dylibloader_wrapper_wayland_client)( int);
+extern void (*wl_display_disconnect_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_get_fd_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_dispatch_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_dispatch_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_dispatch_queue_pending_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_dispatch_pending_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_get_error_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern uint32_t (*wl_display_get_protocol_error_dylibloader_wrapper_wayland_client)(struct wl_display*,const struct wl_interface**, uint32_t*);
+extern int (*wl_display_flush_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_roundtrip_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_roundtrip_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern struct wl_event_queue* (*wl_display_create_queue_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_prepare_read_queue_dylibloader_wrapper_wayland_client)(struct wl_display*,struct wl_event_queue*);
+extern int (*wl_display_prepare_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern void (*wl_display_cancel_read_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern int (*wl_display_read_events_dylibloader_wrapper_wayland_client)(struct wl_display*);
+extern void (*wl_log_set_handler_client_dylibloader_wrapper_wayland_client)( wl_log_func_t);
+int initialize_wayland_client(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
new file mode 100644
index 0000000000..61ccfbf4f9
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
@@ -0,0 +1,89 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:46:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h" --soname libwayland-cursor.so.0 --init-name wayland_cursor --output-header platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_orig_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_orig_wayland_cursor
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h"
+#undef wl_cursor_theme_load
+#undef wl_cursor_theme_destroy
+#undef wl_cursor_theme_get_cursor
+#undef wl_cursor_image_get_buffer
+#undef wl_cursor_frame
+#undef wl_cursor_frame_and_duration
+#include <dlfcn.h>
+#include <stdio.h>
+struct wl_cursor_theme* (*wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor)(const char*, int,struct wl_shm*);
+void (*wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*);
+struct wl_cursor* (*wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*,const char*);
+struct wl_buffer* (*wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_image*);
+int (*wl_cursor_frame_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t);
+int (*wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t, uint32_t*);
+int initialize_wayland_cursor(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-cursor.so.0", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_cursor_theme_load
+ *(void **) (&wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_load");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_theme_destroy
+ *(void **) (&wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_theme_get_cursor
+ *(void **) (&wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_theme_get_cursor");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_image_get_buffer
+ *(void **) (&wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_image_get_buffer");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_frame
+ *(void **) (&wl_cursor_frame_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_frame");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_cursor_frame_and_duration
+ *(void **) (&wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor) = dlsym(handle, "wl_cursor_frame_and_duration");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h
new file mode 100644
index 0000000000..43520e74a1
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h
@@ -0,0 +1,42 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_CURSOR
+#define DYLIBLOAD_WRAPPER_WAYLAND_CURSOR
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:46:12
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h" --soname libwayland-cursor.so.0 --init-name wayland_cursor --output-header platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-cursor-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_orig_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_orig_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_orig_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_orig_wayland_cursor
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-cursor.h"
+#undef wl_cursor_theme_load
+#undef wl_cursor_theme_destroy
+#undef wl_cursor_theme_get_cursor
+#undef wl_cursor_image_get_buffer
+#undef wl_cursor_frame
+#undef wl_cursor_frame_and_duration
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_cursor_theme_load wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_theme_destroy wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_theme_get_cursor wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_image_get_buffer wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_frame wl_cursor_frame_dylibloader_wrapper_wayland_cursor
+#define wl_cursor_frame_and_duration wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor
+extern struct wl_cursor_theme* (*wl_cursor_theme_load_dylibloader_wrapper_wayland_cursor)(const char*, int,struct wl_shm*);
+extern void (*wl_cursor_theme_destroy_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*);
+extern struct wl_cursor* (*wl_cursor_theme_get_cursor_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_theme*,const char*);
+extern struct wl_buffer* (*wl_cursor_image_get_buffer_dylibloader_wrapper_wayland_cursor)(struct wl_cursor_image*);
+extern int (*wl_cursor_frame_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t);
+extern int (*wl_cursor_frame_and_duration_dylibloader_wrapper_wayland_cursor)(struct wl_cursor*, uint32_t, uint32_t*);
+int initialize_wayland_cursor(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
new file mode 100644
index 0000000000..122241d241
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
@@ -0,0 +1,67 @@
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:49:37
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h" --soname libwayland-egl.so.1 --init-name wayland_egl --output-header platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_egl_window_create wl_egl_window_create_dylibloader_orig_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_orig_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_orig_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_orig_wayland_egl
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h"
+#undef wl_egl_window_create
+#undef wl_egl_window_destroy
+#undef wl_egl_window_resize
+#undef wl_egl_window_get_attached_size
+#include <dlfcn.h>
+#include <stdio.h>
+struct wl_egl_window* (*wl_egl_window_create_dylibloader_wrapper_wayland_egl)(struct wl_surface*, int, int);
+void (*wl_egl_window_destroy_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*);
+void (*wl_egl_window_resize_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int, int, int, int);
+void (*wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int*, int*);
+int initialize_wayland_egl(int verbose) {
+ void *handle;
+ char *error;
+ handle = dlopen("libwayland-egl.so.1", RTLD_LAZY);
+ if (!handle) {
+ if (verbose) {
+ fprintf(stderr, "%s\n", dlerror());
+ }
+ return(1);
+ }
+ dlerror();
+// wl_egl_window_create
+ *(void **) (&wl_egl_window_create_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_create");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_destroy
+ *(void **) (&wl_egl_window_destroy_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_destroy");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_resize
+ *(void **) (&wl_egl_window_resize_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_resize");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// wl_egl_window_get_attached_size
+ *(void **) (&wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl) = dlsym(handle, "wl_egl_window_get_attached_size");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+return 0;
+}
diff --git a/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h
new file mode 100644
index 0000000000..c2643f973f
--- /dev/null
+++ b/platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h
@@ -0,0 +1,34 @@
+#ifndef DYLIBLOAD_WRAPPER_WAYLAND_EGL
+#define DYLIBLOAD_WRAPPER_WAYLAND_EGL
+// This file is generated. Do not edit!
+// see https://github.com/hpvb/dynload-wrapper for details
+// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-25 17:49:37
+// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h --sys-include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h" --soname libwayland-egl.so.1 --init-name wayland_egl --output-header platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.h --output-implementation platform/linuxbsd/wayland/dynwrappers/wayland-egl-core-so_wrap.c
+//
+#include <stdint.h>
+
+#define wl_egl_window_create wl_egl_window_create_dylibloader_orig_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_orig_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_orig_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_orig_wayland_egl
+#include "./thirdparty/linuxbsd_headers/wayland/wayland-egl-core.h"
+#undef wl_egl_window_create
+#undef wl_egl_window_destroy
+#undef wl_egl_window_resize
+#undef wl_egl_window_get_attached_size
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define wl_egl_window_create wl_egl_window_create_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_destroy wl_egl_window_destroy_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_resize wl_egl_window_resize_dylibloader_wrapper_wayland_egl
+#define wl_egl_window_get_attached_size wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl
+extern struct wl_egl_window* (*wl_egl_window_create_dylibloader_wrapper_wayland_egl)(struct wl_surface*, int, int);
+extern void (*wl_egl_window_destroy_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*);
+extern void (*wl_egl_window_resize_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int, int, int, int);
+extern void (*wl_egl_window_get_attached_size_dylibloader_wrapper_wayland_egl)(struct wl_egl_window*, int*, int*);
+int initialize_wayland_egl(int verbose);
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/platform/linuxbsd/wayland/egl_manager_wayland.cpp b/platform/linuxbsd/wayland/egl_manager_wayland.cpp
new file mode 100644
index 0000000000..6cf24277a0
--- /dev/null
+++ b/platform/linuxbsd/wayland/egl_manager_wayland.cpp
@@ -0,0 +1,66 @@
+/**************************************************************************/
+/* egl_manager_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "egl_manager_wayland.h"
+
+#ifdef WAYLAND_ENABLED
+#ifdef EGL_ENABLED
+#ifdef GLES3_ENABLED
+
+const char *EGLManagerWayland::_get_platform_extension_name() const {
+ return "EGL_KHR_platform_wayland";
+}
+
+EGLenum EGLManagerWayland::_get_platform_extension_enum() const {
+ return EGL_PLATFORM_WAYLAND_KHR;
+}
+
+EGLenum EGLManagerWayland::_get_platform_api_enum() const {
+ return EGL_OPENGL_API;
+}
+
+Vector<EGLAttrib> EGLManagerWayland::_get_platform_display_attributes() const {
+ return Vector<EGLAttrib>();
+}
+
+Vector<EGLint> EGLManagerWayland::_get_platform_context_attribs() const {
+ Vector<EGLint> ret;
+ ret.push_back(EGL_CONTEXT_MAJOR_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_CONTEXT_MINOR_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_NONE);
+
+ return ret;
+}
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+#endif // WAYLAND_ENABLED
diff --git a/platform/linuxbsd/wayland/egl_manager_wayland.h b/platform/linuxbsd/wayland/egl_manager_wayland.h
new file mode 100644
index 0000000000..551c126760
--- /dev/null
+++ b/platform/linuxbsd/wayland/egl_manager_wayland.h
@@ -0,0 +1,53 @@
+/**************************************************************************/
+/* egl_manager_wayland.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef EGL_MANAGER_WAYLAND_H
+#define EGL_MANAGER_WAYLAND_H
+
+#ifdef WAYLAND_ENABLED
+#ifdef EGL_ENABLED
+#ifdef GLES3_ENABLED
+
+#include "drivers/egl/egl_manager.h"
+
+class EGLManagerWayland : public EGLManager {
+public:
+ virtual const char *_get_platform_extension_name() const override;
+ virtual EGLenum _get_platform_extension_enum() const override;
+ virtual EGLenum _get_platform_api_enum() const override;
+ virtual Vector<EGLAttrib> _get_platform_display_attributes() const override;
+ virtual Vector<EGLint> _get_platform_context_attribs() const override;
+};
+
+#endif // GLES3_ENABLED
+#endif // EGL_ENABLED
+#endif // WAYLAND_ENABLED
+
+#endif // EGL_MANAGER_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/key_mapping_xkb.cpp b/platform/linuxbsd/wayland/key_mapping_xkb.cpp
new file mode 100644
index 0000000000..bd1a1e3835
--- /dev/null
+++ b/platform/linuxbsd/wayland/key_mapping_xkb.cpp
@@ -0,0 +1,411 @@
+/**************************************************************************/
+/* key_mapping_xkb.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "key_mapping_xkb.h"
+
+void KeyMappingXKB::initialize() {
+ // XKB keycode to Godot Key map.
+
+ xkb_keycode_map[XKB_KEY_Escape] = Key::ESCAPE;
+ xkb_keycode_map[XKB_KEY_Tab] = Key::TAB;
+ xkb_keycode_map[XKB_KEY_ISO_Left_Tab] = Key::BACKTAB;
+ xkb_keycode_map[XKB_KEY_BackSpace] = Key::BACKSPACE;
+ xkb_keycode_map[XKB_KEY_Return] = Key::ENTER;
+ xkb_keycode_map[XKB_KEY_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_Clear] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_Pause] = Key::PAUSE;
+ xkb_keycode_map[XKB_KEY_Print] = Key::PRINT;
+ xkb_keycode_map[XKB_KEY_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_Left] = Key::LEFT;
+ xkb_keycode_map[XKB_KEY_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_Prior] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_Next] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_Shift_L] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Shift_R] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Shift_Lock] = Key::SHIFT;
+ xkb_keycode_map[XKB_KEY_Control_L] = Key::CTRL;
+ xkb_keycode_map[XKB_KEY_Control_R] = Key::CTRL;
+ xkb_keycode_map[XKB_KEY_Meta_L] = Key::META;
+ xkb_keycode_map[XKB_KEY_Meta_R] = Key::META;
+ xkb_keycode_map[XKB_KEY_Alt_L] = Key::ALT;
+ xkb_keycode_map[XKB_KEY_Alt_R] = Key::ALT;
+ xkb_keycode_map[XKB_KEY_Caps_Lock] = Key::CAPSLOCK;
+ xkb_keycode_map[XKB_KEY_Num_Lock] = Key::NUMLOCK;
+ xkb_keycode_map[XKB_KEY_Scroll_Lock] = Key::SCROLLLOCK;
+ xkb_keycode_map[XKB_KEY_less] = Key::QUOTELEFT;
+ xkb_keycode_map[XKB_KEY_grave] = Key::SECTION;
+ xkb_keycode_map[XKB_KEY_Super_L] = Key::META;
+ xkb_keycode_map[XKB_KEY_Super_R] = Key::META;
+ xkb_keycode_map[XKB_KEY_Menu] = Key::MENU;
+ xkb_keycode_map[XKB_KEY_Hyper_L] = Key::HYPER;
+ xkb_keycode_map[XKB_KEY_Hyper_R] = Key::HYPER;
+ xkb_keycode_map[XKB_KEY_Help] = Key::HELP;
+ xkb_keycode_map[XKB_KEY_KP_Space] = Key::SPACE;
+ xkb_keycode_map[XKB_KEY_KP_Tab] = Key::TAB;
+ xkb_keycode_map[XKB_KEY_KP_Enter] = Key::KP_ENTER;
+ xkb_keycode_map[XKB_KEY_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_Left] = Key::LEFT;
+ xkb_keycode_map[XKB_KEY_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_Prior] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_Next] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_Begin] = Key::CLEAR;
+ xkb_keycode_map[XKB_KEY_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_KP_Equal] = Key::EQUAL;
+ xkb_keycode_map[XKB_KEY_KP_Separator] = Key::COMMA;
+ xkb_keycode_map[XKB_KEY_KP_Decimal] = Key::KP_PERIOD;
+ xkb_keycode_map[XKB_KEY_KP_Multiply] = Key::KP_MULTIPLY;
+ xkb_keycode_map[XKB_KEY_KP_Divide] = Key::KP_DIVIDE;
+ xkb_keycode_map[XKB_KEY_KP_Subtract] = Key::KP_SUBTRACT;
+ xkb_keycode_map[XKB_KEY_KP_Add] = Key::KP_ADD;
+ xkb_keycode_map[XKB_KEY_KP_0] = Key::KP_0;
+ xkb_keycode_map[XKB_KEY_KP_1] = Key::KP_1;
+ xkb_keycode_map[XKB_KEY_KP_2] = Key::KP_2;
+ xkb_keycode_map[XKB_KEY_KP_3] = Key::KP_3;
+ xkb_keycode_map[XKB_KEY_KP_4] = Key::KP_4;
+ xkb_keycode_map[XKB_KEY_KP_5] = Key::KP_5;
+ xkb_keycode_map[XKB_KEY_KP_6] = Key::KP_6;
+ xkb_keycode_map[XKB_KEY_KP_7] = Key::KP_7;
+ xkb_keycode_map[XKB_KEY_KP_8] = Key::KP_8;
+ xkb_keycode_map[XKB_KEY_KP_9] = Key::KP_9;
+ // Same keys but with numlock off.
+ xkb_keycode_map[XKB_KEY_KP_Insert] = Key::INSERT;
+ xkb_keycode_map[XKB_KEY_KP_Delete] = Key::KEY_DELETE;
+ xkb_keycode_map[XKB_KEY_KP_End] = Key::END;
+ xkb_keycode_map[XKB_KEY_KP_Down] = Key::DOWN;
+ xkb_keycode_map[XKB_KEY_KP_Page_Down] = Key::PAGEDOWN;
+ xkb_keycode_map[XKB_KEY_KP_Left] = Key::LEFT;
+ // X11 documents this (numpad 5) as "begin of line" but no toolkit seems to interpret it this way.
+ // On Windows this is emitting Key::Clear so for consistency it will be mapped to Key::Clear
+ xkb_keycode_map[XKB_KEY_KP_Begin] = Key::CLEAR;
+ xkb_keycode_map[XKB_KEY_KP_Right] = Key::RIGHT;
+ xkb_keycode_map[XKB_KEY_KP_Home] = Key::HOME;
+ xkb_keycode_map[XKB_KEY_KP_Up] = Key::UP;
+ xkb_keycode_map[XKB_KEY_KP_Page_Up] = Key::PAGEUP;
+ xkb_keycode_map[XKB_KEY_F1] = Key::F1;
+ xkb_keycode_map[XKB_KEY_F2] = Key::F2;
+ xkb_keycode_map[XKB_KEY_F3] = Key::F3;
+ xkb_keycode_map[XKB_KEY_F4] = Key::F4;
+ xkb_keycode_map[XKB_KEY_F5] = Key::F5;
+ xkb_keycode_map[XKB_KEY_F6] = Key::F6;
+ xkb_keycode_map[XKB_KEY_F7] = Key::F7;
+ xkb_keycode_map[XKB_KEY_F8] = Key::F8;
+ xkb_keycode_map[XKB_KEY_F9] = Key::F9;
+ xkb_keycode_map[XKB_KEY_F10] = Key::F10;
+ xkb_keycode_map[XKB_KEY_F11] = Key::F11;
+ xkb_keycode_map[XKB_KEY_F12] = Key::F12;
+ xkb_keycode_map[XKB_KEY_F13] = Key::F13;
+ xkb_keycode_map[XKB_KEY_F14] = Key::F14;
+ xkb_keycode_map[XKB_KEY_F15] = Key::F15;
+ xkb_keycode_map[XKB_KEY_F16] = Key::F16;
+ xkb_keycode_map[XKB_KEY_F17] = Key::F17;
+ xkb_keycode_map[XKB_KEY_F18] = Key::F18;
+ xkb_keycode_map[XKB_KEY_F19] = Key::F19;
+ xkb_keycode_map[XKB_KEY_F20] = Key::F20;
+ xkb_keycode_map[XKB_KEY_F21] = Key::F21;
+ xkb_keycode_map[XKB_KEY_F22] = Key::F22;
+ xkb_keycode_map[XKB_KEY_F23] = Key::F23;
+ xkb_keycode_map[XKB_KEY_F24] = Key::F24;
+ xkb_keycode_map[XKB_KEY_F25] = Key::F25;
+ xkb_keycode_map[XKB_KEY_F26] = Key::F26;
+ xkb_keycode_map[XKB_KEY_F27] = Key::F27;
+ xkb_keycode_map[XKB_KEY_F28] = Key::F28;
+ xkb_keycode_map[XKB_KEY_F29] = Key::F29;
+ xkb_keycode_map[XKB_KEY_F30] = Key::F30;
+ xkb_keycode_map[XKB_KEY_F31] = Key::F31;
+ xkb_keycode_map[XKB_KEY_F32] = Key::F32;
+ xkb_keycode_map[XKB_KEY_F33] = Key::F33;
+ xkb_keycode_map[XKB_KEY_F34] = Key::F34;
+ xkb_keycode_map[XKB_KEY_F35] = Key::F35;
+ xkb_keycode_map[XKB_KEY_yen] = Key::YEN;
+ xkb_keycode_map[XKB_KEY_section] = Key::SECTION;
+ // Media keys.
+ xkb_keycode_map[XKB_KEY_XF86Back] = Key::BACK;
+ xkb_keycode_map[XKB_KEY_XF86Forward] = Key::FORWARD;
+ xkb_keycode_map[XKB_KEY_XF86Stop] = Key::STOP;
+ xkb_keycode_map[XKB_KEY_XF86Refresh] = Key::REFRESH;
+ xkb_keycode_map[XKB_KEY_XF86Favorites] = Key::FAVORITES;
+ xkb_keycode_map[XKB_KEY_XF86OpenURL] = Key::OPENURL;
+ xkb_keycode_map[XKB_KEY_XF86HomePage] = Key::HOMEPAGE;
+ xkb_keycode_map[XKB_KEY_XF86Search] = Key::SEARCH;
+ xkb_keycode_map[XKB_KEY_XF86AudioLowerVolume] = Key::VOLUMEDOWN;
+ xkb_keycode_map[XKB_KEY_XF86AudioMute] = Key::VOLUMEMUTE;
+ xkb_keycode_map[XKB_KEY_XF86AudioRaiseVolume] = Key::VOLUMEUP;
+ xkb_keycode_map[XKB_KEY_XF86AudioPlay] = Key::MEDIAPLAY;
+ xkb_keycode_map[XKB_KEY_XF86AudioStop] = Key::MEDIASTOP;
+ xkb_keycode_map[XKB_KEY_XF86AudioPrev] = Key::MEDIAPREVIOUS;
+ xkb_keycode_map[XKB_KEY_XF86AudioNext] = Key::MEDIANEXT;
+ xkb_keycode_map[XKB_KEY_XF86AudioRecord] = Key::MEDIARECORD;
+ xkb_keycode_map[XKB_KEY_XF86Standby] = Key::STANDBY;
+ // Launch keys.
+ xkb_keycode_map[XKB_KEY_XF86Mail] = Key::LAUNCHMAIL;
+ xkb_keycode_map[XKB_KEY_XF86AudioMedia] = Key::LAUNCHMEDIA;
+ xkb_keycode_map[XKB_KEY_XF86MyComputer] = Key::LAUNCH0;
+ xkb_keycode_map[XKB_KEY_XF86Calculator] = Key::LAUNCH1;
+ xkb_keycode_map[XKB_KEY_XF86Launch0] = Key::LAUNCH2;
+ xkb_keycode_map[XKB_KEY_XF86Launch1] = Key::LAUNCH3;
+ xkb_keycode_map[XKB_KEY_XF86Launch2] = Key::LAUNCH4;
+ xkb_keycode_map[XKB_KEY_XF86Launch3] = Key::LAUNCH5;
+ xkb_keycode_map[XKB_KEY_XF86Launch4] = Key::LAUNCH6;
+ xkb_keycode_map[XKB_KEY_XF86Launch5] = Key::LAUNCH7;
+ xkb_keycode_map[XKB_KEY_XF86Launch6] = Key::LAUNCH8;
+ xkb_keycode_map[XKB_KEY_XF86Launch7] = Key::LAUNCH9;
+ xkb_keycode_map[XKB_KEY_XF86Launch8] = Key::LAUNCHA;
+ xkb_keycode_map[XKB_KEY_XF86Launch9] = Key::LAUNCHB;
+ xkb_keycode_map[XKB_KEY_XF86LaunchA] = Key::LAUNCHC;
+ xkb_keycode_map[XKB_KEY_XF86LaunchB] = Key::LAUNCHD;
+ xkb_keycode_map[XKB_KEY_XF86LaunchC] = Key::LAUNCHE;
+ xkb_keycode_map[XKB_KEY_XF86LaunchD] = Key::LAUNCHF;
+
+ // Scancode to Godot Key map.
+ scancode_map[0x09] = Key::ESCAPE;
+ scancode_map[0x0A] = Key::KEY_1;
+ scancode_map[0x0B] = Key::KEY_2;
+ scancode_map[0x0C] = Key::KEY_3;
+ scancode_map[0x0D] = Key::KEY_4;
+ scancode_map[0x0E] = Key::KEY_5;
+ scancode_map[0x0F] = Key::KEY_6;
+ scancode_map[0x10] = Key::KEY_7;
+ scancode_map[0x11] = Key::KEY_8;
+ scancode_map[0x12] = Key::KEY_9;
+ scancode_map[0x13] = Key::KEY_0;
+ scancode_map[0x14] = Key::MINUS;
+ scancode_map[0x15] = Key::EQUAL;
+ scancode_map[0x16] = Key::BACKSPACE;
+ scancode_map[0x17] = Key::TAB;
+ scancode_map[0x18] = Key::Q;
+ scancode_map[0x19] = Key::W;
+ scancode_map[0x1A] = Key::E;
+ scancode_map[0x1B] = Key::R;
+ scancode_map[0x1C] = Key::T;
+ scancode_map[0x1D] = Key::Y;
+ scancode_map[0x1E] = Key::U;
+ scancode_map[0x1F] = Key::I;
+ scancode_map[0x20] = Key::O;
+ scancode_map[0x21] = Key::P;
+ scancode_map[0x22] = Key::BRACELEFT;
+ scancode_map[0x23] = Key::BRACERIGHT;
+ scancode_map[0x24] = Key::ENTER;
+ scancode_map[0x25] = Key::CTRL; // Left
+ scancode_map[0x26] = Key::A;
+ scancode_map[0x27] = Key::S;
+ scancode_map[0x28] = Key::D;
+ scancode_map[0x29] = Key::F;
+ scancode_map[0x2A] = Key::G;
+ scancode_map[0x2B] = Key::H;
+ scancode_map[0x2C] = Key::J;
+ scancode_map[0x2D] = Key::K;
+ scancode_map[0x2E] = Key::L;
+ scancode_map[0x2F] = Key::SEMICOLON;
+ scancode_map[0x30] = Key::APOSTROPHE;
+ scancode_map[0x31] = Key::SECTION;
+ scancode_map[0x32] = Key::SHIFT; // Left
+ scancode_map[0x33] = Key::BACKSLASH;
+ scancode_map[0x34] = Key::Z;
+ scancode_map[0x35] = Key::X;
+ scancode_map[0x36] = Key::C;
+ scancode_map[0x37] = Key::V;
+ scancode_map[0x38] = Key::B;
+ scancode_map[0x39] = Key::N;
+ scancode_map[0x3A] = Key::M;
+ scancode_map[0x3B] = Key::COMMA;
+ scancode_map[0x3C] = Key::PERIOD;
+ scancode_map[0x3D] = Key::SLASH;
+ scancode_map[0x3E] = Key::SHIFT; // Right
+ scancode_map[0x3F] = Key::KP_MULTIPLY;
+ scancode_map[0x40] = Key::ALT; // Left
+ scancode_map[0x41] = Key::SPACE;
+ scancode_map[0x42] = Key::CAPSLOCK;
+ scancode_map[0x43] = Key::F1;
+ scancode_map[0x44] = Key::F2;
+ scancode_map[0x45] = Key::F3;
+ scancode_map[0x46] = Key::F4;
+ scancode_map[0x47] = Key::F5;
+ scancode_map[0x48] = Key::F6;
+ scancode_map[0x49] = Key::F7;
+ scancode_map[0x4A] = Key::F8;
+ scancode_map[0x4B] = Key::F9;
+ scancode_map[0x4C] = Key::F10;
+ scancode_map[0x4D] = Key::NUMLOCK;
+ scancode_map[0x4E] = Key::SCROLLLOCK;
+ scancode_map[0x4F] = Key::KP_7;
+ scancode_map[0x50] = Key::KP_8;
+ scancode_map[0x51] = Key::KP_9;
+ scancode_map[0x52] = Key::KP_SUBTRACT;
+ scancode_map[0x53] = Key::KP_4;
+ scancode_map[0x54] = Key::KP_5;
+ scancode_map[0x55] = Key::KP_6;
+ scancode_map[0x56] = Key::KP_ADD;
+ scancode_map[0x57] = Key::KP_1;
+ scancode_map[0x58] = Key::KP_2;
+ scancode_map[0x59] = Key::KP_3;
+ scancode_map[0x5A] = Key::KP_0;
+ scancode_map[0x5B] = Key::KP_PERIOD;
+ //scancode_map[0x5C]
+ //scancode_map[0x5D] // Zenkaku Hankaku
+ scancode_map[0x5E] = Key::QUOTELEFT;
+ scancode_map[0x5F] = Key::F11;
+ scancode_map[0x60] = Key::F12;
+ //scancode_map[0x61] // Romaji
+ //scancode_map[0x62] // Katakana
+ //scancode_map[0x63] // Hiragana
+ //scancode_map[0x64] // Henkan
+ //scancode_map[0x65] // Hiragana Katakana
+ //scancode_map[0x66] // Muhenkan
+ scancode_map[0x67] = Key::COMMA; // KP_Separator
+ scancode_map[0x68] = Key::KP_ENTER;
+ scancode_map[0x69] = Key::CTRL; // Right
+ scancode_map[0x6A] = Key::KP_DIVIDE;
+ scancode_map[0x6B] = Key::PRINT;
+ scancode_map[0x6C] = Key::ALT; // Right
+ scancode_map[0x6D] = Key::ENTER;
+ scancode_map[0x6E] = Key::HOME;
+ scancode_map[0x6F] = Key::UP;
+ scancode_map[0x70] = Key::PAGEUP;
+ scancode_map[0x71] = Key::LEFT;
+ scancode_map[0x72] = Key::RIGHT;
+ scancode_map[0x73] = Key::END;
+ scancode_map[0x74] = Key::DOWN;
+ scancode_map[0x75] = Key::PAGEDOWN;
+ scancode_map[0x76] = Key::INSERT;
+ scancode_map[0x77] = Key::KEY_DELETE;
+ //scancode_map[0x78] // Macro
+ scancode_map[0x79] = Key::VOLUMEMUTE;
+ scancode_map[0x7A] = Key::VOLUMEDOWN;
+ scancode_map[0x7B] = Key::VOLUMEUP;
+ //scancode_map[0x7C] // Power
+ scancode_map[0x7D] = Key::EQUAL; // KP_Equal
+ //scancode_map[0x7E] // KP_PlusMinus
+ scancode_map[0x7F] = Key::PAUSE;
+ scancode_map[0x80] = Key::LAUNCH0;
+ scancode_map[0x81] = Key::COMMA; // KP_Comma
+ //scancode_map[0x82] // Hangul
+ //scancode_map[0x83] // Hangul_Hanja
+ scancode_map[0x84] = Key::YEN;
+ scancode_map[0x85] = Key::META; // Left
+ scancode_map[0x86] = Key::META; // Right
+ scancode_map[0x87] = Key::MENU;
+
+ scancode_map[0xA6] = Key::BACK; // On Chromebooks
+ scancode_map[0xA7] = Key::FORWARD; // On Chromebooks
+
+ scancode_map[0xB5] = Key::REFRESH; // On Chromebooks
+
+ scancode_map[0xBF] = Key::F13;
+ scancode_map[0xC0] = Key::F14;
+ scancode_map[0xC1] = Key::F15;
+ scancode_map[0xC2] = Key::F16;
+ scancode_map[0xC3] = Key::F17;
+ scancode_map[0xC4] = Key::F18;
+ scancode_map[0xC5] = Key::F19;
+ scancode_map[0xC6] = Key::F20;
+ scancode_map[0xC7] = Key::F21;
+ scancode_map[0xC8] = Key::F22;
+ scancode_map[0xC9] = Key::F23;
+ scancode_map[0xCA] = Key::F24;
+ scancode_map[0xCB] = Key::F25;
+ scancode_map[0xCC] = Key::F26;
+ scancode_map[0xCD] = Key::F27;
+ scancode_map[0xCE] = Key::F28;
+ scancode_map[0xCF] = Key::F29;
+ scancode_map[0xD0] = Key::F30;
+ scancode_map[0xD1] = Key::F31;
+ scancode_map[0xD2] = Key::F32;
+ scancode_map[0xD3] = Key::F33;
+ scancode_map[0xD4] = Key::F34;
+ scancode_map[0xD5] = Key::F35;
+
+ // Godot to scancode map.
+ for (const KeyValue<unsigned int, Key> &E : scancode_map) {
+ scancode_map_inv[E.value] = E.key;
+ }
+
+ // Scancode to physical location map.
+ // Ctrl.
+ location_map[0x25] = KeyLocation::LEFT;
+ location_map[0x69] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x32] = KeyLocation::LEFT;
+ location_map[0x3E] = KeyLocation::RIGHT;
+ // Alt.
+ location_map[0x40] = KeyLocation::LEFT;
+ location_map[0x6C] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x85] = KeyLocation::LEFT;
+ location_map[0x86] = KeyLocation::RIGHT;
+}
+
+Key KeyMappingXKB::get_keycode(xkb_keycode_t p_keysym) {
+ if (p_keysym >= 0x20 && p_keysym < 0x7E) { // ASCII, maps 1-1
+ if (p_keysym > 0x60 && p_keysym < 0x7B) { // Lowercase ASCII.
+ return (Key)(p_keysym - 32);
+ } else {
+ return (Key)p_keysym;
+ }
+ }
+
+ const Key *key = xkb_keycode_map.getptr(p_keysym);
+ if (key) {
+ return *key;
+ }
+ return Key::NONE;
+}
+
+Key KeyMappingXKB::get_scancode(unsigned int p_code) {
+ const Key *key = scancode_map.getptr(p_code);
+ if (key) {
+ return *key;
+ }
+
+ return Key::NONE;
+}
+
+xkb_keycode_t KeyMappingXKB::get_xkb_keycode(Key p_key) {
+ const unsigned int *key = scancode_map_inv.getptr(p_key);
+ if (key) {
+ return *key;
+ }
+ return 0x00;
+}
+
+KeyLocation KeyMappingXKB::get_location(unsigned int p_code) {
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/linuxbsd/wayland/key_mapping_xkb.h b/platform/linuxbsd/wayland/key_mapping_xkb.h
new file mode 100644
index 0000000000..306a8f25b5
--- /dev/null
+++ b/platform/linuxbsd/wayland/key_mapping_xkb.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* key_mapping_xkb.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef KEY_MAPPING_XKB_H
+#define KEY_MAPPING_XKB_H
+
+#include "core/os/keyboard.h"
+#include "core/templates/hash_map.h"
+
+#ifdef SOWRAP_ENABLED
+#include "xkbcommon-so_wrap.h"
+#else
+#include <xkbcommon/xkbcommon.h>
+#endif // SOWRAP_ENABLED
+
+class KeyMappingXKB {
+ struct HashMapHasherKeys {
+ static _FORCE_INLINE_ uint32_t hash(Key p_key) { return hash_fmix32(static_cast<uint32_t>(p_key)); }
+ static _FORCE_INLINE_ uint32_t hash(unsigned p_key) { return hash_fmix32(p_key); }
+ };
+
+ static inline HashMap<xkb_keycode_t, Key, HashMapHasherKeys> xkb_keycode_map;
+ static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
+ static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
+ static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
+
+ KeyMappingXKB(){};
+
+public:
+ static void initialize();
+
+ static Key get_keycode(xkb_keysym_t p_keysym);
+ static xkb_keycode_t get_xkb_keycode(Key p_keycode);
+ static Key get_scancode(unsigned int p_code);
+ static KeyLocation get_location(unsigned int p_code);
+};
+
+#endif // KEY_MAPPING_XKB_H
diff --git a/platform/linuxbsd/wayland/vulkan_context_wayland.cpp b/platform/linuxbsd/wayland/vulkan_context_wayland.cpp
new file mode 100644
index 0000000000..b3f28a1678
--- /dev/null
+++ b/platform/linuxbsd/wayland/vulkan_context_wayland.cpp
@@ -0,0 +1,59 @@
+/**************************************************************************/
+/* vulkan_context_wayland.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "vulkan_context_wayland.h"
+
+#ifdef VULKAN_ENABLED
+
+#ifdef USE_VOLK
+#include <volk.h>
+#else
+#include <vulkan/vulkan.h>
+#endif
+
+const char *VulkanContextWayland::_get_platform_surface_extension() const {
+ return VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME;
+}
+
+Error VulkanContextWayland::window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) {
+ const WindowPlatformData *wpd = (const WindowPlatformData *)p_platform_data;
+
+ VkWaylandSurfaceCreateInfoKHR createInfo = {};
+ createInfo.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR;
+ createInfo.display = wpd->display;
+ createInfo.surface = wpd->surface;
+
+ VkSurfaceKHR surface = VK_NULL_HANDLE;
+ VkResult err = vkCreateWaylandSurfaceKHR(get_instance(), &createInfo, nullptr, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+ return _window_create(p_window_id, p_vsync_mode, surface, p_width, p_height);
+}
+
+#endif // VULKAN_ENABLED
diff --git a/platform/linuxbsd/wayland/vulkan_context_wayland.h b/platform/linuxbsd/wayland/vulkan_context_wayland.h
new file mode 100644
index 0000000000..b0a7d1ff87
--- /dev/null
+++ b/platform/linuxbsd/wayland/vulkan_context_wayland.h
@@ -0,0 +1,52 @@
+/**************************************************************************/
+/* vulkan_context_wayland.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef VULKAN_CONTEXT_WAYLAND_H
+#define VULKAN_CONTEXT_WAYLAND_H
+
+#ifdef VULKAN_ENABLED
+
+#include "drivers/vulkan/vulkan_context.h"
+
+class VulkanContextWayland : public VulkanContext {
+ const char *_get_platform_surface_extension() const override final;
+
+public:
+ struct WindowPlatformData {
+ struct wl_display *display;
+ struct wl_surface *surface;
+ };
+
+ Error window_create(DisplayServer::WindowID p_window_id, DisplayServer::VSyncMode p_vsync_mode, int p_width, int p_height, const void *p_platform_data) override final;
+};
+
+#endif // VULKAN_ENABLED
+
+#endif // VULKAN_CONTEXT_WAYLAND_H
diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp
new file mode 100644
index 0000000000..0e9c3fb776
--- /dev/null
+++ b/platform/linuxbsd/wayland/wayland_thread.cpp
@@ -0,0 +1,4049 @@
+/**************************************************************************/
+/* wayland_thread.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "wayland_thread.h"
+
+#ifdef WAYLAND_ENABLED
+
+// FIXME: Does this cause issues with *BSDs?
+#include <linux/input-event-codes.h>
+
+// For the actual polling thread.
+#include <poll.h>
+
+// For shared memory buffer creation.
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+// Fix the wl_array_for_each macro to work with C++. This is based on the
+// original from `wayland-util.h` in the Wayland client library.
+#undef wl_array_for_each
+#define wl_array_for_each(pos, array) \
+ for (pos = (decltype(pos))(array)->data; (const char *)pos < ((const char *)(array)->data + (array)->size); (pos)++)
+
+#define WAYLAND_THREAD_DEBUG_LOGS_ENABLED
+#ifdef WAYLAND_THREAD_DEBUG_LOGS_ENABLED
+#define DEBUG_LOG_WAYLAND_THREAD(...) print_verbose(__VA_ARGS__)
+#else
+#define DEBUG_LOG_WAYLAND_THREAD(...)
+#endif
+
+// Read the content pointed by fd into a Vector<uint8_t>.
+Vector<uint8_t> WaylandThread::_read_fd(int fd) {
+ // This is pretty much an arbitrary size.
+ uint32_t chunk_size = 2048;
+
+ LocalVector<uint8_t> data;
+ data.resize(chunk_size);
+
+ uint32_t bytes_read = 0;
+
+ while (true) {
+ ssize_t last_bytes_read = read(fd, data.ptr() + bytes_read, chunk_size);
+ if (last_bytes_read < 0) {
+ ERR_PRINT(vformat("Read error %d.", errno));
+
+ data.clear();
+ break;
+ }
+
+ if (last_bytes_read == 0) {
+ // We're done, we've reached the EOF.
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Done reading %d bytes.", bytes_read));
+
+ close(fd);
+
+ data.resize(bytes_read);
+ break;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Read chunk of %d bytes.", last_bytes_read));
+
+ bytes_read += last_bytes_read;
+
+ // Increase the buffer size by one chunk in preparation of the next read.
+ data.resize(bytes_read + chunk_size);
+ }
+
+ return data;
+}
+
+// Based on the wayland book's shared memory boilerplate (PD/CC0).
+// See: https://wayland-book.com/surfaces/shared-memory.html
+int WaylandThread::_allocate_shm_file(size_t size) {
+ int retries = 100;
+
+ do {
+ // Generate a random name.
+ char name[] = "/wl_shm-godot-XXXXXX";
+ for (long unsigned int i = sizeof(name) - 7; i < sizeof(name) - 1; i++) {
+ name[i] = Math::random('A', 'Z');
+ }
+
+ // Try to open a shared memory object with that name.
+ int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0) {
+ // Success, unlink its name as we just need the file descriptor.
+ shm_unlink(name);
+
+ // Resize the file to the requested length.
+ int ret;
+ do {
+ ret = ftruncate(fd, size);
+ } while (ret < 0 && errno == EINTR);
+
+ if (ret < 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+ }
+
+ retries--;
+ } while (retries > 0 && errno == EEXIST);
+
+ return -1;
+}
+
+// Return the content of a wl_data_offer.
+Vector<uint8_t> WaylandThread::_wl_data_offer_read(struct wl_display *p_display, const char *p_mime, struct wl_data_offer *p_offer) {
+ if (!p_offer) {
+ return Vector<uint8_t>();
+ }
+
+ int fds[2];
+ if (pipe(fds) == 0) {
+ wl_data_offer_receive(p_offer, p_mime, fds[1]);
+
+ // Let the compositor know about the pipe.
+ // NOTE: It's important to just flush and not roundtrip here as we would risk
+ // running some cleanup event, like for example `wl_data_device::leave`. We're
+ // going to wait for the message anyways as the read will probably block if
+ // the compositor doesn't read from the other end of the pipe.
+ wl_display_flush(p_display);
+
+ // Close the write end of the pipe, which we don't need and would otherwise
+ // just stall our next `read`s.
+ close(fds[1]);
+
+ return _read_fd(fds[0]);
+ }
+
+ return Vector<uint8_t>();
+}
+
+// Read the content of a wp_primary_selection_offer.
+Vector<uint8_t> WaylandThread::_wp_primary_selection_offer_read(struct wl_display *p_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *p_offer) {
+ if (!p_offer) {
+ return Vector<uint8_t>();
+ }
+
+ int fds[2];
+ if (pipe(fds) == 0) {
+ // This function expects to return a string, so we can only ask for a MIME of
+ // "text/plain"
+ zwp_primary_selection_offer_v1_receive(p_offer, p_mime, fds[1]);
+
+ // Wait for the compositor to know about the pipe.
+ wl_display_roundtrip(p_display);
+
+ // Close the write end of the pipe, which we don't need and would otherwise
+ // just stall our next `read`s.
+ close(fds[1]);
+
+ return _read_fd(fds[0]);
+ }
+
+ return Vector<uint8_t>();
+}
+
+// Sets up an `InputEventKey` and returns whether it has any meaningful value.
+bool WaylandThread::_seat_state_configure_key_event(SeatState &p_ss, Ref<InputEventKey> p_event, xkb_keycode_t p_keycode, bool p_pressed) {
+ // TODO: Handle keys that release multiple symbols?
+ Key keycode = KeyMappingXKB::get_keycode(xkb_state_key_get_one_sym(p_ss.xkb_state, p_keycode));
+ Key physical_keycode = KeyMappingXKB::get_scancode(p_keycode);
+ KeyLocation key_location = KeyMappingXKB::get_location(p_keycode);
+
+ if (physical_keycode == Key::NONE) {
+ return false;
+ }
+
+ if (keycode == Key::NONE) {
+ keycode = physical_keycode;
+ }
+
+ if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
+ keycode -= 'a' - 'A';
+ }
+
+ p_event->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ p_event->set_shift_pressed(p_ss.shift_pressed);
+ p_event->set_ctrl_pressed(p_ss.ctrl_pressed);
+ p_event->set_alt_pressed(p_ss.alt_pressed);
+ p_event->set_meta_pressed(p_ss.meta_pressed);
+
+ p_event->set_pressed(p_pressed);
+ p_event->set_keycode(keycode);
+ p_event->set_physical_keycode(physical_keycode);
+ p_event->set_location(key_location);
+
+ uint32_t unicode = xkb_state_key_get_utf32(p_ss.xkb_state, p_keycode);
+
+ if (unicode != 0) {
+ p_event->set_key_label(fix_key_label(unicode, keycode));
+ } else {
+ p_event->set_key_label(keycode);
+ }
+
+ if (p_pressed) {
+ p_event->set_unicode(fix_unicode(unicode));
+ }
+
+ // Taken from DisplayServerX11.
+ if (p_event->get_keycode() == Key::BACKTAB) {
+ // Make it consistent across platforms.
+ p_event->set_keycode(Key::TAB);
+ p_event->set_physical_keycode(Key::TAB);
+ p_event->set_shift_pressed(true);
+ }
+
+ return true;
+}
+
+void WaylandThread::_set_current_seat(struct wl_seat *p_seat) {
+ if (p_seat == wl_seat_current) {
+ return;
+ }
+
+ SeatState *old_state = wl_seat_get_seat_state(wl_seat_current);
+
+ if (old_state) {
+ seat_state_unlock_pointer(old_state);
+ }
+
+ SeatState *new_state = wl_seat_get_seat_state(p_seat);
+ seat_state_unlock_pointer(new_state);
+
+ wl_seat_current = p_seat;
+ pointer_set_constraint(pointer_constraint);
+}
+
+// Returns whether it loaded the theme or not.
+bool WaylandThread::_load_cursor_theme(int p_cursor_size) {
+ if (wl_cursor_theme) {
+ wl_cursor_theme_destroy(wl_cursor_theme);
+ wl_cursor_theme = nullptr;
+
+ current_wl_cursor = nullptr;
+ }
+
+ if (cursor_theme_name.is_empty()) {
+ cursor_theme_name = "default";
+ }
+
+ print_verbose(vformat("Loading cursor theme \"%s\" size %d.", cursor_theme_name, p_cursor_size));
+
+ wl_cursor_theme = wl_cursor_theme_load(cursor_theme_name.utf8().get_data(), p_cursor_size, registry.wl_shm);
+
+ ERR_FAIL_NULL_V_MSG(wl_cursor_theme, false, "Can't load any cursor theme.");
+
+ static const char *cursor_names[] = {
+ "left_ptr",
+ "xterm",
+ "hand2",
+ "cross",
+ "watch",
+ "left_ptr_watch",
+ "fleur",
+ "dnd-move",
+ "crossed_circle",
+ "v_double_arrow",
+ "h_double_arrow",
+ "size_bdiag",
+ "size_fdiag",
+ "move",
+ "row_resize",
+ "col_resize",
+ "question_arrow"
+ };
+
+ static const char *cursor_names_fallback[] = {
+ nullptr,
+ nullptr,
+ "pointer",
+ "cross",
+ "wait",
+ "progress",
+ "grabbing",
+ "hand1",
+ "forbidden",
+ "ns-resize",
+ "ew-resize",
+ "fd_double_arrow",
+ "bd_double_arrow",
+ "fleur",
+ "sb_v_double_arrow",
+ "sb_h_double_arrow",
+ "help"
+ };
+
+ for (int i = 0; i < DisplayServer::CURSOR_MAX; i++) {
+ struct wl_cursor *cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names[i]);
+
+ if (!cursor && cursor_names_fallback[i]) {
+ cursor = wl_cursor_theme_get_cursor(wl_cursor_theme, cursor_names_fallback[i]);
+ }
+
+ if (cursor && cursor->image_count > 0) {
+ wl_cursors[i] = cursor;
+ } else {
+ wl_cursors[i] = nullptr;
+ print_verbose("Failed loading cursor: " + String(cursor_names[i]));
+ }
+ }
+
+ return true;
+}
+
+void WaylandThread::_update_scale(int p_scale) {
+ if (p_scale <= cursor_scale) {
+ return;
+ }
+
+ print_verbose(vformat("Bumping cursor scale to %d", p_scale));
+
+ // There's some display that's bigger than the cache, let's update it.
+ cursor_scale = p_scale;
+
+ if (wl_cursor_theme == nullptr) {
+ // Ugh. Either we're still initializing (this must've been called from the
+ // first roundtrips) or we had some error while doing so. We'll trust that it
+ // will be updated for us if needed.
+ return;
+ }
+
+ int cursor_size = unscaled_cursor_size * p_scale;
+
+ if (_load_cursor_theme(cursor_size)) {
+ cursor_set_shape(last_cursor_shape);
+ }
+}
+
+void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version) {
+ RegistryState *registry = (RegistryState *)data;
+ ERR_FAIL_NULL(registry);
+
+ if (strcmp(interface, wl_shm_interface.name) == 0) {
+ registry->wl_shm = (struct wl_shm *)wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);
+ registry->wl_shm_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zxdg_exporter_v1_interface.name) == 0) {
+ registry->wl_exporter = (struct zxdg_exporter_v1 *)wl_registry_bind(wl_registry, name, &zxdg_exporter_v1_interface, 1);
+ registry->wl_exporter_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ registry->wl_compositor = (struct wl_compositor *)wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4);
+ registry->wl_compositor_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
+ registry->wl_subcompositor = (struct wl_subcompositor *)wl_registry_bind(wl_registry, name, &wl_subcompositor_interface, 1);
+ registry->wl_subcompositor_name = name;
+ return;
+ }
+
+ if (strcmp(interface, wl_data_device_manager_interface.name) == 0) {
+ registry->wl_data_device_manager = (struct wl_data_device_manager *)wl_registry_bind(wl_registry, name, &wl_data_device_manager_interface, 3);
+ registry->wl_data_device_manager_name = name;
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_device == nullptr) {
+ ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
+ wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
+ }
+ }
+ return;
+ }
+
+ if (strcmp(interface, wl_output_interface.name) == 0) {
+ struct wl_output *wl_output = (struct wl_output *)wl_registry_bind(wl_registry, name, &wl_output_interface, 2);
+ wl_proxy_tag_godot((struct wl_proxy *)wl_output);
+
+ registry->wl_outputs.push_back(wl_output);
+
+ ScreenState *ss = memnew(ScreenState);
+ ss->wl_output_name = name;
+ ss->wayland_thread = registry->wayland_thread;
+
+ wl_proxy_tag_godot((struct wl_proxy *)wl_output);
+ wl_output_add_listener(wl_output, &wl_output_listener, ss);
+ return;
+ }
+
+ if (strcmp(interface, wl_seat_interface.name) == 0) {
+ struct wl_seat *wl_seat = (struct wl_seat *)wl_registry_bind(wl_registry, name, &wl_seat_interface, 5);
+ wl_proxy_tag_godot((struct wl_proxy *)wl_seat);
+
+ SeatState *ss = memnew(SeatState);
+ ss->wl_seat = wl_seat;
+ ss->wl_seat_name = name;
+
+ ss->registry = registry;
+ ss->wayland_thread = registry->wayland_thread;
+
+ // Some extra stuff depends on other globals. We'll initialize them if the
+ // globals are already there, otherwise we'll have to do that once and if they
+ // get announced.
+ //
+ // NOTE: Don't forget to also bind/destroy with the respective global.
+ if (!ss->wl_data_device && registry->wl_data_device_manager) {
+ // Clipboard & DnD.
+ ss->wl_data_device = wl_data_device_manager_get_data_device(registry->wl_data_device_manager, wl_seat);
+ wl_data_device_add_listener(ss->wl_data_device, &wl_data_device_listener, ss);
+ }
+
+ if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
+ // Primary selection.
+ ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
+ zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (!ss->wp_tablet_seat && registry->wp_tablet_manager) {
+ // Tablet.
+ ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
+ zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
+ }
+#endif
+
+ registry->wl_seats.push_back(wl_seat);
+
+ wl_seat_add_listener(wl_seat, &wl_seat_listener, ss);
+
+ if (registry->wayland_thread->wl_seat_current == nullptr) {
+ registry->wayland_thread->_set_current_seat(wl_seat);
+ }
+
+ return;
+ }
+
+ if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(5, (int)version)));
+ registry->xdg_wm_base_name = name;
+
+ xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr);
+ return;
+ }
+
+ if (strcmp(interface, wp_viewporter_interface.name) == 0) {
+ registry->wp_viewporter = (struct wp_viewporter *)wl_registry_bind(wl_registry, name, &wp_viewporter_interface, 1);
+ registry->wp_viewporter_name = name;
+ }
+
+ if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0) {
+ registry->wp_fractional_scale_manager = (struct wp_fractional_scale_manager_v1 *)wl_registry_bind(wl_registry, name, &wp_fractional_scale_manager_v1_interface, 1);
+ registry->wp_fractional_scale_manager_name = name;
+
+ // NOTE: We're not mapping the fractional scale object here because this is
+ // supposed to be a "startup global". If for some reason this isn't true (who
+ // knows), add a conditional branch for creating the add-on object.
+ }
+
+ if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) {
+ registry->xdg_decoration_manager = (struct zxdg_decoration_manager_v1 *)wl_registry_bind(wl_registry, name, &zxdg_decoration_manager_v1_interface, 1);
+ registry->xdg_decoration_manager_name = name;
+ return;
+ }
+
+ if (strcmp(interface, xdg_activation_v1_interface.name) == 0) {
+ registry->xdg_activation = (struct xdg_activation_v1 *)wl_registry_bind(wl_registry, name, &xdg_activation_v1_interface, 1);
+ registry->xdg_activation_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_primary_selection_device_manager_v1_interface.name) == 0) {
+ registry->wp_primary_selection_device_manager = (struct zwp_primary_selection_device_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_primary_selection_device_manager_v1_interface, 1);
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->wp_primary_selection_device && registry->wp_primary_selection_device_manager) {
+ ss->wp_primary_selection_device = zwp_primary_selection_device_manager_v1_get_device(registry->wp_primary_selection_device_manager, wl_seat);
+ zwp_primary_selection_device_v1_add_listener(ss->wp_primary_selection_device, &wp_primary_selection_device_listener, ss);
+ }
+ }
+ }
+
+ if (strcmp(interface, zwp_relative_pointer_manager_v1_interface.name) == 0) {
+ registry->wp_relative_pointer_manager = (struct zwp_relative_pointer_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_relative_pointer_manager_v1_interface, 1);
+ registry->wp_relative_pointer_manager_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_pointer_constraints_v1_interface.name) == 0) {
+ registry->wp_pointer_constraints = (struct zwp_pointer_constraints_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_constraints_v1_interface, 1);
+ registry->wp_pointer_constraints_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_pointer_gestures_v1_interface.name) == 0) {
+ registry->wp_pointer_gestures = (struct zwp_pointer_gestures_v1 *)wl_registry_bind(wl_registry, name, &zwp_pointer_gestures_v1_interface, 1);
+ registry->wp_pointer_gestures_name = name;
+ return;
+ }
+
+ if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) {
+ registry->wp_idle_inhibit_manager = (struct zwp_idle_inhibit_manager_v1 *)wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, 1);
+ registry->wp_idle_inhibit_manager_name = name;
+ return;
+ }
+
+#if 0
+ // FIXME: Broken.
+ if (strcmp(interface, zwp_tablet_manager_v2_interface.name) == 0) {
+ registry->wp_tablet_manager = (struct zwp_tablet_manager_v2 *)wl_registry_bind(wl_registry, name, &zwp_tablet_manager_v2_interface, 1);
+ registry->wp_tablet_manager_name = name;
+
+ // This global creates some seats data. Let's do that for the ones already available.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ ss->wp_tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(registry->wp_tablet_manager, wl_seat);
+ zwp_tablet_seat_v2_add_listener(ss->wp_tablet_seat, &wp_tablet_seat_listener, ss);
+ }
+
+ return;
+ }
+#endif
+}
+
+void WaylandThread::_wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name) {
+ RegistryState *registry = (RegistryState *)data;
+ ERR_FAIL_NULL(registry);
+
+ if (name == registry->wl_shm_name) {
+ if (registry->wl_shm) {
+ wl_shm_destroy(registry->wl_shm);
+ registry->wl_shm = nullptr;
+ }
+
+ registry->wl_shm_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_exporter_name) {
+ if (registry->wl_exporter) {
+ zxdg_exporter_v1_destroy(registry->wl_exporter);
+ registry->wl_exporter = nullptr;
+ }
+
+ registry->wl_exporter_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_compositor_name) {
+ if (registry->wl_compositor) {
+ wl_compositor_destroy(registry->wl_compositor);
+ registry->wl_compositor = nullptr;
+ }
+
+ registry->wl_compositor_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_subcompositor_name) {
+ if (registry->wl_subcompositor) {
+ wl_subcompositor_destroy(registry->wl_subcompositor);
+ registry->wl_subcompositor = nullptr;
+ }
+
+ registry->wl_subcompositor_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wl_data_device_manager_name) {
+ if (registry->wl_data_device_manager) {
+ wl_data_device_manager_destroy(registry->wl_data_device_manager);
+ registry->wl_data_device_manager = nullptr;
+ }
+
+ registry->wl_data_device_manager_name = 0;
+
+ // This global is used to create some seat data. Let's clean it.
+ for (struct wl_seat *wl_seat : registry->wl_seats) {
+ SeatState *ss = wl_seat_get_seat_state(wl_seat);
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_device) {
+ wl_data_device_destroy(ss->wl_data_device);
+ ss->wl_data_device = nullptr;
+ }
+
+ ss->wl_data_device = nullptr;
+ }
+
+ return;
+ }
+
+ if (name == registry->xdg_wm_base_name) {
+ if (registry->xdg_wm_base) {
+ xdg_wm_base_destroy(registry->xdg_wm_base);
+ registry->xdg_wm_base = nullptr;
+ }
+
+ registry->xdg_wm_base_name = 0;
+
+ return;
+ }
+
+ if (name == registry->wp_viewporter_name) {
+ WindowState *ws = &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));
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor) {
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) {
+}
+
+void WaylandThread::_wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.position.x = x;
+
+ ss->pending_data.position.x = x;
+ ss->pending_data.position.y = y;
+
+ ss->pending_data.physical_size.width = physical_width;
+ ss->pending_data.physical_size.height = physical_height;
+
+ ss->pending_data.make.parse_utf8(make);
+ ss->pending_data.model.parse_utf8(model);
+}
+
+void WaylandThread::_wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.size.width = width;
+ ss->pending_data.size.height = height;
+
+ ss->pending_data.refresh_rate = refresh ? refresh / 1000.0f : -1;
+}
+
+void WaylandThread::_wl_output_on_done(void *data, struct wl_output *wl_output) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->data = ss->pending_data;
+
+ ss->wayland_thread->_update_scale(ss->data.scale);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x done.", (size_t)wl_output));
+}
+
+void WaylandThread::_wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor) {
+ ScreenState *ss = (ScreenState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->pending_data.scale = factor;
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Output %x scale %d", (size_t)wl_output, factor));
+}
+
+void WaylandThread::_wl_output_on_name(void *data, struct wl_output *wl_output, const char *name) {
+}
+
+void WaylandThread::_wl_output_on_description(void *data, struct wl_output *wl_output, const char *description) {
+}
+
+void WaylandThread::_xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) {
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+void WaylandThread::_xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) {
+ xdg_surface_ack_configure(xdg_surface, serial);
+
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("xdg surface on configure width %d height %d", ws->rect.size.width, ws->rect.size.height));
+}
+
+void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ // Expect the window to be in windowed mode. The mode will get overridden if
+ // the compositor reports otherwise.
+ ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ uint32_t *state = nullptr;
+ wl_array_for_each(state, states) {
+ switch (*state) {
+ case XDG_TOPLEVEL_STATE_MAXIMIZED: {
+ ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
+ } break;
+
+ case XDG_TOPLEVEL_STATE_FULLSCREEN: {
+ ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
+ } break;
+
+ default: {
+ // We don't care about the other states (for now).
+ } break;
+ }
+ }
+
+ if (width != 0 && height != 0) {
+ window_state_update_size(ws, width, height);
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("XDG toplevel on configure width %d height %d.", width, height));
+}
+
+void WaylandThread::_xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
+ ws->wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) {
+}
+
+void WaylandThread::_xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->can_maximize = false;
+ ws->can_fullscreen = false;
+ ws->can_minimize = false;
+
+ uint32_t *capability = nullptr;
+ wl_array_for_each(capability, capabilities) {
+ switch (*capability) {
+ case XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE: {
+ ws->can_maximize = true;
+ } break;
+ case XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN: {
+ ws->can_fullscreen = true;
+ } break;
+
+ case XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE: {
+ ws->can_minimize = true;
+ } break;
+
+ default: {
+ } break;
+ }
+ }
+}
+
+void WaylandThread::_xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->exported_handle = vformat("wayland:%s", String::utf8(handle));
+}
+
+void WaylandThread::_xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) {
+ if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE) {
+#ifdef LIBDECOR_ENABLED
+ WARN_PRINT_ONCE("Native client side decorations are not yet supported without libdecor!");
+#else
+ WARN_PRINT_ONCE("Native client side decorations are not yet supported!");
+#endif // LIBDECOR_ENABLED
+ }
+}
+
+#ifdef LIBDECOR_ENABLED
+void WaylandThread::libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message) {
+ ERR_PRINT(vformat("libdecor error %d: %s", error, message));
+}
+
+// NOTE: This is pretty much a reimplementation of _xdg_surface_on_configure
+// and _xdg_toplevel_on_configure. Libdecor really likes wrapping everything,
+// forcing us to do stuff like this.
+void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data) {
+ WindowState *ws = (WindowState *)user_data;
+ ERR_FAIL_NULL(ws);
+
+ int width = 0;
+ int height = 0;
+
+ ws->pending_libdecor_configuration = configuration;
+
+ if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) {
+ // The configuration doesn't have a size. We'll use the one already set in the window.
+ width = ws->rect.size.width;
+ height = ws->rect.size.height;
+ }
+
+ ERR_FAIL_COND_MSG(width == 0 || height == 0, "Window has invalid size.");
+
+ libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE;
+
+ // Expect the window to be in windowed mode. The mode will get overridden if
+ // the compositor reports otherwise.
+ ws->mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ if (libdecor_configuration_get_window_state(configuration, &window_state)) {
+ if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) {
+ ws->mode = DisplayServer::WINDOW_MODE_MAXIMIZED;
+ }
+
+ if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) {
+ ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
+ }
+ }
+
+ window_state_update_size(ws, width, height);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("libdecor frame on configure rect %s", ws->rect));
+}
+
+void WaylandThread::libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data) {
+ WindowState *ws = (WindowState *)user_data;
+ ERR_FAIL_NULL(ws);
+
+ Ref<WindowEventMessage> winevent_msg;
+ winevent_msg.instantiate();
+ winevent_msg->event = DisplayServer::WINDOW_EVENT_CLOSE_REQUEST;
+
+ ws->wayland_thread->push_message(winevent_msg);
+
+ DEBUG_LOG_WAYLAND_THREAD("libdecor frame on close");
+}
+
+void WaylandThread::libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data) {
+ // We're skipping this as we don't really care about libdecor's commit for
+ // atomicity reasons. See `_frame_wl_callback_on_done` for more info.
+
+ DEBUG_LOG_WAYLAND_THREAD("libdecor frame on commit");
+}
+
+void WaylandThread::libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) {
+}
+#endif // LIBDECOR_ENABLED
+
+void WaylandThread::_wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) {
+ SeatState *ss = (SeatState *)data;
+
+ ERR_FAIL_NULL(ss);
+
+ // TODO: Handle touch.
+
+ // Pointer handling.
+ if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
+ ss->cursor_surface = wl_compositor_create_surface(ss->registry->wl_compositor);
+ ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
+ wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
+ wl_surface_commit(ss->cursor_surface);
+
+ ss->wl_pointer = wl_seat_get_pointer(wl_seat);
+ wl_pointer_add_listener(ss->wl_pointer, &wl_pointer_listener, ss);
+
+ if (ss->registry->wp_relative_pointer_manager) {
+ ss->wp_relative_pointer = zwp_relative_pointer_manager_v1_get_relative_pointer(ss->registry->wp_relative_pointer_manager, ss->wl_pointer);
+ zwp_relative_pointer_v1_add_listener(ss->wp_relative_pointer, &wp_relative_pointer_listener, ss);
+ }
+
+ if (ss->registry->wp_pointer_gestures) {
+ ss->wp_pointer_gesture_pinch = zwp_pointer_gestures_v1_get_pinch_gesture(ss->registry->wp_pointer_gestures, ss->wl_pointer);
+ zwp_pointer_gesture_pinch_v1_add_listener(ss->wp_pointer_gesture_pinch, &wp_pointer_gesture_pinch_listener, ss);
+ }
+
+ // TODO: Constrain new pointers if the global mouse mode is constrained.
+ } else {
+ if (ss->cursor_frame_callback) {
+ // Just in case. I got bitten by weird race-like conditions already.
+ wl_callback_set_user_data(ss->cursor_frame_callback, nullptr);
+
+ wl_callback_destroy(ss->cursor_frame_callback);
+ ss->cursor_frame_callback = nullptr;
+ }
+
+ if (ss->cursor_surface) {
+ wl_surface_destroy(ss->cursor_surface);
+ ss->cursor_surface = nullptr;
+ }
+
+ if (ss->wl_pointer) {
+ wl_pointer_destroy(ss->wl_pointer);
+ ss->wl_pointer = nullptr;
+ }
+
+ if (ss->wp_relative_pointer) {
+ zwp_relative_pointer_v1_destroy(ss->wp_relative_pointer);
+ ss->wp_relative_pointer = nullptr;
+ }
+
+ if (ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(ss->wp_confined_pointer);
+ ss->wp_confined_pointer = nullptr;
+ }
+
+ if (ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(ss->wp_locked_pointer);
+ ss->wp_locked_pointer = nullptr;
+ }
+ }
+
+ // Keyboard handling.
+ if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) {
+ ss->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ ERR_FAIL_NULL(ss->xkb_context);
+
+ ss->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(ss->wl_keyboard, &wl_keyboard_listener, ss);
+ } else {
+ if (ss->xkb_context) {
+ xkb_context_unref(ss->xkb_context);
+ ss->xkb_context = nullptr;
+ }
+
+ if (ss->wl_keyboard) {
+ wl_keyboard_destroy(ss->wl_keyboard);
+ ss->wl_keyboard = nullptr;
+ }
+ }
+}
+
+void WaylandThread::_wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name) {
+}
+
+void WaylandThread::_cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms) {
+ wl_callback_destroy(wl_callback);
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->cursor_time_ms = time_ms;
+
+ ss->cursor_frame_callback = wl_surface_frame(ss->cursor_surface);
+ wl_callback_add_listener(ss->cursor_frame_callback, &cursor_frame_callback_listener, ss);
+ wl_surface_commit(ss->cursor_surface);
+
+ seat_state_update_cursor(ss);
+}
+
+void WaylandThread::_wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ return;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD("Pointing window.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ERR_FAIL_NULL(ss->cursor_surface);
+ ss->pointer_enter_serial = serial;
+ ss->pointed_surface = surface;
+ ss->last_pointed_surface = surface;
+
+ seat_state_update_cursor(ss);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+
+ ss->wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
+ if (!surface || !wl_proxy_is_godot((struct wl_proxy *)surface)) {
+ return;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD("Left window.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->pointed_surface = nullptr;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ // TODO: Scale only when sending the Wayland message.
+ pd.position.x = wl_fixed_to_int(surface_x);
+ pd.position.y = wl_fixed_to_int(surface_y);
+
+ pd.position = scale_vector2i(pd.position, window_state_get_scale_factor(ws));
+
+ pd.motion_time = time;
+}
+
+void WaylandThread::_wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ MouseButton button_pressed = MouseButton::NONE;
+
+ switch (button) {
+ case BTN_LEFT:
+ button_pressed = MouseButton::LEFT;
+ break;
+
+ case BTN_MIDDLE:
+ button_pressed = MouseButton::MIDDLE;
+ break;
+
+ case BTN_RIGHT:
+ button_pressed = MouseButton::RIGHT;
+ break;
+
+ case BTN_EXTRA:
+ button_pressed = MouseButton::MB_XBUTTON1;
+ break;
+
+ case BTN_SIDE:
+ button_pressed = MouseButton::MB_XBUTTON2;
+ break;
+
+ default: {
+ }
+ }
+
+ MouseButtonMask mask = mouse_button_to_mask(button_pressed);
+
+ if (state & WL_POINTER_BUTTON_STATE_PRESSED) {
+ pd.pressed_button_mask.set_flag(mask);
+ pd.last_button_pressed = button_pressed;
+ pd.double_click_begun = true;
+ } else {
+ pd.pressed_button_mask.clear_flag(mask);
+ }
+
+ pd.button_time = time;
+ pd.button_serial = serial;
+}
+
+void WaylandThread::_wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ switch (axis) {
+ case WL_POINTER_AXIS_VERTICAL_SCROLL: {
+ pd.scroll_vector.y = wl_fixed_to_double(value);
+ } break;
+
+ case WL_POINTER_AXIS_HORIZONTAL_SCROLL: {
+ pd.scroll_vector.x = wl_fixed_to_double(value);
+ } break;
+ }
+
+ pd.button_time = time;
+}
+
+void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ wayland_thread->_set_current_seat(ss->wl_seat);
+
+ PointerData &old_pd = ss->pointer_data;
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (old_pd.motion_time != pd.motion_time || old_pd.relative_motion_time != pd.relative_motion_time) {
+ Ref<InputEventMouseMotion> mm;
+ mm.instantiate();
+
+ // Set all pressed modifiers.
+ mm->set_shift_pressed(ss->shift_pressed);
+ mm->set_ctrl_pressed(ss->ctrl_pressed);
+ mm->set_alt_pressed(ss->alt_pressed);
+ mm->set_meta_pressed(ss->meta_pressed);
+
+ mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mm->set_button_mask(pd.pressed_button_mask);
+ mm->set_position(pd.position);
+ mm->set_global_position(pd.position);
+
+ Vector2i pos_delta = pd.position - old_pd.position;
+
+ if (old_pd.relative_motion_time != pd.relative_motion_time) {
+ uint32_t time_delta = pd.relative_motion_time - old_pd.relative_motion_time;
+
+ mm->set_relative(pd.relative_motion);
+ mm->set_velocity((Vector2)pos_delta / time_delta);
+ } else {
+ // The spec includes the possibility of having motion events without an
+ // associated relative motion event. If that's the case, fallback to a
+ // simple delta of the position. The captured mouse won't report the
+ // relative speed anymore though.
+ uint32_t time_delta = pd.motion_time - old_pd.motion_time;
+
+ mm->set_relative(pd.position - old_pd.position);
+ mm->set_velocity((Vector2)pos_delta / time_delta);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mm;
+
+ wayland_thread->push_message(msg);
+ }
+
+ if (pd.discrete_scroll_vector - old_pd.discrete_scroll_vector != Vector2i()) {
+ // This is a discrete scroll (eg. from a scroll wheel), so we'll just emit
+ // scroll wheel buttons.
+ if (pd.scroll_vector.y != 0) {
+ MouseButton button = pd.scroll_vector.y > 0 ? MouseButton::WHEEL_DOWN : MouseButton::WHEEL_UP;
+ pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
+ }
+
+ if (pd.scroll_vector.x != 0) {
+ MouseButton button = pd.scroll_vector.x > 0 ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT;
+ pd.pressed_button_mask.set_flag(mouse_button_to_mask(button));
+ }
+ } else {
+ if (pd.scroll_vector - old_pd.scroll_vector != Vector2()) {
+ // This is a continuous scroll, so we'll emit a pan gesture.
+ Ref<InputEventPanGesture> pg;
+ pg.instantiate();
+
+ // Set all pressed modifiers.
+ pg->set_shift_pressed(ss->shift_pressed);
+ pg->set_ctrl_pressed(ss->ctrl_pressed);
+ pg->set_alt_pressed(ss->alt_pressed);
+ pg->set_meta_pressed(ss->meta_pressed);
+
+ pg->set_position(pd.position);
+
+ pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ pg->set_delta(pd.scroll_vector);
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = pg;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+
+ if (old_pd.pressed_button_mask != pd.pressed_button_mask) {
+ BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((uint32_t)old_pd.pressed_button_mask ^ (uint32_t)pd.pressed_button_mask);
+
+ const MouseButton buttons_to_test[] = {
+ MouseButton::LEFT,
+ MouseButton::MIDDLE,
+ MouseButton::RIGHT,
+ MouseButton::WHEEL_UP,
+ MouseButton::WHEEL_DOWN,
+ MouseButton::WHEEL_LEFT,
+ MouseButton::WHEEL_RIGHT,
+ MouseButton::MB_XBUTTON1,
+ MouseButton::MB_XBUTTON2,
+ };
+
+ for (MouseButton test_button : buttons_to_test) {
+ MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
+ if (pressed_mask_delta.has_flag(test_button_mask)) {
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+
+ // Set all pressed modifiers.
+ mb->set_shift_pressed(ss->shift_pressed);
+ mb->set_ctrl_pressed(ss->ctrl_pressed);
+ mb->set_alt_pressed(ss->alt_pressed);
+ mb->set_meta_pressed(ss->meta_pressed);
+
+ mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mb->set_position(pd.position);
+ mb->set_global_position(pd.position);
+
+ if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN) {
+ // If this is a discrete scroll, specify how many "clicks" it did for this
+ // pointer frame.
+ mb->set_factor(abs(pd.discrete_scroll_vector.y));
+ }
+
+ if (test_button == MouseButton::WHEEL_RIGHT || test_button == MouseButton::WHEEL_LEFT) {
+ // If this is a discrete scroll, specify how many "clicks" it did for this
+ // pointer frame.
+ mb->set_factor(abs(pd.discrete_scroll_vector.x));
+ }
+
+ mb->set_button_mask(pd.pressed_button_mask);
+
+ mb->set_button_index(test_button);
+ mb->set_pressed(pd.pressed_button_mask.has_flag(test_button_mask));
+
+ // We have to set the last position pressed here as we can't take for
+ // granted what the individual events might have seen due to them not having
+ // a garaunteed order.
+ if (mb->is_pressed()) {
+ pd.last_pressed_position = pd.position;
+ }
+
+ if (old_pd.double_click_begun && mb->is_pressed() && pd.last_button_pressed == old_pd.last_button_pressed && (pd.button_time - old_pd.button_time) < 400 && Vector2(old_pd.last_pressed_position).distance_to(Vector2(pd.last_pressed_position)) < 5) {
+ pd.double_click_begun = false;
+ mb->set_double_click(true);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mb;
+
+ wayland_thread->push_message(msg);
+
+ // Send an event resetting immediately the wheel key.
+ // Wayland specification defines axis_stop events as optional and says to
+ // treat all axis events as unterminated. As such, we have to manually do
+ // it ourselves.
+ if (test_button == MouseButton::WHEEL_UP || test_button == MouseButton::WHEEL_DOWN || test_button == MouseButton::WHEEL_LEFT || test_button == MouseButton::WHEEL_RIGHT) {
+ // FIXME: This is ugly, I can't find a clean way to clone an InputEvent.
+ // This works for now, despite being horrible.
+ Ref<InputEventMouseButton> wh_up;
+ wh_up.instantiate();
+
+ wh_up->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ wh_up->set_position(pd.position);
+ wh_up->set_global_position(pd.position);
+
+ // We have to unset the button to avoid it getting stuck.
+ pd.pressed_button_mask.clear_flag(test_button_mask);
+ wh_up->set_button_mask(pd.pressed_button_mask);
+
+ wh_up->set_button_index(test_button);
+ wh_up->set_pressed(false);
+
+ Ref<InputEventMessage> msg_up;
+ msg_up.instantiate();
+ msg_up->event = wh_up;
+ wayland_thread->push_message(msg_up);
+ }
+ }
+ }
+ }
+
+ // Reset the scroll vectors as we already handled them.
+ pd.scroll_vector = Vector2();
+ pd.discrete_scroll_vector = Vector2();
+
+ // Update the data all getters read. Wayland's specification requires us to do
+ // this, since all pointer actions are sent in individual events.
+ old_pd = pd;
+}
+
+void WaylandThread::_wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ ss->pointer_data_buffer.scroll_type = axis_source;
+}
+
+void WaylandThread::_wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) {
+}
+
+void WaylandThread::_wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (!ss->pointed_surface) {
+ // We're probably on a decoration or some other third-party thing.
+ return;
+ }
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ pd.discrete_scroll_vector.y = discrete;
+ }
+
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ pd.discrete_scroll_vector.x = discrete;
+ }
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) {
+}
+
+// TODO: Add support to this event.
+void WaylandThread::_wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction) {
+}
+
+void WaylandThread::_wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) {
+ ERR_FAIL_COND_MSG(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, "Unsupported keymap format announced from the Wayland compositor.");
+
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->keymap_buffer) {
+ // We have already a mapped buffer, so we unmap it. There's no need to reset
+ // its pointer or size, as we're gonna set them below.
+ munmap((void *)ss->keymap_buffer, ss->keymap_buffer_size);
+ ss->keymap_buffer = nullptr;
+ }
+
+ ss->keymap_buffer = (const char *)mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ss->keymap_buffer_size = size;
+
+ xkb_keymap_unref(ss->xkb_keymap);
+ ss->xkb_keymap = xkb_keymap_new_from_string(ss->xkb_context, ss->keymap_buffer,
+ XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ xkb_state_unref(ss->xkb_state);
+ ss->xkb_state = xkb_state_new(ss->xkb_keymap);
+}
+
+void WaylandThread::_wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ wayland_thread->_set_current_seat(ss->wl_seat);
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_FOCUS_IN;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->repeating_keycode = XKB_KEYCODE_INVALID;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_FOCUS_OUT;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ // We have to add 8 to the scancode to get an XKB-compatible keycode.
+ xkb_keycode_t xkb_keycode = key + 8;
+
+ bool pressed = state & WL_KEYBOARD_KEY_STATE_PRESSED;
+
+ if (pressed) {
+ if (xkb_keymap_key_repeats(ss->xkb_keymap, xkb_keycode)) {
+ ss->last_repeat_start_msec = OS::get_singleton()->get_ticks_msec();
+ ss->repeating_keycode = xkb_keycode;
+ }
+
+ ss->last_key_pressed_serial = serial;
+ } else if (ss->repeating_keycode == xkb_keycode) {
+ ss->repeating_keycode = XKB_KEYCODE_INVALID;
+ }
+
+ Ref<InputEventKey> k;
+ k.instantiate();
+
+ if (!_seat_state_configure_key_event(*ss, k, xkb_keycode, pressed)) {
+ return;
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+ msg->event = k;
+ wayland_thread->push_message(msg);
+}
+
+void WaylandThread::_wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ xkb_state_update_mask(ss->xkb_state, mods_depressed, mods_latched, mods_locked, ss->current_layout_index, ss->current_layout_index, group);
+
+ ss->shift_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED);
+ ss->ctrl_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED);
+ ss->alt_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED);
+ ss->meta_pressed = xkb_state_mod_name_is_active(ss->xkb_state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_DEPRESSED);
+
+ ss->current_layout_index = group;
+}
+
+void WaylandThread::_wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->repeat_key_delay_msec = 1000 / rate;
+ ss->repeat_start_delay_msec = delay;
+}
+
+// NOTE: Don't forget to `memfree` the offer's state.
+void WaylandThread::_wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
+ wl_proxy_tag_godot((struct wl_proxy *)id);
+ wl_data_offer_add_listener(id, &wl_data_offer_listener, memnew(OfferState));
+}
+
+void WaylandThread::_wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->dnd_enter_serial = serial;
+ ss->wl_data_offer_dnd = id;
+
+ // Godot only supports DnD file copying for now.
+ wl_data_offer_accept(id, serial, "text/uri-list");
+ wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
+}
+
+void WaylandThread::_wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_offer_dnd) {
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
+ wl_data_offer_destroy(ss->wl_data_offer_dnd);
+ ss->wl_data_offer_dnd = nullptr;
+ }
+}
+
+void WaylandThread::_wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
+}
+
+void WaylandThread::_wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ OfferState *os = wl_data_offer_get_offer_state(ss->wl_data_offer_dnd);
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ Ref<DropFilesEventMessage> msg;
+ msg.instantiate();
+
+ Vector<uint8_t> list_data = _wl_data_offer_read(wayland_thread->wl_display, "text/uri-list", ss->wl_data_offer_dnd);
+
+ msg->files = String::utf8((const char *)list_data.ptr(), list_data.size()).split("\r\n", false);
+ for (int i = 0; i < msg->files.size(); i++) {
+ msg->files.write[i] = msg->files[i].replace("file://", "").uri_decode();
+ }
+
+ wayland_thread->push_message(msg);
+
+ wl_data_offer_finish(ss->wl_data_offer_dnd);
+ }
+
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_dnd));
+ wl_data_offer_destroy(ss->wl_data_offer_dnd);
+ ss->wl_data_offer_dnd = nullptr;
+}
+
+void WaylandThread::_wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wl_data_offer_selection) {
+ memdelete(wl_data_offer_get_offer_state(ss->wl_data_offer_selection));
+ wl_data_offer_destroy(ss->wl_data_offer_selection);
+ }
+
+ ss->wl_data_offer_selection = id;
+}
+
+void WaylandThread::_wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type) {
+ OfferState *os = (OfferState *)data;
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ os->mime_types.insert(String::utf8(mime_type));
+ }
+}
+
+void WaylandThread::_wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions) {
+}
+
+void WaylandThread::_wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) {
+}
+
+void WaylandThread::_wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type) {
+}
+
+void WaylandThread::_wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ Vector<uint8_t> *data_to_send = nullptr;
+
+ if (wl_data_source == ss->wl_data_source_selection) {
+ data_to_send = &ss->selection_data;
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested selection.");
+ }
+
+ if (data_to_send) {
+ ssize_t written_bytes = 0;
+
+ bool valid_mime = false;
+
+ if (strcmp(mime_type, "text/plain;charset=utf-8") == 0) {
+ valid_mime = true;
+ } else if (strcmp(mime_type, "text/plain") == 0) {
+ valid_mime = true;
+ }
+
+ if (valid_mime) {
+ written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
+ }
+
+ if (written_bytes > 0) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
+ } else if (written_bytes == 0) {
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
+ } else {
+ ERR_PRINT(vformat("Clipboard: write error %d.", errno));
+ }
+ }
+
+ close(fd);
+}
+
+void WaylandThread::_wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ wl_data_source_destroy(wl_data_source);
+
+ if (wl_data_source == ss->wl_data_source_selection) {
+ ss->wl_data_source_selection = nullptr;
+ ss->selection_data.clear();
+
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: selection set by another program.");
+ return;
+ }
+}
+
+void WaylandThread::_wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source) {
+}
+
+void WaylandThread::_wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source) {
+}
+
+void WaylandThread::_wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action) {
+}
+
+void WaylandThread::_wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+
+ ws->preferred_fractional_scale = (double)scale / 120;
+
+ window_state_update_size(ws, ws->rect.size.width, ws->rect.size.height);
+}
+
+void WaylandThread::_wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ pd.relative_motion.x = wl_fixed_to_double(dx);
+ pd.relative_motion.y = wl_fixed_to_double(dy);
+
+ pd.relative_motion_time = uptime_lo;
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (fingers == 2) {
+ ss->old_pinch_scale = wl_fixed_from_int(1);
+ ss->active_gesture = Gesture::MAGNIFY;
+ }
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ PointerData &pd = ss->pointer_data_buffer;
+
+ if (ss->active_gesture == Gesture::MAGNIFY) {
+ Ref<InputEventMagnifyGesture> mg;
+ mg.instantiate();
+
+ mg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ mg->set_shift_pressed(ss->shift_pressed);
+ mg->set_ctrl_pressed(ss->ctrl_pressed);
+ mg->set_alt_pressed(ss->alt_pressed);
+ mg->set_meta_pressed(ss->meta_pressed);
+
+ mg->set_position(pd.position);
+
+ wl_fixed_t scale_delta = scale - ss->old_pinch_scale;
+ mg->set_factor(1 + wl_fixed_to_double(scale_delta));
+
+ Ref<InputEventMessage> magnify_msg;
+ magnify_msg.instantiate();
+ magnify_msg->event = mg;
+
+ // Since Wayland allows only one gesture at a time and godot instead expects
+ // both of them, we'll have to create two separate input events: one for
+ // magnification and one for panning.
+
+ Ref<InputEventPanGesture> pg;
+ pg.instantiate();
+
+ pg->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ pg->set_shift_pressed(ss->shift_pressed);
+ pg->set_ctrl_pressed(ss->ctrl_pressed);
+ pg->set_alt_pressed(ss->alt_pressed);
+ pg->set_meta_pressed(ss->meta_pressed);
+
+ pg->set_position(pd.position);
+ pg->set_delta(Vector2(wl_fixed_to_double(dx), wl_fixed_to_double(dy)));
+
+ Ref<InputEventMessage> pan_msg;
+ pan_msg.instantiate();
+ pan_msg->event = pg;
+
+ wayland_thread->push_message(magnify_msg);
+ wayland_thread->push_message(pan_msg);
+
+ ss->old_pinch_scale = scale;
+ }
+}
+
+void WaylandThread::_wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->active_gesture = Gesture::NONE;
+}
+
+// NOTE: Don't forget to `memfree` the offer's state.
+void WaylandThread::_wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer) {
+ wl_proxy_tag_godot((struct wl_proxy *)offer);
+ zwp_primary_selection_offer_v1_add_listener(offer, &wp_primary_selection_offer_listener, memnew(OfferState));
+}
+
+void WaylandThread::_wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (ss->wp_primary_selection_offer) {
+ memfree(wp_primary_selection_offer_get_offer_state(ss->wp_primary_selection_offer));
+ zwp_primary_selection_offer_v1_destroy(ss->wp_primary_selection_offer);
+ }
+
+ ss->wp_primary_selection_offer = id;
+}
+
+void WaylandThread::_wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type) {
+ OfferState *os = (OfferState *)data;
+ ERR_FAIL_NULL(os);
+
+ if (os) {
+ os->mime_types.insert(String::utf8(mime_type));
+ }
+}
+
+void WaylandThread::_wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ Vector<uint8_t> *data_to_send = nullptr;
+
+ if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
+ data_to_send = &ss->primary_data;
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: requested primary selection.");
+ }
+
+ if (data_to_send) {
+ ssize_t written_bytes = 0;
+
+ if (strcmp(mime_type, "text/plain") == 0) {
+ written_bytes = write(fd, data_to_send->ptr(), data_to_send->size());
+ }
+
+ if (written_bytes > 0) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Clipboard: sent %d bytes.", written_bytes));
+ } else if (written_bytes == 0) {
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: no bytes sent.");
+ } else {
+ ERR_PRINT(vformat("Clipboard: write error %d.", errno));
+ }
+ }
+
+ close(fd);
+}
+
+void WaylandThread::_wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (wp_primary_selection_source_v1 == ss->wp_primary_selection_source) {
+ zwp_primary_selection_source_v1_destroy(ss->wp_primary_selection_source);
+ ss->wp_primary_selection_source = nullptr;
+
+ ss->primary_data.clear();
+
+ DEBUG_LOG_WAYLAND_THREAD("Clipboard: primary selection set by another program.");
+ return;
+ }
+}
+
+void WaylandThread::_wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tablet %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tools.push_back(id);
+
+ zwp_tablet_tool_v2_add_listener(id, &wp_tablet_tool_listener, ss);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on tool %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet seat %x on pad %x added", (size_t)zwp_tablet_seat_v2, (size_t)id));
+}
+
+void WaylandThread::_wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on type %d", (size_t)zwp_tablet_tool_v2, tool_type));
+}
+
+void WaylandThread::_wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware serial %x%x", (size_t)zwp_tablet_tool_v2, hardware_serial_hi, hardware_serial_lo));
+}
+
+void WaylandThread::_wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on hardware id wacom hardware id %x%x", (size_t)zwp_tablet_tool_v2, hardware_id_hi, hardware_id_lo));
+}
+
+void WaylandThread::_wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ if (capability == ZWP_TABLET_TOOL_V2_TYPE_ERASER) {
+ ss->tablet_tool_data_buffer.is_eraser = true;
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on capability %d", (size_t)zwp_tablet_tool_v2, capability));
+}
+
+void WaylandThread::_wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on done", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ List<struct zwp_tablet_tool_v2 *>::Element *it = ss->tablet_tools.front();
+
+ while (it) {
+ struct zwp_tablet_tool_v2 *tool = it->get();
+
+ if (tool == zwp_tablet_tool_v2) {
+ zwp_tablet_tool_v2_destroy(tool);
+ ss->tablet_tools.erase(it);
+ break;
+ }
+ }
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on removed", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->tablet_tool_data_buffer.in_proximity = true;
+
+ ss->pointer_enter_serial = serial;
+ ss->pointed_surface = surface;
+ ss->last_pointed_surface = surface;
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_ENTER;
+ wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD("Tablet tool entered window.");
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity in serial %d tablet %x surface %x", (size_t)zwp_tablet_tool_v2, serial, (size_t)tablet, (size_t)surface));
+}
+
+void WaylandThread::_wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ ss->pointed_surface = nullptr;
+ ss->tablet_tool_data_buffer.in_proximity = false;
+
+ DEBUG_LOG_WAYLAND_THREAD("Tablet tool left window.");
+
+ Ref<WindowEventMessage> msg;
+ msg.instantiate();
+ msg->event = DisplayServer::WINDOW_EVENT_MOUSE_EXIT;
+
+ wayland_thread->push_message(msg);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on proximity out", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.touching = true;
+ td.pressed_button_mask.set_flag(mouse_button_to_mask(MouseButton::LEFT));
+ td.last_button_pressed = MouseButton::LEFT;
+ td.double_click_begun = true;
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on down serial %x", (size_t)zwp_tablet_tool_v2, serial));
+}
+
+void WaylandThread::_wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.touching = false;
+ td.pressed_button_mask.clear_flag(mouse_button_to_mask(MouseButton::LEFT));
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("wp tablet tool %x on up", (size_t)zwp_tablet_tool_v2));
+}
+
+void WaylandThread::_wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WindowState *ws = wl_surface_get_window_state(ss->pointed_surface);
+ ERR_FAIL_NULL(ws);
+
+ double scale_factor = window_state_get_scale_factor(ws);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ td.position = scale_vector2i(td.position, scale_factor);
+}
+
+void WaylandThread::_wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tool_data_buffer.pressure = pressure;
+}
+
+void WaylandThread::_wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance) {
+ // Unsupported
+}
+
+void WaylandThread::_wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ ss->tablet_tool_data_buffer.tilt.x = wl_fixed_to_double(tilt_x);
+ ss->tablet_tool_data_buffer.tilt.y = wl_fixed_to_double(tilt_y);
+}
+
+void WaylandThread::_wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees) {
+ // Unsupported.
+}
+
+void WaylandThread::_wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position) {
+ // Unsupported.
+}
+
+void WaylandThread::_wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks) {
+ // TODO
+}
+
+void WaylandThread::_wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ MouseButton mouse_button = MouseButton::NONE;
+
+ if (button == BTN_STYLUS) {
+ mouse_button = MouseButton::LEFT;
+ }
+
+ if (button == BTN_STYLUS2) {
+ mouse_button = MouseButton::RIGHT;
+ }
+
+ if (mouse_button != MouseButton::NONE) {
+ MouseButtonMask mask = mouse_button_to_mask(mouse_button);
+
+ if (state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_PRESSED) {
+ td.pressed_button_mask.set_flag(mask);
+ td.last_button_pressed = mouse_button;
+ td.double_click_begun = true;
+ } else {
+ td.pressed_button_mask.clear_flag(mask);
+ }
+
+ // The protocol doesn't cover this, but we can use this funky hack to make
+ // double clicking work.
+ td.button_time = OS::get_singleton()->get_ticks_msec();
+ }
+}
+
+void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time) {
+ SeatState *ss = (SeatState *)data;
+ ERR_FAIL_NULL(ss);
+
+ WaylandThread *wayland_thread = ss->wayland_thread;
+ ERR_FAIL_NULL(wayland_thread);
+
+ TabletToolData &old_td = ss->tablet_tool_data;
+ TabletToolData &td = ss->tablet_tool_data_buffer;
+
+ if (old_td.position != td.position || old_td.tilt != td.tilt || old_td.pressure != td.pressure) {
+ Ref<InputEventMouseMotion> mm;
+ mm.instantiate();
+
+ mm->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+
+ // Set all pressed modifiers.
+ mm->set_shift_pressed(ss->shift_pressed);
+ mm->set_ctrl_pressed(ss->ctrl_pressed);
+ mm->set_alt_pressed(ss->alt_pressed);
+ mm->set_meta_pressed(ss->meta_pressed);
+
+ mm->set_button_mask(td.pressed_button_mask);
+
+ mm->set_position(td.position);
+ mm->set_global_position(td.position);
+
+ // NOTE: The Godot API expects normalized values and we store them raw,
+ // straight from the compositor, so we have to normalize them here.
+
+ // According to the tablet proto spec, tilt is expressed in degrees relative
+ // to the Z axis of the tablet, so it shouldn't go over 90 degrees, I think.
+ // TODO: Investigate whether the tilt can go over 90 degrees (it shouldn't).
+ mm->set_tilt(td.tilt / 90);
+
+ // The tablet proto spec explicitly says that pressure is defined as a value
+ // between 0 to 65535.
+ mm->set_pressure(td.pressure / (float)65535);
+
+ // FIXME: Tool handling is broken.
+ mm->set_pen_inverted(td.is_eraser);
+
+ mm->set_relative(td.position - old_td.position);
+
+ // FIXME: Stop doing this to calculate speed.
+ // FIXME2: It has been done, port this from the pointer logic once this works again.
+ Input::get_singleton()->set_mouse_position(td.position);
+ mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity());
+
+ Ref<InputEventMessage> inputev_msg;
+ inputev_msg.instantiate();
+
+ inputev_msg->event = mm;
+
+ wayland_thread->push_message(inputev_msg);
+ }
+
+ if (old_td.pressed_button_mask != td.pressed_button_mask) {
+ BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((int64_t)old_td.pressed_button_mask ^ (int64_t)td.pressed_button_mask);
+
+ for (MouseButton test_button : { MouseButton::LEFT, MouseButton::RIGHT }) {
+ MouseButtonMask test_button_mask = mouse_button_to_mask(test_button);
+
+ if (pressed_mask_delta.has_flag(test_button_mask)) {
+ Ref<InputEventMouseButton> mb;
+ mb.instantiate();
+
+ // Set all pressed modifiers.
+ mb->set_shift_pressed(ss->shift_pressed);
+ mb->set_ctrl_pressed(ss->ctrl_pressed);
+ mb->set_alt_pressed(ss->alt_pressed);
+ mb->set_meta_pressed(ss->meta_pressed);
+
+ mb->set_window_id(DisplayServer::MAIN_WINDOW_ID);
+ mb->set_position(td.position);
+ mb->set_global_position(td.position);
+
+ mb->set_button_mask(td.pressed_button_mask);
+ mb->set_button_index(test_button);
+ mb->set_pressed(td.pressed_button_mask.has_flag(test_button_mask));
+
+ // We have to set the last position pressed here as we can't take for
+ // granted what the individual events might have seen due to them not having
+ // a garaunteed order.
+ if (mb->is_pressed()) {
+ td.last_pressed_position = td.position;
+ }
+
+ if (old_td.double_click_begun && mb->is_pressed() && td.last_button_pressed == old_td.last_button_pressed && (td.button_time - old_td.button_time) < 400 && Vector2(td.last_pressed_position).distance_to(Vector2(old_td.last_pressed_position)) < 5) {
+ td.double_click_begun = false;
+ mb->set_double_click(true);
+ }
+
+ Ref<InputEventMessage> msg;
+ msg.instantiate();
+
+ msg->event = mb;
+
+ wayland_thread->push_message(msg);
+ }
+ }
+ }
+
+ old_td = td;
+}
+
+void WaylandThread::_xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token) {
+ WindowState *ws = (WindowState *)data;
+ ERR_FAIL_NULL(ws);
+ ERR_FAIL_NULL(ws->wayland_thread);
+ ERR_FAIL_NULL(ws->wl_surface);
+
+ xdg_activation_v1_activate(ws->wayland_thread->registry.xdg_activation, token, ws->wl_surface);
+ xdg_activation_token_v1_destroy(xdg_activation_token);
+
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Received activation token and requested window activation."));
+}
+
+// NOTE: This must be started after a valid wl_display is loaded.
+void WaylandThread::_poll_events_thread(void *p_data) {
+ ThreadData *data = (ThreadData *)p_data;
+ ERR_FAIL_NULL(data);
+ ERR_FAIL_NULL(data->wl_display);
+
+ struct pollfd poll_fd;
+ poll_fd.fd = wl_display_get_fd(data->wl_display);
+ poll_fd.events = POLLIN | POLLHUP;
+
+ while (true) {
+ // Empty the event queue while it's full.
+ while (wl_display_prepare_read(data->wl_display) != 0) {
+ // We aren't using wl_display_dispatch(), instead "manually" handling events
+ // through wl_display_dispatch_pending so that we can use a global mutex and
+ // be sure that this and the main thread won't race over stuff, as long as
+ // the main thread locks it too.
+ //
+ // Note that the main thread can still call wl_display_roundtrip as that
+ // method directly handles all events, effectively bypassing this polling
+ // loop and thus the mutex locking, avoiding a deadlock.
+ MutexLock mutex_lock(data->mutex);
+
+ if (wl_display_dispatch_pending(data->wl_display) == -1) {
+ // Oh no. We'll check and handle any display error below.
+ break;
+ }
+ }
+
+ int werror = wl_display_get_error(data->wl_display);
+
+ if (werror) {
+ if (werror == EPROTO) {
+ struct wl_interface *wl_interface = nullptr;
+ uint32_t id = 0;
+
+ int error_code = wl_display_get_protocol_error(data->wl_display, (const struct wl_interface **)&wl_interface, &id);
+ CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id));
+ } else {
+ CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror));
+ }
+ }
+
+ wl_display_flush(data->wl_display);
+
+ // Wait for the event file descriptor to have new data.
+ poll(&poll_fd, 1, -1);
+
+ if (data->thread_done.is_set()) {
+ wl_display_cancel_read(data->wl_display);
+ break;
+ }
+
+ if (poll_fd.revents | POLLIN) {
+ // Load the queues with fresh new data.
+ wl_display_read_events(data->wl_display);
+ } else {
+ // Oh well... Stop signaling that we want to read.
+ wl_display_cancel_read(data->wl_display);
+ }
+
+ // The docs advise to redispatch unconditionally and it looks like that if we
+ // don't do this we can't catch protocol errors, which is bad.
+ MutexLock mutex_lock(data->mutex);
+ wl_display_dispatch_pending(data->wl_display);
+ }
+}
+
+struct wl_display *WaylandThread::get_wl_display() const {
+ return wl_display;
+}
+
+// NOTE: Stuff like libdecor can (and will) register foreign proxies which
+// aren't formatted as we like. This method is needed to detect whether a proxy
+// has our tag. Also, be careful! The proxy has to be manually tagged or it
+// won't be recognized.
+bool WaylandThread::wl_proxy_is_godot(struct wl_proxy *p_proxy) {
+ ERR_FAIL_NULL_V(p_proxy, false);
+
+ return wl_proxy_get_tag(p_proxy) == &proxy_tag;
+}
+
+void WaylandThread::wl_proxy_tag_godot(struct wl_proxy *p_proxy) {
+ ERR_FAIL_NULL(p_proxy);
+
+ wl_proxy_set_tag(p_proxy, &proxy_tag);
+}
+
+// Returns the wl_surface's `WindowState`, otherwise `nullptr`.
+// NOTE: This will fail if the surface isn't tagged as ours.
+WaylandThread::WindowState *WaylandThread::wl_surface_get_window_state(struct wl_surface *p_surface) {
+ if (p_surface && wl_proxy_is_godot((wl_proxy *)p_surface)) {
+ return (WindowState *)wl_surface_get_user_data(p_surface);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_outputs's `ScreenState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::ScreenState *WaylandThread::wl_output_get_screen_state(struct wl_output *p_output) {
+ if (p_output && wl_proxy_is_godot((wl_proxy *)p_output)) {
+ return (ScreenState *)wl_output_get_user_data(p_output);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_seat's `SeatState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::SeatState *WaylandThread::wl_seat_get_seat_state(struct wl_seat *p_seat) {
+ if (p_seat && wl_proxy_is_godot((wl_proxy *)p_seat)) {
+ return (SeatState *)wl_seat_get_user_data(p_seat);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::OfferState *WaylandThread::wl_data_offer_get_offer_state(struct wl_data_offer *p_offer) {
+ if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
+ return (OfferState *)wl_data_offer_get_user_data(p_offer);
+ }
+
+ return nullptr;
+}
+
+// Returns the wl_data_offer's `OfferState`, otherwise `nullptr`.
+// NOTE: This will fail if the output isn't tagged as ours.
+WaylandThread::OfferState *WaylandThread::wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer) {
+ if (p_offer && wl_proxy_is_godot((wl_proxy *)p_offer)) {
+ return (OfferState *)zwp_primary_selection_offer_v1_get_user_data(p_offer);
+ }
+
+ return nullptr;
+}
+
+// This is implemented as a method because this is the simplest way of
+// accounting for dynamic output scale changes.
+int WaylandThread::window_state_get_preferred_buffer_scale(WindowState *p_ws) {
+ ERR_FAIL_NULL_V(p_ws, 1);
+
+ if (p_ws->preferred_fractional_scale > 0) {
+ // We're scaling fractionally. Per spec, the buffer scale is always 1.
+ return 1;
+ }
+
+ if (p_ws->wl_outputs.is_empty()) {
+ DEBUG_LOG_WAYLAND_THREAD("Window has no output associated, returning buffer scale of 1.");
+ return 1;
+ }
+
+ // TODO: Cache value?
+ int max_size = 1;
+
+ // ================================ IMPORTANT =================================
+ // NOTE: Due to a Godot limitation, we can't really rescale the whole UI yet.
+ // Because of this reason, all platforms have resorted to forcing the highest
+ // scale possible of a system on any window, despite of what screen it's onto.
+ // On this backend everything's already in place for dynamic window scale
+ // handling, but in the meantime we'll just select the biggest _global_ output.
+ // To restore dynamic scale selection, simply iterate over `p_ws->wl_outputs`
+ // instead.
+ for (struct wl_output *wl_output : p_ws->registry->wl_outputs) {
+ ScreenState *ss = wl_output_get_screen_state(wl_output);
+
+ if (ss && ss->pending_data.scale > max_size) {
+ // NOTE: For some mystical reason, wl_output.done is emitted _after_ windows
+ // get resized but the scale event gets sent _before_ that. I'm still leaning
+ // towards the idea that rescaling when a window gets a resolution change is a
+ // pretty good approach, but this means that we'll have to use the screen data
+ // before it's "committed".
+ // FIXME: Use the committed data. Somehow.
+ max_size = ss->pending_data.scale;
+ }
+ }
+
+ return max_size;
+}
+
+double WaylandThread::window_state_get_scale_factor(WindowState *p_ws) {
+ ERR_FAIL_NULL_V(p_ws, 1);
+
+ if (p_ws->fractional_scale > 0) {
+ // The fractional scale amount takes priority.
+ return p_ws->fractional_scale;
+ }
+
+ return p_ws->buffer_scale;
+}
+
+void WaylandThread::window_state_update_size(WindowState *p_ws, int p_width, int p_height) {
+ ERR_FAIL_NULL(p_ws);
+
+ int preferred_buffer_scale = window_state_get_preferred_buffer_scale(p_ws);
+ bool using_fractional = p_ws->preferred_fractional_scale > 0;
+
+ // If neither is true we no-op.
+ bool scale_changed = false;
+ bool size_changed = false;
+
+ if (p_ws->rect.size.width != p_width || p_ws->rect.size.height != p_height) {
+ p_ws->rect.size.width = p_width;
+ p_ws->rect.size.height = p_height;
+
+ size_changed = true;
+ }
+
+ if (using_fractional && p_ws->fractional_scale != p_ws->preferred_fractional_scale) {
+ p_ws->fractional_scale = p_ws->preferred_fractional_scale;
+ scale_changed = true;
+ }
+
+ if (p_ws->buffer_scale != preferred_buffer_scale) {
+ // The buffer scale is always important, even if we use frac scaling.
+ p_ws->buffer_scale = preferred_buffer_scale;
+ p_ws->buffer_scale_changed = true;
+
+ if (!using_fractional) {
+ // We don't bother updating everything else if it's turned on though.
+ scale_changed = true;
+ }
+ }
+
+ if (p_ws->wl_surface && (size_changed || scale_changed)) {
+ if (p_ws->wp_viewport) {
+ wp_viewport_set_destination(p_ws->wp_viewport, p_width, p_height);
+ }
+
+ if (p_ws->xdg_surface) {
+ xdg_surface_set_window_geometry(p_ws->xdg_surface, 0, 0, p_width, p_height);
+ }
+ }
+
+#ifdef LIBDECOR_ENABLED
+ if (p_ws->libdecor_frame) {
+ struct libdecor_state *state = libdecor_state_new(p_width, p_height);
+ libdecor_frame_commit(p_ws->libdecor_frame, state, p_ws->pending_libdecor_configuration);
+ libdecor_state_free(state);
+ p_ws->pending_libdecor_configuration = nullptr;
+ }
+#endif
+
+ if (size_changed || scale_changed) {
+ Size2i scaled_size = scale_vector2i(p_ws->rect.size, window_state_get_scale_factor(p_ws));
+
+ if (using_fractional) {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (fractional scale x%f).", p_ws->rect.size, scaled_size, p_ws->fractional_scale));
+ } else {
+ DEBUG_LOG_WAYLAND_THREAD(vformat("Resizing the window from %s to %s (buffer scale x%d).", p_ws->rect.size, scaled_size, p_ws->buffer_scale));
+ }
+
+ // FIXME: Actually resize the hint instead of centering it.
+ p_ws->wayland_thread->pointer_set_hint(scaled_size / 2);
+
+ Ref<WindowRectMessage> rect_msg;
+ rect_msg.instantiate();
+ rect_msg->rect = p_ws->rect;
+ rect_msg->rect.size = scaled_size;
+ p_ws->wayland_thread->push_message(rect_msg);
+ }
+
+ if (scale_changed) {
+ Ref<WindowEventMessage> dpi_msg;
+ dpi_msg.instantiate();
+ dpi_msg->event = DisplayServer::WINDOW_EVENT_DPI_CHANGE;
+ p_ws->wayland_thread->push_message(dpi_msg);
+ }
+}
+
+// Scales a vector according to wp_fractional_scale's rules, where coordinates
+// must be scaled with away from zero half-rounding.
+Vector2i WaylandThread::scale_vector2i(const Vector2i &p_vector, double p_amount) {
+ // This snippet is tiny, I know, but this is done a lot.
+ int x = round(p_vector.x * p_amount);
+ int y = round(p_vector.y * p_amount);
+
+ return Vector2i(x, y);
+}
+
+void WaylandThread::seat_state_unlock_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_locked_pointer) {
+ zwp_locked_pointer_v1_destroy(p_ss->wp_locked_pointer);
+ p_ss->wp_locked_pointer = nullptr;
+ }
+
+ if (p_ss->wp_confined_pointer) {
+ zwp_confined_pointer_v1_destroy(p_ss->wp_confined_pointer);
+ p_ss->wp_confined_pointer = nullptr;
+ }
+}
+
+void WaylandThread::seat_state_lock_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (registry.wp_pointer_constraints == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_locked_pointer == nullptr) {
+ struct wl_surface *locked_surface = p_ss->last_pointed_surface;
+
+ if (locked_surface == nullptr) {
+ locked_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
+ }
+
+ ERR_FAIL_NULL(locked_surface);
+
+ p_ss->wp_locked_pointer = zwp_pointer_constraints_v1_lock_pointer(registry.wp_pointer_constraints, locked_surface, p_ss->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
+}
+
+void WaylandThread::seat_state_set_hint(SeatState *p_ss, int p_x, int p_y) {
+ if (p_ss->wp_locked_pointer == nullptr) {
+ return;
+ }
+
+ zwp_locked_pointer_v1_set_cursor_position_hint(p_ss->wp_locked_pointer, wl_fixed_from_int(p_x), wl_fixed_from_int(p_y));
+}
+
+void WaylandThread::seat_state_confine_pointer(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_pointer == nullptr) {
+ return;
+ }
+
+ if (registry.wp_pointer_constraints == nullptr) {
+ return;
+ }
+
+ if (p_ss->wp_confined_pointer == nullptr) {
+ struct wl_surface *confined_surface = p_ss->last_pointed_surface;
+
+ if (confined_surface == nullptr) {
+ confined_surface = window_get_wl_surface(DisplayServer::MAIN_WINDOW_ID);
+ }
+
+ ERR_FAIL_NULL(confined_surface);
+
+ p_ss->wp_confined_pointer = zwp_pointer_constraints_v1_confine_pointer(registry.wp_pointer_constraints, confined_surface, p_ss->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ }
+}
+
+void WaylandThread::seat_state_update_cursor(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+ ERR_FAIL_NULL(p_ss->wayland_thread);
+
+ if (p_ss->wl_pointer && p_ss->cursor_surface) {
+ // NOTE: Those values are valid by default and will hide the cursor when
+ // unchanged, which happens when both the current custom cursor and the
+ // current wl_cursor are `nullptr`.
+ struct wl_buffer *cursor_buffer = nullptr;
+ uint32_t hotspot_x = 0;
+ uint32_t hotspot_y = 0;
+ int scale = 1;
+
+ CustomCursor *custom_cursor = p_ss->wayland_thread->current_custom_cursor;
+ struct wl_cursor *wl_cursor = p_ss->wayland_thread->current_wl_cursor;
+
+ if (custom_cursor) {
+ cursor_buffer = custom_cursor->wl_buffer;
+ hotspot_x = custom_cursor->hotspot.x;
+ hotspot_y = custom_cursor->hotspot.y;
+
+ // We can't really reasonably scale custom cursors, so we'll let the
+ // compositor do it for us (badly).
+ scale = 1;
+ } else if (wl_cursor) {
+ int frame_idx = wl_cursor_frame(wl_cursor, p_ss->cursor_time_ms);
+
+ struct wl_cursor_image *wl_cursor_image = wl_cursor->images[frame_idx];
+
+ scale = p_ss->wayland_thread->cursor_scale;
+
+ cursor_buffer = wl_cursor_image_get_buffer(wl_cursor_image);
+
+ // As the surface's buffer is scaled (thus the surface is smaller) and the
+ // hotspot must be expressed in surface-local coordinates, we need to scale
+ // them down accordingly.
+ hotspot_x = wl_cursor_image->hotspot_x / scale;
+ hotspot_y = wl_cursor_image->hotspot_y / scale;
+ }
+
+ wl_pointer_set_cursor(p_ss->wl_pointer, p_ss->pointer_enter_serial, p_ss->cursor_surface, hotspot_x, hotspot_y);
+ wl_surface_set_buffer_scale(p_ss->cursor_surface, scale);
+ wl_surface_attach(p_ss->cursor_surface, cursor_buffer, 0, 0);
+ wl_surface_damage_buffer(p_ss->cursor_surface, 0, 0, INT_MAX, INT_MAX);
+
+ wl_surface_commit(p_ss->cursor_surface);
+ }
+}
+
+void WaylandThread::seat_state_echo_keys(SeatState *p_ss) {
+ ERR_FAIL_NULL(p_ss);
+
+ if (p_ss->wl_keyboard == nullptr) {
+ return;
+ }
+
+ // TODO: Comment and document out properly this block of code.
+ // In short, this implements key repeating.
+ if (p_ss->repeat_key_delay_msec && p_ss->repeating_keycode != XKB_KEYCODE_INVALID) {
+ uint64_t current_ticks = OS::get_singleton()->get_ticks_msec();
+ uint64_t delayed_start_ticks = p_ss->last_repeat_start_msec + p_ss->repeat_start_delay_msec;
+
+ if (p_ss->last_repeat_msec < delayed_start_ticks) {
+ p_ss->last_repeat_msec = delayed_start_ticks;
+ }
+
+ if (current_ticks >= delayed_start_ticks) {
+ uint64_t ticks_delta = current_ticks - p_ss->last_repeat_msec;
+
+ int keys_amount = (ticks_delta / p_ss->repeat_key_delay_msec);
+
+ for (int i = 0; i < keys_amount; i++) {
+ Ref<InputEventKey> k;
+ k.instantiate();
+
+ if (!_seat_state_configure_key_event(*p_ss, k, p_ss->repeating_keycode, true)) {
+ continue;
+ }
+
+ k->set_echo(true);
+
+ Input::get_singleton()->parse_input_event(k);
+ }
+
+ p_ss->last_repeat_msec += ticks_delta - (ticks_delta % p_ss->repeat_key_delay_msec);
+ }
+ }
+}
+
+void WaylandThread::push_message(Ref<Message> message) {
+ messages.push_back(message);
+}
+
+bool WaylandThread::has_message() {
+ return messages.front() != nullptr;
+}
+
+Ref<WaylandThread::Message> WaylandThread::pop_message() {
+ if (messages.front() != nullptr) {
+ Ref<Message> msg = messages.front()->get();
+ messages.pop_front();
+ return msg;
+ }
+
+ // This method should only be called if `has_messages` returns true but if
+ // that isn't the case we'll just return an invalid `Ref`. After all, due to
+ // its `InputEvent`-like interface, we still have to dynamically cast and check
+ // the `Ref`'s validity anyways.
+ return Ref<Message>();
+}
+
+void WaylandThread::window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height) {
+ // TODO: Implement multi-window support.
+ WindowState &ws = main_window;
+
+ ws.registry = &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..86033c1a09
--- /dev/null
+++ b/platform/linuxbsd/wayland/wayland_thread.h
@@ -0,0 +1,949 @@
+/**************************************************************************/
+/* wayland_thread.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef WAYLAND_THREAD_H
+#define WAYLAND_THREAD_H
+
+#ifdef WAYLAND_ENABLED
+
+#include "key_mapping_xkb.h"
+
+#ifdef SOWRAP_ENABLED
+#include "wayland/dynwrappers/wayland-client-core-so_wrap.h"
+#include "wayland/dynwrappers/wayland-cursor-so_wrap.h"
+#include "wayland/dynwrappers/wayland-egl-core-so_wrap.h"
+#include "xkbcommon-so_wrap.h"
+#else
+#include <wayland-client-core.h>
+#include <wayland-cursor.h>
+#include <xkbcommon/xkbcommon.h>
+#endif // SOWRAP_ENABLED
+
+// These must go after the Wayland client include to work properly.
+#include "wayland/protocol/idle_inhibit.gen.h"
+#include "wayland/protocol/primary_selection.gen.h"
+// These three protocol headers name wl_pointer method arguments as `pointer`,
+// which is the same name as X11's pointer typedef. This trips some very
+// annoying shadowing warnings. A `#define` works around this issue.
+#define pointer wl_pointer
+#include "wayland/protocol/pointer_constraints.gen.h"
+#include "wayland/protocol/pointer_gestures.gen.h"
+#include "wayland/protocol/relative_pointer.gen.h"
+#undef pointer
+#include "wayland/protocol/fractional_scale.gen.h"
+#include "wayland/protocol/tablet.gen.h"
+#include "wayland/protocol/viewporter.gen.h"
+#include "wayland/protocol/wayland.gen.h"
+#include "wayland/protocol/xdg_activation.gen.h"
+#include "wayland/protocol/xdg_decoration.gen.h"
+#include "wayland/protocol/xdg_foreign.gen.h"
+#include "wayland/protocol/xdg_shell.gen.h"
+
+#ifdef LIBDECOR_ENABLED
+#ifdef SOWRAP_ENABLED
+#include "dynwrappers/libdecor-so_wrap.h"
+#else
+#include <libdecor-0/libdecor.h>
+#endif // SOWRAP_ENABLED
+#endif // LIBDECOR_ENABLED
+
+#include "core/os/thread.h"
+#include "servers/display_server.h"
+
+class WaylandThread {
+public:
+ // Messages used for exchanging information between Godot's and Wayland's thread.
+ class Message : public RefCounted {
+ public:
+ Message() {}
+ virtual ~Message() = default;
+ };
+
+ // Message data for window rect changes.
+ class WindowRectMessage : public Message {
+ public:
+ // NOTE: This is in "scaled" terms. For example, if there's a 1920x1080 rect
+ // with a scale factor of 2, the actual value of `rect` will be 3840x2160.
+ Rect2i rect;
+ };
+
+ class WindowEventMessage : public Message {
+ public:
+ DisplayServer::WindowEvent event;
+ };
+
+ class InputEventMessage : public Message {
+ public:
+ Ref<InputEvent> event;
+ };
+
+ class DropFilesEventMessage : public Message {
+ public:
+ Vector<String> files;
+ };
+
+ struct RegistryState {
+ WaylandThread *wayland_thread;
+
+ // Core Wayland globals.
+ struct wl_shm *wl_shm = nullptr;
+ uint32_t wl_shm_name = 0;
+
+ struct wl_compositor *wl_compositor = nullptr;
+ uint32_t wl_compositor_name = 0;
+
+ struct wl_subcompositor *wl_subcompositor = nullptr;
+ uint32_t wl_subcompositor_name = 0;
+
+ struct wl_data_device_manager *wl_data_device_manager = nullptr;
+ uint32_t wl_data_device_manager_name = 0;
+
+ List<struct wl_output *> wl_outputs;
+ List<struct wl_seat *> wl_seats;
+
+ // xdg-shell globals.
+
+ struct xdg_wm_base *xdg_wm_base = nullptr;
+ uint32_t xdg_wm_base_name = 0;
+
+ struct zxdg_exporter_v1 *wl_exporter = nullptr;
+ uint32_t wl_exporter_name = 0;
+
+ // wayland-protocols globals.
+
+ struct wp_viewporter *wp_viewporter = nullptr;
+ uint32_t wp_viewporter_name = 0;
+
+ struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager = nullptr;
+ uint32_t wp_fractional_scale_manager_name = 0;
+
+ struct zxdg_decoration_manager_v1 *xdg_decoration_manager = nullptr;
+ uint32_t xdg_decoration_manager_name = 0;
+
+ struct xdg_activation_v1 *xdg_activation = nullptr;
+ uint32_t xdg_activation_name = 0;
+
+ struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr;
+ uint32_t wp_primary_selection_device_manager_name = 0;
+
+ struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr;
+ uint32_t wp_relative_pointer_manager_name = 0;
+
+ struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
+ uint32_t wp_pointer_constraints_name = 0;
+
+ struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
+ uint32_t wp_pointer_gestures_name = 0;
+
+ struct zwp_idle_inhibit_manager_v1 *wp_idle_inhibit_manager = nullptr;
+ uint32_t wp_idle_inhibit_manager_name = 0;
+
+ struct zwp_tablet_manager_v2 *wp_tablet_manager = nullptr;
+ uint32_t wp_tablet_manager_name = 0;
+ };
+
+ // General Wayland-specific states. Shouldn't be accessed directly.
+ // TODO: Make private?
+
+ struct WindowState {
+ DisplayServer::WindowID id;
+
+ Rect2i rect;
+ DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED;
+
+ // These are true by default as it isn't guaranteed that we'll find an
+ // xdg-shell implementation with wm_capabilities available. If and once we
+ // receive a wm_capabilities event these will get reset and updated with
+ // whatever the compositor says.
+ bool can_minimize = false;
+ bool can_maximize = false;
+ bool can_fullscreen = false;
+
+ HashSet<struct wl_output *> wl_outputs;
+
+ // NOTE: If for whatever reason this callback is destroyed _while_ the event
+ // thread is still running, it might be a good idea to set its user data to
+ // `nullptr`. From some initial testing of mine, it looks like it might still
+ // be called even after being destroyed, pointing to probably invalid window
+ // data by then and segfaulting hard.
+ struct wl_callback *frame_callback = nullptr;
+
+ struct wl_surface *wl_surface = nullptr;
+ struct xdg_surface *xdg_surface = nullptr;
+ struct xdg_toplevel *xdg_toplevel = nullptr;
+
+ struct wp_viewport *wp_viewport = nullptr;
+ struct wp_fractional_scale_v1 *wp_fractional_scale = nullptr;
+ struct zxdg_exported_v1 *xdg_exported = nullptr;
+
+ String exported_handle;
+
+ // Currently applied buffer scale.
+ int buffer_scale = 1;
+
+ // Buffer scale must be applied right before rendering but _after_ committing
+ // everything else or otherwise we might have an inconsistent state (e.g.
+ // double scale and odd resolution). This flag assists with that; when set,
+ // on the next frame, we'll commit whatever is set in `buffer_scale`.
+ bool buffer_scale_changed = false;
+
+ // NOTE: The preferred buffer scale is currently only dynamically calculated.
+ // It can be accessed by calling `window_state_get_preferred_buffer_scale`.
+
+ // Override used by the fractional scale add-on object. If less or equal to 0
+ // (default) then the normal output-based scale is used instead.
+ double fractional_scale = 0;
+
+ // What the compositor is recommending us.
+ double preferred_fractional_scale = 0;
+
+ struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration = nullptr;
+
+ struct zwp_idle_inhibitor_v1 *wp_idle_inhibitor = nullptr;
+
+#ifdef LIBDECOR_ENABLED
+ // If this is null the xdg_* variables must be set and vice-versa. This way we
+ // can handle this mess gracefully enough to hopefully being able of getting
+ // rid of this cleanly once we have our own CSDs.
+ struct libdecor_frame *libdecor_frame = nullptr;
+ struct libdecor_configuration *pending_libdecor_configuration = nullptr;
+#endif
+
+ RegistryState *registry;
+ WaylandThread *wayland_thread;
+ };
+
+ // "High level" Godot-side screen data.
+ struct ScreenData {
+ // Geometry data.
+ Point2i position;
+
+ String make;
+ String model;
+
+ Size2i size;
+ Size2i physical_size;
+
+ float refresh_rate = -1;
+ int scale = 1;
+ };
+
+ struct ScreenState {
+ uint32_t wl_output_name = 0;
+
+ ScreenData pending_data;
+ ScreenData data;
+
+ WaylandThread *wayland_thread;
+ };
+
+ enum class Gesture {
+ NONE,
+ MAGNIFY,
+ };
+
+ enum class PointerConstraint {
+ NONE,
+ LOCKED,
+ CONFINED,
+ };
+
+ struct PointerData {
+ Point2i position;
+ uint32_t motion_time = 0;
+
+ // Relative motion has its own optional event and so needs its own time.
+ Vector2 relative_motion;
+ uint32_t relative_motion_time = 0;
+
+ BitField<MouseButtonMask> pressed_button_mask;
+
+ MouseButton last_button_pressed = MouseButton::NONE;
+ Point2i last_pressed_position;
+
+ // This is needed to check for a new double click every time.
+ bool double_click_begun = false;
+
+ uint32_t button_time = 0;
+ uint32_t button_serial = 0;
+
+ uint32_t scroll_type = WL_POINTER_AXIS_SOURCE_WHEEL;
+
+ // The amount "scrolled" in pixels, in each direction.
+ Vector2 scroll_vector;
+
+ // The amount of scroll "clicks" in each direction.
+ Vector2i discrete_scroll_vector;
+
+ uint32_t pinch_scale = 1;
+ };
+
+ struct TabletToolData {
+ Point2i position;
+ Vector2i tilt;
+ uint32_t pressure = 0;
+
+ BitField<MouseButtonMask> pressed_button_mask;
+
+ MouseButton last_button_pressed = MouseButton::NONE;
+ Point2i last_pressed_position;
+
+ bool double_click_begun = false;
+
+ // Note: the protocol doesn't have it (I guess that this isn't really meant to
+ // be used as a mouse...), but we'll hack one in with the current ticks.
+ uint64_t button_time = 0;
+
+ bool is_eraser = false;
+
+ bool in_proximity = false;
+ bool touching = false;
+ };
+
+ struct OfferState {
+ HashSet<String> mime_types;
+ };
+
+ struct SeatState {
+ RegistryState *registry = nullptr;
+
+ WaylandThread *wayland_thread = nullptr;
+
+ struct wl_seat *wl_seat = nullptr;
+ uint32_t wl_seat_name = 0;
+
+ // Pointer.
+ struct wl_pointer *wl_pointer = nullptr;
+
+ uint32_t pointer_enter_serial = 0;
+
+ struct wl_surface *pointed_surface = nullptr;
+ struct wl_surface *last_pointed_surface = nullptr;
+
+ struct zwp_relative_pointer_v1 *wp_relative_pointer = nullptr;
+ struct zwp_locked_pointer_v1 *wp_locked_pointer = nullptr;
+ struct zwp_confined_pointer_v1 *wp_confined_pointer = nullptr;
+
+ struct zwp_pointer_gesture_pinch_v1 *wp_pointer_gesture_pinch = nullptr;
+
+ // NOTE: According to the wp_pointer_gestures protocol specification, there
+ // can be only one active gesture at a time.
+ Gesture active_gesture = Gesture::NONE;
+
+ // Used for delta calculations.
+ // NOTE: The wp_pointer_gestures protocol keeps track of the total scale of
+ // the pinch gesture, while godot instead wants its delta.
+ wl_fixed_t old_pinch_scale = 0;
+
+ struct wl_surface *cursor_surface = nullptr;
+ struct wl_callback *cursor_frame_callback = nullptr;
+ uint32_t cursor_time_ms = 0;
+
+ // This variable is needed to buffer all pointer changes until a
+ // wl_pointer.frame event, as per Wayland's specification. Everything is
+ // first set in `data_buffer` and then `data` is set with its contents on
+ // an input frame event. All methods should generally read from
+ // `pointer_data` and write to `data_buffer`.
+ PointerData pointer_data_buffer;
+ PointerData pointer_data;
+
+ // Keyboard.
+ struct wl_keyboard *wl_keyboard = nullptr;
+
+ struct xkb_context *xkb_context = nullptr;
+ struct xkb_keymap *xkb_keymap = nullptr;
+ struct xkb_state *xkb_state = nullptr;
+
+ const char *keymap_buffer = nullptr;
+ uint32_t keymap_buffer_size = 0;
+
+ xkb_layout_index_t current_layout_index = 0;
+
+ int32_t repeat_key_delay_msec = 0;
+ int32_t repeat_start_delay_msec = 0;
+
+ xkb_keycode_t repeating_keycode = XKB_KEYCODE_INVALID;
+ uint64_t last_repeat_start_msec = 0;
+ uint64_t last_repeat_msec = 0;
+
+ bool shift_pressed = false;
+ bool ctrl_pressed = false;
+ bool alt_pressed = false;
+ bool meta_pressed = false;
+
+ uint32_t last_key_pressed_serial = 0;
+
+ struct wl_data_device *wl_data_device = nullptr;
+
+ // Drag and drop.
+ struct wl_data_offer *wl_data_offer_dnd = nullptr;
+ uint32_t dnd_enter_serial = 0;
+
+ // Clipboard.
+ struct wl_data_source *wl_data_source_selection = nullptr;
+ Vector<uint8_t> selection_data;
+
+ struct wl_data_offer *wl_data_offer_selection = nullptr;
+
+ // Primary selection.
+ struct zwp_primary_selection_device_v1 *wp_primary_selection_device = nullptr;
+
+ struct zwp_primary_selection_source_v1 *wp_primary_selection_source = nullptr;
+ Vector<uint8_t> primary_data;
+
+ struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer = nullptr;
+
+ // Tablet.
+ struct zwp_tablet_seat_v2 *wp_tablet_seat = nullptr;
+
+ List<struct zwp_tablet_tool_v2 *> tablet_tools;
+
+ TabletToolData tablet_tool_data_buffer;
+ TabletToolData tablet_tool_data;
+ };
+
+ struct CustomCursor {
+ struct wl_buffer *wl_buffer = nullptr;
+ uint32_t *buffer_data = nullptr;
+ uint32_t buffer_data_size = 0;
+
+ RID rid;
+ Point2i hotspot;
+ };
+
+private:
+ struct ThreadData {
+ SafeFlag thread_done;
+ Mutex mutex;
+
+ struct wl_display *wl_display = nullptr;
+ };
+
+ // FIXME: Is this the right thing to do?
+ inline static const char *proxy_tag = "godot";
+
+ Thread events_thread;
+ ThreadData thread_data;
+
+ WindowState main_window;
+
+ List<Ref<Message>> messages;
+
+ String cursor_theme_name;
+ int unscaled_cursor_size = 24;
+
+ // NOTE: Regarding screen scale handling, the cursor cache is currently
+ // "static", by which I mean that we try to change it as little as possible and
+ // thus will be as big as the largest screen. This is mainly due to the fact
+ // that doing it dynamically doesn't look like it's worth it to me currently,
+ // especially as usually screen scales don't change continuously.
+ int cursor_scale = 1;
+
+ struct wl_cursor_theme *wl_cursor_theme = nullptr;
+ struct wl_cursor *wl_cursors[DisplayServer::CURSOR_MAX] = {};
+
+ HashMap<DisplayServer::CursorShape, CustomCursor> custom_cursors;
+
+ struct wl_cursor *current_wl_cursor = nullptr;
+ struct CustomCursor *current_custom_cursor = nullptr;
+
+ DisplayServer::CursorShape last_cursor_shape = DisplayServer::CURSOR_ARROW;
+
+ PointerConstraint pointer_constraint = PointerConstraint::NONE;
+
+ struct wl_display *wl_display = nullptr;
+ struct wl_registry *wl_registry = nullptr;
+
+ struct wl_seat *wl_seat_current = nullptr;
+
+ bool frame = true;
+
+ RegistryState registry;
+
+ bool initialized = false;
+
+#ifdef LIBDECOR_ENABLED
+ struct libdecor *libdecor_context = nullptr;
+#endif // LIBDECOR_ENABLED
+
+ // Main polling method.
+ static void _poll_events_thread(void *p_data);
+
+ // Core Wayland event handlers.
+ static void _wl_registry_on_global(void *data, struct wl_registry *wl_registry, uint32_t name, const char *interface, uint32_t version);
+ static void _wl_registry_on_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name);
+
+ static void _wl_surface_on_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output);
+ static void _wl_surface_on_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output);
+ static void _wl_surface_on_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t factor);
+ static void _wl_surface_on_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform);
+
+ static void _frame_wl_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t callback_data);
+
+ static void _wl_output_on_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char *make, const char *model, int32_t transform);
+ static void _wl_output_on_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh);
+ static void _wl_output_on_done(void *data, struct wl_output *wl_output);
+ static void _wl_output_on_scale(void *data, struct wl_output *wl_output, int32_t factor);
+ static void _wl_output_on_name(void *data, struct wl_output *wl_output, const char *name);
+ static void _wl_output_on_description(void *data, struct wl_output *wl_output, const char *description);
+
+ static void _wl_seat_on_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities);
+ static void _wl_seat_on_name(void *data, struct wl_seat *wl_seat, const char *name);
+
+ static void _cursor_frame_callback_on_done(void *data, struct wl_callback *wl_callback, uint32_t time_ms);
+
+ static void _wl_pointer_on_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
+ static void _wl_pointer_on_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface);
+ static void _wl_pointer_on_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y);
+ static void _wl_pointer_on_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state);
+ static void _wl_pointer_on_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
+ static void _wl_pointer_on_frame(void *data, struct wl_pointer *wl_pointer);
+ static void _wl_pointer_on_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source);
+ static void _wl_pointer_on_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis);
+ static void _wl_pointer_on_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete);
+ static void _wl_pointer_on_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120);
+ static void _wl_pointer_on_axis_relative_direction(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction);
+
+ static void _wl_keyboard_on_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size);
+ static void _wl_keyboard_on_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys);
+ static void _wl_keyboard_on_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface);
+ static void _wl_keyboard_on_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state);
+ static void _wl_keyboard_on_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group);
+ static void _wl_keyboard_on_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay);
+
+ static void _wl_data_device_on_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
+ static void _wl_data_device_on_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id);
+ static void _wl_data_device_on_leave(void *data, struct wl_data_device *wl_data_device);
+ static void _wl_data_device_on_motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y);
+ static void _wl_data_device_on_drop(void *data, struct wl_data_device *wl_data_device);
+ static void _wl_data_device_on_selection(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id);
+
+ static void _wl_data_offer_on_offer(void *data, struct wl_data_offer *wl_data_offer, const char *mime_type);
+ static void _wl_data_offer_on_source_actions(void *data, struct wl_data_offer *wl_data_offer, uint32_t source_actions);
+ static void _wl_data_offer_on_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action);
+
+ static void _wl_data_source_on_target(void *data, struct wl_data_source *wl_data_source, const char *mime_type);
+ static void _wl_data_source_on_send(void *data, struct wl_data_source *wl_data_source, const char *mime_type, int32_t fd);
+ static void _wl_data_source_on_cancelled(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_dnd_drop_performed(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_dnd_finished(void *data, struct wl_data_source *wl_data_source);
+ static void _wl_data_source_on_action(void *data, struct wl_data_source *wl_data_source, uint32_t dnd_action);
+
+ // xdg-shell event handlers.
+ static void _xdg_wm_base_on_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial);
+
+ static void _xdg_surface_on_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
+
+ static void _xdg_toplevel_on_configure(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height, struct wl_array *states);
+ static void _xdg_toplevel_on_close(void *data, struct xdg_toplevel *xdg_toplevel);
+ static void _xdg_toplevel_on_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height);
+ static void _xdg_toplevel_on_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities);
+
+ // wayland-protocols event handlers.
+ static void _wp_fractional_scale_on_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
+
+ static void _wp_relative_pointer_on_relative_motion(void *data, struct zwp_relative_pointer_v1 *wp_relative_pointer_v1, uint32_t uptime_hi, uint32_t uptime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel);
+
+ static void _wp_pointer_gesture_pinch_on_begin(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, struct wl_surface *surface, uint32_t fingers);
+ static void _wp_pointer_gesture_pinch_on_update(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t time, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t scale, wl_fixed_t rotation);
+ static void _wp_pointer_gesture_pinch_on_end(void *data, struct zwp_pointer_gesture_pinch_v1 *zwp_pointer_gesture_pinch_v1, uint32_t serial, uint32_t time, int32_t cancelled);
+
+ static void _wp_primary_selection_device_on_data_offer(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *offer);
+ static void _wp_primary_selection_device_on_selection(void *data, struct zwp_primary_selection_device_v1 *wp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id);
+
+ static void _wp_primary_selection_offer_on_offer(void *data, struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type);
+
+ static void _wp_primary_selection_source_on_send(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1, const char *mime_type, int32_t fd);
+ static void _wp_primary_selection_source_on_cancelled(void *data, struct zwp_primary_selection_source_v1 *wp_primary_selection_source_v1);
+
+ static void _wp_tablet_seat_on_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_v2 *id);
+ static void _wp_tablet_seat_on_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_tool_v2 *id);
+ static void _wp_tablet_seat_on_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2, struct zwp_tablet_pad_v2 *id);
+
+ static void _wp_tablet_tool_on_type(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type);
+ static void _wp_tablet_tool_on_hardware_serial(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_serial_hi, uint32_t hardware_serial_lo);
+ static void _wp_tablet_tool_on_hardware_id_wacom(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t hardware_id_hi, uint32_t hardware_id_lo);
+ static void _wp_tablet_tool_on_capability(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability);
+ static void _wp_tablet_tool_on_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_proximity_in(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface);
+ static void _wp_tablet_tool_on_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_down(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial);
+ static void _wp_tablet_tool_on_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2);
+ static void _wp_tablet_tool_on_motion(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y);
+ static void _wp_tablet_tool_on_pressure(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure);
+ static void _wp_tablet_tool_on_distance(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance);
+ static void _wp_tablet_tool_on_tilt(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t tilt_x, wl_fixed_t tilt_y);
+ static void _wp_tablet_tool_on_rotation(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees);
+ static void _wp_tablet_tool_on_slider(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position);
+ static void _wp_tablet_tool_on_wheel(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees, int32_t clicks);
+ static void _wp_tablet_tool_on_button(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial, uint32_t button, uint32_t state);
+ static void _wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time);
+
+ static void _xdg_toplevel_decoration_on_configure(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode);
+
+ static void _xdg_exported_on_exported(void *data, zxdg_exported_v1 *exported, const char *handle);
+
+ static void _xdg_activation_token_on_done(void *data, struct xdg_activation_token_v1 *xdg_activation_token, const char *token);
+
+ // Core Wayland event listeners.
+ static constexpr struct wl_registry_listener wl_registry_listener = {
+ .global = _wl_registry_on_global,
+ .global_remove = _wl_registry_on_global_remove,
+ };
+
+ static constexpr struct wl_surface_listener wl_surface_listener = {
+ .enter = _wl_surface_on_enter,
+ .leave = _wl_surface_on_leave,
+ .preferred_buffer_scale = _wl_surface_on_preferred_buffer_scale,
+ .preferred_buffer_transform = _wl_surface_on_preferred_buffer_transform,
+ };
+
+ static constexpr struct wl_callback_listener frame_wl_callback_listener {
+ .done = _frame_wl_callback_on_done,
+ };
+
+ static constexpr struct wl_output_listener wl_output_listener = {
+ .geometry = _wl_output_on_geometry,
+ .mode = _wl_output_on_mode,
+ .done = _wl_output_on_done,
+ .scale = _wl_output_on_scale,
+ .name = _wl_output_on_name,
+ .description = _wl_output_on_description,
+ };
+
+ static constexpr struct wl_seat_listener wl_seat_listener = {
+ .capabilities = _wl_seat_on_capabilities,
+ .name = _wl_seat_on_name,
+ };
+
+ static constexpr struct wl_callback_listener cursor_frame_callback_listener {
+ .done = _cursor_frame_callback_on_done,
+ };
+
+ static constexpr struct wl_pointer_listener wl_pointer_listener = {
+ .enter = _wl_pointer_on_enter,
+ .leave = _wl_pointer_on_leave,
+ .motion = _wl_pointer_on_motion,
+ .button = _wl_pointer_on_button,
+ .axis = _wl_pointer_on_axis,
+ .frame = _wl_pointer_on_frame,
+ .axis_source = _wl_pointer_on_axis_source,
+ .axis_stop = _wl_pointer_on_axis_stop,
+ .axis_discrete = _wl_pointer_on_axis_discrete,
+ .axis_value120 = _wl_pointer_on_axis_value120,
+ .axis_relative_direction = _wl_pointer_on_axis_relative_direction,
+ };
+
+ static constexpr struct wl_keyboard_listener wl_keyboard_listener = {
+ .keymap = _wl_keyboard_on_keymap,
+ .enter = _wl_keyboard_on_enter,
+ .leave = _wl_keyboard_on_leave,
+ .key = _wl_keyboard_on_key,
+ .modifiers = _wl_keyboard_on_modifiers,
+ .repeat_info = _wl_keyboard_on_repeat_info,
+ };
+
+ static constexpr struct wl_data_device_listener wl_data_device_listener = {
+ .data_offer = _wl_data_device_on_data_offer,
+ .enter = _wl_data_device_on_enter,
+ .leave = _wl_data_device_on_leave,
+ .motion = _wl_data_device_on_motion,
+ .drop = _wl_data_device_on_drop,
+ .selection = _wl_data_device_on_selection,
+ };
+
+ static constexpr struct wl_data_offer_listener wl_data_offer_listener = {
+ .offer = _wl_data_offer_on_offer,
+ .source_actions = _wl_data_offer_on_source_actions,
+ .action = _wl_data_offer_on_action,
+ };
+
+ static constexpr struct wl_data_source_listener wl_data_source_listener = {
+ .target = _wl_data_source_on_target,
+ .send = _wl_data_source_on_send,
+ .cancelled = _wl_data_source_on_cancelled,
+ .dnd_drop_performed = _wl_data_source_on_dnd_drop_performed,
+ .dnd_finished = _wl_data_source_on_dnd_finished,
+ .action = _wl_data_source_on_action,
+ };
+
+ // xdg-shell event listeners.
+ static constexpr struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = _xdg_wm_base_on_ping,
+ };
+
+ static constexpr struct xdg_surface_listener xdg_surface_listener = {
+ .configure = _xdg_surface_on_configure,
+ };
+
+ static constexpr struct xdg_toplevel_listener xdg_toplevel_listener = {
+ .configure = _xdg_toplevel_on_configure,
+ .close = _xdg_toplevel_on_close,
+ .configure_bounds = _xdg_toplevel_on_configure_bounds,
+ .wm_capabilities = _xdg_toplevel_on_wm_capabilities,
+ };
+
+ // wayland-protocols event listeners.
+ static constexpr struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
+ .preferred_scale = _wp_fractional_scale_on_preferred_scale,
+ };
+
+ static constexpr struct zwp_relative_pointer_v1_listener wp_relative_pointer_listener = {
+ .relative_motion = _wp_relative_pointer_on_relative_motion,
+ };
+
+ static constexpr struct zwp_pointer_gesture_pinch_v1_listener wp_pointer_gesture_pinch_listener = {
+ .begin = _wp_pointer_gesture_pinch_on_begin,
+ .update = _wp_pointer_gesture_pinch_on_update,
+ .end = _wp_pointer_gesture_pinch_on_end,
+ };
+
+ static constexpr struct zwp_primary_selection_device_v1_listener wp_primary_selection_device_listener = {
+ .data_offer = _wp_primary_selection_device_on_data_offer,
+ .selection = _wp_primary_selection_device_on_selection,
+ };
+
+ static constexpr struct zwp_primary_selection_offer_v1_listener wp_primary_selection_offer_listener = {
+ .offer = _wp_primary_selection_offer_on_offer,
+ };
+
+ static constexpr struct zwp_primary_selection_source_v1_listener wp_primary_selection_source_listener = {
+ .send = _wp_primary_selection_source_on_send,
+ .cancelled = _wp_primary_selection_source_on_cancelled,
+ };
+
+ static constexpr struct zwp_tablet_seat_v2_listener wp_tablet_seat_listener = {
+ .tablet_added = _wp_tablet_seat_on_tablet_added,
+ .tool_added = _wp_tablet_seat_on_tool_added,
+ .pad_added = _wp_tablet_seat_on_pad_added,
+ };
+
+ static constexpr struct zwp_tablet_tool_v2_listener wp_tablet_tool_listener = {
+ .type = _wp_tablet_tool_on_type,
+ .hardware_serial = _wp_tablet_tool_on_hardware_serial,
+ .hardware_id_wacom = _wp_tablet_tool_on_hardware_id_wacom,
+ .capability = _wp_tablet_tool_on_capability,
+ .done = _wp_tablet_tool_on_done,
+ .removed = _wp_tablet_tool_on_removed,
+ .proximity_in = _wp_tablet_tool_on_proximity_in,
+ .proximity_out = _wp_tablet_tool_on_proximity_out,
+ .down = _wp_tablet_tool_on_down,
+ .up = _wp_tablet_tool_on_up,
+ .motion = _wp_tablet_tool_on_motion,
+ .pressure = _wp_tablet_tool_on_pressure,
+ .distance = _wp_tablet_tool_on_distance,
+ .tilt = _wp_tablet_tool_on_tilt,
+ .rotation = _wp_tablet_tool_on_rotation,
+ .slider = _wp_tablet_tool_on_slider,
+ .wheel = _wp_tablet_tool_on_wheel,
+ .button = _wp_tablet_tool_on_button,
+ .frame = _wp_tablet_tool_on_frame,
+ };
+
+ static constexpr struct zxdg_exported_v1_listener xdg_exported_listener = {
+ .handle = _xdg_exported_on_exported
+ };
+
+ static constexpr struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
+ .configure = _xdg_toplevel_decoration_on_configure,
+ };
+
+ static constexpr struct xdg_activation_token_v1_listener xdg_activation_token_listener = {
+ .done = _xdg_activation_token_on_done,
+ };
+
+#ifdef LIBDECOR_ENABLED
+ // libdecor event handlers.
+ static void libdecor_on_error(struct libdecor *context, enum libdecor_error error, const char *message);
+
+ static void libdecor_frame_on_configure(struct libdecor_frame *frame, struct libdecor_configuration *configuration, void *user_data);
+
+ static void libdecor_frame_on_close(struct libdecor_frame *frame, void *user_data);
+
+ static void libdecor_frame_on_commit(struct libdecor_frame *frame, void *user_data);
+
+ static void libdecor_frame_on_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data);
+
+ // libdecor event listeners.
+ static constexpr struct libdecor_interface libdecor_interface = {
+ .error = libdecor_on_error,
+ .reserved0 = nullptr,
+ .reserved1 = nullptr,
+ .reserved2 = nullptr,
+ .reserved3 = nullptr,
+ .reserved4 = nullptr,
+ .reserved5 = nullptr,
+ .reserved6 = nullptr,
+ .reserved7 = nullptr,
+ .reserved8 = nullptr,
+ .reserved9 = nullptr,
+ };
+
+ static constexpr struct libdecor_frame_interface libdecor_frame_interface = {
+ .configure = libdecor_frame_on_configure,
+ .close = libdecor_frame_on_close,
+ .commit = libdecor_frame_on_commit,
+ .dismiss_popup = libdecor_frame_on_dismiss_popup,
+ .reserved0 = nullptr,
+ .reserved1 = nullptr,
+ .reserved2 = nullptr,
+ .reserved3 = nullptr,
+ .reserved4 = nullptr,
+ .reserved5 = nullptr,
+ .reserved6 = nullptr,
+ .reserved7 = nullptr,
+ .reserved8 = nullptr,
+ .reserved9 = nullptr,
+ };
+#endif // LIBDECOR_ENABLED
+
+ static Vector<uint8_t> _read_fd(int fd);
+ static int _allocate_shm_file(size_t size);
+
+ static Vector<uint8_t> _wl_data_offer_read(struct wl_display *wl_display, const char *p_mime, struct wl_data_offer *wl_data_offer);
+ static Vector<uint8_t> _wp_primary_selection_offer_read(struct wl_display *wl_display, const char *p_mime, struct zwp_primary_selection_offer_v1 *wp_primary_selection_offer);
+
+ static void _seat_state_set_current(WaylandThread::SeatState &p_ss);
+ static bool _seat_state_configure_key_event(WaylandThread::SeatState &p_seat, Ref<InputEventKey> p_event, xkb_keycode_t p_keycode, bool p_pressed);
+
+ static void _wayland_state_update_cursor();
+
+ void _set_current_seat(struct wl_seat *p_seat);
+
+ bool _load_cursor_theme(int p_cursor_size);
+
+ void _update_scale(int p_scale);
+
+public:
+ Mutex &mutex = thread_data.mutex;
+
+ struct wl_display *get_wl_display() const;
+
+ // Core Wayland utilities for integrating with our own data structures.
+ static bool wl_proxy_is_godot(struct wl_proxy *p_proxy);
+ static void wl_proxy_tag_godot(struct wl_proxy *p_proxy);
+
+ static WindowState *wl_surface_get_window_state(struct wl_surface *p_surface);
+ static ScreenState *wl_output_get_screen_state(struct wl_output *p_output);
+ static SeatState *wl_seat_get_seat_state(struct wl_seat *p_seat);
+ static OfferState *wl_data_offer_get_offer_state(struct wl_data_offer *p_offer);
+
+ static OfferState *wp_primary_selection_offer_get_offer_state(struct zwp_primary_selection_offer_v1 *p_offer);
+
+ void seat_state_unlock_pointer(SeatState *p_ss);
+ void seat_state_lock_pointer(SeatState *p_ss);
+ void seat_state_set_hint(SeatState *p_ss, int p_x, int p_y);
+ void seat_state_confine_pointer(SeatState *p_ss);
+
+ static void seat_state_update_cursor(SeatState *p_ss);
+
+ void seat_state_echo_keys(SeatState *p_ss);
+
+ static int window_state_get_preferred_buffer_scale(WindowState *p_ws);
+ static double window_state_get_scale_factor(WindowState *p_ws);
+ static void window_state_update_size(WindowState *p_ws, int p_width, int p_height);
+
+ static Vector2i scale_vector2i(const Vector2i &p_vector, double p_amount);
+
+ void push_message(Ref<Message> message);
+ bool has_message();
+ Ref<Message> pop_message();
+
+ void window_create(DisplayServer::WindowID p_window_id, int p_width, int p_height);
+
+ struct wl_surface *window_get_wl_surface(DisplayServer::WindowID p_window_id) const;
+
+ void window_set_max_size(DisplayServer::WindowID p_window_id, const Size2i &p_size);
+ void window_set_min_size(DisplayServer::WindowID p_window_id, const Size2i &p_size);
+
+ bool window_can_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode) const;
+ void window_try_set_mode(DisplayServer::WindowID p_window_id, DisplayServer::WindowMode p_window_mode);
+ DisplayServer::WindowMode window_get_mode(DisplayServer::WindowID p_window_id) const;
+
+ void window_set_borderless(DisplayServer::WindowID p_window_id, bool p_borderless);
+ void window_set_title(DisplayServer::WindowID p_window_id, const String &p_title);
+ void window_set_app_id(DisplayServer::WindowID p_window_id, const String &p_app_id);
+
+ bool window_is_focused(DisplayServer::WindowID p_window_id);
+
+ // Optional - requires xdg_activation_v1
+ void window_request_attention(DisplayServer::WindowID p_window_id);
+
+ // Optional - require idle_inhibit_unstable_v1
+ void window_set_idle_inhibition(DisplayServer::WindowID p_window_id, bool p_enable);
+ bool window_get_idle_inhibition(DisplayServer::WindowID p_window_id) const;
+
+ ScreenData screen_get_data(int p_screen) const;
+ int get_screen_count() const;
+
+ void pointer_set_constraint(PointerConstraint p_constraint);
+ void pointer_set_hint(const Point2i &p_hint);
+ PointerConstraint pointer_get_constraint() const;
+ DisplayServer::WindowID pointer_get_pointed_window_id() const;
+ BitField<MouseButtonMask> pointer_get_button_mask() const;
+
+ void cursor_hide();
+ void cursor_set_shape(DisplayServer::CursorShape p_cursor_shape);
+
+ void cursor_set_custom_shape(DisplayServer::CursorShape p_cursor_shape);
+ void cursor_shape_set_custom_image(DisplayServer::CursorShape p_cursor_shape, Ref<Image> p_image, const Point2i &p_hotspot);
+ void cursor_shape_clear_custom_image(DisplayServer::CursorShape p_cursor_shape);
+
+ int keyboard_get_layout_count() const;
+ int keyboard_get_current_layout_index() const;
+ void keyboard_set_current_layout_index(int p_index);
+ String keyboard_get_layout_name(int p_index) const;
+
+ Key keyboard_get_key_from_physical(Key p_key) const;
+
+ void keyboard_echo_keys();
+
+ bool selection_has_mime(const String &p_mime) const;
+ Vector<uint8_t> selection_get_mime(const String &p_mime) const;
+
+ void selection_set_text(const String &p_text);
+
+ // Optional primary support - requires wp_primary_selection_unstable_v1
+ bool primary_has_mime(const String &p_mime) const;
+ Vector<uint8_t> primary_get_mime(const String &p_mime) const;
+
+ void primary_set_text(const String &p_text);
+
+ void set_frame();
+ bool get_reset_frame();
+
+ Error init();
+ void destroy();
+};
+
+#endif // WAYLAND_ENABLED
+
+#endif // WAYLAND_THREAD_H
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 13a0a9f877..93d528bab6 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -372,7 +372,18 @@ Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_
}
String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
- return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, String(), p_filename, p_mode, p_filters, TypedArray<Dictionary>(), p_callback, false);
+}
+
+Error DisplayServerX11::file_dialog_with_options_show(const String &p_title, const String &p_current_directory, const String &p_root, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const TypedArray<Dictionary> &p_options, const Callable &p_callback) {
+ WindowID window_id = last_focused_window;
+
+ if (!windows.has(window_id)) {
+ window_id = MAIN_WINDOW_ID;
+ }
+
+ String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_root, p_filename, p_mode, p_filters, p_options, p_callback, true);
}
#endif
@@ -3501,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';
@@ -3538,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) {
@@ -3563,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.
@@ -3662,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) {
@@ -6061,10 +6080,11 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
if (context_rd) {
if (context_rd->initialize() != OK) {
+ ERR_PRINT(vformat("Could not initialize %s", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
r_error = ERR_CANT_CREATE;
- ERR_FAIL_MSG(vformat("Could not initialize %s", context_rd->get_api_name()));
+ return;
}
driver_found = true;
}
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 27bf7951ff..da4085772a 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -402,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;
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/crash_handler_macos.mm b/platform/macos/crash_handler_macos.mm
index 7c0cab0210..c370422bfa 100644
--- a/platform/macos/crash_handler_macos.mm
+++ b/platform/macos/crash_handler_macos.mm
@@ -75,6 +75,7 @@ static void handle_crash(int sig) {
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGILL, SIG_DFL);
+ signal(SIGTRAP, SIG_DFL);
if (OS::get_singleton() == nullptr) {
abort();
@@ -193,6 +194,7 @@ void CrashHandler::disable() {
signal(SIGSEGV, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGILL, SIG_DFL);
+ signal(SIGTRAP, SIG_DFL);
#endif
disabled = true;
@@ -203,5 +205,6 @@ void CrashHandler::initialize() {
signal(SIGSEGV, handle_crash);
signal(SIGFPE, handle_crash);
signal(SIGILL, handle_crash);
+ signal(SIGTRAP, handle_crash);
#endif
}
diff --git a/platform/macos/detect.py b/platform/macos/detect.py
index 4a8e9cd956..0d1e40fb3d 100644
--- a/platform/macos/detect.py
+++ b/platform/macos/detect.py
@@ -67,13 +67,14 @@ def get_mvk_sdk_path():
if not os.path.exists(dirname):
return ""
+ ver_min = ver_parse("1.3.231.0")
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)
- if ver_comp > ver_num:
+ if ver_comp > ver_num and ver_comp >= ver_min:
# Try new SDK location.
lib_name = os.path.join(
os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index cc343a6d64..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;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 378688f78a..cad8435cbb 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);
@@ -1672,6 +1676,10 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu) {
+ [sub_menu setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
}
}
}
@@ -2079,139 +2087,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;
+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);
}
-- (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;
+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);
}
-- (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;
- }
-}
-
-@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_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 +2154,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 +2219,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 +2270,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)));
+ }
}
}
}
diff --git a/platform/macos/godot_application_delegate.mm b/platform/macos/godot_application_delegate.mm
index a1925195b8..c1de8ade58 100644
--- a/platform/macos/godot_application_delegate.mm
+++ b/platform/macos/godot_application_delegate.mm
@@ -35,6 +35,10 @@
@implementation GodotApplicationDelegate
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
+ return YES;
+}
+
- (void)forceUnbundledWindowActivationHackStep1 {
// Step 1: Switch focus to macOS SystemUIServer process.
// Required to perform step 2, TransformProcessType will fail if app is already the in focus.
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 139411249c..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/detect.py b/platform/web/detect.py
index bce03eb79e..bbe1634dfa 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -169,7 +169,7 @@ def configure(env: "Environment"):
env.AddMethod(create_template_zip, "CreateTemplateZip")
# Closure compiler extern and support for ecmascript specs (const, let, etc).
- env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2020"
+ env["ENV"]["EMCC_CLOSURE_ARGS"] = "--language_in ECMASCRIPT_2021"
env["CC"] = "emcc"
env["CXX"] = "em++"
@@ -206,6 +206,11 @@ def configure(env: "Environment"):
env.Append(LINKFLAGS=["-s", "USE_WEBGL2=1"])
# Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-s", "OFFSCREEN_FRAMEBUFFER=1"])
+ # Breaking change since emscripten 3.1.51
+ # https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md#3151---121323
+ if cc_semver >= (3, 1, 51):
+ # Enables the use of *glGetProcAddress()
+ env.Append(LINKFLAGS=["-s", "GL_ENABLE_GET_PROC_ADDRESS=1"])
if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
@@ -229,6 +234,9 @@ def configure(env: "Environment"):
# Workaround https://github.com/emscripten-core/emscripten/issues/19781.
if cc_semver >= (3, 1, 42) and cc_semver < (3, 1, 46):
env.Append(LINKFLAGS=["-Wl,-u,scalbnf"])
+ # Workaround https://github.com/emscripten-core/emscripten/issues/16836.
+ if cc_semver >= (3, 1, 47):
+ env.Append(LINKFLAGS=["-Wl,-u,_emscripten_run_callback_on_thread"])
if env["dlink_enabled"]:
if env["proxy_to_pthread"]:
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/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/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index d7e72612b4..e2967ee8b8 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -676,7 +676,7 @@ Ref<Texture2D> EditorExportPlatformWeb::get_run_icon() const {
void EditorExportPlatformWeb::_server_thread_poll(void *data) {
EditorExportPlatformWeb *ej = static_cast<EditorExportPlatformWeb *>(data);
- while (!ej->server_quit) {
+ while (!ej->server_quit.get()) {
OS::get_singleton()->delay_usec(6900);
{
MutexLock lock(ej->server_lock);
@@ -714,7 +714,7 @@ EditorExportPlatformWeb::~EditorExportPlatformWeb() {
if (server.is_valid()) {
server->stop();
}
- server_quit = true;
+ server_quit.set(true);
if (server_thread.is_started()) {
server_thread.wait_to_finish();
}
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index 98e3fe729e..c73d6d6794 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -52,7 +52,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform {
int menu_options = 0;
Ref<EditorHTTPServer> server;
- bool server_quit = false;
+ SafeNumeric<bool> server_quit;
Mutex server_lock;
Thread server_thread;
diff --git a/platform/web/js/engine/config.js b/platform/web/js/engine/config.js
index 0b6626968e..a7134da6fa 100644
--- a/platform/web/js/engine/config.js
+++ b/platform/web/js/engine/config.js
@@ -19,7 +19,7 @@ const EngineConfig = {}; // eslint-disable-line no-unused-vars
const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars
const cfg = /** @lends {InternalConfig.prototype} */ {
/**
- * Whether the unload the engine automatically after the instance is initialized.
+ * Whether to unload the engine automatically after the instance is initialized.
*
* @memberof EngineConfig
* @default
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 1cfbc33ef8..6010d4ba76 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -7,6 +7,8 @@ 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",
@@ -43,7 +45,8 @@ 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)
@@ -65,6 +68,7 @@ if env["windows_subsystem"] == "gui":
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"]:
@@ -97,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.
@@ -109,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"
@@ -121,7 +125,7 @@ if env["d3d12"]:
)
# PIX
- if env["pix_path"] != "":
+ if env["use_pix"]:
pix_dll = "WinPixEventRuntime.dll"
env.Command(
"#bin/" + pix_dll,
@@ -134,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 79698f5bd7..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,6 +474,16 @@ def configure_msvc(env, vcvars_msvc_config):
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"])
LIBS += ["d3d12", "dxgi", "dxguid"]
LIBS += ["version"] # Mesa dependency.
@@ -449,18 +492,16 @@ def configure_msvc(env, vcvars_msvc_config):
if env["target"] == "release_debug":
env.Append(CXXFLAGS=["/bigobj"])
- arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
-
# PIX
- if env["pix_path"] != "":
+ if not env["arch"] in ["x86_64", "arm64"] or env["pix_path"] == "" or not os.path.exists(env["pix_path"]):
+ env["use_pix"] = False
+
+ if env["use_pix"]:
+ arch_subdir = "arm64" if env["arch"] == "arm64" else "x64"
+
env.Append(LIBPATH=[env["pix_path"] + "/bin/" + arch_subdir])
LIBS += ["WinPixEventRuntime"]
- # Mesa
- if env["mesa_libs"] == "":
- print("The Direct3D 12 rendering driver requires mesa_libs to be set.")
- sys.exit(255)
-
env.Append(LIBPATH=[env["mesa_libs"] + "/bin"])
LIBS += ["libNIR.windows." + env["arch"]]
@@ -658,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 b56954ae81..c5bcb70d9f 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)));
+ }
}
}
}
@@ -2963,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();
@@ -3095,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.
@@ -3141,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;
}
@@ -3159,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();
@@ -4164,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);
@@ -4190,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) {
@@ -4355,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
@@ -4380,10 +4625,11 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
}
#endif
if (context_rd->window_create(id, p_vsync_mode, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top, &wpd) != OK) {
+ ERR_PRINT(vformat("Failed to create %s Window.", context_rd->get_api_name()));
memdelete(context_rd);
context_rd = nullptr;
windows.erase(id);
- ERR_FAIL_V_MSG(INVALID_WINDOW_ID, vformat("Failed to create %s Window.", context_rd->get_api_name()));
+ return INVALID_WINDOW_ID;
}
wd.context_created = true;
}
@@ -4493,6 +4739,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;
@@ -4614,8 +4861,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 2668e14540..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;
diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis
index 94cc52be98..d049ed9a3d 100644
--- a/platform/windows/godot.natvis
+++ b/platform/windows/godot.natvis
@@ -2,9 +2,9 @@
<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>
+ <Size>_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Size>
<ValuePointer>($T1 *) _cowdata._ptr</ValuePointer>
</ArrayItems>
</Expand>
@@ -12,9 +12,9 @@
<Type Name="Array">
<Expand>
- <Item Name="[size]">_p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Item>
+ <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 int *)(_p->array._cowdata._ptr))[-1]) : 0</Size>
+ <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>
@@ -22,9 +22,9 @@
<Type Name="TypedArray&lt;*&gt;">
<Expand>
- <Item Name="[size]"> _p->array._cowdata._ptr ? (((const unsigned int *)(_p->array._cowdata._ptr))[-1]) : 0</Item>
+ <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 int *)(_p->array._cowdata._ptr))[-1]) : 0</Size>
+ <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>
@@ -77,7 +77,7 @@
<Type Name="Vector&lt;StringName&gt;" IncludeView="NodePathHelper">
<Expand>
<ArrayItems>
- <Size>_cowdata._ptr ? (((const unsigned int *)(_cowdata._ptr))[-1]) : 0</Size>
+ <Size>_cowdata._ptr ? (((const unsigned long long *)(_cowdata._ptr))[-1]) : 0</Size>
<ValuePointer>((StringName *)_cowdata._ptr),view(NodePathHelper)</ValuePointer>
</ArrayItems>
</Expand>
@@ -140,9 +140,9 @@
<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>
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!",
+ ]