diff options
Diffstat (limited to 'platform')
108 files changed, 5533 insertions, 3400 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py index 6da1e5f3d6..0accacb679 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -115,7 +115,8 @@ def configure(env):      if env["android_arch"] == "x86_64":          if get_platform(env["ndk_platform"]) < 21:              print( -                "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21" +                "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting" +                " ndk_platform=android-21"              )              env["ndk_platform"] = "android-21"          env["ARCH"] = "arch-x86_64" @@ -136,7 +137,8 @@ def configure(env):      elif env["android_arch"] == "arm64v8":          if get_platform(env["ndk_platform"]) < 21:              print( -                "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21" +                "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting" +                " ndk_platform=android-21"              )              env["ndk_platform"] = "android-21"          env["ARCH"] = "arch-arm64" @@ -164,7 +166,7 @@ def configure(env):      elif env["target"] == "debug":          env.Append(LINKFLAGS=["-O0"])          env.Append(CCFLAGS=["-O0", "-g", "-fno-limit-debug-info"]) -        env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Append(CPPDEFINES=["_DEBUG", "DEBUG_ENABLED"])          env.Append(CPPFLAGS=["-UNDEBUG"])      # Compiler configuration @@ -231,7 +233,10 @@ def configure(env):      env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))])      env.Append( -        CCFLAGS="-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split() +        CCFLAGS=( +            "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden" +            " -fno-strict-aliasing".split() +        )      )      env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"]) diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 7193519a52..3aa2fb5451 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -36,9 +36,6 @@  #include "java_godot_wrapper.h"  #include "os_android.h" -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif  #if defined(VULKAN_ENABLED)  #include "drivers/vulkan/rendering_device_vulkan.h"  #include "platform/android/vulkan/vulkan_context_android.h" @@ -155,12 +152,12 @@ bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {  	return true;  } -void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) { +void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {  	GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();  	ERR_FAIL_COND(!godot_io_java);  	if (godot_io_java->has_vk()) { -		godot_io_java->show_vk(p_existing_text, p_max_length, p_cursor_start, p_cursor_end); +		godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end);  	} else {  		ERR_PRINT("Virtual keyboard not available");  	} diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 4cae52fa76..5cdc69ee83 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -113,7 +113,7 @@ public:  	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;  	virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; -	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); +	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);  	virtual void virtual_keyboard_hide();  	virtual int virtual_keyboard_get_height() const; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index ed85256695..5e6cc3e4e2 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -29,6 +29,7 @@  /*************************************************************************/  #include "export.h" +#include "gradle_export_util.h"  #include "core/io/image_loader.h"  #include "core/io/marshalls.h" @@ -43,6 +44,7 @@  #include "editor/editor_log.h"  #include "editor/editor_node.h"  #include "editor/editor_settings.h" +#include "main/splash.gen.h"  #include "platform/android/export/gradle_export_util.h"  #include "platform/android/logo.gen.h"  #include "platform/android/plugin/godot_plugin_config.h" @@ -199,6 +201,9 @@ static const char *android_perms[] = {  	nullptr  }; +static const char *SPLASH_IMAGE_EXPORT_PATH = "res/drawable/splash.png"; +static const char *SPLASH_BG_COLOR_PATH = "res/drawable/splash_bg_color.png"; +  struct LauncherIcon {  	const char *export_path;  	int dimensions; @@ -452,7 +457,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		String name;  		bool first = true;  		for (int i = 0; i < basename.length(); i++) { -			CharType c = basename[i]; +			char32_t c = basename[i];  			if (c >= '0' && c <= '9' && first) {  				continue;  			} @@ -483,7 +488,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		int segments = 0;  		bool first = true;  		for (int i = 0; i < pname.length(); i++) { -			CharType c = pname[i]; +			char32_t c = pname[i];  			if (first && c == '.') {  				if (r_error) {  					*r_error = TTR("Package segments must be of non-zero length."); @@ -722,7 +727,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		return OK;  	} -	static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { +	static Error save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {  		APKExportData *ed = (APKExportData *)p_userdata;  		String dst_path = p_path.replace_first("res://", "assets/"); @@ -730,7 +735,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		return OK;  	} -	static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { +	static Error ignore_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {  		return OK;  	} @@ -767,6 +772,30 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		}  	} +	void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) { +		String manifest_text = +				"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +				"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" +				"    xmlns:tools=\"http://schemas.android.com/tools\">\n"; + +		manifest_text += _get_screen_sizes_tag(p_preset); +		manifest_text += _get_gles_tag(); + +		Vector<String> perms; +		_get_permissions(p_preset, p_give_internet, perms); +		for (int i = 0; i < perms.size(); i++) { +			manifest_text += vformat("    <uses-permission android:name=\"%s\" />\n", perms.get(i)); +		} + +		manifest_text += _get_xr_features_tag(p_preset); +		manifest_text += _get_instrumentation_tag(p_preset); +		String plugins_names = get_plugins_names(get_enabled_plugins(p_preset)); +		manifest_text += _get_application_tag(p_preset, plugins_names); +		manifest_text += "</manifest>\n"; +		String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); +		store_string_at_path(manifest_path, manifest_text); +	} +  	void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {  		// Leaving the unused types commented because looking these constants up  		// again later would be annoying @@ -848,7 +877,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  						if (string_flags & UTF8_FLAG) {  						} else {  							uint32_t len = decode_uint16(&p_manifest[string_at]); -							Vector<CharType> ucstring; +							Vector<char32_t> ucstring;  							ucstring.resize(len + 1);  							for (uint32_t j = 0; j < len; j++) {  								uint16_t c = decode_uint16(&p_manifest[string_at + 2 + 2 * j]); @@ -1309,7 +1338,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		} else {  			String str;  			for (uint32_t i = 0; i < len; i++) { -				CharType c = decode_uint16(&p_bytes[offset + i * 2]); +				char32_t c = decode_uint16(&p_bytes[offset + i * 2]);  				if (c == 0) {  					break;  				} @@ -1408,23 +1437,143 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  		//printf("end\n");  	} -	void _process_launcher_icons(const String &p_processing_file_name, const Ref<Image> &p_source_image, const LauncherIcon p_icon, Vector<uint8_t> &p_data) { -		if (p_processing_file_name == p_icon.export_path) { -			Ref<Image> working_image = p_source_image; +	void _load_image_data(const Ref<Image> &p_splash_image, Vector<uint8_t> &p_data) { +		Vector<uint8_t> png_buffer; +		Error err = PNGDriverCommon::image_to_png(p_splash_image, png_buffer); +		if (err == OK) { +			p_data.resize(png_buffer.size()); +			memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); +		} else { +			String err_str = String("Failed to convert splash image to png."); +			WARN_PRINT(err_str.utf8().get_data()); +		} +	} + +	void _process_launcher_icons(const String &p_file_name, const Ref<Image> &p_source_image, int dimension, Vector<uint8_t> &p_data) { +		Ref<Image> working_image = p_source_image; -			if (p_source_image->get_width() != p_icon.dimensions || p_source_image->get_height() != p_icon.dimensions) { -				working_image = p_source_image->duplicate(); -				working_image->resize(p_icon.dimensions, p_icon.dimensions, Image::Interpolation::INTERPOLATE_LANCZOS); +		if (p_source_image->get_width() != dimension || p_source_image->get_height() != dimension) { +			working_image = p_source_image->duplicate(); +			working_image->resize(dimension, dimension, Image::Interpolation::INTERPOLATE_LANCZOS); +		} + +		Vector<uint8_t> png_buffer; +		Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); +		if (err == OK) { +			p_data.resize(png_buffer.size()); +			memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); +		} else { +			String err_str = String("Failed to convert resized icon (") + p_file_name + ") to png."; +			WARN_PRINT(err_str.utf8().get_data()); +		} +	} + +	void load_splash_refs(Ref<Image> &splash_image, Ref<Image> &splash_bg_color_image) { +		// TODO: Figure out how to handle remaining boot splash parameters (e.g: fullsize, filter) +		String project_splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + +		if (!project_splash_path.empty()) { +			splash_image.instance(); +			const Error err = ImageLoader::load_image(project_splash_path, splash_image); +			if (err) { +				splash_image.unref();  			} +		} -			Vector<uint8_t> png_buffer; -			Error err = PNGDriverCommon::image_to_png(working_image, png_buffer); -			if (err == OK) { -				p_data.resize(png_buffer.size()); -				memcpy(p_data.ptrw(), png_buffer.ptr(), p_data.size()); -			} else { -				String err_str = String("Failed to convert resized icon (") + p_processing_file_name + ") to png."; -				WARN_PRINT(err_str.utf8().get_data()); +		if (splash_image.is_null()) { +			// Use the default +			splash_image = Ref<Image>(memnew(Image(boot_splash_png))); +		} + +		// Setup the splash bg color +		bool bg_color_valid; +		Color bg_color = ProjectSettings::get_singleton()->get("application/boot_splash/bg_color", &bg_color_valid); +		if (!bg_color_valid) { +			bg_color = boot_splash_bg_color; +		} + +		splash_bg_color_image.instance(); +		splash_bg_color_image->create(splash_image->get_width(), splash_image->get_height(), false, splash_image->get_format()); +		splash_bg_color_image->fill(bg_color); +	} + +	void load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background) { +		String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); + +		icon.instance(); +		foreground.instance(); +		background.instance(); + +		// Regular icon: user selection -> project icon -> default. +		String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); +		if (path.empty() || ImageLoader::load_image(path, icon) != OK) { +			ImageLoader::load_image(project_icon_path, icon); +		} + +		// Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). +		path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); +		if (path.empty() || ImageLoader::load_image(path, foreground) != OK) { +			foreground = icon; +		} + +		// Adaptive background: user selection -> default. +		path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); +		if (!path.empty()) { +			ImageLoader::load_image(path, background); +		} +	} + +	void store_image(const LauncherIcon launcher_icon, const Vector<uint8_t> &data) { +		store_image(launcher_icon.export_path, data); +	} + +	void store_image(const String &export_path, const Vector<uint8_t> &data) { +		String img_path = export_path.insert(0, "res://android/build/"); +		store_file_at_path(img_path, data); +	} + +	void _copy_icons_to_gradle_project(const Ref<EditorExportPreset> &p_preset, +			const Ref<Image> &splash_image, +			const Ref<Image> &splash_bg_color_image, +			const Ref<Image> &main_image, +			const Ref<Image> &foreground, +			const Ref<Image> &background) { +		// Store the splash image +		if (splash_image.is_valid() && !splash_image->empty()) { +			Vector<uint8_t> data; +			_load_image_data(splash_image, data); +			store_image(SPLASH_IMAGE_EXPORT_PATH, data); +		} + +		// Store the splash bg color image +		if (splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) { +			Vector<uint8_t> data; +			_load_image_data(splash_bg_color_image, data); +			store_image(SPLASH_BG_COLOR_PATH, data); +		} + +		// Prepare images to be resized for the icons. If some image ends up being uninitialized, +		// the default image from the export template will be used. + +		for (int i = 0; i < icon_densities_count; ++i) { +			if (main_image.is_valid() && !main_image->empty()) { +				Vector<uint8_t> data; +				_process_launcher_icons(launcher_icons[i].export_path, main_image, launcher_icons[i].dimensions, data); +				store_image(launcher_icons[i], data); +			} + +			if (foreground.is_valid() && !foreground->empty()) { +				Vector<uint8_t> data; +				_process_launcher_icons(launcher_adaptive_icon_foregrounds[i].export_path, foreground, +						launcher_adaptive_icon_foregrounds[i].dimensions, data); +				store_image(launcher_adaptive_icon_foregrounds[i], data); +			} + +			if (background.is_valid() && !background->empty()) { +				Vector<uint8_t> data; +				_process_launcher_icons(launcher_adaptive_icon_backgrounds[i].export_path, background, +						launcher_adaptive_icon_backgrounds[i].dimensions, data); +				store_image(launcher_adaptive_icon_backgrounds[i], data);  			}  		}  	} @@ -1442,7 +1591,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  	}  public: -	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total); +	typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);  public:  	virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) override { @@ -1471,6 +1620,7 @@ public:  		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));  		r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.apk"), ""));  		r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "custom_template/use_custom_build"), false)); +		r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "custom_template/export_format", PROPERTY_HINT_ENUM, "Export APK,Export AAB"), 0));  		Vector<PluginConfig> plugins_configs = get_plugins();  		for (int i = 0; i < plugins_configs.size(); i++) { @@ -1891,6 +2041,13 @@ public:  			}  		} +		if (int(p_preset->get("custom_template/export_format")) == 1 && /*AAB*/ +				!bool(p_preset->get("custom_template/use_custom_build"))) { +			valid = false; +			err += TTR("\"Export AAB\" is only valid when \"Use Custom Build\" is enabled."); +			err += "\n"; +		} +  		r_error = err;  		return valid;  	} @@ -1898,6 +2055,7 @@ public:  	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override {  		List<String> list;  		list.push_back("apk"); +		list.push_back("aab");  		return list;  	} @@ -1924,7 +2082,21 @@ public:  		return have_plugins_changed || first_build;  	} -	Error get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { +	String get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +		int version_code = p_preset->get("version/code"); +		String package_name = p_preset->get("package/unique_name"); +		String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; +		String fullpath = p_path.get_base_dir().plus_file(apk_file_name); +		return fullpath; +	} + +	Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, const String &p_path) { +		String fullpath = get_apk_expansion_fullpath(p_preset, p_path); +		Error err = save_pack(p_preset, fullpath); +		return err; +	} + +	void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) {  		String cmdline = p_preset->get("command_line/extra_args");  		Vector<String> command_line_strings = cmdline.strip_edges().split(" ");  		for (int i = 0; i < command_line_strings.size(); i++) { @@ -1938,17 +2110,8 @@ public:  		bool apk_expansion = p_preset->get("apk_expansion/enable");  		if (apk_expansion) { -			int version_code = p_preset->get("version/code"); -			String package_name = p_preset->get("package/unique_name"); -			String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; -			String fullpath = p_path.get_base_dir().plus_file(apk_file_name); +			String fullpath = get_apk_expansion_fullpath(p_preset, p_path);  			String apk_expansion_public_key = p_preset->get("apk_expansion/public_key"); -			Error err = save_pack(p_preset, fullpath); - -			if (err != OK) { -				EditorNode::add_io_error("Could not write expansion package file: " + apk_file_name); -				return err; -			}  			command_line_strings.push_back("--use_apk_expansion");  			command_line_strings.push_back("--apk_expansion_md5"); @@ -1994,6 +2157,92 @@ public:  				copymem(&r_command_line_flags.write[base + 4], command_line_argument.ptr(), length);  			}  		} +	} + +	Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, String apk_path, EditorProgress ep) { +		String release_keystore = p_preset->get("keystore/release"); +		String release_username = p_preset->get("keystore/release_user"); +		String release_password = p_preset->get("keystore/release_password"); + +		String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); +		if (!FileAccess::exists(jarsigner)) { +			EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); +			return OK; +		} + +		String keystore; +		String password; +		String user; +		if (p_debug) { +			keystore = p_preset->get("keystore/debug"); +			password = p_preset->get("keystore/debug_password"); +			user = p_preset->get("keystore/debug_user"); + +			if (keystore.empty()) { +				keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); +				password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); +				user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); +			} + +			if (ep.step("Signing debug APK...", 103)) { +				return ERR_SKIP; +			} + +		} else { +			keystore = release_keystore; +			password = release_password; +			user = release_username; + +			if (ep.step("Signing release APK...", 103)) { +				return ERR_SKIP; +			} +		} + +		if (!FileAccess::exists(keystore)) { +			EditorNode::add_io_error("Could not find keystore, unable to export."); +			return ERR_FILE_CANT_OPEN; +		} + +		List<String> args; +		args.push_back("-digestalg"); +		args.push_back("SHA-256"); +		args.push_back("-sigalg"); +		args.push_back("SHA256withRSA"); +		String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url"); +		if (tsa_url != "") { +			args.push_back("-tsa"); +			args.push_back(tsa_url); +		} +		args.push_back("-verbose"); +		args.push_back("-keystore"); +		args.push_back(keystore); +		args.push_back("-storepass"); +		args.push_back(password); +		args.push_back(apk_path); +		args.push_back(user); +		int retval; +		OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); +		if (retval) { +			EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); +			return ERR_CANT_CREATE; +		} + +		if (ep.step("Verifying APK...", 104)) { +			return ERR_SKIP; +		} + +		args.clear(); +		args.push_back("-verify"); +		args.push_back("-keystore"); +		args.push_back(keystore); +		args.push_back(apk_path); +		args.push_back("-verbose"); + +		OS::get_singleton()->execute(jarsigner, args, true, NULL, NULL, &retval); +		if (retval) { +			EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); +			return ERR_CANT_CREATE; +		}  		return OK;  	} @@ -2001,41 +2250,102 @@ public:  		ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);  		String src_apk; +		Error err;  		EditorProgress ep("export", "Exporting for Android", 105, true);  		bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build")); +		int export_format = int(p_preset->get("custom_template/export_format")); +		bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); +		bool _signed = p_preset->get("package/signed"); +		bool apk_expansion = p_preset->get("apk_expansion/enable"); +		Vector<String> enabled_abis = get_enabled_abis(p_preset); + +		Ref<Image> splash_image; +		Ref<Image> splash_bg_color_image; +		load_splash_refs(splash_image, splash_bg_color_image); + +		Ref<Image> main_image; +		Ref<Image> foreground; +		Ref<Image> background; + +		load_icon_refs(p_preset, main_image, foreground, background); + +		Vector<uint8_t> command_line_flags; +		// Write command line flags into the command_line_flags variable. +		get_command_line_flags(p_preset, p_path, p_flags, command_line_flags); + +		if (export_format == 1) { +			if (!p_path.ends_with(".aab")) { +				EditorNode::get_singleton()->show_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension.")); +				return ERR_UNCONFIGURED; +			} +			if (apk_expansion) { +				EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle.")); +				return ERR_UNCONFIGURED; +			} +		} +		if (export_format == 0 && !p_path.ends_with(".apk")) { +			EditorNode::get_singleton()->show_warning( +					TTR("Invalid filename! Android APK requires the *.apk extension.")); +			return ERR_UNCONFIGURED; +		} +		if (export_format > 1 || export_format < 0) { +			EditorNode::add_io_error("Unsupported export format!\n"); +			return ERR_UNCONFIGURED; //TODO: is this the right error? +		}  		if (use_custom_build) { -			//re-generate build.gradle and AndroidManifest.xml -			{ //test that installed build version is alright -				FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); -				if (!f) { -					EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); -					return ERR_UNCONFIGURED; -				} -				String version = f->get_line().strip_edges(); -				if (version != VERSION_FULL_CONFIG) { -					EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n   Template installed: %s\n   Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); -					return ERR_UNCONFIGURED; -				} +			//test that installed build version is alright +			FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::READ); +			if (!f) { +				EditorNode::get_singleton()->show_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); +				return ERR_UNCONFIGURED; +			} +			String version = f->get_line().strip_edges(); +			if (version != VERSION_FULL_CONFIG) { +				EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n   Template installed: %s\n   Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); +				return ERR_UNCONFIGURED;  			} +			String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); +			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'.");  			// TODO: should we use "package/name" or "application/config/name"?  			String project_name = get_project_name(p_preset->get("package/name")); -			// instead of calling _fix_resources -			Error err = _create_project_name_strings_files(p_preset, project_name); +			err = _create_project_name_strings_files(p_preset, project_name); //project name localization.  			if (err != OK) {  				EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");  			} -			//build project if custom build is enabled -			String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path"); - -			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'."); +			// Copies the project icon files into the appropriate Gradle project directory. +			_copy_icons_to_gradle_project(p_preset, splash_image, splash_bg_color_image, main_image, foreground, background); +			// Write an AndroidManifest.xml file into the Gradle project directory. +			_write_tmp_manifest(p_preset, p_give_internet, p_debug); + +			//stores all the project files inside the Gradle project directory. Also includes all ABIs +			if (!apk_expansion) { +				DirAccess *da_res = DirAccess::create(DirAccess::ACCESS_RESOURCES); +				if (da_res->dir_exists("res://android/build/assets")) { +					DirAccess *da_assets = DirAccess::open("res://android/build/assets"); +					da_assets->erase_contents_recursive(); +					da_res->remove("res://android/build/assets"); +				} +				err = export_project_files(p_preset, rename_and_store_file_in_gradle_project, NULL, ignore_so_file); +				if (err != OK) { +					EditorNode::add_io_error("Could not export project files to gradle project\n"); +					return err; +				} +			} else { +				err = save_apk_expansion_file(p_preset, p_path); +				if (err != OK) { +					EditorNode::add_io_error("Could not write expansion package file!"); +					return err; +				} +			} +			store_file_at_path("res://android/build/assets/_cl_", command_line_flags);  			OS::get_singleton()->set_environment("ANDROID_HOME", sdk_path); //set and overwrite if required -  			String build_command; +  #ifdef WINDOWS_ENABLED  			build_command = "gradlew.bat";  #else @@ -2043,10 +2353,12 @@ public:  #endif  			String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); -  			build_command = build_path.plus_file(build_command);  			String package_name = get_package_name(p_preset->get("package/unique_name")); +			String version_code = itos(p_preset->get("version/code")); +			String version_name = p_preset->get("version/name"); +			String enabled_abi_string = String("|").join(enabled_abis);  			Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);  			String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins); @@ -2058,8 +2370,20 @@ public:  			if (clean_build_required) {  				cmdline.push_back("clean");  			} -			cmdline.push_back("build"); + +			String build_type = p_debug ? "Debug" : "Release"; +			if (export_format == 1) { +				String bundle_build_command = vformat("bundle%s", build_type); +				cmdline.push_back(bundle_build_command); +			} else if (export_format == 0) { +				String apk_build_command = vformat("assemble%s", build_type); +				cmdline.push_back(apk_build_command); +			} +  			cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name. +			cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code. +			cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name. +			cmdline.push_back("-Pexport_enabled_abis=" + enabled_abi_string); // argument to specify enabled ABIs.  			cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.  			cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.  			cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies. @@ -2077,35 +2401,54 @@ public:  				EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation."));  				return ERR_CANT_CREATE;  			} -			if (p_debug) { -				src_apk = build_path.plus_file("build/outputs/apk/debug/android_debug.apk"); -			} else { -				src_apk = build_path.plus_file("build/outputs/apk/release/android_release.apk"); + +			List<String> copy_args; +			String copy_command; +			if (export_format == 1) { +				copy_command = vformat("copyAndRename%sAab", build_type); +			} else if (export_format == 0) { +				copy_command = vformat("copyAndRename%sApk", build_type);  			} -			if (!FileAccess::exists(src_apk)) { -				EditorNode::get_singleton()->show_warning(TTR("No build apk generated at: ") + "\n" + src_apk); +			copy_args.push_back(copy_command); + +			copy_args.push_back("-p"); // argument to specify the start directory. +			copy_args.push_back(build_path); // start directory. + +			String export_filename = p_path.get_file(); +			String export_path = p_path.get_base_dir(); + +			copy_args.push_back("-Pexport_path=file:" + export_path); +			copy_args.push_back("-Pexport_filename=" + export_filename); + +			int copy_result = EditorNode::get_singleton()->execute_and_show_output(TTR("Moving output"), build_command, copy_args); +			if (copy_result != 0) { +				EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs."));  				return ERR_CANT_CREATE;  			} - -		} else { +			if (_signed) { +				err = sign_apk(p_preset, p_debug, p_path, ep); +				if (err != OK) { +					return err; +				} +			} +			return OK; +		} +		// This is the start of the Legacy build system +		if (p_debug) +			src_apk = p_preset->get("custom_template/debug"); +		else +			src_apk = p_preset->get("custom_template/release"); +		src_apk = src_apk.strip_edges(); +		if (src_apk == "") {  			if (p_debug) { -				src_apk = p_preset->get("custom_template/debug"); +				src_apk = find_export_template("android_debug.apk");  			} else { -				src_apk = p_preset->get("custom_template/release"); +				src_apk = find_export_template("android_release.apk");  			} - -			src_apk = src_apk.strip_edges();  			if (src_apk == "") { -				if (p_debug) { -					src_apk = find_export_template("android_debug.apk"); -				} else { -					src_apk = find_export_template("android_release.apk"); -				} -				if (src_apk == "") { -					EditorNode::add_io_error("Package not found: " + src_apk); -					return ERR_FILE_NOT_FOUND; -				} +				EditorNode::add_io_error("Package not found: " + src_apk); +				return ERR_FILE_NOT_FOUND;  			}  		} @@ -2142,50 +2485,13 @@ public:  		zipFile unaligned_apk = zipOpen2(tmp_unaligned_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io2); -		bool _signed = p_preset->get("package/signed");  		String cmdline = p_preset->get("command_line/extra_args");  		String version_name = p_preset->get("version/name");  		String package_name = p_preset->get("package/unique_name"); -		bool apk_expansion = p_preset->get("apk_expansion/enable");  		String apk_expansion_pkey = p_preset->get("apk_expansion/public_key"); -		String release_keystore = p_preset->get("keystore/release"); -		String release_username = p_preset->get("keystore/release_user"); -		String release_password = p_preset->get("keystore/release_password"); - -		Vector<String> enabled_abis = get_enabled_abis(p_preset); - -		String project_icon_path = ProjectSettings::get_singleton()->get("application/config/icon"); - -		// Prepare images to be resized for the icons. If some image ends up being uninitialized, the default image from the export template will be used. -		Ref<Image> launcher_icon_image; -		Ref<Image> launcher_adaptive_icon_foreground_image; -		Ref<Image> launcher_adaptive_icon_background_image; - -		launcher_icon_image.instance(); -		launcher_adaptive_icon_foreground_image.instance(); -		launcher_adaptive_icon_background_image.instance(); - -		// Regular icon: user selection -> project icon -> default. -		String path = static_cast<String>(p_preset->get(launcher_icon_option)).strip_edges(); -		if (path.empty() || ImageLoader::load_image(path, launcher_icon_image) != OK) { -			ImageLoader::load_image(project_icon_path, launcher_icon_image); -		} - -		// Adaptive foreground: user selection -> regular icon (user selection -> project icon -> default). -		path = static_cast<String>(p_preset->get(launcher_adaptive_icon_foreground_option)).strip_edges(); -		if (path.empty() || ImageLoader::load_image(path, launcher_adaptive_icon_foreground_image) != OK) { -			launcher_adaptive_icon_foreground_image = launcher_icon_image; -		} - -		// Adaptive background: user selection -> default. -		path = static_cast<String>(p_preset->get(launcher_adaptive_icon_background_option)).strip_edges(); -		if (!path.empty()) { -			ImageLoader::load_image(path, launcher_adaptive_icon_background_image); -		} -  		Vector<String> invalid_abis(enabled_abis);  		while (ret == UNZ_OK) {  			//get filename @@ -2206,26 +2512,38 @@ public:  			unzCloseCurrentFile(pkg);  			//write -  			if (file == "AndroidManifest.xml") { -				_fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG)); +				_fix_manifest(p_preset, data, p_give_internet);  			} -  			if (file == "resources.arsc") { -				if (!use_custom_build) { -					_fix_resources(p_preset, data); -				} +				_fix_resources(p_preset, data); +			} + +			// Process the splash image +			if (file == SPLASH_IMAGE_EXPORT_PATH && splash_image.is_valid() && !splash_image->empty()) { +				_load_image_data(splash_image, data); +			} + +			// Process the splash bg color image +			if (file == SPLASH_BG_COLOR_PATH && splash_bg_color_image.is_valid() && !splash_bg_color_image->empty()) { +				_load_image_data(splash_bg_color_image, data);  			}  			for (int i = 0; i < icon_densities_count; ++i) { -				if (launcher_icon_image.is_valid() && !launcher_icon_image->empty()) { -					_process_launcher_icons(file, launcher_icon_image, launcher_icons[i], data); +				if (main_image.is_valid() && !main_image->empty()) { +					if (file == launcher_icons[i].export_path) { +						_process_launcher_icons(file, main_image, launcher_icons[i].dimensions, data); +					}  				} -				if (launcher_adaptive_icon_foreground_image.is_valid() && !launcher_adaptive_icon_foreground_image->empty()) { -					_process_launcher_icons(file, launcher_adaptive_icon_foreground_image, launcher_adaptive_icon_foregrounds[i], data); +				if (foreground.is_valid() && !foreground->empty()) { +					if (file == launcher_adaptive_icon_foregrounds[i].export_path) { +						_process_launcher_icons(file, foreground, launcher_adaptive_icon_foregrounds[i].dimensions, data); +					}  				} -				if (launcher_adaptive_icon_background_image.is_valid() && !launcher_adaptive_icon_background_image->empty()) { -					_process_launcher_icons(file, launcher_adaptive_icon_background_image, launcher_adaptive_icon_backgrounds[i], data); +				if (background.is_valid() && !background->empty()) { +					if (file == launcher_adaptive_icon_backgrounds[i].export_path) { +						_process_launcher_icons(file, background, launcher_adaptive_icon_backgrounds[i].dimensions, data); +					}  				}  			} @@ -2283,18 +2601,26 @@ public:  		if (ep.step("Adding files...", 1)) {  			CLEANUP_AND_RETURN(ERR_SKIP);  		} -		Error err = OK; +		err = OK;  		if (p_flags & DEBUG_FLAG_DUMB_CLIENT) {  			APKExportData ed;  			ed.ep = &ep;  			ed.apk = unaligned_apk;  			err = export_project_files(p_preset, ignore_apk_file, &ed, save_apk_so); -		} else if (!apk_expansion) { -			APKExportData ed; -			ed.ep = &ep; -			ed.apk = unaligned_apk; -			err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so); +		} else { +			if (apk_expansion) { +				err = save_apk_expansion_file(p_preset, p_path); +				if (err != OK) { +					EditorNode::add_io_error("Could not write expansion package file!"); +					return err; +				} +			} else { +				APKExportData ed; +				ed.ep = &ep; +				ed.apk = unaligned_apk; +				err = export_project_files(p_preset, save_apk_file, &ed, save_apk_so); +			}  		}  		if (err != OK) { @@ -2303,10 +2629,6 @@ public:  			CLEANUP_AND_RETURN(ERR_SKIP);  		} -		Vector<uint8_t> command_line_flags; -		// Write command line flags into the command_line_flags variable. -		err = get_command_line_flags(p_preset, p_path, p_flags, command_line_flags); -  		zip_fileinfo zipfi = get_zip_fileinfo();  		zipOpenNewFileInZip(unaligned_apk,  				"assets/_cl_", @@ -2328,84 +2650,9 @@ public:  		}  		if (_signed) { -			String jarsigner = EditorSettings::get_singleton()->get("export/android/jarsigner"); -			if (!FileAccess::exists(jarsigner)) { -				EditorNode::add_io_error("'jarsigner' could not be found.\nPlease supply a path in the Editor Settings.\nThe resulting APK is unsigned."); -				CLEANUP_AND_RETURN(OK); -			} - -			String keystore; -			String password; -			String user; -			if (p_debug) { -				keystore = p_preset->get("keystore/debug"); -				password = p_preset->get("keystore/debug_password"); -				user = p_preset->get("keystore/debug_user"); - -				if (keystore.empty()) { -					keystore = EditorSettings::get_singleton()->get("export/android/debug_keystore"); -					password = EditorSettings::get_singleton()->get("export/android/debug_keystore_pass"); -					user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); -				} - -				if (ep.step("Signing debug APK...", 103)) { -					CLEANUP_AND_RETURN(ERR_SKIP); -				} - -			} else { -				keystore = release_keystore; -				password = release_password; -				user = release_username; - -				if (ep.step("Signing release APK...", 103)) { -					CLEANUP_AND_RETURN(ERR_SKIP); -				} -			} - -			if (!FileAccess::exists(keystore)) { -				EditorNode::add_io_error("Could not find keystore, unable to export."); -				CLEANUP_AND_RETURN(ERR_FILE_CANT_OPEN); -			} - -			List<String> args; -			args.push_back("-digestalg"); -			args.push_back("SHA-256"); -			args.push_back("-sigalg"); -			args.push_back("SHA256withRSA"); -			String tsa_url = EditorSettings::get_singleton()->get("export/android/timestamping_authority_url"); -			if (tsa_url != "") { -				args.push_back("-tsa"); -				args.push_back(tsa_url); -			} -			args.push_back("-verbose"); -			args.push_back("-keystore"); -			args.push_back(keystore); -			args.push_back("-storepass"); -			args.push_back(password); -			args.push_back(tmp_unaligned_path); -			args.push_back(user); -			int retval; -			OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval); -			if (retval) { -				EditorNode::add_io_error("'jarsigner' returned with error #" + itos(retval)); -				CLEANUP_AND_RETURN(ERR_CANT_CREATE); -			} - -			if (ep.step("Verifying APK...", 104)) { -				CLEANUP_AND_RETURN(ERR_SKIP); -			} - -			args.clear(); -			args.push_back("-verify"); -			args.push_back("-keystore"); -			args.push_back(keystore); -			args.push_back(tmp_unaligned_path); -			args.push_back("-verbose"); - -			OS::get_singleton()->execute(jarsigner, args, true, nullptr, nullptr, &retval); -			if (retval) { -				EditorNode::add_io_error("'jarsigner' verification of APK failed. Make sure to use a jarsigner from OpenJDK 8."); -				CLEANUP_AND_RETURN(ERR_CANT_CREATE); +			err = sign_apk(p_preset, p_debug, tmp_unaligned_path, ep); +			if (err != OK) { +				CLEANUP_AND_RETURN(err);  			}  		} diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 622860c307..95f870bc35 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -99,7 +99,7 @@ Error store_string_at_path(const String &p_path, const String &p_data) {  // It is used by the export_project_files method to save all the asset files into the gradle project.  // It's functionality mirrors that of the method save_apk_file.  // This method will be called ONLY when custom build is enabled. -Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { +Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {  	String dst_path = p_path.replace_first("res://", "res://android/build/assets/");  	Error err = store_file_at_path(dst_path, p_data);  	return err; @@ -142,4 +142,104 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset  	return OK;  } +String bool_to_string(bool v) { +	return v ? "true" : "false"; +} + +String _get_gles_tag() { +	bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" && +					 !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"); +	return min_gles3 ? "    <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : ""; +} + +String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) { +	String manifest_screen_sizes = "    <supports-screens \n        tools:node=\"replace\""; +	String sizes[] = { "small", "normal", "large", "xlarge" }; +	size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]); +	for (size_t i = 0; i < num_sizes; i++) { +		String feature_name = vformat("screen/support_%s", sizes[i]); +		String feature_support = bool_to_string(p_preset->get(feature_name)); +		String xml_entry = vformat("\n        android:%sScreens=\"%s\"", sizes[i], feature_support); +		manifest_screen_sizes += xml_entry; +	} +	manifest_screen_sizes += " />\n"; +	return manifest_screen_sizes; +} + +String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) { +	String manifest_xr_features; +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	if (uses_xr) { +		int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof +		if (dof_index == 1) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n"; +		} else if (dof_index == 2) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n"; +		} +		int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required +		if (hand_tracking_index == 1) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n"; +		} else if (hand_tracking_index == 2) { +			manifest_xr_features += "    <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n"; +		} +	} +	return manifest_xr_features; +} + +String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) { +	String package_name = p_preset->get("package/unique_name"); +	String manifest_instrumentation_text = vformat( +			"    <instrumentation\n" +			"        tools:node=\"replace\"\n" +			"        android:name=\".GodotInstrumentation\"\n" +			"        android:icon=\"@mipmap/icon\"\n" +			"        android:label=\"@string/godot_project_name_string\"\n" +			"        android:targetPackage=\"%s\" />\n", +			package_name); +	return manifest_instrumentation_text; +} + +String _get_plugins_tag(const String &plugins_names) { +	if (!plugins_names.empty()) { +		return vformat("    <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names); +	} else { +		return "    <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n"; +	} +} + +String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) { +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape"; +	String manifest_activity_text = vformat( +			"        <activity android:name=\"com.godot.game.GodotApp\" " +			"tools:replace=\"android:screenOrientation\" " +			"android:screenOrientation=\"%s\">\n", +			orientation); +	if (uses_xr) { +		String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness")); +		manifest_activity_text += vformat("            <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness); +	} else { +		manifest_activity_text += "            <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n"; +	} +	manifest_activity_text += "        </activity>\n"; +	return manifest_activity_text; +} + +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) { +	bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1; +	String manifest_application_text = +			"    <application android:label=\"@string/godot_project_name_string\"\n" +			"        android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n" +			"        android:icon=\"@mipmap/icon\">)\n\n" +			"        <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n"; + +	manifest_application_text += _get_plugins_tag(plugins_names); +	if (uses_xr) { +		manifest_application_text += "        <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; +	} +	manifest_application_text += _get_activity_tag(p_preset); +	manifest_application_text += "    </application>\n"; +	return manifest_application_text; +} +  #endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 48c09552c1..e94681659c 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -38,7 +38,7 @@          <activity              android:name=".GodotApp"              android:label="@string/godot_project_name_string" -            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen" +            android:theme="@style/GodotAppSplashTheme"              android:launchMode="singleTask"              android:screenOrientation="landscape"              android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 19202d2310..ceacfec9e1 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -80,8 +80,15 @@ android {              ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"          } +        ndk { +            String[] export_abi_list = getExportEnabledABIs() +            abiFilters export_abi_list +        } +          // Feel free to modify the application id to your own.          applicationId getExportPackageName() +        versionCode getExportVersionCode() +        versionName getExportVersionName()          minSdkVersion versions.minSdk          targetSdkVersion versions.targetSdk      } @@ -123,3 +130,27 @@ android {          }      }  } + +task copyAndRenameDebugApk(type: Copy) { +    from "$buildDir/outputs/apk/debug/android_debug.apk" +    into getExportPath() +    rename "android_debug.apk", getExportFilename() +} + +task copyAndRenameReleaseApk(type: Copy) { +    from "$buildDir/outputs/apk/release/android_release.apk" +    into getExportPath() +    rename "android_release.apk", getExportFilename() +} + +task copyAndRenameDebugAab(type: Copy) { +    from "$buildDir/outputs/bundle/debug/build-debug.aab" +    into getExportPath() +    rename "build-debug.aab", getExportFilename() +} + +task copyAndRenameReleaseAab(type: Copy) { +    from "$buildDir/outputs/bundle/release/build-release.aab" +    into getExportPath() +    rename "build-release.aab", getExportFilename() +} diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index acfdef531e..d1176e6196 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -28,8 +28,55 @@ ext.getExportPackageName = { ->      return appId  } +ext.getExportVersionCode = { -> +    String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : "" +    if (versionCode == null || versionCode.isEmpty()) { +        versionCode = "1" +    } +    return Integer.parseInt(versionCode) +} + +ext.getExportVersionName = { -> +    String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : "" +    if (versionName == null || versionName.isEmpty()) { +        versionName = "1.0" +    } +    return versionName +} +  final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|" +// get the list of ABIs the project should be exported to +ext.getExportEnabledABIs = { -> +    String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""; +    if (enabledABIs == null || enabledABIs.isEmpty()) { +        enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" +    } +    Set<String> exportAbiFilter = []; +    for (String abi_name : enabledABIs.split(PLUGIN_VALUE_SEPARATOR_REGEX)) { +        if (!abi_name.trim().isEmpty()){ +            exportAbiFilter.add(abi_name); +        } +    } +    return exportAbiFilter; +} + +ext.getExportPath = { +    String exportPath = project.hasProperty("export_path") ? project.property("export_path") : "" +    if (exportPath == null || exportPath.isEmpty()) { +        exportPath = "." +    } +    return exportPath +} + +ext.getExportFilename = { +    String exportFilename = project.hasProperty("export_filename") ? project.property("export_filename") : "" +    if (exportFilename == null || exportFilename.isEmpty()) { +        exportFilename = "godot_android" +    } +    return exportFilename +} +  /**   * Parse the project properties for the 'plugins_maven_repos' property and return the list   * of maven repos. diff --git a/platform/android/java/app/res/drawable/splash.png b/platform/android/java/app/res/drawable/splash.png Binary files differnew file mode 100644 index 0000000000..7bddd4325a --- /dev/null +++ b/platform/android/java/app/res/drawable/splash.png diff --git a/platform/android/java/app/res/drawable/splash_bg_color.png b/platform/android/java/app/res/drawable/splash_bg_color.png Binary files differnew file mode 100644 index 0000000000..004b6fd508 --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_bg_color.png diff --git a/platform/android/java/app/res/drawable/splash_drawable.xml b/platform/android/java/app/res/drawable/splash_drawable.xml new file mode 100644 index 0000000000..2794a40817 --- /dev/null +++ b/platform/android/java/app/res/drawable/splash_drawable.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + +	<item android:drawable="@drawable/splash_bg_color" /> + +	<item> +		<bitmap +				android:gravity="center" +				android:src="@drawable/splash" /> +	</item> + +</layer-list> diff --git a/platform/android/java/app/res/values/themes.xml b/platform/android/java/app/res/values/themes.xml new file mode 100644 index 0000000000..26912538d3 --- /dev/null +++ b/platform/android/java/app/res/values/themes.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + +	<style name="GodotAppMainTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen"/> + +	<style name="GodotAppSplashTheme" parent="@style/GodotAppMainTheme"> +		<item name="android:windowBackground">@drawable/splash_drawable</item> +	</style> +</resources> 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 1af5950cbe..51df70969e 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -32,9 +32,16 @@ package com.godot.game;  import org.godotengine.godot.FullScreenGodotApp; +import android.os.Bundle; +  /**   * Template activity for Godot Android custom builds.   * Feel free to extend and modify this class for your custom logic.   */  public class GodotApp extends FullScreenGodotApp { +	@Override +	public void onCreate(Bundle savedInstanceState) { +		setTheme(R.style.GodotAppMainTheme); +		super.onCreate(savedInstanceState); +	}  } diff --git a/platform/android/java/lib/res/values/dimens.xml b/platform/android/java/lib/res/values/dimens.xml new file mode 100644 index 0000000000..9034dbbcc1 --- /dev/null +++ b/platform/android/java/lib/res/values/dimens.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> +	<dimen name="text_edit_height">48dp</dimen> +</resources> diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java index 138c2de94c..5aa48d87da 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -34,6 +34,8 @@ import android.content.Intent;  import android.os.Bundle;  import android.view.KeyEvent; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable;  import androidx.fragment.app.FragmentActivity;  /** @@ -43,13 +45,18 @@ import androidx.fragment.app.FragmentActivity;   * within an Android app.   */  public abstract class FullScreenGodotApp extends FragmentActivity { -	protected Godot godotFragment; +	@Nullable +	private Godot godotFragment;  	@Override  	public void onCreate(Bundle savedInstanceState) {  		super.onCreate(savedInstanceState);  		setContentView(R.layout.godot_app_layout); -		godotFragment = new Godot(); +		godotFragment = initGodotInstance(); +		if (godotFragment == null) { +			throw new IllegalStateException("Godot instance must be non-null."); +		} +  		getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss();  	} @@ -76,4 +83,17 @@ public abstract class FullScreenGodotApp extends FragmentActivity {  		}  		return super.onKeyMultiple(inKeyCode, repeatCount, event);  	} + +	/** +	 * Used to initialize the Godot fragment instance in {@link FullScreenGodotApp#onCreate(Bundle)}. +	 */ +	@NonNull +	protected Godot initGodotInstance() { +		return new Godot(); +	} + +	@Nullable +	protected final Godot getGodotFragment() { +		return godotFragment; +	}  } 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 1ae400abb5..524f32bf5e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -151,8 +151,7 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  	private void setButtonPausedState(boolean paused) {  		mStatePaused = paused; -		int stringResourceID = paused ? R.string.text_button_resume : -										R.string.text_button_pause; +		int stringResourceID = paused ? R.string.text_button_resume : R.string.text_button_pause;  		mPauseButton.setText(stringResourceID);  	} @@ -221,7 +220,8 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		// GodotEditText layout  		GodotEditText editText = new GodotEditText(activity); -		editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); +		editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, +				(int)getResources().getDimension(R.dimen.text_edit_height)));  		// ...add to FrameLayout  		containerLayout.addView(editText); @@ -467,7 +467,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		final Activity activity = getActivity();  		Window window = activity.getWindow();  		window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); -		window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);  		mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE);  		pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 4dd228e53b..c2f3c88416 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -461,9 +461,9 @@ public class GodotIO {  		return (int)(metrics.density * 160f);  	} -	public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) { +	public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {  		if (edit != null) -			edit.showKeyboard(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end); +			edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);  		//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);  		//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java index c0defd008e..c95339c583 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java @@ -36,6 +36,7 @@ import android.content.Context;  import android.os.Handler;  import android.os.Message;  import android.text.InputFilter; +import android.text.InputType;  import android.util.AttributeSet;  import android.view.KeyEvent;  import android.view.inputmethod.EditorInfo; @@ -58,7 +59,8 @@ public class GodotEditText extends EditText {  	private GodotTextInputWrapper mInputWrapper;  	private EditHandler sHandler = new EditHandler(this);  	private String mOriginText; -	private int mMaxInputLength; +	private int mMaxInputLength = Integer.MAX_VALUE; +	private boolean mMultiline = false;  	private static class EditHandler extends Handler {  		private final WeakReference<GodotEditText> mEdit; @@ -95,7 +97,11 @@ public class GodotEditText extends EditText {  	protected void initView() {  		setPadding(0, 0, 0, 0); -		setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); +		setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE); +	} + +	public boolean isMultiline() { +		return mMultiline;  	}  	private void handleMessage(final Message msg) { @@ -115,6 +121,12 @@ public class GodotEditText extends EditText {  						edit.mInputWrapper.setSelection(false);  					} +					int inputType = InputType.TYPE_CLASS_TEXT; +					if (edit.isMultiline()) { +						inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE; +					} +					edit.setInputType(inputType); +  					edit.mInputWrapper.setOriginText(text);  					edit.addTextChangedListener(edit.mInputWrapper);  					final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); @@ -189,7 +201,7 @@ public class GodotEditText extends EditText {  	// ===========================================================  	// Methods  	// =========================================================== -	public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) { +	public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {  		int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;  		if (p_cursor_start == -1) { // cursor position not given  			this.mOriginText = p_existing_text; @@ -202,6 +214,8 @@ public class GodotEditText extends EditText {  			this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);  		} +		this.mMultiline = p_multiline; +  		final Message msg = new Message();  		msg.what = HANDLER_OPEN_IME_KEYBOARD;  		msg.obj = this; diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java index 9c7cf9f341..4dd1054738 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java @@ -123,7 +123,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene  			public void run() {  				for (int i = 0; i < count; ++i) {  					int key = newChars[i]; -					if (key == '\n') { +					if ((key == '\n') && !mEdit.isMultiline()) {  						// Return keys are handled through action events  						continue;  					} @@ -151,7 +151,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene  			});  		} -		if (pActionID == EditorInfo.IME_NULL) { +		if (pActionID == EditorInfo.IME_ACTION_DONE) {  			// Enter key has been pressed  			GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);  			GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false); diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt index aeb4628d5d..7fa8e3b4e5 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkRenderer.kt @@ -71,7 +71,7 @@ internal class VkRenderer {  	 */  	fun onVkSurfaceChanged(surface: Surface, width: Int, height: Int) {  		GodotLib.resize(surface, width, height) -		 +  		for (plugin in pluginRegistry.getAllPlugins()) {  			plugin.onVkSurfaceChanged(surface, width, height)  		} diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 0a42adeaf2..4ccbc6b97e 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc  		_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");  		_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");  		_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); -		_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;III)V"); +		_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");  		_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");  		_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");  		_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); @@ -132,11 +132,11 @@ bool GodotIOJavaWrapper::has_vk() {  	return (_show_keyboard != 0) && (_hide_keyboard != 0);  } -void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end) { +void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {  	if (_show_keyboard) {  		JNIEnv *env = ThreadAndroid::get_env();  		jstring jStr = env->NewStringUTF(p_existing.utf8().get_data()); -		env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length, p_cursor_start, p_cursor_end); +		env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);  	}  } diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index 1742021379..6465ded985 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -70,7 +70,7 @@ public:  	int get_screen_dpi();  	String get_unique_id();  	bool has_vk(); -	void show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end); +	void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);  	void hide_vk();  	int get_vk_height();  	void set_vk_height(int p_height); diff --git a/platform/android/logo.png b/platform/android/logo.png Binary files differindex df445f6a9c..f44d360a25 100644 --- a/platform/android/logo.png +++ b/platform/android/logo.png diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index b72d29149c..848fd9713a 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -3,10 +3,8 @@  Import("env")  iphone_lib = [ -    "godot_iphone.cpp", -    "os_iphone.cpp", -    "semaphore_iphone.cpp", -    "gl_view.mm", +    "godot_iphone.mm", +    "os_iphone.mm",      "main.m",      "app_delegate.mm",      "view_controller.mm", @@ -15,6 +13,12 @@ iphone_lib = [      "icloud.mm",      "ios.mm",      "vulkan_context_iphone.mm", +    "display_server_iphone.mm", +    "joypad_iphone.mm", +    "godot_view.mm", +    "display_layer.mm", +    "godot_view_renderer.mm", +    "godot_view_gesture_recognizer.mm",  ]  env_ios = env.Clone() diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h index 27552d781a..2f082f1e07 100644 --- a/platform/iphone/app_delegate.h +++ b/platform/iphone/app_delegate.h @@ -28,29 +28,20 @@  /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */  /*************************************************************************/ -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif -#import "view_controller.h"  #import <UIKit/UIKit.h> -#import <CoreMotion/CoreMotion.h> +@class ViewController;  // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,  // so it can't be done with compilation time branching.  //#if defined(OPENGL_ENABLED)  //@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {  //#endif -#if defined(VULKAN_ENABLED) -@interface AppDelegate : NSObject <UIApplicationDelegate> { -#endif -	//@property (strong, nonatomic) UIWindow *window; -	ViewController *view_controller; -	bool is_focus_out; -}; +//#if defined(VULKAN_ENABLED) +@interface AppDelegate : NSObject <UIApplicationDelegate> +//#endif  @property(strong, nonatomic) UIWindow *window; - -+ (ViewController *)getViewController; +@property(strong, class, readonly, nonatomic) ViewController *viewController;  @end diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm index c4ef185bf1..7edbcc4667 100644 --- a/platform/iphone/app_delegate.mm +++ b/platform/iphone/app_delegate.mm @@ -29,644 +29,60 @@  /*************************************************************************/  #import "app_delegate.h" -  #include "core/project_settings.h"  #include "drivers/coreaudio/audio_driver_coreaudio.h" -#if defined(OPENGL_ENABLED) -#import "gl_view.h" -#endif +#import "godot_view.h"  #include "main/main.h"  #include "os_iphone.h" +#import "view_controller.h" -#import "GameController/GameController.h"  #import <AudioToolbox/AudioServices.h> -#define kFilteringFactor 0.1  #define kRenderingFrequency 60 -#define kAccelerometerFrequency 100.0 // Hz - -Error _shell_open(String); -void _set_keep_screen_on(bool p_enabled); - -Error _shell_open(String p_uri) { -	NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; - -	if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]]) -		return ERR_CANT_OPEN; - -	printf("opening url %ls\n", p_uri.c_str()); -	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]]; -	[url release]; -	return OK; -}; - -void _set_keep_screen_on(bool p_enabled) { -	[[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled]; -}; - -void _vibrate() { -	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); -}; - -@implementation AppDelegate - -@synthesize window;  extern int gargc;  extern char **gargv; -extern int iphone_main(int, int, int, char **, String); + +extern int iphone_main(int, char **, String);  extern void iphone_finish(); -CMMotionManager *motionManager; -bool motionInitialised; +@implementation AppDelegate  static ViewController *mainViewController = nil; -+ (ViewController *)getViewController { -	return mainViewController; -} - -NSMutableDictionary *ios_joysticks = nil; -NSMutableArray *pending_ios_joysticks = nil; - -- (GCControllerPlayerIndex)getFreePlayerIndex { -	bool have_player_1 = false; -	bool have_player_2 = false; -	bool have_player_3 = false; -	bool have_player_4 = false; - -	if (ios_joysticks == nil) { -		NSArray *keys = [ios_joysticks allKeys]; -		for (NSNumber *key in keys) { -			GCController *controller = [ios_joysticks objectForKey:key]; -			if (controller.playerIndex == GCControllerPlayerIndex1) { -				have_player_1 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex2) { -				have_player_2 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex3) { -				have_player_3 = true; -			} else if (controller.playerIndex == GCControllerPlayerIndex4) { -				have_player_4 = true; -			}; -		}; -	}; - -	if (!have_player_1) { -		return GCControllerPlayerIndex1; -	} else if (!have_player_2) { -		return GCControllerPlayerIndex2; -	} else if (!have_player_3) { -		return GCControllerPlayerIndex3; -	} else if (!have_player_4) { -		return GCControllerPlayerIndex4; -	} else { -		return GCControllerPlayerIndexUnset; -	}; -}; - -void _ios_add_joystick(GCController *controller, AppDelegate *delegate) { -	// get a new id for our controller -	int joy_id = OSIPhone::get_singleton()->get_unused_joy_id(); -	if (joy_id != -1) { -		// assign our player index -		if (controller.playerIndex == GCControllerPlayerIndexUnset) { -			controller.playerIndex = [delegate getFreePlayerIndex]; -		}; - -		// tell Godot about our new controller -		OSIPhone::get_singleton()->joy_connection_changed( -				joy_id, true, [controller.vendorName UTF8String]); - -		// add it to our dictionary, this will retain our controllers -		[ios_joysticks setObject:controller -						  forKey:[NSNumber numberWithInt:joy_id]]; - -		// set our input handler -		[delegate setControllerInputHandler:controller]; -	} else { -		printf("Couldn't retrieve new joy id\n"); -	}; -} - -static void on_focus_out(ViewController *view_controller, bool *is_focus_out) { -	if (!*is_focus_out) { -		*is_focus_out = true; -		if (OS::get_singleton()->get_main_loop()) -			OS::get_singleton()->get_main_loop()->notification( -					MainLoop::NOTIFICATION_WM_FOCUS_OUT); - -		[view_controller.view stopAnimation]; -		if (OS::get_singleton()->native_video_is_playing()) { -			OSIPhone::get_singleton()->native_video_focus_out(); -		} -		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); -		if (audio) -			audio->stop(); -	} -} - -static void on_focus_in(ViewController *view_controller, bool *is_focus_out) { -	if (*is_focus_out) { -		*is_focus_out = false; -		if (OS::get_singleton()->get_main_loop()) -			OS::get_singleton()->get_main_loop()->notification( -					MainLoop::NOTIFICATION_WM_FOCUS_IN); - -		[view_controller.view startAnimation]; -		if (OSIPhone::get_singleton()->native_video_is_playing()) { -			OSIPhone::get_singleton()->native_video_unpause(); -		} - -		AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton()); -		if (audio) -			audio->start(); -	} ++ (ViewController *)viewController { +	return mainViewController;  } -- (void)controllerWasConnected:(NSNotification *)notification { -	// create our dictionary if we don't have one yet -	if (ios_joysticks == nil) { -		ios_joysticks = [[NSMutableDictionary alloc] init]; -	}; - -	// get our controller -	GCController *controller = (GCController *)notification.object; -	if (controller == nil) { -		printf("Couldn't retrieve new controller\n"); -	} else if ([[ios_joysticks allKeysForObject:controller] count] != 0) { -		printf("Controller is already registered\n"); -	} else if (frame_count > 1) { -		_ios_add_joystick(controller, self); -	} else { -		if (pending_ios_joysticks == nil) -			pending_ios_joysticks = [[NSMutableArray alloc] init]; -		[pending_ios_joysticks addObject:controller]; -	}; -}; - -- (void)controllerWasDisconnected:(NSNotification *)notification { -	if (ios_joysticks != nil) { -		// find our joystick, there should be only one in our dictionary -		GCController *controller = (GCController *)notification.object; -		NSArray *keys = [ios_joysticks allKeysForObject:controller]; -		for (NSNumber *key in keys) { -			// tell Godot this joystick is no longer there -			int joy_id = [key intValue]; -			OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, ""); - -			// and remove it from our dictionary -			[ios_joysticks removeObjectForKey:key]; -		}; -	}; -}; - -- (int)getJoyIdForController:(GCController *)controller { -	if (ios_joysticks != nil) { -		// find our joystick, there should be only one in our dictionary -		NSArray *keys = [ios_joysticks allKeysForObject:controller]; -		for (NSNumber *key in keys) { -			int joy_id = [key intValue]; -			return joy_id; -		}; -	}; - -	return -1; -}; - -- (void)setControllerInputHandler:(GCController *)controller { -	// Hook in the callback handler for the correct gamepad profile. -	// This is a bit of a weird design choice on Apples part. -	// You need to select the most capable gamepad profile for the -	// gamepad attached. -	if (controller.extendedGamepad != nil) { -		// The extended gamepad profile has all the input you could possibly find on -		// a gamepad but will only be active if your gamepad actually has all of -		// these... -		controller.extendedGamepad.valueChangedHandler = ^( -				GCExtendedGamepad *gamepad, GCControllerElement *element) { -			int joy_id = [self getJoyIdForController:controller]; - -			if (element == gamepad.buttonA) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -						gamepad.buttonA.isPressed); -			} else if (element == gamepad.buttonB) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, -						gamepad.buttonB.isPressed); -			} else if (element == gamepad.buttonX) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -						gamepad.buttonX.isPressed); -			} else if (element == gamepad.buttonY) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, -						gamepad.buttonY.isPressed); -			} else if (element == gamepad.leftShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, -						gamepad.leftShoulder.isPressed); -			} else if (element == gamepad.rightShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, -						gamepad.rightShoulder.isPressed); -			} else if (element == gamepad.dpad) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -						gamepad.dpad.up.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -						gamepad.dpad.down.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -						gamepad.dpad.left.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -						gamepad.dpad.right.isPressed); -			}; - -			InputDefault::JoyAxis jx; -			jx.min = -1; -			if (element == gamepad.leftThumbstick) { -				jx.value = gamepad.leftThumbstick.xAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); -				jx.value = -gamepad.leftThumbstick.yAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); -			} else if (element == gamepad.rightThumbstick) { -				jx.value = gamepad.rightThumbstick.xAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); -				jx.value = -gamepad.rightThumbstick.yAxis.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); -			} else if (element == gamepad.leftTrigger) { -				jx.value = gamepad.leftTrigger.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); -			} else if (element == gamepad.rightTrigger) { -				jx.value = gamepad.rightTrigger.value; -				OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); -			}; -		}; -	} else if (controller.gamepad != nil) { -		// gamepad is the standard profile with 4 buttons, shoulder buttons and a -		// D-pad -		controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, -				GCControllerElement *element) { -			int joy_id = [self getJoyIdForController:controller]; - -			if (element == gamepad.buttonA) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -						gamepad.buttonA.isPressed); -			} else if (element == gamepad.buttonB) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, -						gamepad.buttonB.isPressed); -			} else if (element == gamepad.buttonX) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -						gamepad.buttonX.isPressed); -			} else if (element == gamepad.buttonY) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, -						gamepad.buttonY.isPressed); -			} else if (element == gamepad.leftShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, -						gamepad.leftShoulder.isPressed); -			} else if (element == gamepad.rightShoulder) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, -						gamepad.rightShoulder.isPressed); -			} else if (element == gamepad.dpad) { -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -						gamepad.dpad.up.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -						gamepad.dpad.down.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -						gamepad.dpad.left.isPressed); -				OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -						gamepad.dpad.right.isPressed); -			}; -		}; -#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, -		// while we are setting that as the minimum, seems our -		// build environment doesn't like it -	} else if (controller.microGamepad != nil) { -		// micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad -		controller.microGamepad.valueChangedHandler = -				^(GCMicroGamepad *gamepad, GCControllerElement *element) { -					int joy_id = [self getJoyIdForController:controller]; - -					if (element == gamepad.buttonA) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, -								gamepad.buttonA.isPressed); -					} else if (element == gamepad.buttonX) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, -								gamepad.buttonX.isPressed); -					} else if (element == gamepad.dpad) { -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, -								gamepad.dpad.up.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, -								gamepad.dpad.down.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, -								gamepad.dpad.left.isPressed); -						OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, -								gamepad.dpad.right.isPressed); -					}; -				}; -#endif -	}; - -	///@TODO need to add support for controller.motion which gives us access to -	/// the orientation of the device (if supported) - -	///@TODO need to add support for controllerPausedHandler which should be a -	/// toggle -}; - -- (void)initGameControllers { -	// get told when controllers connect, this will be called right away for -	// already connected controllers -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(controllerWasConnected:) -				   name:GCControllerDidConnectNotification -				 object:nil]; - -	// get told when controllers disconnect -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(controllerWasDisconnected:) -				   name:GCControllerDidDisconnectNotification -				 object:nil]; -}; - -- (void)deinitGameControllers { -	[[NSNotificationCenter defaultCenter] -			removeObserver:self -					  name:GCControllerDidConnectNotification -					object:nil]; -	[[NSNotificationCenter defaultCenter] -			removeObserver:self -					  name:GCControllerDidDisconnectNotification -					object:nil]; - -	if (ios_joysticks != nil) { -		[ios_joysticks dealloc]; -		ios_joysticks = nil; -	}; - -	if (pending_ios_joysticks != nil) { -		[pending_ios_joysticks dealloc]; -		pending_ios_joysticks = nil; -	}; -}; - -OS::VideoMode _get_video_mode() { -	int backingWidth; -	int backingHeight; -#if defined(OPENGL_ENABLED) -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, -			GL_RENDERBUFFER_WIDTH_OES, &backingWidth); -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, -			GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); -#endif - -	OS::VideoMode vm; -	vm.fullscreen = true; -	vm.width = backingWidth; -	vm.height = backingHeight; -	vm.resizable = false; -	return vm; -}; - -static int frame_count = 0; -- (void)drawView:(UIView *)view; -{ -	switch (frame_count) { -		case 0: { -			OS::get_singleton()->set_video_mode(_get_video_mode()); - -			if (!OS::get_singleton()) { -				exit(0); -			}; -			++frame_count; - -			NSString *locale_code = [[NSLocale currentLocale] localeIdentifier]; -			OSIPhone::get_singleton()->set_locale( -					String::utf8([locale_code UTF8String])); - -			NSString *uuid; -			if ([[UIDevice currentDevice] -						respondsToSelector:@selector(identifierForVendor)]) { -				uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; -			} else { -				// before iOS 6, so just generate an identifier and store it -				uuid = [[NSUserDefaults standardUserDefaults] -						objectForKey:@"identiferForVendor"]; -				if (!uuid) { -					CFUUIDRef cfuuid = CFUUIDCreate(NULL); -					uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid); -					CFRelease(cfuuid); -					[[NSUserDefaults standardUserDefaults] -							setObject:uuid -							   forKey:@"identifierForVendor"]; -				} -			} - -			OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String])); - -		}; break; - -		case 1: { -			Main::setup2(); -			++frame_count; - -			if (pending_ios_joysticks != nil) { -				for (GCController *controller in pending_ios_joysticks) { -					_ios_add_joystick(controller, self); -				} -				[pending_ios_joysticks dealloc]; -				pending_ios_joysticks = nil; -			} - -			// this might be necessary before here -			NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; -			for (NSString *key in dict) { -				NSObject *value = [dict objectForKey:key]; -				String ukey = String::utf8([key UTF8String]); - -				// we need a NSObject to Variant conversor - -				if ([value isKindOfClass:[NSString class]]) { -					NSString *str = (NSString *)value; -					String uval = String::utf8([str UTF8String]); - -					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); - -				} else if ([value isKindOfClass:[NSNumber class]]) { -					NSNumber *n = (NSNumber *)value; -					double dval = [n doubleValue]; - -					ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); -				}; -				// do stuff -			} - -		}; break; - -		case 2: { -			Main::start(); -			++frame_count; - -		}; break; // no fallthrough - -		default: { -			if (OSIPhone::get_singleton()) { -				// OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1], -				// accel[2]); -				if (motionInitialised) { -					// Just using polling approach for now, we can set this up so it sends -					// data to us in intervals, might be better. See Apple reference pages -					// for more details: -					// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc - -					// Apple splits our accelerometer date into a gravity and user movement -					// component. We add them back together -					CMAcceleration gravity = motionManager.deviceMotion.gravity; -					CMAcceleration acceleration = -							motionManager.deviceMotion.userAcceleration; - -					///@TODO We don't seem to be getting data here, is my device broken or -					/// is this code incorrect? -					CMMagneticField magnetic = -							motionManager.deviceMotion.magneticField.field; - -					///@TODO we can access rotationRate as a CMRotationRate variable -					///(processed date) or CMGyroData (raw data), have to see what works -					/// best -					CMRotationRate rotation = motionManager.deviceMotion.rotationRate; - -					// Adjust for screen orientation. -					// [[UIDevice currentDevice] orientation] changes even if we've fixed -					// our orientation which is not a good thing when you're trying to get -					// your user to move the screen in all directions and want consistent -					// output - -					///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] -					/// is a bit of a hack. Godot obviously knows the orientation so maybe -					/// we -					// can use that instead? (note that left and right seem swapped) - -					switch ([[UIApplication sharedApplication] statusBarOrientation]) { -						case UIDeviceOrientationLandscapeLeft: { -							OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									-(acceleration.y + gravity.y), (acceleration.x + gravity.x), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									-magnetic.y, magnetic.x, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, -									rotation.z); -						}; break; -						case UIDeviceOrientationLandscapeRight: { -							OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									(acceleration.y + gravity.y), -(acceleration.x + gravity.x), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									magnetic.y, -magnetic.x, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, -									rotation.z); -						}; break; -						case UIDeviceOrientationPortraitUpsideDown: { -							OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									-(acceleration.x + gravity.x), (acceleration.y + gravity.y), -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer( -									-magnetic.x, magnetic.y, magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, -									rotation.z); -						}; break; -						default: { // assume portrait -							OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, -									gravity.z); -							OSIPhone::get_singleton()->update_accelerometer( -									acceleration.x + gravity.x, acceleration.y + gravity.y, -									acceleration.z + gravity.z); -							OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, -									magnetic.z); -							OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, -									rotation.z); -						}; break; -					}; -				} - -				bool quit_request = OSIPhone::get_singleton()->iterate(); -			}; - -		}; break; -	}; -}; - -- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { -	if (OS::get_singleton()->get_main_loop()) { -		OS::get_singleton()->get_main_loop()->notification( -				MainLoop::NOTIFICATION_OS_MEMORY_WARNING); -	} -}; -  - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -	CGRect rect = [[UIScreen mainScreen] bounds]; +	// TODO: might be required to make an early return, so app wouldn't crash because of timeout. +	// TODO: logo screen is not displayed while shaders are compiling +	// DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController -	is_focus_out = false; - -	[application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; -	// disable idle timer -	// application.idleTimerDisabled = YES; +	CGRect windowBounds = [[UIScreen mainScreen] bounds];  	// Create a full-screen window -	window = [[UIWindow alloc] initWithFrame:rect]; - -	OS::VideoMode vm = _get_video_mode(); +	self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease]; -	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, -			NSUserDomainMask, YES); +	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);  	NSString *documentsDirectory = [paths objectAtIndex:0]; -	int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String])); +	int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String])); +  	if (err != 0) {  		// bail, things did not go very well for us, should probably output a message on screen with our error code...  		exit(0); -		return FALSE; +		return NO;  	}; -#if defined(OPENGL_ENABLED) -	// WARNING: We must *always* create the GLView after we have constructed the -	// OS with iphone_main. This allows the GLView to access project settings so -	// it can properly initialize the OpenGL context -	GLView *glView = [[GLView alloc] initWithFrame:rect]; -	glView.delegate = self; - -	view_controller = [[ViewController alloc] init]; -	view_controller.view = glView; - -	_set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO); -	glView.useCADisplayLink = -			bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; -	printf("cadisaplylink: %d", glView.useCADisplayLink); -	glView.animationInterval = 1.0 / kRenderingFrequency; -	[glView startAnimation]; -#endif - -#if defined(VULKAN_ENABLED) -	view_controller = [[ViewController alloc] init]; -#endif +	ViewController *viewController = [[ViewController alloc] init]; +	viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO; +	viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency; -	window.rootViewController = view_controller; +	self.window.rootViewController = viewController;  	// Show the window -	[window makeKeyAndVisible]; - -	// Configure and start accelerometer -	if (!motionInitialised) { -		motionManager = [[CMMotionManager alloc] init]; -		if (motionManager.deviceMotionAvailable) { -			motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; -			[motionManager startDeviceMotionUpdatesUsingReferenceFrame: -								   CMAttitudeReferenceFrameXMagneticNorthZVertical]; -			motionInitialised = YES; -		}; -	}; - -	[self initGameControllers]; +	[self.window makeKeyAndVisible];  	[[NSNotificationCenter defaultCenter]  			addObserver:self @@ -674,40 +90,33 @@ static int frame_count = 0;  				   name:AVAudioSessionInterruptionNotification  				 object:[AVAudioSession sharedInstance]]; -	// OSIPhone::screen_width = rect.size.width - rect.origin.x; -	// OSIPhone::screen_height = rect.size.height - rect.origin.y; - -	mainViewController = view_controller; +	mainViewController = viewController;  	// prevent to stop music in another background app  	[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; -	return TRUE; +	return YES;  };  - (void)onAudioInterruption:(NSNotification *)notification {  	if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {  		if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {  			NSLog(@"Audio interruption began"); -			on_focus_out(view_controller, &is_focus_out); +			OSIPhone::get_singleton()->on_focus_out();  		} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {  			NSLog(@"Audio interruption ended"); -			on_focus_in(view_controller, &is_focus_out); +			OSIPhone::get_singleton()->on_focus_in();  		}  	}  }; -- (void)applicationWillTerminate:(UIApplication *)application { -	[self deinitGameControllers]; - -	if (motionInitialised) { -		///@TODO is this the right place to clean this up? -		[motionManager stopDeviceMotionUpdates]; -		[motionManager release]; -		motionManager = nil; -		motionInitialised = NO; -	}; +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { +	if (OS::get_singleton()->get_main_loop()) { +		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING); +	} +}; +- (void)applicationWillTerminate:(UIApplication *)application {  	iphone_finish();  }; @@ -722,15 +131,15 @@ static int frame_count = 0;  // notification panel by swiping from the upper part of the screen.  - (void)applicationWillResignActive:(UIApplication *)application { -	on_focus_out(view_controller, &is_focus_out); +	OSIPhone::get_singleton()->on_focus_out();  }  - (void)applicationDidBecomeActive:(UIApplication *)application { -	on_focus_in(view_controller, &is_focus_out); +	OSIPhone::get_singleton()->on_focus_in();  }  - (void)dealloc { -	[window release]; +	self.window = nil;  	[super dealloc];  } diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 3e6c2f0ecf..66579c1ad7 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -31,7 +31,8 @@ def get_opts():          ("IPHONESDK", "Path to the iPhone SDK", ""),          BoolVariable(              "use_static_mvk", -            "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)", +            "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" +            " validation layers)",              False,          ),          BoolVariable("game_center", "Support for game center", True), @@ -67,7 +68,7 @@ def configure(env):      elif env["target"] == "debug":          env.Append(CCFLAGS=["-gdwarf-2", "-O0"]) -        env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Append(CPPDEFINES=["_DEBUG", ("DEBUG", 1), "DEBUG_ENABLED"])      if env["use_lto"]:          env.Append(CCFLAGS=["-flto"]) @@ -120,18 +121,31 @@ def configure(env):              CCFLAGS=(                  "-arch "                  + arch_flag -                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0" +                + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks" +                " -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"              ).split()          )      elif env["arch"] == "arm":          detect_darwin_sdk_path("iphone", env)          env.Append( -            CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split() +            CCFLAGS=( +                "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing" +                " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" +                " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb" +                ' "-DIBOutlet=__attribute__((iboutlet))"' +                ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"' +                ' "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split() +            )          )      elif env["arch"] == "arm64":          detect_darwin_sdk_path("iphone", env)          env.Append( -            CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split() +            CCFLAGS=( +                "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing" +                " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits" +                " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0" +                " -isysroot $IPHONESDK".split() +            )          )          env.Append(CPPDEFINES=["NEED_LONG_INT"])          env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) @@ -143,6 +157,9 @@ def configure(env):          else:              env.Append(CCFLAGS=["-fno-exceptions"]) +    # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation +    env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) +      ## Link flags      if env["arch"] == "x86" or env["arch"] == "x86_64": @@ -151,7 +168,7 @@ def configure(env):              LINKFLAGS=[                  "-arch",                  arch_flag, -                "-mios-simulator-version-min=10.0", +                "-mios-simulator-version-min=13.0",                  "-isysroot",                  "$IPHONESDK",                  "-Xlinker", @@ -162,9 +179,9 @@ def configure(env):              ]          )      elif env["arch"] == "arm": -        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) +        env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])      if env["arch"] == "arm64": -        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"]) +        env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])      env.Append(          LINKFLAGS=[ @@ -218,7 +235,10 @@ def configure(env):          env.Append(CPPDEFINES=["ICLOUD_ENABLED"])      env.Prepend( -        CPPPATH=["$IPHONESDK/usr/include", "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers",] +        CPPPATH=[ +            "$IPHONESDK/usr/include", +            "$IPHONESDK/System/Library/Frameworks/AudioUnit.framework/Headers", +        ]      )      env["ENV"]["CODESIGN_ALLOCATE"] = "/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate" @@ -228,8 +248,7 @@ def configure(env):      env.Append(CPPDEFINES=["VULKAN_ENABLED"])      env.Append(LINKFLAGS=["-framework", "IOSurface"]) -    if env["use_static_mvk"]: -        env.Append(LINKFLAGS=["-framework", "MoltenVK"]) -        env["builtin_vulkan"] = False -    elif not env["builtin_vulkan"]: -        env.Append(LIBS=["vulkan"]) + +    # Use Static Vulkan for iOS. Dynamic Framework works fine too. +    env.Append(LINKFLAGS=["-framework", "MoltenVK"]) +    env["builtin_vulkan"] = False diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h new file mode 100644 index 0000000000..bfde8f96a3 --- /dev/null +++ b/platform/iphone/display_layer.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/*  display_layer.h                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import <OpenGLES/EAGLDrawable.h> +#import <QuartzCore/QuartzCore.h> + +@protocol DisplayLayer <NSObject> + +- (void)renderDisplayLayer; +- (void)initializeDisplayLayer; +- (void)layoutDisplayLayer; + +@end + +// An ugly workaround for iOS simulator +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +#if defined(__IPHONE_13_0) +API_AVAILABLE(ios(13.0)) +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> +#else +@interface GodotMetalLayer : CALayer <DisplayLayer> +#endif +#else +@interface GodotMetalLayer : CAMetalLayer <DisplayLayer> +#endif +@end + +API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0)) +@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer> + +@end diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm new file mode 100644 index 0000000000..5ec94fb471 --- /dev/null +++ b/platform/iphone/display_layer.mm @@ -0,0 +1,186 @@ +/*************************************************************************/ +/*  display_layer.mm                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import "display_layer.h" +#include "core/os/keyboard.h" +#include "core/project_settings.h" +#include "display_server_iphone.h" +#include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" + +#import <AudioToolbox/AudioServices.h> +#import <GameController/GameController.h> +#import <OpenGLES/EAGL.h> +#import <OpenGLES/ES1/gl.h> +#import <OpenGLES/ES1/glext.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@implementation GodotMetalLayer + +- (void)initializeDisplayLayer { +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +	if (@available(iOS 13, *)) { +		// Simulator supports Metal since iOS 13 +	} else { +		NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering."); +	} +#endif +} + +- (void)layoutDisplayLayer { +} + +- (void)renderDisplayLayer { +} + +@end + +@implementation GodotOpenGLLayer { +	// The pixel dimensions of the backbuffer +	GLint backingWidth; +	GLint backingHeight; + +	EAGLContext *context; +	GLuint viewRenderbuffer, viewFramebuffer; +	GLuint depthRenderbuffer; +} + +- (void)initializeDisplayLayer { +	// Get our backing layer + +	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. +	self.opaque = YES; +	self.drawableProperties = [NSDictionary +			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], +			kEAGLDrawablePropertyRetainedBacking, +			kEAGLColorFormatRGBA8, +			kEAGLDrawablePropertyColorFormat, +			nil]; + +	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + +	// Create GL ES 2 context +	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { +		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; +		NSLog(@"Setting up an OpenGL ES 2.0 context."); +		if (!context) { +			NSLog(@"Failed to create OpenGL ES 2.0 context!"); +			return; +		} +	} + +	if (![EAGLContext setCurrentContext:context]) { +		NSLog(@"Failed to set EAGLContext!"); +		return; +	} +	if (![self createFramebuffer]) { +		NSLog(@"Failed to create frame buffer!"); +		return; +	} +} + +- (void)layoutDisplayLayer { +	[EAGLContext setCurrentContext:context]; +	[self destroyFramebuffer]; +	[self createFramebuffer]; +} + +- (void)renderDisplayLayer { +	[EAGLContext setCurrentContext:context]; +} + +- (void)dealloc { +	if ([EAGLContext currentContext] == context) { +		[EAGLContext setCurrentContext:nil]; +	} + +	if (context) { +		[context release]; +		context = nil; +	} + +	[super dealloc]; +} + +- (BOOL)createFramebuffer { +	glGenFramebuffersOES(1, &viewFramebuffer); +	glGenRenderbuffersOES(1, &viewRenderbuffer); + +	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); +	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); +	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAself) +	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). +	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self]; +	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); + +	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); +	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); + +	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. +	glGenRenderbuffersOES(1, &depthRenderbuffer); +	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); +	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); +	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); + +	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { +		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); +		return NO; +	} + +	//    if (OS::get_singleton()) { +	//        OS::VideoMode vm; +	//        vm.fullscreen = true; +	//        vm.width = backingWidth; +	//        vm.height = backingHeight; +	//        vm.resizable = false; +	//        OS::get_singleton()->set_video_mode(vm); +	//        OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); +	//    }; +	//    gl_view_base_fb = viewFramebuffer; + +	return YES; +} + +// Clean up any buffers we have allocated. +- (void)destroyFramebuffer { +	glDeleteFramebuffersOES(1, &viewFramebuffer); +	viewFramebuffer = 0; +	glDeleteRenderbuffersOES(1, &viewRenderbuffer); +	viewRenderbuffer = 0; + +	if (depthRenderbuffer) { +		glDeleteRenderbuffersOES(1, &depthRenderbuffer); +		depthRenderbuffer = 0; +	} +} + +@end diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h new file mode 100644 index 0000000000..229b1e80db --- /dev/null +++ b/platform/iphone/display_server_iphone.h @@ -0,0 +1,202 @@ +/*************************************************************************/ +/*  display_server_iphone.h                                              */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#ifndef display_server_iphone_h +#define display_server_iphone_h + +#include "core/input/input.h" +#include "servers/display_server.h" + +#if defined(VULKAN_ENABLED) +#include "drivers/vulkan/rendering_device_vulkan.h" +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" + +#include "vulkan_context_iphone.h" + +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +class DisplayServerIPhone : public DisplayServer { +	GDCLASS(DisplayServerIPhone, DisplayServer) + +	_THREAD_SAFE_CLASS_ + +#if defined(VULKAN_ENABLED) +	VulkanContextIPhone *context_vulkan; +	RenderingDeviceVulkan *rendering_device_vulkan; +#endif + +	DisplayServer::ScreenOrientation screen_orientation; + +	ObjectID window_attached_instance_id; + +	Callable window_event_callback; +	Callable window_resize_callback; +	Callable input_event_callback; +	Callable input_text_callback; + +	int virtual_keyboard_height = 0; + +	void perform_event(const Ref<InputEvent> &p_event); + +	DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); +	~DisplayServerIPhone(); + +public: +	String rendering_driver; + +	static DisplayServerIPhone *get_singleton(); + +	static void register_iphone_driver(); +	static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); +	static Vector<String> get_rendering_drivers_func(); + +	// MARK: - Events + +	virtual void process_events() override; + +	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; + +	static void _dispatch_input_events(const Ref<InputEvent> &p_event); +	void send_input_event(const Ref<InputEvent> &p_event) const; +	void send_input_text(const String &p_text) const; +	void send_window_event(DisplayServer::WindowEvent p_event) const; +	void _window_callback(const Callable &p_callable, const Variant &p_arg) const; + +	// MARK: - Input + +	// MARK: Touches + +	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); +	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); +	void touches_cancelled(int p_idx); + +	// MARK: Keyboard + +	void key(uint32_t p_key, bool p_pressed); + +	// MARK: Motion + +	void update_gravity(float p_x, float p_y, float p_z); +	void update_accelerometer(float p_x, float p_y, float p_z); +	void update_magnetometer(float p_x, float p_y, float p_z); +	void update_gyroscope(float p_x, float p_y, float p_z); + +	// MARK: - + +	virtual bool has_feature(Feature p_feature) const override; +	virtual String get_name() const override; + +	virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; + +	virtual int get_screen_count() const override; +	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + +	virtual Vector<DisplayServer::WindowID> get_window_list() const override; + +	virtual WindowID +	get_window_at_screen_position(const Point2i &p_position) const override; + +	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; + +	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; +	virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; + +	virtual float screen_get_max_scale() const override; + +	virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override; +	virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override; + +	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; + +	virtual bool can_any_window_draw() const override; + +	virtual bool screen_is_touchscreen(int p_screen) const override; + +	virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override; +	virtual void virtual_keyboard_hide() override; + +	void virtual_keyboard_set_height(int height); +	virtual int virtual_keyboard_get_height() const override; + +	virtual void clipboard_set(const String &p_text) override; +	virtual String clipboard_get() const override; + +	virtual void screen_set_keep_on(bool p_enable) override; +	virtual bool screen_is_kept_on() const override; + +	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override; +	virtual bool native_video_is_playing() const override; +	virtual void native_video_pause() override; +	virtual void native_video_unpause() override; +	virtual void native_video_stop() override; + +	void resize_window(CGSize size); +}; + +#endif /* display_server_iphone_h */ diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm new file mode 100644 index 0000000000..eea87cecc9 --- /dev/null +++ b/platform/iphone/display_server_iphone.mm @@ -0,0 +1,766 @@ +/*************************************************************************/ +/*  display_server_iphone.mm                                             */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#include "display_server_iphone.h" +#import "app_delegate.h" +#include "core/io/file_access_pack.h" +#include "core/project_settings.h" +#import "godot_view.h" +#include "ios.h" +#include "os_iphone.h" +#import "view_controller.h" + +#import <Foundation/Foundation.h> +#import <sys/utsname.h> + +static const float kDisplayServerIPhoneAcceleration = 1; +static NSDictionary *iOSModelToDPI = @{ +	@[ +		@"iPad1,1", +		@"iPad2,1", +		@"iPad2,2", +		@"iPad2,3", +		@"iPad2,4", +	] : @132, +	@[ +		@"iPhone1,1", +		@"iPhone1,2", +		@"iPhone2,1", +		@"iPad2,5", +		@"iPad2,6", +		@"iPad2,7", +		@"iPod1,1", +		@"iPod2,1", +		@"iPod3,1", +	] : @163, +	@[ +		@"iPad3,1", +		@"iPad3,2", +		@"iPad3,3", +		@"iPad3,4", +		@"iPad3,5", +		@"iPad3,6", +		@"iPad4,1", +		@"iPad4,2", +		@"iPad4,3", +		@"iPad5,3", +		@"iPad5,4", +		@"iPad6,3", +		@"iPad6,4", +		@"iPad6,7", +		@"iPad6,8", +		@"iPad6,11", +		@"iPad6,12", +		@"iPad7,1", +		@"iPad7,2", +		@"iPad7,3", +		@"iPad7,4", +		@"iPad7,5", +		@"iPad7,6", +		@"iPad7,11", +		@"iPad7,12", +		@"iPad8,1", +		@"iPad8,2", +		@"iPad8,3", +		@"iPad8,4", +		@"iPad8,5", +		@"iPad8,6", +		@"iPad8,7", +		@"iPad8,8", +		@"iPad8,9", +		@"iPad8,10", +		@"iPad8,11", +		@"iPad8,12", +		@"iPad11,3", +		@"iPad11,4", +	] : @264, +	@[ +		@"iPhone3,1", +		@"iPhone3,2", +		@"iPhone3,3", +		@"iPhone4,1", +		@"iPhone5,1", +		@"iPhone5,2", +		@"iPhone5,3", +		@"iPhone5,4", +		@"iPhone6,1", +		@"iPhone6,2", +		@"iPhone7,2", +		@"iPhone8,1", +		@"iPhone8,4", +		@"iPhone9,1", +		@"iPhone9,3", +		@"iPhone10,1", +		@"iPhone10,4", +		@"iPhone11,8", +		@"iPhone12,1", +		@"iPhone12,8", +		@"iPad4,4", +		@"iPad4,5", +		@"iPad4,6", +		@"iPad4,7", +		@"iPad4,8", +		@"iPad4,9", +		@"iPad5,1", +		@"iPad5,2", +		@"iPad11,1", +		@"iPad11,2", +		@"iPod4,1", +		@"iPod5,1", +		@"iPod7,1", +		@"iPod9,1", +	] : @326, +	@[ +		@"iPhone7,1", +		@"iPhone8,2", +		@"iPhone9,2", +		@"iPhone9,4", +		@"iPhone10,2", +		@"iPhone10,5", +	] : @401, +	@[ +		@"iPhone10,3", +		@"iPhone10,6", +		@"iPhone11,2", +		@"iPhone11,4", +		@"iPhone11,6", +		@"iPhone12,3", +		@"iPhone12,5", +	] : @458, +}; + +DisplayServerIPhone *DisplayServerIPhone::get_singleton() { +	return (DisplayServerIPhone *)DisplayServer::get_singleton(); +} + +DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +	rendering_driver = p_rendering_driver; + +#if defined(OPENGL_ENABLED) +	// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented +	// again, + +	if (rendering_driver == "opengl_es") { +		bool gl_initialization_error = false; + +		// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? + +		if (RasterizerGLES2::is_viable() == OK) { +			RasterizerGLES2::register_config(); +			RasterizerGLES2::make_current(); +		} else { +			gl_initialization_error = true; +		} + +		if (gl_initialization_error) { +			OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver"); +			//        return ERR_UNAVAILABLE; +		} + +		//    rendering_server = memnew(RenderingServerRaster); +		//    // FIXME: Reimplement threaded rendering +		//    if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { +		//        rendering_server = memnew(RenderingServerWrapMT(rendering_server, +		//        false)); +		//    } +		//    rendering_server->init(); +		// rendering_server->cursor_set_visible(false, 0); + +		// reset this to what it should be, it will have been set to 0 after +		// rendering_server->init() is called +		//    RasterizerStorageGLES2::system_fbo = gl_view_base_fb; +	} +#endif + +#if defined(VULKAN_ENABLED) +	rendering_driver = "vulkan"; + +	context_vulkan = nullptr; +	rendering_device_vulkan = nullptr; + +	if (rendering_driver == "vulkan") { +		context_vulkan = memnew(VulkanContextIPhone); +		if (context_vulkan->initialize() != OK) { +			memdelete(context_vulkan); +			context_vulkan = nullptr; +			ERR_FAIL_MSG("Failed to initialize Vulkan context"); +		} + +		CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"]; + +		if (!layer) { +			ERR_FAIL_MSG("Failed to create iOS rendering layer."); +		} + +		Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale(); +		if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) { +			memdelete(context_vulkan); +			context_vulkan = nullptr; +			ERR_FAIL_MSG("Failed to create Vulkan window."); +		} + +		rendering_device_vulkan = memnew(RenderingDeviceVulkan); +		rendering_device_vulkan->initialize(context_vulkan); + +		RasterizerRD::make_current(); +	} +#endif + +	bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)); +	screen_set_keep_on(keep_screen_on); + +	Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); + +	r_error = OK; +} + +DisplayServerIPhone::~DisplayServerIPhone() { +#if defined(VULKAN_ENABLED) +	if (rendering_driver == "vulkan") { +		if (rendering_device_vulkan) { +			rendering_device_vulkan->finalize(); +			memdelete(rendering_device_vulkan); +			rendering_device_vulkan = NULL; +		} + +		if (context_vulkan) { +			context_vulkan->window_destroy(MAIN_WINDOW_ID); +			memdelete(context_vulkan); +			context_vulkan = NULL; +		} +	} +#endif +} + +DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +	return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error)); +} + +Vector<String> DisplayServerIPhone::get_rendering_drivers_func() { +	Vector<String> drivers; + +#if defined(VULKAN_ENABLED) +	drivers.push_back("vulkan"); +#endif +#if defined(OPENGL_ENABLED) +	drivers.push_back("opengl_es"); +#endif + +	return drivers; +} + +void DisplayServerIPhone::register_iphone_driver() { +	register_create_function("iphone", create_func, get_rendering_drivers_func); +} + +// MARK: Events + +void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) { +	window_resize_callback = p_callable; +} + +void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) { +	window_event_callback = p_callable; +} +void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) { +	input_event_callback = p_callable; +} + +void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) { +	input_text_callback = p_callable; +} + +void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::process_events() { +} + +void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) { +	DisplayServerIPhone::get_singleton()->send_input_event(p_event); +} + +void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const { +	_window_callback(input_event_callback, p_event); +} + +void DisplayServerIPhone::send_input_text(const String &p_text) const { +	_window_callback(input_text_callback, p_text); +} + +void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const { +	_window_callback(window_event_callback, int(p_event)); +} + +void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const { +	if (!p_callable.is_null()) { +		const Variant *argp = &p_arg; +		Variant ret; +		Callable::CallError ce; +		p_callable.call((const Variant **)&argp, 1, ret, ce); +	} +} + +// MARK: - Input + +// MARK: Touches + +void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { +	if (!GLOBAL_DEF("debug/disable_touch", false)) { +		Ref<InputEventScreenTouch> ev; +		ev.instance(); + +		ev->set_index(p_idx); +		ev->set_pressed(p_pressed); +		ev->set_position(Vector2(p_x, p_y)); +		perform_event(ev); +	} +}; + +void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { +	if (!GLOBAL_DEF("debug/disable_touch", false)) { +		Ref<InputEventScreenDrag> ev; +		ev.instance(); +		ev->set_index(p_idx); +		ev->set_position(Vector2(p_x, p_y)); +		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); +		perform_event(ev); +	}; +}; + +void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) { +	Input::get_singleton()->parse_input_event(p_event); +}; + +void DisplayServerIPhone::touches_cancelled(int p_idx) { +	touch_press(p_idx, -1, -1, false, false); +}; + +// MARK: Keyboard + +void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) { +	Ref<InputEventKey> ev; +	ev.instance(); +	ev->set_echo(false); +	ev->set_pressed(p_pressed); +	ev->set_keycode(p_key); +	ev->set_physical_keycode(p_key); +	ev->set_unicode(p_key); +	perform_event(ev); +}; + +// MARK: Motion + +void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_accelerometer(float p_x, float p_y, +		float p_z) { +	// Found out the Z should not be negated! Pass as is! +	Vector3 v_accelerometer = Vector3( +			p_x / kDisplayServerIPhoneAcceleration, +			p_y / kDisplayServerIPhoneAcceleration, +			p_z / kDisplayServerIPhoneAcceleration); + +	Input::get_singleton()->set_accelerometer(v_accelerometer); + +	/* +  if (p_x != last_accel.x) { +      //printf("updating accel x %f\n", p_x); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_0; +      ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); +      last_accel.x = p_x; +      queue_event(ev); +  }; +  if (p_y != last_accel.y) { +      //printf("updating accel y %f\n", p_y); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_1; +      ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); +      last_accel.y = p_y; +      queue_event(ev); +  }; +  if (p_z != last_accel.z) { +      //printf("updating accel z %f\n", p_z); +      InputEvent ev; +      ev.type = InputEvent::JOYPAD_MOTION; +      ev.device = 0; +      ev.joy_motion.axis = JOY_ANALOG_2; +      ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); +      last_accel.z = p_z; +      queue_event(ev); +  }; +  */ +}; + +void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z)); +}; + +void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) { +	Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z)); +}; + +// MARK: - + +bool DisplayServerIPhone::has_feature(Feature p_feature) const { +	switch (p_feature) { +		// case FEATURE_CONSOLE_WINDOW: +		// case FEATURE_CURSOR_SHAPE: +		// case FEATURE_CUSTOM_CURSOR_SHAPE: +		// case FEATURE_GLOBAL_MENU: +		// case FEATURE_HIDPI: +		// case FEATURE_ICON: +		// case FEATURE_IME: +		// case FEATURE_MOUSE: +		// case FEATURE_MOUSE_WARP: +		// case FEATURE_NATIVE_DIALOG: +		// case FEATURE_NATIVE_ICON: +		// case FEATURE_NATIVE_VIDEO: +		// case FEATURE_WINDOW_TRANSPARENCY: +		case FEATURE_CLIPBOARD: +		case FEATURE_KEEP_SCREEN_ON: +		case FEATURE_ORIENTATION: +		case FEATURE_TOUCHSCREEN: +		case FEATURE_VIRTUAL_KEYBOARD: +			return true; +		default: +			return false; +	} +} + +String DisplayServerIPhone::get_name() const { +	return "iPhone"; +} + +void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) { +	const CharString utf8_alert = p_alert.utf8(); +	const CharString utf8_title = p_title.utf8(); +	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +int DisplayServerIPhone::get_screen_count() const { +	return 1; +} + +Point2i DisplayServerIPhone::screen_get_position(int p_screen) const { +	return Size2i(); +} + +Size2i DisplayServerIPhone::screen_get_size(int p_screen) const { +	CALayer *layer = AppDelegate.viewController.godotView.renderingLayer; + +	if (!layer) { +		return Size2i(); +	} + +	return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen); +} + +Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const { +	if (@available(iOS 11, *)) { +		UIEdgeInsets insets = UIEdgeInsetsZero; +		UIView *view = AppDelegate.viewController.godotView; + +		if ([view respondsToSelector:@selector(safeAreaInsets)]) { +			insets = [view safeAreaInsets]; +		} + +		float scale = screen_get_scale(p_screen); +		Size2i insets_position = Size2i(insets.left, insets.top) * scale; +		Size2i insets_size = Size2i(insets.left + insets.right, insets.top + insets.bottom) * scale; + +		return Rect2i(screen_get_position(p_screen) + insets_position, screen_get_size(p_screen) - insets_size); +	} else { +		return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen)); +	} +} + +int DisplayServerIPhone::screen_get_dpi(int p_screen) const { +	struct utsname systemInfo; +	uname(&systemInfo); + +	NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; + +	for (NSArray *keyArray in iOSModelToDPI) { +		if ([keyArray containsObject:string]) { +			NSNumber *value = iOSModelToDPI[keyArray]; +			return [value intValue]; +		} +	} + +	return 163; +} + +float DisplayServerIPhone::screen_get_scale(int p_screen) const { +	return [UIScreen mainScreen].nativeScale; +} + +Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const { +	Vector<DisplayServer::WindowID> list; +	list.push_back(MAIN_WINDOW_ID); +	return list; +} + +DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const { +	return MAIN_WINDOW_ID; +} + +void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { +	window_attached_instance_id = p_instance; +} + +ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const { +	return window_attached_instance_id; +} + +void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) { +	// Probably not supported for iOS +} + +int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const { +	return SCREEN_OF_MAIN_WINDOW; +} + +void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) { +	// Probably not supported for iOS +} + +Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const { +	return Point2i(); +} + +void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) { +	// Probably not supported for single window iOS app +} + +void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const { +	return Size2i(); +} + +void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const { +	return Size2i(); +} + +void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) { +	// Probably not supported for iOS +} + +Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const { +	CGRect screenBounds = [UIScreen mainScreen].bounds; +	return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); +} + +Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const { +	return window_get_size(p_window); +} + +void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) { +	// Probably not supported for iOS +} + +DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const { +	return WindowMode::WINDOW_MODE_FULLSCREEN; +} + +bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const { +	return false; +} + +void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { +	// Probably not supported for iOS +} + +bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const { +	return false; +} + +void DisplayServerIPhone::window_request_attention(WindowID p_window) { +	// Probably not supported for iOS +} + +void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) { +	// Probably not supported for iOS +} + +float DisplayServerIPhone::screen_get_max_scale() const { +	return screen_get_scale(SCREEN_OF_MAIN_WINDOW); +}; + +void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) { +	screen_orientation = p_orientation; +} + +DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const { +	return screen_orientation; +} + +bool DisplayServerIPhone::window_can_draw(WindowID p_window) const { +	return true; +} + +bool DisplayServerIPhone::can_any_window_draw() const { +	return true; +} + +bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const { +	return true; +} + +void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) { +	[AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text]; +} + +void DisplayServerIPhone::virtual_keyboard_hide() { +	[AppDelegate.viewController.godotView resignFirstResponder]; +} + +void DisplayServerIPhone::virtual_keyboard_set_height(int height) { +	virtual_keyboard_height = height * screen_get_max_scale(); +} + +int DisplayServerIPhone::virtual_keyboard_get_height() const { +	return virtual_keyboard_height; +} + +void DisplayServerIPhone::clipboard_set(const String &p_text) { +	[UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()]; +} + +String DisplayServerIPhone::clipboard_get() const { +	NSString *text = [UIPasteboard generalPasteboard].string; + +	return String::utf8([text UTF8String]); +} + +void DisplayServerIPhone::screen_set_keep_on(bool p_enable) { +	[UIApplication sharedApplication].idleTimerDisabled = p_enable; +} + +bool DisplayServerIPhone::screen_is_kept_on() const { +	return [UIApplication sharedApplication].idleTimerDisabled; +} + +Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) { +	FileAccess *f = FileAccess::open(p_path, FileAccess::READ); +	bool exists = f && f->is_open(); + +	String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir(); + +	if (!exists) { +		return FAILED; +	} + +	String tempFile = OSIPhone::get_singleton()->get_user_data_dir(); + +	if (p_path.begins_with("res://")) { +		if (PackedData::get_singleton()->has_path(p_path)) { +			printf("Unable to play %s using the native player as it resides in a .pck file\n", p_path.utf8().get_data()); +			return ERR_INVALID_PARAMETER; +		} else { +			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); +		} +	} else if (p_path.begins_with("user://")) { +		p_path = p_path.replace("user:/", user_data_dir); +	} + +	memdelete(f); + +	printf("Playing video: %s\n", p_path.utf8().get_data()); + +	String file_path = ProjectSettings::get_singleton()->globalize_path(p_path); + +	NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease]; +	NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()]; +	NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()]; + +	if (![AppDelegate.viewController playVideoAtPath:filePath +											  volume:p_volume +											   audio:audioTrack +											subtitle:subtitleTrack]) { +		return OK; +	} + +	return FAILED; +} + +bool DisplayServerIPhone::native_video_is_playing() const { +	return [AppDelegate.viewController isVideoPlaying]; +} + +void DisplayServerIPhone::native_video_pause() { +	if (native_video_is_playing()) { +		[AppDelegate.viewController pauseVideo]; +	} +} + +void DisplayServerIPhone::native_video_unpause() { +	[AppDelegate.viewController unpauseVideo]; +}; + +void DisplayServerIPhone::native_video_stop() { +	if (native_video_is_playing()) { +		[AppDelegate.viewController stopVideo]; +	} +} + +void DisplayServerIPhone::resize_window(CGSize viewSize) { +	Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale(); + +#if defined(VULKAN_ENABLED) +	if (rendering_driver == "vulkan") { +		if (context_vulkan) { +			context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y); +		} +	} +#endif + +	Variant resize_rect = Rect2i(Point2i(), size); +	_window_callback(window_resize_callback, resize_rect); +} diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 4a751488cb..19f7c8e482 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -86,6 +86,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {  	struct IOSExportAsset {  		String exported_path;  		bool is_framework; // framework is anything linked to the binary, otherwise it's a resource +		bool should_embed;  	};  	String _get_additional_plist_content(); @@ -100,7 +101,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {  	Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);  	void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets); -	Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets); +	Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets);  	Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);  	bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const { @@ -114,7 +115,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {  		}  		for (int i = 0; i < pname.length(); i++) { -			CharType c = pname[i]; +			char32_t c = pname[i];  			if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {  				if (r_error) {  					*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); @@ -159,9 +160,8 @@ public:  void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {  	String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); -	if (driver == "GLES2") { -		r_features->push_back("etc"); -	} else if (driver == "Vulkan") { +	r_features->push_back("pvrtc"); +	if (driver == "Vulkan") {  		// FIXME: Review if this is correct.  		r_features->push_back("etc2");  	} @@ -912,15 +912,6 @@ struct ExportLibsData {  };  void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) { -	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); -	Vector<String> frameworks; -	for (int i = 0; i < export_plugins.size(); ++i) { -		Vector<String> plugin_frameworks = export_plugins[i]->get_ios_frameworks(); -		for (int j = 0; j < plugin_frameworks.size(); ++j) { -			frameworks.push_back(plugin_frameworks[j]); -		} -	} -  	// that is just a random number, we just need Godot IDs not to clash with  	// existing IDs in the project.  	PbxId current_id = { 0x58938401, 0, 0 }; @@ -945,15 +936,19 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese  		String type;  		if (asset.exported_path.ends_with(".framework")) { -			additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; -			framework_id = (++current_id).str(); -			pbx_embeded_frameworks += framework_id + ",\n"; +			if (asset.should_embed) { +				additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; +				framework_id = (++current_id).str(); +				pbx_embeded_frameworks += framework_id + ",\n"; +			}  			type = "wrapper.framework";  		} else if (asset.exported_path.ends_with(".xcframework")) { -			additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; -			framework_id = (++current_id).str(); -			pbx_embeded_frameworks += framework_id + ",\n"; +			if (asset.should_embed) { +				additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; +				framework_id = (++current_id).str(); +				pbx_embeded_frameworks += framework_id + ",\n"; +			}  			type = "wrapper.xcframework";  		} else if (asset.exported_path.ends_with(".dylib")) { @@ -1026,7 +1021,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) { +Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) {  	DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);  	String binary_name = p_out_dir.get_file().get_basename(); @@ -1035,7 +1030,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir  		String asset = p_assets[f_idx];  		if (!asset.begins_with("res://")) {  			// either SDK-builtin or already a part of the export template -			IOSExportAsset exported_asset = { asset, p_is_framework }; +			IOSExportAsset exported_asset = { asset, p_is_framework, p_should_embed };  			r_exported_assets.push_back(exported_asset);  		} else {  			DirAccess *da = DirAccess::create_for_path(asset); @@ -1101,7 +1096,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir  				memdelete(filesystem_da);  				return err;  			} -			IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework }; +			IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed };  			r_exported_assets.push_back(exported_asset);  			if (create_framework) { @@ -1165,19 +1160,23 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir  Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets) {  	Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();  	for (int i = 0; i < export_plugins.size(); i++) { -		Vector<String> frameworks = export_plugins[i]->get_ios_frameworks(); -		Error err = _export_additional_assets(p_out_dir, frameworks, true, r_exported_assets); +		Vector<String> linked_frameworks = export_plugins[i]->get_ios_frameworks(); +		Error err = _export_additional_assets(p_out_dir, linked_frameworks, true, false, r_exported_assets); +		ERR_FAIL_COND_V(err, err); + +		Vector<String> embedded_frameworks = export_plugins[i]->get_ios_embedded_frameworks(); +		err = _export_additional_assets(p_out_dir, embedded_frameworks, true, true, r_exported_assets);  		ERR_FAIL_COND_V(err, err);  		Vector<String> project_static_libs = export_plugins[i]->get_ios_project_static_libs();  		for (int j = 0; j < project_static_libs.size(); j++) {  			project_static_libs.write[j] = project_static_libs[j].get_file(); // Only the file name as it's copied to the project  		} -		err = _export_additional_assets(p_out_dir, project_static_libs, true, r_exported_assets); +		err = _export_additional_assets(p_out_dir, project_static_libs, true, true, r_exported_assets);  		ERR_FAIL_COND_V(err, err);  		Vector<String> ios_bundle_files = export_plugins[i]->get_ios_bundle_files(); -		err = _export_additional_assets(p_out_dir, ios_bundle_files, false, r_exported_assets); +		err = _export_additional_assets(p_out_dir, ios_bundle_files, false, false, r_exported_assets);  		ERR_FAIL_COND_V(err, err);  	} @@ -1185,7 +1184,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir  	for (int i = 0; i < p_libraries.size(); ++i) {  		library_paths.push_back(p_libraries[i].path);  	} -	Error err = _export_additional_assets(p_out_dir, library_paths, true, r_exported_assets); +	Error err = _export_additional_assets(p_out_dir, library_paths, true, true, r_exported_assets);  	ERR_FAIL_COND_V(err, err);  	return OK; @@ -1649,7 +1648,7 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset  		}  	} -	String etc_error = test_etc2(); +	String etc_error = test_etc2_or_pvrtc();  	if (etc_error != String()) {  		valid = false;  		err += etc_error; diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h index 0d3ef5b696..6705674ac6 100644 --- a/platform/iphone/game_center.h +++ b/platform/iphone/game_center.h @@ -48,15 +48,15 @@ class GameCenter : public Object {  	void return_connect_error(const char *p_error_description);  public: -	void connect(); +	Error authenticate();  	bool is_authenticated(); -	Error post_score(Variant p_score); -	Error award_achievement(Variant p_params); +	Error post_score(Dictionary p_score); +	Error award_achievement(Dictionary p_params);  	void reset_achievements();  	void request_achievements();  	void request_achievement_descriptions(); -	Error show_game_center(Variant p_params); +	Error show_game_center(Dictionary p_params);  	Error request_identity_verification_signature();  	void game_center_closed(); diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm index 8d470da1a8..b237ba6bb6 100644 --- a/platform/iphone/game_center.mm +++ b/platform/iphone/game_center.mm @@ -47,13 +47,16 @@ extern "C" {  #import "app_delegate.h"  }; +#import "view_controller.h" +  GameCenter *GameCenter::instance = NULL;  void GameCenter::_bind_methods() { +	ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate);  	ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);  	ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score); -	ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement); +	ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);  	ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);  	ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);  	ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions); @@ -64,34 +67,17 @@ void GameCenter::_bind_methods() {  	ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event);  }; -void GameCenter::return_connect_error(const char *p_error_description) { -	authenticated = false; -	Dictionary ret; -	ret["type"] = "authentication"; -	ret["result"] = "error"; -	ret["error_code"] = 0; -	ret["error_description"] = p_error_description; -	pending_events.push_back(ret); -} - -void GameCenter::connect() { +Error GameCenter::authenticate() {  	//if this class isn't available, game center isn't implemented  	if ((NSClassFromString(@"GKLocalPlayer")) == nil) { -		return_connect_error("GameCenter not available"); -		return; +		return ERR_UNAVAILABLE;  	}  	GKLocalPlayer *player = [GKLocalPlayer localPlayer]; -	if (![player respondsToSelector:@selector(authenticateHandler)]) { -		return_connect_error("GameCenter doesn't respond to 'authenticateHandler'"); -		return; -	} +	ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE);  	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; -	if (!root_controller) { -		return_connect_error("Window doesn't have root ViewController"); -		return; -	} +	ERR_FAIL_COND_V(!root_controller, FAILED);  	// This handler is called several times.  First when the view needs to be shown, then again  	// after the view is cancelled or the user logs in.  Or if the user's already logged in, it's @@ -105,7 +91,14 @@ void GameCenter::connect() {  			ret["type"] = "authentication";  			if (player.isAuthenticated) {  				ret["result"] = "ok"; -				ret["player_id"] = [player.playerID UTF8String]; +				if (@available(iOS 13, *)) { +					ret["player_id"] = [player.teamPlayerID UTF8String]; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +				} else { +					ret["player_id"] = [player.playerID UTF8String]; +#endif +				} +  				GameCenter::get_singleton()->authenticated = true;  			} else {  				ret["result"] = "error"; @@ -117,17 +110,18 @@ void GameCenter::connect() {  			pending_events.push_back(ret);  		};  	}); + +	return OK;  };  bool GameCenter::is_authenticated() {  	return authenticated;  }; -Error GameCenter::post_score(Variant p_score) { -	Dictionary params = p_score; -	ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER); -	float score = params["score"]; -	String category = params["category"]; +Error GameCenter::post_score(Dictionary p_score) { +	ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER); +	float score = p_score["score"]; +	String category = p_score["category"];  	NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];  	GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease]; @@ -153,11 +147,10 @@ Error GameCenter::post_score(Variant p_score) {  	return OK;  }; -Error GameCenter::award_achievement(Variant p_params) { -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER); -	String name = params["name"]; -	float progress = params["progress"]; +Error GameCenter::award_achievement(Dictionary p_params) { +	ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER); +	String name = p_params["name"]; +	float progress = p_params["progress"];  	NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];  	GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease]; @@ -167,8 +160,8 @@ Error GameCenter::award_achievement(Variant p_params) {  	achievement.percentComplete = progress;  	achievement.showsCompletionBanner = NO; -	if (params.has("show_completion_banner")) { -		achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO; +	if (p_params.has("show_completion_banner")) { +		achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;  	}  	[GKAchievement reportAchievements:@[ achievement ] @@ -202,7 +195,7 @@ void GameCenter::request_achievement_descriptions() {  			Array hidden;  			Array replayable; -			for (int i = 0; i < [descriptions count]; i++) { +			for (NSUInteger i = 0; i < [descriptions count]; i++) {  				GKAchievementDescription *description = [descriptions objectAtIndex:i];  				const char *str = [description.identifier UTF8String]; @@ -250,7 +243,7 @@ void GameCenter::request_achievements() {  			PackedStringArray names;  			PackedFloat32Array percentages; -			for (int i = 0; i < [achievements count]; i++) { +			for (NSUInteger i = 0; i < [achievements count]; i++) {  				GKAchievement *achievement = [achievements objectAtIndex:i];  				const char *str = [achievement.identifier UTF8String];  				names.push_back(String::utf8(str != NULL ? str : "")); @@ -285,14 +278,12 @@ void GameCenter::reset_achievements() {  	}];  }; -Error GameCenter::show_game_center(Variant p_params) { +Error GameCenter::show_game_center(Dictionary p_params) {  	ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED); -	Dictionary params = p_params; -  	GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault; -	if (params.has("view")) { -		String view_name = params["view"]; +	if (p_params.has("view")) { +		String view_name = p_params["view"];  		if (view_name == "default") {  			view_state = GKGameCenterViewControllerStateDefault;  		} else if (view_name == "leaderboards") { @@ -306,7 +297,7 @@ Error GameCenter::show_game_center(Variant p_params) {  		}  	} -	GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init]; +	GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];  	ERR_FAIL_COND_V(!controller, FAILED);  	ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController; @@ -316,8 +307,8 @@ Error GameCenter::show_game_center(Variant p_params) {  	controller.viewState = view_state;  	if (view_state == GKGameCenterViewControllerStateLeaderboards) {  		controller.leaderboardIdentifier = nil; -		if (params.has("leaderboard_name")) { -			String name = params["leaderboard_name"]; +		if (p_params.has("leaderboard_name")) { +			String name = p_params["leaderboard_name"];  			NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];  			controller.leaderboardIdentifier = name_str;  		} @@ -341,7 +332,13 @@ Error GameCenter::request_identity_verification_signature() {  			ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];  			ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];  			ret["timestamp"] = timestamp; -			ret["player_id"] = [player.playerID UTF8String]; +			if (@available(iOS 13, *)) { +				ret["player_id"] = [player.teamPlayerID UTF8String]; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +			} else { +				ret["player_id"] = [player.playerID UTF8String]; +#endif +			}  		} else {  			ret["result"] = "error";  			ret["error_code"] = (int64_t)error.code; diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h deleted file mode 100644 index 975aa4b70a..0000000000 --- a/platform/iphone/gl_view.h +++ /dev/null @@ -1,123 +0,0 @@ -/*************************************************************************/ -/*  gl_view.h                                                            */ -/*************************************************************************/ -/*                       This file is part of:                           */ -/*                           GODOT ENGINE                                */ -/*                      https://godotengine.org                          */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ -/* Copyright (c) 2014-2020 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.                */ -/*************************************************************************/ - -#import <AVFoundation/AVFoundation.h> -#import <MediaPlayer/MediaPlayer.h> -#import <OpenGLES/EAGL.h> -#import <OpenGLES/ES1/gl.h> -#import <OpenGLES/ES1/glext.h> -#import <UIKit/UIKit.h> - -@protocol GLViewDelegate; - -@interface GLView : UIView <UIKeyInput> { -@private -	// The pixel dimensions of the backbuffer -	GLint backingWidth; -	GLint backingHeight; - -	EAGLContext *context; - -	// OpenGL names for the renderbuffer and framebuffers used to render to this view -	GLuint viewRenderbuffer, viewFramebuffer; - -	// OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist) -	GLuint depthRenderbuffer; - -	BOOL useCADisplayLink; -	// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 -	CADisplayLink *displayLink; - -	// An animation timer that, when animation is started, will periodically call -drawView at the given rate. -	// Only used if CADisplayLink is not -	NSTimer *animationTimer; - -	NSTimeInterval animationInterval; - -	// Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer. -	id<GLViewDelegate> delegate; - -	// Flag to denote that the -setupView method of a delegate has been called. -	// Resets to NO whenever the delegate changes. -	BOOL delegateSetup; -	BOOL active; -	float screen_scale; -} - -@property(nonatomic, assign) id<GLViewDelegate> delegate; - -// AVPlayer-related properties -@property(strong, nonatomic) AVAsset *avAsset; -@property(strong, nonatomic) AVPlayerItem *avPlayerItem; -@property(strong, nonatomic) AVPlayer *avPlayer; -@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; - -@property(strong, nonatomic) UIWindow *backgroundWindow; - -@property(nonatomic) UITextAutocorrectionType autocorrectionType; - -- (void)startAnimation; -- (void)stopAnimation; -- (void)drawView; - -- (BOOL)canBecomeFirstResponder; - -- (void)open_keyboard; -- (void)hide_keyboard; -- (void)deleteBackward; -- (BOOL)hasText; -- (void)insertText:(NSString *)p_text; - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification; -- (void)keyboardOnScreen:(NSNotification *)notification; -- (void)keyboardHidden:(NSNotification *)notification; - -@property NSTimeInterval animationInterval; -@property(nonatomic, assign) BOOL useCADisplayLink; - -@end - -@protocol GLViewDelegate <NSObject> - -@required - -// Draw with OpenGL ES -- (void)drawView:(GLView *)view; - -@optional - -// Called whenever you need to do some initialization before rendering. -- (void)setupView:(GLView *)view; - -@end diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm deleted file mode 100644 index 1169ebc6b4..0000000000 --- a/platform/iphone/gl_view.mm +++ /dev/null @@ -1,702 +0,0 @@ -/*************************************************************************/ -/*  gl_view.mm                                                           */ -/*************************************************************************/ -/*                       This file is part of:                           */ -/*                           GODOT ENGINE                                */ -/*                      https://godotengine.org                          */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ -/* Copyright (c) 2014-2020 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.                */ -/*************************************************************************/ - -#import "gl_view.h" - -#include "core/os/keyboard.h" -#include "core/project_settings.h" -#include "os_iphone.h" -#include "servers/audio_server.h" - -#import <OpenGLES/EAGLDrawable.h> -#import <QuartzCore/QuartzCore.h> - -/* -@interface GLView (private) - -- (id)initGLES; -- (BOOL)createFramebuffer; -- (void)destroyFramebuffer; -@end -*/ - -int gl_view_base_fb; -static String keyboard_text; -static GLView *_instance = NULL; - -static bool video_found_error = false; -static bool video_playing = false; -static CMTime video_current_time; - -void _show_keyboard(String); -void _hide_keyboard(); -bool _play_video(String, float, String, String); -bool _is_video_playing(); -void _pause_video(); -void _focus_out_video(); -void _unpause_video(); -void _stop_video(); -CGFloat _points_to_pixels(CGFloat); - -void _show_keyboard(String p_existing) { -	keyboard_text = p_existing; -	printf("instance on show is %p\n", _instance); -	[_instance open_keyboard]; -}; - -void _hide_keyboard() { -	printf("instance on hide is %p\n", _instance); -	[_instance hide_keyboard]; -	keyboard_text = ""; -}; - -Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) { -	UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0); -	if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) { -		insets = [_instance safeAreaInsets]; -	} -	ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0, -			Rect2(0, 0, p_window_width, p_window_height)); -	UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right)); -	return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top); -} - -bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { -	p_path = ProjectSettings::get_singleton()->globalize_path(p_path); - -	NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease]; - -	_instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]]; - -	_instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset]; -	[_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil]; - -	_instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem]; -	_instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer]; - -	[_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil]; -	[[NSNotificationCenter defaultCenter] -			addObserver:_instance -			   selector:@selector(playerItemDidReachEnd:) -				   name:AVPlayerItemDidPlayToEndTimeNotification -				 object:[_instance.avPlayer currentItem]]; - -	[_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; - -	[_instance.avPlayerLayer setFrame:_instance.bounds]; -	[_instance.layer addSublayer:_instance.avPlayerLayer]; -	[_instance.avPlayer play]; - -	AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; - -	NSMutableArray *allAudioParams = [NSMutableArray array]; -	for (id track in audioGroup.options) { -		NSString *language = [[track locale] localeIdentifier]; -		NSLog(@"subtitle lang: %@", language); - -		if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) { -			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; -			[audioInputParams setVolume:p_volume atTime:kCMTimeZero]; -			[audioInputParams setTrackID:[track trackID]]; -			[allAudioParams addObject:audioInputParams]; - -			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; -			[audioMix setInputParameters:allAudioParams]; - -			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; -			[_instance.avPlayer.currentItem setAudioMix:audioMix]; - -			break; -		} -	} - -	AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; -	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; - -	for (id track in useableTracks) { -		NSString *language = [[track locale] localeIdentifier]; -		NSLog(@"subtitle lang: %@", language); - -		if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) { -			[_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; -			break; -		} -	} - -	video_playing = true; - -	return true; -} - -bool _is_video_playing() { -	if (_instance.avPlayer.error) { -		printf("Error during playback\n"); -	} -	return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error); -} - -void _pause_video() { -	video_current_time = _instance.avPlayer.currentTime; -	[_instance.avPlayer pause]; -	video_playing = false; -} - -void _focus_out_video() { -	printf("focus out pausing video\n"); -	[_instance.avPlayer pause]; -}; - -void _unpause_video() { -	[_instance.avPlayer play]; -	video_playing = true; -}; - -void _stop_video() { -	[_instance.avPlayer pause]; -	[_instance.avPlayerLayer removeFromSuperlayer]; -	_instance.avPlayer = nil; -	video_playing = false; -} - -CGFloat _points_to_pixels(CGFloat points) { -	float pixelPerInch; -	if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { -		pixelPerInch = 132; -	} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { -		pixelPerInch = 163; -	} else { -		pixelPerInch = 160; -	} -	CGFloat pointsPerInch = 72.0; -	return (points / pointsPerInch * pixelPerInch); -} - -@implementation GLView - -@synthesize animationInterval; - -static const int max_touches = 8; -static UITouch *touches[max_touches]; - -static void init_touches() { -	for (int i = 0; i < max_touches; i++) { -		touches[i] = NULL; -	}; -}; - -static int get_touch_id(UITouch *p_touch) { -	int first = -1; -	for (int i = 0; i < max_touches; i++) { -		if (first == -1 && touches[i] == NULL) { -			first = i; -			continue; -		}; -		if (touches[i] == p_touch) -			return i; -	}; - -	if (first != -1) { -		touches[first] = p_touch; -		return first; -	}; - -	return -1; -}; - -static int remove_touch(UITouch *p_touch) { -	int remaining = 0; -	for (int i = 0; i < max_touches; i++) { -		if (touches[i] == NULL) -			continue; -		if (touches[i] == p_touch) -			touches[i] = NULL; -		else -			++remaining; -	}; -	return remaining; -}; - -static void clear_touches() { -	for (int i = 0; i < max_touches; i++) { -		touches[i] = NULL; -	}; -}; - -// Implement this to override the default layer class (which is [CALayer class]). -// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering. -+ (Class)layerClass { -	return [CAEAGLLayer class]; -} - -//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder: -- (id)initWithCoder:(NSCoder *)coder { -	active = FALSE; -	if ((self = [super initWithCoder:coder])) { -		self = [self initGLES]; -	} -	return self; -} - -- (id)initGLES { -	// Get our backing layer -	CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer; - -	// Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color. -	eaglLayer.opaque = YES; -	eaglLayer.drawableProperties = [NSDictionary -			dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE], -			kEAGLDrawablePropertyRetainedBacking, -			kEAGLColorFormatRGBA8, -			kEAGLDrawablePropertyColorFormat, -			nil]; - -	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - -	// Create GL ES 2 context -	if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") { -		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; -		NSLog(@"Setting up an OpenGL ES 2.0 context."); -		if (!context) { -			NSLog(@"Failed to create OpenGL ES 2.0 context!"); -			return nil; -		} -	} - -	if (![EAGLContext setCurrentContext:context]) { -		NSLog(@"Failed to set EAGLContext!"); -		return nil; -	} -	if (![self createFramebuffer]) { -		NSLog(@"Failed to create frame buffer!"); -		return nil; -	} - -	// Default the animation interval to 1/60th of a second. -	animationInterval = 1.0 / 60.0; -	return self; -} - -- (id<GLViewDelegate>)delegate { -	return delegate; -} - -// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called. -- (void)setDelegate:(id<GLViewDelegate>)d { -	delegate = d; -	delegateSetup = ![delegate respondsToSelector:@selector(setupView:)]; -} - -@synthesize useCADisplayLink; - -// If our view is resized, we'll be asked to layout subviews. -// This is the perfect opportunity to also update the framebuffer so that it is -// the same size as our display area. - -- (void)layoutSubviews { -	[EAGLContext setCurrentContext:context]; -	[self destroyFramebuffer]; -	[self createFramebuffer]; -	[self drawView]; -} - -- (BOOL)createFramebuffer { -	// Generate IDs for a framebuffer object and a color renderbuffer -	UIScreen *mainscr = [UIScreen mainScreen]; -	printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height); -	self.contentScaleFactor = mainscr.nativeScale; - -	glGenFramebuffersOES(1, &viewFramebuffer); -	glGenRenderbuffersOES(1, &viewRenderbuffer); - -	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); -	// This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer) -	// allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view). -	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer]; -	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); - -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); -	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); - -	// For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer. -	glGenRenderbuffersOES(1, &depthRenderbuffer); -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); -	glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); -	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); - -	if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { -		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); -		return NO; -	} - -	if (OS::get_singleton()) { -		OS::VideoMode vm; -		vm.fullscreen = true; -		vm.width = backingWidth; -		vm.height = backingHeight; -		vm.resizable = false; -		OS::get_singleton()->set_video_mode(vm); -		OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer); -	}; -	gl_view_base_fb = viewFramebuffer; - -	return YES; -} - -// Clean up any buffers we have allocated. -- (void)destroyFramebuffer { -	glDeleteFramebuffersOES(1, &viewFramebuffer); -	viewFramebuffer = 0; -	glDeleteRenderbuffersOES(1, &viewRenderbuffer); -	viewRenderbuffer = 0; - -	if (depthRenderbuffer) { -		glDeleteRenderbuffersOES(1, &depthRenderbuffer); -		depthRenderbuffer = 0; -	} -} - -- (void)startAnimation { -	if (active) -		return; -	active = TRUE; -	printf("start animation!\n"); -	if (useCADisplayLink) { -		// Approximate frame rate -		// assumes device refreshes at 60 fps -		int frameInterval = (int)floor(animationInterval * 60.0f); - -		displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; -		[displayLink setFrameInterval:frameInterval]; - -		// Setup DisplayLink in main thread -		[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; -	} else { -		animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; -	} - -	if (video_playing) { -		_unpause_video(); -	} -} - -- (void)stopAnimation { -	if (!active) -		return; -	active = FALSE; -	printf("******** stop animation!\n"); - -	if (useCADisplayLink) { -		[displayLink invalidate]; -		displayLink = nil; -	} else { -		[animationTimer invalidate]; -		animationTimer = nil; -	} - -	clear_touches(); - -	if (video_playing) { -		// save position -	} -} - -- (void)setAnimationInterval:(NSTimeInterval)interval { -	animationInterval = interval; -	if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) { -		[self stopAnimation]; -		[self startAnimation]; -	} -} - -// 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.0, TRUE) == kCFRunLoopRunHandledSource) -			; - -		// We are good to go, resume the CADisplayLink -		[displayLink setPaused:NO]; -	} - -	// Make sure that you are drawing to the current context -	[EAGLContext setCurrentContext:context]; - -	// If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again. -	if (!delegateSetup) { -		[delegate setupView:self]; -		delegateSetup = YES; -	} - -	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); - -	[delegate drawView:self]; - -	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); -	[context presentRenderbuffer:GL_RENDERBUFFER_OES]; - -#ifdef DEBUG_ENABLED -	GLenum err = glGetError(); -	if (err) -		NSLog(@"DrawView: %x error", err); -#endif -} - -- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseBegan) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			CGPoint touchPoint = [touch locationInView:self]; -			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); -		}; -	}; -} - -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseMoved) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			CGPoint touchPoint = [touch locationInView:self]; -			CGPoint prev_point = [touch previousLocationInView:self]; -			OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); -		}; -	}; -} - -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { -	NSArray *tlist = [[event allTouches] allObjects]; -	for (unsigned int i = 0; i < [tlist count]; i++) { -		if ([touches containsObject:[tlist objectAtIndex:i]]) { -			UITouch *touch = [tlist objectAtIndex:i]; -			if (touch.phase != UITouchPhaseEnded) -				continue; -			int tid = get_touch_id(touch); -			ERR_FAIL_COND(tid == -1); -			remove_touch(touch); -			CGPoint touchPoint = [touch locationInView:self]; -			OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); -		}; -	}; -} - -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { -	OSIPhone::get_singleton()->touches_cancelled(); -	clear_touches(); -}; - -- (BOOL)canBecomeFirstResponder { -	return YES; -}; - -- (void)open_keyboard { -	//keyboard_text = p_existing; -	[self becomeFirstResponder]; -}; - -- (void)hide_keyboard { -	//keyboard_text = p_existing; -	[self resignFirstResponder]; -}; - -- (void)keyboardOnScreen:(NSNotification *)notification { -	NSDictionary *info = notification.userInfo; -	NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; - -	CGRect rawFrame = [value CGRectValue]; -	CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil]; - -	OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height)); -} - -- (void)keyboardHidden:(NSNotification *)notification { -	OSIPhone::get_singleton()->set_virtual_keyboard_height(0); -} - -- (void)deleteBackward { -	if (keyboard_text.length()) -		keyboard_text.erase(keyboard_text.length() - 1, 1); -	OSIPhone::get_singleton()->key(KEY_BACKSPACE, true); -}; - -- (BOOL)hasText { -	return keyboard_text.length() ? YES : NO; -}; - -- (void)insertText:(NSString *)p_text { -	String character; -	character.parse_utf8([p_text UTF8String]); -	keyboard_text = keyboard_text + character; -	OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); -	printf("inserting text with character %lc\n", (CharType)character[0]); -}; - -- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { -	printf("*********** route changed!\n"); -	NSDictionary *interuptionDict = notification.userInfo; - -	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; - -	switch (routeChangeReason) { -		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { -			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); -			NSLog(@"Headphone/Line plugged in"); -		}; break; - -		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { -			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); -			NSLog(@"Headphone/Line was pulled. Resuming video play...."); -			if (_is_video_playing()) { -				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ -					[_instance.avPlayer play]; // NOTE: change this line according your current player implementation -					NSLog(@"resumed play"); -				}); -			}; -		}; break; - -		case AVAudioSessionRouteChangeReasonCategoryChange: { -			// called at start - also when other audio wants to play -			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); -		}; break; -	} -} - -// When created via code however, we get initWithFrame -- (id)initWithFrame:(CGRect)frame { -	self = [super initWithFrame:frame]; -	_instance = self; -	printf("after init super %p\n", self); -	if (self != nil) { -		self = [self initGLES]; -		printf("after init gles %p\n", self); -	} -	init_touches(); -	self.multipleTouchEnabled = YES; -	self.autocorrectionType = UITextAutocorrectionTypeNo; - -	printf("******** adding observer for sound routing changes\n"); -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(audioRouteChangeListenerCallback:) -				   name:AVAudioSessionRouteChangeNotification -				 object:nil]; - -	printf("******** adding observer for keyboard show/hide\n"); -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(keyboardOnScreen:) -				   name:UIKeyboardDidShowNotification -				 object:nil]; -	[[NSNotificationCenter defaultCenter] -			addObserver:self -			   selector:@selector(keyboardHidden:) -				   name:UIKeyboardDidHideNotification -				 object:nil]; - -	//self.autoresizesSubviews = YES; -	//[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth]; - -	return self; -} - -//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers { -//	return YES; -//} - -//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{ -//	return YES; -//} - -// Stop animating and release resources when they are no longer needed. -- (void)dealloc { -	[self stopAnimation]; - -	if ([EAGLContext currentContext] == context) { -		[EAGLContext setCurrentContext:nil]; -	} - -	[context release]; -	context = nil; - -	[super dealloc]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { -	if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) { -		if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) { -			_stop_video(); -			video_found_error = true; -		} - -		if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay && -				_instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && -				CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) { -			//NSLog(@"time: %@", video_current_time); - -			[_instance.avPlayer seekToTime:video_current_time]; -			video_current_time = kCMTimeZero; -		} -	} - -	if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) { -		NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate); -		if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) { -			dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ -				[_instance.avPlayer play]; // NOTE: change this line according your current player implementation -				NSLog(@"resumed play"); -			}); - -			NSLog(@" . . . PAUSED (or just started)"); -		} -	} -} - -- (void)playerItemDidReachEnd:(NSNotification *)notification { -	_stop_video(); -} - -@end diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm index b9d217c9d2..090b772947 100644 --- a/platform/iphone/godot_iphone.cpp +++ b/platform/iphone/godot_iphone.mm @@ -1,5 +1,5 @@  /*************************************************************************/ -/*  godot_iphone.cpp                                                     */ +/*  godot_iphone.mm                                                      */  /*************************************************************************/  /*                       This file is part of:                           */  /*                           GODOT ENGINE                                */ @@ -38,19 +38,53 @@  static OSIPhone *os = nullptr; -extern "C" { -int add_path(int p_argc, char **p_args); -int add_cmdline(int p_argc, char **p_args); +int add_path(int, char **); +int add_cmdline(int, char **); +int iphone_main(int, char **, String); + +int add_path(int p_argc, char **p_args) { +	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; +	if (!str) { +		return p_argc; +	} + +	p_args[p_argc++] = (char *)"--path"; +	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo +	p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; +	p_args[p_argc] = NULL; +	[str release]; + +	return p_argc;  }; -int iphone_main(int, int, int, char **, String); +int add_cmdline(int p_argc, char **p_args) { +	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; +	if (!arr) { +		return p_argc; +	} + +	for (NSUInteger i = 0; i < [arr count]; i++) { +		NSString *str = [arr objectAtIndex:i]; +		if (!str) { +			continue; +		} +		[str retain]; // @todo delete these at some point +		p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding]; +		[str release]; +	}; + +	p_args[p_argc] = NULL; + +	return p_argc; +}; -int iphone_main(int width, int height, int argc, char **argv, String data_dir) { +int iphone_main(int argc, char **argv, String data_dir) {  	size_t len = strlen(argv[0]);  	while (len--) { -		if (argv[0][len] == '/') +		if (argv[0][len] == '/') {  			break; +		}  	}  	if (len >= 0) { @@ -65,7 +99,10 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {  	char cwd[512];  	getcwd(cwd, sizeof(cwd));  	printf("cwd %s\n", cwd); -	os = new OSIPhone(width, height, data_dir); +	os = new OSIPhone(data_dir); + +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE  	char *fargv[64];  	for (int i = 0; i < argc; i++) { @@ -76,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {  	argc = add_cmdline(argc, fargv);  	printf("os created\n"); +  	Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);  	printf("setup %i\n", err); -	if (err != OK) +	if (err != OK) {  		return 255; +	} + +	os->initialize_modules();  	return 0;  }; diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h new file mode 100644 index 0000000000..62fa2f5a32 --- /dev/null +++ b/platform/iphone/godot_view.h @@ -0,0 +1,56 @@ +/*************************************************************************/ +/*  godot_view.h                                                         */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +class String; + +@protocol DisplayLayer; +@protocol GodotViewRendererProtocol; + +@interface GodotView : UIView <UIKeyInput> + +@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer; + +@property(assign, readonly, nonatomic) BOOL isActive; + +@property(assign, nonatomic) BOOL useCADisplayLink; +@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer; +@property(assign, readonly, nonatomic) BOOL canRender; + +@property(assign, nonatomic) NSTimeInterval renderingInterval; + +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName; +- (void)stopRendering; +- (void)startRendering; + +- (BOOL)becomeFirstResponderWithString:(String)p_existing; + +@end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm new file mode 100644 index 0000000000..c0a31549c4 --- /dev/null +++ b/platform/iphone/godot_view.mm @@ -0,0 +1,499 @@ +/*************************************************************************/ +/*  godot_view.mm                                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import "godot_view.h" +#include "core/os/keyboard.h" +#include "core/ustring.h" +#import "display_layer.h" +#include "display_server_iphone.h" +#import "godot_view_gesture_recognizer.h" +#import "godot_view_renderer.h" + +#import <CoreMotion/CoreMotion.h> + +static const int max_touches = 8; + +@interface GodotView () { +	UITouch *godot_touches[max_touches]; +	String keyboard_text; +} + +@property(assign, nonatomic) BOOL isActive; + +// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15 +@property(strong, nonatomic) CADisplayLink *displayLink; + +// An animation timer that, when animation is started, will periodically call -drawView at the given rate. +// Only used if CADisplayLink is not +@property(strong, nonatomic) NSTimer *animationTimer; + +@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer; + +@property(strong, nonatomic) CMMotionManager *motionManager; + +@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer; + +@end + +@implementation GodotView + +- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName { +	if (self.renderingLayer) { +		return self.renderingLayer; +	} + +	CALayer<DisplayLayer> *layer; + +	if ([driverName isEqualToString:@"vulkan"]) { +		layer = [GodotMetalLayer layer]; +	} else if ([driverName isEqualToString:@"opengl_es"]) { +		if (@available(iOS 13, *)) { +			NSLog(@"OpenGL ES is deprecated on iOS 13"); +		} +#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR +		return nil; +#else +		layer = [GodotOpenGLLayer layer]; +#endif +	} else { +		return nil; +	} + +	layer.frame = self.bounds; +	layer.contentsScale = self.contentScaleFactor; + +	[self.layer addSublayer:layer]; +	self.renderingLayer = layer; + +	[layer initializeDisplayLayer]; + +	return self.renderingLayer; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { +	self = [super initWithCoder:coder]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (instancetype)initWithFrame:(CGRect)frame { +	self = [super initWithFrame:frame]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)dealloc { +	[self stopRendering]; + +	self.renderer = nil; + +	if (self.renderingLayer) { +		[self.renderingLayer removeFromSuperlayer]; +		self.renderingLayer = nil; +	} + +	if (self.motionManager) { +		[self.motionManager stopDeviceMotionUpdates]; +		self.motionManager = nil; +	} + +	if (self.displayLink) { +		[self.displayLink invalidate]; +		self.displayLink = nil; +	} + +	if (self.animationTimer) { +		[self.animationTimer invalidate]; +		self.animationTimer = nil; +	} + +	if (self.delayGestureRecognizer) { +		self.delayGestureRecognizer = nil; +	} + +	[super dealloc]; +} + +- (void)godot_commonInit { +	self.contentScaleFactor = [UIScreen mainScreen].nativeScale; + +	[self initTouches]; + +	// Configure and start accelerometer +	if (!self.motionManager) { +		self.motionManager = [[[CMMotionManager alloc] init] autorelease]; +		if (self.motionManager.deviceMotionAvailable) { +			self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0; +			[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical]; +		} else { +			self.motionManager = nil; +		} +	} + +	// Initialize delay gesture recognizer +	GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init]; +	self.delayGestureRecognizer = gestureRecognizer; +	[self addGestureRecognizer:self.delayGestureRecognizer]; +	[gestureRecognizer release]; +} + +- (void)stopRendering { +	if (!self.isActive) { +		return; +	} + +	self.isActive = NO; + +	printf("******** stop animation!\n"); + +	if (self.useCADisplayLink) { +		[self.displayLink invalidate]; +		self.displayLink = nil; +	} else { +		[self.animationTimer invalidate]; +		self.animationTimer = nil; +	} + +	[self clearTouches]; +} + +- (void)startRendering { +	if (self.isActive) { +		return; +	} + +	self.isActive = YES; + +	printf("start animation!\n"); + +	if (self.useCADisplayLink) { +		self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)]; + +		//        if (@available(iOS 10, *)) { +		self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval); +		//        } else { +		//            // Approximate frame rate +		//            // assumes device refreshes at 60 fps +		//            int frameInterval = (int)floor(self.renderingInterval * 60.0f); +		//            [self.displayLink setFrameInterval:frameInterval]; +		//        } + +		// Setup DisplayLink in main thread +		[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; +	} else { +		self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; +	} +} + +- (void)drawView { +	if (!self.isActive) { +		printf("draw view not active!\n"); +		return; +	} + +	if (self.useCADisplayLink) { +		// Pause the CADisplayLink to avoid recursion +		[self.displayLink setPaused:YES]; + +		// Process all input events +		while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource) +			; + +		// We are good to go, resume the CADisplayLink +		[self.displayLink setPaused:NO]; +	} + +	[self.renderingLayer renderDisplayLayer]; + +	if (!self.renderer) { +		return; +	} + +	if ([self.renderer setupView:self]) { +		return; +	} + +	[self handleMotion]; +	[self.renderer renderOnView:self]; +} + +- (BOOL)canRender { +	if (self.useCADisplayLink) { +		return self.displayLink != nil; +	} else { +		return self.animationTimer != nil; +	} +} + +- (void)setRenderingInterval:(NSTimeInterval)renderingInterval { +	_renderingInterval = renderingInterval; + +	if (self.canRender) { +		[self stopRendering]; +		[self startRendering]; +	} +} + +- (void)layoutSubviews { +	if (self.renderingLayer) { +		self.renderingLayer.frame = self.bounds; +		[self.renderingLayer layoutDisplayLayer]; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size); +		} +	} + +	[super layoutSubviews]; +} + +// MARK: - Input + +// MARK: Keyboard + +- (BOOL)canBecomeFirstResponder { +	return YES; +} + +- (BOOL)becomeFirstResponderWithString:(String)p_existing { +	keyboard_text = p_existing; +	return [self becomeFirstResponder]; +} + +- (BOOL)resignFirstResponder { +	keyboard_text = String(); +	return [super resignFirstResponder]; +} + +- (void)deleteBackward { +	if (keyboard_text.length()) { +		keyboard_text.erase(keyboard_text.length() - 1, 1); +	} +	DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true); +} + +- (BOOL)hasText { +	return keyboard_text.length() > 0; +} + +- (void)insertText:(NSString *)p_text { +	String character; +	character.parse_utf8([p_text UTF8String]); +	keyboard_text = keyboard_text + character; +	DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true); +} + +// MARK: Touches + +- (void)initTouches { +	for (int i = 0; i < max_touches; i++) { +		godot_touches[i] = NULL; +	} +} + +- (int)getTouchIDForTouch:(UITouch *)p_touch { +	int first = -1; +	for (int i = 0; i < max_touches; i++) { +		if (first == -1 && godot_touches[i] == NULL) { +			first = i; +			continue; +		} +		if (godot_touches[i] == p_touch) { +			return i; +		} +	} + +	if (first != -1) { +		godot_touches[first] = p_touch; +		return first; +	} + +	return -1; +} + +- (int)removeTouch:(UITouch *)p_touch { +	int remaining = 0; +	for (int i = 0; i < max_touches; i++) { +		if (godot_touches[i] == NULL) { +			continue; +		} +		if (godot_touches[i] == p_touch) { +			godot_touches[i] = NULL; +		} else { +			++remaining; +		} +	} +	return remaining; +} + +- (void)clearTouches { +	for (int i = 0; i < max_touches; i++) { +		godot_touches[i] = NULL; +	} +} + +- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touchesSet containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			CGPoint touchPoint = [touch locationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1); +		} +	} +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			CGPoint touchPoint = [touch locationInView:self]; +			CGPoint prev_point = [touch previousLocationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor); +		} +	} +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			[self removeTouch:touch]; +			CGPoint touchPoint = [touch locationInView:self]; +			DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false); +		} +	} +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +	NSArray *tlist = [event.allTouches allObjects]; +	for (unsigned int i = 0; i < [tlist count]; i++) { +		if ([touches containsObject:[tlist objectAtIndex:i]]) { +			UITouch *touch = [tlist objectAtIndex:i]; +			int tid = [self getTouchIDForTouch:touch]; +			ERR_FAIL_COND(tid == -1); +			DisplayServerIPhone::get_singleton()->touches_cancelled(tid); +		} +	} +	[self clearTouches]; +} + +// MARK: Motion + +- (void)handleMotion { +	if (!self.motionManager) { +		return; +	} + +	// Just using polling approach for now, we can set this up so it sends +	// data to us in intervals, might be better. See Apple reference pages +	// for more details: +	// https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc + +	// Apple splits our accelerometer date into a gravity and user movement +	// component. We add them back together +	CMAcceleration gravity = self.motionManager.deviceMotion.gravity; +	CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration; + +	///@TODO We don't seem to be getting data here, is my device broken or +	/// is this code incorrect? +	CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field; + +	///@TODO we can access rotationRate as a CMRotationRate variable +	///(processed date) or CMGyroData (raw data), have to see what works +	/// best +	CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate; + +	// Adjust for screen orientation. +	// [[UIDevice currentDevice] orientation] changes even if we've fixed +	// our orientation which is not a good thing when you're trying to get +	// your user to move the screen in all directions and want consistent +	// output + +	///@TODO Using [[UIApplication sharedApplication] statusBarOrientation] +	/// is a bit of a hack. Godot obviously knows the orientation so maybe +	/// we +	// can use that instead? (note that left and right seem swapped) + +	UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown; + +	if (@available(iOS 13, *)) { +		interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation; +#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR +	} else { +		interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; +#endif +	} + +	switch (interfaceOrientation) { +		case UIInterfaceOrientationLandscapeLeft: { +			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z); +		} break; +		case UIInterfaceOrientationLandscapeRight: { +			DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z); +		} break; +		case UIInterfaceOrientationPortraitUpsideDown: { +			DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z); +		} break; +		default: { // assume portrait +			DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z); +			DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z); +			DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z); +			DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z); +		} break; +	} +} + +@end diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h new file mode 100644 index 0000000000..1431a9fb89 --- /dev/null +++ b/platform/iphone/godot_view_gesture_recognizer.h @@ -0,0 +1,46 @@ +/*************************************************************************/ +/*  godot_view_gesture_recognizer.h                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +// GLViewGestureRecognizer allows iOS gestures to work currectly by +// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer. +// It catches all gestures incoming to UIView and delays them for 150ms +// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer) +// If touch cancellation or end message is fired it fires delayed +// begin touch immediately as well as last touch signal + +#import <UIKit/UIKit.h> + +@interface GodotViewGestureRecognizer : UIGestureRecognizer + +@property(nonatomic, readonly, assign) NSTimeInterval delayTimeInterval; + +- (instancetype)init; + +@end diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm new file mode 100644 index 0000000000..99ee42ecb3 --- /dev/null +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -0,0 +1,178 @@ +/*************************************************************************/ +/*  godot_view_gesture_recognizer.mm                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import "godot_view_gesture_recognizer.h" + +#include "core/project_settings.h" + +// Minimum distance for touches to move to fire +// a delay timer before scheduled time. +// Should be the low enough to not cause issues with dragging +// but big enough to allow click to work. +const CGFloat kGLGestureMovementDistance = 0.5; + +@interface GodotViewGestureRecognizer () + +@property(nonatomic, readwrite, assign) NSTimeInterval delayTimeInterval; + +@end + +@interface GodotViewGestureRecognizer () + +// Timer used to delay begin touch message. +// Should work as simple emulation of UIDelayedAction +@property(strong, nonatomic) NSTimer *delayTimer; + +// Delayed touch parameters +@property(strong, nonatomic) NSSet *delayedTouches; +@property(strong, nonatomic) UIEvent *delayedEvent; + +@end + +@implementation GodotViewGestureRecognizer + +- (instancetype)init { +	self = [super init]; + +	self.cancelsTouchesInView = YES; +	self.delaysTouchesBegan = YES; +	self.delaysTouchesEnded = YES; + +	self.delayTimeInterval = GLOBAL_GET("input_devices/pointing/ios/touch_delay"); + +	return self; +} + +- (void)dealloc { +	if (self.delayTimer) { +		[self.delayTimer invalidate]; +		self.delayTimer = nil; +	} + +	if (self.delayedTouches) { +		self.delayedTouches = nil; +	} + +	if (self.delayedEvent) { +		self.delayedEvent = nil; +	} + +	[super dealloc]; +} + +- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event { +	[self.delayTimer fire]; + +	self.delayedTouches = touches; +	self.delayedEvent = event; + +	self.delayTimer = [NSTimer +			scheduledTimerWithTimeInterval:self.delayTimeInterval +									target:self +								  selector:@selector(fireDelayedTouches:) +								  userInfo:nil +								   repeats:NO]; +} + +- (void)fireDelayedTouches:(id)timer { +	[self.delayTimer invalidate]; +	self.delayTimer = nil; + +	if (self.delayedTouches) { +		[self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; +	} + +	self.delayedTouches = nil; +	self.delayedEvent = nil; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; +	[self delayTouches:cleared andEvent:event]; +	[cleared release]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved]; + +	if (self.delayTimer) { +		// We should check if movement was significant enough to fire an event +		// for dragging to work correctly. +		for (UITouch *touch in cleared) { +			CGPoint from = [touch locationInView:self.view]; +			CGPoint to = [touch previousLocationInView:self.view]; +			CGFloat xDistance = from.x - to.x; +			CGFloat yDistance = from.y - to.y; + +			CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance); + +			// Early exit, since one of touches has moved enough to fire a drag event. +			if (distance > kGLGestureMovementDistance) { +				[self.delayTimer fire]; +				[self.view touchesMoved:cleared withEvent:event]; +				[cleared release]; +				return; +			} +		} + +		[cleared release]; +		return; +	} + +	[self.view touchesMoved:cleared withEvent:event]; +	[cleared release]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +	[self.delayTimer fire]; + +	NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; +	[self.view touchesEnded:cleared withEvent:event]; +	[cleared release]; +} + +- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +	[self.delayTimer fire]; +	[self.view touchesCancelled:touches withEvent:event]; +}; + +- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { +	NSMutableSet *cleared = [touches mutableCopy]; + +	for (UITouch *touch in touches) { +		if (touch.phase != phaseToSave) { +			[cleared removeObject:touch]; +		} +	} + +	return cleared; +} + +@end diff --git a/platform/iphone/godot_view_renderer.h b/platform/iphone/godot_view_renderer.h new file mode 100644 index 0000000000..ea8998c808 --- /dev/null +++ b/platform/iphone/godot_view_renderer.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/*  godot_view_renderer.h                                                */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import <UIKit/UIKit.h> + +@protocol GodotViewRendererProtocol <NSObject> + +@property(assign, readonly, nonatomic) BOOL hasFinishedSetup; + +- (BOOL)setupView:(UIView *)view; +- (void)renderOnView:(UIView *)view; + +@end + +@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol> + +@end diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm new file mode 100644 index 0000000000..045fb451f0 --- /dev/null +++ b/platform/iphone/godot_view_renderer.mm @@ -0,0 +1,117 @@ +/*************************************************************************/ +/*  godot_view_renderer.mm                                               */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import "godot_view_renderer.h" +#include "core/os/keyboard.h" +#include "core/project_settings.h" +#import "display_server_iphone.h" +#include "main/main.h" +#include "os_iphone.h" +#include "servers/audio_server.h" + +#import <AudioToolbox/AudioServices.h> +#import <CoreMotion/CoreMotion.h> +#import <GameController/GameController.h> +#import <QuartzCore/QuartzCore.h> +#import <UIKit/UIKit.h> + +@interface GodotViewRenderer () + +@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup; +@property(assign, nonatomic) BOOL hasStartedMain; +@property(assign, nonatomic) BOOL hasFinishedSetup; + +@end + +@implementation GodotViewRenderer + +- (BOOL)setupView:(UIView *)view { +	if (self.hasFinishedSetup) { +		return NO; +	} + +	if (!OS::get_singleton()) { +		exit(0); +	} + +	if (!self.hasFinishedProjectDataSetup) { +		[self setupProjectData]; +		return YES; +	} + +	if (!self.hasStartedMain) { +		self.hasStartedMain = YES; +		OSIPhone::get_singleton()->start(); +		return YES; +	} + +	self.hasFinishedSetup = YES; + +	return NO; +} + +- (void)setupProjectData { +	self.hasFinishedProjectDataSetup = YES; + +	Main::setup2(); + +	// this might be necessary before here +	NSDictionary *dict = [[NSBundle mainBundle] infoDictionary]; +	for (NSString *key in dict) { +		NSObject *value = [dict objectForKey:key]; +		String ukey = String::utf8([key UTF8String]); + +		// we need a NSObject to Variant conversor + +		if ([value isKindOfClass:[NSString class]]) { +			NSString *str = (NSString *)value; +			String uval = String::utf8([str UTF8String]); + +			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval); + +		} else if ([value isKindOfClass:[NSNumber class]]) { +			NSNumber *n = (NSNumber *)value; +			double dval = [n doubleValue]; + +			ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval); +		}; +		// do stuff +	} +} + +- (void)renderOnView:(UIView *)view { +	if (!OSIPhone::get_singleton()) { +		return; +	} + +	OSIPhone::get_singleton()->iterate(); +} + +@end diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h index b11e22fec6..381edfa718 100644 --- a/platform/iphone/icloud.h +++ b/platform/iphone/icloud.h @@ -44,9 +44,9 @@ class ICloud : public Object {  	List<Variant> pending_events;  public: -	Error remove_key(Variant p_param); -	Variant set_key_values(Variant p_param); -	Variant get_key_value(Variant p_param); +	Error remove_key(String p_param); +	Array set_key_values(Dictionary p_params); +	Variant get_key_value(String p_param);  	Error synchronize_key_values();  	Variant get_all_key_values(); diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm index c768274b1f..d3086e6cea 100644 --- a/platform/iphone/icloud.mm +++ b/platform/iphone/icloud.mm @@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;  void ICloud::_bind_methods() {  	ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key); +  	ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);  	ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value); +  	ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);  	ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values); @@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {  	} else if ([object isKindOfClass:[NSArray class]]) {  		Array result;  		NSArray *array = (NSArray *)object; -		for (unsigned int i = 0; i < [array count]; ++i) { +		for (NSUInteger i = 0; i < [array count]; ++i) {  			NSObject *value = [array objectAtIndex:i];  			result.push_back(nsobject_to_variant(value));  		} @@ -149,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {  NSObject *variant_to_nsobject(Variant v) {  	if (v.get_type() == Variant::STRING) {  		return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease]; -	} else if (v.get_type() == Variant::REAL) { +	} else if (v.get_type() == Variant::FLOAT) {  		return [NSNumber numberWithDouble:(double)v];  	} else if (v.get_type() == Variant::INT) {  		return [NSNumber numberWithLongLong:(long)(int)v]; @@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {  		NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];  		Dictionary dic = v;  		Array keys = dic.keys(); -		for (unsigned int i = 0; i < keys.size(); ++i) { +		for (int i = 0; i < keys.size(); ++i) {  			NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];  			NSObject *value = variant_to_nsobject(dic[keys[i]]); @@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {  	} else if (v.get_type() == Variant::ARRAY) {  		NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];  		Array arr = v; -		for (unsigned int i = 0; i < arr.size(); ++i) { +		for (int i = 0; i < arr.size(); ++i) {  			NSObject *value = variant_to_nsobject(arr[i]);  			if (value == NULL) {  				//trying to add something unsupported to the array. cancel the whole array @@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {  	return NULL;  } -Error ICloud::remove_key(Variant p_param) { -	String param = p_param; -	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; +Error ICloud::remove_key(String p_param) { +	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];  	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore]; @@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {  }  //return an array of the keys that could not be set -Variant ICloud::set_key_values(Variant p_params) { -	Dictionary params = p_params; -	Array keys = params.keys(); +Array ICloud::set_key_values(Dictionary p_params) { +	Array keys = p_params.keys();  	Array error_keys; -	for (unsigned int i = 0; i < keys.size(); ++i) { +	for (int i = 0; i < keys.size(); ++i) {  		String variant_key = keys[i]; -		Variant variant_value = params[variant_key]; +		Variant variant_value = p_params[variant_key];  		NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];  		if (key == NULL) { @@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {  	return error_keys;  } -Variant ICloud::get_key_value(Variant p_param) { -	String param = p_param; - -	NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease]; +Variant ICloud::get_key_value(String p_param) { +	NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];  	NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];  	if (![[store dictionaryRepresentation] objectForKey:key]) { diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h index 44e65e77ed..beb58af2c7 100644 --- a/platform/iphone/in_app_store.h +++ b/platform/iphone/in_app_store.h @@ -44,9 +44,9 @@ class InAppStore : public Object {  	List<Variant> pending_events;  public: -	Error request_product_info(Variant p_params); +	Error request_product_info(Dictionary p_params);  	Error restore_purchases(); -	Error purchase(Variant p_params); +	Error purchase(Dictionary p_params);  	int get_pending_event_count();  	Variant pop_pending_event(); diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index 548dcc549d..1477f92200 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -39,8 +39,10 @@ extern "C" {  bool auto_finish_transactions = true;  NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary]; +static NSArray *latestProducts;  @interface SKProduct (LocalizedPrice) +  @property(nonatomic, readonly) NSString *localizedPrice;  @end @@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {  - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {  	NSArray *products = response.products; +	latestProducts = products; +  	Dictionary ret;  	ret["type"] = "product_info";  	ret["result"] = "ok"; @@ -126,16 +130,15 @@ void InAppStore::_bind_methods() {  @end -Error InAppStore::request_product_info(Variant p_params) { -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER); +Error InAppStore::request_product_info(Dictionary p_params) { +	ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER); -	PackedStringArray pids = params["product_ids"]; +	PackedStringArray pids = p_params["product_ids"];  	printf("************ request product info! %i\n", pids.size());  	NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];  	for (int i = 0; i < pids.size(); i++) { -		printf("******** adding %ls to product list\n", pids[i].c_str()); +		printf("******** adding %s to product list\n", pids[i].utf8().get_data());  		NSString *pid = [[[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()] autorelease];  		[array addObject:pid];  	}; @@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {  						// which is still available in iOS 7.  						// Use SKPaymentTransaction's transactionReceipt. -						receipt = transaction.transactionReceipt; +						receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];  					}  				} else { -					receipt = transaction.transactionReceipt; +					receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];  				}  				NSString *receipt_to_send = nil; @@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {  @end -Error InAppStore::purchase(Variant p_params) { +Error InAppStore::purchase(Dictionary p_params) {  	ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);  	if (![SKPaymentQueue canMakePayments])  		return ERR_UNAVAILABLE;  	printf("purchasing!\n"); -	Dictionary params = p_params; -	ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER); +	ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER); + +	NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease]; + +	SKProduct *product = nil; + +	if (latestProducts) { +		for (SKProduct *storedProduct in latestProducts) { +			if ([storedProduct.productIdentifier isEqualToString:pid]) { +				product = storedProduct; +				break; +			} +		} +	} + +	if (!product) { +		return ERR_INVALID_PARAMETER; +	} -	NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease]; -	SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid]; +	SKPayment *payment = [SKPayment paymentWithProduct:product];  	SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];  	[defq addPayment:payment];  	printf("purchase sent!\n"); diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm index 5923f558a5..6d7699c0c9 100644 --- a/platform/iphone/ios.mm +++ b/platform/iphone/ios.mm @@ -29,17 +29,27 @@  /*************************************************************************/  #include "ios.h" -#include <sys/sysctl.h> - +#import "app_delegate.h"  #import <UIKit/UIKit.h> +#include <sys/sysctl.h>  void iOS::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);  };  void iOS::alert(const char *p_alert, const char *p_title) { -	UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease]; -	[alert show]; +	NSString *title = [NSString stringWithUTF8String:p_title]; +	NSString *message = [NSString stringWithUTF8String:p_alert]; + +	UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; +	UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK" +													 style:UIAlertActionStyleCancel +												   handler:^(id){ +												   }]; + +	[alert addAction:button]; + +	[AppDelegate.viewController presentViewController:alert animated:YES completion:nil];  }  String iOS::get_model() const { @@ -76,7 +86,7 @@ String iOS::get_rate_url(int p_app_id) const {  	// ios7 for everything?  	ret = templ_iOS7.replace("APP_ID", String::num(p_app_id)); -	printf("returning rate url %ls\n", ret.c_str()); +	printf("returning rate url %s\n", ret.utf8().get_data());  	return ret;  }; diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h new file mode 100644 index 0000000000..85e26e1dc8 --- /dev/null +++ b/platform/iphone/joypad_iphone.h @@ -0,0 +1,50 @@ +/*************************************************************************/ +/*  joypad_iphone.h                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import <GameController/GameController.h> + +@interface JoypadIPhoneObserver : NSObject + +- (void)startObserving; +- (void)startProcessing; +- (void)finishObserving; + +@end + +class JoypadIPhone { +private: +	JoypadIPhoneObserver *observer; + +public: +	JoypadIPhone(); +	~JoypadIPhone(); + +	void start_processing(); +}; diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm new file mode 100644 index 0000000000..6088f1c25c --- /dev/null +++ b/platform/iphone/joypad_iphone.mm @@ -0,0 +1,380 @@ +/*************************************************************************/ +/*  joypad_iphone.mm                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#import "joypad_iphone.h" +#include "core/project_settings.h" +#include "drivers/coreaudio/audio_driver_coreaudio.h" +#include "main/main.h" + +#import "godot_view.h" + +#include "os_iphone.h" + +JoypadIPhone::JoypadIPhone() { +	observer = [[JoypadIPhoneObserver alloc] init]; +	[observer startObserving]; +} + +JoypadIPhone::~JoypadIPhone() { +	if (observer) { +		[observer finishObserving]; +		observer = nil; +	} +} + +void JoypadIPhone::start_processing() { +	if (observer) { +		[observer startProcessing]; +	} +} + +@interface JoypadIPhoneObserver () + +@property(assign, nonatomic) BOOL isObserving; +@property(assign, nonatomic) BOOL isProcessing; +@property(strong, nonatomic) NSMutableDictionary *connectedJoypads; +@property(strong, nonatomic) NSMutableArray *joypadsQueue; + +@end + +@implementation JoypadIPhoneObserver + +- (instancetype)init { +	self = [super init]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)godot_commonInit { +	self.isObserving = NO; +	self.isProcessing = NO; +} + +- (void)startProcessing { +	self.isProcessing = YES; + +	for (GCController *controller in self.joypadsQueue) { +		[self addiOSJoypad:controller]; +	} + +	[self.joypadsQueue removeAllObjects]; +} + +- (void)startObserving { +	if (self.isObserving) { +		return; +	} + +	self.isObserving = YES; + +	self.connectedJoypads = [NSMutableDictionary dictionary]; +	self.joypadsQueue = [NSMutableArray array]; + +	// get told when controllers connect, this will be called right away for +	// already connected controllers +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(controllerWasConnected:) +				   name:GCControllerDidConnectNotification +				 object:nil]; + +	// get told when controllers disconnect +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(controllerWasDisconnected:) +				   name:GCControllerDidDisconnectNotification +				 object:nil]; +} + +- (void)finishObserving { +	if (self.isObserving) { +		[[NSNotificationCenter defaultCenter] removeObserver:self]; +	} + +	self.isObserving = NO; +	self.isProcessing = NO; + +	self.connectedJoypads = nil; +	self.joypadsQueue = nil; +} + +- (void)dealloc { +	[self finishObserving]; + +	[super dealloc]; +} + +- (int)getJoyIdForController:(GCController *)controller { +	NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; + +	for (NSNumber *key in keys) { +		int joy_id = [key intValue]; +		return joy_id; +	}; + +	return -1; +}; + +- (void)addiOSJoypad:(GCController *)controller { +	//     get a new id for our controller +	int joy_id = Input::get_singleton()->get_unused_joy_id(); + +	if (joy_id == -1) { +		printf("Couldn't retrieve new joy id\n"); +		return; +	} + +	// assign our player index +	if (controller.playerIndex == GCControllerPlayerIndexUnset) { +		controller.playerIndex = [self getFreePlayerIndex]; +	}; + +	// tell Godot about our new controller +	Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]); + +	// add it to our dictionary, this will retain our controllers +	[self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]]; + +	// set our input handler +	[self setControllerInputHandler:controller]; +} + +- (void)controllerWasConnected:(NSNotification *)notification { +	// get our controller +	GCController *controller = (GCController *)notification.object; + +	if (!controller) { +		printf("Couldn't retrieve new controller\n"); +		return; +	} + +	if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) { +		printf("Controller is already registered\n"); +	} else if (!self.isProcessing) { +		[self.joypadsQueue addObject:controller]; +	} else { +		[self addiOSJoypad:controller]; +	} +} + +- (void)controllerWasDisconnected:(NSNotification *)notification { +	// find our joystick, there should be only one in our dictionary +	GCController *controller = (GCController *)notification.object; + +	if (!controller) { +		return; +	} + +	NSArray *keys = [self.connectedJoypads allKeysForObject:controller]; +	for (NSNumber *key in keys) { +		// tell Godot this joystick is no longer there +		int joy_id = [key intValue]; +		Input::get_singleton()->joy_connection_changed(joy_id, false, ""); + +		// and remove it from our dictionary +		[self.connectedJoypads removeObjectForKey:key]; +	}; +}; + +- (GCControllerPlayerIndex)getFreePlayerIndex { +	bool have_player_1 = false; +	bool have_player_2 = false; +	bool have_player_3 = false; +	bool have_player_4 = false; + +	if (self.connectedJoypads == nil) { +		NSArray *keys = [self.connectedJoypads allKeys]; +		for (NSNumber *key in keys) { +			GCController *controller = [self.connectedJoypads objectForKey:key]; +			if (controller.playerIndex == GCControllerPlayerIndex1) { +				have_player_1 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex2) { +				have_player_2 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex3) { +				have_player_3 = true; +			} else if (controller.playerIndex == GCControllerPlayerIndex4) { +				have_player_4 = true; +			}; +		}; +	}; + +	if (!have_player_1) { +		return GCControllerPlayerIndex1; +	} else if (!have_player_2) { +		return GCControllerPlayerIndex2; +	} else if (!have_player_3) { +		return GCControllerPlayerIndex3; +	} else if (!have_player_4) { +		return GCControllerPlayerIndex4; +	} else { +		return GCControllerPlayerIndexUnset; +	}; +} + +- (void)setControllerInputHandler:(GCController *)controller { +	// Hook in the callback handler for the correct gamepad profile. +	// This is a bit of a weird design choice on Apples part. +	// You need to select the most capable gamepad profile for the +	// gamepad attached. +	if (controller.extendedGamepad != nil) { +		// The extended gamepad profile has all the input you could possibly find on +		// a gamepad but will only be active if your gamepad actually has all of +		// these... +		controller.extendedGamepad.valueChangedHandler = ^( +				GCExtendedGamepad *gamepad, GCControllerElement *element) { +			int joy_id = [self getJoyIdForController:controller]; + +			if (element == gamepad.buttonA) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +						gamepad.buttonA.isPressed); +			} else if (element == gamepad.buttonB) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, +						gamepad.buttonB.isPressed); +			} else if (element == gamepad.buttonX) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +						gamepad.buttonX.isPressed); +			} else if (element == gamepad.buttonY) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, +						gamepad.buttonY.isPressed); +			} else if (element == gamepad.leftShoulder) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, +						gamepad.leftShoulder.isPressed); +			} else if (element == gamepad.rightShoulder) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, +						gamepad.rightShoulder.isPressed); +			} else if (element == gamepad.dpad) { +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +						gamepad.dpad.up.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +						gamepad.dpad.down.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +						gamepad.dpad.left.isPressed); +				Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +						gamepad.dpad.right.isPressed); +			}; + +			Input::JoyAxis jx; +			jx.min = -1; +			if (element == gamepad.leftThumbstick) { +				jx.value = gamepad.leftThumbstick.xAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx); +				jx.value = -gamepad.leftThumbstick.yAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx); +			} else if (element == gamepad.rightThumbstick) { +				jx.value = gamepad.rightThumbstick.xAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx); +				jx.value = -gamepad.rightThumbstick.yAxis.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx); +			} else if (element == gamepad.leftTrigger) { +				jx.value = gamepad.leftTrigger.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx); +			} else if (element == gamepad.rightTrigger) { +				jx.value = gamepad.rightTrigger.value; +				Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx); +			}; +		}; +	} +	//    else if (controller.gamepad != nil) { +	//        // gamepad is the standard profile with 4 buttons, shoulder buttons and a +	//        // D-pad +	//        controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad, +	//                GCControllerElement *element) { +	//            int joy_id = [self getJoyIdForController:controller]; +	// +	//            if (element == gamepad.buttonA) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +	//                        gamepad.buttonA.isPressed); +	//            } else if (element == gamepad.buttonB) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B, +	//                        gamepad.buttonB.isPressed); +	//            } else if (element == gamepad.buttonX) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +	//                        gamepad.buttonX.isPressed); +	//            } else if (element == gamepad.buttonY) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y, +	//                        gamepad.buttonY.isPressed); +	//            } else if (element == gamepad.leftShoulder) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER, +	//                        gamepad.leftShoulder.isPressed); +	//            } else if (element == gamepad.rightShoulder) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER, +	//                        gamepad.rightShoulder.isPressed); +	//            } else if (element == gamepad.dpad) { +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +	//                        gamepad.dpad.up.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +	//                        gamepad.dpad.down.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +	//                        gamepad.dpad.left.isPressed); +	//                Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +	//                        gamepad.dpad.right.isPressed); +	//            }; +	//        }; +	//#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+, +	//        // while we are setting that as the minimum, seems our +	//        // build environment doesn't like it +	//    } else if (controller.microGamepad != nil) { +	//        // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad +	//        controller.microGamepad.valueChangedHandler = +	//                ^(GCMicroGamepad *gamepad, GCControllerElement *element) { +	//                    int joy_id = [self getJoyIdForController:controller]; +	// +	//                    if (element == gamepad.buttonA) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A, +	//                                gamepad.buttonA.isPressed); +	//                    } else if (element == gamepad.buttonX) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X, +	//                                gamepad.buttonX.isPressed); +	//                    } else if (element == gamepad.dpad) { +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP, +	//                                gamepad.dpad.up.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN, +	//                                gamepad.dpad.down.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT, +	//                                gamepad.dpad.left.isPressed); +	//                        Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT, +	//                                gamepad.dpad.right.isPressed); +	//                    }; +	//                }; +	//#endif +	//    }; + +	///@TODO need to add support for controller.motion which gives us access to +	/// the orientation of the device (if supported) + +	///@TODO need to add support for controllerPausedHandler which should be a +	/// toggle +}; + +@end diff --git a/platform/iphone/logo.png b/platform/iphone/logo.png Binary files differindex 405b6f93ca..966d8aa70a 100644 --- a/platform/iphone/logo.png +++ b/platform/iphone/logo.png diff --git a/platform/iphone/main.m b/platform/iphone/main.m index 164db2a74b..c292f02822 100644 --- a/platform/iphone/main.m +++ b/platform/iphone/main.m @@ -32,20 +32,25 @@  #import <UIKit/UIKit.h>  #include <stdio.h> +#include <vulkan/vulkan.h>  int gargc;  char **gargv;  int main(int argc, char *argv[]) { +#if defined(VULKAN_ENABLED) +	//MoltenVK - enable full component swizzling support +	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1); +#endif +  	printf("*********** main.m\n");  	gargc = argc;  	gargv = argv; -	NSAutoreleasePool *pool = [NSAutoreleasePool new]; -	AppDelegate *app = [AppDelegate alloc];  	printf("running app main\n"); -	UIApplicationMain(argc, argv, nil, @"AppDelegate"); -	printf("main done, pool release\n"); -	[pool release]; +	@autoreleasepool { +		UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); +	} +	printf("main done\n");  	return 0;  } diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp deleted file mode 100644 index 41dd623e69..0000000000 --- a/platform/iphone/os_iphone.cpp +++ /dev/null @@ -1,632 +0,0 @@ -/*************************************************************************/ -/*  os_iphone.cpp                                                        */ -/*************************************************************************/ -/*                       This file is part of:                           */ -/*                           GODOT ENGINE                                */ -/*                      https://godotengine.org                          */ -/*************************************************************************/ -/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ -/* Copyright (c) 2014-2020 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.                */ -/*************************************************************************/ - -#ifdef IPHONE_ENABLED - -#include "os_iphone.h" - -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif - -#if defined(VULKAN_ENABLED) -#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" -// #import <QuartzCore/CAMetalLayer.h> -#include <vulkan/vulkan_metal.h> -#endif - -#include "servers/rendering/rendering_server_raster.h" -#include "servers/rendering/rendering_server_wrap_mt.h" - -#include "main/main.h" - -#include "core/io/file_access_pack.h" -#include "core/os/dir_access.h" -#include "core/os/file_access.h" -#include "core/project_settings.h" -#include "drivers/unix/syslog_logger.h" - -#include "semaphore_iphone.h" - -#include <dlfcn.h> - -int OSIPhone::get_video_driver_count() const { -	return 2; -}; - -const char *OSIPhone::get_video_driver_name(int p_driver) const { -	switch (p_driver) { -		case VIDEO_DRIVER_GLES2: -			return "GLES2"; -	} -	ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + "."); -}; - -OSIPhone *OSIPhone::get_singleton() { -	return (OSIPhone *)OS::get_singleton(); -}; - -extern int gl_view_base_fb; // from gl_view.mm - -void OSIPhone::set_data_dir(String p_dir) { -	DirAccess *da = DirAccess::open(p_dir); - -	data_dir = da->get_current_dir(); -	printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str()); -	memdelete(da); -}; - -void OSIPhone::set_unique_id(String p_id) { -	unique_id = p_id; -}; - -String OSIPhone::get_unique_id() const { -	return unique_id; -}; - -void OSIPhone::initialize_core() { -	OS_Unix::initialize_core(); - -	set_data_dir(data_dir); -}; - -int OSIPhone::get_current_video_driver() const { -	return video_driver_index; -} - -Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) { -	video_driver_index = p_video_driver; - -#if defined(OPENGL_ENABLED) -	bool gl_initialization_error = false; - -	// FIXME: Add Vulkan support via MoltenVK. Add fallback code back? - -	if (RasterizerGLES2::is_viable() == OK) { -		RasterizerGLES2::register_config(); -		RasterizerGLES2::make_current(); -	} else { -		gl_initialization_error = true; -	} - -	if (gl_initialization_error) { -		OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", -				"Unable to initialize video driver"); -		return ERR_UNAVAILABLE; -	} -#endif - -#if defined(VULKAN_ENABLED) -	RasterizerRD::make_current(); -#endif - -	rendering_server = memnew(RenderingServerRaster); -	// FIXME: Reimplement threaded rendering -	if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { -		rendering_server = memnew(RenderingServerWrapMT(rendering_server, false)); -	} -	rendering_server->init(); -	//rendering_server->cursor_set_visible(false, 0); - -#if defined(OPENGL_ENABLED) -	// reset this to what it should be, it will have been set to 0 after rendering_server->init() is called -	RasterizerStorageGLES2::system_fbo = gl_view_base_fb; -#endif - -	AudioDriverManager::initialize(p_audio_driver); - -	input = memnew(InputDefault); - -#ifdef GAME_CENTER_ENABLED -	game_center = memnew(GameCenter); -	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); -	game_center->connect(); -#endif - -#ifdef STOREKIT_ENABLED -	store_kit = memnew(InAppStore); -	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit)); -#endif - -#ifdef ICLOUD_ENABLED -	icloud = memnew(ICloud); -	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); -	//icloud->connect(); -#endif -	ios = memnew(iOS); -	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); - -	return OK; -}; - -MainLoop *OSIPhone::get_main_loop() const { -	return main_loop; -}; - -void OSIPhone::set_main_loop(MainLoop *p_main_loop) { -	main_loop = p_main_loop; - -	if (main_loop) { -		input->set_main_loop(p_main_loop); -		main_loop->init(); -	} -}; - -bool OSIPhone::iterate() { -	if (!main_loop) -		return true; - -	if (main_loop) { -		for (int i = 0; i < event_count; i++) { -			input->parse_input_event(event_queue[i]); -		}; -	}; -	event_count = 0; - -	return Main::iteration(); -}; - -void OSIPhone::key(uint32_t p_key, bool p_pressed) { -	Ref<InputEventKey> ev; -	ev.instance(); -	ev->set_echo(false); -	ev->set_pressed(p_pressed); -	ev->set_keycode(p_key); -	ev->set_physical_keycode(p_key); -	ev->set_unicode(p_key); -	queue_event(ev); -}; - -void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) { -	if (!GLOBAL_DEF("debug/disable_touch", false)) { -		Ref<InputEventScreenTouch> ev; -		ev.instance(); - -		ev->set_index(p_idx); -		ev->set_pressed(p_pressed); -		ev->set_position(Vector2(p_x, p_y)); -		queue_event(ev); -	}; - -	touch_list.pressed[p_idx] = p_pressed; -}; - -void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) { -	if (!GLOBAL_DEF("debug/disable_touch", false)) { -		Ref<InputEventScreenDrag> ev; -		ev.instance(); -		ev->set_index(p_idx); -		ev->set_position(Vector2(p_x, p_y)); -		ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y)); -		queue_event(ev); -	}; -}; - -void OSIPhone::queue_event(const Ref<InputEvent> &p_event) { -	ERR_FAIL_INDEX(event_count, MAX_EVENTS); - -	event_queue[event_count++] = p_event; -}; - -void OSIPhone::touches_cancelled() { -	for (int i = 0; i < MAX_MOUSE_COUNT; i++) { -		if (touch_list.pressed[i]) { -			// send a mouse_up outside the screen -			touch_press(i, -1, -1, false, false); -		}; -	}; -}; - -static const float ACCEL_RANGE = 1; - -void OSIPhone::update_gravity(float p_x, float p_y, float p_z) { -	input->set_gravity(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) { -	// Found out the Z should not be negated! Pass as is! -	input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE)); - -	/* -	if (p_x != last_accel.x) { -		//printf("updating accel x %f\n", p_x); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_0; -		ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE); -		last_accel.x = p_x; -		queue_event(ev); -	}; -	if (p_y != last_accel.y) { -		//printf("updating accel y %f\n", p_y); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_1; -		ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE); -		last_accel.y = p_y; -		queue_event(ev); -	}; -	if (p_z != last_accel.z) { -		//printf("updating accel z %f\n", p_z); -		InputEvent ev; -		ev.type = InputEvent::JOYPAD_MOTION; -		ev.device = 0; -		ev.joy_motion.axis = JOY_ANALOG_2; -		ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE); -		last_accel.z = p_z; -		queue_event(ev); -	}; -	*/ -}; - -void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) { -	input->set_magnetometer(Vector3(p_x, p_y, p_z)); -}; - -void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) { -	input->set_gyroscope(Vector3(p_x, p_y, p_z)); -}; - -int OSIPhone::get_unused_joy_id() { -	return input->get_unused_joy_id(); -}; - -void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) { -	input->joy_connection_changed(p_idx, p_connected, p_name); -}; - -void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) { -	input->joy_button(p_device, p_button, p_pressed); -}; - -void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) { -	input->joy_axis(p_device, p_axis, p_value); -}; - -void OSIPhone::delete_main_loop() { -	if (main_loop) { -		main_loop->finish(); -		memdelete(main_loop); -	}; - -	main_loop = nullptr; -}; - -void OSIPhone::finalize() { -	delete_main_loop(); - -	memdelete(input); -	memdelete(ios); - -#ifdef GAME_CENTER_ENABLED -	memdelete(game_center); -#endif - -#ifdef STOREKIT_ENABLED -	memdelete(store_kit); -#endif - -#ifdef ICLOUD_ENABLED -	memdelete(icloud); -#endif - -	rendering_server->finish(); -	memdelete(rendering_server); -	//	memdelete(rasterizer); - -	// Free unhandled events before close -	for (int i = 0; i < MAX_EVENTS; i++) { -		event_queue[i].unref(); -	}; -	event_count = 0; -}; - -void OSIPhone::set_mouse_show(bool p_show) {} -void OSIPhone::set_mouse_grab(bool p_grab) {} - -bool OSIPhone::is_mouse_grab_enabled() const { -	return true; -}; - -Point2 OSIPhone::get_mouse_position() const { -	return Point2(); -}; - -int OSIPhone::get_mouse_button_state() const { -	return 0; -}; - -void OSIPhone::set_window_title(const String &p_title) {} - -void OSIPhone::alert(const String &p_alert, const String &p_title) { -	const CharString utf8_alert = p_alert.utf8(); -	const CharString utf8_title = p_title.utf8(); -	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); -} - -Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { -	if (p_path.length() == 0) { -		p_library_handle = RTLD_SELF; -		return OK; -	} -	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); -} - -Error OSIPhone::close_dynamic_library(void *p_library_handle) { -	if (p_library_handle == RTLD_SELF) { -		return OK; -	} -	return OS_Unix::close_dynamic_library(p_library_handle); -} - -HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; -void register_dynamic_symbol(char *name, void *address) { -	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; -} - -Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { -	if (p_library_handle == RTLD_SELF) { -		void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); -		if (ptr) { -			p_symbol_handle = *ptr; -			return OK; -		} -	} -	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); -} - -void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) { -	video_mode = p_video_mode; -}; - -OS::VideoMode OSIPhone::get_video_mode(int p_screen) const { -	return video_mode; -}; - -void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const { -	p_list->push_back(video_mode); -}; - -bool OSIPhone::can_draw() const { -	if (native_video_is_playing()) -		return false; -	return true; -}; - -int OSIPhone::set_base_framebuffer(int p_fb) { -#if defined(OPENGL_ENABLED) -	// gl_view_base_fb has not been updated yet -	RasterizerStorageGLES2::system_fbo = p_fb; -#endif - -	return 0; -}; - -bool OSIPhone::has_virtual_keyboard() const { -	return true; -}; - -extern void _show_keyboard(String p_existing); -extern void _hide_keyboard(); -extern Error _shell_open(String p_uri); -extern void _set_keep_screen_on(bool p_enabled); -extern void _vibrate(); - -void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { -	_show_keyboard(p_existing_text); -}; - -void OSIPhone::hide_virtual_keyboard() { -	_hide_keyboard(); -}; - -void OSIPhone::set_virtual_keyboard_height(int p_height) { -	virtual_keyboard_height = p_height; -} - -int OSIPhone::get_virtual_keyboard_height() const { -	return virtual_keyboard_height; -} - -Error OSIPhone::shell_open(String p_uri) { -	return _shell_open(p_uri); -}; - -void OSIPhone::set_keep_screen_on(bool p_enabled) { -	OS::set_keep_screen_on(p_enabled); -	_set_keep_screen_on(p_enabled); -}; - -String OSIPhone::get_user_data_dir() const { -	return data_dir; -}; - -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); -} - -extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height); - -Rect2 OSIPhone::get_window_safe_area() const { -	return _get_ios_window_safe_area(video_mode.width, video_mode.height); -} - -bool OSIPhone::has_touchscreen_ui_hint() const { -	return true; -} - -void OSIPhone::set_locale(String p_locale) { -	locale_code = p_locale; -} - -String OSIPhone::get_locale() const { -	return locale_code; -} - -extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); -extern bool _is_video_playing(); -extern void _pause_video(); -extern void _unpause_video(); -extern void _stop_video(); -extern void _focus_out_video(); - -Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) { -	FileAccess *f = FileAccess::open(p_path, FileAccess::READ); -	bool exists = f && f->is_open(); - -	String tempFile = get_user_data_dir(); -	if (!exists) -		return FAILED; - -	if (p_path.begins_with("res://")) { -		if (PackedData::get_singleton()->has_path(p_path)) { -			print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str()); -			return ERR_INVALID_PARAMETER; -		} else { -			p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path()); -		} -	} else if (p_path.begins_with("user://")) -		p_path = p_path.replace("user:/", get_user_data_dir()); - -	memdelete(f); - -	print("Playing video: %S\n", p_path.c_str()); -	if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track)) -		return OK; -	return FAILED; -} - -bool OSIPhone::native_video_is_playing() const { -	return _is_video_playing(); -} - -void OSIPhone::native_video_pause() { -	if (native_video_is_playing()) -		_pause_video(); -} - -void OSIPhone::native_video_unpause() { -	_unpause_video(); -}; - -void OSIPhone::native_video_focus_out() { -	_focus_out_video(); -}; - -void OSIPhone::native_video_stop() { -	if (native_video_is_playing()) -		_stop_video(); -} - -void OSIPhone::vibrate_handheld(int p_duration_ms) { -	// iOS does not support duration for vibration -	_vibrate(); -} - -bool OSIPhone::_check_internal_feature_support(const String &p_feature) { -	return p_feature == "mobile"; -} - -// Initialization order between compilation units is not guaranteed, -// so we use this as a hack to ensure certain code is called before -// everything else, but after all units are initialized. -typedef void (*init_callback)(); -static init_callback *ios_init_callbacks = nullptr; -static int ios_init_callbacks_count = 0; -static int ios_init_callbacks_capacity = 0; - -void add_ios_init_callback(init_callback cb) { -	if (ios_init_callbacks_count == ios_init_callbacks_capacity) { -		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); -		if (new_ptr) { -			ios_init_callbacks = (init_callback *)(new_ptr); -			ios_init_callbacks_capacity += 32; -		} -	} -	if (ios_init_callbacks_capacity > ios_init_callbacks_count) { -		ios_init_callbacks[ios_init_callbacks_count] = cb; -		++ios_init_callbacks_count; -	} -} - -OSIPhone::OSIPhone(int width, int height, String p_data_dir) { -	for (int i = 0; i < ios_init_callbacks_count; ++i) { -		ios_init_callbacks[i](); -	} -	free(ios_init_callbacks); -	ios_init_callbacks = nullptr; -	ios_init_callbacks_count = 0; -	ios_init_callbacks_capacity = 0; - -	main_loop = nullptr; -	rendering_server = nullptr; - -	VideoMode vm; -	vm.fullscreen = true; -	vm.width = width; -	vm.height = height; -	vm.resizable = false; -	set_video_mode(vm); -	event_count = 0; -	virtual_keyboard_height = 0; - -	// can't call set_data_dir from here, since it requires DirAccess -	// which is initialized in initialize_core -	data_dir = p_data_dir; - -	Vector<Logger *> loggers; -	loggers.push_back(memnew(SyslogLogger)); -#ifdef DEBUG_ENABLED -	// it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode -	loggers.push_back(memnew(StdLogger)); -#endif -	_set_logger(memnew(CompositeLogger(loggers))); - -	AudioDriverManager::add_driver(&audio_driver); -}; - -OSIPhone::~OSIPhone() { -} - -#endif diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h index 955eb15d57..c6f95869ee 100644 --- a/platform/iphone/os_iphone.h +++ b/platform/iphone/os_iphone.h @@ -33,16 +33,15 @@  #ifndef OS_IPHONE_H  #define OS_IPHONE_H -#include "core/input/input.h"  #include "drivers/coreaudio/audio_driver_coreaudio.h"  #include "drivers/unix/os_unix.h"  #include "game_center.h"  #include "icloud.h"  #include "in_app_store.h"  #include "ios.h" +#include "joypad_iphone.h"  #include "servers/audio_server.h"  #include "servers/rendering/rasterizer.h" -#include "servers/rendering_server.h"  #if defined(VULKAN_ENABLED)  #include "drivers/vulkan/rendering_device_vulkan.h" @@ -51,16 +50,9 @@  class OSIPhone : public OS_Unix {  private: -	enum { -		MAX_MOUSE_COUNT = 8, -		MAX_EVENTS = 64, -	}; -  	static HashMap<String, void *> dynamic_symbol_lookup_table;  	friend void register_dynamic_symbol(char *name, void *address); -	RenderingServer *rendering_server; -  	AudioDriverCoreAudio audio_driver;  #ifdef GAME_CENTER_ENABLED @@ -74,139 +66,68 @@ private:  #endif  	iOS *ios; -	MainLoop *main_loop; - -#if defined(VULKAN_ENABLED) -	VulkanContextIPhone *context_vulkan; -	RenderingDeviceVulkan *rendering_device_vulkan; -#endif -	VideoMode video_mode; - -	virtual int get_video_driver_count() const; -	virtual const char *get_video_driver_name(int p_driver) const; +	JoypadIPhone *joypad_iphone; -	virtual int get_current_video_driver() const; - -	virtual void initialize_core(); -	virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); - -	virtual void set_main_loop(MainLoop *p_main_loop); -	virtual MainLoop *get_main_loop() const; - -	virtual void delete_main_loop(); - -	virtual void finalize(); +	MainLoop *main_loop; -	struct MouseList { -		bool pressed[MAX_MOUSE_COUNT]; -		MouseList() { -			for (int i = 0; i < MAX_MOUSE_COUNT; i++) -				pressed[i] = false; -		}; -	}; +	virtual void initialize_core() override; +	virtual void initialize() override; -	MouseList touch_list; +	virtual void initialize_joypads() override { +	} -	Vector3 last_accel; +	virtual void set_main_loop(MainLoop *p_main_loop) override; +	virtual MainLoop *get_main_loop() const override; -	Ref<InputEvent> event_queue[MAX_EVENTS]; -	int event_count; -	void queue_event(const Ref<InputEvent> &p_event); +	virtual void delete_main_loop() override; -	String data_dir; -	String unique_id; -	String locale_code; +	virtual void finalize() override; -	InputDefault *input; +	String user_data_dir; -	int virtual_keyboard_height; +	bool is_focused = false; -	int video_driver_index; +	void deinitialize_modules();  public: -	bool iterate(); - -	uint8_t get_orientations() const; - -	void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick); -	void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y); -	void touches_cancelled(); -	void key(uint32_t p_key, bool p_pressed); -	void set_virtual_keyboard_height(int p_height); - -	int set_base_framebuffer(int p_fb); - -	void update_gravity(float p_x, float p_y, float p_z); -	void update_accelerometer(float p_x, float p_y, float p_z); -	void update_magnetometer(float p_x, float p_y, float p_z); -	void update_gyroscope(float p_x, float p_y, float p_z); - -	int get_unused_joy_id(); -	void joy_connection_changed(int p_idx, bool p_connected, String p_name); -	void joy_button(int p_device, int p_button, bool p_pressed); -	void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value); -  	static OSIPhone *get_singleton(); -	virtual void set_mouse_show(bool p_show); -	virtual void set_mouse_grab(bool p_grab); -	virtual bool is_mouse_grab_enabled() const; -	virtual Point2 get_mouse_position() const; -	virtual int get_mouse_button_state() const; -	virtual void set_window_title(const String &p_title); - -	virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); - -	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); -	virtual Error close_dynamic_library(void *p_library_handle); -	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false); - -	virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0); -	virtual VideoMode get_video_mode(int p_screen = 0) const; -	virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const; +	OSIPhone(String p_data_dir); +	~OSIPhone(); -	virtual void set_keep_screen_on(bool p_enabled); +	void initialize_modules(); -	virtual bool can_draw() const; +	bool iterate(); -	virtual bool has_virtual_keyboard() const; -	virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); -	virtual void hide_virtual_keyboard(); -	virtual int get_virtual_keyboard_height() const; +	void start(); -	virtual Size2 get_window_size() const; -	virtual Rect2 get_window_safe_area() const; +	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override; +	virtual Error close_dynamic_library(void *p_library_handle) override; +	virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override; -	virtual bool has_touchscreen_ui_hint() const; +	virtual void alert(const String &p_alert, +			const String &p_title = "ALERT!") override; -	void set_data_dir(String p_dir); +	virtual String get_name() const override; +	virtual String get_model_name() const override; -	virtual String get_name() const; -	virtual String get_model_name() const; +	virtual Error shell_open(String p_uri) override; -	Error shell_open(String p_uri); +	void set_user_data_dir(String p_dir); +	virtual String get_user_data_dir() const override; -	String get_user_data_dir() const; +	virtual String get_locale() const override; -	void set_locale(String p_locale); -	String get_locale() const; +	virtual String get_unique_id() const override; -	void set_unique_id(String p_id); -	String get_unique_id() const; +	virtual void vibrate_handheld(int p_duration_ms = 500) override; -	virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track); -	virtual bool native_video_is_playing() const; -	virtual void native_video_pause(); -	virtual void native_video_unpause(); -	virtual void native_video_focus_out(); -	virtual void native_video_stop(); -	virtual void vibrate_handheld(int p_duration_ms = 500); +	virtual bool _check_internal_feature_support(const String &p_feature) override; -	virtual bool _check_internal_feature_support(const String &p_feature); -	OSIPhone(int width, int height, String p_data_dir); -	~OSIPhone(); +	void on_focus_out(); +	void on_focus_in();  };  #endif // OS_IPHONE_H -#endif +#endif // IPHONE_ENABLED diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm new file mode 100644 index 0000000000..5baa5b66fc --- /dev/null +++ b/platform/iphone/os_iphone.mm @@ -0,0 +1,364 @@ +/*************************************************************************/ +/*  os_iphone.mm                                                         */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 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.                */ +/*************************************************************************/ + +#ifdef IPHONE_ENABLED + +#include "os_iphone.h" +#import "app_delegate.h" +#include "core/io/file_access_pack.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "display_server_iphone.h" +#include "drivers/unix/syslog_logger.h" +#import "godot_view.h" +#include "main/main.h" +#import "view_controller.h" + +#import <AudioToolbox/AudioServices.h> +#import <UIKit/UIKit.h> +#import <dlfcn.h> + +#if defined(VULKAN_ENABLED) +#include "servers/rendering/rasterizer_rd/rasterizer_rd.h" +#import <QuartzCore/CAMetalLayer.h> +#include <vulkan/vulkan_metal.h> +#endif + +// Initialization order between compilation units is not guaranteed, +// so we use this as a hack to ensure certain code is called before +// everything else, but after all units are initialized. +typedef void (*init_callback)(); +static init_callback *ios_init_callbacks = nullptr; +static int ios_init_callbacks_count = 0; +static int ios_init_callbacks_capacity = 0; +HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table; + +void add_ios_init_callback(init_callback cb) { +	if (ios_init_callbacks_count == ios_init_callbacks_capacity) { +		void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32); +		if (new_ptr) { +			ios_init_callbacks = (init_callback *)(new_ptr); +			ios_init_callbacks_capacity += 32; +		} +	} +	if (ios_init_callbacks_capacity > ios_init_callbacks_count) { +		ios_init_callbacks[ios_init_callbacks_count] = cb; +		++ios_init_callbacks_count; +	} +} + +void register_dynamic_symbol(char *name, void *address) { +	OSIPhone::dynamic_symbol_lookup_table[String(name)] = address; +} + +OSIPhone *OSIPhone::get_singleton() { +	return (OSIPhone *)OS::get_singleton(); +} + +OSIPhone::OSIPhone(String p_data_dir) { +	for (int i = 0; i < ios_init_callbacks_count; ++i) { +		ios_init_callbacks[i](); +	} +	free(ios_init_callbacks); +	ios_init_callbacks = nullptr; +	ios_init_callbacks_count = 0; +	ios_init_callbacks_capacity = 0; + +	main_loop = nullptr; + +	// can't call set_data_dir from here, since it requires DirAccess +	// which is initialized in initialize_core +	user_data_dir = p_data_dir; + +	Vector<Logger *> loggers; +	loggers.push_back(memnew(SyslogLogger)); +#ifdef DEBUG_ENABLED +	// it seems iOS app's stdout/stderr is only obtainable if you launch it from +	// Xcode +	loggers.push_back(memnew(StdLogger)); +#endif +	_set_logger(memnew(CompositeLogger(loggers))); + +	AudioDriverManager::add_driver(&audio_driver); + +	DisplayServerIPhone::register_iphone_driver(); +} + +OSIPhone::~OSIPhone() {} + +void OSIPhone::initialize_core() { +	OS_Unix::initialize_core(); + +	set_user_data_dir(user_data_dir); +} + +void OSIPhone::initialize() { +	initialize_core(); +} + +void OSIPhone::initialize_modules() { +#ifdef GAME_CENTER_ENABLED +	game_center = memnew(GameCenter); +	Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center)); +#endif + +#ifdef STOREKIT_ENABLED +	store_kit = memnew(InAppStore); +	Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit)); +#endif + +#ifdef ICLOUD_ENABLED +	icloud = memnew(ICloud); +	Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud)); +#endif + +	ios = memnew(iOS); +	Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios)); + +	joypad_iphone = memnew(JoypadIPhone); +} + +void OSIPhone::deinitialize_modules() { +	if (joypad_iphone) { +		memdelete(joypad_iphone); +	} + +	if (ios) { +		memdelete(ios); +	} + +#ifdef GAME_CENTER_ENABLED +	if (game_center) { +		memdelete(game_center); +	} +#endif + +#ifdef STOREKIT_ENABLED +	if (store_kit) { +		memdelete(store_kit); +	} +#endif + +#ifdef ICLOUD_ENABLED +	if (icloud) { +		memdelete(icloud); +	} +#endif +} + +void OSIPhone::set_main_loop(MainLoop *p_main_loop) { +	main_loop = p_main_loop; + +	if (main_loop) { +		main_loop->init(); +	} +} + +MainLoop *OSIPhone::get_main_loop() const { +	return main_loop; +} + +void OSIPhone::delete_main_loop() { +	if (main_loop) { +		main_loop->finish(); +		memdelete(main_loop); +	}; + +	main_loop = nullptr; +} + +bool OSIPhone::iterate() { +	if (!main_loop) { +		return true; +	} + +	if (DisplayServer::get_singleton()) { +		DisplayServer::get_singleton()->process_events(); +	} + +	return Main::iteration(); +} + +void OSIPhone::start() { +	Main::start(); + +	if (joypad_iphone) { +		joypad_iphone->start_processing(); +	} +} + +void OSIPhone::finalize() { +	deinitialize_modules(); + +	// Already gets called +	//    delete_main_loop(); +} + +// MARK: Dynamic Libraries + +Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { +	if (p_path.length() == 0) { +		p_library_handle = RTLD_SELF; +		return OK; +	} +	return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path); +} + +Error OSIPhone::close_dynamic_library(void *p_library_handle) { +	if (p_library_handle == RTLD_SELF) { +		return OK; +	} +	return OS_Unix::close_dynamic_library(p_library_handle); +} + +Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) { +	if (p_library_handle == RTLD_SELF) { +		void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name); +		if (ptr) { +			p_symbol_handle = *ptr; +			return OK; +		} +	} +	return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional); +} + +void OSIPhone::alert(const String &p_alert, const String &p_title) { +	const CharString utf8_alert = p_alert.utf8(); +	const CharString utf8_title = p_title.utf8(); +	iOS::alert(utf8_alert.get_data(), utf8_title.get_data()); +} + +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(); +} + +Error OSIPhone::shell_open(String p_uri) { +	NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()]; +	NSURL *url = [NSURL URLWithString:urlPath]; +	[urlPath release]; + +	if (![[UIApplication sharedApplication] canOpenURL:url]) { +		return ERR_CANT_OPEN; +	} + +	printf("opening url %s\n", p_uri.utf8().get_data()); + +	//    if (@available(iOS 10, *)) { +	[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; +	//    } else { +	//        [[UIApplication sharedApplication] openURL:url]; +	//    } + +	return OK; +}; + +void OSIPhone::set_user_data_dir(String p_dir) { +	DirAccess *da = DirAccess::open(p_dir); + +	user_data_dir = da->get_current_dir(); +	printf("setting data dir to %s from %s\n", user_data_dir.utf8().get_data(), p_dir.utf8().get_data()); +	memdelete(da); +} + +String OSIPhone::get_user_data_dir() const { +	return user_data_dir; +} + +String OSIPhone::get_locale() const { +	NSString *preferedLanguage = [NSLocale preferredLanguages].firstObject; + +	if (preferedLanguage) { +		return String::utf8([preferedLanguage UTF8String]).replace("-", "_"); +	} + +	NSString *localeIdentifier = [[NSLocale currentLocale] localeIdentifier]; +	return String::utf8([localeIdentifier UTF8String]).replace("-", "_"); +} + +String OSIPhone::get_unique_id() const { +	NSString *uuid = [UIDevice currentDevice].identifierForVendor.UUIDString; +	return String::utf8([uuid UTF8String]); +} + +void OSIPhone::vibrate_handheld(int p_duration_ms) { +	// iOS does not support duration for vibration +	AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); +} + +bool OSIPhone::_check_internal_feature_support(const String &p_feature) { +	return p_feature == "mobile"; +} + +void OSIPhone::on_focus_out() { +	if (is_focused) { +		is_focused = false; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT); +		} + +		[AppDelegate.viewController.godotView stopRendering]; + +		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { +			DisplayServerIPhone::get_singleton()->native_video_pause(); +		} + +		audio_driver.stop(); +	} +} + +void OSIPhone::on_focus_in() { +	if (!is_focused) { +		is_focused = true; + +		if (DisplayServerIPhone::get_singleton()) { +			DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN); +		} + +		[AppDelegate.viewController.godotView startRendering]; + +		if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) { +			DisplayServerIPhone::get_singleton()->native_video_unpause(); +		} + +		audio_driver.start(); +	} +} + +#endif // IPHONE_ENABLED diff --git a/platform/iphone/platform_config.h b/platform/iphone/platform_config.h index bc190ba956..2bbbe47c0d 100644 --- a/platform/iphone/platform_config.h +++ b/platform/iphone/platform_config.h @@ -30,8 +30,6 @@  #include <alloca.h> -#define GLES2_INCLUDE_H <ES2/gl.h> -  #define PLATFORM_REFCOUNT  #define PTHREAD_RENAME_SELF diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h index f6bbe11d97..dffdc01d4a 100644 --- a/platform/iphone/view_controller.h +++ b/platform/iphone/view_controller.h @@ -31,20 +31,18 @@  #import <GameKit/GameKit.h>  #import <UIKit/UIKit.h> -@interface ViewController : UIViewController <GKGameCenterControllerDelegate> { -}; +@class GodotView; -- (BOOL)shouldAutorotateToInterfaceOrientation: -		(UIInterfaceOrientation)p_orientation; +@interface ViewController : UIViewController <GKGameCenterControllerDelegate> -- (void)didReceiveMemoryWarning; +- (GodotView *)godotView; -- (void)viewDidLoad; +// MARK: Native Video Player -- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures; - -- (BOOL)prefersStatusBarHidden; - -- (BOOL)prefersHomeIndicatorAutoHidden; +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack; +- (BOOL)isVideoPlaying; +- (void)pauseVideo; +- (void)unpauseVideo; +- (void)stopVideo;  @end diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm index 279bcc1226..31597f7856 100644 --- a/platform/iphone/view_controller.mm +++ b/platform/iphone/view_controller.mm @@ -29,96 +29,174 @@  /*************************************************************************/  #import "view_controller.h" - +#include "core/project_settings.h" +#include "display_server_iphone.h" +#import "godot_view.h" +#import "godot_view_renderer.h"  #include "os_iphone.h" -#include "core/project_settings.h" +#import <GameController/GameController.h> -extern "C" { +@interface ViewController () -int add_path(int, char **); -int add_cmdline(int, char **); +@property(strong, nonatomic) GodotViewRenderer *renderer; -int add_path(int p_argc, char **p_args) { -	NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"]; -	if (!str) -		return p_argc; +// TODO: separate view to handle video +// AVPlayer-related properties +@property(strong, nonatomic) AVAsset *avAsset; +@property(strong, nonatomic) AVPlayerItem *avPlayerItem; +@property(strong, nonatomic) AVPlayer *avPlayer; +@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer; +@property(assign, nonatomic) CMTime videoCurrentTime; +@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying; +@property(assign, nonatomic) BOOL videoHasFoundError; -	p_args[p_argc++] = "--path"; -	[str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo -	p_args[p_argc++] = (char *)[str cString]; -	p_args[p_argc] = NULL; +@end -	return p_argc; -}; +@implementation ViewController -int add_cmdline(int p_argc, char **p_args) { -	NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"]; -	if (!arr) -		return p_argc; +- (GodotView *)godotView { +	return (GodotView *)self.view; +} -	for (int i = 0; i < [arr count]; i++) { -		NSString *str = [arr objectAtIndex:i]; -		if (!str) -			continue; -		[str retain]; // @todo delete these at some point -		p_args[p_argc++] = (char *)[str cString]; -	}; +- (void)loadView { +	GodotView *view = [[GodotView alloc] init]; +	GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init]; -	p_args[p_argc] = NULL; +	self.renderer = renderer; +	self.view = view; -	return p_argc; -}; -}; // extern "C" +	view.renderer = self.renderer; -@interface ViewController () +	[renderer release]; +	[view release]; +} -@end +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { +	self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; -@implementation ViewController +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder { +	self = [super initWithCoder:coder]; + +	if (self) { +		[self godot_commonInit]; +	} + +	return self; +} + +- (void)godot_commonInit { +	self.isVideoCurrentlyPlaying = NO; +	self.videoCurrentTime = kCMTimeZero; +	self.videoHasFoundError = false; +}  - (void)didReceiveMemoryWarning { +	[super didReceiveMemoryWarning];  	printf("*********** did receive memory warning!\n"); -}; +}  - (void)viewDidLoad {  	[super viewDidLoad]; +	[self observeKeyboard]; +	[self observeAudio]; +  	if (@available(iOS 11.0, *)) {  		[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];  	}  } +- (void)observeKeyboard { +	printf("******** adding observer for keyboard show/hide\n"); +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(keyboardOnScreen:) +				   name:UIKeyboardDidShowNotification +				 object:nil]; +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(keyboardHidden:) +				   name:UIKeyboardDidHideNotification +				 object:nil]; +} + +- (void)observeAudio { +	printf("******** adding observer for sound routing changes\n"); +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(audioRouteChangeListenerCallback:) +				   name:AVAudioSessionRouteChangeNotification +				 object:nil]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { +	if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) { +		[self handleVideoOrPlayerStatus]; +	} + +	if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) { +		[self handleVideoPlayRate]; +	} +} + +- (void)dealloc { +	[self stopVideo]; + +	self.renderer = nil; + +	[[NSNotificationCenter defaultCenter] removeObserver:self]; + +	[super dealloc]; +} + +// MARK: Orientation +  - (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {  	return UIRectEdgeAll;  }  - (BOOL)shouldAutorotate { -	switch (OS::get_singleton()->get_screen_orientation()) { -		case OS::SCREEN_SENSOR: -		case OS::SCREEN_SENSOR_LANDSCAPE: -		case OS::SCREEN_SENSOR_PORTRAIT: +	if (!DisplayServerIPhone::get_singleton()) { +		return NO; +	} + +	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { +		case DisplayServer::SCREEN_SENSOR: +		case DisplayServer::SCREEN_SENSOR_LANDSCAPE: +		case DisplayServer::SCREEN_SENSOR_PORTRAIT:  			return YES;  		default:  			return NO;  	} -}; +}  - (UIInterfaceOrientationMask)supportedInterfaceOrientations { -	switch (OS::get_singleton()->get_screen_orientation()) { -		case OS::SCREEN_PORTRAIT: +	if (!DisplayServerIPhone::get_singleton()) { +		return UIInterfaceOrientationMaskAll; +	} + +	switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) { +		case DisplayServer::SCREEN_PORTRAIT:  			return UIInterfaceOrientationMaskPortrait; -		case OS::SCREEN_REVERSE_LANDSCAPE: +		case DisplayServer::SCREEN_REVERSE_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscapeRight; -		case OS::SCREEN_REVERSE_PORTRAIT: +		case DisplayServer::SCREEN_REVERSE_PORTRAIT:  			return UIInterfaceOrientationMaskPortraitUpsideDown; -		case OS::SCREEN_SENSOR_LANDSCAPE: +		case DisplayServer::SCREEN_SENSOR_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscape; -		case OS::SCREEN_SENSOR_PORTRAIT: +		case DisplayServer::SCREEN_SENSOR_PORTRAIT:  			return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown; -		case OS::SCREEN_SENSOR: +		case DisplayServer::SCREEN_SENSOR:  			return UIInterfaceOrientationMaskAll; -		case OS::SCREEN_LANDSCAPE: +		case DisplayServer::SCREEN_LANDSCAPE:  			return UIInterfaceOrientationMaskLandscapeLeft;  	}  }; @@ -135,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {  	}  } +// MARK: Keyboard + +- (void)keyboardOnScreen:(NSNotification *)notification { +	NSDictionary *info = notification.userInfo; +	NSValue *value = info[UIKeyboardFrameEndUserInfoKey]; + +	CGRect rawFrame = [value CGRectValue]; +	CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil]; + +	if (DisplayServerIPhone::get_singleton()) { +		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height); +	} +} + +- (void)keyboardHidden:(NSNotification *)notification { +	if (DisplayServerIPhone::get_singleton()) { +		DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0); +	} +} + +// MARK: Audio + +- (void)audioRouteChangeListenerCallback:(NSNotification *)notification { +	printf("*********** route changed!\n"); +	NSDictionary *interuptionDict = notification.userInfo; + +	NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue]; + +	switch (routeChangeReason) { +		case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { +			NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable"); +			NSLog(@"Headphone/Line plugged in"); +		} break; +		case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: { +			NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable"); +			NSLog(@"Headphone/Line was pulled. Resuming video play...."); +			if ([self isVideoPlaying]) { +				dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +					[self.avPlayer play]; // NOTE: change this line according your current player implementation +					NSLog(@"resumed play"); +				}); +			} +		} break; +		case AVAudioSessionRouteChangeReasonCategoryChange: { +			// called at start - also when other audio wants to play +			NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange"); +		} break; +	} +} + +// MARK: Native Video Player + +- (void)handleVideoOrPlayerStatus { +	if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) { +		[self stopVideo]; +		self.videoHasFoundError = true; +	} + +	if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) { +		//        NSLog(@"time: %@", self.video_current_time); +		[self.avPlayer seekToTime:self.videoCurrentTime]; +		self.videoCurrentTime = kCMTimeZero; +	} +} + +- (void)handleVideoPlayRate { +	NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate); +	if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) { +		dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ +			[self.avPlayer play]; // NOTE: change this line according your current player implementation +			NSLog(@"resumed play"); +		}); + +		NSLog(@" . . . PAUSED (or just started)"); +	} +} + +- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack { +	self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]]; + +	self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset]; +	[self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil]; + +	self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem]; +	self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer]; + +	[self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil]; +	[[NSNotificationCenter defaultCenter] +			addObserver:self +			   selector:@selector(playerItemDidReachEnd:) +				   name:AVPlayerItemDidPlayToEndTimeNotification +				 object:[self.avPlayer currentItem]]; + +	[self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0]; + +	[self.avPlayerLayer setFrame:self.view.bounds]; +	[self.view.layer addSublayer:self.avPlayerLayer]; +	[self.avPlayer play]; + +	AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible]; + +	NSMutableArray *allAudioParams = [NSMutableArray array]; +	for (id track in audioGroup.options) { +		NSString *language = [[track locale] localeIdentifier]; +		NSLog(@"subtitle lang: %@", language); + +		if ([language isEqualToString:audioTrack]) { +			AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters]; +			[audioInputParams setVolume:videoVolume atTime:kCMTimeZero]; +			[audioInputParams setTrackID:[track trackID]]; +			[allAudioParams addObject:audioInputParams]; + +			AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix]; +			[audioMix setInputParameters:allAudioParams]; + +			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup]; +			[self.avPlayer.currentItem setAudioMix:audioMix]; + +			break; +		} +	} + +	AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible]; +	NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]]; + +	for (id track in useableTracks) { +		NSString *language = [[track locale] localeIdentifier]; +		NSLog(@"subtitle lang: %@", language); + +		if ([language isEqualToString:subtitleTrack]) { +			[self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup]; +			break; +		} +	} + +	self.isVideoCurrentlyPlaying = YES; + +	return true; +} + +- (BOOL)isVideoPlaying { +	if (self.avPlayer.error) { +		printf("Error during playback\n"); +	} +	return (self.avPlayer.rate > 0 && !self.avPlayer.error); +} + +- (void)pauseVideo { +	self.videoCurrentTime = self.avPlayer.currentTime; +	[self.avPlayer pause]; +	self.isVideoCurrentlyPlaying = NO; +} + +- (void)unpauseVideo { +	[self.avPlayer play]; +	self.isVideoCurrentlyPlaying = YES; +} + +- (void)playerItemDidReachEnd:(NSNotification *)notification { +	[self stopVideo]; +} + +- (void)stopVideo { +	[self.avPlayer pause]; +	[self.avPlayerLayer removeFromSuperlayer]; +	self.avPlayerLayer = nil; + +	if (self.avPlayerItem) { +		[self.avPlayerItem removeObserver:self forKeyPath:@"status"]; +		self.avPlayerItem = nil; +	} + +	if (self.avPlayer) { +		[self.avPlayer removeObserver:self forKeyPath:@"status"]; +		self.avPlayer = nil; +	} + +	self.avAsset = nil; + +	self.isVideoCurrentlyPlaying = NO; +} + +// MARK: Delegates +  #ifdef GAME_CENTER_ENABLED  - (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {  	//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h index cadd701636..5c3d5fe33e 100644 --- a/platform/iphone/vulkan_context_iphone.h +++ b/platform/iphone/vulkan_context_iphone.h @@ -32,13 +32,14 @@  #define VULKAN_CONTEXT_IPHONE_H  #include "drivers/vulkan/vulkan_context.h" -// #import <UIKit/UIKit.h> + +#import <UIKit/UIKit.h>  class VulkanContextIPhone : public VulkanContext {  	virtual const char *_get_platform_surface_extension() const;  public: -	int window_create(void *p_window, int p_width, int p_height); +	Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);  	VulkanContextIPhone();  	~VulkanContextIPhone(); diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm index 44c940dc3a..cb4dbe7f85 100644 --- a/platform/iphone/vulkan_context_iphone.mm +++ b/platform/iphone/vulkan_context_iphone.mm @@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {  	return VK_MVK_IOS_SURFACE_EXTENSION_NAME;  } -int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) { +Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id, +		CALayer *p_metal_layer, int p_width, +		int p_height) {  	VkIOSSurfaceCreateInfoMVK createInfo; -	createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; +	createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;  	createInfo.pNext = NULL;  	createInfo.flags = 0; -	createInfo.pView = p_window; +	createInfo.pView = p_metal_layer;  	VkSurfaceKHR surface; -	VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); -	ERR_FAIL_COND_V(err, -1); -	return _window_create(surface, p_width, p_height); -} +	VkResult err = +			vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface); +	ERR_FAIL_COND_V(err, ERR_CANT_CREATE); -VulkanContextIPhone::VulkanContextIPhone() { +	return _window_create(p_window_id, surface, p_width, p_height);  } -VulkanContextIPhone::~VulkanContextIPhone() { -} +VulkanContextIPhone::VulkanContextIPhone() {} + +VulkanContextIPhone::~VulkanContextIPhone() {} diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index dcf9a46bf9..21456efde5 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -66,5 +66,5 @@ env.Zip(      zip_files,      ZIPROOT=zip_dir,      ZIPSUFFIX="${PROGSUFFIX}${ZIPSUFFIX}", -    ZIPCOMSTR="Archving $SOURCES as $TARGET", +    ZIPCOMSTR="Archiving $SOURCES as $TARGET",  ) diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index f029a91db0..c1607301d7 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -46,21 +46,21 @@ public:  	static AudioDriverJavaScript *singleton; -	virtual const char *get_name() const; +	const char *get_name() const override; -	virtual Error init(); -	virtual void start(); +	Error init() override; +	void start() override;  	void resume(); -	virtual float get_latency(); -	virtual int get_mix_rate() const; -	virtual SpeakerMode get_speaker_mode() const; -	virtual void lock(); -	virtual void unlock(); -	virtual void finish(); +	float get_latency() override; +	int get_mix_rate() const override; +	SpeakerMode get_speaker_mode() const override; +	void lock() override; +	void unlock() override; +	void finish() override;  	void finish_async(); -	virtual Error capture_start(); -	virtual Error capture_stop(); +	Error capture_start() override; +	Error capture_stop() override;  	AudioDriverJavaScript();  }; diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 2f0a2faa83..8dc33bdf64 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -75,7 +75,7 @@ bool DisplayServerJavaScript::check_size_force_redraw() {  	if (last_width != canvas_width || last_height != canvas_height) {  		last_width = canvas_width;  		last_height = canvas_height; -		// Update the framebuffer size and for redraw. +		// Update the framebuffer size for redraw.  		emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height);  		return true;  	} @@ -829,6 +829,19 @@ DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_dr  }  DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { +	r_error = OK; // Always succeeds for now. + +	/* clang-format off */ +	swap_cancel_ok = EM_ASM_INT({ +		const win = (['Windows', 'Win64', 'Win32', 'WinCE']); +		const plat = navigator.platform || ""; +		if (win.indexOf(plat) !== -1) { +			return 1; +		} +		return 0; +	}) == 1; +	/* clang-format on */ +  	RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu?  #if 0  	EmscriptenWebGLContextAttributes attributes; @@ -879,15 +892,18 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive  #define SET_EM_CALLBACK(target, ev, cb)                                  \  	result = emscripten_set_##ev##_callback(target, nullptr, true, &cb); \  	EM_CHECK(ev) +#define SET_EM_WINDOW_CALLBACK(ev, cb)                                                         \ +	result = emscripten_set_##ev##_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, false, &cb); \ +	EM_CHECK(ev)  #define SET_EM_CALLBACK_NOTARGET(ev, cb)                         \  	result = emscripten_set_##ev##_callback(nullptr, true, &cb); \  	EM_CHECK(ev)  	// These callbacks from Emscripten's html5.h suffice to access most  	// JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM  	// is used below. -	SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback)  	SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) -	SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback) +	SET_EM_WINDOW_CALLBACK(mousemove, mousemove_callback) +	SET_EM_WINDOW_CALLBACK(mouseup, mouse_button_callback)  	SET_EM_CALLBACK(canvas_id, wheel, wheel_callback)  	SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback)  	SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) @@ -905,27 +921,25 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive  	/* clang-format off */  	EM_ASM_ARGS({ -		Module.listeners = {}; +		// Bind native event listeners. +		// Module.listeners, and Module.drop_handler are defined in native/utils.js  		const canvas = Module['canvas'];  		const send_window_event = cwrap('send_window_event', null, ['number']);  		const notifications = arguments;  		(['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) { -			Module.listeners[event] = send_window_event.bind(null, notifications[index]); -			canvas.addEventListener(event, Module.listeners[event]); +			Module.listeners.add(canvas, event, send_window_event.bind(null, notifications[index]), true);  		});  		// Clipboard  		const update_clipboard = cwrap('update_clipboard', null, ['string']); -		Module.listeners['paste'] = function(evt) { +		Module.listeners.add(window, 'paste', function(evt) {  			update_clipboard(evt.clipboardData.getData('text')); -		}; -		window.addEventListener('paste', Module.listeners['paste'], false); -		Module.listeners['dragover'] = function(ev) { +		}, false); +		// Drag an drop +		Module.listeners.add(canvas, 'dragover', function(ev) {  			// Prevent default behavior (which would try to open the file(s))  			ev.preventDefault(); -		}; -		Module.listeners['drop'] = Module.drop_handler; // Defined in native/utils.js -		canvas.addEventListener('dragover', Module.listeners['dragover'], false); -		canvas.addEventListener('drop', Module.listeners['drop'], false); +		}, false); +		Module.listeners.add(canvas, 'drop', Module.drop_handler, false);  	},  		WINDOW_EVENT_MOUSE_ENTER,  		WINDOW_EVENT_MOUSE_EXIT, @@ -939,14 +953,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive  DisplayServerJavaScript::~DisplayServerJavaScript() {  	EM_ASM({ -		Object.entries(Module.listeners).forEach(function(kv) { -			if (kv[0] == 'paste') { -				window.removeEventListener(kv[0], kv[1], true); -			} else { -				Module['canvas'].removeEventListener(kv[0], kv[1]); -			} -		}); -		Module.listeners = {}; +		Module.listeners.clear();  	});  	//emscripten_webgl_commit_frame();  	//emscripten_webgl_destroy_context(webgl_ctx); @@ -1096,7 +1103,11 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const {  void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) {  	last_width = p_size.x;  	last_height = p_size.y; -	emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); +	double scale = EM_ASM_DOUBLE({ +		return window.devicePixelRatio || 1; +	}); +	emscripten_set_canvas_element_size(canvas_id, p_size.x * scale, p_size.y * scale); +	emscripten_set_element_css_size(canvas_id, p_size.x, p_size.y);  }  Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { @@ -1181,6 +1192,10 @@ int DisplayServerJavaScript::get_current_video_driver() const {  	return 1;  } +bool DisplayServerJavaScript::get_swap_cancel_ok() { +	return swap_cancel_ok; +} +  void DisplayServerJavaScript::swap_buffers() {  	//emscripten_webgl_commit_frame();  } diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index b149665d67..d7116be36f 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -56,6 +56,8 @@ class DisplayServerJavaScript : public DisplayServer {  	int last_width = 0;  	int last_height = 0; +	bool swap_cancel_ok = false; +  	// utilities  	static Point2 compute_position_in_canvas(int p_x, int p_y);  	static void focus_canvas(); @@ -91,7 +93,7 @@ class DisplayServerJavaScript : public DisplayServer {  	static void _dispatch_input_event(const Ref<InputEvent> &p_event);  protected: -	virtual int get_current_video_driver() const; +	int get_current_video_driver() const;  public:  	// Override return type to make writing static callbacks less tedious. @@ -111,91 +113,92 @@ public:  	bool check_size_force_redraw();  	// from DisplayServer -	virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); -	virtual bool has_feature(Feature p_feature) const; -	virtual String get_name() const; +	void alert(const String &p_alert, const String &p_title = "ALERT!") override; +	bool has_feature(Feature p_feature) const override; +	String get_name() const override;  	// cursor -	virtual void cursor_set_shape(CursorShape p_shape); -	virtual CursorShape cursor_get_shape() const; -	virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()); +	void cursor_set_shape(CursorShape p_shape) override; +	CursorShape cursor_get_shape() const override; +	void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override;  	// mouse -	virtual void mouse_set_mode(MouseMode p_mode); -	virtual MouseMode mouse_get_mode() const; +	void mouse_set_mode(MouseMode p_mode) override; +	MouseMode mouse_get_mode() const override;  	// touch -	virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; +	bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;  	// clipboard -	virtual void clipboard_set(const String &p_text); -	virtual String clipboard_get() const; +	void clipboard_set(const String &p_text) override; +	String clipboard_get() const override;  	// screen -	virtual int get_screen_count() const; -	virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const; -	virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const; -	virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; -	virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; +	int get_screen_count() const override; +	Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; +	int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;  	// windows -	virtual Vector<DisplayServer::WindowID> get_window_list() const; -	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; +	Vector<DisplayServer::WindowID> get_window_list() const override; +	WindowID get_window_at_screen_position(const Point2i &p_position) const override; -	virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID); -	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const; +	void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; +	ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); +	void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); -	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); -	virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); +	void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; +	void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); +	void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); +	void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const; -	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID); +	int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override; +	void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const; -	virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID); +	Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; +	void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; -	virtual void window_set_transient(WindowID p_window, WindowID p_parent); +	void window_set_transient(WindowID p_window, WindowID p_parent) override; -	virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); -	virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const; +	void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); -	virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const; +	void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID); -	virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const; -	virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const; // FIXME: Find clearer name for this. +	void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; +	Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; +	Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID); -	virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const; +	void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; +	WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const; +	bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID); -	virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const; +	void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override; +	bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID); -	virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID); +	void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override; +	void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override; -	virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const; +	bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override; -	virtual bool can_any_window_draw() const; +	bool can_any_window_draw() const override;  	// events -	virtual void process_events(); +	void process_events() override;  	// icon -	virtual void set_icon(const Ref<Image> &p_icon); +	void set_icon(const Ref<Image> &p_icon) override;  	// others -	virtual void swap_buffers(); +	bool get_swap_cancel_ok() override; +	void swap_buffers() override;  	static void register_javascript_driver();  	DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error); diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js index d709422abb..adcd919a6b 100644 --- a/platform/javascript/engine/engine.js +++ b/platform/javascript/engine/engine.js @@ -33,6 +33,7 @@ Function('return this')()['Engine'] = (function() {  		this.resizeCanvasOnStart = false;  		this.onExecute = null;  		this.onExit = null; +		this.persistentPaths = [];  	};  	Engine.prototype.init = /** @param {string=} basePath */ function(basePath) { @@ -56,12 +57,14 @@ Function('return this')()['Engine'] = (function() {  			config['locateFile'] = Utils.createLocateRewrite(loadPath);  			config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);  			Godot(config).then(function(module) { -				me.rtenv = module; -				if (unloadAfterInit) { -					unload(); -				} -				resolve(); -				config = null; +				module['initFS'](me.persistentPaths).then(function(fs_err) { +					me.rtenv = module; +					if (unloadAfterInit) { +						unload(); +					} +					resolve(); +					config = null; +				});  			});  		});  		return initPromise; @@ -121,7 +124,7 @@ Function('return this')()['Engine'] = (function() {  				if (me.onExit)  					me.onExit(code);  				me.rtenv = null; -			} +			};  			return new Promise(function(resolve, reject) {  				preloader.preloadedFiles.forEach(function(file) {  					me.rtenv['copyToFS'](file.path, file.buffer); @@ -207,18 +210,22 @@ Function('return this')()['Engine'] = (function() {  		if (this.rtenv)  			this.rtenv.onExecute = onExecute;  		this.onExecute = onExecute; -	} +	};  	Engine.prototype.setOnExit = function(onExit) {  		this.onExit = onExit; -	} +	};  	Engine.prototype.copyToFS = function(path, buffer) {  		if (this.rtenv == null) {  			throw new Error("Engine must be inited before copying files");  		}  		this.rtenv['copyToFS'](path, buffer); -	} +	}; + +	Engine.prototype.setPersistentPaths = function(persistentPaths) { +		this.persistentPaths = persistentPaths; +	};  	// Closure compiler exported engine methods.  	/** @export */ @@ -241,5 +248,6 @@ Function('return this')()['Engine'] = (function() {  	Engine.prototype['setOnExecute'] = Engine.prototype.setOnExecute;  	Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;  	Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; +	Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths;  	return Engine;  })(); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 6a3a977cfb..230575abce 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -258,6 +258,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re  		current_line = current_line.replace("$GODOT_BASENAME", p_name);  		current_line = current_line.replace("$GODOT_PROJECT_NAME", ProjectSettings::get_singleton()->get_setting("application/config/name"));  		current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); +		current_line = current_line.replace("$GODOT_FULL_WINDOW", p_preset->get("html/full_window_size") ? "true" : "false");  		current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");  		current_line = current_line.replace("$GODOT_ARGS", flags_json);  		str_export += current_line + "\n"; @@ -291,6 +292,7 @@ void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_op  	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); +	r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/full_window_size"), true));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));  	r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));  } diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 99672745e7..01722c4bc8 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -34,10 +34,22 @@  #include "platform/javascript/os_javascript.h"  #include <emscripten/emscripten.h> +#include <stdlib.h>  static OS_JavaScript *os = nullptr;  static uint64_t target_ticks = 0; +extern "C" EMSCRIPTEN_KEEPALIVE void _request_quit_callback(char *p_filev[], int p_filec) { +	DisplayServerJavaScript *ds = DisplayServerJavaScript::get_singleton(); +	if (ds) { +		Variant event = int(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST); +		Variant *eventp = &event; +		Variant ret; +		Callable::CallError ce; +		ds->window_event_callback.call((const Variant **)&eventp, 1, ret, ce); +	} +} +  void exit_callback() {  	emscripten_cancel_main_loop(); // After this, we can exit!  	Main::cleanup(); @@ -76,12 +88,27 @@ void main_loop_callback() {  		/* clang-format on */  		os->get_main_loop()->finish();  		os->finalize_async(); // Will add all the async finish functions. +		/* clang-format off */  		EM_ASM({  			Promise.all(Module.async_finish).then(function() {  				Module.async_finish = []; +				return new Promise(function(accept, reject) { +					if (!Module.idbfs) { +						accept(); +						return; +					} +					FS.syncfs(function(error) { +						if (error) { +							err('Failed to save IDB file system: ' + error.message); +						} +						accept(); +					}); +				}); +			}).then(function() {  				ccall("cleanup_after_sync", null, []);  			});  		}); +		/* clang-format on */  	}  } @@ -89,31 +116,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE void cleanup_after_sync() {  	emscripten_set_main_loop(exit_callback, -1, false);  } -extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { -	String idbfs_err = String::utf8(p_idbfs_err); -	if (!idbfs_err.empty()) { -		print_line("IndexedDB not available: " + idbfs_err); -	} -	os->set_idb_available(idbfs_err.empty()); -	// TODO: Check error return value. -	Main::setup2(); // Manual second phase. -	// Ease up compatibility. -	ResourceLoader::set_abort_on_missing_resources(false); -	Main::start(); -	os->get_main_loop()->init(); -	// Immediately run the first iteration. -	// We are inside an animation frame, we want to immediately draw on the newly setup canvas. -	main_loop_callback(); -	emscripten_resume_main_loop(); -} - +/// When calling main, it is assumed FS is setup and synced.  int main(int argc, char *argv[]) { -	// Create and mount userfs immediately. -	EM_ASM({ -		FS.mkdir('/userfs'); -		FS.mount(IDBFS, {}, '/userfs'); -	}); -  	// Configure locale.  	char locale_ptr[16];  	/* clang-format off */ @@ -131,22 +135,30 @@ int main(int argc, char *argv[]) {  	/* clang-format on */  	os = new OS_JavaScript(); -	Main::setup(argv[0], argc - 1, &argv[1], false); -	emscripten_set_main_loop(main_loop_callback, -1, false); -	emscripten_pause_main_loop(); // Will need to wait for FS sync. +	os->set_idb_available((bool)EM_ASM_INT({ return Module.idbfs })); + +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE + +	Main::setup(argv[0], argc - 1, &argv[1]); + +	// Ease up compatibility. +	ResourceLoader::set_abort_on_missing_resources(false); -	// Sync from persistent state into memory and then -	// run the 'main_after_fs_sync' function. +	Main::start(); +	os->get_main_loop()->init(); +	// Expose method for requesting quit.  	/* clang-format off */  	EM_ASM({ -		FS.syncfs(true, function(err) { -			requestAnimationFrame(function() { -				ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]); -			}); -		}); +		Module['request_quit'] = function() { +			ccall("_request_quit_callback", null, []); +		};  	});  	/* clang-format on */ +	emscripten_set_main_loop(main_loop_callback, -1, false); +	// Immediately run the first iteration. +	// We are inside an animation frame, we want to immediately draw on the newly setup canvas. +	main_loop_callback();  	return 0; -	// Continued async in main_after_fs_sync() from the syncfs() callback.  } diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js index 95585d26ae..0b3698fd86 100644 --- a/platform/javascript/native/utils.js +++ b/platform/javascript/native/utils.js @@ -28,6 +28,38 @@  /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */  /*************************************************************************/ +Module['initFS'] = function(persistentPaths) { +	FS.mkdir('/userfs'); +	FS.mount(IDBFS, {}, '/userfs'); + +	function createRecursive(dir) { +		try { +			FS.stat(dir); +		} catch (e) { +			if (e.errno !== ERRNO_CODES.ENOENT) { +				throw e; +			} +			FS.mkdirTree(dir); +		} +	} + +	persistentPaths.forEach(function(path) { +		createRecursive(path); +		FS.mount(IDBFS, {}, path); +	}); +	return new Promise(function(resolve, reject) { +		FS.syncfs(true, function(err) { +			if (err) { +				Module.idbfs = false; +				console.log("IndexedDB not available: " + err.message); +			} else { +				Module.idbfs = true; +			} +			resolve(err); +		}); +	}); +}; +  Module['copyToFS'] = function(path, buffer) {  	var p = path.lastIndexOf("/");  	var dir = "/"; @@ -37,7 +69,7 @@ Module['copyToFS'] = function(path, buffer) {  	try {  		FS.stat(dir);  	} catch (e) { -		if (e.errno !== ERRNO_CODES.ENOENT) { // 'ENOENT', see https://github.com/emscripten-core/emscripten/blob/master/system/lib/libc/musl/arch/emscripten/bits/errno.h +		if (e.errno !== ERRNO_CODES.ENOENT) {  			throw e;  		}  		FS.mkdirTree(dir); @@ -202,3 +234,44 @@ Module.drop_handler = (function() {  		});  	}  })(); + +function EventHandlers() { +	function Handler(target, event, method, capture) { +		this.target = target; +		this.event = event; +		this.method = method; +		this.capture = capture; +	} + +	var listeners = []; + +	function has(target, event, method, capture) { +		return listeners.findIndex(function(e) { +			return e.target === target && e.event === event && e.method === method && e.capture == capture; +		}) !== -1; +	} + +	this.add = function(target, event, method, capture) { +		if (has(target, event, method, capture)) { +			return; +		} +		listeners.push(new Handler(target, event, method, capture)); +		target.addEventListener(event, method, capture); +	}; + +	this.remove = function(target, event, method, capture) { +		if (!has(target, event, method, capture)) { +			return; +		} +		target.removeEventListener(event, method, capture); +	}; + +	this.clear = function() { +		listeners.forEach(function(h) { +			h.target.removeEventListener(h.event, h.method, h.capture); +		}); +		listeners.length = 0; +	}; +} + +Module.listeners = new EventHandlers(); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 1ff4304bcf..cf5751f384 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -46,24 +46,6 @@  #include <emscripten.h>  #include <stdlib.h> -bool OS_JavaScript::has_touchscreen_ui_hint() const { -	/* clang-format off */ -	return EM_ASM_INT_V( -		return 'ontouchstart' in window; -	); -	/* clang-format on */ -} - -// Audio - -int OS_JavaScript::get_audio_driver_count() const { -	return 1; -} - -const char *OS_JavaScript::get_audio_driver_name(int p_driver) const { -	return "JavaScript"; -} -  // Lifecycle  void OS_JavaScript::initialize() {  	OS_Unix::initialize_core(); @@ -90,27 +72,24 @@ MainLoop *OS_JavaScript::get_main_loop() const {  	return main_loop;  } -void OS_JavaScript::main_loop_callback() { -	get_singleton()->main_loop_iterate(); +extern "C" EMSCRIPTEN_KEEPALIVE void _idb_synced() { +	OS_JavaScript::get_singleton()->idb_is_syncing = false;  }  bool OS_JavaScript::main_loop_iterate() { -	if (is_userfs_persistent() && sync_wait_time >= 0) { -		int64_t current_time = get_ticks_msec(); -		int64_t elapsed_time = current_time - last_sync_check_time; -		last_sync_check_time = current_time; - -		sync_wait_time -= elapsed_time; - -		if (sync_wait_time < 0) { -			/* clang-format off */ -			EM_ASM( -				FS.syncfs(function(error) { -					if (error) { err('Failed to save IDB file system: ' + error.message); } -				}); -			); -			/* clang-format on */ -		} +	if (is_userfs_persistent() && idb_needs_sync && !idb_is_syncing) { +		idb_is_syncing = true; +		idb_needs_sync = false; +		/* clang-format off */ +		EM_ASM({ +			FS.syncfs(function(error) { +				if (error) { +					err('Failed to save IDB file system: ' + error.message); +				} +				ccall("_idb_synced", 'void', [], []); +			}); +		}); +		/* clang-format on */  	}  	DisplayServer::get_singleton()->process_events(); @@ -201,10 +180,6 @@ String OS_JavaScript::get_name() const {  	return "HTML5";  } -bool OS_JavaScript::can_draw() const { -	return true; // Always? -} -  String OS_JavaScript::get_user_data_dir() const {  	return "/userfs";  }; @@ -222,11 +197,17 @@ String OS_JavaScript::get_data_path() const {  }  void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags) { -	OS_JavaScript *os = get_singleton(); -	if (os->is_userfs_persistent() && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) { -		os->last_sync_check_time = OS::get_singleton()->get_ticks_msec(); -		// Wait five seconds in case more files are about to be closed. -		os->sync_wait_time = 5000; +	OS_JavaScript *os = OS_JavaScript::get_singleton(); +	if (!(os->is_userfs_persistent() && (p_flags & FileAccess::WRITE))) { +		return; // FS persistence is not working or we are not writing. +	} +	bool is_file_persistent = p_file.begins_with("/userfs"); +#ifdef TOOLS_ENABLED +	// Hack for editor persistence (can we track). +	is_file_persistent = is_file_persistent || p_file.begins_with("/home/web_user/"); +#endif +	if (is_file_persistent) { +		os->idb_needs_sync = true;  	}  } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 22234f9355..85551d708b 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -44,57 +44,52 @@ class OS_JavaScript : public OS_Unix {  	bool finalizing = false;  	bool idb_available = false; -	int64_t sync_wait_time = -1; -	int64_t last_sync_check_time = -1; +	bool idb_needs_sync = false;  	static void main_loop_callback();  	static void file_access_close_callback(const String &p_file, int p_flags);  protected: -	virtual void initialize(); +	void initialize() override; -	virtual void set_main_loop(MainLoop *p_main_loop); -	virtual void delete_main_loop(); +	void set_main_loop(MainLoop *p_main_loop) override; +	void delete_main_loop() override; -	virtual void finalize(); +	void finalize() override; -	virtual bool _check_internal_feature_support(const String &p_feature); +	bool _check_internal_feature_support(const String &p_feature) override;  public: +	bool idb_is_syncing = false; +  	// Override return type to make writing static callbacks less tedious.  	static OS_JavaScript *get_singleton(); -	virtual void initialize_joypads(); - -	virtual bool has_touchscreen_ui_hint() const; - -	virtual int get_audio_driver_count() const; -	virtual const char *get_audio_driver_name(int p_driver) const; +	void initialize_joypads() override; -	virtual MainLoop *get_main_loop() const; +	MainLoop *get_main_loop() const override;  	void finalize_async();  	bool main_loop_iterate(); -	virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr); -	virtual Error kill(const ProcessID &p_pid); -	virtual int get_process_id() const; +	Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr) override; +	Error kill(const ProcessID &p_pid) override; +	int get_process_id() const override; -	String get_executable_path() const; -	virtual Error shell_open(String p_uri); -	virtual String get_name() const; +	String get_executable_path() const override; +	Error shell_open(String p_uri) override; +	String get_name() const override;  	// Override default OS implementation which would block the main thread with delay_usec.  	// Implemented in javascript_main.cpp loop callback instead. -	virtual void add_frame_delay(bool p_can_draw) {} -	virtual bool can_draw() const; +	void add_frame_delay(bool p_can_draw) override {} -	virtual String get_cache_path() const; -	virtual String get_config_path() const; -	virtual String get_data_path() const; -	virtual String get_user_data_dir() const; +	String get_cache_path() const override; +	String get_config_path() const override; +	String get_data_path() const override; +	String get_user_data_dir() const override;  	void set_idb_available(bool p_idb_available); -	virtual bool is_userfs_persistent() const; +	bool is_userfs_persistent() const override;  	void resume_audio();  	bool is_finalizing() { return finalizing; } diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index b3553e961a..e2b88b7704 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -67,7 +67,7 @@ static void handle_crash(int sig) {  		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);  	} -	fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); +	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());  	char **strings = backtrace_symbols(bt_buffer, size);  	if (strings) {  		for (size_t i = 1; i < size; i++) { @@ -109,7 +109,7 @@ static void handle_crash(int sig) {  				output.erase(output.length() - 1, 1);  			} -			fprintf(stderr, "[%ld] %s (%ls)\n", (long int)i, fname, output.c_str()); +			fprintf(stderr, "[%ld] %s (%s)\n", (long int)i, fname, output.utf8().get_data());  		}  		free(strings); diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index 07fa06bc06..f5e2c72bbc 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -35,6 +35,11 @@ def can_build():          print("xinerama not found.. x11 disabled.")          return False +    x11_error = os.system("pkg-config xext --modversion > /dev/null ") +    if x11_error: +        print("xext not found.. x11 disabled.") +        return False +      x11_error = os.system("pkg-config xrandr --modversion > /dev/null ")      if x11_error:          print("xrandr not found.. x11 disabled.") @@ -109,7 +114,7 @@ def configure(env):      elif env["target"] == "debug":          env.Prepend(CCFLAGS=["-g3"]) -        env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])          env.Append(LINKFLAGS=["-rdynamic"])      ## Architecture @@ -194,6 +199,7 @@ def configure(env):      env.ParseConfig("pkg-config x11 --cflags --libs")      env.ParseConfig("pkg-config xcursor --cflags --libs")      env.ParseConfig("pkg-config xinerama --cflags --libs") +    env.ParseConfig("pkg-config xext --cflags --libs")      env.ParseConfig("pkg-config xrandr --cflags --libs")      env.ParseConfig("pkg-config xrender --cflags --libs")      env.ParseConfig("pkg-config xi --cflags --libs") diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 874a3a6392..8b0d08d1cb 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -39,10 +39,6 @@  #include "main/main.h"  #include "scene/resources/texture.h" -#if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h" -#endif -  #if defined(VULKAN_ENABLED)  #include "servers/rendering/rasterizer_rd/rasterizer_rd.h"  #endif @@ -54,6 +50,7 @@  #include <X11/Xatom.h>  #include <X11/Xutil.h>  #include <X11/extensions/Xinerama.h> +#include <X11/extensions/shape.h>  // ICCCM  #define WM_NormalState 1L // window normal state @@ -87,6 +84,13 @@  #define VALUATOR_TILTX 3  #define VALUATOR_TILTY 4 +//#define DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +#define DEBUG_LOG_X11(...) printf(__VA_ARGS__) +#else +#define DEBUG_LOG_X11(...) +#endif +  static const double abs_resolution_mult = 10000.0;  static const double abs_resolution_range_mult = 10.0; @@ -318,23 +322,19 @@ bool DisplayServerX11::_refresh_device_info() {  }  void DisplayServerX11::_flush_mouse_motion() { -	while (true) { -		if (XPending(x11_display) > 0) { -			XEvent event; -			XPeekEvent(x11_display, &event); - -			if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { -				XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; - -				if (event_data->evtype == XI_RawMotion) { -					XNextEvent(x11_display, &event); -				} else { -					break; -				} -			} else { -				break; +	// Block events polling while flushing motion events. +	MutexLock mutex_lock(events_mutex); + +	for (uint32_t event_index = 0; event_index < polled_events.size(); ++event_index) { +		XEvent &event = polled_events[event_index]; +		if (XGetEventData(x11_display, &event.xcookie) && event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) { +			XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; +			if (event_data->evtype == XI_RawMotion) { +				XFreeEventData(x11_display, &event.xcookie); +				polled_events.remove(event_index--); +				continue;  			} -		} else { +			XFreeEventData(x11_display, &event.xcookie);  			break;  		}  	} @@ -447,12 +447,25 @@ int DisplayServerX11::mouse_get_button_state() const {  void DisplayServerX11::clipboard_set(const String &p_text) {  	_THREAD_SAFE_METHOD_ -	internal_clipboard = p_text; +	{ +		// The clipboard content can be accessed while polling for events. +		MutexLock mutex_lock(events_mutex); +		internal_clipboard = p_text; +	} +  	XSetSelectionOwner(x11_display, XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, CurrentTime);  	XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, CurrentTime);  } -static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard, Atom target) { +Bool DisplayServerX11::_predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg) { +	if (event->type == SelectionNotify && event->xselection.requestor == *(Window *)arg) { +		return True; +	} else { +		return False; +	} +} + +String DisplayServerX11::_clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const {  	String ret;  	Atom type; @@ -460,23 +473,27 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x  	int format, result;  	unsigned long len, bytes_left, dummy;  	unsigned char *data; -	Window Sown = XGetSelectionOwner(x11_display, p_source); +	Window selection_owner = XGetSelectionOwner(x11_display, p_source); -	if (Sown == x11_window) { -		return p_internal_clipboard; -	}; +	if (selection_owner == x11_window) { +		return internal_clipboard; +	} -	if (Sown != None) { -		XConvertSelection(x11_display, p_source, target, selection, -				x11_window, CurrentTime); -		XFlush(x11_display); -		while (true) { +	if (selection_owner != None) { +		{ +			// Block events polling while processing selection events. +			MutexLock mutex_lock(events_mutex); + +			XConvertSelection(x11_display, p_source, target, selection, +					x11_window, CurrentTime); + +			XFlush(x11_display); + +			// Blocking wait for predicate to be True +			// and remove the event from the queue.  			XEvent event; -			XNextEvent(x11_display, &event); -			if (event.type == SelectionNotify && event.xselection.requestor == x11_window) { -				break; -			}; -		}; +			XIfEvent(x11_display, &event, _predicate_clipboard_selection, (XPointer)&x11_window); +		}  		//  		// Do not get any data, see how much data is there @@ -510,14 +527,14 @@ static String _clipboard_get_impl(Atom p_source, Window x11_window, ::Display *x  	return ret;  } -static String _clipboard_get(Atom p_source, Window x11_window, ::Display *x11_display, String p_internal_clipboard) { +String DisplayServerX11::_clipboard_get(Atom p_source, Window x11_window) const {  	String ret;  	Atom utf8_atom = XInternAtom(x11_display, "UTF8_STRING", True);  	if (utf8_atom != None) { -		ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, utf8_atom); +		ret = _clipboard_get_impl(p_source, x11_window, utf8_atom);  	} -	if (ret == "") { -		ret = _clipboard_get_impl(p_source, x11_window, x11_display, p_internal_clipboard, XA_STRING); +	if (ret.empty()) { +		ret = _clipboard_get_impl(p_source, x11_window, XA_STRING);  	}  	return ret;  } @@ -526,11 +543,11 @@ String DisplayServerX11::clipboard_get() const {  	_THREAD_SAFE_METHOD_  	String ret; -	ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); +	ret = _clipboard_get(XInternAtom(x11_display, "CLIPBOARD", 0), windows[MAIN_WINDOW_ID].x11_window); -	if (ret == "") { -		ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window, x11_display, internal_clipboard); -	}; +	if (ret.empty()) { +		ret = _clipboard_get(XA_PRIMARY, windows[MAIN_WINDOW_ID].x11_window); +	}  	return ret;  } @@ -685,6 +702,14 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u  	return id;  } +void DisplayServerX11::show_window(WindowID p_id) { +	_THREAD_SAFE_METHOD_ + +	WindowData &wd = windows[p_id]; + +	XMapWindow(x11_display, wd.x11_window); +} +  void DisplayServerX11::delete_sub_window(WindowID p_id) {  	_THREAD_SAFE_METHOD_ @@ -693,6 +718,8 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {  	WindowData &wd = windows[p_id]; +	DEBUG_LOG_X11("delete_sub_window: %lu (%u) \n", wd.x11_window, p_id); +  	while (wd.transient_children.size()) {  		window_set_transient(wd.transient_children.front()->get(), INVALID_WINDOW_ID);  	} @@ -729,15 +756,31 @@ ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) co  }  DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { -#warning This is an incorrect implementation, if windows overlap, it should return the topmost visible one or none if occluded by a foreign window - +	WindowID found_window = INVALID_WINDOW_ID; +	WindowID parent_window = INVALID_WINDOW_ID; +	unsigned int focus_order = 0;  	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { -		Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key())); +		const WindowData &wd = E->get(); + +		// Discard windows with no focus. +		if (wd.focus_order == 0) { +			continue; +		} + +		// Find topmost window which contains the given position. +		WindowID window_id = E->key(); +		Rect2i win_rect = Rect2i(window_get_position(window_id), window_get_size(window_id));  		if (win_rect.has_point(p_position)) { -			return E->key(); +			// For siblings, pick the window which was focused last. +			if ((parent_window != wd.transient_parent) || (wd.focus_order > focus_order)) { +				found_window = window_id; +				parent_window = wd.transient_parent; +				focus_order = wd.focus_order; +			}  		}  	} -	return INVALID_WINDOW_ID; + +	return found_window;  }  void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window) { @@ -753,6 +796,38 @@ void DisplayServerX11::window_set_title(const String &p_title, WindowID p_window  	XChangeProperty(x11_display, wd.x11_window, _net_wm_name, utf8_string, 8, PropModeReplace, (unsigned char *)p_title.utf8().get_data(), p_title.utf8().length());  } +void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { +	_THREAD_SAFE_METHOD_ + +	ERR_FAIL_COND(!windows.has(p_window)); +	const WindowData &wd = windows[p_window]; + +	int event_base, error_base; +	const Bool ext_okay = XShapeQueryExtension(x11_display, &event_base, &error_base); +	if (ext_okay) { +		Region region; +		if (p_region.size() == 0) { +			region = XCreateRegion(); +			XRectangle rect; +			rect.x = 0; +			rect.y = 0; +			rect.width = window_get_real_size(p_window).x; +			rect.height = window_get_real_size(p_window).y; +			XUnionRectWithRegion(&rect, region, region); +		} else { +			XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); +			for (int i = 0; i < p_region.size(); i++) { +				points[i].x = p_region[i].x; +				points[i].y = p_region[i].y; +			} +			region = XPolygonRegion(points, p_region.size(), EvenOddRule); +			memfree(points); +		} +		XShapeCombineRegion(x11_display, wd.x11_window, ShapeInput, 0, 0, region, ShapeSet); +		XDestroyRegion(region); +	} +} +  void DisplayServerX11::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {  	_THREAD_SAFE_METHOD_ @@ -846,24 +921,34 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent  	ERR_FAIL_COND(!windows.has(p_window));  	WindowData &wd_window = windows[p_window]; -	ERR_FAIL_COND(wd_window.transient_parent == p_parent); +	WindowID prev_parent = wd_window.transient_parent; +	ERR_FAIL_COND(prev_parent == p_parent);  	ERR_FAIL_COND_MSG(wd_window.on_top, "Windows with the 'on top' can't become transient.");  	if (p_parent == INVALID_WINDOW_ID) {  		//remove transient -		ERR_FAIL_COND(wd_window.transient_parent == INVALID_WINDOW_ID); -		ERR_FAIL_COND(!windows.has(wd_window.transient_parent)); +		ERR_FAIL_COND(prev_parent == INVALID_WINDOW_ID); +		ERR_FAIL_COND(!windows.has(prev_parent)); -		WindowData &wd_parent = windows[wd_window.transient_parent]; +		WindowData &wd_parent = windows[prev_parent];  		wd_window.transient_parent = INVALID_WINDOW_ID;  		wd_parent.transient_children.erase(p_window);  		XSetTransientForHint(x11_display, wd_window.x11_window, None); + +		// Set focus to parent sub window to avoid losing all focus with nested menus. +		// RevertToPointerRoot is used to make sure we don't lose all focus in case +		// a subwindow and its parent are both destroyed. +		if (wd_window.menu_type && !wd_window.no_focus) { +			if (!wd_parent.no_focus) { +				XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime); +			} +		}  	} else {  		ERR_FAIL_COND(!windows.has(p_parent)); -		ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); +		ERR_FAIL_COND_MSG(prev_parent != INVALID_WINDOW_ID, "Window already has a transient parent");  		WindowData &wd_parent = windows[p_parent];  		wd_window.transient_parent = p_parent; @@ -873,6 +958,46 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent  	}  } +// Helper method. Assumes that the window id has already been checked and exists. +void DisplayServerX11::_update_size_hints(WindowID p_window) { +	WindowData &wd = windows[p_window]; +	WindowMode window_mode = window_get_mode(p_window); +	XSizeHints *xsh = XAllocSizeHints(); + +	// Always set the position and size hints - they should be synchronized with the actual values after the window is mapped anyway +	xsh->flags |= PPosition | PSize; +	xsh->x = wd.position.x; +	xsh->y = wd.position.y; +	xsh->width = wd.size.width; +	xsh->height = wd.size.height; + +	if (window_mode == WINDOW_MODE_FULLSCREEN) { +		// Do not set any other hints to prevent the window manager from ignoring the fullscreen flags +	} else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { +		// If resizing is disabled, use the forced size +		xsh->flags |= PMinSize | PMaxSize; +		xsh->min_width = wd.size.x; +		xsh->max_width = wd.size.x; +		xsh->min_height = wd.size.y; +		xsh->max_height = wd.size.y; +	} else { +		// Otherwise, just respect min_size and max_size +		if (wd.min_size != Size2i()) { +			xsh->flags |= PMinSize; +			xsh->min_width = wd.min_size.x; +			xsh->min_height = wd.min_size.y; +		} +		if (wd.max_size != Size2i()) { +			xsh->flags |= PMaxSize; +			xsh->max_width = wd.max_size.x; +			xsh->max_height = wd.max_size.y; +		} +	} + +	XSetWMNormalHints(x11_display, wd.x11_window, xsh); +	XFree(xsh); +} +  Point2i DisplayServerX11::window_get_position(WindowID p_window) const {  	_THREAD_SAFE_METHOD_ @@ -926,25 +1051,8 @@ void DisplayServerX11::window_set_max_size(const Size2i p_size, WindowID p_windo  	}  	wd.max_size = p_size; -	if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { -		XSizeHints *xsh; -		xsh = XAllocSizeHints(); -		xsh->flags = 0L; -		if (wd.min_size != Size2i()) { -			xsh->flags |= PMinSize; -			xsh->min_width = wd.min_size.x; -			xsh->min_height = wd.min_size.y; -		} -		if (wd.max_size != Size2i()) { -			xsh->flags |= PMaxSize; -			xsh->max_width = wd.max_size.x; -			xsh->max_height = wd.max_size.y; -		} -		XSetWMNormalHints(x11_display, wd.x11_window, xsh); -		XFree(xsh); - -		XFlush(x11_display); -	} +	_update_size_hints(p_window); +	XFlush(x11_display);  }  Size2i DisplayServerX11::window_get_max_size(WindowID p_window) const { @@ -968,25 +1076,8 @@ void DisplayServerX11::window_set_min_size(const Size2i p_size, WindowID p_windo  	}  	wd.min_size = p_size; -	if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { -		XSizeHints *xsh; -		xsh = XAllocSizeHints(); -		xsh->flags = 0L; -		if (wd.min_size != Size2i()) { -			xsh->flags |= PMinSize; -			xsh->min_width = wd.min_size.x; -			xsh->min_height = wd.min_size.y; -		} -		if (wd.max_size != Size2i()) { -			xsh->flags |= PMaxSize; -			xsh->max_width = wd.max_size.x; -			xsh->max_height = wd.max_size.y; -		} -		XSetWMNormalHints(x11_display, wd.x11_window, xsh); -		XFree(xsh); - -		XFlush(x11_display); -	} +	_update_size_hints(p_window); +	XFlush(x11_display);  }  Size2i DisplayServerX11::window_get_min_size(WindowID p_window) const { @@ -1019,37 +1110,15 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {  	int old_w = xwa.width;  	int old_h = xwa.height; -	// If window resizable is disabled we need to update the attributes first -	XSizeHints *xsh; -	xsh = XAllocSizeHints(); -	if (!window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { -		xsh->flags = PMinSize | PMaxSize; -		xsh->min_width = size.x; -		xsh->max_width = size.x; -		xsh->min_height = size.y; -		xsh->max_height = size.y; -	} else { -		xsh->flags = 0L; -		if (wd.min_size != Size2i()) { -			xsh->flags |= PMinSize; -			xsh->min_width = wd.min_size.x; -			xsh->min_height = wd.min_size.y; -		} -		if (wd.max_size != Size2i()) { -			xsh->flags |= PMaxSize; -			xsh->max_width = wd.max_size.x; -			xsh->max_height = wd.max_size.y; -		} -	} -	XSetWMNormalHints(x11_display, wd.x11_window, xsh); -	XFree(xsh); +	// Update our videomode width and height +	wd.size = size; + +	// Update the size hints first to make sure the window size can be set +	_update_size_hints(p_window);  	// Resize the window  	XResizeWindow(x11_display, wd.x11_window, size.x, size.y); -	// Update our videomode width and height -	wd.size = size; -  	for (int timeout = 0; timeout < 50; ++timeout) {  		XSync(x11_display, False);  		XGetWindowAttributes(x11_display, wd.x11_window, &xwa); @@ -1205,14 +1274,9 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {  		XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);  	} -	if (p_enabled && window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { +	if (p_enabled) {  		// Set the window as resizable to prevent window managers to ignore the fullscreen state flag. -		XSizeHints *xsh; - -		xsh = XAllocSizeHints(); -		xsh->flags = 0L; -		XSetWMNormalHints(x11_display, wd.x11_window, xsh); -		XFree(xsh); +		_update_size_hints(p_window);  	}  	// Using EWMH -- Extended Window Manager Hints @@ -1240,30 +1304,7 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) {  	if (!p_enabled) {  		// Reset the non-resizable flags if we un-set these before. -		Size2i size = window_get_size(p_window); -		XSizeHints *xsh; -		xsh = XAllocSizeHints(); -		if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { -			xsh->flags = PMinSize | PMaxSize; -			xsh->min_width = size.x; -			xsh->max_width = size.x; -			xsh->min_height = size.y; -			xsh->max_height = size.y; -		} else { -			xsh->flags = 0L; -			if (wd.min_size != Size2i()) { -				xsh->flags |= PMinSize; -				xsh->min_width = wd.min_size.x; -				xsh->min_height = wd.min_size.y; -			} -			if (wd.max_size != Size2i()) { -				xsh->flags |= PMaxSize; -				xsh->max_width = wd.max_size.x; -				xsh->max_height = wd.max_size.y; -			} -		} -		XSetWMNormalHints(x11_display, wd.x11_window, xsh); -		XFree(xsh); +		_update_size_hints(p_window);  		// put back or remove decorations according to the last set borderless state  		Hints hints; @@ -1321,13 +1362,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {  		} break;  		case WINDOW_MODE_FULLSCREEN: {  			//Remove full-screen +			wd.fullscreen = false; +  			_set_wm_fullscreen(p_window, false);  			//un-maximize required for always on top  			bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); -			wd.fullscreen = false; -  			window_set_position(wd.last_position_before_fs, p_window);  			if (on_top) { @@ -1373,15 +1414,16 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {  		} break;  		case WINDOW_MODE_FULLSCREEN: {  			wd.last_position_before_fs = wd.position; +  			if (window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window)) {  				_set_wm_maximized(p_window, true);  			} -			_set_wm_fullscreen(p_window, true); +  			wd.fullscreen = true; +			_set_wm_fullscreen(p_window, true);  		} break;  		case WINDOW_MODE_MAXIMIZED: {  			_set_wm_maximized(p_window, true); -  		} break;  	}  } @@ -1448,37 +1490,11 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo  	switch (p_flag) {  		case WINDOW_FLAG_RESIZE_DISABLED: { -			XSizeHints *xsh; -			xsh = XAllocSizeHints(); -			if (p_enabled) { -				Size2i size = window_get_size(p_window); - -				xsh->flags = PMinSize | PMaxSize; -				xsh->min_width = size.x; -				xsh->max_width = size.x; -				xsh->min_height = size.y; -				xsh->max_height = size.y; -			} else { -				xsh->flags = 0L; -				if (wd.min_size != Size2i()) { -					xsh->flags |= PMinSize; -					xsh->min_width = wd.min_size.x; -					xsh->min_height = wd.min_size.y; -				} -				if (wd.max_size != Size2i()) { -					xsh->flags |= PMaxSize; -					xsh->max_width = wd.max_size.x; -					xsh->max_height = wd.max_size.y; -				} -			} - -			XSetWMNormalHints(x11_display, wd.x11_window, xsh); -			XFree(xsh); -  			wd.resize_disabled = p_enabled; -			XFlush(x11_display); +			_update_size_hints(p_window); +			XFlush(x11_display);  		} break;  		case WINDOW_FLAG_BORDERLESS: {  			Hints hints; @@ -1646,10 +1662,16 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win  		return;  	} +	// Block events polling while changing input focus +	// because it triggers some event polling internally.  	if (p_active) { -		XSetICFocus(wd.xic); +		{ +			MutexLock mutex_lock(events_mutex); +			XSetICFocus(wd.xic); +		}  		window_set_ime_position(wd.im_position, p_window);  	} else { +		MutexLock mutex_lock(events_mutex);  		XUnsetICFocus(wd.xic);  	}  } @@ -1670,7 +1692,14 @@ void DisplayServerX11::window_set_ime_position(const Point2i &p_pos, WindowID p_  	spot.x = short(p_pos.x);  	spot.y = short(p_pos.y);  	XVaNestedList preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, nullptr); -	XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); + +	{ +		// Block events polling during this call +		// because it triggers some event polling internally. +		MutexLock mutex_lock(events_mutex); +		XSetICValues(wd.xic, XNPreeditAttributes, preedit_attr, nullptr); +	} +  	XFree(preedit_attr);  } @@ -1990,7 +2019,7 @@ unsigned int DisplayServerX11::_get_mouse_button_state(unsigned int p_x11_button  	return last_button_state;  } -void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo) { +void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo) {  	WindowData wd = windows[p_window];  	// X11 functions don't know what const is  	XKeyEvent *xkeyevent = p_event; @@ -2127,7 +2156,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,  	/* Phase 4, determine if event must be filtered */  	// This seems to be a side-effect of using XIM. -	// XEventFilter looks like a core X11 function, +	// XFilterEvent looks like a core X11 function,  	// but it's actually just used to see if we must  	// ignore a deadkey, or events XIM determines  	// must not reach the actual gui. @@ -2161,17 +2190,16 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,  	// Echo characters in X11 are a keyrelease and a keypress  	// one after the other with the (almot) same timestamp. -	// To detect them, i use XPeekEvent and check that their -	// difference in time is below a threshold. +	// To detect them, i compare to the next event in list and +	// check that their difference in time is below a threshold.  	if (xkeyevent->type != KeyPress) {  		p_echo = false;  		// make sure there are events pending,  		// so this call won't block. -		if (XPending(x11_display) > 0) { -			XEvent peek_event; -			XPeekEvent(x11_display, &peek_event); +		if (p_event_index + 1 < p_events.size()) { +			XEvent &peek_event = p_events[p_event_index + 1];  			// I'm using a threshold of 5 msecs,  			// since sometimes there seems to be a little @@ -2186,9 +2214,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,  				KeySym rk;  				XLookupString((XKeyEvent *)&peek_event, str, 256, &rk, nullptr);  				if (rk == keysym_keycode) { -					XEvent event; -					XNextEvent(x11_display, &event); //erase next event -					_handle_key_event(p_window, (XKeyEvent *)&event, true); +					// Consume to next event. +					++p_event_index; +					_handle_key_event(p_window, (XKeyEvent *)&peek_event, p_events, p_event_index, true);  					return; //ignore current, echo next  				}  			} @@ -2243,6 +2271,66 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,  	Input::get_singleton()->accumulate_input_event(k);  } +void DisplayServerX11::_handle_selection_request_event(XSelectionRequestEvent *p_event) { +	XEvent respond; +	if (p_event->target == XInternAtom(x11_display, "UTF8_STRING", 0) || +			p_event->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || +			p_event->target == XInternAtom(x11_display, "TEXT", 0) || +			p_event->target == XA_STRING || +			p_event->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || +			p_event->target == XInternAtom(x11_display, "text/plain", 0)) { +		// Directly using internal clipboard because we know our window +		// is the owner during a selection request. +		CharString clip = internal_clipboard.utf8(); +		XChangeProperty(x11_display, +				p_event->requestor, +				p_event->property, +				p_event->target, +				8, +				PropModeReplace, +				(unsigned char *)clip.get_data(), +				clip.length()); +		respond.xselection.property = p_event->property; +	} else if (p_event->target == XInternAtom(x11_display, "TARGETS", 0)) { +		Atom data[7]; +		data[0] = XInternAtom(x11_display, "TARGETS", 0); +		data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); +		data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); +		data[3] = XInternAtom(x11_display, "TEXT", 0); +		data[4] = XA_STRING; +		data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); +		data[6] = XInternAtom(x11_display, "text/plain", 0); + +		XChangeProperty(x11_display, +				p_event->requestor, +				p_event->property, +				XA_ATOM, +				32, +				PropModeReplace, +				(unsigned char *)&data, +				sizeof(data) / sizeof(data[0])); +		respond.xselection.property = p_event->property; + +	} else { +		char *targetname = XGetAtomName(x11_display, p_event->target); +		printf("No Target '%s'\n", targetname); +		if (targetname) { +			XFree(targetname); +		} +		respond.xselection.property = None; +	} + +	respond.xselection.type = SelectionNotify; +	respond.xselection.display = p_event->display; +	respond.xselection.requestor = p_event->requestor; +	respond.xselection.selection = p_event->selection; +	respond.xselection.target = p_event->target; +	respond.xselection.time = p_event->time; + +	XSendEvent(x11_display, p_event->requestor, True, NoEventMask, &respond); +	XFlush(x11_display); +} +  void DisplayServerX11::_xim_destroy_callback(::XIM im, ::XPointer client_data,  		::XPointer call_data) {  	WARN_PRINT("Input method stopped"); @@ -2355,9 +2443,73 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev  	}  } +void DisplayServerX11::_poll_events_thread(void *ud) { +	DisplayServerX11 *display_server = (DisplayServerX11 *)ud; +	display_server->_poll_events(); +} + +Bool DisplayServerX11::_predicate_all_events(Display *display, XEvent *event, XPointer arg) { +	// Just accept all events. +	return True; +} + +void DisplayServerX11::_poll_events() { +	int x11_fd = ConnectionNumber(x11_display); +	fd_set in_fds; + +	while (!events_thread_done) { +		XFlush(x11_display); + +		FD_ZERO(&in_fds); +		FD_SET(x11_fd, &in_fds); + +		struct timeval tv; +		tv.tv_usec = 0; +		tv.tv_sec = 1; + +		// Wait for next event or timeout. +		int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv); +		if (num_ready_fds < 0) { +			ERR_PRINT("_poll_events: select error: " + itos(errno)); +		} + +		// Process events from the queue. +		{ +			MutexLock mutex_lock(events_mutex); + +			// Non-blocking wait for next event +			// and remove it from the queue. +			XEvent ev; +			while (XCheckIfEvent(x11_display, &ev, _predicate_all_events, nullptr)) { +				// Check if the input manager wants to process the event. +				if (XFilterEvent(&ev, None)) { +					// Event has been filtered by the Input Manager, +					// it has to be ignored and a new one will be received. +					continue; +				} + +				// Handle selection request events directly in the event thread, because +				// communication through the x server takes several events sent back and forth +				// and we don't want to block other programs while processing only one each frame. +				if (ev.type == SelectionRequest) { +					_handle_selection_request_event(&(ev.xselectionrequest)); +					continue; +				} + +				polled_events.push_back(ev); +			} +		} +	} +} +  void DisplayServerX11::process_events() {  	_THREAD_SAFE_METHOD_ +#ifdef DISPLAY_SERVER_X11_DEBUG_LOGS_ENABLED +	static int frame = 0; +	++frame; +#endif +  	if (app_focused) {  		//verify that one of the windows has focus, else send focus out notification  		bool focus_found = false; @@ -2374,6 +2526,7 @@ void DisplayServerX11::process_events() {  			if (delta > 250) {  				//X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecessary focus in/outs.  				if (OS::get_singleton()->get_main_loop()) { +					DEBUG_LOG_X11("All focus lost, triggering NOTIFICATION_APPLICATION_FOCUS_OUT\n");  					OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT);  				}  				app_focused = false; @@ -2392,9 +2545,16 @@ void DisplayServerX11::process_events() {  	xi.tilt = Vector2();  	xi.pressure_supported = false; -	while (XPending(x11_display) > 0) { -		XEvent event; -		XNextEvent(x11_display, &event); +	LocalVector<XEvent> events; +	{ +		// Block events polling while flushing events. +		MutexLock mutex_lock(events_mutex); +		events = polled_events; +		polled_events.clear(); +	} + +	for (uint32_t event_index = 0; event_index < events.size(); ++event_index) { +		XEvent &event = events[event_index];  		WindowID window_id = MAIN_WINDOW_ID; @@ -2406,10 +2566,6 @@ void DisplayServerX11::process_events() {  			}  		} -		if (XFilterEvent(&event, None)) { -			continue; -		} -  		if (XGetEventData(x11_display, &event.xcookie)) {  			if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi.opcode) {  				XIDeviceEvent *event_data = (XIDeviceEvent *)event.xcookie.data; @@ -2572,32 +2728,67 @@ void DisplayServerX11::process_events() {  		XFreeEventData(x11_display, &event.xcookie);  		switch (event.type) { -			case Expose: +			case MapNotify: { +				DEBUG_LOG_X11("[%u] MapNotify window=%lu (%u) \n", frame, event.xmap.window, window_id); + +				const WindowData &wd = windows[window_id]; + +				// Set focus when menu window is started. +				// RevertToPointerRoot is used to make sure we don't lose all focus in case +				// a subwindow and its parent are both destroyed. +				if (wd.menu_type && !wd.no_focus) { +					XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); +				} +			} break; + +			case Expose: { +				DEBUG_LOG_X11("[%u] Expose window=%lu (%u), count='%u' \n", frame, event.xexpose.window, window_id, event.xexpose.count); +  				Main::force_redraw(); -				break; +			} break; + +			case NoExpose: { +				DEBUG_LOG_X11("[%u] NoExpose drawable=%lu (%u) \n", frame, event.xnoexpose.drawable, window_id); -			case NoExpose:  				windows[window_id].minimized = true; -				break; +			} break;  			case VisibilityNotify: { +				DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); +  				XVisibilityEvent *visibility = (XVisibilityEvent *)&event;  				windows[window_id].minimized = (visibility->state == VisibilityFullyObscured);  			} break; +  			case LeaveNotify: { +				DEBUG_LOG_X11("[%u] LeaveNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); +  				if (!mouse_mode_grab) {  					_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT);  				}  			} break; +  			case EnterNotify: { +				DEBUG_LOG_X11("[%u] EnterNotify window=%lu (%u), mode='%u' \n", frame, event.xcrossing.window, window_id, event.xcrossing.mode); +  				if (!mouse_mode_grab) {  					_send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_ENTER);  				}  			} break; -			case FocusIn: -				windows[window_id].focused = true; -				_send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); + +			case FocusIn: { +				DEBUG_LOG_X11("[%u] FocusIn window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + +				WindowData &wd = windows[window_id]; + +				wd.focused = true; + +				// Keep track of focus order for overlapping windows. +				static unsigned int focus_order = 0; +				wd.focus_order = ++focus_order; + +				_send_window_event(wd, WINDOW_EVENT_FOCUS_IN);  				if (mouse_mode_grab) {  					// Show and update the cursor if confined and the window regained focus. @@ -2621,8 +2812,11 @@ void DisplayServerX11::process_events() {  					XIGrabDevice(x11_display, xi.touch_devices[i], x11_window, CurrentTime, None, XIGrabModeAsync, XIGrabModeAsync, False, &xi.touch_event_mask);  				}*/  #endif -				if (windows[window_id].xic) { -					XSetICFocus(windows[window_id].xic); +				if (wd.xic) { +					// Block events polling while changing input focus +					// because it triggers some event polling internally. +					MutexLock mutex_lock(events_mutex); +					XSetICFocus(wd.xic);  				}  				if (!app_focused) { @@ -2631,12 +2825,17 @@ void DisplayServerX11::process_events() {  					}  					app_focused = true;  				} -				break; +			} break; + +			case FocusOut: { +				DEBUG_LOG_X11("[%u] FocusOut window=%lu (%u), mode='%u' \n", frame, event.xfocus.window, window_id, event.xfocus.mode); + +				WindowData &wd = windows[window_id]; + +				wd.focused = false; -			case FocusOut: -				windows[window_id].focused = false;  				Input::get_singleton()->release_pressed_events(); -				_send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); +				_send_window_event(wd, WINDOW_EVENT_FOCUS_OUT);  				if (mouse_mode_grab) {  					for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -2665,14 +2864,29 @@ void DisplayServerX11::process_events() {  				}  				xi.state.clear();  #endif -				if (windows[window_id].xic) { -					XSetICFocus(windows[window_id].xic); +				if (wd.xic) { +					// Block events polling while changing input focus +					// because it triggers some event polling internally. +					MutexLock mutex_lock(events_mutex); +					XUnsetICFocus(wd.xic); +				} +			} break; + +			case ConfigureNotify: { +				DEBUG_LOG_X11("[%u] ConfigureNotify window=%lu (%u), event=%lu, above=%lu, override_redirect=%u \n", frame, event.xconfigure.window, window_id, event.xconfigure.event, event.xconfigure.above, event.xconfigure.override_redirect); + +				const WindowData &wd = windows[window_id]; + +				// Set focus when menu window is re-used. +				// RevertToPointerRoot is used to make sure we don't lose all focus in case +				// a subwindow and its parent are both destroyed. +				if (wd.menu_type && !wd.no_focus) { +					XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime);  				} -				break; -			case ConfigureNotify:  				_window_changed(&event); -				break; +			} break; +  			case ButtonPress:  			case ButtonRelease: {  				/* exit in case of a mouse button press */ @@ -2699,7 +2913,18 @@ void DisplayServerX11::process_events() {  				mb->set_pressed((event.type == ButtonPress)); +				const WindowData &wd = windows[window_id]; +  				if (event.type == ButtonPress) { +					DEBUG_LOG_X11("[%u] ButtonPress window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + +					// Ensure window focus on click. +					// RevertToPointerRoot is used to make sure we don't lose all focus in case +					// a subwindow and its parent are both destroyed. +					if (!wd.no_focus) { +						XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); +					} +  					uint64_t diff = OS::get_singleton()->get_ticks_usec() / 1000 - last_click_ms;  					if (mb->get_button_index() == last_click_button_index) { @@ -2718,6 +2943,33 @@ void DisplayServerX11::process_events() {  						last_click_ms += diff;  						last_click_pos = Point2i(event.xbutton.x, event.xbutton.y);  					} +				} else { +					DEBUG_LOG_X11("[%u] ButtonRelease window=%lu (%u), button_index=%u \n", frame, event.xbutton.window, window_id, mb->get_button_index()); + +					if (!wd.focused) { +						// Propagate the event to the focused window, +						// because it's received only on the topmost window. +						// Note: This is needed for drag & drop to work between windows, +						// because the engine expects events to keep being processed +						// on the same window dragging started. +						for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { +							const WindowData &wd_other = E->get(); +							WindowID window_id_other = E->key(); +							if (wd_other.focused) { +								if (window_id_other != window_id) { +									int x, y; +									Window child; +									XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xbutton.x, event.xbutton.y, &x, &y, &child); + +									mb->set_window_id(window_id_other); +									mb->set_position(Vector2(x, y)); +									mb->set_global_position(mb->get_position()); +									Input::get_singleton()->accumulate_input_event(mb); +								} +								break; +							} +						} +					}  				}  				Input::get_singleton()->accumulate_input_event(mb); @@ -2735,11 +2987,11 @@ void DisplayServerX11::process_events() {  						break;  					} -					if (XPending(x11_display) > 0) { -						XEvent tevent; -						XPeekEvent(x11_display, &tevent); -						if (tevent.type == MotionNotify) { -							XNextEvent(x11_display, &event); +					if (event_index + 1 < events.size()) { +						const XEvent &next_event = events[event_index + 1]; +						if (next_event.type == MotionNotify) { +							++event_index; +							event = next_event;  						} else {  							break;  						} @@ -2767,6 +3019,9 @@ void DisplayServerX11::process_events() {  					break;  				} +				const WindowData &wd = windows[window_id]; +				bool focused = wd.focused; +  				if (mouse_mode == MOUSE_MODE_CAPTURED) {  					if (xi.relative_motion.x == 0 && xi.relative_motion.y == 0) {  						break; @@ -2775,7 +3030,7 @@ void DisplayServerX11::process_events() {  					Point2i new_center = pos;  					pos = last_mouse_pos + xi.relative_motion;  					center = new_center; -					do_mouse_warp = windows[window_id].focused; // warp the cursor if we're focused in +					do_mouse_warp = focused; // warp the cursor if we're focused in  				}  				if (!last_mouse_pos_valid) { @@ -2817,14 +3072,11 @@ void DisplayServerX11::process_events() {  				}  				mm->set_tilt(xi.tilt); -				// Make the absolute position integral so it doesn't look _too_ weird :) -				Point2i posi(pos); -  				_get_key_modifier_state(event.xmotion.state, mm);  				mm->set_button_mask(mouse_get_button_state()); -				mm->set_position(posi); -				mm->set_global_position(posi); -				Input::get_singleton()->set_mouse_position(posi); +				mm->set_position(pos); +				mm->set_global_position(pos); +				Input::get_singleton()->set_mouse_position(pos);  				mm->set_speed(Input::get_singleton()->get_last_mouse_speed());  				mm->set_relative(rel); @@ -2835,8 +3087,32 @@ void DisplayServerX11::process_events() {  				// Don't propagate the motion event unless we have focus  				// this is so that the relative motion doesn't get messed up  				// after we regain focus. -				if (windows[window_id].focused || !mouse_mode_grab) { +				if (focused) {  					Input::get_singleton()->accumulate_input_event(mm); +				} else { +					// Propagate the event to the focused window, +					// because it's received only on the topmost window. +					// Note: This is needed for drag & drop to work between windows, +					// because the engine expects events to keep being processed +					// on the same window dragging started. +					for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { +						const WindowData &wd_other = E->get(); +						if (wd_other.focused) { +							int x, y; +							Window child; +							XTranslateCoordinates(x11_display, wd.x11_window, wd_other.x11_window, event.xmotion.x, event.xmotion.y, &x, &y, &child); + +							Point2i pos_focused(x, y); + +							mm->set_window_id(E->key()); +							mm->set_position(pos_focused); +							mm->set_global_position(pos_focused); +							mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); +							Input::get_singleton()->accumulate_input_event(mm); + +							break; +						} +					}  				}  			} break; @@ -2846,67 +3122,7 @@ void DisplayServerX11::process_events() {  				// key event is a little complex, so  				// it will be handled in its own function. -				_handle_key_event(window_id, (XKeyEvent *)&event); -			} break; -			case SelectionRequest: { -				XSelectionRequestEvent *req; -				XEvent e, respond; -				e = event; - -				req = &(e.xselectionrequest); -				if (req->target == XInternAtom(x11_display, "UTF8_STRING", 0) || -						req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) || -						req->target == XInternAtom(x11_display, "TEXT", 0) || -						req->target == XA_STRING || -						req->target == XInternAtom(x11_display, "text/plain;charset=utf-8", 0) || -						req->target == XInternAtom(x11_display, "text/plain", 0)) { -					CharString clip = clipboard_get().utf8(); -					XChangeProperty(x11_display, -							req->requestor, -							req->property, -							req->target, -							8, -							PropModeReplace, -							(unsigned char *)clip.get_data(), -							clip.length()); -					respond.xselection.property = req->property; -				} else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) { -					Atom data[7]; -					data[0] = XInternAtom(x11_display, "TARGETS", 0); -					data[1] = XInternAtom(x11_display, "UTF8_STRING", 0); -					data[2] = XInternAtom(x11_display, "COMPOUND_TEXT", 0); -					data[3] = XInternAtom(x11_display, "TEXT", 0); -					data[4] = XA_STRING; -					data[5] = XInternAtom(x11_display, "text/plain;charset=utf-8", 0); -					data[6] = XInternAtom(x11_display, "text/plain", 0); - -					XChangeProperty(x11_display, -							req->requestor, -							req->property, -							XA_ATOM, -							32, -							PropModeReplace, -							(unsigned char *)&data, -							sizeof(data) / sizeof(data[0])); -					respond.xselection.property = req->property; - -				} else { -					char *targetname = XGetAtomName(x11_display, req->target); -					printf("No Target '%s'\n", targetname); -					if (targetname) { -						XFree(targetname); -					} -					respond.xselection.property = None; -				} - -				respond.xselection.type = SelectionNotify; -				respond.xselection.display = req->display; -				respond.xselection.requestor = req->requestor; -				respond.xselection.selection = req->selection; -				respond.xselection.target = req->target; -				respond.xselection.time = req->time; -				XSendEvent(x11_display, req->requestor, True, NoEventMask, &respond); -				XFlush(x11_display); +				_handle_key_event(window_id, (XKeyEvent *)&event, events, event_index);  			} break;  			case SelectionNotify: @@ -3213,12 +3429,37 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u  	unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; -	WindowID id; +	WindowID id = window_id_counter++; +	WindowData &wd = windows[id]; + +	if ((id != MAIN_WINDOW_ID) && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) { +		wd.menu_type = true; +	} + +	if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { +		wd.menu_type = true; +		wd.no_focus = true; +	} + +	// Setup for menu subwindows: +	// - override_redirect forces the WM not to interfere with the window, to avoid delays due to +	//   handling decorations and placement. +	//   On the other hand, focus changes need to be handled manually when this is set. +	// - save_under is a hint for the WM to keep the content of windows behind to avoid repaint. +	if (wd.menu_type) { +		windowAttributes.override_redirect = True; +		windowAttributes.save_under = True; +		valuemask |= CWOverrideRedirect | CWSaveUnder; +	} +  	{ -		WindowData wd;  		wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); -		XMapWindow(x11_display, wd.x11_window); +		// Enable receiving notification when the window is initialized (MapNotify) +		// so the focus can be set at the right time. +		if (wd.menu_type && !wd.no_focus) { +			XSelectInput(x11_display, wd.x11_window, StructureNotifyMask); +		}  		//associate PID  		// make PID known to X11 @@ -3276,6 +3517,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u  		XChangeProperty(x11_display, wd.x11_window, xdnd_aware, XA_ATOM, 32, PropModeReplace, (unsigned char *)&xdnd_version, 1);  		if (xim && xim_style) { +			// Block events polling while changing input focus +			// because it triggers some event polling internally. +			MutexLock mutex_lock(events_mutex); +  			wd.xic = XCreateIC(xim, XNInputStyle, xim_style, XNClientWindow, wd.x11_window, XNFocusWindow, wd.x11_window, (char *)nullptr);  			if (XGetICValues(wd.xic, XNFilterEvents, &im_event_mask, nullptr) != nullptr) {  				WARN_PRINT("XGetICValues couldn't obtain XNFilterEvents value"); @@ -3294,87 +3539,30 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u  		_update_context(wd); -		id = window_id_counter++; - -		windows[id] = wd; - -		{ -			if (p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT) { -				XSizeHints *xsh; -				xsh = XAllocSizeHints(); - -				xsh->flags = PMinSize | PMaxSize; -				xsh->min_width = p_rect.size.width; -				xsh->max_width = p_rect.size.width; -				xsh->min_height = p_rect.size.height; -				xsh->max_height = p_rect.size.height; - -				XSetWMNormalHints(x11_display, wd.x11_window, xsh); -				XFree(xsh); -			} - -			bool make_utility = false; - -			if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { -				Hints hints; -				Atom property; -				hints.flags = 2; -				hints.decorations = 0; -				property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); -				XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); - -				make_utility = true; -			} -			if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { -				make_utility = true; -			} - -			if (make_utility) { -				//this one seems to disable the fade animations for regular windows -				//but has the drawback that will not get focus by default, so -				//we need to force it, unless no focus requested - -				Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); -				Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - -				XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); - -				if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { -					//but as utility appears unfocused, it needs to be forcefuly focused, unless no focus requested -					XEvent xev; -					Atom net_active_window = XInternAtom(x11_display, "_NET_ACTIVE_WINDOW", False); - -					memset(&xev, 0, sizeof(xev)); -					xev.type = ClientMessage; -					xev.xclient.window = wd.x11_window; -					xev.xclient.message_type = net_active_window; -					xev.xclient.format = 32; -					xev.xclient.data.l[0] = 1; -					xev.xclient.data.l[1] = CurrentTime; - -					XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); -				} -			} else { -				Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); -				Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); - -				XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); -			} +		if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { +			Hints hints; +			Atom property; +			hints.flags = 2; +			hints.decorations = 0; +			property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); +			XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);  		} -		if (id != MAIN_WINDOW_ID) { -			XSizeHints my_hints = XSizeHints(); +		if (wd.menu_type) { +			// Set Utility type to disable fade animations. +			Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_UTILITY", False); +			Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); -			my_hints.flags = PPosition | PSize; /* I want to specify position and size */ -			my_hints.x = p_rect.position.x; /* The origin and size coords I want */ -			my_hints.y = p_rect.position.y; -			my_hints.width = p_rect.size.width; -			my_hints.height = p_rect.size.height; +			XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1); +		} else { +			Atom type_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE_NORMAL", False); +			Atom wt_atom = XInternAtom(x11_display, "_NET_WM_WINDOW_TYPE", False); -			XSetNormalHints(x11_display, wd.x11_window, &my_hints); -			XMoveWindow(x11_display, wd.x11_window, p_rect.position.x, p_rect.position.y); +			XChangeProperty(x11_display, wd.x11_window, wt_atom, XA_ATOM, 32, PropModeReplace, (unsigned char *)&type_atom, 1);  		} +		_update_size_hints(id); +  #if defined(VULKAN_ENABLED)  		if (context_vulkan) {  			Error err = context_vulkan->window_create(id, wd.x11_window, x11_display, p_rect.size.width, p_rect.size.height); @@ -3391,8 +3579,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u  		XFree(visualInfo);  	} -	WindowData &wd = windows[id]; -  	window_set_mode(p_mode, id);  	//sync size @@ -3414,6 +3600,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u  	if (cursors[current_cursor] != None) {  		XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);  	} +  	return id;  } @@ -3485,7 +3672,12 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode  	xrandr_handle = dlopen("libXrandr.so.2", RTLD_LAZY);  	if (!xrandr_handle) {  		err = dlerror(); -		fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); +		// For some arcane reason, NetBSD now ships libXrandr.so.3 while the rest of the world has libXrandr.so.2... +		// In case this happens for other X11 platforms in the future, let's give it a try too before failing. +		xrandr_handle = dlopen("libXrandr.so.3", RTLD_LAZY); +		if (!xrandr_handle) { +			fprintf(stderr, "could not load libXrandr.so.2, Error: %s\n", err); +		}  	} else {  		XRRQueryVersion(x11_display, &xrandr_major, &xrandr_minor);  		if (((xrandr_major << 8) | xrandr_minor) >= 0x0105) { @@ -3653,6 +3845,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode  			window_set_flag(WindowFlags(i), true, main_window);  		}  	} +	show_window(main_window);  //create RenderingDevice if used  #if defined(VULKAN_ENABLED) @@ -3830,12 +4023,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode  		}  	} +	events_thread = Thread::create(_poll_events_thread, this); +  	_update_real_mouse_position(windows[MAIN_WINDOW_ID]);  	r_error = OK;  }  DisplayServerX11::~DisplayServerX11() { +	events_thread_done = true; +	Thread::wait_to_finish(events_thread); +	memdelete(events_thread); +	events_thread = nullptr; +  	//destroy all windows  	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) {  #ifdef VULKAN_ENABLED diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index b5d2ea1c63..740bf81fd9 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -36,6 +36,7 @@  #include "servers/display_server.h"  #include "core/input/input.h" +#include "core/local_vector.h"  #include "drivers/alsa/audio_driver_alsa.h"  #include "drivers/alsamidi/midi_driver_alsamidi.h"  #include "drivers/pulseaudio/audio_driver_pulseaudio.h" @@ -132,6 +133,9 @@ class DisplayServerX11 : public DisplayServer {  		ObjectID instance_id; +		bool menu_type = false; +		bool no_focus = false; +  		//better to guess on the fly, given WM can change it  		//WindowMode mode;  		bool fullscreen = false; //OS can't exit from this mode @@ -141,6 +145,8 @@ class DisplayServerX11 : public DisplayServer {  		Vector2i last_position_before_fs;  		bool focused = false;  		bool minimized = false; + +		unsigned int focus_order = 0;  	};  	Map<WindowID, WindowData> windows; @@ -197,7 +203,11 @@ class DisplayServerX11 : public DisplayServer {  	MouseMode mouse_mode;  	Point2i center; -	void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); +	void _handle_key_event(WindowID p_window, XKeyEvent *p_event, LocalVector<XEvent> &p_events, uint32_t &p_event_index, bool p_echo = false); +	void _handle_selection_request_event(XSelectionRequestEvent *p_event); + +	String _clipboard_get_impl(Atom p_source, Window x11_window, Atom target) const; +	String _clipboard_get(Atom p_source, Window x11_window) const;  	//bool minimized;  	//bool window_has_focus; @@ -235,6 +245,7 @@ class DisplayServerX11 : public DisplayServer {  	void _update_real_mouse_position(const WindowData &wd);  	bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; +	void _update_size_hints(WindowID p_window);  	void _set_wm_fullscreen(WindowID p_window, bool p_enabled);  	void _set_wm_maximized(WindowID p_window, bool p_enabled); @@ -246,6 +257,16 @@ class DisplayServerX11 : public DisplayServer {  	static void _dispatch_input_events(const Ref<InputEvent> &p_event);  	void _dispatch_input_event(const Ref<InputEvent> &p_event); +	mutable Mutex events_mutex; +	Thread *events_thread = nullptr; +	bool events_thread_done = false; +	LocalVector<XEvent> polled_events; +	static void _poll_events_thread(void *ud); +	void _poll_events(); + +	static Bool _predicate_all_events(Display *display, XEvent *event, XPointer arg); +	static Bool _predicate_clipboard_selection(Display *display, XEvent *event, XPointer arg); +  protected:  	void _window_changed(XEvent *event); @@ -276,6 +297,7 @@ public:  	virtual Vector<DisplayServer::WindowID> get_window_list() const;  	virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); +	virtual void show_window(WindowID p_id);  	virtual void delete_sub_window(WindowID p_id);  	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; @@ -284,6 +306,8 @@ public:  	virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const;  	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); +	virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID); +  	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);  	virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);  	virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index 3ed64e9d46..e1796ccefe 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -41,6 +41,9 @@ int main(int argc, char *argv[]) {  	setlocale(LC_CTYPE, ""); +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	char *cwd = (char *)malloc(PATH_MAX);  	ERR_FAIL_COND_V(!cwd, ERR_OUT_OF_MEMORY);  	char *ret = getcwd(cwd, PATH_MAX); diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 5edaf35c50..fda1358dfd 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -311,16 +311,9 @@ void JoypadLinux::open_joypad(const char *p_path) {  			return;  		} -		//check if the device supports basic gamepad events, prevents certain keyboards from -		//being detected as joypads +		// Check if the device supports basic gamepad events  		if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && -					(test_bit(ABS_X, absbit) || test_bit(ABS_Y, absbit) || test_bit(ABS_HAT0X, absbit) || -							test_bit(ABS_GAS, absbit) || test_bit(ABS_RUDDER, absbit)) && -					(test_bit(BTN_A, keybit) || test_bit(BTN_THUMBL, keybit) || -							test_bit(BTN_TRIGGER, keybit) || test_bit(BTN_1, keybit))) && -				!(test_bit(EV_ABS, evbit) && -						test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit) && -						test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit))) { +					test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) {  			close(fd);  			return;  		} diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 8c6f3b1167..e00a32e3ba 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -88,7 +88,9 @@ void OS_LinuxBSD::finalize() {  #endif  #ifdef JOYDEV_ENABLED -	memdelete(joypad); +	if (joypad) { +		memdelete(joypad); +	}  #endif  } diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 4295721c68..cd4fbd9db5 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -48,7 +48,7 @@ class OS_LinuxBSD : public OS_Unix {  	bool force_quit;  #ifdef JOYDEV_ENABLED -	JoypadLinux *joypad; +	JoypadLinux *joypad = nullptr;  #endif  #ifdef ALSA_ENABLED diff --git a/platform/linuxbsd/platform_config.h b/platform/linuxbsd/platform_config.h index ac30519132..571ad03db0 100644 --- a/platform/linuxbsd/platform_config.h +++ b/platform/linuxbsd/platform_config.h @@ -31,9 +31,15 @@  #ifdef __linux__  #include <alloca.h>  #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include <stdlib.h> // alloca +// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, +// include NetBSD, use pthread_setname_np. NetBSD's version however requires +// a different format, we handle this directly in thread_posix. +#ifdef __NetBSD__ +#define PTHREAD_NETBSD_SET_NAME +#else  #define PTHREAD_BSD_SET_NAME  #endif - -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h" +#endif diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 5da0118686..9fb2f63935 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -90,7 +90,7 @@ static void handle_crash(int sig) {  	if (OS::get_singleton()->get_main_loop())  		OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); -	fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); +	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());  	char **strings = backtrace_symbols(bt_buffer, size);  	if (strings) {  		void *load_addr = (void *)load_address(); @@ -142,7 +142,7 @@ static void handle_crash(int sig) {  				}  			} -			fprintf(stderr, "[%zu] %ls\n", i, output.c_str()); +			fprintf(stderr, "[%zu] %s\n", i, output.utf8().get_data());  		}  		free(strings); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index ca28e1e70e..6fc1dc65af 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -1,6 +1,5 @@  import os  import sys -import subprocess  from methods import detect_darwin_sdk_path @@ -28,7 +27,8 @@ def get_opts():          ("MACOS_SDK_PATH", "Path to the macOS SDK", ""),          BoolVariable(              "use_static_mvk", -            "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)", +            "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables" +            " validation layers)",              False,          ),          EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), @@ -50,9 +50,11 @@ def configure(env):      if env["target"] == "release":          if env["optimize"] == "speed":  # optimize for speed (default) -            env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize", "-msse2"]) +            env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"])          else:  # optimize for size -            env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize", "-msse2"]) +            env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"]) +        if env["arch"] != "arm64": +            env.Prepend(CCFLAGS=["-msse2"])          if env["debug_symbols"] == "yes":              env.Prepend(CCFLAGS=["-g1"]) @@ -72,7 +74,8 @@ def configure(env):      elif env["target"] == "debug":          env.Prepend(CCFLAGS=["-g3"]) -        env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Prepend(CPPDEFINES=["DEBUG_ENABLED"]) +        env.Prepend(LINKFLAGS=["-Xlinker", "-no_deduplicate"])      ## Architecture diff --git a/platform/osx/dir_access_osx.h b/platform/osx/dir_access_osx.h index d61ee181f0..91b8f9b2c5 100644 --- a/platform/osx/dir_access_osx.h +++ b/platform/osx/dir_access_osx.h @@ -47,6 +47,8 @@ protected:  	virtual int get_drive_count();  	virtual String get_drive(int p_drive); + +	virtual bool is_hidden(const String &p_name);  };  #endif //UNIX ENABLED diff --git a/platform/osx/dir_access_osx.mm b/platform/osx/dir_access_osx.mm index 7791ba5407..439c6a075f 100644 --- a/platform/osx/dir_access_osx.mm +++ b/platform/osx/dir_access_osx.mm @@ -68,4 +68,14 @@ String DirAccessOSX::get_drive(int p_drive) {  	return volname;  } +bool DirAccessOSX::is_hidden(const String &p_name) { +	String f = get_current_dir().plus_file(p_name); +	NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; +	NSNumber *hidden = nil; +	if (![url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:nil]) { +		return DirAccessUnix::is_hidden(p_name); +	} +	return [hidden boolValue]; +} +  #endif //posix_enabled diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index 68e8454fd0..073d35008b 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -102,6 +102,8 @@ public:  		id window_object;  		id window_view; +		Vector<Vector2> mpath; +  #if defined(OPENGL_ENABLED)  		ContextGL_OSX *context_gles2 = nullptr;  #endif @@ -230,6 +232,7 @@ public:  	virtual Vector<int> get_window_list() const override;  	virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override; +	virtual void show_window(WindowID p_id) override;  	virtual void delete_sub_window(WindowID p_id) override;  	virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override; @@ -239,6 +242,7 @@ public:  	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;  	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override; +	virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;  	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;  	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 5e209a6e6b..49f0e7bfa3 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -33,6 +33,7 @@  #include "os_osx.h"  #include "core/io/marshalls.h" +#include "core/math/geometry_2d.h"  #include "core/os/keyboard.h"  #include "main/main.h"  #include "scene/resources/texture.h" @@ -45,7 +46,6 @@  #include <IOKit/hid/IOHIDLib.h>  #if defined(OPENGL_ENABLED) -#include "drivers/gles2/rasterizer_gles2.h"  //TODO - reimplement OpenGLES  #import <AppKit/NSOpenGLView.h> @@ -312,8 +312,6 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {  		DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID);  	} -	DS_OSX->windows.erase(window_id); -  	if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) {  		DisplayServerOSX::WindowData &pwd = DS_OSX->windows[wd.transient_parent];  		[pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to parent. @@ -333,6 +331,8 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) {  		DS_OSX->context_vulkan->window_destroy(window_id);  	}  #endif + +	DS_OSX->windows.erase(window_id);  }  - (void)windowDidEnterFullScreen:(NSNotification *)notification { @@ -1944,8 +1944,12 @@ void DisplayServerOSX::alert(const String &p_alert, const String &p_title) {  	[window setInformativeText:ns_alert];  	[window setAlertStyle:NSAlertStyleWarning]; +	id key_window = [[NSApplication sharedApplication] keyWindow];  	[window runModal];  	[window release]; +	if (key_window) { +		[key_window makeKeyAndOrderFront:nil]; +	}  }  Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { @@ -2037,6 +2041,12 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) {  			CGDisplayHideCursor(kCGDirectMainDisplay);  		}  		CGAssociateMouseAndMouseCursorPosition(false); +		WindowData &wd = windows[MAIN_WINDOW_ID]; +		const NSRect contentRect = [wd.window_view frame]; +		NSRect pointInWindowRect = NSMakeRect(contentRect.size.width / 2, contentRect.size.height / 2, 0, 0); +		NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; +		CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; +		CGWarpMouseCursorPosition(lMouseWarpPos);  	} else if (p_mode == MOUSE_MODE_HIDDEN) {  		if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) {  			CGDisplayHideCursor(kCGDirectMainDisplay); @@ -2311,18 +2321,23 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, u  	_THREAD_SAFE_METHOD_  	WindowID id = _create_window(p_mode, p_rect); -	WindowData &wd = windows[id];  	for (int i = 0; i < WINDOW_FLAG_MAX; i++) {  		if (p_flags & (1 << i)) {  			window_set_flag(WindowFlags(i), true, id);  		}  	} + +	return id; +} + +void DisplayServerOSX::show_window(WindowID p_id) { +	WindowData &wd = windows[p_id]; +  	if (wd.no_focus) {  		[wd.window_object orderFront:nil];  	} else {  		[wd.window_object makeKeyAndOrderFront:nil];  	} -	return id;  }  void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { @@ -2366,7 +2381,11 @@ void DisplayServerOSX::_update_window(WindowData p_wd) {  		[p_wd.window_object setHidesOnDeactivate:YES];  	} else {  		// Reset these when our window is not a borderless window that covers up the screen -		[p_wd.window_object setLevel:NSNormalWindowLevel]; +		if (p_wd.on_top) { +			[p_wd.window_object setLevel:NSFloatingWindowLevel]; +		} else { +			[p_wd.window_object setLevel:NSNormalWindowLevel]; +		}  		[p_wd.window_object setHidesOnDeactivate:NO];  	}  } @@ -2392,6 +2411,15 @@ void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window  	[wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];  } +void DisplayServerOSX::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { +	_THREAD_SAFE_METHOD_ + +	ERR_FAIL_COND(!windows.has(p_window)); +	WindowData &wd = windows[p_window]; + +	wd.mpath = p_region; +} +  void DisplayServerOSX::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {  	_THREAD_SAFE_METHOD_ @@ -2793,7 +2821,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo  		} break;  		case WINDOW_FLAG_BORDERLESS: {  			// OrderOut prevents a lose focus bug with the window -			[wd.window_object orderOut:nil]; +			if ([wd.window_object isVisible]) { +				[wd.window_object orderOut:nil]; +			}  			wd.borderless = p_enabled;  			if (p_enabled) {  				[wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; @@ -2807,7 +2837,13 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo  				[wd.window_object setFrame:frameRect display:NO];  			}  			_update_window(wd); -			[wd.window_object makeKeyAndOrderFront:nil]; +			if ([wd.window_object isVisible]) { +				if (wd.no_focus) { +					[wd.window_object orderFront:nil]; +				} else { +					[wd.window_object makeKeyAndOrderFront:nil]; +				} +			}  		} break;  		case WINDOW_FLAG_ALWAYS_ON_TOP: {  			wd.on_top = p_enabled; @@ -2875,7 +2911,11 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {  	const WindowData &wd = windows[p_window];  	[[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; -	[wd.window_object makeKeyAndOrderFront:nil]; +	if (wd.no_focus) { +		[wd.window_object orderFront:nil]; +	} else { +		[wd.window_object makeKeyAndOrderFront:nil]; +	}  }  bool DisplayServerOSX::window_can_draw(WindowID p_window) const { @@ -3332,6 +3372,26 @@ void DisplayServerOSX::process_events() {  		Input::get_singleton()->flush_accumulated_events();  	} +	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { +		WindowData &wd = E->get(); +		if (wd.mpath.size() > 0) { +			const Vector2 mpos = _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); +			if (Geometry2D::is_point_in_polygon(mpos, wd.mpath)) { +				if ([wd.window_object ignoresMouseEvents]) { +					[wd.window_object setIgnoresMouseEvents:NO]; +				} +			} else { +				if (![wd.window_object ignoresMouseEvents]) { +					[wd.window_object setIgnoresMouseEvents:YES]; +				} +			} +		} else { +			if ([wd.window_object ignoresMouseEvents]) { +				[wd.window_object setIgnoresMouseEvents:NO]; +			} +		} +	} +  	[autoreleasePool drain];  	autoreleasePool = [[NSAutoreleasePool alloc] init];  } @@ -3755,7 +3815,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode  			window_set_flag(WindowFlags(i), true, main_window);  		}  	} -	[windows[main_window].window_object makeKeyAndOrderFront:nil]; +	show_window(MAIN_WINDOW_ID);  #if defined(OPENGL_ENABLED)  	if (rendering_driver == "opengl_es") { @@ -3784,9 +3844,11 @@ DisplayServerOSX::~DisplayServerOSX() {  	}  	//destroy all windows -	for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { -		[E->get().window_object setContentView:nil]; -		[E->get().window_object close]; +	for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) { +		Map<WindowID, WindowData>::Element *F = E; +		E = E->next(); +		[F->get().window_object setContentView:nil]; +		[F->get().window_object close];  	}  	//destroy drivers diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index ae45e0734d..9f2160dd9e 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -78,7 +78,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {  		}  		for (int i = 0; i < pname.length(); i++) { -			CharType c = pname[i]; +			char32_t c = pname[i];  			if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {  				if (r_error) {  					*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); @@ -95,8 +95,8 @@ protected:  	virtual void get_export_options(List<ExportOption> *r_options) override;  public: -	virtual String get_name() const override { return "Mac OSX"; } -	virtual String get_os_name() const override { return "OSX"; } +	virtual String get_name() const override { return "macOS"; } +	virtual String get_os_name() const override { return "macOS"; }  	virtual Ref<Texture2D> get_logo() const override { return logo; }  	virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override { diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 93d0d6168c..4e73d5441c 100644 --- a/platform/osx/godot_main_osx.mm +++ b/platform/osx/godot_main_osx.mm @@ -37,7 +37,7 @@  int main(int argc, char **argv) {  #if defined(VULKAN_ENABLED) -	//MoltenVK - enable full component swizzling support +	// MoltenVK - enable full component swizzling support  	setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);  #endif @@ -60,6 +60,9 @@ int main(int argc, char **argv) {  	OS_OSX os;  	Error err; +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	if (os.open_with_filename != "") {  		char *argv_c = (char *)malloc(os.open_with_filename.utf8().size());  		memcpy(argv_c, os.open_with_filename.utf8().get_data(), os.open_with_filename.utf8().size()); diff --git a/platform/osx/logo.png b/platform/osx/logo.png Binary files differindex 834bbf3ba6..b5a660b165 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 9204a145bf..5a9e43450f 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -44,7 +44,7 @@ class OS_OSX : public OS_Unix {  	bool force_quit; -	JoypadOSX *joypad_osx; +	JoypadOSX *joypad_osx = nullptr;  #ifdef COREAUDIO_ENABLED  	AudioDriverCoreAudio audio_driver; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c4eb5407af..399a29cbe0 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -145,7 +145,9 @@ void OS_OSX::finalize() {  	delete_main_loop(); -	memdelete(joypad_osx); +	if (joypad_osx) { +		memdelete(joypad_osx); +	}  }  void OS_OSX::set_main_loop(MainLoop *p_main_loop) { diff --git a/platform/osx/platform_config.h b/platform/osx/platform_config.h index 155f37ed55..e657aca955 100644 --- a/platform/osx/platform_config.h +++ b/platform/osx/platform_config.h @@ -30,5 +30,4 @@  #include <alloca.h> -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"  #define PTHREAD_RENAME_SELF diff --git a/platform/server/detect.py b/platform/server/detect.py index a73810cdf4..4c5a4527ae 100644 --- a/platform/server/detect.py +++ b/platform/server/detect.py @@ -79,7 +79,7 @@ def configure(env):      elif env["target"] == "debug":          env.Prepend(CCFLAGS=["-g3"]) -        env.Prepend(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Prepend(CPPDEFINES=["DEBUG_ENABLED"])          env.Append(LINKFLAGS=["-rdynamic"])      ## Architecture diff --git a/platform/server/godot_server.cpp b/platform/server/godot_server.cpp index 32bd943ac3..9f22240a80 100644 --- a/platform/server/godot_server.cpp +++ b/platform/server/godot_server.cpp @@ -34,6 +34,9 @@  int main(int argc, char *argv[]) {  	OS_Server os; +	// We must override main when testing is enabled +	TEST_MAIN_OVERRIDE +  	Error err = Main::setup(argv[0], argc - 1, &argv[1]);  	if (err != OK)  		return 255; diff --git a/platform/server/platform_config.h b/platform/server/platform_config.h index bdff93f02b..73136ec81b 100644 --- a/platform/server/platform_config.h +++ b/platform/server/platform_config.h @@ -31,10 +31,19 @@  #if defined(__linux__) || defined(__APPLE__)  #include <alloca.h>  #endif -#if defined(__FreeBSD__) || defined(__OpenBSD__) -#include <stdlib.h> + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) +#include <stdlib.h> // alloca +// FreeBSD and OpenBSD use pthread_set_name_np, while other platforms, +// include NetBSD, use pthread_setname_np. NetBSD's version however requires +// a different format, we handle this directly in thread_posix. +#ifdef __NetBSD__ +#define PTHREAD_NETBSD_SET_NAME +#else  #define PTHREAD_BSD_SET_NAME  #endif +#endif +  #ifdef __APPLE__  #define PTHREAD_RENAME_SELF  #endif diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py index 669bfe6814..a7ca26c16c 100644 --- a/platform/uwp/detect.py +++ b/platform/uwp/detect.py @@ -65,12 +65,14 @@ def configure(env):          env.Append(CCFLAGS=["/MD"])          env.Append(CPPDEFINES=["DEBUG_ENABLED"])          env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) +        env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])      elif env["target"] == "debug":          env.Append(CCFLAGS=["/Zi"])          env.Append(CCFLAGS=["/MDd"]) -        env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Append(CPPDEFINES=["DEBUG_ENABLED"])          env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) +        env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])          env.Append(LINKFLAGS=["/DEBUG"])      ## Compiler configuration @@ -120,7 +122,9 @@ def configure(env):              print("Compiled program architecture will be a x86 executable. (forcing bits=32).")          else:              print( -                "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup." +                "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings" +                " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture" +                " this build is compiled for. You should check your settings/compilation setup."              )              env["bits"] = "32" @@ -160,7 +164,10 @@ def configure(env):      env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/x86/store/references"])      env.Append( -        CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split() +        CCFLAGS=( +            '/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX-' +            " /Zc:forScope /Gd /EHsc /nologo".split() +        )      )      env.Append(CPPDEFINES=["_UNICODE", "UNICODE", ("WINAPI_FAMILY", "WINAPI_FAMILY_APP")])      env.Append(CXXFLAGS=["/ZW"]) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index ede0d7c76b..5679ec3eac 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -961,7 +961,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {  		return true;  	} -	static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total) { +	static Error save_appx_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {  		AppxPackager *packager = (AppxPackager *)p_userdata;  		String dst_path = p_path.replace_first("res://", "game/"); diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp index ee25754704..44ab075816 100644 --- a/platform/uwp/os_uwp.cpp +++ b/platform/uwp/os_uwp.cpp @@ -35,7 +35,6 @@  #include "core/io/marshalls.h"  #include "core/project_settings.h" -#include "drivers/gles2/rasterizer_gles2.h"  #include "drivers/unix/ip_unix.h"  #include "drivers/windows/dir_access_windows.h"  #include "drivers/windows/file_access_windows.h" @@ -297,7 +296,7 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a  void OS_UWP::set_clipboard(const String &p_text) {  	DataPackage ^ clip = ref new DataPackage();  	clip->RequestedOperation = DataPackageOperation::Copy; -	clip->SetText(ref new Platform::String((const wchar_t *)p_text.c_str())); +	clip->SetText(ref new Platform::String((LPCWSTR)(p_text.utf16().get_data())));  	Clipboard::SetContent(clip);  }; @@ -347,8 +346,8 @@ void OS_UWP::finalize_core() {  }  void OS_UWP::alert(const String &p_alert, const String &p_title) { -	Platform::String ^ alert = ref new Platform::String(p_alert.c_str()); -	Platform::String ^ title = ref new Platform::String(p_title.c_str()); +	Platform::String ^ alert = ref new Platform::String((LPCWSTR)(p_alert.utf16().get_data())); +	Platform::String ^ title = ref new Platform::String((LPCWSTR)(p_title.utf16().get_data()));  	MessageDialog ^ msg = ref new MessageDialog(alert, title); @@ -715,7 +714,7 @@ bool OS_UWP::has_virtual_keyboard() const {  	return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;  } -void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) { +void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {  	InputPane ^ pane = InputPane::GetForCurrentView();  	pane->TryShow();  } @@ -739,7 +738,7 @@ static String format_error_message(DWORD id) {  Error OS_UWP::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {  	String full_path = "game/" + p_path; -	p_library_handle = (void *)LoadPackagedLibrary(full_path.c_str(), 0); +	p_library_handle = (void *)LoadPackagedLibrary((LPCWSTR)(full_path.utf16().get_data()), 0);  	ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + full_path + ", error: " + format_error_message(GetLastError()) + ".");  	return OK;  } diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h index c35b634353..892327bac5 100644 --- a/platform/uwp/os_uwp.h +++ b/platform/uwp/os_uwp.h @@ -234,7 +234,7 @@ public:  	virtual bool has_touchscreen_ui_hint() const;  	virtual bool has_virtual_keyboard() const; -	virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1); +	virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);  	virtual void hide_virtual_keyboard();  	virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false); diff --git a/platform/windows/SCsub b/platform/windows/SCsub index daffe59f34..e3f86977a4 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -26,10 +26,10 @@ prog = env.add_program("#bin/godot", common_win + res_obj, PROGSUFFIX=env["PROGS  # Microsoft Visual Studio Project Generation  if env["vsproj"]: -    env.vs_srcs = env.vs_srcs + ["platform/windows/" + res_file] -    env.vs_srcs = env.vs_srcs + ["platform/windows/godot.natvis"] +    env.vs_srcs += ["platform/windows/" + res_file] +    env.vs_srcs += ["platform/windows/godot.natvis"]      for x in common_win: -        env.vs_srcs = env.vs_srcs + ["platform/windows/" + str(x)] +        env.vs_srcs += ["platform/windows/" + str(x)]  if not os.getenv("VCINSTALLDIR"):      if (env["debug_symbols"] == "full" or env["debug_symbols"] == "yes") and env["separate_debug_symbols"]: diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 996d9722f5..02031ef6bb 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -175,7 +175,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {  		msg = proj_settings->get("debug/settings/crash_handler/message");  	} -	fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str()); +	fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data());  	int n = 0;  	do { diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 9f79e92dcb..6b503c1561 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -65,6 +65,7 @@ def get_opts():          # Vista support dropped after EOL due to GH-10243          ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"),          EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")), +        EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("console", "gui")),          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), @@ -128,7 +129,9 @@ def setup_msvc_manual(env):          print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).")      else:          print( -            "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR." +            "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings" +            " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this" +            " build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."          ) @@ -175,13 +178,14 @@ def configure_msvc(env, manual_msvc_config):      """Configure env to work with MSVC"""      # Build type +    if env["tests"]: +        env["windows_subsystem"] = "console"      if env["target"] == "release":          if env["optimize"] == "speed":  # optimize for speed (default)              env.Append(CCFLAGS=["/O2"])          else:  # optimize for size              env.Append(CCFLAGS=["/O1"]) -        env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"])          env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"])          env.Append(LINKFLAGS=["/OPT:REF"]) @@ -191,19 +195,23 @@ def configure_msvc(env, manual_msvc_config):          else:  # optimize for size              env.Append(CCFLAGS=["/O1"])          env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"]) -        env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"])          env.Append(LINKFLAGS=["/OPT:REF"])      elif env["target"] == "debug":          env.AppendUnique(CCFLAGS=["/Z7", "/Od", "/EHsc"]) -        env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED", "D3D_DEBUG_INFO"]) -        env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) +        env.AppendUnique(CPPDEFINES=["DEBUG_ENABLED"])          env.Append(LINKFLAGS=["/DEBUG"])      if env["debug_symbols"] == "full" or env["debug_symbols"] == "yes":          env.AppendUnique(CCFLAGS=["/Z7"])          env.AppendUnique(LINKFLAGS=["/DEBUG"]) +    if env["windows_subsystem"] == "gui": +        env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) +    else: +        env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) +        env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) +      ## Compile/link flags      env.AppendUnique(CCFLAGS=["/MT", "/Gd", "/GR", "/nologo"]) @@ -301,6 +309,9 @@ def configure_mingw(env):      ## Build type +    if env["tests"]: +        env["windows_subsystem"] = "console" +      if env["target"] == "release":          env.Append(CCFLAGS=["-msse2"]) @@ -312,8 +323,6 @@ def configure_mingw(env):          else:  # optimize for size              env.Prepend(CCFLAGS=["-Os"]) -        env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) -          if env["debug_symbols"] == "yes":              env.Prepend(CCFLAGS=["-g1"])          if env["debug_symbols"] == "full": @@ -333,7 +342,13 @@ def configure_mingw(env):      elif env["target"] == "debug":          env.Append(CCFLAGS=["-g3"]) -        env.Append(CPPDEFINES=["DEBUG_ENABLED", "DEBUG_MEMORY_ENABLED"]) +        env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + +    if env["windows_subsystem"] == "gui": +        env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) +    else: +        env.Append(LINKFLAGS=["-Wl,--subsystem,console"]) +        env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"])      ## Compiler configuration @@ -424,7 +439,7 @@ def configure_mingw(env):      else:          env.Append(LIBS=["cfgmgr32"]) -    ## TODO !!! Reenable when OpenGLES Rendering Device is implemented !!! +    ## TODO !!! Re-enable when OpenGLES Rendering Device is implemented !!!      # env.Append(CPPDEFINES=['OPENGL_ENABLED'])      env.Append(LIBS=["opengl32"]) diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 6ee9b6d698..dfbb734ee4 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -29,7 +29,9 @@  /*************************************************************************/  #include "display_server_windows.h" +  #include "core/io/marshalls.h" +#include "core/math/geometry_2d.h"  #include "main/main.h"  #include "os_windows.h"  #include "scene/resources/texture.h" @@ -42,7 +44,7 @@ static String format_error_message(DWORD id) {  	size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,  			nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); -	String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); +	String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size);  	LocalFree(messageBuffer); @@ -78,7 +80,7 @@ String DisplayServerWindows::get_name() const {  }  void DisplayServerWindows::alert(const String &p_alert, const String &p_title) { -	MessageBoxW(nullptr, p_alert.c_str(), p_title.c_str(), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); +	MessageBoxW(nullptr, (LPCWSTR)(p_alert.utf16().get_data()), (LPCWSTR)(p_title.utf16().get_data()), MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);  }  void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { @@ -177,11 +179,12 @@ void DisplayServerWindows::clipboard_set(const String &p_text) {  	}  	EmptyClipboard(); -	HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (text.length() + 1) * sizeof(CharType)); +	Char16String utf16 = text.utf16(); +	HGLOBAL mem = GlobalAlloc(GMEM_MOVEABLE, (utf16.length() + 1) * sizeof(WCHAR));  	ERR_FAIL_COND_MSG(mem == nullptr, "Unable to allocate memory for clipboard contents.");  	LPWSTR lptstrCopy = (LPWSTR)GlobalLock(mem); -	memcpy(lptstrCopy, text.c_str(), (text.length() + 1) * sizeof(CharType)); +	memcpy(lptstrCopy, utf16.get_data(), (utf16.length() + 1) * sizeof(WCHAR));  	GlobalUnlock(mem);  	SetClipboardData(CF_UNICODETEXT, mem); @@ -218,7 +221,7 @@ String DisplayServerWindows::clipboard_get() const {  		if (mem != nullptr) {  			LPWSTR ptr = (LPWSTR)GlobalLock(mem);  			if (ptr != nullptr) { -				ret = String((CharType *)ptr); +				ret = String::utf16((const char16_t *)ptr);  				GlobalUnlock(mem);  			};  		}; @@ -493,15 +496,21 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod  		wd.no_focus = true;  	} -	_update_window_style(window_id); +	return window_id; +} + +void DisplayServerWindows::show_window(WindowID p_id) { +	WindowData &wd = windows[p_id]; + +	if (p_id != MAIN_WINDOW_ID) { +		_update_window_style(p_id); +	} -	ShowWindow(wd.hWnd, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window -	if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) { +	ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window +	if (!wd.no_focus) {  		SetForegroundWindow(wd.hWnd); // Slightly Higher Priority  		SetFocus(wd.hWnd); // Sets Keyboard Focus To  	} - -	return window_id;  }  void DisplayServerWindows::delete_sub_window(WindowID p_window) { @@ -587,7 +596,37 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi  	_THREAD_SAFE_METHOD_  	ERR_FAIL_COND(!windows.has(p_window)); -	SetWindowTextW(windows[p_window].hWnd, p_title.c_str()); +	SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data())); +} + +void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) { +	_THREAD_SAFE_METHOD_ + +	ERR_FAIL_COND(!windows.has(p_window)); +	windows[p_window].mpath = p_region; +	_update_window_mouse_passthrough(p_window); +} + +void DisplayServerWindows::_update_window_mouse_passthrough(WindowID p_window) { +	if (windows[p_window].mpath.size() == 0) { +		SetWindowRgn(windows[p_window].hWnd, nullptr, TRUE); +	} else { +		POINT *points = (POINT *)memalloc(sizeof(POINT) * windows[p_window].mpath.size()); +		for (int i = 0; i < windows[p_window].mpath.size(); i++) { +			if (windows[p_window].borderless) { +				points[i].x = windows[p_window].mpath[i].x; +				points[i].y = windows[p_window].mpath[i].y; +			} else { +				points[i].x = windows[p_window].mpath[i].x + GetSystemMetrics(SM_CXSIZEFRAME); +				points[i].y = windows[p_window].mpath[i].y + GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYCAPTION); +			} +		} + +		HRGN region = CreatePolygonRgn(points, windows[p_window].mpath.size(), ALTERNATE); +		SetWindowRgn(windows[p_window].hWnd, region, TRUE); +		DeleteObject(region); +		memfree(points); +	}  }  int DisplayServerWindows::window_get_current_screen(WindowID p_window) const { @@ -1003,6 +1042,7 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W  		case WINDOW_FLAG_BORDERLESS: {  			wd.borderless = p_enabled;  			_update_window_style(p_window); +			_update_window_mouse_passthrough(p_window);  		} break;  		case WINDOW_FLAG_ALWAYS_ON_TOP: {  			ERR_FAIL_COND_MSG(wd.transient_parent != INVALID_WINDOW_ID && p_enabled, "Transient windows can't become on top"); @@ -1417,13 +1457,13 @@ String DisplayServerWindows::keyboard_get_layout_language(int p_index) const {  	HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));  	GetKeyboardLayoutList(layout_count, layouts); -	wchar_t buf[LOCALE_NAME_MAX_LENGTH]; -	memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); +	WCHAR buf[LOCALE_NAME_MAX_LENGTH]; +	memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));  	LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);  	memfree(layouts); -	return String(buf).substr(0, 2); +	return String::utf16((const char16_t *)buf).substr(0, 2);  }  String _get_full_layout_name_from_registry(HKL p_layout) { @@ -1431,17 +1471,17 @@ String _get_full_layout_name_from_registry(HKL p_layout) {  	String ret;  	HKEY hkey; -	wchar_t layout_text[1024]; -	memset(layout_text, 0, 1024 * sizeof(wchar_t)); +	WCHAR layout_text[1024]; +	memset(layout_text, 0, 1024 * sizeof(WCHAR)); -	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)id.c_str(), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) { +	if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)(id.utf16().get_data()), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {  		return ret;  	}  	DWORD buffer = 1024;  	DWORD vtype = REG_SZ;  	if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) { -		ret = String(layout_text); +		ret = String::utf16((const char16_t *)layout_text);  	}  	RegCloseKey(hkey);  	return ret; @@ -1457,15 +1497,15 @@ String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {  	String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).  	if (ret == String()) { -		wchar_t buf[LOCALE_NAME_MAX_LENGTH]; -		memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t)); +		WCHAR buf[LOCALE_NAME_MAX_LENGTH]; +		memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(WCHAR));  		LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0); -		wchar_t name[1024]; -		memset(name, 0, 1024 * sizeof(wchar_t)); +		WCHAR name[1024]; +		memset(name, 0, 1024 * sizeof(WCHAR));  		GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024); -		ret = String(name); +		ret = String::utf16((const char16_t *)name);  	}  	memfree(layouts); @@ -2026,8 +2066,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA  					Ref<InputEventMouseMotion> mm;  					mm.instance();  					mm->set_window_id(window_id); -					mm->set_control(GetKeyState(VK_CONTROL) != 0); -					mm->set_shift(GetKeyState(VK_SHIFT) != 0); +					mm->set_control(GetKeyState(VK_CONTROL) < 0); +					mm->set_shift(GetKeyState(VK_SHIFT) < 0);  					mm->set_alt(alt_mem);  					mm->set_pressure(windows[window_id].last_pressure); @@ -2169,8 +2209,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA  				mm->set_tilt(Vector2((float)pen_info.tiltX / 90, (float)pen_info.tiltY / 90));  			} -			mm->set_control((wParam & MK_CONTROL) != 0); -			mm->set_shift((wParam & MK_SHIFT) != 0); +			mm->set_control(GetKeyState(VK_CONTROL) < 0); +			mm->set_shift(GetKeyState(VK_SHIFT) < 0);  			mm->set_alt(alt_mem);  			mm->set_button_mask(last_button_state); @@ -2705,7 +2745,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA  		case WM_DROPFILES: {  			HDROP hDropInfo = (HDROP)wParam;  			const int buffsize = 4096; -			wchar_t buf[buffsize]; +			WCHAR buf[buffsize];  			int fcount = DragQueryFileW(hDropInfo, 0xFFFFFFFF, nullptr, 0); @@ -2713,7 +2753,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA  			for (int i = 0; i < fcount; i++) {  				DragQueryFileW(hDropInfo, i, buf, buffsize); -				String file = buf; +				String file = String::utf16((const char16_t *)buf);  				files.push_back(file);  			} @@ -3121,9 +3161,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win  		}  	} -	ShowWindow(windows[MAIN_WINDOW_ID].hWnd, SW_SHOW); // Show The Window -	SetForegroundWindow(windows[MAIN_WINDOW_ID].hWnd); // Slightly Higher Priority -	SetFocus(windows[MAIN_WINDOW_ID].hWnd); // Sets Keyboard Focus To +	show_window(MAIN_WINDOW_ID);  #if defined(VULKAN_ENABLED) diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 725f9697c5..0fca2589ae 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -323,6 +323,8 @@ private:  		HWND hWnd;  		//layered window +		Vector<Vector2> mpath; +  		bool preserve_window_size = false;  		bool pre_fs_valid = false;  		RECT pre_fs_rect; @@ -416,6 +418,7 @@ private:  	void _touch_event(WindowID p_window, bool p_pressed, float p_x, float p_y, int idx);  	void _update_window_style(WindowID p_window, bool p_repaint = true, bool p_maximized = false); +	void _update_window_mouse_passthrough(WindowID p_window);  	void _update_real_mouse_position(WindowID p_window); @@ -460,6 +463,7 @@ public:  	virtual Vector<DisplayServer::WindowID> get_window_list() const;  	virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); +	virtual void show_window(WindowID p_window);  	virtual void delete_sub_window(WindowID p_window);  	virtual WindowID get_window_at_screen_position(const Point2i &p_position) const; @@ -476,6 +480,7 @@ public:  	virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID);  	virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID); +	virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID);  	virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const;  	virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID); diff --git a/platform/windows/godot.natvis b/platform/windows/godot.natvis index 593557cc69..90f0b55d0a 100644 --- a/platform/windows/godot.natvis +++ b/platform/windows/godot.natvis @@ -19,7 +19,7 @@  			</ArrayItems>  		</Expand>  	</Type> -	 +  	<Type Name="List<*>">  		<Expand>  			<Item Name="[size]">_data ? (_data->size_cache) : 0</Item> @@ -62,7 +62,7 @@  		<DisplayString Condition="type == Variant::POOL_COLOR_ARRAY">{*(PoolColorArray *)_data._mem}</DisplayString>  		<StringView Condition="type == Variant::STRING && ((String *)(_data._mem))->_cowdata._ptr">((String *)(_data._mem))->_cowdata._ptr,su</StringView> -		 +  		<Expand>  			<Item Name="[value]" Condition="type == Variant::BOOL">_data._bool</Item>  			<Item Name="[value]" Condition="type == Variant::INT">_data._int</Item> @@ -143,7 +143,7 @@  			<Item Name="alpha">a</Item>  		</Expand>  	</Type> -	 +  	<Type Name="Node" Inheritable="false">  		<Expand>  			<Item Name="Object">(Object*)this</Item> diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 910059a9fc..add559a717 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -146,6 +146,8 @@ int widechar_main(int argc, wchar_t **argv) {  		argv_utf8[i] = wc_to_utf8(argv[i]);  	} +	TEST_MAIN_PARAM_OVERRIDE(argc, argv_utf8) +  	Error err = Main::setup(argv_utf8[0], argc - 1, &argv_utf8[1]);  	if (err != OK) { @@ -186,10 +188,12 @@ int _main() {  	return result;  } -int main(int _argc, char **_argv) { +int main(int argc, char **argv) { +	// override the arguments for the test handler / if symbol is provided +	// TEST_MAIN_OVERRIDE +  	// _argc and _argv are ignored  	// we are going to use the WideChar version of them instead -  #ifdef CRASH_HANDLER_EXCEPTION  	__try {  		return _main(); diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp index 65caee3035..d1454c9096 100644 --- a/platform/windows/joypad_windows.cpp +++ b/platform/windows/joypad_windows.cpp @@ -146,8 +146,8 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {  	if (have_device(instance->guidInstance) || num == -1)  		return false; -	d_joypads[joypad_count] = dinput_gamepad(); -	dinput_gamepad *joy = &d_joypads[joypad_count]; +	d_joypads[num] = dinput_gamepad(); +	dinput_gamepad *joy = &d_joypads[num];  	const DWORD devtype = (instance->dwDevType & 0xFF); @@ -171,7 +171,7 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {  	WORD version = 0;  	sprintf_s(uid, "%04x%04x%04x%04x%04x%04x%04x%04x", type, 0, vendor, 0, product, 0, version, 0); -	id_to_change = joypad_count; +	id_to_change = num;  	slider_count = 0;  	joy->di_joy->SetDataFormat(&c_dfDIJoystick2); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 5b15896b0c..f73516b370 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -84,7 +84,7 @@ static String format_error_message(DWORD id) {  	size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,  			nullptr, id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, nullptr); -	String msg = "Error " + itos(id) + ": " + String(messageBuffer, size); +	String msg = "Error " + itos(id) + ": " + String::utf16((const char16_t *)messageBuffer, size);  	LocalFree(messageBuffer); @@ -107,15 +107,11 @@ void RedirectIOToConsole() {  	// set the screen buffer to be big enough to let us scroll text -	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), - -			&coninfo); +	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);  	coninfo.dwSize.Y = MAX_CONSOLE_LINES; -	SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), - -			coninfo.dwSize); +	SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);  	// redirect unbuffered STDOUT to the console @@ -265,10 +261,10 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han  	DLL_DIRECTORY_COOKIE cookie = nullptr;  	if (p_also_set_library_path && has_dll_directory_api) { -		cookie = add_dll_directory(path.get_base_dir().c_str()); +		cookie = add_dll_directory((LPCWSTR)(path.get_base_dir().utf16().get_data()));  	} -	p_library_handle = (void *)LoadLibraryExW(path.c_str(), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); +	p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(path.utf16().get_data()), nullptr, (p_also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0);  	ERR_FAIL_COND_V_MSG(!p_library_handle, ERR_CANT_OPEN, "Can't open dynamic library: " + p_path + ", error: " + format_error_message(GetLastError()) + ".");  	if (cookie) { @@ -407,7 +403,7 @@ uint64_t OS_Windows::get_ticks_usec() const {  String OS_Windows::_quote_command_line_argument(const String &p_text) const {  	for (int i = 0; i < p_text.size(); i++) { -		CharType c = p_text[i]; +		char32_t c = p_text[i];  		if (c == ' ' || c == '&' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '=' || c == ';' || c == '!' || c == '\'' || c == '+' || c == ',' || c == '`' || c == '~') {  			return "\"" + p_text + "\"";  		} @@ -428,7 +424,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,  		// Note: _wpopen is calling command as "cmd.exe /c argss", instead of executing it directly, add extra quotes around full command, to prevent it from stripping quotes in the command.  		argss = _quote_command_line_argument(argss); -		FILE *f = _wpopen(argss.c_str(), L"r"); +		FILE *f = _wpopen((LPCWSTR)(argss.utf16().get_data()), L"r");  		ERR_FAIL_COND_V(!f, ERR_CANT_OPEN);  		char buf[65535]; @@ -463,13 +459,8 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,  	ZeroMemory(&pi.pi, sizeof(pi.pi));  	LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; -	Vector<CharType> modstr; // Windows wants to change this no idea why. -	modstr.resize(cmdline.size()); -	for (int i = 0; i < cmdline.size(); i++) { -		modstr.write[i] = cmdline[i]; -	} - -	int ret = CreateProcessW(nullptr, modstr.ptrw(), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi); +	Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why. +	int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, 0, NORMAL_PRIORITY_CLASS & CREATE_NO_WINDOW, nullptr, nullptr, si_w, &pi.pi);  	ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK);  	if (p_blocking) { @@ -509,26 +500,26 @@ int OS_Windows::get_process_id() const {  }  Error OS_Windows::set_cwd(const String &p_cwd) { -	if (_wchdir(p_cwd.c_str()) != 0) +	if (_wchdir((LPCWSTR)(p_cwd.utf16().get_data())) != 0)  		return ERR_CANT_OPEN;  	return OK;  }  String OS_Windows::get_executable_path() const { -	wchar_t bufname[4096]; +	WCHAR bufname[4096];  	GetModuleFileNameW(nullptr, bufname, 4096); -	String s = bufname; +	String s = String::utf16((const char16_t *)bufname);  	return s;  }  bool OS_Windows::has_environment(const String &p_var) const {  #ifdef MINGW_ENABLED -	return _wgetenv(p_var.c_str()) != nullptr; +	return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr;  #else -	wchar_t *env; +	WCHAR *env;  	size_t len; -	_wdupenv_s(&env, &len, p_var.c_str()); +	_wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data()));  	const bool has_env = env != nullptr;  	free(env);  	return has_env; @@ -536,16 +527,16 @@ bool OS_Windows::has_environment(const String &p_var) const {  };  String OS_Windows::get_environment(const String &p_var) const { -	wchar_t wval[0x7Fff]; // MSDN says 32767 char is the maximum -	int wlen = GetEnvironmentVariableW(p_var.c_str(), wval, 0x7Fff); +	WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum +	int wlen = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), wval, 0x7fff);  	if (wlen > 0) { -		return wval; +		return String::utf16((const char16_t *)wval);  	}  	return "";  }  bool OS_Windows::set_environment(const String &p_var, const String &p_value) const { -	return (bool)SetEnvironmentVariableW(p_var.c_str(), p_value.c_str()); +	return (bool)SetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), (LPCWSTR)(p_value.utf16().get_data()));  }  String OS_Windows::get_stdin_string(bool p_block) { @@ -558,7 +549,7 @@ String OS_Windows::get_stdin_string(bool p_block) {  }  Error OS_Windows::shell_open(String p_uri) { -	ShellExecuteW(nullptr, nullptr, p_uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +	ShellExecuteW(nullptr, nullptr, (LPCWSTR)(p_uri.utf16().get_data()), nullptr, nullptr, SW_SHOWNORMAL);  	return OK;  } @@ -701,7 +692,7 @@ String OS_Windows::get_system_dir(SystemDir p_dir) const {  	PWSTR szPath;  	HRESULT res = SHGetKnownFolderPath(id, 0, nullptr, &szPath);  	ERR_FAIL_COND_V(res != S_OK, String()); -	String path = String(szPath); +	String path = String::utf16((const char16_t *)szPath);  	CoTaskMemFree(szPath);  	return path;  } @@ -727,7 +718,7 @@ String OS_Windows::get_user_data_dir() const {  String OS_Windows::get_unique_id() const {  	HW_PROFILE_INFO HwProfInfo;  	ERR_FAIL_COND_V(!GetCurrentHwProfile(&HwProfInfo), ""); -	return String(HwProfInfo.szHwProfileGuid); +	return String::utf16((const char16_t *)(HwProfInfo.szHwProfileGuid), HW_PROFILE_GUIDLEN);  }  bool OS_Windows::_check_internal_feature_support(const String &p_feature) { @@ -744,9 +735,11 @@ bool OS_Windows::is_disable_crash_handler() const {  Error OS_Windows::move_to_trash(const String &p_path) {  	SHFILEOPSTRUCTW sf; -	WCHAR *from = new WCHAR[p_path.length() + 2]; -	wcscpy_s(from, p_path.length() + 1, p_path.c_str()); -	from[p_path.length() + 1] = 0; + +	Char16String utf16 = p_path.utf16(); +	WCHAR *from = new WCHAR[utf16.length() + 2]; +	wcscpy_s(from, utf16.length() + 1, (LPCWSTR)(utf16.get_data())); +	from[utf16.length() + 1] = 0;  	sf.hwnd = main_window;  	sf.wFunc = FO_DELETE; diff --git a/platform/windows/platform_config.h b/platform/windows/platform_config.h index 290decac5f..09a16614e0 100644 --- a/platform/windows/platform_config.h +++ b/platform/windows/platform_config.h @@ -29,5 +29,3 @@  /*************************************************************************/  #include <malloc.h> - -#define GLES2_INCLUDE_H "thirdparty/glad/glad/glad.h"  |