diff options
Diffstat (limited to 'platform')
56 files changed, 1334 insertions, 996 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py index 61ccad9ac3..6b0c60fe8b 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -27,8 +27,7 @@ def get_opts(): ("ANDROID_NDK_ROOT", "Path to the Android NDK", get_android_ndk_root()), ("ANDROID_SDK_ROOT", "Path to the Android SDK", get_android_sdk_root()), ("ndk_platform", 'Target platform (android-<api>, e.g. "android-24")', "android-24"), - EnumVariable("android_arch", "Target architecture", "armv7", ("armv7", "arm64v8", "x86", "x86_64")), - BoolVariable("android_neon", "Enable NEON support (armv7 only)", True), + EnumVariable("android_arch", "Target architecture", "arm64v8", ("armv7", "arm64v8", "x86", "x86_64")), ] @@ -143,10 +142,7 @@ def configure(env): if env["android_arch"] not in ["armv7", "arm64v8", "x86", "x86_64"]: env["android_arch"] = "armv7" - neon_text = "" - if env["android_arch"] == "armv7" and env["android_neon"]: - neon_text = " (with NEON)" - print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")" + neon_text) + print("Building for Android, platform " + env["ndk_platform"] + " (" + env["android_arch"] + ")") can_vectorize = True if env["android_arch"] == "x86": @@ -174,10 +170,7 @@ def configure(env): target_subpath = "arm-linux-androideabi-4.9" abi_subpath = "arm-linux-androideabi" arch_subpath = "armeabi-v7a" - if env["android_neon"]: - env.extra_suffix = ".armv7.neon" + env.extra_suffix - else: - env.extra_suffix = ".armv7" + env.extra_suffix + env.extra_suffix = ".armv7" + env.extra_suffix elif env["android_arch"] == "arm64v8": if get_platform(env["ndk_platform"]) < 21: print( @@ -210,6 +203,7 @@ def configure(env): env.Append(LINKFLAGS=["-O0"]) env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"]) env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) env.Append(CPPFLAGS=["-UNDEBUG"]) # Compiler configuration @@ -288,7 +282,6 @@ def configure(env): if get_platform(env["ndk_platform"]) >= 24: env.Append(CPPDEFINES=[("_FILE_OFFSET_BITS", 64)]) - env["neon_enabled"] = False if env["android_arch"] == "x86": target_opts = ["-target", "i686-none-linux-android"] # The NDK adds this if targeting API < 21, so we can drop it when Godot targets it at least @@ -301,12 +294,9 @@ def configure(env): target_opts = ["-target", "armv7-none-linux-androideabi"] env.Append(CCFLAGS="-march=armv7-a -mfloat-abi=softfp".split()) env.Append(CPPDEFINES=["__ARM_ARCH_7__", "__ARM_ARCH_7A__"]) - if env["android_neon"]: - env["neon_enabled"] = True - env.Append(CCFLAGS=["-mfpu=neon"]) - env.Append(CPPDEFINES=["__ARM_NEON__"]) - else: - env.Append(CCFLAGS=["-mfpu=vfpv3-d16"]) + # Enable ARM NEON instructions to compile more optimized code. + env.Append(CCFLAGS=["-mfpu=neon"]) + env.Append(CPPDEFINES=["__ARM_NEON__"]) elif env["android_arch"] == "arm64v8": target_opts = ["-target", "aarch64-none-linux-android"] diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 60ba1c558a..6ef17faf06 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -429,9 +429,8 @@ String EditorExportPlatformAndroid::get_package_name(const String &p_package) co return pname; } -String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset) const { - int export_format = int(p_preset->get("custom_template/export_format")); - return export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY; +String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const { + return p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY; } bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const { @@ -1645,10 +1644,12 @@ void EditorExportPlatformAndroid::get_export_options(List<ExportOption> *r_optio } plugins_changed.clear(); - Vector<String> abis = get_abis(); + const Vector<String> abis = get_abis(); for (int i = 0; i < abis.size(); ++i) { - String abi = abis[i]; - bool is_default = (abi == "armeabi-v7a" || abi == "arm64-v8a"); + const String abi = abis[i]; + // All Android devices supporting Vulkan run 64-bit Android, + // so there is usually no point in exporting for 32-bit Android. + const bool is_default = abi == "arm64-v8a"; r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "architectures/" + abi), is_default)); } @@ -2477,7 +2478,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return ERR_UNCONFIGURED; } } - const String assets_directory = get_assets_directory(p_preset); + const String assets_directory = get_assets_directory(p_preset, export_format); String sdk_path = EDITOR_GET("export/android/android_sdk_path"); ERR_FAIL_COND_V_MSG(sdk_path.is_empty(), ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/android_sdk_path'."); print_verbose("Android sdk path: " + sdk_path); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index d33f616f11..e0ffaa718b 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -104,7 +104,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String get_package_name(const String &p_package) const; - String get_assets_directory(const Ref<EditorExportPreset> &p_preset) const; + String get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const; bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index fcee54e493..3fafdf8573 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,12 +1,12 @@ ext.versions = [ - androidGradlePlugin: '4.2.2', + androidGradlePlugin: '7.0.3', compileSdk : 30, minSdk : 19, targetSdk : 30, buildTools : '30.0.3', kotlinVersion : '1.5.10', fragmentVersion : '1.3.6', - javaVersion : 1.8, + javaVersion : 11, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. ] diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.jar b/platform/android/java/gradle/wrapper/gradle-wrapper.jar Binary files differindex f6b961fd5a..e708b1c023 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.jar +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.jar diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index 74c5636f8a..ffed3a254e 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Wed Jun 23 23:42:22 PDT 2021 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/platform/android/java/gradlew b/platform/android/java/gradlew index cccdd3d517..4f906e0c81 100755 --- a/platform/android/java/gradlew +++ b/platform/android/java/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/platform/android/java/gradlew.bat b/platform/android/java/gradlew.bat index 11cc30edb0..107acd32c4 100644 --- a/platform/android/java/gradlew.bat +++ b/platform/android/java/gradlew.bat @@ -1,7 +1,23 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem Gradle startup script for Windows @rem @rem ########################################################################## @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell @@ -75,7 +80,7 @@ if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 21fb31d991..034885aa32 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -230,10 +230,13 @@ String OS_Android::get_user_data_dir() const { } String OS_Android::get_cache_path() const { + if (cache_dir_cache != String()) + return cache_dir_cache; + String cache_dir = godot_io_java->get_cache_dir(); if (cache_dir != "") { - cache_dir = _remove_symlink(cache_dir); - return cache_dir; + cache_dir_cache = _remove_symlink(cache_dir); + return cache_dir_cache; } return "."; } diff --git a/platform/android/os_android.h b/platform/android/os_android.h index c938297821..ce8083388f 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -57,6 +57,7 @@ private: #endif mutable String data_dir_cache; + mutable String cache_dir_cache; AudioDriverOpenSL audio_driver_android; diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index d10ea5c68c..c6f91665c5 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -44,7 +44,7 @@ extern int gargc; extern char **gargv; -extern int iphone_main(int, char **, String); +extern int iphone_main(int, char **, String, String); extern void iphone_finish(); @implementation AppDelegate @@ -67,8 +67,10 @@ static ViewController *mainViewController = nil; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; + paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *cacheDirectory = [paths objectAtIndex:0]; - int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String])); + int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]), String::utf8([cacheDirectory UTF8String])); if (err != 0) { // bail, things did not go very well for us, should probably output a message on screen with our error code... diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 05e24c5003..3c6453ff9b 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -59,6 +59,7 @@ def configure(env): elif env["target"] == "debug": env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) if env["use_lto"]: env.Append(CCFLAGS=["-flto"]) diff --git a/platform/iphone/godot_iphone.mm b/platform/iphone/godot_iphone.mm index 6c3e1eabde..6f6f9d0708 100644 --- a/platform/iphone/godot_iphone.mm +++ b/platform/iphone/godot_iphone.mm @@ -74,7 +74,7 @@ int add_cmdline(int p_argc, char **p_args) { return p_argc; }; -int iphone_main(int argc, char **argv, String data_dir) { +int iphone_main(int argc, char **argv, String data_dir, String cache_dir) { size_t len = strlen(argv[0]); while (len--) { @@ -95,7 +95,7 @@ int iphone_main(int argc, char **argv, String data_dir) { char cwd[512]; getcwd(cwd, sizeof(cwd)); printf("cwd %s\n", cwd); - os = new OSIPhone(data_dir); + os = new OSIPhone(data_dir, cache_dir); // We must override main when testing is enabled TEST_MAIN_OVERRIDE diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 248369369d..7a81d8f593 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -72,6 +72,7 @@ private: virtual void finalize() override; String user_data_dir; + String cache_dir; bool is_focused = false; @@ -80,7 +81,7 @@ private: public: static OSIPhone *get_singleton(); - OSIPhone(String p_data_dir); + OSIPhone(String p_data_dir, String p_cache_dir); ~OSIPhone(); void initialize_modules(); @@ -103,6 +104,8 @@ public: void set_user_data_dir(String p_dir); virtual String get_user_data_dir() const override; + virtual String get_cache_path() const override; + virtual String get_locale() const override; virtual String get_unique_id() const override; diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm index c88d253691..fc07d321b7 100644 --- a/platform/iphone/os_iphone.mm +++ b/platform/iphone/os_iphone.mm @@ -87,7 +87,7 @@ OSIPhone *OSIPhone::get_singleton() { return (OSIPhone *)OS::get_singleton(); } -OSIPhone::OSIPhone(String p_data_dir) { +OSIPhone::OSIPhone(String p_data_dir, String p_cache_dir) { for (int i = 0; i < ios_init_callbacks_count; ++i) { ios_init_callbacks[i](); } @@ -101,6 +101,7 @@ OSIPhone::OSIPhone(String p_data_dir) { // can't call set_data_dir from here, since it requires DirAccess // which is initialized in initialize_core user_data_dir = p_data_dir; + cache_dir = p_cache_dir; Vector<Logger *> loggers; loggers.push_back(memnew(SyslogLogger)); @@ -266,6 +267,10 @@ String OSIPhone::get_user_data_dir() const { return user_data_dir; } +String OSIPhone::get_cache_path() const { + return cache_dir; +} + String OSIPhone::get_locale() const { NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject; diff --git a/platform/javascript/.eslintrc.js b/platform/javascript/.eslintrc.js index 0ff9d67d26..2c81f1f02d 100644 --- a/platform/javascript/.eslintrc.js +++ b/platform/javascript/.eslintrc.js @@ -39,5 +39,13 @@ module.exports = { // Closure compiler (exported properties) "quote-props": ["error", "consistent"], "dot-notation": "off", + // No comma dangle for functions (it's madness, and ES2017) + "comma-dangle": ["error", { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], } }; diff --git a/platform/javascript/.eslintrc.libs.js b/platform/javascript/.eslintrc.libs.js index 81b1b8c864..8e579fd462 100644 --- a/platform/javascript/.eslintrc.libs.js +++ b/platform/javascript/.eslintrc.libs.js @@ -15,6 +15,7 @@ module.exports = { "IDBFS": true, "GodotOS": true, "GodotConfig": true, + "GodotEventListeners": true, "GodotRuntime": true, "GodotFS": true, "IDHandler": true, diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index 62a8660ae4..fa9e6eed15 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,6 +20,7 @@ sys_env.AddJSLibraries( "js/libs/library_godot_fetch.js", "js/libs/library_godot_os.js", "js/libs/library_godot_runtime.js", + "js/libs/library_godot_input.js", ] ) diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index 45a2cd595a..df4c790755 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -131,9 +131,10 @@ void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_pa } dir->list_dir_begin(); String cur = dir->get_next(); + String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); while (!cur.is_empty()) { String cs = p_path.plus_file(cur); - if (cur == "." || cur == ".." || cur == ".import") { + if (cur == "." || cur == ".." || cur == project_data_dir_name) { // Skip } else if (dir->current_is_dir()) { String path = cs.replace_first(p_base_path, "") + "/"; diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index cfe6c69072..626aef3c60 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -117,14 +117,15 @@ Error AudioDriverJavaScript::init() { if (output_rb) { memdelete_arr(output_rb); } - output_rb = memnew_arr(float, buffer_length *channel_count); + const size_t array_size = buffer_length * (size_t)channel_count; + output_rb = memnew_arr(float, array_size); if (!output_rb) { return ERR_OUT_OF_MEMORY; } if (input_rb) { memdelete_arr(input_rb); } - input_rb = memnew_arr(float, buffer_length *channel_count); + input_rb = memnew_arr(float, array_size); if (!input_rb) { return ERR_OUT_OF_MEMORY; } diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 173b558b6d..9494ab6fa5 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -82,6 +82,7 @@ def configure(env): env.Append(LINKFLAGS=["--profiling-funcs"]) else: # "debug" env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) env.Append(CCFLAGS=["-O1", "-g"]) env.Append(LINKFLAGS=["-O1", "-g"]) env["use_assertions"] = True diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index be4d2cba20..c2eb826db9 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -62,26 +62,13 @@ bool DisplayServerJavaScript::check_size_force_redraw() { return godot_js_display_size_update() != 0; } -Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { - int point[2]; - godot_js_display_compute_position(p_x, p_y, point, point + 1); - return Point2(point[0], point[1]); -} - -EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { +void DisplayServerJavaScript::fullscreen_change_callback(int p_fullscreen) { DisplayServerJavaScript *display = get_singleton(); - // Empty ID is canvas. - String target_id = String::utf8(p_event->id); - if (target_id.is_empty() || target_id == String::utf8(&(display->canvas_id[1]))) { - // This event property is the only reliable data on - // browser fullscreen state. - if (p_event->isFullscreen) { - display->window_mode = WINDOW_MODE_FULLSCREEN; - } else { - display->window_mode = WINDOW_MODE_WINDOWED; - } + if (p_fullscreen) { + display->window_mode = WINDOW_MODE_FULLSCREEN; + } else { + display->window_mode = WINDOW_MODE_WINDOWED; } - return false; } // Drag and drop callback. @@ -118,88 +105,51 @@ void DisplayServerJavaScript::request_quit_callback() { // Keys -template <typename T> -void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { - godot_event->set_shift_pressed(emscripten_event_ptr->shiftKey); - godot_event->set_alt_pressed(emscripten_event_ptr->altKey); - godot_event->set_ctrl_pressed(emscripten_event_ptr->ctrlKey); - godot_event->set_meta_pressed(emscripten_event_ptr->metaKey); +void DisplayServerJavaScript::dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod) { + ev->set_shift_pressed(p_mod & 1); + ev->set_alt_pressed(p_mod & 2); + ev->set_ctrl_pressed(p_mod & 4); + ev->set_meta_pressed(p_mod & 8); } -Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { +void DisplayServerJavaScript::key_callback(int p_pressed, int p_repeat, int p_modifiers) { + DisplayServerJavaScript *ds = get_singleton(); + JSKeyEvent &key_event = ds->key_event; + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); + Ref<InputEventKey> ev; ev.instantiate(); - ev->set_echo(emscripten_event->repeat); - dom2godot_mod(emscripten_event, ev); - ev->set_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, false)); - ev->set_physical_keycode(dom_code2godot_scancode(emscripten_event->code, emscripten_event->key, true)); - - String unicode = String::utf8(emscripten_event->key); - // Check if empty or multi-character (e.g. `CapsLock`). - if (unicode.length() != 1) { - // Might be empty as well, but better than nonsense. - unicode = String::utf8(emscripten_event->charValue); - } + ev->set_echo(p_repeat); + ev->set_keycode(dom_code2godot_scancode(key_event.code, key_event.key, false)); + ev->set_physical_keycode(dom_code2godot_scancode(key_event.code, key_event.key, true)); + ev->set_pressed(p_pressed); + dom2godot_mod(ev, p_modifiers); + + String unicode = String::utf8(key_event.key); if (unicode.length() == 1) { ev->set_unicode(unicode[0]); } - - return ev; -} - -EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventKey> ev = setup_key_event(p_event); - ev->set_pressed(true); - if (ev->get_unicode() == 0 && keycode_has_unicode(ev->get_keycode())) { - // Defer to keypress event for legacy unicode retrieval. - display->deferred_key_event = ev; - // Do not suppress keypress event. - return false; - } Input::get_singleton()->parse_input_event(ev); // Make sure to flush all events so we can call restricted APIs inside the event. Input::get_singleton()->flush_buffered_events(); - - return true; -} - -EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - display->deferred_key_event->set_unicode(p_event->charCode); - Input::get_singleton()->parse_input_event(display->deferred_key_event); - - // Make sure to flush all events so we can call restricted APIs inside the event. - Input::get_singleton()->flush_buffered_events(); - - return true; -} - -EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data) { - Ref<InputEventKey> ev = setup_key_event(p_event); - ev->set_pressed(false); - Input::get_singleton()->parse_input_event(ev); - - // Make sure to flush all events so we can call restricted APIs inside the event. - Input::get_singleton()->flush_buffered_events(); - - return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != (Key)0; } // Mouse -EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); +int DisplayServerJavaScript::mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers) { + DisplayServerJavaScript *ds = get_singleton(); Ref<InputEventMouseButton> ev; ev.instantiate(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN); - ev->set_position(compute_position_in_canvas(p_event->clientX, p_event->clientY)); + ev->set_pressed(p_pressed); + ev->set_position(Point2(p_x, p_y)); ev->set_global_position(ev->get_position()); - dom2godot_mod(p_event, ev); + ev->set_pressed(p_pressed); + dom2godot_mod(ev, p_modifiers); - switch (p_event->button) { + switch (p_button) { case DOM_BUTTON_LEFT: ev->set_button_index(MOUSE_BUTTON_LEFT); break; @@ -219,34 +169,30 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E return false; } - if (ev->is_pressed()) { - double diff = emscripten_get_now() - display->last_click_ms; + if (p_pressed) { + uint64_t diff = (OS::get_singleton()->get_ticks_usec() / 1000) - ds->last_click_ms; - if (ev->get_button_index() == display->last_click_button_index) { - if (diff < 400 && Point2(display->last_click_pos).distance_to(ev->get_position()) < 5) { - display->last_click_ms = 0; - display->last_click_pos = Point2(-100, -100); - display->last_click_button_index = -1; + if (ev->get_button_index() == ds->last_click_button_index) { + if (diff < 400 && Point2(ds->last_click_pos).distance_to(ev->get_position()) < 5) { + ds->last_click_ms = 0; + ds->last_click_pos = Point2(-100, -100); + ds->last_click_button_index = -1; ev->set_double_click(true); } } else { - display->last_click_button_index = ev->get_button_index(); + ds->last_click_button_index = ev->get_button_index(); } if (!ev->is_double_click()) { - display->last_click_ms += diff; - display->last_click_pos = ev->get_position(); + ds->last_click_ms += diff; + ds->last_click_pos = ev->get_position(); } } - Input *input = Input::get_singleton(); - int mask = input->get_mouse_button_mask(); - MouseButton button_flag = MouseButton(1 << (ev->get_button_index() - 1)); + int mask = Input::get_singleton()->get_mouse_button_mask(); + int button_flag = 1 << (ev->get_button_index() - 1); if (ev->is_pressed()) { - // Since the event is consumed, focus manually. The containing iframe, - // if exists, may not have focus yet, so focus even if already focused. - focus_canvas(); mask |= button_flag; } else if (mask & button_flag) { mask &= ~button_flag; @@ -256,7 +202,9 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E } ev->set_button_mask(mask); - input->parse_input_event(ev); + Input::get_singleton()->parse_input_event(ev); + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); // Make sure to flush all events so we can call restricted APIs inside the event. Input::get_singleton()->flush_buffered_events(); @@ -266,31 +214,26 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E return true; } -EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *ds = get_singleton(); - Input *input = Input::get_singleton(); - int input_mask = input->get_mouse_button_mask(); - Point2 pos = compute_position_in_canvas(p_event->clientX, p_event->clientY); +void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers) { + int input_mask = Input::get_singleton()->get_mouse_button_mask(); // For motion outside the canvas, only read mouse movement if dragging // started inside the canvas; imitating desktop app behaviour. - if (!ds->cursor_inside_canvas && !input_mask) - return false; + if (!get_singleton()->cursor_inside_canvas && !input_mask) + return; Ref<InputEventMouseMotion> ev; ev.instantiate(); - dom2godot_mod(p_event, ev); + dom2godot_mod(ev, p_modifiers); ev->set_button_mask(input_mask); - ev->set_position(pos); + ev->set_position(Point2(p_x, p_y)); ev->set_global_position(ev->get_position()); - ev->set_relative(Vector2(p_event->movementX, p_event->movementY)); - input->set_mouse_position(ev->get_position()); - ev->set_speed(input->get_last_mouse_speed()); + ev->set_relative(Vector2(p_rel_x, p_rel_y)); + Input::get_singleton()->set_mouse_position(ev->get_position()); + ev->set_speed(Input::get_singleton()->get_last_mouse_speed()); - input->parse_input_event(ev); - // Don't suppress mouseover/-leave events. - return false; + Input::get_singleton()->parse_input_event(ev); } // Cursor @@ -430,17 +373,15 @@ void DisplayServerJavaScript::mouse_set_mode(MouseMode p_mode) { if (p_mode == MOUSE_MODE_VISIBLE) { godot_js_display_cursor_set_visible(1); - emscripten_exit_pointerlock(); + godot_js_display_cursor_lock_set(0); } else if (p_mode == MOUSE_MODE_HIDDEN) { godot_js_display_cursor_set_visible(0); - emscripten_exit_pointerlock(); + godot_js_display_cursor_lock_set(0); } else if (p_mode == MOUSE_MODE_CAPTURED) { godot_js_display_cursor_set_visible(1); - EMSCRIPTEN_RESULT result = emscripten_request_pointerlock(canvas_id, false); - ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); - ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "MOUSE_MODE_CAPTURED can only be entered from within an appropriate input callback."); + godot_js_display_cursor_lock_set(1); } } @@ -449,18 +390,17 @@ DisplayServer::MouseMode DisplayServerJavaScript::mouse_get_mode() const { return MOUSE_MODE_HIDDEN; } - EmscriptenPointerlockChangeEvent ev; - emscripten_get_pointerlock_status(&ev); - return (ev.isActive && String::utf8(ev.id) == String::utf8(&canvas_id[1])) ? MOUSE_MODE_CAPTURED : MOUSE_MODE_VISIBLE; + if (godot_js_display_cursor_is_locked()) { + return MOUSE_MODE_CAPTURED; + } + return MOUSE_MODE_VISIBLE; } // Wheel -EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data) { - ERR_FAIL_COND_V(p_event_type != EMSCRIPTEN_EVENT_WHEEL, false); - DisplayServerJavaScript *ds = get_singleton(); - if (!is_canvas_focused()) { - if (ds->cursor_inside_canvas) { - focus_canvas(); +int DisplayServerJavaScript::mouse_wheel_callback(double p_delta_x, double p_delta_y) { + if (!godot_js_display_canvas_is_focused()) { + if (get_singleton()->cursor_inside_canvas) { + godot_js_display_canvas_focus(); } else { return false; } @@ -477,16 +417,17 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript ev->set_ctrl_pressed(input->is_key_pressed(KEY_CTRL)); ev->set_meta_pressed(input->is_key_pressed(KEY_META)); - if (p_event->deltaY < 0) + if (p_delta_y < 0) { ev->set_button_index(MOUSE_BUTTON_WHEEL_UP); - else if (p_event->deltaY > 0) + } else if (p_delta_y > 0) { ev->set_button_index(MOUSE_BUTTON_WHEEL_DOWN); - else if (p_event->deltaX > 0) + } else if (p_delta_x > 0) { ev->set_button_index(MOUSE_BUTTON_WHEEL_LEFT); - else if (p_event->deltaX < 0) + } else if (p_delta_x < 0) { ev->set_button_index(MOUSE_BUTTON_WHEEL_RIGHT); - else + } else { return false; + } // Different browsers give wildly different delta values, and we can't // interpret deltaMode, so use default value for wheel events' factor. @@ -494,7 +435,7 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript int button_flag = 1 << (ev->get_button_index() - 1); ev->set_pressed(true); - ev->set_button_mask(MouseButton(input->get_mouse_button_mask() | button_flag)); + ev->set_button_mask(input->get_mouse_button_mask() | button_flag); input->parse_input_event(ev); Ref<InputEventMouseButton> release = ev->duplicate(); @@ -506,52 +447,43 @@ EM_BOOL DisplayServerJavaScript::wheel_callback(int p_event_type, const Emscript } // Touch -EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventScreenTouch> ev; - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev.instantiate(); - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - display->touches[i] = ev->get_position(); - ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART); +void DisplayServerJavaScript::touch_callback(int p_type, int p_count) { + DisplayServerJavaScript *ds = get_singleton(); - Input::get_singleton()->parse_input_event(ev); - } + const JSTouchEvent &touch_event = ds->touch_event; + for (int i = 0; i < p_count; i++) { + Point2 point(touch_event.coords[i * 2], touch_event.coords[i * 2 + 1]); + if (p_type == 2) { + // touchmove + Ref<InputEventScreenDrag> ev; + ev.instantiate(); + ev->set_index(touch_event.identifier[i]); + ev->set_position(point); + + Point2 &prev = ds->touches[i]; + ev->set_relative(ev->get_position() - prev); + prev = ev->get_position(); + + Input::get_singleton()->parse_input_event(ev); + } else { + // touchstart/touchend + Ref<InputEventScreenTouch> ev; - // Make sure to flush all events so we can call restricted APIs inside the event. - Input::get_singleton()->flush_buffered_events(); + // Resume audio context after input in case autoplay was denied. + OS_JavaScript::get_singleton()->resume_audio(); - // Resume audio context after input in case autoplay was denied. - return true; -} + ev.instantiate(); + ev->set_index(touch_event.identifier[i]); + ev->set_position(point); + ev->set_pressed(p_type == 0); + ds->touches[i] = point; -EM_BOOL DisplayServerJavaScript::touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data) { - DisplayServerJavaScript *display = get_singleton(); - Ref<InputEventScreenDrag> ev; - int lowest_id_index = -1; - for (int i = 0; i < p_event->numTouches; ++i) { - const EmscriptenTouchPoint &touch = p_event->touches[i]; - if (lowest_id_index == -1 || touch.identifier < p_event->touches[lowest_id_index].identifier) - lowest_id_index = i; - if (!touch.isChanged) - continue; - ev.instantiate(); - ev->set_index(touch.identifier); - ev->set_position(compute_position_in_canvas(touch.clientX, touch.clientY)); - Point2 &prev = display->touches[i]; - ev->set_relative(ev->get_position() - prev); - prev = ev->get_position(); - - Input::get_singleton()->parse_input_event(ev); + Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + } } - return true; } bool DisplayServerJavaScript::screen_is_touchscreen(int p_screen) const { @@ -595,6 +527,10 @@ void DisplayServerJavaScript::virtual_keyboard_hide() { godot_js_display_vk_hide(); } +void DisplayServerJavaScript::window_blur_callback() { + Input::get_singleton()->release_pressed_events(); +} + // Gamepad void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid) { Input *input = Input::get_singleton(); @@ -607,14 +543,14 @@ void DisplayServerJavaScript::gamepad_callback(int p_index, int p_connected, con void DisplayServerJavaScript::process_joypads() { Input *input = Input::get_singleton(); - int32_t pads = godot_js_display_gamepad_sample_count(); + int32_t pads = godot_js_input_gamepad_sample_count(); int32_t s_btns_num = 0; int32_t s_axes_num = 0; int32_t s_standard = 0; float s_btns[16]; float s_axes[10]; for (int idx = 0; idx < pads; idx++) { - int err = godot_js_display_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard); + int err = godot_js_input_gamepad_sample_get(idx, s_btns, &s_btns_num, s_axes, &s_axes_num, &s_standard); if (err) { continue; } @@ -712,11 +648,6 @@ void DisplayServerJavaScript::set_icon(const Ref<Image> &p_icon) { } void DisplayServerJavaScript::_dispatch_input_event(const Ref<InputEvent> &p_event) { - OS_JavaScript *os = OS_JavaScript::get_singleton(); - - // Resume audio context after input in case autoplay was denied. - os->resume_audio(); - Callable cb = get_singleton()->input_event_callback; if (!cb.is_null()) { Variant ev = p_event; @@ -782,43 +713,24 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive video_driver_index = p_video_driver; #endif - EMSCRIPTEN_RESULT result; -#define EM_CHECK(ev) \ - if (result != EMSCRIPTEN_RESULT_SUCCESS) \ - ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); -#define SET_EM_CALLBACK(target, ev, cb) \ - result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \ - EM_CHECK(ev) -#define SET_EM_WINDOW_CALLBACK(ev, cb) \ - result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, &cb); \ - EM_CHECK(ev) - // These callbacks from Emscripten's html5.h suffice to access most - // JavaScript APIs. - SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) - SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback) - SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback) - SET_EM_CALLBACK(canvas_id, wheel, wheel_callback) - SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback) - SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) - SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback) - SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback) - SET_EM_CALLBACK(canvas_id, keydown, keydown_callback) - SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) - SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) - SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) -#undef SET_EM_CALLBACK -#undef EM_CHECK - - // For APIs that are not (sufficiently) exposed, a - // library is used below (implemented in library_godot_display.js). + // JS Input interface (js/libs/library_godot_input.js) + godot_js_input_mouse_button_cb(&DisplayServerJavaScript::mouse_button_callback); + godot_js_input_mouse_move_cb(&DisplayServerJavaScript::mouse_move_callback); + godot_js_input_mouse_wheel_cb(&DisplayServerJavaScript::mouse_wheel_callback); + godot_js_input_touch_cb(&DisplayServerJavaScript::touch_callback, touch_event.identifier, touch_event.coords); + godot_js_input_key_cb(&DisplayServerJavaScript::key_callback, key_event.code, key_event.key); + godot_js_input_paste_cb(update_clipboard_callback); + godot_js_input_drop_files_cb(drop_files_js_callback); + godot_js_input_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); + + // JS Display interface (js/libs/library_godot_display.js) + godot_js_display_fullscreen_cb(&DisplayServerJavaScript::fullscreen_change_callback); + godot_js_display_window_blur_cb(&window_blur_callback); godot_js_display_notification_cb(&send_window_event_callback, WINDOW_EVENT_MOUSE_ENTER, WINDOW_EVENT_MOUSE_EXIT, WINDOW_EVENT_FOCUS_IN, WINDOW_EVENT_FOCUS_OUT); - godot_js_display_paste_cb(update_clipboard_callback); - godot_js_display_drop_files_cb(drop_files_js_callback); - godot_js_display_gamepad_cb(&DisplayServerJavaScript::gamepad_callback); godot_js_display_vk_cb(&vk_input_text_callback); Input::get_singleton()->set_event_dispatch_function(_dispatch_input_event); @@ -1041,7 +953,7 @@ bool DisplayServerJavaScript::can_any_window_draw() const { void DisplayServerJavaScript::process_events() { Input::get_singleton()->flush_buffered_events(); - if (godot_js_display_gamepad_sample() == OK) { + if (godot_js_input_gamepad_sample() == OK) { process_joypads(); } } diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index bf5e229c9a..1aa600c421 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -38,6 +38,19 @@ class DisplayServerJavaScript : public DisplayServer { private: + struct JSTouchEvent { + uint32_t identifier[32] = { 0 }; + double coords[64] = { 0 }; + }; + JSTouchEvent touch_event; + + struct JSKeyEvent { + char code[32] = { 0 }; + char key[32] = { 0 }; + uint8_t modifiers[4] = { 0 }; + }; + JSKeyEvent key_event; + WindowMode window_mode = WINDOW_MODE_WINDOWED; ObjectID window_attached_instance_id = {}; @@ -47,44 +60,31 @@ private: Callable drop_files_callback; String clipboard; - Ref<InputEventKey> deferred_key_event; Point2 touches[32]; char canvas_id[256] = { 0 }; bool cursor_inside_canvas = true; CursorShape cursor_shape = CURSOR_ARROW; Point2i last_click_pos = Point2(-100, -100); // TODO check this again. - double last_click_ms = 0; + uint64_t last_click_ms = 0; int last_click_button_index = -1; bool swap_cancel_ok = false; // utilities - static Point2 compute_position_in_canvas(int p_x, int p_y); static void focus_canvas(); static bool is_canvas_focused(); - template <typename T> - static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event); - static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event); + static void dom2godot_mod(Ref<InputEventWithModifiers> ev, int p_mod); static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape); // events - static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data); - - static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - static EM_BOOL keypress_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - static EM_BOOL keyup_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); - + static void fullscreen_change_callback(int p_fullscreen); + static int mouse_button_callback(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers); + static void mouse_move_callback(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers); + static int mouse_wheel_callback(double p_delta_x, double p_delta_y); + static void touch_callback(int p_type, int p_count); + static void key_callback(int p_pressed, int p_repeat, int p_modifiers); static void vk_input_text_callback(const char *p_text, int p_cursor); - - static EM_BOOL mousemove_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); - static EM_BOOL mouse_button_callback(int p_event_type, const EmscriptenMouseEvent *p_event, void *p_user_data); - - static EM_BOOL wheel_callback(int p_event_type, const EmscriptenWheelEvent *p_event, void *p_user_data); - - static EM_BOOL touch_press_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); - static EM_BOOL touchmove_callback(int p_event_type, const EmscriptenTouchEvent *p_event, void *p_user_data); - static void gamepad_callback(int p_index, int p_connected, const char *p_id, const char *p_guid); void process_joypads(); @@ -94,6 +94,7 @@ private: static void _dispatch_input_event(const Ref<InputEvent> &p_event); static void request_quit_callback(); + static void window_blur_callback(); static void update_clipboard_callback(const char *p_text); static void send_window_event_callback(int p_notification); static void drop_files_js_callback(char **p_filev, int p_filec); diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp index 5d285a6e60..c7bd172751 100644 --- a/platform/javascript/export/export_plugin.cpp +++ b/platform/javascript/export/export_plugin.cpp @@ -99,8 +99,8 @@ void EditorExportPlatformJavaScript::_replace_strings(Map<String, String> p_repl Vector<String> lines = str_template.split("\n"); for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - for (Map<String, String>::Element *E = p_replaces.front(); E; E = E->next()) { - current_line = current_line.replace(E->key(), E->get()); + for (const KeyValue<String, String> &E : p_replaces) { + current_line = current_line.replace(E.key, E.value); } out += current_line + "\n"; } diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index d332af2c31..8051b270e6 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -50,12 +50,28 @@ extern int godot_js_os_execute(const char *p_json); extern void godot_js_os_shell_open(const char *p_uri); extern int godot_js_os_hw_concurrency_get(); +// Input +extern void godot_js_input_mouse_button_cb(int (*p_callback)(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers)); +extern void godot_js_input_mouse_move_cb(void (*p_callback)(double p_x, double p_y, double p_rel_x, double p_rel_y, int p_modifiers)); +extern void godot_js_input_mouse_wheel_cb(int (*p_callback)(double p_delta_x, double p_delta_y)); +extern void godot_js_input_touch_cb(void (*p_callback)(int p_type, int p_count), uint32_t *r_identifiers, double *r_coords); +extern void godot_js_input_key_cb(void (*p_callback)(int p_type, int p_repeat, int p_modifiers), char r_code[32], char r_key[32]); + +// Input gamepad +extern void godot_js_input_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid)); +extern int godot_js_input_gamepad_sample(); +extern int godot_js_input_gamepad_sample_count(); +extern int godot_js_input_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard); +extern void godot_js_input_paste_cb(void (*p_callback)(const char *p_text)); +extern void godot_js_input_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); + // Display extern int godot_js_display_screen_dpi_get(); extern double godot_js_display_pixel_ratio_get(); extern void godot_js_display_alert(const char *p_text); extern int godot_js_display_touchscreen_is_available(); extern int godot_js_display_is_swap_ok_cancel(); +extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen, int p_hidpi); // Display canvas extern void godot_js_display_canvas_focus(); @@ -68,7 +84,6 @@ extern void godot_js_display_window_size_get(int32_t *p_x, int32_t *p_y); extern void godot_js_display_screen_size_get(int32_t *p_x, int32_t *p_y); extern int godot_js_display_fullscreen_request(); extern int godot_js_display_fullscreen_exit(); -extern void godot_js_display_compute_position(int p_x, int p_y, int32_t *r_x, int32_t *r_y); extern void godot_js_display_window_title_set(const char *p_text); extern void godot_js_display_window_icon_set(const uint8_t *p_ptr, int p_len); extern int godot_js_display_has_webgl(int p_version); @@ -82,18 +97,13 @@ extern void godot_js_display_cursor_set_shape(const char *p_cursor); extern int godot_js_display_cursor_is_hidden(); extern void godot_js_display_cursor_set_custom_shape(const char *p_shape, const uint8_t *p_ptr, int p_len, int p_hotspot_x, int p_hotspot_y); extern void godot_js_display_cursor_set_visible(int p_visible); - -// Display gamepad -extern void godot_js_display_gamepad_cb(void (*p_on_change)(int p_index, int p_connected, const char *p_id, const char *p_guid)); -extern int godot_js_display_gamepad_sample(); -extern int godot_js_display_gamepad_sample_count(); -extern int godot_js_display_gamepad_sample_get(int p_idx, float r_btns[16], int32_t *r_btns_num, float r_axes[10], int32_t *r_axes_num, int32_t *r_standard); +extern void godot_js_display_cursor_lock_set(int p_lock); +extern int godot_js_display_cursor_is_locked(); // Display listeners +extern void godot_js_display_fullscreen_cb(void (*p_callback)(int p_fullscreen)); +extern void godot_js_display_window_blur_cb(void (*p_callback)()); extern void godot_js_display_notification_cb(void (*p_callback)(int p_notification), int p_enter, int p_exit, int p_in, int p_out); -extern void godot_js_display_paste_cb(void (*p_callback)(const char *p_text)); -extern void godot_js_display_drop_files_cb(void (*p_callback)(char **p_filev, int p_filec)); -extern void godot_js_display_setup_canvas(int p_width, int p_height, int p_fullscreen, int p_hidpi); // Display Virtual Keyboard extern int godot_js_display_vk_available(); diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js index f6010fd12a..6cbb0567f4 100644 --- a/platform/javascript/js/libs/library_godot_audio.js +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -229,7 +229,7 @@ const GodotAudioWorklet = { 'godot-processor', { 'outputChannelCount': [channels], - }, + } ); return Promise.resolve(); }); diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index affae90370..2689f1c22c 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -28,224 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -/* - * Display Server listeners. - * Keeps track of registered event listeners so it can remove them on shutdown. - */ -const GodotDisplayListeners = { - $GodotDisplayListeners__deps: ['$GodotOS'], - $GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });', - $GodotDisplayListeners: { - handlers: [], - - has: function (target, event, method, capture) { - return GodotDisplayListeners.handlers.findIndex(function (e) { - return e.target === target && e.event === event && e.method === method && e.capture === capture; - }) !== -1; - }, - - add: function (target, event, method, capture) { - if (GodotDisplayListeners.has(target, event, method, capture)) { - return; - } - function Handler(p_target, p_event, p_method, p_capture) { - this.target = p_target; - this.event = p_event; - this.method = p_method; - this.capture = p_capture; - } - GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture)); - target.addEventListener(event, method, capture); - }, - - clear: function () { - GodotDisplayListeners.handlers.forEach(function (h) { - h.target.removeEventListener(h.event, h.method, h.capture); - }); - GodotDisplayListeners.handlers.length = 0; - }, - }, -}; -mergeInto(LibraryManager.library, GodotDisplayListeners); - -/* - * Drag and drop handler. - * This is pretty big, but basically detect dropped files on GodotConfig.canvas, - * process them one by one (recursively for directories), and copies them to - * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot - * event (that requires a string array of paths). - * - * NOTE: The temporary files are removed after the callback. This means that - * deferred callbacks won't be able to access the files. - */ -const GodotDisplayDragDrop = { - $GodotDisplayDragDrop__deps: ['$FS', '$GodotFS'], - $GodotDisplayDragDrop: { - promises: [], - pending_files: [], - - add_entry: function (entry) { - if (entry.isDirectory) { - GodotDisplayDragDrop.add_dir(entry); - } else if (entry.isFile) { - GodotDisplayDragDrop.add_file(entry); - } else { - GodotRuntime.error('Unrecognized entry...', entry); - } - }, - - add_dir: function (entry) { - GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) { - const reader = entry.createReader(); - reader.readEntries(function (entries) { - for (let i = 0; i < entries.length; i++) { - GodotDisplayDragDrop.add_entry(entries[i]); - } - resolve(); - }); - })); - }, - - add_file: function (entry) { - GodotDisplayDragDrop.promises.push(new Promise(function (resolve, reject) { - entry.file(function (file) { - const reader = new FileReader(); - reader.onload = function () { - const f = { - 'path': file.relativePath || file.webkitRelativePath, - 'name': file.name, - 'type': file.type, - 'size': file.size, - 'data': reader.result, - }; - if (!f['path']) { - f['path'] = f['name']; - } - GodotDisplayDragDrop.pending_files.push(f); - resolve(); - }; - reader.onerror = function () { - GodotRuntime.print('Error reading file'); - reject(); - }; - reader.readAsArrayBuffer(file); - }, function (err) { - GodotRuntime.print('Error!'); - reject(); - }); - })); - }, - - process: function (resolve, reject) { - if (GodotDisplayDragDrop.promises.length === 0) { - resolve(); - return; - } - GodotDisplayDragDrop.promises.pop().then(function () { - setTimeout(function () { - GodotDisplayDragDrop.process(resolve, reject); - }, 0); - }); - }, - - _process_event: function (ev, callback) { - ev.preventDefault(); - if (ev.dataTransfer.items) { - // Use DataTransferItemList interface to access the file(s) - for (let i = 0; i < ev.dataTransfer.items.length; i++) { - const item = ev.dataTransfer.items[i]; - let entry = null; - if ('getAsEntry' in item) { - entry = item.getAsEntry(); - } else if ('webkitGetAsEntry' in item) { - entry = item.webkitGetAsEntry(); - } - if (entry) { - GodotDisplayDragDrop.add_entry(entry); - } - } - } else { - GodotRuntime.error('File upload not supported'); - } - new Promise(GodotDisplayDragDrop.process).then(function () { - const DROP = `/tmp/drop-${parseInt(Math.random() * (1 << 30), 10)}/`; - const drops = []; - const files = []; - FS.mkdir(DROP); - GodotDisplayDragDrop.pending_files.forEach((elem) => { - const path = elem['path']; - GodotFS.copy_to_fs(DROP + path, elem['data']); - let idx = path.indexOf('/'); - if (idx === -1) { - // Root file - drops.push(DROP + path); - } else { - // Subdir - const sub = path.substr(0, idx); - idx = sub.indexOf('/'); - if (idx < 0 && drops.indexOf(DROP + sub) === -1) { - drops.push(DROP + sub); - } - } - files.push(DROP + path); - }); - GodotDisplayDragDrop.promises = []; - GodotDisplayDragDrop.pending_files = []; - callback(drops); - if (GodotConfig.persistent_drops) { - // Delay removal at exit. - GodotOS.atexit(function (resolve, reject) { - GodotDisplayDragDrop.remove_drop(files, DROP); - resolve(); - }); - } else { - GodotDisplayDragDrop.remove_drop(files, DROP); - } - }); - }, - - remove_drop: function (files, drop_path) { - const dirs = [drop_path.substr(0, drop_path.length - 1)]; - // Remove temporary files - files.forEach(function (file) { - FS.unlink(file); - let dir = file.replace(drop_path, ''); - let idx = dir.lastIndexOf('/'); - while (idx > 0) { - dir = dir.substr(0, idx); - if (dirs.indexOf(drop_path + dir) === -1) { - dirs.push(drop_path + dir); - } - idx = dir.lastIndexOf('/'); - } - }); - // Remove dirs. - dirs.sort(function (a, b) { - const al = (a.match(/\//g) || []).length; - const bl = (b.match(/\//g) || []).length; - if (al > bl) { - return -1; - } else if (al < bl) { - return 1; - } - return 0; - }).forEach(function (dir) { - FS.rmdir(dir); - }); - }, - - handler: function (callback) { - return function (ev) { - GodotDisplayDragDrop._process_event(ev, callback); - }; - }, - }, -}; -mergeInto(LibraryManager.library, GodotDisplayDragDrop); - const GodotDisplayVK = { - $GodotDisplayVK__deps: ['$GodotRuntime', '$GodotConfig', '$GodotDisplayListeners'], + $GodotDisplayVK__deps: ['$GodotRuntime', '$GodotConfig', '$GodotEventListeners'], $GodotDisplayVK__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayVK.clear(); resolve(); });', $GodotDisplayVK: { textinput: null, @@ -271,12 +56,12 @@ const GodotDisplayVK = { elem.style.outline = 'none'; elem.readonly = true; elem.disabled = true; - GodotDisplayListeners.add(elem, 'input', function (evt) { + GodotEventListeners.add(elem, 'input', function (evt) { const c_str = GodotRuntime.allocString(elem.value); input_cb(c_str, elem.selectionEnd); GodotRuntime.free(c_str); }, false); - GodotDisplayListeners.add(elem, 'blur', function (evt) { + GodotEventListeners.add(elem, 'blur', function (evt) { elem.style.display = 'none'; elem.readonly = true; elem.disabled = true; @@ -376,136 +161,23 @@ const GodotDisplayCursor = { delete GodotDisplayCursor.cursors[key]; }); }, - }, -}; -mergeInto(LibraryManager.library, GodotDisplayCursor); - -/* - * Display Gamepad API helper. - */ -const GodotDisplayGamepads = { - $GodotDisplayGamepads__deps: ['$GodotRuntime', '$GodotDisplayListeners'], - $GodotDisplayGamepads: { - samples: [], - - get_pads: function () { - try { - // Will throw in iframe when permission is denied. - // Will throw/warn in the future for insecure contexts. - // See https://github.com/w3c/gamepad/pull/120 - const pads = navigator.getGamepads(); - if (pads) { - return pads; - } - return []; - } catch (e) { - return []; - } - }, - - get_samples: function () { - return GodotDisplayGamepads.samples; - }, - - get_sample: function (index) { - const samples = GodotDisplayGamepads.samples; - return index < samples.length ? samples[index] : null; - }, - - sample: function () { - const pads = GodotDisplayGamepads.get_pads(); - const samples = []; - for (let i = 0; i < pads.length; i++) { - const pad = pads[i]; - if (!pad) { - samples.push(null); - continue; - } - const s = { - standard: pad.mapping === 'standard', - buttons: [], - axes: [], - connected: pad.connected, - }; - for (let b = 0; b < pad.buttons.length; b++) { - s.buttons.push(pad.buttons[b].value); - } - for (let a = 0; a < pad.axes.length; a++) { - s.axes.push(pad.axes[a]); - } - samples.push(s); + lockPointer: function () { + const canvas = GodotConfig.canvas; + if (canvas.requestPointerLock) { + canvas.requestPointerLock(); } - GodotDisplayGamepads.samples = samples; }, - - init: function (onchange) { - GodotDisplayListeners.samples = []; - function add(pad) { - const guid = GodotDisplayGamepads.get_guid(pad); - const c_id = GodotRuntime.allocString(pad.id); - const c_guid = GodotRuntime.allocString(guid); - onchange(pad.index, 1, c_id, c_guid); - GodotRuntime.free(c_id); - GodotRuntime.free(c_guid); + releasePointer: function () { + if (document.exitPointerLock) { + document.exitPointerLock(); } - const pads = GodotDisplayGamepads.get_pads(); - for (let i = 0; i < pads.length; i++) { - // Might be reserved space. - if (pads[i]) { - add(pads[i]); - } - } - GodotDisplayListeners.add(window, 'gamepadconnected', function (evt) { - add(evt.gamepad); - }, false); - GodotDisplayListeners.add(window, 'gamepaddisconnected', function (evt) { - onchange(evt.gamepad.index, 0); - }, false); }, - - get_guid: function (pad) { - if (pad.mapping) { - return pad.mapping; - } - const ua = navigator.userAgent; - let os = 'Unknown'; - if (ua.indexOf('Android') >= 0) { - os = 'Android'; - } else if (ua.indexOf('Linux') >= 0) { - os = 'Linux'; - } else if (ua.indexOf('iPhone') >= 0) { - os = 'iOS'; - } else if (ua.indexOf('Macintosh') >= 0) { - // Updated iPads will fall into this category. - os = 'MacOSX'; - } else if (ua.indexOf('Windows') >= 0) { - os = 'Windows'; - } - - const id = pad.id; - // Chrom* style: NAME (Vendor: xxxx Product: xxxx) - const exp1 = /vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i; - // Firefox/Safari style (safari may remove leading zeores) - const exp2 = /^([0-9a-f]+)-([0-9a-f]+)-/i; - let vendor = ''; - let product = ''; - if (exp1.test(id)) { - const match = exp1.exec(id); - vendor = match[1].padStart(4, '0'); - product = match[2].padStart(4, '0'); - } else if (exp2.test(id)) { - const match = exp2.exec(id); - vendor = match[1].padStart(4, '0'); - product = match[2].padStart(4, '0'); - } - if (!vendor || !product) { - return `${os}Unknown`; - } - return os + vendor + product; + isPointerLocked: function () { + return document.pointerLockElement === GodotConfig.canvas; }, }, }; -mergeInto(LibraryManager.library, GodotDisplayGamepads); +mergeInto(LibraryManager.library, GodotDisplayCursor); const GodotDisplayScreen = { $GodotDisplayScreen__deps: ['$GodotConfig', '$GodotOS', '$GL', 'emscripten_webgl_get_current_context'], @@ -622,7 +294,7 @@ mergeInto(LibraryManager.library, GodotDisplayScreen); * Exposes all the functions needed by DisplayServer implementation. */ const GodotDisplay = { - $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop', '$GodotDisplayGamepads', '$GodotDisplayScreen', '$GodotDisplayVK'], + $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotEventListeners', '$GodotDisplayScreen', '$GodotDisplayVK'], $GodotDisplay: { window_icon: '', findDPI: function () { @@ -710,15 +382,6 @@ const GodotDisplay = { GodotRuntime.setHeapValue(p_height, GodotConfig.canvas.height, 'i32'); }, - godot_js_display_compute_position: function (x, y, r_x, r_y) { - const canvas = GodotConfig.canvas; - const rect = canvas.getBoundingClientRect(); - const rw = canvas.width / rect.width; - const rh = canvas.height / rect.height; - GodotRuntime.setHeapValue(r_x, (x - rect.x) * rw, 'i32'); - GodotRuntime.setHeapValue(r_y, (y - rect.y) * rh, 'i32'); - }, - godot_js_display_has_webgl__sig: 'ii', godot_js_display_has_webgl: function (p_version) { if (p_version !== 1 && p_version !== 2) { @@ -859,60 +522,64 @@ const GodotDisplay = { } }, + godot_js_display_cursor_lock_set__sig: 'vi', + godot_js_display_cursor_lock_set: function (p_lock) { + if (p_lock) { + GodotDisplayCursor.lockPointer(); + } else { + GodotDisplayCursor.releasePointer(); + } + }, + + godot_js_display_cursor_is_locked__sig: 'i', + godot_js_display_cursor_is_locked: function () { + return GodotDisplayCursor.isPointerLocked() ? 1 : 0; + }, + /* * Listeners */ - godot_js_display_notification_cb__sig: 'viiiii', - godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { + godot_js_display_fullscreen_cb__sig: 'vi', + godot_js_display_fullscreen_cb: function (callback) { const canvas = GodotConfig.canvas; const func = GodotRuntime.get_func(callback); - const notif = [p_enter, p_exit, p_in, p_out]; - ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) { - GodotDisplayListeners.add(canvas, evt_name, function () { - func(notif[idx]); - }, true); - }); + function change_cb(evt) { + if (evt.target === canvas) { + func(GodotDisplayScreen.isFullscreen()); + } + } + GodotEventListeners.add(document, 'fullscreenchange', change_cb, false); + GodotEventListeners.add(document, 'mozfullscreenchange', change_cb, false); + GodotEventListeners.add(document, 'webkitfullscreenchange', change_cb, false); }, - godot_js_display_paste_cb__sig: 'vi', - godot_js_display_paste_cb: function (callback) { + godot_js_display_window_blur_cb__sig: 'vi', + godot_js_display_window_blur_cb: function (callback) { const func = GodotRuntime.get_func(callback); - GodotDisplayListeners.add(window, 'paste', function (evt) { - const text = evt.clipboardData.getData('text'); - const ptr = GodotRuntime.allocString(text); - func(ptr); - GodotRuntime.free(ptr); + GodotEventListeners.add(window, 'blur', function () { + func(); }, false); }, - godot_js_display_drop_files_cb__sig: 'vi', - godot_js_display_drop_files_cb: function (callback) { - const func = GodotRuntime.get_func(callback); - const dropFiles = function (files) { - const args = files || []; - if (!args.length) { - return; - } - const argc = args.length; - const argv = GodotRuntime.allocStringArray(args); - func(argv, argc); - GodotRuntime.freeStringArray(argv, argc); - }; + godot_js_display_notification_cb__sig: 'viiiii', + godot_js_display_notification_cb: function (callback, p_enter, p_exit, p_in, p_out) { const canvas = GodotConfig.canvas; - GodotDisplayListeners.add(canvas, 'dragover', function (ev) { - // Prevent default behavior (which would try to open the file(s)) - ev.preventDefault(); - }, false); - GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); + const func = GodotRuntime.get_func(callback); + const notif = [p_enter, p_exit, p_in, p_out]; + ['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function (evt_name, idx) { + GodotEventListeners.add(canvas, evt_name, function () { + func(notif[idx]); + }, true); + }); }, godot_js_display_setup_canvas__sig: 'viiii', godot_js_display_setup_canvas: function (p_width, p_height, p_fullscreen, p_hidpi) { const canvas = GodotConfig.canvas; - GodotDisplayListeners.add(canvas, 'contextmenu', function (ev) { + GodotEventListeners.add(canvas, 'contextmenu', function (ev) { ev.preventDefault(); }, false); - GodotDisplayListeners.add(canvas, 'webglcontextlost', function (ev) { + GodotEventListeners.add(canvas, 'webglcontextlost', function (ev) { alert('WebGL context lost, please reload the page'); // eslint-disable-line no-alert ev.preventDefault(); }, false); @@ -965,49 +632,6 @@ const GodotDisplay = { GodotDisplayVK.init(input_cb); } }, - - /* - * Gamepads - */ - godot_js_display_gamepad_cb__sig: 'vi', - godot_js_display_gamepad_cb: function (change_cb) { - const onchange = GodotRuntime.get_func(change_cb); - GodotDisplayGamepads.init(onchange); - }, - - godot_js_display_gamepad_sample_count__sig: 'i', - godot_js_display_gamepad_sample_count: function () { - return GodotDisplayGamepads.get_samples().length; - }, - - godot_js_display_gamepad_sample__sig: 'i', - godot_js_display_gamepad_sample: function () { - GodotDisplayGamepads.sample(); - return 0; - }, - - godot_js_display_gamepad_sample_get__sig: 'iiiiiii', - godot_js_display_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) { - const sample = GodotDisplayGamepads.get_sample(p_index); - if (!sample || !sample.connected) { - return 1; - } - const btns = sample.buttons; - const btns_len = btns.length < 16 ? btns.length : 16; - for (let i = 0; i < btns_len; i++) { - GodotRuntime.setHeapValue(r_btns + (i << 2), btns[i], 'float'); - } - GodotRuntime.setHeapValue(r_btns_num, btns_len, 'i32'); - const axes = sample.axes; - const axes_len = axes.length < 10 ? axes.length : 10; - for (let i = 0; i < axes_len; i++) { - GodotRuntime.setHeapValue(r_axes + (i << 2), axes[i], 'float'); - } - GodotRuntime.setHeapValue(r_axes_num, axes_len, 'i32'); - const is_standard = sample.standard ? 1 : 0; - GodotRuntime.setHeapValue(r_standard, is_standard, 'i32'); - return 0; - }, }; autoAddDeps(GodotDisplay, '$GodotDisplay'); diff --git a/platform/javascript/js/libs/library_godot_input.js b/platform/javascript/js/libs/library_godot_input.js new file mode 100644 index 0000000000..587c320f35 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_input.js @@ -0,0 +1,526 @@ +/*************************************************************************/ +/* library_godot_input.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +/* + * Gamepad API helper. + */ +const GodotInputGamepads = { + $GodotInputGamepads__deps: ['$GodotRuntime', '$GodotEventListeners'], + $GodotInputGamepads: { + samples: [], + + get_pads: function () { + try { + // Will throw in iframe when permission is denied. + // Will throw/warn in the future for insecure contexts. + // See https://github.com/w3c/gamepad/pull/120 + const pads = navigator.getGamepads(); + if (pads) { + return pads; + } + return []; + } catch (e) { + return []; + } + }, + + get_samples: function () { + return GodotInputGamepads.samples; + }, + + get_sample: function (index) { + const samples = GodotInputGamepads.samples; + return index < samples.length ? samples[index] : null; + }, + + sample: function () { + const pads = GodotInputGamepads.get_pads(); + const samples = []; + for (let i = 0; i < pads.length; i++) { + const pad = pads[i]; + if (!pad) { + samples.push(null); + continue; + } + const s = { + standard: pad.mapping === 'standard', + buttons: [], + axes: [], + connected: pad.connected, + }; + for (let b = 0; b < pad.buttons.length; b++) { + s.buttons.push(pad.buttons[b].value); + } + for (let a = 0; a < pad.axes.length; a++) { + s.axes.push(pad.axes[a]); + } + samples.push(s); + } + GodotInputGamepads.samples = samples; + }, + + init: function (onchange) { + GodotEventListeners.samples = []; + function add(pad) { + const guid = GodotInputGamepads.get_guid(pad); + const c_id = GodotRuntime.allocString(pad.id); + const c_guid = GodotRuntime.allocString(guid); + onchange(pad.index, 1, c_id, c_guid); + GodotRuntime.free(c_id); + GodotRuntime.free(c_guid); + } + const pads = GodotInputGamepads.get_pads(); + for (let i = 0; i < pads.length; i++) { + // Might be reserved space. + if (pads[i]) { + add(pads[i]); + } + } + GodotEventListeners.add(window, 'gamepadconnected', function (evt) { + add(evt.gamepad); + }, false); + GodotEventListeners.add(window, 'gamepaddisconnected', function (evt) { + onchange(evt.gamepad.index, 0); + }, false); + }, + + get_guid: function (pad) { + if (pad.mapping) { + return pad.mapping; + } + const ua = navigator.userAgent; + let os = 'Unknown'; + if (ua.indexOf('Android') >= 0) { + os = 'Android'; + } else if (ua.indexOf('Linux') >= 0) { + os = 'Linux'; + } else if (ua.indexOf('iPhone') >= 0) { + os = 'iOS'; + } else if (ua.indexOf('Macintosh') >= 0) { + // Updated iPads will fall into this category. + os = 'MacOSX'; + } else if (ua.indexOf('Windows') >= 0) { + os = 'Windows'; + } + + const id = pad.id; + // Chrom* style: NAME (Vendor: xxxx Product: xxxx) + const exp1 = /vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i; + // Firefox/Safari style (safari may remove leading zeores) + const exp2 = /^([0-9a-f]+)-([0-9a-f]+)-/i; + let vendor = ''; + let product = ''; + if (exp1.test(id)) { + const match = exp1.exec(id); + vendor = match[1].padStart(4, '0'); + product = match[2].padStart(4, '0'); + } else if (exp2.test(id)) { + const match = exp2.exec(id); + vendor = match[1].padStart(4, '0'); + product = match[2].padStart(4, '0'); + } + if (!vendor || !product) { + return `${os}Unknown`; + } + return os + vendor + product; + }, + }, +}; +mergeInto(LibraryManager.library, GodotInputGamepads); + +/* + * Drag and drop helper. + * This is pretty big, but basically detect dropped files on GodotConfig.canvas, + * process them one by one (recursively for directories), and copies them to + * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot + * event (that requires a string array of paths). + * + * NOTE: The temporary files are removed after the callback. This means that + * deferred callbacks won't be able to access the files. + */ +const GodotInputDragDrop = { + $GodotInputDragDrop__deps: ['$FS', '$GodotFS'], + $GodotInputDragDrop: { + promises: [], + pending_files: [], + + add_entry: function (entry) { + if (entry.isDirectory) { + GodotInputDragDrop.add_dir(entry); + } else if (entry.isFile) { + GodotInputDragDrop.add_file(entry); + } else { + GodotRuntime.error('Unrecognized entry...', entry); + } + }, + + add_dir: function (entry) { + GodotInputDragDrop.promises.push(new Promise(function (resolve, reject) { + const reader = entry.createReader(); + reader.readEntries(function (entries) { + for (let i = 0; i < entries.length; i++) { + GodotInputDragDrop.add_entry(entries[i]); + } + resolve(); + }); + })); + }, + + add_file: function (entry) { + GodotInputDragDrop.promises.push(new Promise(function (resolve, reject) { + entry.file(function (file) { + const reader = new FileReader(); + reader.onload = function () { + const f = { + 'path': file.relativePath || file.webkitRelativePath, + 'name': file.name, + 'type': file.type, + 'size': file.size, + 'data': reader.result, + }; + if (!f['path']) { + f['path'] = f['name']; + } + GodotInputDragDrop.pending_files.push(f); + resolve(); + }; + reader.onerror = function () { + GodotRuntime.print('Error reading file'); + reject(); + }; + reader.readAsArrayBuffer(file); + }, function (err) { + GodotRuntime.print('Error!'); + reject(); + }); + })); + }, + + process: function (resolve, reject) { + if (GodotInputDragDrop.promises.length === 0) { + resolve(); + return; + } + GodotInputDragDrop.promises.pop().then(function () { + setTimeout(function () { + GodotInputDragDrop.process(resolve, reject); + }, 0); + }); + }, + + _process_event: function (ev, callback) { + ev.preventDefault(); + if (ev.dataTransfer.items) { + // Use DataTransferItemList interface to access the file(s) + for (let i = 0; i < ev.dataTransfer.items.length; i++) { + const item = ev.dataTransfer.items[i]; + let entry = null; + if ('getAsEntry' in item) { + entry = item.getAsEntry(); + } else if ('webkitGetAsEntry' in item) { + entry = item.webkitGetAsEntry(); + } + if (entry) { + GodotInputDragDrop.add_entry(entry); + } + } + } else { + GodotRuntime.error('File upload not supported'); + } + new Promise(GodotInputDragDrop.process).then(function () { + const DROP = `/tmp/drop-${parseInt(Math.random() * (1 << 30), 10)}/`; + const drops = []; + const files = []; + FS.mkdir(DROP); + GodotInputDragDrop.pending_files.forEach((elem) => { + const path = elem['path']; + GodotFS.copy_to_fs(DROP + path, elem['data']); + let idx = path.indexOf('/'); + if (idx === -1) { + // Root file + drops.push(DROP + path); + } else { + // Subdir + const sub = path.substr(0, idx); + idx = sub.indexOf('/'); + if (idx < 0 && drops.indexOf(DROP + sub) === -1) { + drops.push(DROP + sub); + } + } + files.push(DROP + path); + }); + GodotInputDragDrop.promises = []; + GodotInputDragDrop.pending_files = []; + callback(drops); + if (GodotConfig.persistent_drops) { + // Delay removal at exit. + GodotOS.atexit(function (resolve, reject) { + GodotInputDragDrop.remove_drop(files, DROP); + resolve(); + }); + } else { + GodotInputDragDrop.remove_drop(files, DROP); + } + }); + }, + + remove_drop: function (files, drop_path) { + const dirs = [drop_path.substr(0, drop_path.length - 1)]; + // Remove temporary files + files.forEach(function (file) { + FS.unlink(file); + let dir = file.replace(drop_path, ''); + let idx = dir.lastIndexOf('/'); + while (idx > 0) { + dir = dir.substr(0, idx); + if (dirs.indexOf(drop_path + dir) === -1) { + dirs.push(drop_path + dir); + } + idx = dir.lastIndexOf('/'); + } + }); + // Remove dirs. + dirs.sort(function (a, b) { + const al = (a.match(/\//g) || []).length; + const bl = (b.match(/\//g) || []).length; + if (al > bl) { + return -1; + } else if (al < bl) { + return 1; + } + return 0; + }).forEach(function (dir) { + FS.rmdir(dir); + }); + }, + + handler: function (callback) { + return function (ev) { + GodotInputDragDrop._process_event(ev, callback); + }; + }, + }, +}; +mergeInto(LibraryManager.library, GodotInputDragDrop); + +/* + * Godot exposed input functions. + */ +const GodotInput = { + $GodotInput__deps: ['$GodotRuntime', '$GodotConfig', '$GodotEventListeners', '$GodotInputGamepads', '$GodotInputDragDrop'], + $GodotInput: { + getModifiers: function (evt) { + return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3); + }, + computePosition: function (evt, rect) { + const canvas = GodotConfig.canvas; + const rw = canvas.width / rect.width; + const rh = canvas.height / rect.height; + const x = (evt.clientX - rect.x) * rw; + const y = (evt.clientY - rect.y) * rh; + return [x, y]; + }, + }, + + /* + * Mouse API + */ + godot_js_input_mouse_move_cb__sig: 'vi', + godot_js_input_mouse_move_cb: function (callback) { + const func = GodotRuntime.get_func(callback); + const canvas = GodotConfig.canvas; + function move_cb(evt) { + const rect = canvas.getBoundingClientRect(); + const pos = GodotInput.computePosition(evt, rect); + // Scale movement + const rw = canvas.width / rect.width; + const rh = canvas.height / rect.height; + const rel_pos_x = evt.movementX * rw; + const rel_pos_y = evt.movementY * rh; + const modifiers = GodotInput.getModifiers(evt); + func(pos[0], pos[1], rel_pos_x, rel_pos_y, modifiers); + } + GodotEventListeners.add(window, 'mousemove', move_cb, false); + }, + + godot_js_input_mouse_wheel_cb__sig: 'vi', + godot_js_input_mouse_wheel_cb: function (callback) { + const func = GodotRuntime.get_func(callback); + function wheel_cb(evt) { + if (func(evt['deltaX'] || 0, evt['deltaY'] || 0)) { + evt.preventDefault(); + } + } + GodotEventListeners.add(GodotConfig.canvas, 'wheel', wheel_cb, false); + }, + + godot_js_input_mouse_button_cb__sig: 'vi', + godot_js_input_mouse_button_cb: function (callback) { + const func = GodotRuntime.get_func(callback); + const canvas = GodotConfig.canvas; + function button_cb(p_pressed, evt) { + const rect = canvas.getBoundingClientRect(); + const pos = GodotInput.computePosition(evt, rect); + const modifiers = GodotInput.getModifiers(evt); + if (func(p_pressed, evt.button, pos[0], pos[1], modifiers)) { + evt.preventDefault(); + } + } + GodotEventListeners.add(canvas, 'mousedown', button_cb.bind(null, 1), false); + GodotEventListeners.add(window, 'mouseup', button_cb.bind(null, 0), false); + }, + + /* + * Touch API + */ + godot_js_input_touch_cb__sig: 'viii', + godot_js_input_touch_cb: function (callback, ids, coords) { + const func = GodotRuntime.get_func(callback); + const canvas = GodotConfig.canvas; + function touch_cb(type, evt) { + const rect = canvas.getBoundingClientRect(); + const touches = evt.changedTouches; + for (let i = 0; i < touches.length; i++) { + const touch = touches[i]; + const pos = GodotInput.computePosition(touch, rect); + GodotRuntime.setHeapValue(coords + (i * 2), pos[0], 'double'); + GodotRuntime.setHeapValue(coords + (i * 2 + 8), pos[1], 'double'); + GodotRuntime.setHeapValue(ids + i, touch.identifier, 'i32'); + } + func(type, touches.length); + if (evt.cancelable) { + evt.preventDefault(); + } + } + GodotEventListeners.add(canvas, 'touchstart', touch_cb.bind(null, 0), false); + GodotEventListeners.add(canvas, 'touchend', touch_cb.bind(null, 1), false); + GodotEventListeners.add(canvas, 'touchcancel', touch_cb.bind(null, 1), false); + GodotEventListeners.add(canvas, 'touchmove', touch_cb.bind(null, 2), false); + }, + + /* + * Key API + */ + godot_js_input_key_cb__sig: 'viii', + godot_js_input_key_cb: function (callback, code, key) { + const func = GodotRuntime.get_func(callback); + function key_cb(pressed, evt) { + const modifiers = GodotInput.getModifiers(evt); + GodotRuntime.stringToHeap(evt.code, code, 32); + GodotRuntime.stringToHeap(evt.key, key, 32); + func(pressed, evt.repeat, modifiers); + evt.preventDefault(); + } + GodotEventListeners.add(GodotConfig.canvas, 'keydown', key_cb.bind(null, 1), false); + GodotEventListeners.add(GodotConfig.canvas, 'keyup', key_cb.bind(null, 0), false); + }, + + /* + * Gamepad API + */ + godot_js_input_gamepad_cb__sig: 'vi', + godot_js_input_gamepad_cb: function (change_cb) { + const onchange = GodotRuntime.get_func(change_cb); + GodotInputGamepads.init(onchange); + }, + + godot_js_input_gamepad_sample_count__sig: 'i', + godot_js_input_gamepad_sample_count: function () { + return GodotInputGamepads.get_samples().length; + }, + + godot_js_input_gamepad_sample__sig: 'i', + godot_js_input_gamepad_sample: function () { + GodotInputGamepads.sample(); + return 0; + }, + + godot_js_input_gamepad_sample_get__sig: 'iiiiiii', + godot_js_input_gamepad_sample_get: function (p_index, r_btns, r_btns_num, r_axes, r_axes_num, r_standard) { + const sample = GodotInputGamepads.get_sample(p_index); + if (!sample || !sample.connected) { + return 1; + } + const btns = sample.buttons; + const btns_len = btns.length < 16 ? btns.length : 16; + for (let i = 0; i < btns_len; i++) { + GodotRuntime.setHeapValue(r_btns + (i << 2), btns[i], 'float'); + } + GodotRuntime.setHeapValue(r_btns_num, btns_len, 'i32'); + const axes = sample.axes; + const axes_len = axes.length < 10 ? axes.length : 10; + for (let i = 0; i < axes_len; i++) { + GodotRuntime.setHeapValue(r_axes + (i << 2), axes[i], 'float'); + } + GodotRuntime.setHeapValue(r_axes_num, axes_len, 'i32'); + const is_standard = sample.standard ? 1 : 0; + GodotRuntime.setHeapValue(r_standard, is_standard, 'i32'); + return 0; + }, + + /* + * Drag/Drop API + */ + godot_js_input_drop_files_cb__sig: 'vi', + godot_js_input_drop_files_cb: function (callback) { + const func = GodotRuntime.get_func(callback); + const dropFiles = function (files) { + const args = files || []; + if (!args.length) { + return; + } + const argc = args.length; + const argv = GodotRuntime.allocStringArray(args); + func(argv, argc); + GodotRuntime.freeStringArray(argv, argc); + }; + const canvas = GodotConfig.canvas; + GodotEventListeners.add(canvas, 'dragover', function (ev) { + // Prevent default behavior (which would try to open the file(s)) + ev.preventDefault(); + }, false); + GodotEventListeners.add(canvas, 'drop', GodotInputDragDrop.handler(dropFiles)); + }, + + /* Paste API */ + godot_js_input_paste_cb__sig: 'vi', + godot_js_input_paste_cb: function (callback) { + const func = GodotRuntime.get_func(callback); + GodotEventListeners.add(window, 'paste', function (evt) { + const text = evt.clipboardData.getData('text'); + const ptr = GodotRuntime.allocString(text); + func(ptr); + GodotRuntime.free(ptr); + }, false); + }, +}; + +autoAddDeps(GodotInput, '$GodotInput'); +mergeInto(LibraryManager.library, GodotInput); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 99e7ee8b5f..c552e99415 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -328,3 +328,43 @@ const GodotOS = { autoAddDeps(GodotOS, '$GodotOS'); mergeInto(LibraryManager.library, GodotOS); + +/* + * Godot event listeners. + * Keeps track of registered event listeners so it can remove them on shutdown. + */ +const GodotEventListeners = { + $GodotEventListeners__deps: ['$GodotOS'], + $GodotEventListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotEventListeners.clear(); resolve(); });', + $GodotEventListeners: { + handlers: [], + + has: function (target, event, method, capture) { + return GodotEventListeners.handlers.findIndex(function (e) { + return e.target === target && e.event === event && e.method === method && e.capture === capture; + }) !== -1; + }, + + add: function (target, event, method, capture) { + if (GodotEventListeners.has(target, event, method, capture)) { + return; + } + function Handler(p_target, p_event, p_method, p_capture) { + this.target = p_target; + this.event = p_event; + this.method = p_method; + this.capture = p_capture; + } + GodotEventListeners.handlers.push(new Handler(target, event, method, capture)); + target.addEventListener(event, method, capture); + }, + + clear: function () { + GodotEventListeners.handlers.forEach(function (h) { + h.target.removeEventListener(h.event, h.method, h.capture); + }); + GodotEventListeners.handlers.length = 0; + }, + }, +}; +mergeInto(LibraryManager.library, GodotEventListeners); diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 8eb22c1c72..afb7c7b2ab 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -113,6 +113,7 @@ def configure(env): elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + env.Prepend(CPPDEFINES=["DEV_ENABLED"]) env.Append(LINKFLAGS=["-rdynamic"]) ## Architecture diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 5c6824cf44..f2cd336b39 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -306,11 +306,11 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { // The only modes that show a cursor are VISIBLE and CONFINED bool showCursor = (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED); - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + for (const KeyValue<WindowID, WindowData> &E : windows) { if (showCursor) { - XDefineCursor(x11_display, E->get().x11_window, cursors[current_cursor]); // show cursor + XDefineCursor(x11_display, E.value.x11_window, cursors[current_cursor]); // show cursor } else { - XDefineCursor(x11_display, E->get().x11_window, null_cursor); // hide cursor + XDefineCursor(x11_display, E.value.x11_window, null_cursor); // hide cursor } } mouse_mode = p_mode; @@ -785,8 +785,8 @@ Vector<DisplayServer::WindowID> DisplayServerX11::get_window_list() const { _THREAD_SAFE_METHOD_ Vector<int> ret; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<WindowID, WindowData> &E : windows) { + ret.push_back(E.key); } return ret; } @@ -864,8 +864,8 @@ DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const WindowID found_window = INVALID_WINDOW_ID; WindowID parent_window = INVALID_WINDOW_ID; unsigned int focus_order = 0; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - const WindowData &wd = E->get(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + const WindowData &wd = E.value; // Discard windows with no focus. if (wd.focus_order == 0) { @@ -873,7 +873,7 @@ DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const } // Find topmost window which contains the given position. - WindowID window_id = E->key(); + WindowID window_id = E.key; Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id)); if (win_rect.has_point(p_position)) { // For siblings, pick the window which was focused last. @@ -1768,8 +1768,8 @@ bool DisplayServerX11::window_can_draw(WindowID p_window) const { bool DisplayServerX11::can_any_window_draw() const { _THREAD_SAFE_METHOD_ - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (window_get_mode(E->key()) != WINDOW_MODE_MINIMIZED) { + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (window_get_mode(E.key) != WINDOW_MODE_MINIMIZED) { return true; } } @@ -1841,12 +1841,12 @@ void DisplayServerX11::cursor_set_shape(CursorShape p_shape) { if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { if (cursors[p_shape] != None) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]); + for (const KeyValue<WindowID, WindowData> &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); } } else if (cursors[CURSOR_ARROW] != None) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - XDefineCursor(x11_display, E->get().x11_window, cursors[CURSOR_ARROW]); + for (const KeyValue<WindowID, WindowData> &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[CURSOR_ARROW]); } } } @@ -1944,8 +1944,8 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape if (p_shape == current_cursor) { if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - XDefineCursor(x11_display, E->get().x11_window, cursors[p_shape]); + for (const KeyValue<WindowID, WindowData> &E : windows) { + XDefineCursor(x11_display, E.value.x11_window, cursors[p_shape]); } } } @@ -2068,6 +2068,24 @@ String DisplayServerX11::keyboard_get_layout_name(int p_index) const { return ret; } +Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { + unsigned int modifiers = p_keycode & KEY_MODIFIER_MASK; + unsigned int keycode_no_mod = p_keycode & KEY_CODE_MASK; + unsigned int xkeycode = KeyMappingX11::get_xlibcode((Key)keycode_no_mod); + KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); + if (xkeysym >= 'a' && xkeysym <= 'z') { + xkeysym -= ('a' - 'A'); + } + + Key key = KeyMappingX11::get_keycode(xkeysym); + // If not found, fallback to QWERTY. + // This should match the behavior of the event pump + if (key == KEY_NONE) { + return p_keycode; + } + return (Key)(key | modifiers); +} + DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) { Atom actual_type = None; int actual_format = 0; @@ -2517,8 +2535,8 @@ void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data, DisplayServerX11 *ds = reinterpret_cast<DisplayServerX11 *>(client_data); ds->xim = nullptr; - for (Map<WindowID, WindowData>::Element *E = ds->windows.front(); E; E = E->next()) { - E->get().xic = nullptr; + for (KeyValue<WindowID, WindowData> &E : ds->windows) { + E.value.xic = nullptr; } } @@ -2526,9 +2544,9 @@ void DisplayServerX11::_window_changed(XEvent *event) { WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (event->xany.window == E->get().x11_window) { - window_id = E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (event->xany.window == E.value.x11_window) { + window_id = E.key; break; } } @@ -2602,8 +2620,8 @@ void DisplayServerX11::_dispatch_input_event(const Ref<InputEvent> &p_event) { callable.call((const Variant **)&evp, 1, ret, ce); } else { //send to all windows - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Callable callable = E->get().input_event_callback; + for (KeyValue<WindowID, WindowData> &E : windows) { + Callable callable = E.value.input_event_callback; if (callable.is_null()) { continue; } @@ -2703,8 +2721,8 @@ void DisplayServerX11::process_events() { if (app_focused) { //verify that one of the windows has focus, else send focus out notification bool focus_found = false; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().focused) { + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (E.value.focused) { focus_found = true; break; } @@ -2749,9 +2767,9 @@ void DisplayServerX11::process_events() { WindowID window_id = MAIN_WINDOW_ID; // Assign the event to the relevant window - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (event.xany.window == E->get().x11_window) { - window_id = E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (event.xany.window == E.value.x11_window) { + window_id = E.key; break; } } @@ -2990,17 +3008,17 @@ void DisplayServerX11::process_events() { if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + for (const KeyValue<WindowID, WindowData> &E : windows) { if (mouse_mode == MOUSE_MODE_CONFINED) { - XUndefineCursor(x11_display, E->get().x11_window); + XUndefineCursor(x11_display, E.value.x11_window); } else if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { // Or re-hide it. - XDefineCursor(x11_display, E->get().x11_window, null_cursor); + XDefineCursor(x11_display, E.value.x11_window, null_cursor); } XGrabPointer( - x11_display, E->get().x11_window, True, + x11_display, E.value.x11_window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, E->get().x11_window, None, CurrentTime); + GrabModeAsync, GrabModeAsync, E.value.x11_window, None, CurrentTime); } } #ifdef TOUCH_ENABLED @@ -3036,11 +3054,11 @@ void DisplayServerX11::process_events() { _send_window_event(wd, WINDOW_EVENT_FOCUS_OUT); if (mouse_mode_grab) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + for (const KeyValue<WindowID, WindowData> &E : windows) { //dear X11, I try, I really try, but you never work, you do whathever you want. if (mouse_mode == MOUSE_MODE_CAPTURED) { // Show the cursor if we're in captured mode so it doesn't look weird. - XUndefineCursor(x11_display, E->get().x11_window); + XUndefineCursor(x11_display, E.value.x11_window); } } XUngrabPointer(x11_display, CurrentTime); @@ -3052,12 +3070,12 @@ void DisplayServerX11::process_events() { }*/ // Release every pointer to avoid sticky points - for (Map<int, Vector2>::Element *E = xi.state.front(); E; E = E->next()) { + for (const KeyValue<int, Vector2> &E : xi.state) { Ref<InputEventScreenTouch> st; st.instantiate(); - st->set_index(E->key()); + st->set_index(E.key); st->set_window_id(window_id); - st->set_position(E->get()); + st->set_position(E.value); Input::get_singleton()->parse_input_event(st); } xi.state.clear(); @@ -3144,9 +3162,9 @@ void DisplayServerX11::process_events() { // Note: This is needed for drag & drop to work between windows, // because the engine expects events to keep being processed // on the same window dragging started. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - const WindowData &wd_other = E->get(); - WindowID window_id_other = E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + const WindowData &wd_other = E.value; + WindowID window_id_other = E.key; if (wd_other.focused) { if (window_id_other != window_id) { int x, y; @@ -3287,8 +3305,8 @@ void DisplayServerX11::process_events() { // Note: This is needed for drag & drop to work between windows, // because the engine expects events to keep being processed // on the same window dragging started. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - const WindowData &wd_other = E->get(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + const WindowData &wd_other = E.value; if (wd_other.focused) { int x, y; Window child; @@ -3296,7 +3314,7 @@ void DisplayServerX11::process_events() { Point2i pos_focused(x, y); - mm->set_window_id(E->key()); + mm->set_window_id(E.key); mm->set_position(pos_focused); mm->set_global_position(pos_focused); mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); @@ -3487,8 +3505,8 @@ void DisplayServerX11::set_context(Context p_context) { context = p_context; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - _update_context(E->get()); + for (KeyValue<WindowID, WindowData> &E : windows) { + _update_context(E.value); } } @@ -4268,14 +4286,14 @@ DisplayServerX11::~DisplayServerX11() { events_thread.wait_to_finish(); //destroy all windows - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + for (KeyValue<WindowID, WindowData> &E : windows) { #ifdef VULKAN_ENABLED if (rendering_driver == "vulkan") { - context_vulkan->window_destroy(E->key()); + context_vulkan->window_destroy(E.key); } #endif - WindowData &wd = E->get(); + WindowData &wd = E.value; if (wd.xic) { XDestroyIC(wd.xic); wd.xic = nullptr; diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 052c6d6b7b..1887c7105b 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -372,6 +372,7 @@ public: 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; diff --git a/platform/linuxbsd/freedesktop_screensaver.cpp b/platform/linuxbsd/freedesktop_screensaver.cpp index a6a3b27d76..3973d43d49 100644 --- a/platform/linuxbsd/freedesktop_screensaver.cpp +++ b/platform/linuxbsd/freedesktop_screensaver.cpp @@ -50,6 +50,7 @@ void FreeDesktopScreenSaver::inhibit() { DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { + dbus_error_free(&error); unsupported = true; return; } @@ -72,6 +73,7 @@ void FreeDesktopScreenSaver::inhibit() { DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); dbus_message_unref(message); if (dbus_error_is_set(&error)) { + dbus_error_free(&error); dbus_connection_unref(bus); unsupported = false; return; @@ -96,6 +98,7 @@ void FreeDesktopScreenSaver::uninhibit() { DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); if (dbus_error_is_set(&error)) { + dbus_error_free(&error); unsupported = true; return; } @@ -110,6 +113,7 @@ void FreeDesktopScreenSaver::uninhibit() { DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); if (dbus_error_is_set(&error)) { + dbus_error_free(&error); dbus_connection_unref(bus); unsupported = true; return; diff --git a/platform/linuxbsd/key_mapping_x11.cpp b/platform/linuxbsd/key_mapping_x11.cpp index a1ef28234d..829feda40a 100644 --- a/platform/linuxbsd/key_mapping_x11.cpp +++ b/platform/linuxbsd/key_mapping_x11.cpp @@ -309,6 +309,18 @@ unsigned int KeyMappingX11::get_scancode(unsigned int p_code) { return keycode; } +unsigned int KeyMappingX11::get_xlibcode(unsigned int p_keysym) { + unsigned int code = 0; + for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { + if (_scancode_to_keycode[i].keysym == p_keysym) { + code = _scancode_to_keycode[i].keycode; + break; + } + } + + return code; +} + Key KeyMappingX11::get_keycode(KeySym p_keysym) { // kinda bruteforce.. could optimize. diff --git a/platform/linuxbsd/key_mapping_x11.h b/platform/linuxbsd/key_mapping_x11.h index 598db1c45a..d4f1554671 100644 --- a/platform/linuxbsd/key_mapping_x11.h +++ b/platform/linuxbsd/key_mapping_x11.h @@ -45,6 +45,7 @@ class KeyMappingX11 { public: static Key get_keycode(KeySym p_keysym); + static unsigned int get_xlibcode(unsigned int p_keysym); static unsigned int get_scancode(unsigned int p_code); static KeySym get_keysym(Key p_code); static unsigned int get_unicode_from_keysym(KeySym p_keysym); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 2c9801f512..69474c6dec 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -463,7 +463,10 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // The trash can is successfully created, now we check that we don't exceed our file name length limit. // If the file name is too long trim it so we can add the identifying number and ".trashinfo". // Assumes that the file name length limit is 255 characters. - String file_name = basename(p_path.utf8().get_data()); + String file_name = p_path.get_file(); + if (file_name.length() == 0) { + file_name = p_path.get_base_dir().get_file(); + } if (file_name.length() > 240) { file_name = file_name.substr(0, file_name.length() - 15); } diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 6b25daf05d..10cf2b591e 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -64,6 +64,7 @@ def configure(env): elif env["target"] == "debug": env.Prepend(CCFLAGS=["-g3"]) env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) + env.Prepend(CPPDEFINES=["DEV_ENABLED"]) env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"]) ## Architecture diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 06b14f1c14..e8f2858489 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -305,6 +305,7 @@ public: 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 force_process_and_drop_events() override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index f037d75fbd..3fe055a511 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -930,146 +930,155 @@ static bool isNumpadKey(unsigned int key) { return false; } +// Keyboard symbol translation table +static const Key _osx_to_godot_table[128] = { + /* 00 */ KEY_A, + /* 01 */ KEY_S, + /* 02 */ KEY_D, + /* 03 */ KEY_F, + /* 04 */ KEY_H, + /* 05 */ KEY_G, + /* 06 */ KEY_Z, + /* 07 */ KEY_X, + /* 08 */ KEY_C, + /* 09 */ KEY_V, + /* 0a */ KEY_SECTION, /* ISO Section */ + /* 0b */ KEY_B, + /* 0c */ KEY_Q, + /* 0d */ KEY_W, + /* 0e */ KEY_E, + /* 0f */ KEY_R, + /* 10 */ KEY_Y, + /* 11 */ KEY_T, + /* 12 */ KEY_1, + /* 13 */ KEY_2, + /* 14 */ KEY_3, + /* 15 */ KEY_4, + /* 16 */ KEY_6, + /* 17 */ KEY_5, + /* 18 */ KEY_EQUAL, + /* 19 */ KEY_9, + /* 1a */ KEY_7, + /* 1b */ KEY_MINUS, + /* 1c */ KEY_8, + /* 1d */ KEY_0, + /* 1e */ KEY_BRACERIGHT, + /* 1f */ KEY_O, + /* 20 */ KEY_U, + /* 21 */ KEY_BRACELEFT, + /* 22 */ KEY_I, + /* 23 */ KEY_P, + /* 24 */ KEY_ENTER, + /* 25 */ KEY_L, + /* 26 */ KEY_J, + /* 27 */ KEY_APOSTROPHE, + /* 28 */ KEY_K, + /* 29 */ KEY_SEMICOLON, + /* 2a */ KEY_BACKSLASH, + /* 2b */ KEY_COMMA, + /* 2c */ KEY_SLASH, + /* 2d */ KEY_N, + /* 2e */ KEY_M, + /* 2f */ KEY_PERIOD, + /* 30 */ KEY_TAB, + /* 31 */ KEY_SPACE, + /* 32 */ KEY_QUOTELEFT, + /* 33 */ KEY_BACKSPACE, + /* 34 */ KEY_UNKNOWN, + /* 35 */ KEY_ESCAPE, + /* 36 */ KEY_META, + /* 37 */ KEY_META, + /* 38 */ KEY_SHIFT, + /* 39 */ KEY_CAPSLOCK, + /* 3a */ KEY_ALT, + /* 3b */ KEY_CTRL, + /* 3c */ KEY_SHIFT, + /* 3d */ KEY_ALT, + /* 3e */ KEY_CTRL, + /* 3f */ KEY_UNKNOWN, /* Function */ + /* 40 */ KEY_UNKNOWN, /* F17 */ + /* 41 */ KEY_KP_PERIOD, + /* 42 */ KEY_UNKNOWN, + /* 43 */ KEY_KP_MULTIPLY, + /* 44 */ KEY_UNKNOWN, + /* 45 */ KEY_KP_ADD, + /* 46 */ KEY_UNKNOWN, + /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ + /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ + /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ + /* 4a */ KEY_VOLUMEMUTE, /* Mute */ + /* 4b */ KEY_KP_DIVIDE, + /* 4c */ KEY_KP_ENTER, + /* 4d */ KEY_UNKNOWN, + /* 4e */ KEY_KP_SUBTRACT, + /* 4f */ KEY_UNKNOWN, /* F18 */ + /* 50 */ KEY_UNKNOWN, /* F19 */ + /* 51 */ KEY_EQUAL, /* KeypadEqual */ + /* 52 */ KEY_KP_0, + /* 53 */ KEY_KP_1, + /* 54 */ KEY_KP_2, + /* 55 */ KEY_KP_3, + /* 56 */ KEY_KP_4, + /* 57 */ KEY_KP_5, + /* 58 */ KEY_KP_6, + /* 59 */ KEY_KP_7, + /* 5a */ KEY_UNKNOWN, /* F20 */ + /* 5b */ KEY_KP_8, + /* 5c */ KEY_KP_9, + /* 5d */ KEY_YEN, /* JIS Yen */ + /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ + /* 5f */ KEY_COMMA, /* JIS KeypadComma */ + /* 60 */ KEY_F5, + /* 61 */ KEY_F6, + /* 62 */ KEY_F7, + /* 63 */ KEY_F3, + /* 64 */ KEY_F8, + /* 65 */ KEY_F9, + /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ + /* 67 */ KEY_F11, + /* 68 */ KEY_UNKNOWN, /* JIS Kana */ + /* 69 */ KEY_F13, + /* 6a */ KEY_F16, + /* 6b */ KEY_F14, + /* 6c */ KEY_UNKNOWN, + /* 6d */ KEY_F10, + /* 6e */ KEY_MENU, + /* 6f */ KEY_F12, + /* 70 */ KEY_UNKNOWN, + /* 71 */ KEY_F15, + /* 72 */ KEY_INSERT, /* Really Help... */ + /* 73 */ KEY_HOME, + /* 74 */ KEY_PAGEUP, + /* 75 */ KEY_DELETE, + /* 76 */ KEY_F4, + /* 77 */ KEY_END, + /* 78 */ KEY_F2, + /* 79 */ KEY_PAGEDOWN, + /* 7a */ KEY_F1, + /* 7b */ KEY_LEFT, + /* 7c */ KEY_RIGHT, + /* 7d */ KEY_DOWN, + /* 7e */ KEY_UP, + /* 7f */ KEY_UNKNOWN, +}; + // Translates a OS X keycode to a Godot keycode -// static Key translateKey(unsigned int key) { - // Keyboard symbol translation table - static const Key table[128] = { - /* 00 */ KEY_A, - /* 01 */ KEY_S, - /* 02 */ KEY_D, - /* 03 */ KEY_F, - /* 04 */ KEY_H, - /* 05 */ KEY_G, - /* 06 */ KEY_Z, - /* 07 */ KEY_X, - /* 08 */ KEY_C, - /* 09 */ KEY_V, - /* 0a */ KEY_SECTION, /* ISO Section */ - /* 0b */ KEY_B, - /* 0c */ KEY_Q, - /* 0d */ KEY_W, - /* 0e */ KEY_E, - /* 0f */ KEY_R, - /* 10 */ KEY_Y, - /* 11 */ KEY_T, - /* 12 */ KEY_1, - /* 13 */ KEY_2, - /* 14 */ KEY_3, - /* 15 */ KEY_4, - /* 16 */ KEY_6, - /* 17 */ KEY_5, - /* 18 */ KEY_EQUAL, - /* 19 */ KEY_9, - /* 1a */ KEY_7, - /* 1b */ KEY_MINUS, - /* 1c */ KEY_8, - /* 1d */ KEY_0, - /* 1e */ KEY_BRACERIGHT, - /* 1f */ KEY_O, - /* 20 */ KEY_U, - /* 21 */ KEY_BRACELEFT, - /* 22 */ KEY_I, - /* 23 */ KEY_P, - /* 24 */ KEY_ENTER, - /* 25 */ KEY_L, - /* 26 */ KEY_J, - /* 27 */ KEY_APOSTROPHE, - /* 28 */ KEY_K, - /* 29 */ KEY_SEMICOLON, - /* 2a */ KEY_BACKSLASH, - /* 2b */ KEY_COMMA, - /* 2c */ KEY_SLASH, - /* 2d */ KEY_N, - /* 2e */ KEY_M, - /* 2f */ KEY_PERIOD, - /* 30 */ KEY_TAB, - /* 31 */ KEY_SPACE, - /* 32 */ KEY_QUOTELEFT, - /* 33 */ KEY_BACKSPACE, - /* 34 */ KEY_UNKNOWN, - /* 35 */ KEY_ESCAPE, - /* 36 */ KEY_META, - /* 37 */ KEY_META, - /* 38 */ KEY_SHIFT, - /* 39 */ KEY_CAPSLOCK, - /* 3a */ KEY_ALT, - /* 3b */ KEY_CTRL, - /* 3c */ KEY_SHIFT, - /* 3d */ KEY_ALT, - /* 3e */ KEY_CTRL, - /* 3f */ KEY_UNKNOWN, /* Function */ - /* 40 */ KEY_UNKNOWN, /* F17 */ - /* 41 */ KEY_KP_PERIOD, - /* 42 */ KEY_UNKNOWN, - /* 43 */ KEY_KP_MULTIPLY, - /* 44 */ KEY_UNKNOWN, - /* 45 */ KEY_KP_ADD, - /* 46 */ KEY_UNKNOWN, - /* 47 */ KEY_NUMLOCK, /* Really KeypadClear... */ - /* 48 */ KEY_VOLUMEUP, /* VolumeUp */ - /* 49 */ KEY_VOLUMEDOWN, /* VolumeDown */ - /* 4a */ KEY_VOLUMEMUTE, /* Mute */ - /* 4b */ KEY_KP_DIVIDE, - /* 4c */ KEY_KP_ENTER, - /* 4d */ KEY_UNKNOWN, - /* 4e */ KEY_KP_SUBTRACT, - /* 4f */ KEY_UNKNOWN, /* F18 */ - /* 50 */ KEY_UNKNOWN, /* F19 */ - /* 51 */ KEY_EQUAL, /* KeypadEqual */ - /* 52 */ KEY_KP_0, - /* 53 */ KEY_KP_1, - /* 54 */ KEY_KP_2, - /* 55 */ KEY_KP_3, - /* 56 */ KEY_KP_4, - /* 57 */ KEY_KP_5, - /* 58 */ KEY_KP_6, - /* 59 */ KEY_KP_7, - /* 5a */ KEY_UNKNOWN, /* F20 */ - /* 5b */ KEY_KP_8, - /* 5c */ KEY_KP_9, - /* 5d */ KEY_YEN, /* JIS Yen */ - /* 5e */ KEY_UNDERSCORE, /* JIS Underscore */ - /* 5f */ KEY_COMMA, /* JIS KeypadComma */ - /* 60 */ KEY_F5, - /* 61 */ KEY_F6, - /* 62 */ KEY_F7, - /* 63 */ KEY_F3, - /* 64 */ KEY_F8, - /* 65 */ KEY_F9, - /* 66 */ KEY_UNKNOWN, /* JIS Eisu */ - /* 67 */ KEY_F11, - /* 68 */ KEY_UNKNOWN, /* JIS Kana */ - /* 69 */ KEY_F13, - /* 6a */ KEY_F16, - /* 6b */ KEY_F14, - /* 6c */ KEY_UNKNOWN, - /* 6d */ KEY_F10, - /* 6e */ KEY_MENU, - /* 6f */ KEY_F12, - /* 70 */ KEY_UNKNOWN, - /* 71 */ KEY_F15, - /* 72 */ KEY_INSERT, /* Really Help... */ - /* 73 */ KEY_HOME, - /* 74 */ KEY_PAGEUP, - /* 75 */ KEY_DELETE, - /* 76 */ KEY_F4, - /* 77 */ KEY_END, - /* 78 */ KEY_F2, - /* 79 */ KEY_PAGEDOWN, - /* 7a */ KEY_F1, - /* 7b */ KEY_LEFT, - /* 7c */ KEY_RIGHT, - /* 7d */ KEY_DOWN, - /* 7e */ KEY_UP, - /* 7f */ KEY_UNKNOWN, - }; - if (key >= 128) { return KEY_UNKNOWN; } - return table[key]; + return _osx_to_godot_table[key]; +} + +// Translates a Godot keycode back to a OSX keycode +static unsigned int unmapKey(Key key) { + for (int i = 0; i <= 126; i++) { + if (_osx_to_godot_table[i] == key) { + return i; + } + } + return 127; } struct _KeyCodeMap { @@ -3005,7 +3014,7 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape ERR_FAIL_COND(!image.is_valid()); NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL + initWithBitmapDataPlanes:nullptr pixelsWide:int(texture_size.width) pixelsHigh:int(texture_size.height) bitsPerSample:8 @@ -3200,6 +3209,17 @@ String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { return kbd_layouts[p_index].name; } +Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const { + if (p_keycode == KEY_PAUSE) { + return p_keycode; + } + + unsigned int modifiers = p_keycode & KEY_MODIFIER_MASK; + unsigned int keycode_no_mod = p_keycode & KEY_CODE_MASK; + unsigned int osx_keycode = unmapKey((Key)keycode_no_mod); + return (Key)(remapKey(osx_keycode, 0) | modifiers); +} + void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { Ref<InputEvent> ev = p_event; Input::get_singleton()->parse_input_event(ev); @@ -3398,7 +3418,7 @@ void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) { img = img->duplicate(); img->convert(Image::FORMAT_RGBA8); NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] - initWithBitmapDataPlanes:NULL + initWithBitmapDataPlanes:nullptr pixelsWide:img->get_width() pixelsHigh:img->get_height() bitsPerSample:8 diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 54a3104482..2404c20153 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -95,6 +95,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_pictures", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_music", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/entitlements/app_sandbox/files_movies", PROPERTY_HINT_ENUM, "No,Read-only,Read-write"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::ARRAY, "codesign/entitlements/app_sandbox/helper_executables", PROPERTY_HINT_ARRAY_TYPE, itos(Variant::STRING) + "/" + itos(PROPERTY_HINT_GLOBAL_FILE) + ":"), Array())); r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); @@ -535,6 +536,8 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = ERR_CANT_CREATE; } + Array helpers = p_preset->get("codesign/entitlements/app_sandbox/helper_executables"); + // Create our folder structure. if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/MacOS"); @@ -546,6 +549,11 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Frameworks"); } + if ((err == OK) && helpers.size() > 0) { + print_line("Creating " + tmp_app_path_name + "/Contents/Helpers"); + err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Helpers"); + } + if (err == OK) { print_line("Creating " + tmp_app_path_name + "/Contents/Resources"); err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); @@ -688,6 +696,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p bool sign_enabled = p_preset->get("codesign/enable"); String ent_path = p_preset->get("codesign/entitlements/custom_file"); + String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements"); if (sign_enabled && (ent_path == "")) { ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); @@ -819,10 +828,43 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p } else { err = ERR_CANT_CREATE; } + + if ((err == OK) && helpers.size() > 0) { + ent_f = FileAccess::open(hlp_ent_path, FileAccess::WRITE); + if (ent_f) { + ent_f->store_line("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + ent_f->store_line("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"); + ent_f->store_line("<plist version=\"1.0\">"); + ent_f->store_line("<dict>"); + ent_f->store_line("<key>com.apple.security.app-sandbox</key>"); + ent_f->store_line("<true/>"); + ent_f->store_line("<key>com.apple.security.inherit</key>"); + ent_f->store_line("<true/>"); + ent_f->store_line("</dict>"); + ent_f->store_line("</plist>"); + + ent_f->close(); + memdelete(ent_f); + } else { + err = ERR_CANT_CREATE; + } + } + } + + if ((err == OK) && helpers.size() > 0) { + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + for (int i = 0; i < helpers.size(); i++) { + String hlp_path = helpers[i]; + err = da->copy(hlp_path, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file()); + if (err == OK && sign_enabled) { + err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), hlp_ent_path); + } + FileAccess::set_unix_permissions(tmp_app_path_name + "/Contents/Helpers/" + hlp_path.get_file(), 0755); + } } if (err == OK) { - DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { String src_path = ProjectSettings::get_singleton()->globalize_path(shared_objects[i].path); if (da->dir_exists(src_path)) { @@ -842,7 +884,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(), ent_path); } } - memdelete(da); } if (sign_enabled) { @@ -903,6 +944,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = _notarize(p_preset, p_path); } + // Clean up temporary entitlements files. + DirAccess::remove_file_or_error(hlp_ent_path); + // Clean up temporary .app dir. tmp_app_dir->change_dir(tmp_app_path_name); tmp_app_dir->erase_contents_recursive(); @@ -916,7 +960,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { String dir = p_root_path.plus_file(p_folder); - DirAccess *da = DirAccess::open(dir); + DirAccessRef da = DirAccess::open(dir); da->list_dir_begin(); String f; while ((f = da->get_next()) != "") { @@ -967,7 +1011,7 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); } else { - bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)); + bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers"); OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); @@ -1012,7 +1056,6 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String } } da->list_dir_end(); - memdelete(da); } bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index df41ccd892..a52436a70a 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -82,6 +82,7 @@ public: virtual String get_data_path() const override; virtual String get_cache_path() const override; virtual String get_bundle_resource_dir() const override; + virtual String get_bundle_icon_path() const override; virtual String get_godot_dir_name() const override; virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c6e35fee83..e9cb46ed21 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -314,17 +314,27 @@ String OS_OSX::get_name() const { return "macOS"; } +_FORCE_INLINE_ String _get_framework_executable(const String p_path) { + // Append framework executable name, or return as is if p_path is not a framework. + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { + return p_path.plus_file(p_path.get_file().get_basename()); + } else { + return p_path; + } +} + Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - String path = p_path; + String path = _get_framework_executable(p_path); if (!FileAccess::exists(path)) { - //this code exists so gdnative can load .dylib files from within the executable path - path = get_executable_path().get_base_dir().plus_file(p_path.get_file()); + // This code exists so gdnative can load .dylib files from within the executable path. + path = _get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); } if (!FileAccess::exists(path)) { - //this code exists so gdnative can load .dylib files from a standard macOS location - path = get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file()); + // This code exists so gdnative can load .dylib files from a standard macOS location. + path = _get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); } p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); @@ -379,14 +389,26 @@ String OS_OSX::get_cache_path() const { } String OS_OSX::get_bundle_resource_dir() const { + String ret; + NSBundle *main = [NSBundle mainBundle]; - NSString *resourcePath = [main resourcePath]; + if (main) { + NSString *resourcePath = [main resourcePath]; + ret.parse_utf8([resourcePath UTF8String]); + } + return ret; +} - char *utfs = strdup([resourcePath UTF8String]); +String OS_OSX::get_bundle_icon_path() const { String ret; - ret.parse_utf8(utfs); - free(utfs); + NSBundle *main = [NSBundle mainBundle]; + if (main) { + NSString *iconPath = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; + if (iconPath) { + ret.parse_utf8([iconPath UTF8String]); + } + } return ret; } diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 28922a4f59..f31b43cd49 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -72,6 +72,7 @@ def configure(env): env.Append(CCFLAGS=["/Zi"]) env.Append(CCFLAGS=["/MDd"]) env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) env.Append(LINKFLAGS=["/DEBUG"]) diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index 6ac5b55156..daf3c14f3b 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -28,14 +28,12 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -// Must include Winsock before windows.h (included by os_uwp.h) -#include "drivers/unix/net_socket_posix.h" - #include "os_uwp.h" #include "core/config/project_settings.h" #include "core/io/marshalls.h" #include "drivers/unix/ip_unix.h" +#include "drivers/unix/net_socket_posix.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" #include "drivers/windows/mutex_windows.h" diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index c9b2600c8e..7945f409a1 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -45,6 +45,7 @@ #include <fcntl.h> #include <io.h> #include <stdio.h> +#define WIN32_LEAN_AND_MEAN #include <windows.h> class OS_UWP : public OS { diff --git a/platform/windows/context_gl_windows.h b/platform/windows/context_gl_windows.h index c8e8a0891d..feff1d825b 100644 --- a/platform/windows/context_gl_windows.h +++ b/platform/windows/context_gl_windows.h @@ -38,6 +38,7 @@ #include "core/error/error_list.h" #include "core/os/os.h" +#define WIN32_LEAN_AND_MEAN #include <windows.h> typedef bool(APIENTRY *PFNWGLSWAPINTERVALEXTPROC)(int interval); diff --git a/platform/windows/crash_handler_windows.h b/platform/windows/crash_handler_windows.h index e1ec8e6787..5cdc6d3e05 100644 --- a/platform/windows/crash_handler_windows.h +++ b/platform/windows/crash_handler_windows.h @@ -31,6 +31,7 @@ #ifndef CRASH_HANDLER_WINDOWS_H #define CRASH_HANDLER_WINDOWS_H +#define WIN32_LEAN_AND_MEAN #include <windows.h> // Crash handler exception only enabled with MSVC diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 3961480d23..6f99368e99 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -208,6 +208,7 @@ def configure_msvc(env, manual_msvc_config): elif env["target"] == "debug": env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od", "/EHsc"]) env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) + env.AppendUnique(CPPDEFINES=["DEV_ENABLED"]) env.Append(LINKFLAGS=["/DEBUG"]) if env["debug_symbols"]: @@ -362,6 +363,7 @@ def configure_mingw(env): elif env["target"] == "debug": env.Append(CCFLAGS=["-g3"]) env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + env.Append(CPPDEFINES=["DEV_ENABLED"]) if env["windows_subsystem"] == "gui": env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 6402702415..905c4142a8 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -38,7 +38,6 @@ #include <avrt.h> -#ifdef DEBUG_ENABLED static String format_error_message(DWORD id) { LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -50,7 +49,6 @@ static String format_error_message(DWORD id) { return msg; } -#endif // DEBUG_ENABLED bool DisplayServerWindows::has_feature(Feature p_feature) const { switch (p_feature) { @@ -454,8 +452,8 @@ Vector<DisplayServer::WindowID> DisplayServerWindows::get_window_list() const { _THREAD_SAFE_METHOD_ Vector<DisplayServer::WindowID> ret; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<WindowID, WindowData> &E : windows) { + ret.push_back(E.key); } return ret; } @@ -465,9 +463,9 @@ DisplayServer::WindowID DisplayServerWindows::get_window_at_screen_position(cons p.x = p_position.x; p.y = p_position.y; HWND hwnd = WindowFromPoint(p); - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().hWnd == hwnd) { - return E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (E.value.hWnd == hwnd) { + return E.key; } } @@ -1131,8 +1129,8 @@ bool DisplayServerWindows::window_can_draw(WindowID p_window) const { bool DisplayServerWindows::can_any_window_draw() const { _THREAD_SAFE_METHOD_ - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (!E->get().minimized) { + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (!E.value.minimized) { return true; } } @@ -1210,7 +1208,7 @@ void DisplayServerWindows::cursor_set_shape(CursorShape p_shape) { IDC_CROSS, IDC_WAIT, IDC_APPSTARTING, - IDC_ARROW, + IDC_SIZEALL, IDC_ARROW, IDC_NO, IDC_SIZENS, @@ -1476,6 +1474,42 @@ String DisplayServerWindows::keyboard_get_layout_language(int p_index) const { return String::utf16((const char16_t *)buf).substr(0, 2); } +Key DisplayServerWindows::keyboard_get_keycode_from_physical(Key p_keycode) const { + unsigned int modifiers = p_keycode & KEY_MODIFIER_MASK; + Key keycode_no_mod = (Key)(p_keycode & KEY_CODE_MASK); + + if (keycode_no_mod == KEY_PRINT || + keycode_no_mod == KEY_KP_ADD || + keycode_no_mod == KEY_KP_5 || + (keycode_no_mod >= KEY_0 && keycode_no_mod <= KEY_9)) { + return p_keycode; + } + + unsigned int scancode = KeyMappingWindows::get_scancode(keycode_no_mod); + if (scancode == 0) { + return p_keycode; + } + + HKL current_layout = GetKeyboardLayout(0); + UINT vk = MapVirtualKeyEx(scancode, MAPVK_VSC_TO_VK, current_layout); + if (vk == 0) { + return p_keycode; + } + + UINT char_code = MapVirtualKeyEx(vk, MAPVK_VK_TO_CHAR, current_layout) & 0x7FFF; + // Unlike a similar Linux/BSD check which matches full Latin-1 range, + // we limit these to ASCII to fix some layouts, including Arabic ones + if (char_code >= 32 && char_code <= 127) { + // Godot uses 'braces' instead of 'brackets' + if (char_code == KEY_BRACKETLEFT || char_code == KEY_BRACKETRIGHT) { + char_code += 32; + } + return (Key)(char_code | modifiers); + } + + return (Key)(KeyMappingWindows::get_keysym(vk) | modifiers); +} + String _get_full_layout_name_from_registry(HKL p_layout) { String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0"); String ret; @@ -1816,8 +1850,8 @@ void DisplayServerWindows::_dispatch_input_event(const Ref<InputEvent> &p_event) callable.call((const Variant **)&evp, 1, ret, ce); } else { // Send to all windows. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Callable callable = E->get().input_event_callback; + for (const KeyValue<WindowID, WindowData> &E : windows) { + const Callable callable = E.value.input_event_callback; if (callable.is_null()) { continue; } @@ -1844,9 +1878,9 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA bool window_created = false; // Check whether window exists. - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().hWnd == hWnd) { - window_id = E->key(); + for (const KeyValue<WindowID, WindowData> &E : windows) { + if (E.value.hWnd == hWnd) { + window_id = E.key; window_created = true; break; } @@ -1882,8 +1916,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA ReleaseCapture(); // Release every touch to avoid sticky points. - for (Map<int, Vector2>::Element *E = touch_state.front(); E; E = E->next()) { - _touch_event(window_id, false, E->get().x, E->get().y, E->key()); + for (const KeyValue<int, Vector2> &E : touch_state) { + _touch_event(window_id, false, E.value.x, E.value.y, E.key); } touch_state.clear(); @@ -2943,8 +2977,8 @@ void DisplayServerWindows::_process_key_events() { } void DisplayServerWindows::_update_tablet_ctx(const String &p_old_driver, const String &p_new_driver) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - WindowData &wd = E->get(); + for (KeyValue<WindowID, WindowData> &E : windows) { + WindowData &wd = E.value; wd.block_mm = false; if ((p_old_driver == "wintab") && wintab_available && wd.wtctx) { wintab_WTEnable(wd.wtctx, false); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index c02a90c543..6a90b28579 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -63,6 +63,7 @@ #include <fcntl.h> #include <io.h> #include <stdio.h> +#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <windowsx.h> @@ -543,6 +544,7 @@ public: 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 int tablet_get_driver_count() const override; virtual String tablet_get_driver_name(int p_driver) const override; diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp index db99d6c122..8016d20470 100644 --- a/platform/windows/key_mapping_windows.cpp +++ b/platform/windows/key_mapping_windows.cpp @@ -345,6 +345,16 @@ unsigned int KeyMappingWindows::get_keysym(unsigned int p_code) { return KEY_UNKNOWN; } +unsigned int KeyMappingWindows::get_scancode(Key p_keycode) { + for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { + if (_scancode_to_keycode[i].keysym == p_keycode) { + return _scancode_to_keycode[i].keycode; + } + } + + return 0; +} + unsigned int KeyMappingWindows::get_scansym(unsigned int p_code, bool p_extended) { unsigned int keycode = KEY_UNKNOWN; for (int i = 0; _scancode_to_keycode[i].keysym != KEY_UNKNOWN; i++) { diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h index fb07227014..d056e88f06 100644 --- a/platform/windows/key_mapping_windows.h +++ b/platform/windows/key_mapping_windows.h @@ -33,8 +33,8 @@ #include "core/os/keyboard.h" +#define WIN32_LEAN_AND_MEAN #include <windows.h> - #include <winuser.h> class KeyMappingWindows { @@ -42,6 +42,7 @@ class KeyMappingWindows { public: static unsigned int get_keysym(unsigned int p_code); + static unsigned int get_scancode(Key p_keycode); static unsigned int get_scansym(unsigned int p_code, bool p_extended); static bool is_extended_key(unsigned int p_code); }; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 78b7be8a30..6c02bf1e16 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -28,15 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -// Must include Winsock before windows.h (included by os_windows.h) -#include "drivers/unix/net_socket_posix.h" - #include "os_windows.h" #include "core/debugger/engine_debugger.h" #include "core/debugger/script_debugger.h" #include "core/io/marshalls.h" #include "core/version_generated.gen.h" +#include "drivers/unix/net_socket_posix.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" #include "joypad_windows.h" @@ -75,7 +73,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #define GetProcAddress (void *)GetProcAddress #endif -#ifdef DEBUG_ENABLED static String format_error_message(DWORD id) { LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -87,7 +84,6 @@ static String format_error_message(DWORD id) { return msg; } -#endif // DEBUG_ENABLED void RedirectIOToConsole() { int hConHandle; @@ -681,18 +677,27 @@ String OS_Windows::get_data_path() const { } String OS_Windows::get_cache_path() const { - // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. - if (has_environment("XDG_CACHE_HOME")) { - if (get_environment("XDG_CACHE_HOME").is_absolute_path()) { - return get_environment("XDG_CACHE_HOME").replace("\\", "/"); - } else { - WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `%TEMP%` or `get_config_path()` per the XDG Base Directory specification."); + static String cache_path_cache; + if (cache_path_cache.is_empty()) { + // The XDG Base Directory specification technically only applies on Linux/*BSD, but it doesn't hurt to support it on Windows as well. + if (has_environment("XDG_CACHE_HOME")) { + if (get_environment("XDG_CACHE_HOME").is_absolute_path()) { + cache_path_cache = get_environment("XDG_CACHE_HOME").replace("\\", "/"); + } else { + WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `%LOCALAPPDATA%\\cache`, `%TEMP%` or `get_config_path()` per the XDG Base Directory specification."); + } + } + if (cache_path_cache.is_empty() && has_environment("LOCALAPPDATA")) { + cache_path_cache = get_environment("LOCALAPPDATA").replace("\\", "/"); + } + if (cache_path_cache.is_empty() && has_environment("TEMP")) { + cache_path_cache = get_environment("TEMP").replace("\\", "/"); + } + if (cache_path_cache.is_empty()) { + cache_path_cache = get_config_path(); } } - if (has_environment("TEMP")) { - return get_environment("TEMP").replace("\\", "/"); - } - return get_config_path(); + return cache_path_cache; } // Get properly capitalized engine name for system paths diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index c4a2eda8f4..d24afa91eb 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -57,7 +57,9 @@ #include <fcntl.h> #include <io.h> +#include <shellapi.h> #include <stdio.h> +#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <windowsx.h> diff --git a/platform/windows/vulkan_context_win.h b/platform/windows/vulkan_context_win.h index 39dd2641fd..61e66b8ae0 100644 --- a/platform/windows/vulkan_context_win.h +++ b/platform/windows/vulkan_context_win.h @@ -32,6 +32,8 @@ #define VULKAN_DEVICE_WIN_H #include "drivers/vulkan/vulkan_context.h" + +#define WIN32_LEAN_AND_MEAN #include <windows.h> class VulkanContextWindows : public VulkanContext { diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp index 8cab7ca521..e54a61fdfd 100644 --- a/platform/windows/windows_terminal_logger.cpp +++ b/platform/windows/windows_terminal_logger.cpp @@ -33,6 +33,7 @@ #ifdef WINDOWS_ENABLED #include <stdio.h> +#define WIN32_LEAN_AND_MEAN #include <windows.h> void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_err) { |