diff options
Diffstat (limited to 'platform')
53 files changed, 1349 insertions, 264 deletions
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index b37f04c4f8..6d021ad33a 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -598,7 +598,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file()); Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); - ERR_FAIL_COND_V(store_err, store_err); + ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); } } if (!exported) { @@ -766,17 +766,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { uint32_t attr_value = decode_uint32(&p_manifest[iofs + 8]); uint32_t attr_resid = decode_uint32(&p_manifest[iofs + 16]); - String value; - if (attr_value != 0xFFFFFFFF) - value = string_table[attr_value]; - else - value = "Res #" + itos(attr_resid); + const String value = (attr_value != 0xFFFFFFFF) ? string_table[attr_value] : "Res #" + itos(attr_resid); String attrname = string_table[attr_name]; - String nspace; - if (attr_nspace != 0xFFFFFFFF) - nspace = string_table[attr_nspace]; - else - nspace = ""; + const String nspace = (attr_nspace != 0xFFFFFFFF) ? string_table[attr_nspace] : ""; //replace project information if (tname == "manifest" && attrname == "package") { @@ -828,14 +820,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { encode_uint32(min_gles3 ? 0x00030000 : 0x00020000, &p_manifest.write[iofs + 16]); } - if (tname == "meta-data" && attrname == "name" && string_table[attr_value] == "xr_mode_metadata_name") { + // FIXME: `attr_value != 0xFFFFFFFF` below added as a stopgap measure for GH-32553, + // but the issue should be debugged further and properly addressed. + if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") { // Update the meta-data 'android:name' attribute based on the selected XR mode. if (xr_mode_index == 1 /* XRMode.OVR */) { string_table.write[attr_value] = "com.samsung.android.vr.application.mode"; } } - if (tname == "meta-data" && attrname == "value" && string_table[attr_value] == "xr_mode_metadata_value") { + if (tname == "meta-data" && attrname == "value" && value == "xr_mode_metadata_value") { // Update the meta-data 'android:value' attribute based on the selected XR mode. if (xr_mode_index == 1 /* XRMode.OVR */) { string_table.write[attr_value] = "vr_only"; @@ -1290,7 +1284,7 @@ public: r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/xr_mode", PROPERTY_HINT_ENUM, "Regular,Oculus Mobile VR"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "graphics/degrees_of_freedom", PROPERTY_HINT_ENUM, "None,3DOF and 6DOF,6DOF"), 0)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "graphics/32_bits_framebuffer"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "one_click_deploy/clear_previous_install"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_package/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_package/use_custom_build"), false)); @@ -1351,7 +1345,7 @@ public: return logo; } - virtual bool poll_devices() { + virtual bool poll_export() { bool dc = devices_changed; if (dc) { @@ -1361,7 +1355,7 @@ public: return dc; } - virtual int get_device_count() const { + virtual int get_options_count() const { device_lock->lock(); int dc = devices.size(); @@ -1370,20 +1364,31 @@ public: return dc; } - virtual String get_device_name(int p_device) const { + virtual String get_options_tooltip() const { - ERR_FAIL_INDEX_V(p_device, devices.size(), ""); + return TTR("Select device from the list"); + } + + virtual String get_option_label(int p_index) const { + + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); device_lock->lock(); - String s = devices[p_device].name; + String s = devices[p_index].name; device_lock->unlock(); return s; } - virtual String get_device_info(int p_device) const { + virtual String get_option_tooltip(int p_index) const { - ERR_FAIL_INDEX_V(p_device, devices.size(), ""); + ERR_FAIL_INDEX_V(p_index, devices.size(), ""); device_lock->lock(); - String s = devices[p_device].description; + String s = devices[p_index].description; + if (devices.size() == 1) { + // Tooltip will be: + // Name + // Description + s = devices[p_index].name + "\n\n" + s; + } device_lock->unlock(); return s; } @@ -1670,7 +1675,7 @@ public: DirAccessRef da = DirAccess::open("res://android"); - ERR_FAIL_COND(!da); + ERR_FAIL_COND_MSG(!da, "Cannot open directory 'res://android'."); Map<String, List<String> > directory_paths; Map<String, List<String> > manifest_sections; Map<String, List<String> > gradle_sections; @@ -1875,7 +1880,7 @@ public: new_file += "<!--CHUNK_" + text + "_BEGIN-->\n"; if (!found) { - ERR_PRINTS("No end marker found in AndroidManifest.conf for chunk: " + text); + ERR_PRINTS("No end marker found in AndroidManifest.xml for chunk: " + text); f->seek(pos); } else { //add chunk lines @@ -1894,7 +1899,7 @@ public: String last_tag = "android:icon=\"@drawable/icon\""; int last_tag_pos = l.find(last_tag); if (last_tag_pos == -1) { - WARN_PRINTS("No adding of application tags because could not find last tag for <application: " + last_tag); + ERR_PRINTS("Not adding application attributes as the expected tag was not found in '<application': " + last_tag); new_file += l + "\n"; } else { String base = l.substr(0, last_tag_pos + last_tag.length()); @@ -1946,7 +1951,7 @@ public: //build project if custom build is enabled String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); - ERR_FAIL_COND_V(sdk_path == "", ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(sdk_path == "", ERR_UNCONFIGURED, "Android SDK path must be configured in Editor Settings at 'export/android/custom_build_sdk_path'."); _update_custom_build_project(); @@ -1980,9 +1985,9 @@ public: return ERR_CANT_CREATE; } if (p_debug) { - src_apk = build_path.plus_file("build/outputs/apk/debug/build-debug-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); } else { - src_apk = build_path.plus_file("build/outputs/apk/release/build-release-unsigned.apk"); + src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); } if (!FileAccess::exists(src_apk)) { diff --git a/platform/android/file_access_jandroid.cpp b/platform/android/file_access_jandroid.cpp index 5b8cf01138..d4c2a23aa0 100644 --- a/platform/android/file_access_jandroid.cpp +++ b/platform/android/file_access_jandroid.cpp @@ -94,13 +94,13 @@ void FileAccessJAndroid::seek(size_t p_position) { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND(!is_open()); + ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); env->CallVoidMethod(io, _file_seek, id, p_position); } void FileAccessJAndroid::seek_end(int64_t p_position) { - ERR_FAIL_COND(!is_open()); + ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); seek(get_len()); } @@ -108,34 +108,34 @@ void FileAccessJAndroid::seek_end(int64_t p_position) { size_t FileAccessJAndroid::get_position() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_tell, id); } size_t FileAccessJAndroid::get_len() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_get_size, id); } bool FileAccessJAndroid::eof_reached() const { JNIEnv *env = ThreadAndroid::get_env(); - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); return env->CallIntMethod(io, _file_eof, id); } uint8_t FileAccessJAndroid::get_8() const { - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); uint8_t byte; get_buffer(&byte, 1); return byte; } int FileAccessJAndroid::get_buffer(uint8_t *p_dst, int p_length) const { - ERR_FAIL_COND_V(!is_open(), 0); + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); if (p_length == 0) return 0; JNIEnv *env = ThreadAndroid::get_env(); diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index d5f4ba18d6..ba01ec313b 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -26,11 +26,8 @@ <!-- Any tag in this line after android:icon will be erased when doing custom builds. --> <!-- If you want to add tags manually, do before it. --> - <application - android:label="@string/godot_project_name_string" - android:allowBackup="false" - tools:ignore="GoogleAppIndexingWarning" - android:icon="@drawable/icon" > + <!-- WARNING: This should stay on a single line until the parsing code is improved. See GH-32414. --> + <application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon" > <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> <!-- Do these changes in the export preset. Adding new ones is fine. --> diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index fabd7b1dbb..d7469a8765 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -1,3 +1,33 @@ +/*************************************************************************/ +/* GodotApp.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 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. */ +/*************************************************************************/ + package com.godot.game; import org.godotengine.godot.Godot; diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 99ffa937b0..2052017888 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -20,14 +20,33 @@ allprojects { } } -def binDir = "../../../bin/" +ext { + sconsExt = org.gradle.internal.os.OperatingSystem.current().isWindows() ? ".bat" : "" + + supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] + supportedTargets = ['release':"release", 'debug':"release_debug"] + + // Used by gradle to specify which architecture to build for by default when running `./gradlew build`. + // This command is usually used by Android Studio. + // If building manually on the command line, it's recommended to use the + // `./gradlew generateGodotTemplates` build command instead after running the `scons` command. + // The defaultAbi must be one of the {supportedAbis} values. + defaultAbi = "arm64v8" +} + +def rootDir = "../../.." +def binDir = "$rootDir/bin/" + +def getSconsTaskName(String buildType) { + return "compileGodotNativeLibs" + buildType.capitalize() +} /** * Copy the generated 'android_debug.apk' binary template into the Godot bin directory. * Depends on the app build task to ensure the binary is generated prior to copying. */ task copyDebugBinaryToBin(type: Copy) { - dependsOn ':app:build' + dependsOn ':app:assembleDebug' from('app/build/outputs/apk/debug') into(binDir) include('android_debug.apk') @@ -38,7 +57,7 @@ task copyDebugBinaryToBin(type: Copy) { * Depends on the app build task to ensure the binary is generated prior to copying. */ task copyReleaseBinaryToBin(type: Copy) { - dependsOn ':app:build' + dependsOn ':app:assembleRelease' from('app/build/outputs/apk/release') into(binDir) include('android_release.apk') @@ -49,7 +68,7 @@ task copyReleaseBinaryToBin(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyDebugAAR(type: Copy) { - dependsOn ':lib:build' + dependsOn ':lib:assembleDebug' from('lib/build/outputs/aar') into('app/libs/debug') include('godot-lib.debug.aar') @@ -60,7 +79,7 @@ task copyDebugAAR(type: Copy) { * Depends on the library build task to ensure the AAR file is generated prior to copying. */ task copyReleaseAAR(type: Copy) { - dependsOn ':lib:build' + dependsOn ':lib:assembleRelease' from('lib/build/outputs/aar') into('app/libs/release') include('godot-lib.release.aar') @@ -72,8 +91,10 @@ task copyReleaseAAR(type: Copy) { * The zip file also includes some gradle tools to allow building of the custom build. */ task zipCustomBuild(type: Zip) { - dependsOn 'copyDebugAAR' - dependsOn 'copyReleaseAAR' + dependsOn ':generateGodotTemplates' + doFirst { + logger.lifecycle("Generating Godot custom build template") + } from(fileTree(dir: 'app', excludes: ['**/build/**', '**/.gradle/**', '**/*.iml']), fileTree(dir: '.', includes: ['gradle.properties','gradlew', 'gradlew.bat', 'gradle/**'])) include '**/*' archiveName 'android_source.zip' @@ -84,12 +105,48 @@ task zipCustomBuild(type: Zip) { * Master task used to coordinate the tasks defined above to generate the set of Godot templates. */ task generateGodotTemplates(type: GradleBuild) { - tasks = [ - // Copy the generated aar library files to the custom build directory. - 'copyDebugAAR', 'copyReleaseAAR', - // Zip the custom build directory. - 'zipCustomBuild', - // Copy the prebuilt binary templates to the bin directory. - 'copyDebugBinaryToBin', 'copyReleaseBinaryToBin', - ] + // We exclude these gradle tasks so we can run the scons command manually. + for (String buildType : supportedTargets.keySet()) { + startParameter.excludedTaskNames += ":lib:" + getSconsTaskName(buildType) + } + + tasks = [] + + // Only build the apks and aar files for which we have native shared libraries. + for (String target : supportedTargets.keySet()) { + File targetLibs = new File("lib/libs/" + target) + if (targetLibs != null && targetLibs.isDirectory()) { + File[] targetLibsContents = targetLibs.listFiles() + if (targetLibsContents != null && targetLibsContents.length > 0) { + // Copy the generated aar library files to the custom build directory. + tasks += "copy" + target.capitalize() + "AAR" + // Copy the prebuilt binary templates to the bin directory. + tasks += "copy" + target.capitalize() + "BinaryToBin" + } + } + } + + finalizedBy 'zipCustomBuild' +} + +/** + * Clean the generated artifacts. + */ +task cleanGodotTemplates(type: Delete) { + // Delete the generated native libs + delete("lib/libs") + + // Delete the library generated AAR files + delete("lib/build/outputs/aar") + + // Delete the app libs directory contents + delete("app/libs") + + // Delete the generated binary apks + delete("app/build/outputs/apk") + + // Delete the Godot templates in the Godot bin directory + delete("$binDir/android_debug.apk") + delete("$binDir/android_release.apk") + delete("$binDir/android_source.zip") } diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 6d07504e45..13a14422ed 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -5,8 +5,6 @@ dependencies { } def pathToRootDir = "../../../../" -// Note: Only keep the abis you support to speed up the gradle 'assemble' task. -def supportedAbis = ["armv7", "arm64v8", "x86", "x86_64"] android { compileSdkVersion versions.compileSdk @@ -56,27 +54,20 @@ android { // files is only setup for editing support. gradle.startParameter.excludedTaskNames += taskPrefix + "externalNativeBuild" + buildType - // Create tasks to generate the Godot native libraries. - def taskName = "compileGodotNativeLibs" + buildType - def releaseTarget = "release" - if (buildType == "Debug") { - releaseTarget += "_debug" + def releaseTarget = supportedTargets[buildType.toLowerCase()] + if (releaseTarget == null || releaseTarget == "") { + throw new GradleException("Invalid build type: " + buildType) } - def abiTaskNames = [] - // Creating gradle tasks to generate the native libraries for the supported abis. - supportedAbis.each { abi -> - def abiTaskName = taskName + abi.capitalize() - abiTaskNames += abiTaskName - tasks.create(name: abiTaskName, type: Exec) { - executable "scons" - args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${abi}" - } + if (!supportedAbis.contains(defaultAbi)) { + throw new GradleException("Invalid default abi: " + defaultAbi) } - // Creating gradle task to run all of the previously generated tasks. - tasks.create(name: taskName, type: GradleBuild) { - tasks = abiTaskNames + // Creating gradle task to generate the native libraries for the default abi. + def taskName = getSconsTaskName(buildType) + tasks.create(name: taskName, type: Exec) { + executable "scons" + sconsExt + args "--directory=${pathToRootDir}", "platform=android", "target=${releaseTarget}", "android_arch=${defaultAbi}", "-j" + Runtime.runtime.availableProcessors() } // Schedule the tasks so the generated libs are present before the aar file is packaged. diff --git a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png Binary files differindex 2c246b04a4..f849d8e90d 100644 --- a/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-hdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png Binary files differindex 8bcd464bed..1dfb28b33a 100644 --- a/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-mdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png Binary files differindex b458ff3057..302a972049 100644 --- a/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png +++ b/platform/android/java/lib/res/drawable-xxhdpi/notify_panel_notification_icon_bg.png diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 739aa285bf..4dae2dcc53 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -30,7 +30,6 @@ package org.godotengine.godot; -import android.Manifest; import android.annotation.SuppressLint; import android.app.Activity; import android.app.ActivityManager; @@ -56,12 +55,14 @@ import android.hardware.SensorManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.Looper; import android.os.Messenger; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings.Secure; import android.support.annotation.Keep; -import android.support.v4.content.ContextCompat; +import android.support.annotation.Nullable; import android.view.Display; import android.view.KeyEvent; import android.view.MotionEvent; @@ -95,14 +96,12 @@ import java.util.Locale; import javax.microedition.khronos.opengles.GL10; import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.payments.PaymentsManager; +import org.godotengine.godot.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; public abstract class Godot extends Activity implements SensorEventListener, IDownloaderClient { static final int MAX_SINGLETONS = 64; - static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; - static final int REQUEST_CAMERA_PERMISSION = 2; - static final int REQUEST_VIBRATE_PERMISSION = 3; private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -126,6 +125,9 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo private boolean activityResumed; private int mState; + // Used to dispatch events to the main thread. + private final Handler mainThreadHandler = new Handler(Looper.getMainLooper()); + static private Intent mCurrentIntent; @Override @@ -187,6 +189,20 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo Godot.singletons[Godot.singleton_count++] = this; } + /** + * Invoked once during the Godot Android initialization process after creation of the + * {@link GodotView} view. + * <p> + * This method should be overridden by descendants of this class that would like to add + * their view/layout to the Godot view hierarchy. + * + * @return the view to be included; null if no views should be included. + */ + @Nullable + protected View onMainCreateView(Activity activity) { + return null; + } + protected void onMainActivityResult(int requestCode, int resultCode, Intent data) { } @@ -306,6 +322,20 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo public void run() { GodotLib.setup(current_command_line); setKeepScreenOn("True".equals(GodotLib.getGlobal("display/window/energy_saving/keep_screen_on"))); + + // The Godot Android plugins are setup on completion of GodotLib.setup + mainThreadHandler.post(new Runnable() { + @Override + public void run() { + // Include the non-null views returned in the Godot view hierarchy. + for (int i = 0; i < singleton_count; i++) { + View view = singletons[i].onMainCreateView(Godot.this); + if (view != null) { + layout.addView(view); + } + } + } + }); } }); } @@ -973,32 +1003,15 @@ public abstract class Godot extends Activity implements SensorEventListener, IDo } public boolean requestPermission(String p_name) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // Not necessary, asked on install already - return true; - } - - if (p_name.equals("RECORD_AUDIO")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); - return false; - } - } + return PermissionsUtil.requestPermission(p_name, this); + } - if (p_name.equals("CAMERA")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); - return false; - } - } + public boolean requestPermissions() { + return PermissionsUtil.requestManifestPermissions(this); + } - if (p_name.equals("VIBRATE")) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); - return false; - } - } - return true; + public String[] getGrantedPermissions() { + return PermissionsUtil.getGrantedPermissions(this); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 067fa6f4b9..67dce172dc 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -95,6 +95,11 @@ public class GodotLib { public static native void touch(int what, int pointer, int howmany, int[] arr); /** + * Forward hover events from the main thread to the GL thread. + */ + public static native void hover(int type, int x, int y); + + /** * Forward accelerometer sensor events from the main thread to the GL thread. * @see android.hardware.SensorEventListener#onSensorChanged(SensorEvent) */ diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index a443a0ad90..2756ca6c83 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -96,7 +96,6 @@ public class GodotInputHandler implements InputDeviceListener { GodotLib.joybutton(device_id, button, false); } }); - return true; } } else { final int chr = event.getUnicodeChar(0); @@ -108,7 +107,7 @@ public class GodotInputHandler implements InputDeviceListener { }); }; - return false; + return true; } public boolean onKeyDown(final int keyCode, KeyEvent event) { @@ -142,7 +141,6 @@ public class GodotInputHandler implements InputDeviceListener { GodotLib.joybutton(device_id, button, true); } }); - return true; } } else { final int chr = event.getUnicodeChar(0); @@ -154,7 +152,7 @@ public class GodotInputHandler implements InputDeviceListener { }); }; - return false; + return true; } public boolean onGenericMotionEvent(MotionEvent event) { @@ -190,7 +188,18 @@ public class GodotInputHandler implements InputDeviceListener { } return true; } - }; + } else if ((event.getSource() & InputDevice.SOURCE_STYLUS) == InputDevice.SOURCE_STYLUS) { + final int x = Math.round(event.getX()); + final int y = Math.round(event.getY()); + final int type = event.getAction(); + queueEvent(new Runnable() { + @Override + public void run() { + GodotLib.hover(type, x, y); + } + }); + return true; + } return false; } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java new file mode 100644 index 0000000000..2c4a444e5a --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -0,0 +1,157 @@ +package org.godotengine.godot.utils; + +import android.Manifest; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import java.util.ArrayList; +import java.util.List; +import org.godotengine.godot.Godot; + +/** + * This class includes utility functions for Android permissions related operations. + * @author Cagdas Caglak <cagdascaglak@gmail.com> + */ +public final class PermissionsUtil { + + static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + static final int REQUEST_CAMERA_PERMISSION = 2; + static final int REQUEST_VIBRATE_PERMISSION = 3; + static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001; + + private PermissionsUtil() { + } + + /** + * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a> + * @param name the name of the requested permission. + * @param activity the caller activity for this method. + * @return true/false. "true" if permission was granted otherwise returns "false". + */ + public static boolean requestPermission(String name, Godot activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Not necessary, asked on install already + return true; + } + + if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); + return false; + } + + if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); + return false; + } + + if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); + return false; + } + return true; + } + + /** + * Request dangerous permissions which are defined in the Android manifest file from the user. + * @param activity the caller activity for this method. + * @return true/false. "true" if all permissions were granted otherwise returns "false". + */ + public static boolean requestManifestPermissions(Godot activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return true; + } + + String[] manifestPermissions; + try { + manifestPermissions = getManifestPermissions(activity); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + + if (manifestPermissions == null || manifestPermissions.length == 0) + return true; + + List<String> dangerousPermissions = new ArrayList<>(); + for (String manifestPermission : manifestPermissions) { + try { + PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); + int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) { + dangerousPermissions.add(manifestPermission); + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + if (dangerousPermissions.isEmpty()) { + // If list is empty, all of dangerous permissions were granted. + return true; + } + + String[] requestedPermissions = dangerousPermissions.toArray(new String[0]); + activity.requestPermissions(requestedPermissions, REQUEST_ALL_PERMISSION_REQ_CODE); + return false; + } + + /** + * With this function you can get the list of dangerous permissions that have been granted to the Android application. + * @param activity the caller activity for this method. + * @return granted permissions list + */ + public static String[] getGrantedPermissions(Godot activity) { + String[] manifestPermissions; + try { + manifestPermissions = getManifestPermissions(activity); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return new String[0]; + } + if (manifestPermissions == null || manifestPermissions.length == 0) + return new String[0]; + + List<String> dangerousPermissions = new ArrayList<>(); + for (String manifestPermission : manifestPermissions) { + try { + PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission); + int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel; + if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) == PackageManager.PERMISSION_GRANTED) { + dangerousPermissions.add(manifestPermission); + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return new String[0]; + } + } + + return dangerousPermissions.toArray(new String[0]); + } + + /** + * Returns the permissions defined in the AndroidManifest.xml file. + * @param activity the caller activity for this method. + * @return manifest permissions list + * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. + */ + private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException { + PackageManager packageManager = activity.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); + return packageInfo.requestedPermissions; + } + + /** + * Returns the information of the desired permission. + * @param activity the caller activity for this method. + * @param permission the name of the permission. + * @return permission info object + * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. + */ + private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException { + PackageManager packageManager = activity.getPackageManager(); + return packageManager.getPermissionInfo(permission, 0); + } +} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 30676783db..a14e0a1960 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -826,6 +826,13 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jo */ } +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y) { + if (step == 0) + return; + + os_android->process_hover(p_type, Point2(p_x, p_y)); +} + /* * Android Key codes. */ @@ -1386,6 +1393,10 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu if (permission == "android.permission.RECORD_AUDIO" && p_result) { AudioDriver::get_singleton()->capture_start(); } + + if (os_android->get_main_loop()) { + os_android->get_main_loop()->emit_signal("on_request_permissions_result", permission, p_result == JNI_TRUE); + } } JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz) { diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 11bda94f8d..a564bbd4a1 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -45,6 +45,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *en JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_back(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_touch(JNIEnv *env, jobject obj, jint ev, jint pointer, jint count, jintArray positions); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_hover(JNIEnv *env, jobject obj, jint p_type, jint p_x, jint p_y); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv *env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv *env, jobject obj, jint p_device, jint p_button, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv *env, jobject obj, jint p_device, jint p_axis, jfloat p_value); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 8194ee6ecf..e3e613d30b 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -59,6 +59,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); + _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z"); + _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;"); _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); @@ -199,6 +201,34 @@ bool GodotJavaWrapper::request_permission(const String &p_name) { } } +bool GodotJavaWrapper::request_permissions() { + if (_request_permissions) { + JNIEnv *env = ThreadAndroid::get_env(); + return env->CallBooleanMethod(godot_instance, _request_permissions); + } else { + return false; + } +} + +Vector<String> GodotJavaWrapper::get_granted_permissions() const { + Vector<String> permissions_list; + if (_get_granted_permissions) { + JNIEnv *env = ThreadAndroid::get_env(); + jobject permissions_object = env->CallObjectMethod(godot_instance, _get_granted_permissions); + jobjectArray *arr = reinterpret_cast<jobjectArray *>(&permissions_object); + + int i = 0; + jsize len = env->GetArrayLength(*arr); + for (i = 0; i < len; i++) { + jstring jstr = (jstring)env->GetObjectArrayElement(*arr, i); + String str = jstring_to_string(jstr, env); + permissions_list.push_back(str); + env->DeleteLocalRef(jstr); + } + } + return permissions_list; +} + void GodotJavaWrapper::init_input_devices() { if (_init_input_devices) { JNIEnv *env = ThreadAndroid::get_env(); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index b1bd9b7f48..d23ff273cb 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -54,6 +54,8 @@ private: jmethodID _get_clipboard = 0; jmethodID _set_clipboard = 0; jmethodID _request_permission = 0; + jmethodID _request_permissions = 0; + jmethodID _get_granted_permissions = 0; jmethodID _init_input_devices = 0; jmethodID _get_surface = 0; jmethodID _is_activity_resumed = 0; @@ -81,6 +83,8 @@ public: bool has_set_clipboard(); void set_clipboard(const String &p_text); bool request_permission(const String &p_name); + bool request_permissions(); + Vector<String> get_granted_permissions() const; void init_input_devices(); jobject get_surface(); bool is_activity_resumed(); diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex ba2a0e366a..df445f6a9c 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 49ab0ea84a..defee8f1f1 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -220,6 +220,16 @@ bool OS_Android::request_permission(const String &p_name) { return godot_java->request_permission(p_name); } +bool OS_Android::request_permissions() { + + return godot_java->request_permissions(); +} + +Vector<String> OS_Android::get_granted_permissions() const { + + return godot_java->get_granted_permissions(); +} + Error OS_Android::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { p_library_handle = dlopen(p_path.utf8().get_data(), RTLD_NOW); ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + dlerror() + "."); @@ -477,6 +487,23 @@ void OS_Android::process_touch(int p_what, int p_pointer, const Vector<TouchPos> } } +void OS_Android::process_hover(int p_type, Point2 p_pos) { + // https://developer.android.com/reference/android/view/MotionEvent.html#ACTION_HOVER_ENTER + switch (p_type) { + case 7: // hover move + case 9: // hover enter + case 10: { // hover exit + Ref<InputEventMouseMotion> ev; + ev.instance(); + ev->set_position(p_pos); + ev->set_global_position(p_pos); + ev->set_relative(p_pos - hover_prev_pos); + input->parse_input_event(ev); + hover_prev_pos = p_pos; + } break; + } +} + void OS_Android::process_accelerometer(const Vector3 &p_accelerometer) { input->set_accelerometer(p_accelerometer); diff --git a/platform/android/os_android.h b/platform/android/os_android.h index a17941f7c0..a290c0cedd 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -70,6 +70,7 @@ public: private: Vector<TouchPos> touch; + Point2 hover_prev_pos; // needed to calculate the relative position on hover events bool use_gl2; bool use_apk_expansion; @@ -124,6 +125,8 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual bool request_permission(const String &p_name); + virtual bool request_permissions(); + virtual Vector<String> get_granted_permissions() const; virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); @@ -186,6 +189,7 @@ public: void process_magnetometer(const Vector3 &p_magnetometer); void process_gyroscope(const Vector3 &p_gyroscope); void process_touch(int p_what, int p_pointer, const Vector<TouchPos> &p_points); + void process_hover(int p_type, Point2 p_pos); void process_joy_event(JoypadEvent p_event); void process_event(Ref<InputEvent> p_event); void init_video_mode(int p_video_width, int p_video_height); diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 99fbe989df..baae13c53d 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -487,7 +487,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr String sizes; DirAccess *da = DirAccess::open(p_iconset_dir); - ERR_FAIL_COND_V(!da, ERR_CANT_OPEN); + ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_iconset_dir + "'."); for (uint64_t i = 0; i < (sizeof(icon_infos) / sizeof(icon_infos[0])); ++i) { IconInfo info = icon_infos[i]; @@ -537,7 +537,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { DirAccess *da = DirAccess::open(p_dest_dir); - ERR_FAIL_COND_V(!da, ERR_CANT_OPEN); + ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { LoadingScreenInfo info = loading_screen_infos[i]; @@ -546,7 +546,7 @@ Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPre Error err = da->copy(loading_screen_file, p_dest_dir + info.export_name); if (err) { memdelete(da); - String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path: " + loading_screen_file; + String err_str = String("Failed to export loading screen (") + info.preset_key + ") from path '" + loading_screen_file + "'."; ERR_PRINT(err_str.utf8().get_data()); return err; } @@ -757,7 +757,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) { DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(!filesystem_da, ERR_CANT_CREATE); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { String asset = p_assets[f_idx]; if (!asset.begins_with("res://")) { diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h index 4fb721f159..e3c9d212f0 100644 --- a/platform/iphone/gl_view.h +++ b/platform/iphone/gl_view.h @@ -79,8 +79,6 @@ @property(strong, nonatomic) AVPlayer *avPlayer; @property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; -// Old videoplayer properties -@property(strong, nonatomic) MPMoviePlayerController *moviePlayerController; @property(strong, nonatomic) UIWindow *backgroundWindow; @property(nonatomic) UITextAutocorrectionType autocorrectionType; diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm index 4641b2c4ac..4e6b8e1ada 100644 --- a/platform/iphone/gl_view.mm +++ b/platform/iphone/gl_view.mm @@ -337,12 +337,10 @@ static void clear_touches() { // the same size as our display area. - (void)layoutSubviews { - //printf("HERE\n"); [EAGLContext setCurrentContext:context]; [self destroyFramebuffer]; [self createFramebuffer]; [self drawView]; - [self drawView]; } - (BOOL)createFramebuffer { @@ -458,23 +456,23 @@ static void clear_touches() { // Updates the OpenGL view when the timer fires - (void)drawView { + + if (!active) { + printf("draw view not active!\n"); + return; + }; if (useCADisplayLink) { // Pause the CADisplayLink to avoid recursion [displayLink setPaused:YES]; // Process all input events - while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE) == kCFRunLoopRunHandledSource) + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) ; // We are good to go, resume the CADisplayLink [displayLink setPaused:NO]; } - if (!active) { - printf("draw view not active!\n"); - return; - }; - // Make sure that you are drawing to the current context [EAGLContext setCurrentContext:context]; @@ -732,40 +730,4 @@ static void clear_touches() { _stop_video(); } -/* -- (void)moviePlayBackDidFinish:(NSNotification*)notification { - - - NSNumber* reason = [[notification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey]; - switch ([reason intValue]) { - case MPMovieFinishReasonPlaybackEnded: - //NSLog(@"Playback Ended"); - break; - case MPMovieFinishReasonPlaybackError: - //NSLog(@"Playback Error"); - video_found_error = true; - break; - case MPMovieFinishReasonUserExited: - //NSLog(@"User Exited"); - video_found_error = true; - break; - default: - //NSLog(@"Unsupported reason!"); - break; - } - - MPMoviePlayerController *player = [notification object]; - - [[NSNotificationCenter defaultCenter] - removeObserver:self - name:MPMoviePlayerPlaybackDidFinishNotification - object:player]; - - [_instance.moviePlayerController stop]; - [_instance.moviePlayerController.view removeFromSuperview]; - - video_playing = false; -} -*/ - @end diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.cpp index db93db5021..992da2818b 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.cpp @@ -47,7 +47,7 @@ int iphone_main(int, int, int, char **, String); int iphone_main(int width, int height, int argc, char **argv, String data_dir) { - int len = strlen(argv[0]); + size_t len = strlen(argv[0]); while (len--) { if (argv[0][len] == '/') break; diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 490e84c571..8eef430621 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -92,7 +92,7 @@ void InAppStore::_bind_methods() { PoolStringArray localized_prices; PoolStringArray currency_codes; - for (int i = 0; i < [products count]; i++) { + for (NSUInteger i = 0; i < [products count]; i++) { SKProduct *product = [products objectAtIndex:i]; diff --git a/platform/iphone/ios.h b/platform/iphone/ios.h index 91c4725b35..ef45fc7ac3 100644 --- a/platform/iphone/ios.h +++ b/platform/iphone/ios.h @@ -42,6 +42,7 @@ class iOS : public Object { public: static void alert(const char *p_alert, const char *p_title); + String get_model() const; String get_rate_url(int p_app_id) const; iOS(); diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 686422ceb2..6e986df493 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -29,6 +29,7 @@ /*************************************************************************/ #include "ios.h" +#include <sys/sysctl.h> #import <UIKit/UIKit.h> @@ -42,6 +43,21 @@ void iOS::alert(const char *p_alert, const char *p_title) { [alert show]; } +String iOS::get_model() const { + // [[UIDevice currentDevice] model] only returns "iPad" or "iPhone". + size_t size; + sysctlbyname("hw.machine", NULL, &size, NULL, 0); + char *model = (char *)malloc(size); + if (model == NULL) { + return ""; + } + sysctlbyname("hw.machine", model, &size, NULL, 0); + NSString *platform = [NSString stringWithCString:model encoding:NSUTF8StringEncoding]; + free(model); + const char *str = [platform UTF8String]; + return String(str != NULL ? str : ""); +} + String iOS::get_rate_url(int p_app_id) const { String templ = "itms-apps://ax.itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?type=Purple+Software&id=APP_ID"; String templ_iOS7 = "itms-apps://itunes.apple.com/app/idAPP_ID"; diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp index 353078c51c..83b0660ef7 100644 --- a/platform/iphone/os_iphone.cpp +++ b/platform/iphone/os_iphone.cpp @@ -47,8 +47,6 @@ #include "semaphore_iphone.h" -#include "ios.h" - #include <dlfcn.h> int OSIPhone::get_video_driver_count() const { @@ -184,7 +182,8 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); //icloud->connect(); #endif - Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", memnew(iOS))); + ios = memnew(iOS); + Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); return OK; }; @@ -507,6 +506,15 @@ String OSIPhone::get_name() const { return "iOS"; }; +String OSIPhone::get_model_name() const { + + String model = ios->get_model(); + if (model != "") + return model; + + return OS_Unix::get_model_name(); +} + Size2 OSIPhone::get_window_size() const { return Vector2(video_mode.width, video_mode.height); diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 804ba0b1af..63799bbae8 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -41,6 +41,7 @@ #include "game_center.h" #include "icloud.h" #include "in_app_store.h" +#include "ios.h" #include "main/input_default.h" #include "servers/audio_server.h" #include "servers/visual/rasterizer.h" @@ -72,6 +73,7 @@ private: #ifdef ICLOUD_ENABLED ICloud *icloud; #endif + iOS *ios; MainLoop *main_loop; @@ -178,6 +180,7 @@ public: void set_data_dir(String p_dir); virtual String get_name() const; + virtual String get_model_name() const; Error shell_open(String p_uri); diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index d4dc43d57c..0832ae0360 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -55,7 +55,7 @@ JavaScript *JavaScript::get_singleton() { JavaScript::JavaScript() { - ERR_FAIL_COND(singleton != NULL); + ERR_FAIL_COND_MSG(singleton != NULL, "JavaScript singleton already exist."); singleton = this; } diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index a0d6ac9214..c05b765c5e 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -24,6 +24,7 @@ def get_opts(): def get_flags(): return [ ('tools', False), + ('builtin_pcre2_with_jit', False), # Disabling the mbedtls module reduces file size. # The module has little use due to the limited networking functionality # in this platform. For the available networking methods, the browser @@ -82,10 +83,7 @@ def configure(env): env['CXX'] = 'em++' env['LINK'] = 'emcc' - # Emscripten's ar has issues with duplicate file names, so use cc. - env['AR'] = 'emcc' - env['ARFLAGS'] = '-o' - # emranlib is a noop, so it's safe to use with AR=emcc. + env['AR'] = 'emar' env['RANLIB'] = 'emranlib' # Use TempFileMunge since some AR invocations are too long for cmd.exe. @@ -127,6 +125,10 @@ def configure(env): ## Link flags + # We use IDBFS in javascript_main.cpp. Since Emscripten 1.39.1 it needs to + # be linked explicitly. + env.Append(LIBS=['idbfs.js']) + env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) # Allow increasing memory buffer size during runtime. This is efficient diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js index 860d6707ff..1f78aa672d 100644 --- a/platform/javascript/engine.js +++ b/platform/javascript/engine.js @@ -94,6 +94,7 @@ return new Promise(function(resolve, reject) { rtenvProps.onRuntimeInitialized = resolve; rtenvProps.onAbort = reject; + rtenvProps.thisProgram = executableName; rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps, LIBS); }); } @@ -130,13 +131,11 @@ ); }; - this.startGame = function(mainPack) { + this.startGame = function(execName, mainPack) { + + executableName = execName; + var mainArgs = [ '--main-pack', mainPack ]; - executableName = getBaseName(mainPack); - var mainArgs = []; - if (!getPathLeaf(mainPack).endsWith('.pck')) { - mainArgs = ['--main-pack', getPathLeaf(mainPack)]; - } return Promise.all([ // Load from directory, this.init(getBasePath(mainPack)), @@ -187,8 +186,6 @@ this.rtenv.locale = this.rtenv.locale.split('.')[0]; this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart; - this.rtenv.thisProgram = executableName || getBaseName(basePath); - preloadedFiles.forEach(function(file) { var dir = LIBS.PATH.dirname(file.path); try { diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 69dd038709..f3e8d6911a 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -28,9 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#include "core/io/tcp_server.h" #include "core/io/zip_io.h" +#include "editor/editor_export.h" #include "editor/editor_node.h" -#include "editor_export.h" #include "main/splash.gen.h" #include "platform/javascript/logo.gen.h" #include "platform/javascript/run_icon.gen.h" @@ -38,16 +39,153 @@ #define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip" #define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip" +class EditorHTTPServer : public Reference { + +private: + Ref<TCP_Server> server; + Ref<StreamPeerTCP> connection; + uint64_t time; + uint8_t req_buf[4096]; + int req_pos; + + void _clear_client() { + connection = Ref<StreamPeerTCP>(); + memset(req_buf, 0, sizeof(req_buf)); + time = 0; + req_pos = 0; + } + +public: + EditorHTTPServer() { + server.instance(); + stop(); + } + + void stop() { + server->stop(); + _clear_client(); + } + + Error listen(int p_port, IP_Address p_address) { + return server->listen(p_port, p_address); + } + + bool is_listening() const { + return server->is_listening(); + } + + void _send_response() { + Vector<String> psa = String((char *)req_buf).split("\r\n"); + int len = psa.size(); + ERR_FAIL_COND_MSG(len < 4, "Not enough response headers, got: " + itos(len) + ", expected >= 4."); + + Vector<String> req = psa[0].split(" ", false); + ERR_FAIL_COND_MSG(req.size() < 2, "Invalid protocol or status code."); + + // Wrong protocol + ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); + + String filepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); + String basereq = "/tmp_js_export"; + if (req[1] == basereq + ".html") { + filepath += ".html"; + } else if (req[1] == basereq + ".js") { + filepath += ".js"; + } else if (req[1] == basereq + ".pck") { + filepath += ".pck"; + } else if (req[1] == basereq + ".png") { + filepath += ".png"; + } else if (req[1] == basereq + ".wasm") { + filepath += ".wasm"; + } else { + String s = "HTTP/1.1 404 Not Found\r\n"; + s += "Connection: Close\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + return; + } + FileAccess *f = FileAccess::open(filepath, FileAccess::READ); + ERR_FAIL_COND(!f); + String s = "HTTP/1.1 200 OK\r\n"; + s += "Connection: Close\r\n"; + s += "\r\n"; + CharString cs = s.utf8(); + Error err = connection->put_data((const uint8_t *)cs.get_data(), cs.size() - 1); + ERR_FAIL_COND(err != OK); + + while (true) { + uint8_t bytes[4096]; + int read = f->get_buffer(bytes, 4096); + if (read < 1) { + break; + } + err = connection->put_data(bytes, read); + ERR_FAIL_COND(err != OK); + } + } + + void poll() { + if (!server->is_listening()) + return; + if (connection.is_null()) { + if (!server->is_connection_available()) + return; + connection = server->take_connection(); + time = OS::get_singleton()->get_ticks_usec(); + } + if (OS::get_singleton()->get_ticks_usec() - time > 1000000) { + _clear_client(); + return; + } + if (connection->get_status() != StreamPeerTCP::STATUS_CONNECTED) + return; + + while (true) { + + char *r = (char *)req_buf; + int l = req_pos - 1; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + _send_response(); + _clear_client(); + return; + } + + int read = 0; + ERR_FAIL_COND(req_pos >= 4096); + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) { + // Got an error + _clear_client(); + return; + } else if (read != 1) { + // Busy, wait next poll + return; + } + req_pos += read; + } + } +}; + class EditorExportPlatformJavaScript : public EditorExportPlatform { GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform); Ref<ImageTexture> logo; Ref<ImageTexture> run_icon; - bool runnable_when_last_polled; + Ref<ImageTexture> stop_icon; + int menu_options; void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug); +private: + Ref<EditorHTTPServer> server; + bool server_quit; + Mutex *server_lock; + Thread *server_thread; + + static void _server_thread_poll(void *data); + public: virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); @@ -61,11 +199,12 @@ public: virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const; virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - virtual bool poll_devices(); - virtual int get_device_count() const; - virtual String get_device_name(int p_device) const { return TTR("Run in Browser"); } - virtual String get_device_info(int p_device) const { return TTR("Run exported HTML in the system's default browser."); } - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags); + virtual bool poll_export(); + virtual int get_options_count() const; + virtual String get_option_label(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run in Browser"); } + virtual String get_option_tooltip(int p_index) const { return p_index ? TTR("Stop HTTP Server") : TTR("Run exported HTML in the system's default browser."); } + virtual Ref<ImageTexture> get_option_icon(int p_index) const; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags); virtual Ref<Texture> get_run_icon() const; virtual void get_platform_features(List<String> *r_features) { @@ -78,6 +217,7 @@ public: } EditorExportPlatformJavaScript(); + ~EditorExportPlatformJavaScript(); }; void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug) { @@ -337,7 +477,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return OK; } -bool EditorExportPlatformJavaScript::poll_devices() { +bool EditorExportPlatformJavaScript::poll_export() { Ref<EditorExportPreset> preset; @@ -350,17 +490,37 @@ bool EditorExportPlatformJavaScript::poll_devices() { } } - bool prev = runnable_when_last_polled; - runnable_when_last_polled = preset.is_valid(); - return runnable_when_last_polled != prev; + int prev = menu_options; + menu_options = preset.is_valid(); + if (server->is_listening()) { + if (menu_options == 0) { + server_lock->lock(); + server->stop(); + server_lock->unlock(); + } else { + menu_options += 1; + } + } + return menu_options != prev; +} + +Ref<ImageTexture> EditorExportPlatformJavaScript::get_option_icon(int p_index) const { + return p_index == 1 ? stop_icon : EditorExportPlatform::get_option_icon(p_index); } -int EditorExportPlatformJavaScript::get_device_count() const { +int EditorExportPlatformJavaScript::get_options_count() const { - return runnable_when_last_polled; + return menu_options; } -Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { + + if (p_option == 1) { + server_lock->lock(); + server->stop(); + server_lock->unlock(); + return OK; + } String basepath = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_js_export"); String path = basepath + ".html"; @@ -374,7 +534,26 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese DirAccess::remove_file_or_error(basepath + ".wasm"); return err; } - OS::get_singleton()->shell_open(String("file://") + path); + + IP_Address bind_ip; + uint16_t bind_port = EDITOR_GET("export/web/http_port"); + // Resolve host if needed. + String bind_host = EDITOR_GET("export/web/http_host"); + if (bind_host.is_valid_ip_address()) { + bind_ip = bind_host; + } else { + bind_ip = IP::get_singleton()->resolve_hostname(bind_host); + } + ERR_FAIL_COND_V_MSG(!bind_ip.is_valid(), ERR_INVALID_PARAMETER, "Invalid editor setting 'export/web/http_host': '" + bind_host + "'. Try using '127.0.0.1'."); + + // Restart server. + server_lock->lock(); + server->stop(); + err = server->listen(bind_port, bind_ip); + server_lock->unlock(); + ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to start HTTP server."); + + OS::get_singleton()->shell_open(String("http://" + bind_host + ":" + itos(bind_port) + "/tmp_js_export.html")); // FIXME: Find out how to clean up export files after running the successfully // exported game. Might not be trivial. return OK; @@ -385,8 +564,23 @@ Ref<Texture> EditorExportPlatformJavaScript::get_run_icon() const { return run_icon; } +void EditorExportPlatformJavaScript::_server_thread_poll(void *data) { + EditorExportPlatformJavaScript *ej = (EditorExportPlatformJavaScript *)data; + while (!ej->server_quit) { + OS::get_singleton()->delay_usec(1000); + ej->server_lock->lock(); + ej->server->poll(); + ej->server_lock->unlock(); + } +} + EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { + server.instance(); + server_quit = false; + server_lock = Mutex::create(); + server_thread = Thread::create(_server_thread_poll, this); + Ref<Image> img = memnew(Image(_javascript_logo)); logo.instance(); logo->create_from_image(img); @@ -395,11 +589,29 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { run_icon.instance(); run_icon->create_from_image(img); - runnable_when_last_polled = false; + Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); + if (theme.is_valid()) + stop_icon = theme->get_icon("Stop", "EditorIcons"); + else + stop_icon.instance(); + + menu_options = 0; +} + +EditorExportPlatformJavaScript::~EditorExportPlatformJavaScript() { + server->stop(); + server_quit = true; + Thread::wait_to_finish(server_thread); + memdelete(server_lock); + memdelete(server_thread); } void register_javascript_exporter() { + EDITOR_DEF("export/web/http_host", "localhost"); + EDITOR_DEF("export/web/http_port", 8060); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "export/web/http_port", PROPERTY_HINT_RANGE, "1,65535,1")); + Ref<EditorExportPlatformJavaScript> platform; platform.instance(); EditorExport::get_singleton()->add_export_platform(platform); diff --git a/platform/javascript/logo.png b/platform/javascript/logo.png Binary files differindex 36832d93ba..c046d87dc4 100644 --- a/platform/javascript/logo.png +++ b/platform/javascript/logo.png diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 0179bf813d..652f6a1ce1 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -574,6 +574,8 @@ void OS_JavaScript::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_s }, cursors[p_shape].utf8().get_data()); /* clang-format on */ cursors[p_shape] = ""; + + cursors_cache.erase(p_shape); } set_cursor_shape(cursor_shape); @@ -835,7 +837,7 @@ void OS_JavaScript::set_clipboard(const String &p_text) { var text = UTF8ToString($0); if (!navigator.clipboard || !navigator.clipboard.writeText) return 1; - navigator.clipboard.writeText(text).catch(e => { + navigator.clipboard.writeText(text).catch(function(e) { // Setting OS clipboard is only possible from an input callback. console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e); }); diff --git a/platform/osx/camera_osx.mm b/platform/osx/camera_osx.mm index f13cf76beb..af09eec2eb 100644 --- a/platform/osx/camera_osx.mm +++ b/platform/osx/camera_osx.mm @@ -150,8 +150,8 @@ { // do Y - int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); - int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); if ((width[0] != new_width) || (height[0] != new_height)) { width[0] = new_width; @@ -168,8 +168,8 @@ { // do CbCr - int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); - int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); + size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); + size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); if ((width[1] != new_width) || (height[1] != new_height)) { width[1] = new_width; diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index e19fdf1b9f..015859b3c0 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -95,7 +95,7 @@ static void handle_crash(int sig) { if (strings) { void *load_addr = (void *)load_address(); - for (int i = 1; i < size; i++) { + for (size_t i = 1; i < size; i++) { char fname[1024]; Dl_info info; @@ -142,7 +142,7 @@ static void handle_crash(int sig) { } } - fprintf(stderr, "[%d] %ls\n", i, output.c_str()); + fprintf(stderr, "[%zu] %ls\n", i, output.c_str()); } free(strings); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 881ed05025..7882253e7a 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -91,6 +91,9 @@ def configure(env): env['RANLIB'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-ranlib" env['AS'] = mpprefix + "/libexec/llvm-" + mpclangver + "/bin/llvm-as" env.Append(CPPDEFINES=['__MACPORTS__']) #hack to fix libvpx MM256_BROADCASTSI128_SI256 define + else: + env['CC'] = 'clang' + env['CXX'] = 'clang++' detect_darwin_sdk_path('osx', env) env.Append(CCFLAGS=['-isysroot', '$MACOS_SDK_PATH']) diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index ada142005b..75f50aaa28 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -48,18 +48,25 @@ String DirAccessOSX::fix_unicode_name(const char *p_name) const { } int DirAccessOSX::get_drive_count() { - NSArray *vols = [[NSWorkspace sharedWorkspace] mountedLocalVolumePaths]; + NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; + NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; + return [vols count]; } String DirAccessOSX::get_drive(int p_drive) { - NSArray *vols = [[NSWorkspace sharedWorkspace] mountedLocalVolumePaths]; + NSArray *res_keys = [NSArray arrayWithObjects:NSURLVolumeURLKey, NSURLIsSystemImmutableKey, nil]; + NSArray *vols = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:res_keys options:NSVolumeEnumerationSkipHiddenVolumes]; int count = [vols count]; ERR_FAIL_INDEX_V(p_drive, count, ""); - NSString *path = vols[p_drive]; - return String([path UTF8String]); + String volname; + NSString *path = [vols[p_drive] path]; + + volname.parse_utf8([path UTF8String]); + + return volname; } #endif //posix_enabled diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 94090bcdc1..9226aea369 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -132,8 +132,12 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); #ifdef OSX_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray())); #endif r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true)); @@ -360,25 +364,48 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { List<String> args; + if (p_preset->get("codesign/timestamp")) { + args.push_back("--timestamp"); + } + if (p_preset->get("codesign/hardened_runtime")) { + args.push_back("--options"); + args.push_back("runtime"); + } + if (p_preset->get("codesign/entitlements") != "") { /* this should point to our entitlements.plist file that sandboxes our application, I don't know if this should also be placed in our app bundle */ - args.push_back("-entitlements"); + args.push_back("--entitlements"); args.push_back(p_preset->get("codesign/entitlements")); } + + PoolStringArray user_args = p_preset->get("codesign/custom_options"); + for (int i = 0; i < user_args.size(); i++) { + String user_arg = user_args[i].strip_edges(); + if (!user_arg.empty()) { + args.push_back(user_arg); + } + } + args.push_back("-s"); args.push_back(p_preset->get("codesign/identity")); + args.push_back("-v"); /* provide some more feedback */ + args.push_back(p_path); String str; Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true); ERR_FAIL_COND_V(err != OK, err); - print_line("codesign: " + str); + print_line("codesign (" + p_path + "): " + str); if (str.find("no identity found") != -1) { EditorNode::add_io_error("codesign: no identity found"); return FAILED; } + if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { + EditorNode::add_io_error("codesign: invalid entitlements file"); + return FAILED; + } return OK; } @@ -386,7 +413,9 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name) { List<String> args; - OS::get_singleton()->move_to_trash(p_dmg_path); + if (FileAccess::exists(p_dmg_path)) { + OS::get_singleton()->move_to_trash(p_dmg_path); + } args.push_back("create"); args.push_back(p_dmg_path); @@ -647,20 +676,20 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = save_pack(p_preset, pack_path, &shared_objects); // see if we can code sign our new package - String identity = p_preset->get("codesign/identity"); + bool sign_enabled = p_preset->get("codesign/enable"); if (err == OK) { DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { err = da->copy(shared_objects[i].path, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); - if (err == OK && identity != "") { + if (err == OK && sign_enabled) { err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Frameworks/" + shared_objects[i].path.get_file()); } } memdelete(da); } - if (err == OK && identity != "") { + if (err == OK && sign_enabled) { if (ep.step("Code signing bundle", 2)) { return ERR_SKIP; } @@ -673,19 +702,6 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p ///@TODO we should check the contents of /Contents/Frameworks for frameworks to sign } - if (err == OK && identity != "") { - // we should probably loop through all resources and sign them? - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Resources/icon.icns"); - } - - if (err == OK && identity != "") { - err = _code_sign(p_preset, pack_path); - } - - if (err == OK && identity != "") { - err = _code_sign(p_preset, tmp_app_path_name + "/Contents/Info.plist"); - } - // and finally create a DMG if (err == OK) { if (ep.step("Making DMG", 3)) { diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index fa124dac11..4edf347f61 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -578,7 +578,7 @@ JoypadOSX::JoypadOSX() { const size_t n_elements = sizeof(vals) / sizeof(vals[0]); CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, n_elements, &kCFTypeArrayCallBacks) : NULL; - for (int i = 0; i < n_elements; i++) { + for (size_t i = 0; i < n_elements; i++) { if (vals[i]) { CFRelease((CFTypeRef)vals[i]); } diff --git a/platform/osx/logo.png b/platform/osx/logo.png Binary files differindex 62086fc415..834bbf3ba6 100644 --- a/platform/osx/logo.png +++ b/platform/osx/logo.png diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index f1f37e24d2..09a871f26c 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -31,6 +31,8 @@ #ifndef OS_OSX_H #define OS_OSX_H +#define BitMap _QDBitMap // Suppress deprecated QuickDraw definition. + #include "camera_osx.h" #include "core/os/input.h" #include "crash_handler_osx.h" @@ -50,6 +52,7 @@ #include <ApplicationServices/ApplicationServices.h> #include <CoreVideo/CoreVideo.h> +#undef BitMap #undef CursorShape class OS_OSX : public OS_Unix { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index f48d4a307d..ba3ac9862e 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -339,6 +339,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; if (OS_OSX::singleton->is_hidpi_allowed()) { [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:YES]; + } else { + [OS_OSX::singleton->window_view setWantsBestResolutionOpenGLSurface:NO]; } if (newBackingScaleFactor != oldBackingScaleFactor) { @@ -618,7 +620,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; Vector<String> files; - for (int i = 0; i < filenames.count; i++) { + for (NSUInteger i = 0; i < filenames.count; i++) { NSString *ns = [filenames objectAtIndex:i]; char *utfs = strdup([ns UTF8String]); String ret; @@ -707,6 +709,11 @@ static void _mouseDownEvent(NSEvent *event, int index, int mask, bool pressed) { const CGFloat backingScaleFactor = [[event window] backingScaleFactor]; const Vector2 pos = get_mouse_pos([event locationInWindow], backingScaleFactor); mm->set_position(pos); + mm->set_pressure([event pressure]); + if ([event subtype] == NSTabletPointEventSubtype) { + const NSPoint p = [event tilt]; + mm->set_tilt(Vector2(p.x, p.y)); + } mm->set_global_position(pos); mm->set_speed(OS_OSX::singleton->input->get_last_mouse_speed()); Vector2 relativeMotion = Vector2(); @@ -1492,13 +1499,15 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a [window_view setWantsBestResolutionOpenGLSurface:YES]; //if (current_videomode.resizable) [window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + } else { + [window_view setWantsBestResolutionOpenGLSurface:NO]; } //[window_object setTitle:[NSString stringWithUTF8String:"GodotEnginies"]]; [window_object setContentView:window_view]; [window_object setDelegate:window_delegate]; [window_object setAcceptsMouseMovedEvents:YES]; - [window_object center]; + [(NSWindow *)window_object center]; [window_object setRestorable:NO]; @@ -1973,11 +1982,16 @@ void OS_OSX::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c [nsimage release]; } else { // Reset to default system cursor - cursors[p_shape] = NULL; + if (cursors[p_shape] != NULL) { + [cursors[p_shape] release]; + cursors[p_shape] = NULL; + } CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } @@ -2329,12 +2343,12 @@ void OS_OSX::set_current_screen(int p_screen) { }; Point2 OS_OSX::get_native_screen_position(int p_screen) const { - if (p_screen == -1) { + if (p_screen < 0) { p_screen = get_current_screen(); } NSArray *screenArray = [NSScreen screens]; - if (p_screen < [screenArray count]) { + if ((NSUInteger)p_screen < [screenArray count]) { float display_scale = _display_scale([screenArray objectAtIndex:p_screen]); NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; // Return the top-left corner of the screen, for OS X the y starts at the bottom @@ -2353,12 +2367,12 @@ Point2 OS_OSX::get_screen_position(int p_screen) const { } int OS_OSX::get_screen_dpi(int p_screen) const { - if (p_screen == -1) { + if (p_screen < 0) { p_screen = get_current_screen(); } NSArray *screenArray = [NSScreen screens]; - if (p_screen < [screenArray count]) { + if ((NSUInteger)p_screen < [screenArray count]) { float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; @@ -2372,12 +2386,12 @@ int OS_OSX::get_screen_dpi(int p_screen) const { } Size2 OS_OSX::get_screen_size(int p_screen) const { - if (p_screen == -1) { + if (p_screen < 0) { p_screen = get_current_screen(); } NSArray *screenArray = [NSScreen screens]; - if (p_screen < [screenArray count]) { + if ((NSUInteger)p_screen < [screenArray count]) { float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); // Note: Use frame to get the whole screen size NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 7da93eafae..000bd18e7d 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -34,6 +34,7 @@ def get_flags(): return [ ('tools', False), ('xaudio2', True), + ('builtin_pcre2_with_jit', False), ] diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index bb18c2da8a..557699cf37 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -1144,11 +1144,21 @@ public: return valid; } + if (!_valid_resource_name(p_preset->get("package/short_name"))) { + valid = false; + err += TTR("Invalid package short name.") + "\n"; + } + if (!_valid_resource_name(p_preset->get("package/unique_name"))) { valid = false; err += TTR("Invalid package unique name.") + "\n"; } + if (!_valid_resource_name(p_preset->get("package/publisher_display_name"))) { + valid = false; + err += TTR("Invalid package publisher display name.") + "\n"; + } + if (!_valid_guid(p_preset->get("identity/product_guid"))) { valid = false; err += TTR("Invalid product GUID.") + "\n"; @@ -1249,7 +1259,7 @@ public: Error err = OK; FileAccess *fa_pack = FileAccess::open(p_path, FileAccess::WRITE, &err); - ERR_FAIL_COND_V(err != OK, ERR_CANT_CREATE); + ERR_FAIL_COND_V_MSG(err != OK, ERR_CANT_CREATE, "Cannot create file '" + p_path + "'."); AppxPackager packager; packager.init(fa_pack); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index cc9ba720a8..9a2b2bcb98 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -65,6 +65,8 @@ def get_opts(): BoolVariable('separate_debug_symbols', 'Create a separate file containing debugging symbols', False), ('msvc_version', 'MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.', None), BoolVariable('use_mingw', 'Use the Mingw compiler, even if MSVC is installed. Only used on Windows.', False), + BoolVariable('use_llvm', 'Use the LLVM compiler', False), + BoolVariable('use_thinlto', 'Use ThinLTO', False), ] @@ -312,17 +314,33 @@ def configure_mingw(env): env.Append(LINKFLAGS=['-static']) mingw_prefix = env["mingw_prefix_64"] - env["CC"] = mingw_prefix + "gcc" - env['AS'] = mingw_prefix + "as" - env['CXX'] = mingw_prefix + "g++" - env['AR'] = mingw_prefix + "gcc-ar" - env['RANLIB'] = mingw_prefix + "gcc-ranlib" - env['LINK'] = mingw_prefix + "g++" + if env['use_llvm']: + env["CC"] = mingw_prefix + "clang" + env['AS'] = mingw_prefix + "as" + env["CXX"] = mingw_prefix + "clang++" + env['AR'] = mingw_prefix + "ar" + env['RANLIB'] = mingw_prefix + "ranlib" + env["LINK"] = mingw_prefix + "clang++" + else: + env["CC"] = mingw_prefix + "gcc" + env['AS'] = mingw_prefix + "as" + env['CXX'] = mingw_prefix + "g++" + env['AR'] = mingw_prefix + "gcc-ar" + env['RANLIB'] = mingw_prefix + "gcc-ranlib" + env['LINK'] = mingw_prefix + "g++" env["x86_libtheora_opt_gcc"] = True if env['use_lto']: - env.Append(CCFLAGS=['-flto']) - env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))]) + if not env['use_llvm'] and env.GetOption("num_jobs") > 1: + env.Append(CCFLAGS=['-flto']) + env.Append(LINKFLAGS=['-flto=' + str(env.GetOption("num_jobs"))]) + else: + if env['use_thinlto']: + env.Append(CCFLAGS=['-flto=thin']) + env.Append(LINKFLAGS=['-flto=thin']) + else: + env.Append(CCFLAGS=['-flto']) + env.Append(LINKFLAGS=['-flto']) ## Compile flags @@ -332,7 +350,7 @@ def configure_mingw(env): env.Append(CPPDEFINES=[('WINVER', env['target_win_version']), ('_WIN32_WINNT', env['target_win_version'])]) env.Append(LIBS=['mingw32', 'opengl32', 'dsound', 'ole32', 'd3d9', 'winmm', 'gdi32', 'iphlpapi', 'shlwapi', 'wsock32', 'ws2_32', 'kernel32', 'oleaut32', 'dinput8', 'dxguid', 'ksuser', 'imm32', 'bcrypt', 'avrt', 'uuid']) - env.Append(CPPDEFINES=['MINGW_ENABLED']) + env.Append(CPPDEFINES=['MINGW_ENABLED', ('MINGW_HAS_SECURE_API', 1)]) # resrc env.Append(BUILDERS={'RES': env.Builder(action=build_res_file, suffix='.o', src_suffix='.rc')}) diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 827daa2d58..3abb05b494 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -31,6 +31,7 @@ #include "core/os/file_access.h" #include "core/os/os.h" #include "editor/editor_export.h" +#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "platform/windows/logo.gen.h" @@ -38,11 +39,22 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, class EditorExportPlatformWindows : public EditorExportPlatformPC { + Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path); + public: virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); + virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); virtual void get_export_options(List<ExportOption> *r_options); }; +Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { + if (p_preset->get("codesign/enable")) { + return _code_sign(p_preset, p_path); + } else { + return OK; + } +} + Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, p_path, p_flags); @@ -133,12 +145,28 @@ Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> OS::get_singleton()->execute(wine_path, args, true); #endif - return OK; + if (p_preset->get("codesign/enable") && err == OK) { + err = _code_sign(p_preset, p_path); + } + + return err; } void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_options) { EditorExportPlatformPC::get_export_options(r_options); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/enable"), false)); +#ifdef WINDOWS_ENABLED + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/identity_type", PROPERTY_HINT_ENUM, "Select automatically,Use PKCS12 file (specify *.PFX/*.P12 file),Use certificate store (specify SHA1 hash)"), 0)); +#endif + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_GLOBAL_FILE, "*.pfx,*.p12"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/password"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/timestamp"), true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/timestamp_server_url"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/digest_algorithm", PROPERTY_HINT_ENUM, "SHA1,SHA256"), 1)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/description"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray())); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); @@ -149,11 +177,164 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/trademarks"), "")); } +Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) { + List<String> args; + +#ifdef WINDOWS_ENABLED + String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool"); + if (signtool_path != String() && !FileAccess::exists(signtool_path)) { + ERR_PRINTS("Could not find signtool executable at " + signtool_path + ", aborting."); + return ERR_FILE_NOT_FOUND; + } + if (signtool_path == String()) { + signtool_path = "signtool"; // try to run signtool from PATH + } +#else + String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode"); + if (signtool_path != String() && !FileAccess::exists(signtool_path)) { + ERR_PRINTS("Could not find osslsigncode executable at " + signtool_path + ", aborting."); + return ERR_FILE_NOT_FOUND; + } + if (signtool_path == String()) { + signtool_path = "osslsigncode"; // try to run signtool from PATH + } +#endif + + args.push_back("sign"); + + //identity +#ifdef WINDOWS_ENABLED + int id_type = p_preset->get("codesign/identity_type"); + if (id_type == 0) { //auto select + args.push_back("/a"); + } else if (id_type == 1) { //pkcs12 + if (p_preset->get("codesign/identity") != "") { + args.push_back("/f"); + args.push_back(p_preset->get("codesign/identity")); + } else { + EditorNode::add_io_error("codesign: no identity found"); + return FAILED; + } + } else if (id_type == 2) { //Windows certificate store + if (p_preset->get("codesign/identity") != "") { + args.push_back("/sha1"); + args.push_back(p_preset->get("codesign/identity")); + } else { + EditorNode::add_io_error("codesign: no identity found"); + return FAILED; + } + } else { + EditorNode::add_io_error("codesign: invalid identity type"); + return FAILED; + } +#else + if (p_preset->get("codesign/identity") != "") { + args.push_back("-pkcs12"); + args.push_back(p_preset->get("codesign/identity")); + } else { + EditorNode::add_io_error("codesign: no identity found"); + return FAILED; + } +#endif + + //password + if (p_preset->get("codesign/password") != "") { +#ifdef WINDOWS_ENABLED + args.push_back("/p"); +#else + args.push_back("-pass"); +#endif + args.push_back(p_preset->get("codesign/password")); + } + + //timestamp + if (p_preset->get("codesign/timestamp")) { + if (p_preset->get("codesign/timestamp_server") != "") { +#ifdef WINDOWS_ENABLED + args.push_back("/tr"); + args.push_back(p_preset->get("codesign/timestamp_server_url")); + args.push_back("/td"); + if ((int)p_preset->get("codesign/digest_algorithm") == 0) { + args.push_back("sha1"); + } else { + args.push_back("sha256"); + } +#else + args.push_back("-ts"); + args.push_back(p_preset->get("codesign/timestamp_server_url")); +#endif + } else { + EditorNode::add_io_error("codesign: invalid timestamp server"); + return FAILED; + } + } + + //digest +#ifdef WINDOWS_ENABLED + args.push_back("/fd"); +#else + args.push_back("-h"); +#endif + if ((int)p_preset->get("codesign/digest_algorithm") == 0) { + args.push_back("sha1"); + } else { + args.push_back("sha256"); + } + + //description + if (p_preset->get("codesign/description") != "") { +#ifdef WINDOWS_ENABLED + args.push_back("/d"); +#else + args.push_back("-n"); +#endif + args.push_back(p_preset->get("codesign/description")); + } + + //user options + PoolStringArray user_args = p_preset->get("codesign/custom_options"); + for (int i = 0; i < user_args.size(); i++) { + String user_arg = user_args[i].strip_edges(); + if (!user_arg.empty()) { + args.push_back(user_arg); + } + } + +#ifndef WINDOWS_ENABLED + args.push_back("-in"); +#endif + args.push_back(p_path); +#ifndef WINDOWS_ENABLED + args.push_back("-out"); + args.push_back(p_path); +#endif + + String str; + Error err = OS::get_singleton()->execute(signtool_path, args, true, NULL, &str, NULL, true); + ERR_FAIL_COND_V(err != OK, err); + + print_line("codesign (" + p_path + "): " + str); +#ifndef WINDOWS_ENABLED + if (str.find("SignTool Error") != -1) { +#else + if (str.find("Failed") != -1) { +#endif + return FAILED; + } + + return OK; +} + void register_windows_exporter() { EDITOR_DEF("export/windows/rcedit", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); -#ifndef WINDOWS_ENABLED +#ifdef WINDOWS_ENABLED + EDITOR_DEF("export/windows/signtool", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); +#else + EDITOR_DEF("export/windows/osslsigncode", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/osslsigncode", PROPERTY_HINT_GLOBAL_FILE)); // On non-Windows we need WINE to run rcedit EDITOR_DEF("export/windows/wine", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/wine", PROPERTY_HINT_GLOBAL_FILE)); diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis index 55c83c3f3c..593557cc69 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -143,4 +143,12 @@ <Item Name="alpha">a</Item> </Expand> </Type> + + <Type Name="Node" Inheritable="false"> + <Expand> + <Item Name="Object">(Object*)this</Item> + <Item Name="class_name">(StringName*)(((char*)this) + sizeof(Object))</Item> + <Item Name="data">(Node::Data*)(((char*)this) + sizeof(Object) + sizeof(StringName))</Item> + </Expand> + </Type> </AutoVisualizer> diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index be325381bb..429657f332 100644..100755 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -70,6 +70,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #define WM_TOUCH 576 #endif +#ifndef WM_POINTERUPDATE +#define WM_POINTERUPDATE 0x0245 +#endif + typedef struct { int count; int screen; @@ -192,6 +196,9 @@ BOOL WINAPI HandlerRoutine(_In_ DWORD dwCtrlType) { } } +GetPointerTypePtr OS_Windows::win8p_GetPointerType = NULL; +GetPointerPenInfoPtr OS_Windows::win8p_GetPointerPenInfo = NULL; + void OS_Windows::initialize_debugging() { SetConsoleCtrlHandler(HandlerRoutine, TRUE); @@ -288,15 +295,16 @@ void OS_Windows::_drag_event(float p_x, float p_y, int idx) { if (curr->get() == Vector2(p_x, p_y)) return; - curr->get() = Vector2(p_x, p_y); - Ref<InputEventScreenDrag> event; event.instance(); event->set_index(idx); event->set_position(Vector2(p_x, p_y)); + event->set_relative(Vector2(p_x, p_y) - curr->get()); if (main_loop) input->accumulate_input_event(event); + + curr->get() = Vector2(p_x, p_y); }; LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { @@ -480,6 +488,119 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } delete[] lpb; } break; + case WM_POINTERUPDATE: { + if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { + break; + } + + if (!win8p_GetPointerType || !win8p_GetPointerPenInfo) { + break; + } + + uint32_t pointer_id = LOWORD(wParam); + POINTER_INPUT_TYPE pointer_type = PT_POINTER; + if (!win8p_GetPointerType(pointer_id, &pointer_type)) { + break; + } + + if (pointer_type != PT_PEN) { + break; + } + + POINTER_PEN_INFO pen_info; + if (!win8p_GetPointerPenInfo(pointer_id, &pen_info)) { + break; + } + + if (input->is_emulating_mouse_from_touch()) { + // Universal translation enabled; ignore OS translation + LPARAM extra = GetMessageExtraInfo(); + if (IsTouchEvent(extra)) { + break; + } + } + + if (outside) { + //mouse enter + + if (main_loop && mouse_mode != MOUSE_MODE_CAPTURED) + main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER); + + CursorShape c = cursor_shape; + cursor_shape = CURSOR_MAX; + set_cursor_shape(c); + outside = false; + + //Once-Off notification, must call again.... + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = hWnd; + tme.dwHoverTime = HOVER_DEFAULT; + TrackMouseEvent(&tme); + } + + // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. + if (!window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) + break; + + Ref<InputEventMouseMotion> mm; + mm.instance(); + + mm->set_pressure(pen_info.pressure ? (float)pen_info.pressure / 1024 : 0); + mm->set_tilt(Vector2(pen_info.tiltX ? (float)pen_info.tiltX / 90 : 0, pen_info.tiltY ? (float)pen_info.tiltY / 90 : 0)); + + mm->set_control((wParam & MK_CONTROL) != 0); + mm->set_shift((wParam & MK_SHIFT) != 0); + mm->set_alt(alt_mem); + + mm->set_button_mask(last_button_state); + + POINT coords; //client coords + coords.x = GET_X_LPARAM(lParam); + coords.y = GET_Y_LPARAM(lParam); + + ScreenToClient(hWnd, &coords); + + mm->set_position(Vector2(coords.x, coords.y)); + mm->set_global_position(Vector2(coords.x, coords.y)); + + if (mouse_mode == MOUSE_MODE_CAPTURED) { + + Point2i c(video_mode.width / 2, video_mode.height / 2); + old_x = c.x; + old_y = c.y; + + if (mm->get_position() == c) { + center = c; + return 0; + } + + Point2i ncenter = mm->get_position(); + center = ncenter; + POINT pos = { (int)c.x, (int)c.y }; + ClientToScreen(hWnd, &pos); + SetCursorPos(pos.x, pos.y); + } + + input->set_mouse_position(mm->get_position()); + mm->set_speed(input->get_last_mouse_speed()); + + if (old_invalid) { + + old_x = mm->get_position().x; + old_y = mm->get_position().y; + old_invalid = false; + } + + mm->set_relative(Vector2(mm->get_position() - Vector2(old_x, old_y))); + old_x = mm->get_position().x; + old_y = mm->get_position().y; + if (window_has_focus && main_loop) + input->parse_input_event(mm); + + return 0; //Pointer event handled return 0 to avoid duplicate WM_MOUSEMOVE event + } break; case WM_MOUSEMOVE: { if (mouse_mode == MOUSE_MODE_CAPTURED && use_raw_input) { break; @@ -1875,6 +1996,8 @@ void OS_Windows::set_window_fullscreen(bool p_enabled) { if (p_enabled) { + was_maximized = maximized; + if (pre_fs_valid) { GetWindowRect(hWnd, &pre_fs_rect); } @@ -1904,7 +2027,7 @@ void OS_Windows::set_window_fullscreen(bool p_enabled) { rect.bottom = video_mode.height; } - _update_window_style(false); + _update_window_style(false, was_maximized); MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); @@ -2086,12 +2209,16 @@ bool OS_Windows::get_borderless_window() { return video_mode.borderless_window; } -void OS_Windows::_update_window_style(bool repaint) { +void OS_Windows::_update_window_style(bool p_repaint, bool p_maximized) { if (video_mode.fullscreen || video_mode.borderless_window) { SetWindowLongPtr(hWnd, GWL_STYLE, WS_SYSMENU | WS_POPUP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VISIBLE); } else { if (video_mode.resizable) { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE); + if (p_maximized) { + SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_MAXIMIZE); + } else { + SetWindowLongPtr(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE); + } } else { SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_MINIMIZEBOX | WS_POPUPWINDOW | WS_VISIBLE); } @@ -2099,7 +2226,7 @@ void OS_Windows::_update_window_style(bool repaint) { SetWindowPos(hWnd, video_mode.always_on_top ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); - if (repaint) { + if (p_repaint) { RECT rect; GetWindowRect(hWnd, &rect); MoveWindow(hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); @@ -2485,11 +2612,16 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap DeleteObject(bitmap); } else { // Reset to default system cursor - cursors[p_shape] = NULL; + if (cursors[p_shape]) { + DestroyIcon(cursors[p_shape]); + cursors[p_shape] = NULL; + } CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } @@ -2663,7 +2795,7 @@ String OS_Windows::get_executable_path() const { void OS_Windows::set_native_icon(const String &p_filename) { FileAccess *f = FileAccess::open(p_filename, FileAccess::READ); - ERR_FAIL_COND(!f); + ERR_FAIL_COND_MSG(!f, "Cannot open file with icon '" + p_filename + "'."); ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR)); int pos = 0; @@ -2856,7 +2988,7 @@ void OS_Windows::move_window_to_foreground() { Error OS_Windows::shell_open(String p_uri) { - ShellExecuteW(NULL, L"open", p_uri.c_str(), NULL, NULL, SW_SHOWNORMAL); + ShellExecuteW(NULL, NULL, p_uri.c_str(), NULL, NULL, SW_SHOWNORMAL); return OK; } @@ -3241,8 +3373,16 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) { control_mem = false; meta_mem = false; minimized = false; + was_maximized = false; console_visible = IsWindowVisible(GetConsoleWindow()); + //Note: Functions for pen input, available on Windows 8+ + HMODULE user32_lib = LoadLibraryW(L"user32.dll"); + if (user32_lib) { + win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType"); + win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo"); + } + hInstance = _hInstance; pressrc = 0; old_invalid = true; diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 915d025e3b..ce279fb033 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -56,6 +56,71 @@ #include <windows.h> #include <windowsx.h> +#ifndef POINTER_STRUCTURES + +#define POINTER_STRUCTURES + +typedef DWORD POINTER_INPUT_TYPE; +typedef UINT32 POINTER_FLAGS; +typedef UINT32 PEN_FLAGS; +typedef UINT32 PEN_MASK; + +enum tagPOINTER_INPUT_TYPE { + PT_POINTER = 0x00000001, + PT_TOUCH = 0x00000002, + PT_PEN = 0x00000003, + PT_MOUSE = 0x00000004, + PT_TOUCHPAD = 0x00000005 +}; + +typedef enum tagPOINTER_BUTTON_CHANGE_TYPE { + POINTER_CHANGE_NONE, + POINTER_CHANGE_FIRSTBUTTON_DOWN, + POINTER_CHANGE_FIRSTBUTTON_UP, + POINTER_CHANGE_SECONDBUTTON_DOWN, + POINTER_CHANGE_SECONDBUTTON_UP, + POINTER_CHANGE_THIRDBUTTON_DOWN, + POINTER_CHANGE_THIRDBUTTON_UP, + POINTER_CHANGE_FOURTHBUTTON_DOWN, + POINTER_CHANGE_FOURTHBUTTON_UP, + POINTER_CHANGE_FIFTHBUTTON_DOWN, + POINTER_CHANGE_FIFTHBUTTON_UP, +} POINTER_BUTTON_CHANGE_TYPE; + +typedef struct tagPOINTER_INFO { + POINTER_INPUT_TYPE pointerType; + UINT32 pointerId; + UINT32 frameId; + POINTER_FLAGS pointerFlags; + HANDLE sourceDevice; + HWND hwndTarget; + POINT ptPixelLocation; + POINT ptHimetricLocation; + POINT ptPixelLocationRaw; + POINT ptHimetricLocationRaw; + DWORD dwTime; + UINT32 historyCount; + INT32 InputData; + DWORD dwKeyStates; + UINT64 PerformanceCount; + POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; +} POINTER_INFO; + +typedef struct tagPOINTER_PEN_INFO { + POINTER_INFO pointerInfo; + PEN_FLAGS penFlags; + PEN_MASK penMask; + UINT32 pressure; + UINT32 rotation; + INT32 tiltX; + INT32 tiltY; +} POINTER_PEN_INFO; + +#endif + +typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type); +typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info); + typedef struct { BYTE bWidth; // Width, in pixels, of the image BYTE bHeight; // Height, in pixels, of the image @@ -77,11 +142,16 @@ typedef struct { class JoypadWindows; class OS_Windows : public OS { + static GetPointerTypePtr win8p_GetPointerType; + static GetPointerPenInfoPtr win8p_GetPointerPenInfo; + enum { KEY_EVENT_BUFFER_SIZE = 512 }; +#ifdef STDOUT_FILE FILE *stdo; +#endif struct KeyEvent { @@ -107,7 +177,6 @@ class OS_Windows : public OS { VisualServer *visual_server; CameraWindows *camera_server; int pressrc; - HDC hDC; // Private GDI Device Context HINSTANCE hInstance; // Holds The Instance Of The Application HWND hWnd; Point2 last_pos; @@ -175,7 +244,7 @@ class OS_Windows : public OS { void _drag_event(float p_x, float p_y, int idx); void _touch_event(bool p_pressed, float p_x, float p_y, int idx); - void _update_window_style(bool repaint = true); + void _update_window_style(bool p_repaint = true, bool p_maximized = false); void _set_mouse_mode_impl(MouseMode p_mode); @@ -208,6 +277,7 @@ protected: bool minimized; bool borderless; bool console_visible; + bool was_maximized; public: LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp index 687981f32b..54d3759cc5 100644 --- a/platform/x11/os_x11.cpp +++ b/platform/x11/os_x11.cpp @@ -83,6 +83,12 @@ #define XINPUT_CLIENT_VERSION_MAJOR 2 #define XINPUT_CLIENT_VERSION_MINOR 2 +#define VALUATOR_ABSX 0 +#define VALUATOR_ABSY 1 +#define VALUATOR_PRESSURE 2 +#define VALUATOR_TILTX 3 +#define VALUATOR_TILTY 4 + static const double abs_resolution_mult = 10000.0; static const double abs_resolution_range_mult = 10.0; @@ -665,6 +671,15 @@ bool OS_X11::refresh_device_info() { int range_min_y = 0; int range_max_x = 0; int range_max_y = 0; + int pressure_resolution = 0; + int pressure_min = 0; + int pressure_max = 0; + int tilt_resolution_x = 0; + int tilt_resolution_y = 0; + int tilt_range_min_x = 0; + int tilt_range_min_y = 0; + int tilt_range_max_x = 0; + int tilt_range_max_y = 0; for (int j = 0; j < dev->num_classes; j++) { #ifdef TOUCH_ENABLED if (dev->classes[j]->type == XITouchClass && ((XITouchClassInfo *)dev->classes[j])->mode == XIDirectTouch) { @@ -674,16 +689,28 @@ bool OS_X11::refresh_device_info() { if (dev->classes[j]->type == XIValuatorClass) { XIValuatorClassInfo *class_info = (XIValuatorClassInfo *)dev->classes[j]; - if (class_info->number == 0 && class_info->mode == XIModeAbsolute) { + if (class_info->number == VALUATOR_ABSX && class_info->mode == XIModeAbsolute) { resolution_x = class_info->resolution; range_min_x = class_info->min; range_max_x = class_info->max; absolute_mode = true; - } else if (class_info->number == 1 && class_info->mode == XIModeAbsolute) { + } else if (class_info->number == VALUATOR_ABSY && class_info->mode == XIModeAbsolute) { resolution_y = class_info->resolution; range_min_y = class_info->min; range_max_y = class_info->max; absolute_mode = true; + } else if (class_info->number == VALUATOR_PRESSURE && class_info->mode == XIModeAbsolute) { + pressure_resolution = class_info->resolution; + pressure_min = class_info->min; + pressure_max = class_info->max; + } else if (class_info->number == VALUATOR_TILTX && class_info->mode == XIModeAbsolute) { + tilt_resolution_x = class_info->resolution; + tilt_range_min_x = class_info->min; + tilt_range_max_x = class_info->max; + } else if (class_info->number == VALUATOR_TILTY && class_info->mode == XIModeAbsolute) { + tilt_resolution_y = class_info->resolution; + tilt_range_min_y = class_info->min; + tilt_range_max_y = class_info->max; } } } @@ -703,6 +730,18 @@ bool OS_X11::refresh_device_info() { xi.absolute_devices[dev->deviceid] = Vector2(abs_resolution_mult / resolution_x, abs_resolution_mult / resolution_y); print_verbose("XInput: Absolute pointing device: " + String(dev->name)); } + + if (pressure_resolution <= 0) { + pressure_resolution = (pressure_max - pressure_min); + } + if (tilt_resolution_x <= 0) { + tilt_resolution_x = (tilt_range_max_x - tilt_range_min_x); + } + if (tilt_resolution_y <= 0) { + tilt_resolution_y = (tilt_range_max_y - tilt_range_min_y); + } + xi.pressure = 0; + xi.pen_devices[dev->deviceid] = Vector3(pressure_resolution, tilt_resolution_x, tilt_resolution_y); } XIFreeDeviceInfo(info); @@ -1767,7 +1806,8 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { XKeyEvent xkeyevent_no_mod = *xkeyevent; xkeyevent_no_mod.state &= ~ShiftMask; xkeyevent_no_mod.state &= ~ControlMask; - XLookupString(&xkeyevent_no_mod, str, 256, &keysym_keycode, NULL); + XLookupString(xkeyevent, str, 256, &keysym_unicode, NULL); + XLookupString(&xkeyevent_no_mod, NULL, 0, &keysym_keycode, NULL); // Meanwhile, XLookupString returns keysyms useful for unicode. @@ -1777,8 +1817,6 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { xmblen = 8; } - keysym_unicode = keysym_keycode; - if (xkeyevent->type == KeyPress && xic) { Status status; @@ -1828,6 +1866,7 @@ void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) { input->accumulate_input_event(k); } + memfree(utf8string); return; } memfree(utf8string); @@ -2096,14 +2135,39 @@ void OS_X11::process_xevents() { double rel_x = 0.0; double rel_y = 0.0; + double pressure = 0.0; + double tilt_x = 0.0; + double tilt_y = 0.0; - if (XIMaskIsSet(raw_event->valuators.mask, 0)) { + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSX)) { rel_x = *values; values++; } - if (XIMaskIsSet(raw_event->valuators.mask, 1)) { + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_ABSY)) { rel_y = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_PRESSURE)) { + pressure = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTX)) { + tilt_x = *values; + values++; + } + + if (XIMaskIsSet(raw_event->valuators.mask, VALUATOR_TILTY)) { + tilt_y = *values; + } + + Map<int, Vector3>::Element *pen_info = xi.pen_devices.find(device_id); + if (pen_info) { + Vector3 mult = pen_info->value(); + if (mult.x != 0.0) xi.pressure = pressure / mult.x; + if ((mult.y != 0.0) && (mult.z != 0.0)) xi.tilt = Vector2(tilt_x / mult.y, tilt_y / mult.z); } // https://bugs.freedesktop.org/show_bug.cgi?id=71609 @@ -2418,6 +2482,9 @@ void OS_X11::process_xevents() { Ref<InputEventMouseMotion> mm; mm.instance(); + mm->set_pressure(xi.pressure); + mm->set_tilt(xi.tilt); + // Make the absolute position integral so it doesn't look _too_ weird :) Point2i posi(pos); @@ -2996,6 +3063,8 @@ void OS_X11::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c CursorShape c = current_cursor; current_cursor = CURSOR_MAX; set_cursor_shape(c); + + cursors_cache.erase(p_shape); } } diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h index e6c2effacf..a5576f4402 100644 --- a/platform/x11/os_x11.h +++ b/platform/x11/os_x11.h @@ -131,9 +131,12 @@ class OS_X11 : public OS_Unix { int opcode; Vector<int> touch_devices; Map<int, Vector2> absolute_devices; + Map<int, Vector3> pen_devices; XIEventMask all_event_mask; XIEventMask all_master_event_mask; Map<int, Vector2> state; + double pressure; + Vector2 tilt; Vector2 mouse_pos_to_filter; Vector2 relative_motion; Vector2 raw_pos; |