diff options
Diffstat (limited to 'platform')
76 files changed, 4035 insertions, 2768 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub index ecc72019e5..d031d14499 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -18,6 +18,7 @@ android_files = [ "jni_utils.cpp", "android_keys_utils.cpp", "display_server_android.cpp", + "plugin/godot_plugin_jni.cpp", "vulkan/vulkan_context_android.cpp", ] diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 15f61db27c..a7a8801bdc 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -161,6 +161,16 @@ int DisplayServerAndroid::screen_get_dpi(int p_screen) const { return godot_io_java->get_screen_dpi(); } +float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const { + GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java(); + if (!godot_io_java) { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + + return godot_io_java->get_screen_refresh_rate(SCREEN_REFRESH_RATE_FALLBACK); +} + bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const { return true; } @@ -253,6 +263,24 @@ DisplayServer::WindowID DisplayServerAndroid::get_window_at_screen_position(cons return MAIN_WINDOW_ID; } +int64_t DisplayServerAndroid::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)((OS_Android *)OS::get_singleton())->get_godot_java()->get_activity(); + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerAndroid::window_attach_instance_id(ObjectID p_instance, DisplayServer::WindowID p_window) { window_attached_instance_id = p_instance; } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index 6aadc7e1a9..23077a6529 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -106,6 +106,7 @@ public: 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_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; 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) override; @@ -124,6 +125,9 @@ public: virtual Vector<WindowID> get_window_list() const override; virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 78155fbef3..df2d32e152 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -416,10 +416,10 @@ String EditorExportPlatformAndroid::get_package_name(const String &p_package) co bool first = true; for (int i = 0; i < basename.length(); i++) { char32_t c = basename[i]; - if (c >= '0' && c <= '9' && first) { + if (is_digit(c) && first) { continue; } - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { + if (is_ascii_alphanumeric_char(c)) { name += String::chr(c); first = false; } @@ -462,19 +462,19 @@ bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, first = true; continue; } - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { + if (!is_ascii_identifier_char(c)) { if (r_error) { *r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c)); } return false; } - if (first && (c >= '0' && c <= '9')) { + if (first && is_digit(c)) { if (r_error) { *r_error = TTR("A digit cannot be the first character in a package segment."); } return false; } - if (first && c == '_') { + if (first && is_underscore(c)) { if (r_error) { *r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c)); } @@ -975,20 +975,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } } - if (tname == "meta-data" && attrname == "name" && value == "xr_mode_metadata_name") { - // Update the meta-data 'android:name' attribute based on the selected XR mode. - if (xr_mode_index == XR_MODE_OPENXR) { - string_table.write[attr_value] = "com.samsung.android.vr.application.mode"; - } - } - - if (tname == "meta-data" && attrname == "value" && value == "xr_mode_metadata_value") { - // Update the meta-data 'android:value' attribute based on the selected XR mode. - if (xr_mode_index == XR_MODE_OPENXR) { - string_table.write[attr_value] = "vr_only"; - } - } - if (tname == "meta-data" && attrname == "name" && value == "xr_hand_tracking_metadata_name") { if (xr_mode_index == XR_MODE_OPENXR && hand_tracking_index > XR_HAND_TRACKING_NONE) { string_table.write[attr_value] = "com.oculus.handtracking.frequency"; diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 287b669fd1..babd8173d0 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -278,7 +278,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ " android:requestLegacyExternalStorage=\"%s\"\n" " tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n" " tools:ignore=\"GoogleAppIndexingWarning\">\n\n" - " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n" " <meta-data tools:node=\"remove\" android:name=\"xr_hand_tracking_metadata_name\" />\n", bool_to_string(p_preset->get("user_data_backup/allow")), bool_to_string(p_preset->get("package/classify_as_game")), @@ -286,8 +285,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ bool_to_string(p_has_storage_permission)); if (uses_xr) { - manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n"; - bool hand_tracking_enabled = (int)(p_preset->get("xr_features/hand_tracking")) > XR_HAND_TRACKING_NONE; if (hand_tracking_enabled) { int hand_tracking_frequency_index = p_preset->get("xr_features/hand_tracking_frequency"); @@ -296,6 +293,8 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_ " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.frequency\" android:value=\"%s\" />\n", hand_tracking_frequency); } + } else { + manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n"; } manifest_application_text += _get_activity_tag(p_preset); manifest_application_text += " </application>\n"; diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 3924aacccd..4c4501729d 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -33,11 +33,6 @@ <!-- The following metadata values are replaced when Godot exports, modifying them here has no effect. --> <!-- Do these changes in the export preset. Adding new ones is fine. --> - <!-- XR mode metadata. This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> - <meta-data - android:name="xr_mode_metadata_name" - android:value="xr_mode_metadata_value" /> - <!-- XR hand tracking metadata --> <!-- This is modified by the exporter based on the selected xr mode. DO NOT CHANGE the values here. --> <!-- Removed at export time if the xr mode is not VR or hand tracking is disabled. --> @@ -45,6 +40,12 @@ android:name="xr_hand_tracking_metadata_name" android:value="xr_hand_tracking_metadata_value"/> + <!-- Supported Meta devices --> + <!-- This is removed by the exporter if the xr mode is not VR. --> + <meta-data + android:name="com.oculus.supportedDevices" + android:value="all" /> + <activity android:name=".GodotApp" android:label="@string/godot_project_name_string" 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 d679fd92c0..b151e7eec1 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -226,6 +226,14 @@ public class GodotIO { return (int)(metrics.density * 160f); } + public double getScreenRefreshRate(double fallback) { + Display display = activity.getWindowManager().getDefaultDisplay(); + if (display != null) { + return display.getRefreshRate(); + } + return fallback; + } + public int[] screenGetUsableRect() { DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); Display display = activity.getWindowManager().getDefaultDisplay(); diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index e0a535f16e..ff0bcf0716 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -53,6 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc _get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;"); _get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;"); _get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); + _get_screen_refresh_rate = p_env->GetMethodID(cls, "getScreenRefreshRate", "(D)D"); _screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"), _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;"); _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V"); @@ -136,6 +137,19 @@ int GodotIOJavaWrapper::get_screen_dpi() { } } +float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) { + if (_get_screen_refresh_rate) { + JNIEnv *env = get_jni_env(); + if (env == nullptr) { + ERR_PRINT("An error occurred while trying to get screen refresh rate."); + return fallback; + } + return (float)env->CallDoubleMethod(godot_io_instance, _get_screen_refresh_rate, (double)fallback); + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return fallback; +} + void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) { if (_screen_get_usable_rect) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index c96abf1101..38a2b710a9 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -51,6 +51,7 @@ private: jmethodID _get_locale = 0; jmethodID _get_model = 0; jmethodID _get_screen_DPI = 0; + jmethodID _get_screen_refresh_rate = 0; jmethodID _screen_get_usable_rect = 0; jmethodID _get_unique_id = 0; jmethodID _show_keyboard = 0; @@ -71,6 +72,7 @@ public: String get_locale(); String get_model(); int get_screen_dpi(); + float get_screen_refresh_rate(float fallback); void screen_get_usable_rect(int (&p_rect_xywh)[4]); String get_unique_id(); bool has_vk(); diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index 2207eec18d..48aeb3d070 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -126,7 +126,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeEmitS env->DeleteLocalRef(j_param); }; - singleton->emit_signal(SNAME(signal_name), args, count); + singleton->emit_signal(StringName(signal_name), args, count); } JNIEXPORT void JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeRegisterGDNativeLibraries(JNIEnv *env, jclass clazz, jobjectArray gdnlib_paths) { diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py index 4e4b0d81c3..f442235e7c 100644 --- a/platform/iphone/detect.py +++ b/platform/iphone/detect.py @@ -120,7 +120,6 @@ def configure(env): ) ) env.Append(CPPDEFINES=["NEED_LONG_INT"]) - env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"]) # Disable exceptions on non-tools (template) builds if not env["tools"]: diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h index de04bc88e3..7441550f67 100644 --- a/platform/iphone/display_server_iphone.h +++ b/platform/iphone/display_server_iphone.h @@ -129,12 +129,15 @@ public: virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Vector<DisplayServer::WindowID> get_window_list() const override; virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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; diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm index 77e1a6078c..9491c9cf90 100644 --- a/platform/iphone/display_server_iphone.mm +++ b/platform/iphone/display_server_iphone.mm @@ -393,6 +393,10 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const { } } +float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const { + return [UIScreen mainScreen].maximumFramesPerSecond; +} + float DisplayServerIPhone::screen_get_scale(int p_screen) const { return [UIScreen mainScreen].nativeScale; } @@ -407,6 +411,24 @@ DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const return MAIN_WINDOW_ID; } +int64_t DisplayServerIPhone::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(p_window != MAIN_WINDOW_ID, 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)AppDelegate.viewController; + } + case WINDOW_VIEW: { + return (int64_t)AppDelegate.viewController.godotView; + } + default: { + return 0; + } + } +} + void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { window_attached_instance_id = p_instance; } diff --git a/platform/iphone/export/export_plugin.cpp b/platform/iphone/export/export_plugin.cpp index ea17f1ac61..122e64d6a1 100644 --- a/platform/iphone/export/export_plugin.cpp +++ b/platform/iphone/export/export_plugin.cpp @@ -43,7 +43,6 @@ void EditorExportPlatformIOS::get_preset_features(const Ref<EditorExportPreset> Vector<EditorExportPlatformIOS::ExportArchitecture> EditorExportPlatformIOS::_get_supported_architectures() { Vector<ExportArchitecture> archs; - archs.push_back(ExportArchitecture("armv7", false)); // Disabled by default, not included in official templates. archs.push_back(ExportArchitecture("arm64", true)); return archs; } @@ -83,7 +82,7 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_store_team_id"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_debug"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "iPhone Developer")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_debug", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Developer"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_debug", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/provisioning_profile_uuid_release"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), "")); @@ -178,6 +177,10 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ "scaleAspectFill", "scaleToFill" }; + String dbg_sign_id = p_preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : p_preset->get("application/code_sign_identity_debug"); + String rel_sign_id = p_preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : p_preset->get("application/code_sign_identity_release"); + bool dbg_manual = !p_preset->get("application/provisioning_profile_uuid_debug").operator String().is_empty() || (dbg_sign_id != "iPhone Developer"); + bool rel_manual = !p_preset->get("application/provisioning_profile_uuid_release").operator String().is_empty() || (rel_sign_id != "iPhone Distribution"); String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -218,13 +221,25 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$provisioning_profile_uuid_release", p_preset->get("application/provisioning_profile_uuid_release")) + "\n"; } else if (lines[i].find("$provisioning_profile_uuid_debug") != -1) { strnew += lines[i].replace("$provisioning_profile_uuid_debug", p_preset->get("application/provisioning_profile_uuid_debug")) + "\n"; + } else if (lines[i].find("$code_sign_style_debug") != -1) { + if (dbg_manual) { + strnew += lines[i].replace("$code_sign_style_debug", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_debug", "Automatic") + "\n"; + } + } else if (lines[i].find("$code_sign_style_release") != -1) { + if (rel_manual) { + strnew += lines[i].replace("$code_sign_style_release", "Manual") + "\n"; + } else { + strnew += lines[i].replace("$code_sign_style_release", "Automatic") + "\n"; + } } else if (lines[i].find("$provisioning_profile_uuid") != -1) { String uuid = p_debug ? p_preset->get("application/provisioning_profile_uuid_debug") : p_preset->get("application/provisioning_profile_uuid_release"); strnew += lines[i].replace("$provisioning_profile_uuid", uuid) + "\n"; } else if (lines[i].find("$code_sign_identity_debug") != -1) { - strnew += lines[i].replace("$code_sign_identity_debug", p_preset->get("application/code_sign_identity_debug")) + "\n"; + strnew += lines[i].replace("$code_sign_identity_debug", dbg_sign_id) + "\n"; } else if (lines[i].find("$code_sign_identity_release") != -1) { - strnew += lines[i].replace("$code_sign_identity_release", p_preset->get("application/code_sign_identity_release")) + "\n"; + strnew += lines[i].replace("$code_sign_identity_release", rel_sign_id) + "\n"; } else if (lines[i].find("$additional_plist_content") != -1) { strnew += lines[i].replace("$additional_plist_content", p_config.plist_content) + "\n"; } else if (lines[i].find("$godot_archs") != -1) { @@ -770,10 +785,18 @@ Error EditorExportPlatformIOS::_codesign(String p_file, void *p_userdata) { if (p_file.ends_with(".dylib")) { CodesignData *data = (CodesignData *)p_userdata; print_line(String("Signing ") + p_file); + + String sign_id; + if (data->debug) { + sign_id = data->preset->get("application/code_sign_identity_debug").operator String().is_empty() ? "iPhone Developer" : data->preset->get("application/code_sign_identity_debug"); + } else { + sign_id = data->preset->get("application/code_sign_identity_release").operator String().is_empty() ? "iPhone Distribution" : data->preset->get("application/code_sign_identity_release"); + } + List<String> codesign_args; codesign_args.push_back("-f"); codesign_args.push_back("-s"); - codesign_args.push_back(data->preset->get(data->debug ? "application/code_sign_identity_debug" : "application/code_sign_identity_release")); + codesign_args.push_back(sign_id); codesign_args.push_back(p_file); return OS::get_singleton()->execute("codesign", codesign_args); } @@ -1680,6 +1703,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p archive_args.push_back("-destination"); archive_args.push_back("generic/platform=iOS"); archive_args.push_back("archive"); + archive_args.push_back("-allowProvisioningUpdates"); archive_args.push_back("-archivePath"); archive_args.push_back(archive_path); String archive_str; diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h index 756bca14dd..93b23f7ee2 100644 --- a/platform/iphone/export/export_plugin.h +++ b/platform/iphone/export/export_plugin.h @@ -130,7 +130,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { for (int i = 0; i < pname.length(); i++) { char32_t c = pname[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { if (r_error) { *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); } diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h index 1c72a26b4a..fcb97fa63a 100644 --- a/platform/iphone/godot_view.h +++ b/platform/iphone/godot_view.h @@ -59,4 +59,9 @@ class String; - (void)stopRendering; - (void)startRendering; +- (void)godotTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; +- (void)godotTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; + @end diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm index b90c10fa84..ae92f32851 100644 --- a/platform/iphone/godot_view.mm +++ b/platform/iphone/godot_view.mm @@ -336,7 +336,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event { +- (void)godotTouchesBegan:(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]]) { @@ -349,7 +349,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesMoved:(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]]) { @@ -363,7 +363,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesEnded:(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]]) { @@ -377,7 +377,7 @@ static const float earth_gravity = 9.80665; } } -- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { +- (void)godotTouchesCancelled:(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]]) { diff --git a/platform/iphone/godot_view_gesture_recognizer.mm b/platform/iphone/godot_view_gesture_recognizer.mm index b50ba5f942..a46c42765a 100644 --- a/platform/iphone/godot_view_gesture_recognizer.mm +++ b/platform/iphone/godot_view_gesture_recognizer.mm @@ -29,6 +29,7 @@ /*************************************************************************/ #import "godot_view_gesture_recognizer.h" +#import "godot_view.h" #include "core/config/project_settings.h" @@ -58,6 +59,10 @@ const CGFloat kGLGestureMovementDistance = 0.5; @implementation GodotViewGestureRecognizer +- (GodotView *)godotView { + return (GodotView *)self.view; +} + - (instancetype)init { self = [super init]; @@ -104,7 +109,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; self.delayTimer = nil; if (self.delayedTouches) { - [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent]; + [self.godotView godotTouchesBegan:self.delayedTouches withEvent:self.delayedEvent]; } self.delayedTouches = nil; @@ -114,6 +119,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan]; [self delayTouches:cleared andEvent:event]; + + [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { @@ -123,8 +130,8 @@ const CGFloat kGLGestureMovementDistance = 0.5; // 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]; + CGPoint from = [touch locationInView:self.godotView]; + CGPoint to = [touch previousLocationInView:self.godotView]; CGFloat xDistance = from.x - to.x; CGFloat yDistance = from.y - to.y; @@ -133,7 +140,7 @@ const CGFloat kGLGestureMovementDistance = 0.5; // 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]; + [self.godotView godotTouchesMoved:cleared withEvent:event]; return; } } @@ -141,26 +148,32 @@ const CGFloat kGLGestureMovementDistance = 0.5; return; } - [self.view touchesMoved:cleared withEvent:event]; + [self.godotView touchesMoved:cleared withEvent:event]; + + [super touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self.delayTimer fire]; NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded]; - [self.view touchesEnded:cleared withEvent:event]; + [self.godotView godotTouchesEnded:cleared withEvent:event]; + + [super touchesEnded:touches withEvent:event]; } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [self.delayTimer fire]; - [self.view touchesCancelled:touches withEvent:event]; -}; + [self.godotView godotTouchesCancelled:touches withEvent:event]; + + [super touchesCancelled:touches withEvent:event]; +} - (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave { NSMutableSet *cleared = [touches mutableCopy]; for (UITouch *touch in touches) { - if (touch.phase != phaseToSave) { + if (touch.view != self.view || touch.phase != phaseToSave) { [cleared removeObject:touch]; } } diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 0c4accccc3..4190b24b8e 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -71,6 +71,9 @@ void JavaScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "create_object", &JavaScript::_create_object_bind, mi); } ClassDB::bind_method(D_METHOD("download_buffer", "buffer", "name", "mime"), &JavaScript::download_buffer, DEFVAL("application/octet-stream")); + ClassDB::bind_method(D_METHOD("pwa_needs_update"), &JavaScript::pwa_needs_update); + ClassDB::bind_method(D_METHOD("pwa_update"), &JavaScript::pwa_update); + ADD_SIGNAL(MethodInfo("pwa_update_available")); } #if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) @@ -102,6 +105,12 @@ Variant JavaScript::_create_object_bind(const Variant **p_args, int p_argcount, } #endif #if !defined(JAVASCRIPT_ENABLED) +bool JavaScript::pwa_needs_update() const { + return false; +} +Error JavaScript::pwa_update() { + return ERR_UNAVAILABLE; +} void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { } #endif diff --git a/platform/javascript/api/javascript_singleton.h b/platform/javascript/api/javascript_singleton.h index 63f1aec624..e93b0a18a1 100644 --- a/platform/javascript/api/javascript_singleton.h +++ b/platform/javascript/api/javascript_singleton.h @@ -59,6 +59,8 @@ public: Ref<JavaScriptObject> create_callback(const Callable &p_callable); Variant _create_object_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error); void download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime = "application/octet-stream"); + bool pwa_needs_update() const; + Error pwa_update(); static JavaScript *get_singleton(); JavaScript(); diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index e7564a599c..a0e1246c55 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -137,7 +137,6 @@ int DisplayServerJavaScript::mouse_button_callback(int p_pressed, int p_button, DisplayServerJavaScript *ds = get_singleton(); Point2 pos(p_x, p_y); - Input::get_singleton()->set_mouse_position(pos); Ref<InputEventMouseButton> ev; ev.instantiate(); ev->set_position(pos); @@ -219,7 +218,6 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double } Point2 pos(p_x, p_y); - Input::get_singleton()->set_mouse_position(pos); Ref<InputEventMouseMotion> ev; ev.instantiate(); dom2godot_mod(ev, p_modifiers); @@ -229,7 +227,6 @@ void DisplayServerJavaScript::mouse_move_callback(double p_x, double p_y, double ev->set_global_position(pos); ev->set_relative(Vector2(p_rel_x, p_rel_y)); - Input::get_singleton()->set_mouse_position(ev->get_position()); ev->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); Input::get_singleton()->parse_input_event(ev); @@ -666,7 +663,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive godot_js_config_canvas_id_get(canvas_id, 256); // Handle contextmenu, webglcontextlost - godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_window_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); + godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, (p_window_mode == WINDOW_MODE_FULLSCREEN || p_window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), OS::get_singleton()->is_hidpi_allowed() ? 1 : 0); // Check if it's windows. swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1; @@ -797,6 +794,10 @@ float DisplayServerJavaScript::screen_get_scale(int p_screen) const { return godot_js_display_pixel_ratio_get(); } +float DisplayServerJavaScript::screen_get_refresh_rate(int p_screen) const { + return SCREEN_REFRESH_RATE_FALLBACK; // Javascript doesn't have much of a need for the screen refresh rate, and there's no native way to do so. +} + Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const { Vector<WindowID> ret; ret.push_back(MAIN_WINDOW_ID); @@ -900,6 +901,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind } window_mode = WINDOW_MODE_WINDOWED; } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { int result = godot_js_display_fullscreen_request(); ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform."); diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 1ae5d68787..b50956d91c 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -139,6 +139,7 @@ public: virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual void virtual_keyboard_show(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) override; virtual void virtual_keyboard_hide() override; diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp index db0d506cdf..d4c198d631 100644 --- a/platform/javascript/export/export_plugin.cpp +++ b/platform/javascript/export/export_plugin.cpp @@ -139,8 +139,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re } if (p_preset->get("progressive_web_app/enabled")) { head_include += "<link rel='manifest' href='" + p_name + ".manifest.json'>\n"; - head_include += "<script type='application/javascript'>window.addEventListener('load', () => {if ('serviceWorker' in navigator) {navigator.serviceWorker.register('" + - p_name + ".service.worker.js');}});</script>\n"; + config["serviceWorker"] = p_name + ".service.worker.js"; } // Replaces HTML string @@ -188,35 +187,46 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c } Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects) { + String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name"); + if (proj_name.is_empty()) { + proj_name = "Godot Game"; + } + // Service worker const String dir = p_path.get_base_dir(); const String name = p_path.get_file().get_basename(); const ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); Map<String, String> replaces; - replaces["@GODOT_VERSION@"] = "1"; - replaces["@GODOT_NAME@"] = name; + replaces["@GODOT_VERSION@"] = String::num_int64(OS::get_singleton()->get_unix_time()) + "|" + String::num_int64(OS::get_singleton()->get_ticks_usec()); + replaces["@GODOT_NAME@"] = proj_name.substr(0, 16); replaces["@GODOT_OFFLINE_PAGE@"] = name + ".offline.html"; - Array files; - replaces["@GODOT_OPT_CACHE@"] = Variant(files).to_json_string(); - files.push_back(name + ".html"); - files.push_back(name + ".js"); - files.push_back(name + ".wasm"); - files.push_back(name + ".pck"); - files.push_back(name + ".offline.html"); + + // Files cached during worker install. + Array cache_files; + cache_files.push_back(name + ".html"); + cache_files.push_back(name + ".js"); + cache_files.push_back(name + ".offline.html"); if (p_preset->get("html/export_icon")) { - files.push_back(name + ".icon.png"); - files.push_back(name + ".apple-touch-icon.png"); + cache_files.push_back(name + ".icon.png"); + cache_files.push_back(name + ".apple-touch-icon.png"); } if (mode == EXPORT_MODE_THREADS) { - files.push_back(name + ".worker.js"); - files.push_back(name + ".audio.worklet.js"); - } else if (mode == EXPORT_MODE_GDNATIVE) { - files.push_back(name + ".side.wasm"); + cache_files.push_back(name + ".worker.js"); + cache_files.push_back(name + ".audio.worklet.js"); + } + replaces["@GODOT_CACHE@"] = Variant(cache_files).to_json_string(); + + // Heavy files that are cached on demand. + Array opt_cache_files; + opt_cache_files.push_back(name + ".wasm"); + opt_cache_files.push_back(name + ".pck"); + if (mode == EXPORT_MODE_GDNATIVE) { + opt_cache_files.push_back(name + ".side.wasm"); for (int i = 0; i < p_shared_objects.size(); i++) { - files.push_back(p_shared_objects[i].path.get_file()); + opt_cache_files.push_back(p_shared_objects[i].path.get_file()); } } - replaces["@GODOT_CACHE@"] = Variant(files).to_json_string(); + replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); const String sw_path = dir.plus_file(name + ".service.worker.js"); Vector<uint8_t> sw; @@ -256,10 +266,6 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref<EditorExportPreset> & const int orientation = CLAMP(int(p_preset->get("progressive_web_app/orientation")), 0, 3); Dictionary manifest; - String proj_name = ProjectSettings::get_singleton()->get_setting("application/config/name"); - if (proj_name.is_empty()) { - proj_name = "Godot Game"; - } manifest["name"] = proj_name; manifest["start_url"] = "./" + name + ".html"; manifest["display"] = String::utf8(modes[display]); @@ -658,7 +664,7 @@ EditorExportPlatformJavaScript::EditorExportPlatformJavaScript() { Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme(); if (theme.is_valid()) { - stop_icon = theme->get_icon("Stop", "EditorIcons"); + stop_icon = theme->get_icon(SNAME("Stop"), SNAME("EditorIcons")); } else { stop_icon.instantiate(); } diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h index c55a881911..278e317430 100644 --- a/platform/javascript/export/export_plugin.h +++ b/platform/javascript/export/export_plugin.h @@ -87,7 +87,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { icon.instantiate(); const String icon_path = String(GLOBAL_GET("application/config/icon")).strip_edges(); if (icon_path.is_empty() || ImageLoader::load_image(icon_path, icon) != OK) { - return EditorNode::get_singleton()->get_editor_theme()->get_icon("DefaultProjectIcon", "EditorIcons")->get_image(); + return EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("DefaultProjectIcon"), SNAME("EditorIcons"))->get_image(); } return icon; } diff --git a/platform/javascript/godot_js.h b/platform/javascript/godot_js.h index 15de8b5dc7..2cb5c3025c 100644 --- a/platform/javascript/godot_js.h +++ b/platform/javascript/godot_js.h @@ -49,6 +49,8 @@ extern void godot_js_os_fs_sync(void (*p_callback)()); extern int godot_js_os_execute(const char *p_json); extern void godot_js_os_shell_open(const char *p_uri); extern int godot_js_os_hw_concurrency_get(); +extern int godot_js_pwa_cb(void (*p_callback)()); +extern int godot_js_pwa_update(); // Input extern void godot_js_input_mouse_button_cb(int (*p_callback)(int p_pressed, int p_button, double p_x, double p_y, int p_modifiers)); diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp index 45aa68ce7c..c946302862 100644 --- a/platform/javascript/http_client_javascript.cpp +++ b/platform/javascript/http_client_javascript.cpp @@ -87,6 +87,11 @@ Error HTTPClientJavaScript::request(Method p_method, const String &p_url, const ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED); ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER); + Error err = verify_headers(p_headers); + if (err) { + return err; + } + String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url; Vector<CharString> keeper; Vector<const char *> c_strings; diff --git a/platform/javascript/javascript_singleton.cpp b/platform/javascript/javascript_singleton.cpp index b10e8007c0..77858bff01 100644 --- a/platform/javascript/javascript_singleton.cpp +++ b/platform/javascript/javascript_singleton.cpp @@ -30,6 +30,7 @@ #include "api/javascript_singleton.h" #include "emscripten.h" +#include "os_javascript.h" extern "C" { extern void godot_js_os_download_buffer(const uint8_t *p_buf, int p_buf_size, const char *p_name, const char *p_mime); @@ -355,3 +356,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { void JavaScript::download_buffer(Vector<uint8_t> p_arr, const String &p_name, const String &p_mime) { godot_js_os_download_buffer(p_arr.ptr(), p_arr.size(), p_name.utf8().get_data(), p_mime.utf8().get_data()); } + +bool JavaScript::pwa_needs_update() const { + return OS_JavaScript::get_singleton()->pwa_needs_update(); +} +Error JavaScript::pwa_update() { + return OS_JavaScript::get_singleton()->pwa_update(); +} diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js index ba61b14eb7..2e5e1ed0d1 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -107,6 +107,13 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- */ experimentalVK: false, /** + * The progressive web app service worker to install. + * @memberof EngineConfig + * @default + * @type {string} + */ + serviceWorker: '', + /** * @ignore * @type {Array.<string>} */ @@ -225,6 +232,8 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- */ Config.prototype.update = function (opts) { const config = opts || {}; + // NOTE: We must explicitly pass the default, accessing it via + // the key will fail due to closure compiler renames. function parse(key, def) { if (typeof (config[key]) === 'undefined') { return def; @@ -247,6 +256,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- this.persistentDrops = parse('persistentDrops', this.persistentDrops); this.experimentalVK = parse('experimentalVK', this.experimentalVK); this.focusCanvas = parse('focusCanvas', this.focusCanvas); + this.serviceWorker = parse('serviceWorker', this.serviceWorker); this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); this.fileSizes = parse('fileSizes', this.fileSizes); this.args = parse('args', this.args); diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js index 17a8df9e29..d2ba595083 100644 --- a/platform/javascript/js/engine/engine.js +++ b/platform/javascript/js/engine/engine.js @@ -189,6 +189,9 @@ const Engine = (function () { preloader.preloadedFiles.length = 0; // Clear memory me.rtenv['callMain'](me.config.args); initPromise = null; + if (me.config.serviceWorker && 'serviceWorker' in navigator) { + navigator.serviceWorker.register(me.config.serviceWorker); + } resolve(); }); }); diff --git a/platform/javascript/js/libs/library_godot_input.js b/platform/javascript/js/libs/library_godot_input.js index 7a4d0d8126..1e64c260f8 100644 --- a/platform/javascript/js/libs/library_godot_input.js +++ b/platform/javascript/js/libs/library_godot_input.js @@ -87,7 +87,7 @@ const GodotInputGamepads = { }, init: function (onchange) { - GodotEventListeners.samples = []; + GodotInputGamepads.samples = []; function add(pad) { const guid = GodotInputGamepads.get_guid(pad); const c_id = GodotRuntime.allocString(pad.id); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 662d215443..12d06a8d51 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -368,3 +368,58 @@ const GodotEventListeners = { }, }; mergeInto(LibraryManager.library, GodotEventListeners); + +const GodotPWA = { + + $GodotPWA__deps: ['$GodotRuntime', '$GodotEventListeners'], + $GodotPWA: { + hasUpdate: false, + + updateState: function (cb, reg) { + if (!reg) { + return; + } + if (!reg.active) { + return; + } + if (reg.waiting) { + GodotPWA.hasUpdate = true; + cb(); + } + GodotEventListeners.add(reg, 'updatefound', function () { + const installing = reg.installing; + GodotEventListeners.add(installing, 'statechange', function () { + if (installing.state === 'installed') { + GodotPWA.hasUpdate = true; + cb(); + } + }); + }); + }, + }, + + godot_js_pwa_cb__sig: 'vi', + godot_js_pwa_cb: function (p_update_cb) { + if ('serviceWorker' in navigator) { + const cb = GodotRuntime.get_func(p_update_cb); + navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb)); + } + }, + + godot_js_pwa_update__sig: 'i', + godot_js_pwa_update: function () { + if ('serviceWorker' in navigator && GodotPWA.hasUpdate) { + navigator.serviceWorker.getRegistration().then(function (reg) { + if (!reg || !reg.waiting) { + return; + } + reg.waiting.postMessage('update'); + }); + return 0; + } + return 1; + }, +}; + +autoAddDeps(GodotPWA, '$GodotPWA'); +mergeInto(LibraryManager.library, GodotPWA); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index 39eea13ca1..de5ca44f9d 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -45,6 +45,7 @@ #include <emscripten.h> #include <stdlib.h> +#include "api/javascript_singleton.h" #include "godot_js.h" void OS_JavaScript::alert(const String &p_alert, const String &p_title) { @@ -203,6 +204,19 @@ void OS_JavaScript::file_access_close_callback(const String &p_file, int p_flags } } +void OS_JavaScript::update_pwa_state_callback() { + if (OS_JavaScript::get_singleton()) { + OS_JavaScript::get_singleton()->pwa_is_waiting = true; + } + if (JavaScript::get_singleton()) { + JavaScript::get_singleton()->emit_signal("pwa_update_available"); + } +} + +Error OS_JavaScript::pwa_update() { + return godot_js_pwa_update() ? FAILED : OK; +} + bool OS_JavaScript::is_userfs_persistent() const { return idb_available; } @@ -226,6 +240,8 @@ OS_JavaScript::OS_JavaScript() { godot_js_config_locale_get(locale_ptr, 16); setenv("LANG", locale_ptr, true); + godot_js_pwa_cb(&OS_JavaScript::update_pwa_state_callback); + if (AudioDriverJavaScript::is_available()) { #ifdef NO_THREADS audio_drivers.push_back(memnew(AudioDriverScriptProcessor)); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 3404fe9dcf..9e272f9aa1 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -45,11 +45,13 @@ class OS_JavaScript : public OS_Unix { bool idb_is_syncing = false; bool idb_available = false; bool idb_needs_sync = false; + bool pwa_is_waiting = false; static void main_loop_callback(); static void file_access_close_callback(const String &p_file, int p_flags); static void fs_sync_callback(); + static void update_pwa_state_callback(); protected: void initialize() override; @@ -65,6 +67,9 @@ public: // Override return type to make writing static callbacks less tedious. static OS_JavaScript *get_singleton(); + bool pwa_needs_update() const { return pwa_is_waiting; } + Error pwa_update(); + void initialize_joypads() override; MainLoop *get_main_loop() const override; diff --git a/platform/linuxbsd/crash_handler_linuxbsd.cpp b/platform/linuxbsd/crash_handler_linuxbsd.cpp index e9369fefdd..b4ec7924f6 100644 --- a/platform/linuxbsd/crash_handler_linuxbsd.cpp +++ b/platform/linuxbsd/crash_handler_linuxbsd.cpp @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #ifdef DEBUG_ENABLED @@ -71,10 +70,10 @@ static void handle_crash(int sig) { } // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index 213553ab30..86c3534fc9 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -33,6 +33,7 @@ #ifdef X11_ENABLED #include "core/config/project_settings.h" +#include "core/math/math_funcs.h" #include "core/string/print_string.h" #include "core/string/ustring.h" #include "detect_prime_x11.h" @@ -323,20 +324,21 @@ void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { if (mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { //flush pending motion events _flush_mouse_motion(); - WindowData &main_window = windows[MAIN_WINDOW_ID]; + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &window = windows[window_id]; if (XGrabPointer( - x11_display, main_window.x11_window, True, + x11_display, window.x11_window, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, - GrabModeAsync, GrabModeAsync, windows[MAIN_WINDOW_ID].x11_window, None, CurrentTime) != GrabSuccess) { + GrabModeAsync, GrabModeAsync, window.x11_window, None, CurrentTime) != GrabSuccess) { ERR_PRINT("NO GRAB"); } if (mouse_mode == MOUSE_MODE_CAPTURED) { - center.x = main_window.size.width / 2; - center.y = main_window.size.height / 2; + center.x = window.size.width / 2; + center.y = window.size.height / 2; - XWarpPointer(x11_display, None, main_window.x11_window, + XWarpPointer(x11_display, None, window.x11_window, 0, 0, 0, 0, (int)center.x, (int)center.y); Input::get_singleton()->set_mouse_position(center); @@ -358,7 +360,8 @@ void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { if (mouse_mode == MOUSE_MODE_CAPTURED) { last_mouse_pos = p_to; } else { - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + XWarpPointer(x11_display, None, windows[window_id].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } @@ -1052,6 +1055,67 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const { return 96; } +float DisplayServerX11::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + //invalid screen? + ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK); + + //Use xrandr to get screen refresh rate. + if (xrandr_ext_ok) { + XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window); + if (screen_info) { + RRMode current_mode = 0; + xrr_monitor_info *monitors = nullptr; + + if (xrr_get_monitors) { + int count = 0; + monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count); + ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK); + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + + bool found_active_mode = false; + for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting. + XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]); + if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue. + continue; + } + + if (monitor_info->mode != None) { + current_mode = monitor_info->mode; + found_active_mode = true; + break; + } + } + + if (found_active_mode) { + for (int mode = 0; mode < screen_info->nmode; mode++) { + XRRModeInfo m_info = screen_info->modes[mode]; + if (m_info.id == current_mode) { + // Snap to nearest 0.01 to stay consistent with other platforms. + return Math::snapped((float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal), 0.01); + } + } + } + + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occurred. + return SCREEN_REFRESH_RATE_FALLBACK; + } else { + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; + } + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + bool DisplayServerX11::screen_is_touchscreen(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -1154,6 +1218,24 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) { windows.erase(p_id); } +int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return (int64_t)x11_display; + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].x11_window; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerX11::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -1314,8 +1396,9 @@ int DisplayServerX11::window_get_current_screen(WindowID p_window) const { void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_id) { #if defined(GLES3_ENABLED) - if (gl_manager) + if (gl_manager) { gl_manager->window_make_current(p_window_id); + } #endif } @@ -1752,7 +1835,7 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { Hints hints; Atom property; hints.flags = 2; - hints.decorations = window_get_flag(WINDOW_FLAG_BORDERLESS, p_window) ? 0 : 1; + hints.decorations = wd.borderless ? 0 : 1; property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True); if (property != None) { XChangeProperty(x11_display, wd.x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5); @@ -1804,6 +1887,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { //Remove full-screen wd.fullscreen = false; @@ -1856,6 +1940,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { wd.last_position_before_fs = wd.position; @@ -2396,7 +2481,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const { Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod); KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0); - if (xkeysym >= 'a' && xkeysym <= 'z') { + if (is_ascii_lower_case(xkeysym)) { xkeysym -= ('a' - 'A'); } @@ -3334,7 +3419,7 @@ void DisplayServerX11::process_events() { 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]; - + last_focused_window = window_id; wd.focused = true; if (wd.xic) { @@ -3534,9 +3619,9 @@ void DisplayServerX11::process_events() { // The X11 API requires filtering one-by-one through the motion // notify events, in order to figure out which event is the one // generated by warping the mouse pointer. - + WindowID focused_window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; while (true) { - if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[MAIN_WINDOW_ID].size.width / 2 && event.xmotion.y == windows[MAIN_WINDOW_ID].size.height / 2) { + if (mouse_mode == MOUSE_MODE_CAPTURED && event.xmotion.x == windows[focused_window_id].size.width / 2 && event.xmotion.y == windows[focused_window_id].size.height / 2) { //this is likely the warp event since it was warped here center = Vector2(event.xmotion.x, event.xmotion.y); break; @@ -3611,9 +3696,8 @@ void DisplayServerX11::process_events() { // Reset to prevent lingering motion xi.relative_motion.x = 0; xi.relative_motion.y = 0; - if (mouse_mode == MOUSE_MODE_CAPTURED) { - pos = Point2i(windows[MAIN_WINDOW_ID].size.width / 2, windows[MAIN_WINDOW_ID].size.height / 2); + pos = Point2i(windows[focused_window_id].size.width / 2, windows[focused_window_id].size.height / 2); } Ref<InputEventMouseMotion> mm; @@ -3631,7 +3715,6 @@ void DisplayServerX11::process_events() { mm->set_button_mask((MouseButton)mouse_get_button_state()); mm->set_position(pos); mm->set_global_position(pos); - Input::get_singleton()->set_mouse_position(pos); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); mm->set_relative(rel); diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 8929f528d6..2d07361deb 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -143,7 +143,7 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; - bool focused = false; + bool focused = true; bool minimized = false; unsigned int focus_order = 0; @@ -151,6 +151,8 @@ class DisplayServerX11 : public DisplayServer { Map<WindowID, WindowData> windows; + WindowID last_focused_window = INVALID_WINDOW_ID; + WindowID window_id_counter = MAIN_WINDOW_ID; WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect); @@ -301,6 +303,7 @@ public: 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_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; #if defined(DBUS_ENABLED) @@ -316,6 +319,8 @@ public: virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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; diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp index 1721d0e0b3..d3fb1d6705 100644 --- a/platform/linuxbsd/gl_manager_x11.cpp +++ b/platform/linuxbsd/gl_manager_x11.cpp @@ -68,8 +68,9 @@ static int ctxErrorHandler(Display *dpy, XErrorEvent *ev) { int GLManager_X11::_find_or_create_display(Display *p_x11_display) { for (unsigned int n = 0; n < _displays.size(); n++) { const GLDisplay &d = _displays[n]; - if (d.x11_display == p_x11_display) + if (d.x11_display == p_x11_display) { return n; + } } // create @@ -82,8 +83,7 @@ int GLManager_X11::_find_or_create_display(Display *p_x11_display) { GLDisplay &d = _displays[new_display_id]; d.context = memnew(GLManager_X11_Private); - ; - d.context->glx_context = 0; + d.context->glx_context = nullptr; //Error err = _create_context(d); _create_context(d); @@ -124,7 +124,7 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { }; int fbcount; - GLXFBConfig fbconfig = 0; + GLXFBConfig fbconfig = nullptr; XVisualInfo *vi = nullptr; gl_display.x_swa.event_mask = StructureNotifyMask; @@ -137,8 +137,9 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { for (int i = 0; i < fbcount; i++) { vi = (XVisualInfo *)glXGetVisualFromFBConfig(x11_display, fbc[i]); - if (!vi) + if (!vi) { continue; + } XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi->visual); if (!pict_format) { @@ -262,22 +263,26 @@ void GLManager_X11::window_destroy(DisplayServer::WindowID p_window_id) { } void GLManager_X11::release_current() { - if (!_current_window) + if (!_current_window) { return; + } glXMakeCurrent(_x_windisp.x11_display, None, nullptr); } void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) + if (p_window_id == -1) { return; + } GLWindow &win = _windows[p_window_id]; - if (!win.in_use) + if (!win.in_use) { return; + } // noop - if (&win == _current_window) + if (&win == _current_window) { return; + } const GLDisplay &disp = get_display(win.gldisplay_id); @@ -287,8 +292,9 @@ void GLManager_X11::window_make_current(DisplayServer::WindowID p_window_id) { } void GLManager_X11::make_current() { - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -301,8 +307,9 @@ void GLManager_X11::swap_buffers() { // NO NEED TO CALL SWAP BUFFERS for each window... // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml - if (!_current_window) + if (!_current_window) { return; + } if (!_current_window->in_use) { WARN_PRINT("current window not in use!"); return; @@ -335,19 +342,23 @@ void GLManager_X11::set_use_vsync(bool p_use) { } // we need an active window to get a display to set the vsync - if (!_current_window) + if (!_current_window) { return; + } const GLDisplay &disp = get_current_display(); if (!setup) { setup = true; String extensions = glXQueryExtensionsString(disp.x11_display, DefaultScreen(disp.x11_display)); - if (extensions.find("GLX_EXT_swap_control") != -1) + if (extensions.find("GLX_EXT_swap_control") != -1) { glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalEXT"); - if (extensions.find("GLX_MESA_swap_control") != -1) + } + if (extensions.find("GLX_MESA_swap_control") != -1) { glXSwapIntervalMESA = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalMESA"); - if (extensions.find("GLX_SGI_swap_control") != -1) + } + if (extensions.find("GLX_SGI_swap_control") != -1) { glXSwapIntervalSGI = (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddressARB((const GLubyte *)"glXSwapIntervalSGI"); + } } int val = p_use ? 1 : 0; if (glXSwapIntervalMESA) { @@ -357,8 +368,9 @@ void GLManager_X11::set_use_vsync(bool p_use) { } else if (glXSwapIntervalEXT) { GLXDrawable drawable = glXGetCurrentDrawable(); glXSwapIntervalEXT(disp.x11_display, drawable, val); - } else + } else { return; + } use_vsync = p_use; } diff --git a/platform/linuxbsd/joypad_linux.cpp b/platform/linuxbsd/joypad_linux.cpp index 5eda42fea6..8e963238e3 100644 --- a/platform/linuxbsd/joypad_linux.cpp +++ b/platform/linuxbsd/joypad_linux.cpp @@ -333,8 +333,9 @@ void JoypadLinux::open_joypad(const char *p_path) { } // 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))) { + bool has_abs_left = (test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit)); + bool has_abs_right = (test_bit(ABS_RX, absbit) && test_bit(ABS_RY, absbit)); + if (!(test_bit(EV_KEY, evbit) && test_bit(EV_ABS, evbit) && (has_abs_left || has_abs_right))) { close(fd); return; } diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index b5f127bb16..e95a865636 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -448,7 +448,7 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { // Create needed directories for decided trash can location. { - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Error err = dir_access->make_dir_recursive(trash_path); // Issue an error if trash can is not created properly. @@ -457,7 +457,6 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/files"); err = dir_access->make_dir_recursive(trash_path + "/info"); ERR_FAIL_COND_V_MSG(err != OK, err, "Could not create the trash path \"" + trash_path + "\"/info"); - memdelete(dir_access); } // The trash can is successfully created, now we check that we don't exceed our file name length limit. @@ -497,16 +496,15 @@ Error OS_LinuxBSD::move_to_trash(const String &p_path) { String trash_info = "[Trash Info]\nPath=" + p_path.uri_encode() + "\nDeletionDate=" + timestamp + "\n"; { Error err; - FileAccess *file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); + FileAccessRef file = FileAccess::open(trash_path + "/info/" + file_name + ".trashinfo", FileAccess::WRITE, &err); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't create trashinfo file:" + trash_path + "/info/" + file_name + ".trashinfo"); file->store_string(trash_info); file->close(); // Rename our resource before moving it to the trash can. - DirAccess *dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); err = dir_access->rename(p_path, p_path.get_base_dir() + "/" + file_name); ERR_FAIL_COND_V_MSG(err != OK, err, "Can't rename file \"" + p_path + "\""); - memdelete(dir_access); } // Move the given resource to the trash can. diff --git a/platform/osx/SCsub b/platform/osx/SCsub index 8ba106d1c2..d72a75af04 100644 --- a/platform/osx/SCsub +++ b/platform/osx/SCsub @@ -6,14 +6,21 @@ from platform_methods import run_in_subprocess import platform_osx_builders files = [ - "crash_handler_osx.mm", "os_osx.mm", + "godot_application.mm", + "godot_application_delegate.mm", + "crash_handler_osx.mm", + "osx_terminal_logger.mm", "display_server_osx.mm", + "godot_content_view.mm", + "godot_window_delegate.mm", + "godot_window.mm", + "key_mapping_osx.mm", "godot_main_osx.mm", "dir_access_osx.mm", "joypad_osx.cpp", "vulkan_context_osx.mm", - "gl_manager_osx.mm", + "gl_manager_osx_legacy.mm", ] prog = env.add_program("#bin/godot", files) diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm index 16be941308..3e640b3bf3 100644 --- a/platform/osx/crash_handler_osx.mm +++ b/platform/osx/crash_handler_osx.mm @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #include <string.h> @@ -94,10 +93,10 @@ static void handle_crash(int sig) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH); // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); char **strings = backtrace_symbols(bt_buffer, size); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index c67791b340..0ff93bedb4 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -78,14 +78,16 @@ def configure(env): env["osxcross"] = True if env["arch"] == "arm64": - print("Building for macOS 10.15+, platform arm64.") - env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"]) - env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15"]) + print("Building for macOS 11.0+, platform arm64.") + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=11.0"]) else: print("Building for macOS 10.12+, platform x86_64.") env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(CCFLAGS=["-fobjc-arc"]) + if not "osxcross" in env: # regular native build if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") @@ -188,6 +190,8 @@ def configure(env): env.Append(CCFLAGS=["-Wno-deprecated-declarations"]) # Disable deprecation warnings env.Append(LINKFLAGS=["-framework", "OpenGL"]) + env.Append(LINKFLAGS=["-rpath", "@executable_path/../Frameworks", "-rpath", "@executable_path"]) + if env["vulkan"]: env.Append(CPPDEFINES=["VULKAN_ENABLED"]) env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "QuartzCore", "-framework", "IOSurface"]) diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index d609a84e50..eaf03d298e 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -37,13 +37,13 @@ #include "servers/display_server.h" #if defined(GLES3_ENABLED) -#include "gl_manager_osx.h" -#endif +#include "gl_manager_osx_legacy.h" +#endif // GLES3_ENABLED #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_device_vulkan.h" #include "platform/osx/vulkan_context_osx.h" -#endif +#endif // VULKAN_ENABLED #include <AppKit/AppKit.h> #include <AppKit/NSCursor.h> @@ -59,27 +59,8 @@ class DisplayServerOSX : public DisplayServer { _THREAD_SAFE_CLASS_ public: - void _send_event(NSEvent *p_event); - NSMenu *_get_dock_menu() const; - void _menu_callback(id p_sender); - -#if defined(GLES3_ENABLED) - GLManager_OSX *gl_manager = nullptr; -#endif -#if defined(VULKAN_ENABLED) - VulkanContextOSX *context_vulkan = nullptr; - RenderingDeviceVulkan *rendering_device_vulkan = nullptr; -#endif - - const NSMenu *_get_menu_root(const String &p_menu_root) const; - NSMenu *_get_menu_root(const String &p_menu_root); - - NSMenu *apple_menu = nullptr; - NSMenu *dock_menu = nullptr; - Map<String, NSMenu *> submenu; - struct KeyEvent { - WindowID window_id; + WindowID window_id = INVALID_WINDOW_ID; unsigned int osx_state = false; bool pressed = false; bool echo = false; @@ -89,20 +70,6 @@ public: uint32_t unicode = 0; }; - struct WarpEvent { - NSTimeInterval timestamp; - NSPoint delta; - }; - - List<WarpEvent> warp_events; - NSTimeInterval last_warp = 0; - bool ignore_warp = false; - - float display_max_scale = 1.f; - - Vector<KeyEvent> key_event_buffer; - int key_event_pos; - struct WindowData { id window_delegate; id window_object; @@ -116,8 +83,6 @@ public: Size2i max_size; Size2i size; - bool mouse_down_control = false; - bool im_active = false; Size2i im_position; @@ -140,47 +105,102 @@ public: bool no_focus = false; }; +private: +#if defined(GLES3_ENABLED) + GLManager_OSX *gl_manager = nullptr; +#endif +#if defined(VULKAN_ENABLED) + VulkanContextOSX *context_vulkan = nullptr; + RenderingDeviceVulkan *rendering_device_vulkan = nullptr; +#endif + String rendering_driver; + + NSMenu *apple_menu = nullptr; + NSMenu *dock_menu = nullptr; + Map<String, NSMenu *> submenu; + + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + List<WarpEvent> warp_events; + NSTimeInterval last_warp = 0; + bool ignore_warp = false; + + Vector<KeyEvent> key_event_buffer; + int key_event_pos = 0; + Point2i im_selection; String im_text; - Map<WindowID, WindowData> windows; + CGEventSourceRef event_source; + MouseMode mouse_mode = MOUSE_MODE_VISIBLE; + MouseButton last_button_state = MouseButton::NONE; + + bool drop_events = false; + bool in_dispatch_input_event = false; + + struct LayoutInfo { + String name; + String code; + }; + Vector<LayoutInfo> kbd_layouts; + int current_layout = 0; + bool keyboard_layout_dirty = true; + WindowID last_focused_window = INVALID_WINDOW_ID; WindowID window_id_counter = MAIN_WINDOW_ID; + float display_max_scale = 1.f; + Point2i origin; + bool displays_arrangement_dirty = true; - WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); - void _update_window(WindowData p_wd); - void _send_window_event(const WindowData &wd, WindowEvent p_event); - static void _dispatch_input_events(const Ref<InputEvent> &p_event); - void _dispatch_input_event(const Ref<InputEvent> &p_event); - WindowID _find_window_id(id p_window); + CursorShape cursor_shape = CURSOR_ARROW; + NSCursor *cursors[CURSOR_MAX]; + Map<CursorShape, Vector<Variant>> cursors_cache; + + Map<WindowID, WindowData> windows; + const NSMenu *_get_menu_root(const String &p_menu_root) const; + NSMenu *_get_menu_root(const String &p_menu_root); + + WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect); + void _update_window_style(WindowData p_wd); void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); + void _update_displays_arrangement(); Point2i _get_screens_origin() const; Point2i _get_native_screen_position(int p_screen) const; + static void _displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info); + static void _dispatch_input_events(const Ref<InputEvent> &p_event); + void _dispatch_input_event(const Ref<InputEvent> &p_event); void _push_input(const Ref<InputEvent> &p_event); void _process_key_events(); - void _release_pressed_events(); + void _update_keyboard_layouts(); + static void _keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info); - String rendering_driver; + static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); - id autoreleasePool; - CGEventSourceRef eventSource; +public: + NSMenu *get_dock_menu() const; + void menu_callback(id p_sender); - CursorShape cursor_shape; - NSCursor *cursors[CURSOR_MAX]; - Map<CursorShape, Vector<Variant>> cursors_cache; + bool has_window(WindowID p_window) const; + WindowData &get_window(WindowID p_window); - MouseMode mouse_mode; - Point2i last_mouse_pos; - MouseButton last_button_state = MouseButton::NONE; + void send_event(NSEvent *p_event); + void send_window_event(const WindowData &p_wd, WindowEvent p_event); + void release_pressed_events(); + void get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) const; + void update_mouse_pos(WindowData &p_wd, NSPoint p_location_in_window); + void push_to_key_event_buffer(const KeyEvent &p_event); + void update_im_text(const Point2i &p_selection, const String &p_text); + void set_last_focused_window(WindowID p_window); - bool window_focused; - bool drop_events; - bool in_dispatch_input_event = false; + void window_update(WindowID p_window); + void window_destroy(WindowID p_window); + void window_resize(WindowID p_window, int p_width, int p_height); -public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; @@ -214,8 +234,10 @@ public: virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; + bool update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp); virtual void mouse_warp_to_position(const Point2i &p_to) override; virtual Point2i mouse_get_position() const override; + void mouse_set_button_state(MouseButton p_state); virtual MouseButton mouse_get_button_state() const override; virtual void clipboard_set(const String &p_text) override; @@ -228,6 +250,7 @@ public: virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_max_scale() const override; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Vector<int> get_window_list() const override; @@ -282,6 +305,8 @@ public: virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) 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 gl_window_make_current(DisplayServer::WindowID p_window_id) override; @@ -292,6 +317,7 @@ public: virtual Point2i ime_get_selection() const override; virtual String ime_get_text() const override; + void cursor_update_shape(); virtual void cursor_set_shape(CursorShape p_shape) override; virtual CursorShape cursor_get_shape() const override; virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape = CURSOR_ARROW, const Vector2 &p_hotspot = Vector2()) override; diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 60f1eac4b1..137b1b45b0 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -30,6 +30,11 @@ #include "display_server_osx.h" +#include "godot_content_view.h" +#include "godot_menu_item.h" +#include "godot_window.h" +#include "godot_window_delegate.h" +#include "key_mapping_osx.h" #include "os_osx.h" #include "core/io/marshalls.h" @@ -47,217 +52,122 @@ #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" - -#import <AppKit/NSOpenGLView.h> #endif #if defined(VULKAN_ENABLED) #include "servers/rendering/renderer_rd/renderer_compositor_rd.h" - -#include <QuartzCore/CAMetalLayer.h> -#endif - -#ifndef NSAppKitVersionNumber10_14 -#define NSAppKitVersionNumber10_14 1671 #endif -#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) - -static bool ignore_momentum_scroll = false; - -static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) { - r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); - r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); - r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); - r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); -} - -static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { - const NSRect contentRect = [p_wd.window_view frame]; - const float scale = DS_OSX->screen_get_max_scale(); - p_wd.mouse_pos.x = p_locationInWindow.x * scale; - p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale; - DS_OSX->last_mouse_pos = p_wd.mouse_pos; - Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); - return p_wd.mouse_pos; -} - -static void _push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { - Vector<DisplayServerOSX::KeyEvent> &buffer = DS_OSX->key_event_buffer; - if (DS_OSX->key_event_pos >= buffer.size()) { - buffer.resize(1 + DS_OSX->key_event_pos); - } - buffer.write[DS_OSX->key_event_pos++] = p_event; -} - -static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { - if ([NSCursor respondsToSelector:selector]) { - id object = [NSCursor performSelector:selector]; - if ([object isKindOfClass:[NSCursor class]]) { - return object; +const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { + const NSMenu *menu = nullptr; + if (p_menu_root == "") { + // Main menu. + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (submenu.has(p_menu_root)) { + menu = submenu[p_menu_root]; } } - if (fallback) { - // Fallback should be a reasonable default, no need to check. - return [NSCursor performSelector:fallback]; - } - return [NSCursor arrowCursor]; -} - -/*************************************************************************/ -/* GlobalMenuItem */ -/*************************************************************************/ - -@interface GlobalMenuItem : NSObject { -@public - Callable callback; - Variant meta; - bool checkable; -} - -@end - -@implementation GlobalMenuItem -@end - -/*************************************************************************/ -/* GodotWindowDelegate */ -/*************************************************************************/ - -@interface GodotWindowDelegate : NSObject { - DisplayServerOSX::WindowID window_id; -} - -- (void)windowWillClose:(NSNotification *)notification; -- (void)setWindowID:(DisplayServerOSX::WindowID)wid; - -@end - -@implementation GodotWindowDelegate - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -- (BOOL)windowShouldClose:(id)sender { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return YES; + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; } - DS_OSX->_send_window_event(DS_OSX->windows[window_id], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - return NO; + return menu; } -- (void)windowWillClose:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - while (wd.transient_children.size()) { - DS_OSX->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); - } - - if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { - DS_OSX->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); - } - -#if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_destroy(window_id); - } -#endif -#ifdef VULKAN_ENABLED - if (DS_OSX->context_vulkan) { - DS_OSX->context_vulkan->window_destroy(window_id); +NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { + NSMenu *menu = nullptr; + if (p_menu_root == "") { + // Main menu. + menu = [NSApp mainMenu]; + } else if (p_menu_root.to_lower() == "_dock") { + // macOS dock menu. + menu = dock_menu; + } else { + // Submenu. + if (!submenu.has(p_menu_root)) { + NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; + submenu[p_menu_root] = n_menu; + } + menu = submenu[p_menu_root]; } -#endif - - DS_OSX->windows.erase(window_id); -} - -- (void)windowDidEnterFullScreen:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; + if (menu == apple_menu) { + // Do not allow to change Apple menu. + return nullptr; } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.fullscreen = true; - - [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; - [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; - // Force window resize event. - [self windowDidResize:notification]; + return menu; } -- (void)windowDidExitFullScreen:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.fullscreen = false; - - const float scale = DS_OSX->screen_get_max_scale(); - if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / scale; - [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; - } - if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / scale; - [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; - } - - if (wd.resize_disabled) { - [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; - } - - if (wd.on_top) { - [wd.window_object setLevel:NSFloatingWindowLevel]; - } - // Force window resize event. - [self windowDidResize:notification]; -} +DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) { + WindowID id; + const float scale = screen_get_max_scale(); + { + WindowData wd; -- (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!DisplayServerOSX::get_singleton()) { - return; - } + wd.window_delegate = [[GodotWindowDelegate alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); + [wd.window_delegate setWindowID:window_id_counter]; - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + Point2i position = p_rect.position; + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot passes a positive value. + position.y *= -1; + position += _get_screens_origin(); - CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor]; - CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + // initWithContentRect uses bottom-left corner of the window’s frame as origin. + wd.window_object = [[GodotWindow alloc] + initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) + styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable + backing:NSBackingStoreBuffered + defer:NO]; + ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); + [wd.window_object setWindowID:window_id_counter]; - if (newBackingScaleFactor != oldBackingScaleFactor) { - //Set new display scale and window size - const float scale = DS_OSX->screen_get_max_scale(); - const NSRect contentRect = [wd.window_view frame]; + wd.window_view = [[GodotContentView alloc] init]; + ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); + [wd.window_view setWindowID:window_id_counter]; + [wd.window_view setWantsLayer:TRUE]; - wd.size.width = contentRect.size.width * scale; - wd.size.height = contentRect.size.height * scale; + [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + [wd.window_object setContentView:wd.window_view]; + [wd.window_object setDelegate:wd.window_delegate]; + [wd.window_object setAcceptsMouseMovedEvents:YES]; + [wd.window_object setRestorable:NO]; + [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { + [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } CALayer *layer = [wd.window_view layer]; if (layer) { layer.contentsScale = scale; } - //Force window resize event - [self windowDidResize:notification]; +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); + } +#endif +#if defined(GLES3_ENABLED) + if (gl_manager) { + Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); + ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context"); + } +#endif + id = window_id_counter++; + windows[id] = wd; } -} -- (void)windowDidResize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + WindowData &wd = windows[id]; + window_set_mode(p_mode, id); const NSRect contentRect = [wd.window_view frame]; - - const float scale = DS_OSX->screen_get_max_scale(); wd.size.width = contentRect.size.width * scale; wd.size.height = contentRect.size.height * scale; @@ -267,1203 +177,448 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } #if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_resize(window_id, wd.size.width, wd.size.height); + if (gl_manager) { + gl_manager->window_resize(id, wd.size.width, wd.size.height); } #endif #if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); + if (context_vulkan) { + context_vulkan->window_resize(id, wd.size.width, wd.size.height); } #endif - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); - } + return id; } -- (void)windowDidMove:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->_release_pressed_events(); +void DisplayServerOSX::_update_window_style(WindowData p_wd) { + bool borderless_full = false; - if (!wd.rect_changed_callback.is_null()) { - Variant size = Rect2i(DS_OSX->window_get_position(window_id), DS_OSX->window_get_size(window_id)); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); - } -} + if (p_wd.borderless) { + NSRect frameRect = [p_wd.window_object frame]; + NSRect screenRect = [[p_wd.window_object screen] frame]; -- (void)windowDidBecomeKey:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; + // Check if our window covers up the screen. + if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && + frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { + borderless_full = true; + } } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CAPTURED) { - 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); + if (borderless_full) { + // If the window covers up the screen set the level to above the main menu and hide on deactivate. + [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; + [p_wd.window_object setHidesOnDeactivate:YES]; } else { - _ALLOW_DISCARD_ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); - Input::get_singleton()->set_mouse_position(wd.mouse_pos); + // Reset these when our window is not a borderless window that covers up the screen. + if (p_wd.on_top && !p_wd.fullscreen) { + [p_wd.window_object setLevel:NSFloatingWindowLevel]; + } else { + [p_wd.window_object setLevel:NSNormalWindowLevel]; + } + [p_wd.window_object setHidesOnDeactivate:NO]; } - - DS_OSX->window_focused = true; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); } -- (void)windowDidResignKey:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = false; - - DS_OSX->_release_pressed_events(); - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); -} - -- (void)windowDidMiniaturize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { - return; - } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = false; - - DS_OSX->_release_pressed_events(); - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); -} +void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) { + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[p_window]; -- (void)windowDidDeminiaturize:(NSNotification *)notification { - if (!DS_OSX || !DS_OSX->windows.has(window_id)) { + if (!OS::get_singleton()->is_layered_allowed()) { return; } - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - DS_OSX->window_focused = true; - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); -} - -@end - -/*************************************************************************/ -/* GodotContentView */ -/*************************************************************************/ - + if (wd.layered_window != p_enabled) { + if (p_enabled) { + [wd.window_object setBackgroundColor:[NSColor clearColor]]; + [wd.window_object setOpaque:NO]; + [wd.window_object setHasShadow:NO]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor clearColor].CGColor]; + [layer setOpaque:NO]; + } #if defined(GLES3_ENABLED) -@interface GodotContentView : NSOpenGLView <NSTextInputClient> { -#else -@interface GodotContentView : NSView <NSTextInputClient> { -#endif - - DisplayServerOSX::WindowID window_id; - NSTrackingArea *trackingArea; - NSMutableAttributedString *markedText; - bool imeInputEventInProgress; -} - -- (void)cancelComposition; -- (CALayer *)makeBackingLayer; -- (BOOL)wantsUpdateLayer; -- (void)updateLayer; -- (void)setWindowID:(DisplayServerOSX::WindowID)wid; - -@end - -@implementation GodotContentView - -- (void)setWindowID:(DisplayServerOSX::WindowID)wid { - window_id = wid; -} - -+ (void)initialize { - if (self == [GodotContentView class]) { - // nothing left to do here at the moment.. - } -} - -- (CALayer *)makeBackingLayer { -#if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - CALayer *layer = [[CAMetalLayer class] layer]; - return layer; - } + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, true); + } #endif - return [super makeBackingLayer]; -} - -- (void)updateLayer { + wd.layered_window = true; + } else { + [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; + [wd.window_object setOpaque:YES]; + [wd.window_object setHasShadow:YES]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1].CGColor]; + [layer setOpaque:YES]; + } #if defined(GLES3_ENABLED) - if (DS_OSX->gl_manager) { - DS_OSX->gl_manager->window_update(window_id); - } + if (gl_manager) { + gl_manager->window_set_per_pixel_transparency_enabled(p_window, false); + } #endif -#if defined(VULKAN_ENABLED) - if (DS_OSX->context_vulkan) { - [super updateLayer]; + wd.layered_window = false; + } + NSRect frameRect = [wd.window_object frame]; + [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES]; + [wd.window_object setFrame:frameRect display:YES]; } -#endif -} - -- (BOOL)wantsUpdateLayer { - return YES; } -- (id)init { - self = [super init]; - trackingArea = nil; - imeInputEventInProgress = false; - [self updateTrackingAreas]; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 - [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; -#else - [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; -#endif - markedText = [[NSMutableAttributedString alloc] init]; - return self; -} +void DisplayServerOSX::_update_displays_arrangement() { + origin = Point2i(); -- (void)dealloc { - [trackingArea release]; - [markedText release]; - [super dealloc]; + for (int i = 0; i < get_screen_count(); i++) { + Point2i position = _get_native_screen_position(i); + if (position.x < origin.x) { + origin.x = position.x; + } + if (position.y > origin.y) { + origin.y = position.y; + } + } + displays_arrangement_dirty = false; } -static const NSRange kEmptyRange = { NSNotFound, 0 }; - -- (BOOL)hasMarkedText { - return (markedText.length > 0); -} +Point2i DisplayServerOSX::_get_screens_origin() const { + // Returns the native top-left screen coordinate of the smallest rectangle + // that encompasses all screens. Needed in get_screen_position(), + // window_get_position, and window_set_position() + // to convert between OS X native screen coordinates and the ones expected by Godot. -- (NSRange)markedRange { - return NSMakeRange(0, markedText.length); -} + if (displays_arrangement_dirty) { + const_cast<DisplayServerOSX *>(this)->_update_displays_arrangement(); + } -- (NSRange)selectedRange { - return kEmptyRange; + return origin; } -- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { - if ([aString isKindOfClass:[NSAttributedString class]]) { - [markedText initWithAttributedString:aString]; - } else { - [markedText initWithString:aString]; - } - if (markedText.length == 0) { - [self unmarkText]; - return; +Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; + // Return the top-left corner of the screen, for OS X the y starts at the bottom. + return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); } - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (wd.im_active) { - imeInputEventInProgress = true; - DS_OSX->im_text.parse_utf8([[markedText mutableString] UTF8String]); - DS_OSX->im_selection = Point2i(selectedRange.location, selectedRange.length); + return Point2i(); +} - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); +void DisplayServerOSX::_displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->displays_arrangement_dirty = true; } } -- (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) { - [self performSelector:aSelector]; - } +void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) { + ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); } -- (void)unmarkText { - imeInputEventInProgress = false; - [[markedText mutableString] setString:@""]; +void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) { + _THREAD_SAFE_METHOD_ + if (!in_dispatch_input_event) { + in_dispatch_input_event = true; - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + Variant ev = p_event; + Variant *evp = &ev; + Variant ret; + Callable::CallError ce; - if (wd.im_active) { - DS_OSX->im_text = String(); - DS_OSX->im_selection = Point2i(); + Ref<InputEventFromWindow> event_from_window = p_event; + if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { + // Send to a window. + if (windows.has(event_from_window->get_window_id())) { + Callable callable = windows[event_from_window->get_window_id()].input_event_callback; + if (callable.is_null()) { + return; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } + } else { + // Send to all windows. + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + Callable callable = E->get().input_event_callback; + if (callable.is_null()) { + continue; + } + callable.call((const Variant **)&evp, 1, ret, ce); + } + } - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); + in_dispatch_input_event = false; } } -- (NSArray *)validAttributesForMarkedText { - return [NSArray array]; -} - -- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - return nil; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { - return 0; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NSMakeRect(0, 0, 0, 0)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - const NSRect contentRect = [wd.window_view frame]; - const float scale = DS_OSX->screen_get_max_scale(); - NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0); - NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin; - - return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); +void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { + Ref<InputEvent> ev = p_event; + Input::get_singleton()->parse_input_event(ev); } -- (void)cancelComposition { - [self unmarkText]; - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; -} +void DisplayServerOSX::_process_key_events() { + Ref<InputEventKey> k; + for (int i = 0; i < key_event_pos; i++) { + const KeyEvent &ke = key_event_buffer[i]; + if (ke.raw) { + // Non IME input - no composite characters, pass events as is. + k.instantiate(); -- (void)insertText:(id)aString { - [self insertText:aString replacementRange:NSMakeRange(0, 0)]; -} + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); + k->set_unicode(ke.unicode); -- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { - NSEvent *event = [NSApp currentEvent]; + _push_input(k); + } else { + // IME input. + if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { + k.instantiate(); - NSString *characters; - if ([aString isKindOfClass:[NSAttributedString class]]) { - characters = [aString string]; - } else { - characters = (NSString *)aString; - } + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(Key::NONE); + k->set_physical_keycode(Key::NONE); + k->set_unicode(ke.unicode); - NSCharacterSet *ctrlChars = [NSCharacterSet controlCharacterSet]; - NSCharacterSet *wsnlChars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; - if ([characters rangeOfCharacterFromSet:ctrlChars].length && [characters rangeOfCharacterFromSet:wsnlChars].length == 0) { - NSTextInputContext *currentInputContext = [NSTextInputContext currentInputContext]; - [currentInputContext discardMarkedText]; - [self cancelComposition]; - return; - } + _push_input(k); + } + if (ke.keycode != Key::NONE) { + k.instantiate(); - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + k->set_window_id(ke.window_id); + get_key_modifier_state(ke.osx_state, k); + k->set_pressed(ke.pressed); + k->set_echo(ke.echo); + k->set_keycode(ke.keycode); + k->set_physical_keycode((Key)ke.physical_keycode); - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); + if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { + k->set_unicode(key_event_buffer[i + 1].unicode); + } - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - if ((codepoint & 0xFF00) == 0xF700) { - continue; + _push_input(k); + } } - - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = false; - ke.raw = false; // IME input event - ke.keycode = Key::NONE; - ke.physical_keycode = Key::NONE; - ke.unicode = codepoint; - - _push_to_key_event_buffer(ke); } - [self cancelComposition]; -} -- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { - return NSDragOperationCopy; + key_event_pos = 0; } -- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { - return NSDragOperationCopy; -} +void DisplayServerOSX::_update_keyboard_layouts() { + kbd_layouts.clear(); + current_layout = 0; -- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { - ERR_FAIL_COND_V(!DS_OSX->windows.has(window_id), NO); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); + NSString *cur_name = (__bridge NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); + CFRelease(cur_source); - if (!wd.drop_files_callback.is_null()) { - Vector<String> files; - NSPasteboard *pboard = [sender draggingPasteboard]; + // Enum IME layouts. + NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); + for (NSUInteger i = 0; i < [list_ime count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 - NSArray *items = pboard.pasteboardItems; - for (NSPasteboardItem *item in items) { - NSString *path = [item stringForType:NSPasteboardTypeFileURL]; - NSString *ns = [NSURL URLWithString:path].path; - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } -#else - NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; - for (NSString *ns in filenames) { - char *utfs = strdup([ns UTF8String]); - String ret; - ret.parse_utf8(utfs); - free(utfs); - files.push_back(ret); - } -#endif + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); - Variant v = files; - Variant *vp = &v; - Variant ret; - Callable::CallError ce; - wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; + } } - return NO; -} + // Enum plain keyboard layouts. + NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; + NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); + for (NSUInteger i = 0; i < [list_kbd count]; i++) { + LayoutInfo ly; + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + ly.name.parse_utf8([name UTF8String]); -- (BOOL)isOpaque { - return YES; -} + NSArray *langs = (__bridge NSArray *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); + ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); + kbd_layouts.push_back(ly); -- (BOOL)canBecomeKeyView { - if (DS_OSX->windows.has(window_id)) { - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (wd.no_focus) { - return NO; + if ([name isEqualToString:cur_name]) { + current_layout = kbd_layouts.size() - 1; } } - return YES; -} - -- (BOOL)acceptsFirstResponder { - return YES; -} -- (void)cursorUpdate:(NSEvent *)event { - DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; - DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; - DS_OSX->cursor_set_shape(p_shape); + keyboard_layout_dirty = false; } -static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, MouseButton index, MouseButton mask, bool pressed) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (pressed) { - DS_OSX->last_button_state |= mask; - } else { - DS_OSX->last_button_state &= (MouseButton)~mask; - } - - Ref<InputEventMouseButton> mb; - mb.instantiate(); - mb->set_window_id(window_id); - const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]); - _get_key_modifier_state([event modifierFlags], mb); - mb->set_button_index(index); - mb->set_pressed(pressed); - mb->set_position(pos); - mb->set_global_position(pos); - mb->set_button_mask(DS_OSX->last_button_state); - if (index == MouseButton::LEFT && pressed) { - mb->set_double_click([event clickCount] == 2); +void DisplayServerOSX::_keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->keyboard_layout_dirty = true; } - - Input::get_singleton()->parse_input_event(mb); } -- (void)mouseDown:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (([event modifierFlags] & NSEventModifierFlagControl)) { - wd.mouse_down_control = true; - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true); - } else { - wd.mouse_down_control = false; - _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, true); +NSCursor *DisplayServerOSX::_cursor_from_selector(SEL p_selector, SEL p_fallback) { + if ([NSCursor respondsToSelector:p_selector]) { + id object = [NSCursor performSelector:p_selector]; + if ([object isKindOfClass:[NSCursor class]]) { + return object; + } } -} - -- (void)mouseDragged:(NSEvent *)event { - [self mouseMoved:event]; -} - -- (void)mouseUp:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - if (wd.mouse_down_control) { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false); - } else { - _mouseDownEvent(window_id, event, MouseButton::LEFT, MouseButton::MASK_LEFT, false); + if (p_fallback) { + // Fallback should be a reasonable default, no need to check. + return [NSCursor performSelector:p_fallback]; } + return [NSCursor arrowCursor]; } -- (void)mouseMoved:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); - NSPoint mpos = [event locationInWindow]; +NSMenu *DisplayServerOSX::get_dock_menu() const { + return dock_menu; +} - if (DS_OSX->ignore_warp) { - // Discard late events, before warp - if (([event timestamp]) < DS_OSX->last_warp) { - return; - } - DS_OSX->ignore_warp = false; +void DisplayServerOSX::menu_callback(id p_sender) { + if (![p_sender representedObject]) { return; } - if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { - // Discard late events - if (([event timestamp]) < DS_OSX->last_warp) { - return; - } + GodotMenuItem *value = [p_sender representedObject]; - // Warp affects next event delta, subtract previous warp deltas - List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front(); - while (F) { - if (F->get().timestamp < [event timestamp]) { - List<DisplayServerOSX::WarpEvent>::Element *E = F; - delta.x -= E->get().delta.x; - delta.y -= E->get().delta.y; - F = F->next(); - DS_OSX->warp_events.erase(E); + if (value) { + if (value->checkable) { + if ([p_sender state] == NSControlStateValueOff) { + [p_sender setState:NSControlStateValueOn]; } else { - F = F->next(); + [p_sender setState:NSControlStateValueOff]; } } - // Confine mouse position to the window, and update delta - NSRect frame = [wd.window_object frame]; - NSPoint conf_pos = mpos; - conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width); - conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height); - delta.x = conf_pos.x - mpos.x; - delta.y = mpos.y - conf_pos.y; - mpos = conf_pos; - - // Move mouse cursor - NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); - conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; - CGWarpMouseCursorPosition(conf_pos); - - // Save warp data - DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime]; - DisplayServerOSX::WarpEvent ev; - ev.timestamp = DS_OSX->last_warp; - ev.delta = delta; - DS_OSX->warp_events.push_back(ev); - } - - Ref<InputEventMouseMotion> mm; - mm.instantiate(); - - mm->set_window_id(window_id); - mm->set_button_mask(DS_OSX->last_button_state); - const Vector2i pos = _get_mouse_pos(wd, mpos); - mm->set_position(pos); - mm->set_pressure([event pressure]); - if ([event subtype] == NSEventSubtypeTabletPoint) { - const NSPoint p = [event tilt]; - mm->set_tilt(Vector2(p.x, p.y)); + if (value->callback != Callable()) { + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->callback.call((const Variant **)&tagp, 1, ret, ce); + } } - mm->set_global_position(pos); - mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); - const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale(); - mm->set_relative(relativeMotion); - _get_key_modifier_state([event modifierFlags], mm); - - Input::get_singleton()->set_mouse_position(wd.mouse_pos); - Input::get_singleton()->parse_input_event(mm); -} - -- (void)rightMouseDown:(NSEvent *)event { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, true); -} - -- (void)rightMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; } -- (void)rightMouseUp:(NSEvent *)event { - _mouseDownEvent(window_id, event, MouseButton::RIGHT, MouseButton::MASK_RIGHT, false); -} - -- (void)otherMouseDown:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, true); - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, true); - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, true); - } else { - return; - } +bool DisplayServerOSX::has_window(WindowID p_window) const { + return windows.has(p_window); } -- (void)otherMouseDragged:(NSEvent *)event { - [self mouseMoved:event]; +DisplayServerOSX::WindowData &DisplayServerOSX::get_window(WindowID p_window) { + return windows[p_window]; } -- (void)otherMouseUp:(NSEvent *)event { - if ((int)[event buttonNumber] == 2) { - _mouseDownEvent(window_id, event, MouseButton::MIDDLE, MouseButton::MASK_MIDDLE, false); - } else if ((int)[event buttonNumber] == 3) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON1, MouseButton::MASK_XBUTTON1, false); - } else if ((int)[event buttonNumber] == 4) { - _mouseDownEvent(window_id, event, MouseButton::MB_XBUTTON2, MouseButton::MASK_XBUTTON2, false); - } else { - return; - } -} +void DisplayServerOSX::send_event(NSEvent *p_event) { + // Special case handling of command-period, which is traditionally a special + // shortcut in macOS and doesn't arrive at our regular keyDown handler. + if ([p_event type] == NSEventTypeKeyDown) { + if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { + Ref<InputEventKey> k; + k.instantiate(); -- (void)mouseExited:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + get_key_modifier_state([p_event modifierFlags], k); + k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); + k->set_pressed(true); + k->set_keycode(Key::PERIOD); + k->set_physical_keycode(Key::PERIOD); + k->set_echo([p_event isARepeat]); - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + Input::get_singleton()->parse_input_event(k); + } } } -- (void)mouseEntered:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; +void DisplayServerOSX::send_window_event(const WindowData &wd, WindowEvent p_event) { + _THREAD_SAFE_METHOD_ - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { - DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + if (!wd.event_callback.is_null()) { + Variant event = int(p_event); + Variant *eventp = &event; + Variant ret; + Callable::CallError ce; + wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); } - - DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; - DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; - DS_OSX->cursor_set_shape(p_shape); } -- (void)magnifyWithEvent:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - Ref<InputEventMagnifyGesture> ev; - ev.instantiate(); - ev->set_window_id(window_id); - _get_key_modifier_state([event modifierFlags], ev); - ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); - ev->set_factor([event magnification] + 1.0); - - Input::get_singleton()->parse_input_event(ev); -} - -- (void)viewDidChangeBackingProperties { - // nothing left to do here -} - -- (void)updateTrackingAreas { - if (trackingArea != nil) { - [self removeTrackingArea:trackingArea]; - [trackingArea release]; - } - - NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; - trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; - - [self addTrackingArea:trackingArea]; - [super updateTrackingAreas]; -} - -static bool isNumpadKey(unsigned int key) { - static const unsigned int table[] = { - 0x41, /* kVK_ANSI_KeypadDecimal */ - 0x43, /* kVK_ANSI_KeypadMultiply */ - 0x45, /* kVK_ANSI_KeypadPlus */ - 0x47, /* kVK_ANSI_KeypadClear */ - 0x4b, /* kVK_ANSI_KeypadDivide */ - 0x4c, /* kVK_ANSI_KeypadEnter */ - 0x4e, /* kVK_ANSI_KeypadMinus */ - 0x51, /* kVK_ANSI_KeypadEquals */ - 0x52, /* kVK_ANSI_Keypad0 */ - 0x53, /* kVK_ANSI_Keypad1 */ - 0x54, /* kVK_ANSI_Keypad2 */ - 0x55, /* kVK_ANSI_Keypad3 */ - 0x56, /* kVK_ANSI_Keypad4 */ - 0x57, /* kVK_ANSI_Keypad5 */ - 0x58, /* kVK_ANSI_Keypad6 */ - 0x59, /* kVK_ANSI_Keypad7 */ - 0x5b, /* kVK_ANSI_Keypad8 */ - 0x5c, /* kVK_ANSI_Keypad9 */ - 0x5f, /* kVK_JIS_KeypadComma */ - 0x00 - }; - for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) { - return true; - } +void DisplayServerOSX::release_pressed_events() { + _THREAD_SAFE_METHOD_ + if (Input::get_singleton()) { + Input::get_singleton()->release_pressed_events(); } - return false; } -// Keyboard symbol translation table -static const Key _osx_to_godot_table[128] = { - /* 00 */ Key::A, - /* 01 */ Key::S, - /* 02 */ Key::D, - /* 03 */ Key::F, - /* 04 */ Key::H, - /* 05 */ Key::G, - /* 06 */ Key::Z, - /* 07 */ Key::X, - /* 08 */ Key::C, - /* 09 */ Key::V, - /* 0a */ Key::SECTION, /* ISO Section */ - /* 0b */ Key::B, - /* 0c */ Key::Q, - /* 0d */ Key::W, - /* 0e */ Key::E, - /* 0f */ Key::R, - /* 10 */ Key::Y, - /* 11 */ Key::T, - /* 12 */ Key::KEY_1, - /* 13 */ Key::KEY_2, - /* 14 */ Key::KEY_3, - /* 15 */ Key::KEY_4, - /* 16 */ Key::KEY_6, - /* 17 */ Key::KEY_5, - /* 18 */ Key::EQUAL, - /* 19 */ Key::KEY_9, - /* 1a */ Key::KEY_7, - /* 1b */ Key::MINUS, - /* 1c */ Key::KEY_8, - /* 1d */ Key::KEY_0, - /* 1e */ Key::BRACERIGHT, - /* 1f */ Key::O, - /* 20 */ Key::U, - /* 21 */ Key::BRACELEFT, - /* 22 */ Key::I, - /* 23 */ Key::P, - /* 24 */ Key::ENTER, - /* 25 */ Key::L, - /* 26 */ Key::J, - /* 27 */ Key::APOSTROPHE, - /* 28 */ Key::K, - /* 29 */ Key::SEMICOLON, - /* 2a */ Key::BACKSLASH, - /* 2b */ Key::COMMA, - /* 2c */ Key::SLASH, - /* 2d */ Key::N, - /* 2e */ Key::M, - /* 2f */ Key::PERIOD, - /* 30 */ Key::TAB, - /* 31 */ Key::SPACE, - /* 32 */ Key::QUOTELEFT, - /* 33 */ Key::BACKSPACE, - /* 34 */ Key::UNKNOWN, - /* 35 */ Key::ESCAPE, - /* 36 */ Key::META, - /* 37 */ Key::META, - /* 38 */ Key::SHIFT, - /* 39 */ Key::CAPSLOCK, - /* 3a */ Key::ALT, - /* 3b */ Key::CTRL, - /* 3c */ Key::SHIFT, - /* 3d */ Key::ALT, - /* 3e */ Key::CTRL, - /* 3f */ Key::UNKNOWN, /* Function */ - /* 40 */ Key::UNKNOWN, /* F17 */ - /* 41 */ Key::KP_PERIOD, - /* 42 */ Key::UNKNOWN, - /* 43 */ Key::KP_MULTIPLY, - /* 44 */ Key::UNKNOWN, - /* 45 */ Key::KP_ADD, - /* 46 */ Key::UNKNOWN, - /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */ - /* 48 */ Key::VOLUMEUP, /* VolumeUp */ - /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */ - /* 4a */ Key::VOLUMEMUTE, /* Mute */ - /* 4b */ Key::KP_DIVIDE, - /* 4c */ Key::KP_ENTER, - /* 4d */ Key::UNKNOWN, - /* 4e */ Key::KP_SUBTRACT, - /* 4f */ Key::UNKNOWN, /* F18 */ - /* 50 */ Key::UNKNOWN, /* F19 */ - /* 51 */ Key::EQUAL, /* KeypadEqual */ - /* 52 */ Key::KP_0, - /* 53 */ Key::KP_1, - /* 54 */ Key::KP_2, - /* 55 */ Key::KP_3, - /* 56 */ Key::KP_4, - /* 57 */ Key::KP_5, - /* 58 */ Key::KP_6, - /* 59 */ Key::KP_7, - /* 5a */ Key::UNKNOWN, /* F20 */ - /* 5b */ Key::KP_8, - /* 5c */ Key::KP_9, - /* 5d */ Key::YEN, /* JIS Yen */ - /* 5e */ Key::UNDERSCORE, /* JIS Underscore */ - /* 5f */ Key::COMMA, /* JIS KeypadComma */ - /* 60 */ Key::F5, - /* 61 */ Key::F6, - /* 62 */ Key::F7, - /* 63 */ Key::F3, - /* 64 */ Key::F8, - /* 65 */ Key::F9, - /* 66 */ Key::UNKNOWN, /* JIS Eisu */ - /* 67 */ Key::F11, - /* 68 */ Key::UNKNOWN, /* JIS Kana */ - /* 69 */ Key::F13, - /* 6a */ Key::F16, - /* 6b */ Key::F14, - /* 6c */ Key::UNKNOWN, - /* 6d */ Key::F10, - /* 6e */ Key::MENU, - /* 6f */ Key::F12, - /* 70 */ Key::UNKNOWN, - /* 71 */ Key::F15, - /* 72 */ Key::INSERT, /* Really Help... */ - /* 73 */ Key::HOME, - /* 74 */ Key::PAGEUP, - /* 75 */ Key::KEY_DELETE, - /* 76 */ Key::F4, - /* 77 */ Key::END, - /* 78 */ Key::F2, - /* 79 */ Key::PAGEDOWN, - /* 7a */ Key::F1, - /* 7b */ Key::LEFT, - /* 7c */ Key::RIGHT, - /* 7d */ Key::DOWN, - /* 7e */ Key::UP, - /* 7f */ Key::UNKNOWN, -}; - -// Translates a OS X keycode to a Godot keycode -static Key translateKey(unsigned int key) { - if (key >= 128) { - return Key::UNKNOWN; - } - - return _osx_to_godot_table[key]; -} - -// Translates a Godot keycode back to a OSX keycode -static unsigned int unmapKey(Key key) { - for (int i = 0; i <= 126; i++) { - if (_osx_to_godot_table[i] == key) { - return i; - } - } - return 127; -} - -struct _KeyCodeMap { - UniChar kchar; - Key kcode; -}; - -static const _KeyCodeMap _keycodes[55] = { - { '`', Key::QUOTELEFT }, - { '~', Key::ASCIITILDE }, - { '0', Key::KEY_0 }, - { '1', Key::KEY_1 }, - { '2', Key::KEY_2 }, - { '3', Key::KEY_3 }, - { '4', Key::KEY_4 }, - { '5', Key::KEY_5 }, - { '6', Key::KEY_6 }, - { '7', Key::KEY_7 }, - { '8', Key::KEY_8 }, - { '9', Key::KEY_9 }, - { '-', Key::MINUS }, - { '_', Key::UNDERSCORE }, - { '=', Key::EQUAL }, - { '+', Key::PLUS }, - { 'q', Key::Q }, - { 'w', Key::W }, - { 'e', Key::E }, - { 'r', Key::R }, - { 't', Key::T }, - { 'y', Key::Y }, - { 'u', Key::U }, - { 'i', Key::I }, - { 'o', Key::O }, - { 'p', Key::P }, - { '[', Key::BRACELEFT }, - { ']', Key::BRACERIGHT }, - { '{', Key::BRACELEFT }, - { '}', Key::BRACERIGHT }, - { 'a', Key::A }, - { 's', Key::S }, - { 'd', Key::D }, - { 'f', Key::F }, - { 'g', Key::G }, - { 'h', Key::H }, - { 'j', Key::J }, - { 'k', Key::K }, - { 'l', Key::L }, - { ';', Key::SEMICOLON }, - { ':', Key::COLON }, - { '\'', Key::APOSTROPHE }, - { '\"', Key::QUOTEDBL }, - { '\\', Key::BACKSLASH }, - { '#', Key::NUMBERSIGN }, - { 'z', Key::Z }, - { 'x', Key::X }, - { 'c', Key::C }, - { 'v', Key::V }, - { 'b', Key::B }, - { 'n', Key::N }, - { 'm', Key::M }, - { ',', Key::COMMA }, - { '.', Key::PERIOD }, - { '/', Key::SLASH } -}; - -static Key remapKey(unsigned int key, unsigned int state) { - if (isNumpadKey(key)) { - return translateKey(key); - } - - TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) { - return translateKey(key); - } - - CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) { - return translateKey(key); - } - - const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); - - UInt32 keysDown = 0; - UniChar chars[4]; - UniCharCount realLength; - - OSStatus err = UCKeyTranslate(keyboardLayout, - key, - kUCKeyActionDisplay, - (state >> 8) & 0xFF, - LMGetKbdType(), - kUCKeyTranslateNoDeadKeysBit, - &keysDown, - sizeof(chars) / sizeof(chars[0]), - &realLength, - chars); - - if (err != noErr) { - return translateKey(key); - } - - for (unsigned int i = 0; i < 55; i++) { - if (_keycodes[i].kchar == chars[0]) { - return _keycodes[i].kcode; - } - } - return translateKey(key); -} - -- (void)keyDown:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - // Fallback unicode character handler used if IME is not active - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; - - _push_to_key_event_buffer(ke); - } - } else { - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = true; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = false; - ke.unicode = 0; - - _push_to_key_event_buffer(ke); - } - } - - // Pass events to IME handler - if (wd.im_active) { - [self interpretKeyEvents:[NSArray arrayWithObject:event]]; - } +void DisplayServerOSX::get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWithModifiers> r_state) const { + r_state->set_shift_pressed((p_osx_state & NSEventModifierFlagShift)); + r_state->set_ctrl_pressed((p_osx_state & NSEventModifierFlagControl)); + r_state->set_alt_pressed((p_osx_state & NSEventModifierFlagOption)); + r_state->set_meta_pressed((p_osx_state & NSEventModifierFlagCommand)); } -- (void)flagsChanged:(NSEvent *)event { - ignore_momentum_scroll = true; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.echo = false; - ke.raw = true; - - int key = [event keyCode]; - int mod = [event modifierFlags]; - - if (key == 0x36 || key == 0x37) { - if (mod & NSEventModifierFlagCommand) { - mod &= ~NSEventModifierFlagCommand; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x38 || key == 0x3c) { - if (mod & NSEventModifierFlagShift) { - mod &= ~NSEventModifierFlagShift; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3a || key == 0x3d) { - if (mod & NSEventModifierFlagOption) { - mod &= ~NSEventModifierFlagOption; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else if (key == 0x3b || key == 0x3e) { - if (mod & NSEventModifierFlagControl) { - mod &= ~NSEventModifierFlagControl; - ke.pressed = true; - } else { - ke.pressed = false; - } - } else { - return; - } - - ke.osx_state = mod; - ke.keycode = remapKey(key, mod); - ke.physical_keycode = translateKey(key); - ke.unicode = 0; - - _push_to_key_event_buffer(ke); - } +void DisplayServerOSX::update_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_location_in_window) { + const NSRect content_rect = [p_wd.window_view frame]; + const float scale = screen_get_max_scale(); + p_wd.mouse_pos.x = p_location_in_window.x * scale; + p_wd.mouse_pos.y = (content_rect.size.height - p_location_in_window.y) * scale; + Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); } -- (void)keyUp:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - // Ignore all input if IME input is in progress - if (!imeInputEventInProgress) { - NSString *characters = [event characters]; - NSUInteger length = [characters length]; - - // Fallback unicode character handler used if IME is not active - if (!wd.im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) { - Char16String text; - text.resize([characters length] + 1); - [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; - - String u32text; - u32text.parse_utf16(text.ptr(), text.length()); - - for (int i = 0; i < u32text.length(); i++) { - const char32_t codepoint = u32text[i]; - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = codepoint; - - _push_to_key_event_buffer(ke); - } - } else { - DisplayServerOSX::KeyEvent ke; - - ke.window_id = window_id; - ke.osx_state = [event modifierFlags]; - ke.pressed = false; - ke.echo = [event isARepeat]; - ke.keycode = remapKey([event keyCode], [event modifierFlags]); - ke.physical_keycode = translateKey([event keyCode]); - ke.raw = true; - ke.unicode = 0; - - _push_to_key_event_buffer(ke); - } +void DisplayServerOSX::push_to_key_event_buffer(const DisplayServerOSX::KeyEvent &p_event) { + if (key_event_pos >= key_event_buffer.size()) { + key_event_buffer.resize(1 + key_event_pos); } + key_event_buffer.write[key_event_pos++] = p_event; } -inline void sendScrollEvent(DisplayServer::WindowID window_id, MouseButton button, double factor, int modifierFlags) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - MouseButton mask = mouse_button_to_mask(button); - - Ref<InputEventMouseButton> sc; - sc.instantiate(); - - sc->set_window_id(window_id); - _get_key_modifier_state(modifierFlags, sc); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(true); - sc->set_position(wd.mouse_pos); - sc->set_global_position(wd.mouse_pos); - DS_OSX->last_button_state |= (MouseButton)mask; - sc->set_button_mask(DS_OSX->last_button_state); +void DisplayServerOSX::update_im_text(const Point2i &p_selection, const String &p_text) { + im_selection = p_selection; + im_text = p_text; - Input::get_singleton()->parse_input_event(sc); - - sc.instantiate(); - sc->set_window_id(window_id); - sc->set_button_index(button); - sc->set_factor(factor); - sc->set_pressed(false); - sc->set_position(wd.mouse_pos); - sc->set_global_position(wd.mouse_pos); - DS_OSX->last_button_state &= (MouseButton)~mask; - sc->set_button_mask(DS_OSX->last_button_state); - - Input::get_singleton()->parse_input_event(sc); + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE); } -inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy, int modifierFlags) { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - Ref<InputEventPanGesture> pg; - pg.instantiate(); - - pg->set_window_id(window_id); - _get_key_modifier_state(modifierFlags, pg); - pg->set_position(wd.mouse_pos); - pg->set_delta(Vector2(-dx, -dy)); - - Input::get_singleton()->parse_input_event(pg); +void DisplayServerOSX::set_last_focused_window(WindowID p_window) { + last_focused_window = p_window; } -- (void)scrollWheel:(NSEvent *)event { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - double deltaX, deltaY; - - _ALLOW_DISCARD_ _get_mouse_pos(wd, [event locationInWindow]); - - deltaX = [event scrollingDeltaX]; - deltaY = [event scrollingDeltaY]; - - if ([event hasPreciseScrollingDeltas]) { - deltaX *= 0.03; - deltaY *= 0.03; +void DisplayServerOSX::window_update(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_update(p_window); } +#endif +} - if ([event momentumPhase] != NSEventPhaseNone) { - if (ignore_momentum_scroll) { - return; - } - } else { - ignore_momentum_scroll = false; +void DisplayServerOSX::window_destroy(WindowID p_window) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_destroy(p_window); } - - if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { - sendPanEvent(window_id, deltaX, deltaY, [event modifierFlags]); - } else { - if (fabs(deltaX)) { - sendScrollEvent(window_id, 0 > deltaX ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT, fabs(deltaX * 0.3), [event modifierFlags]); - } - if (fabs(deltaY)) { - sendScrollEvent(window_id, 0 < deltaY ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN, fabs(deltaY * 0.3), [event modifierFlags]); - } +#endif +#ifdef VULKAN_ENABLED + if (context_vulkan) { + context_vulkan->window_destroy(p_window); } +#endif + windows.erase(p_window); } -@end - -/*************************************************************************/ -/* GodotWindow */ -/*************************************************************************/ - -@interface GodotWindow : NSWindow { -} - -@end - -@implementation GodotWindow - -- (BOOL)canBecomeKeyWindow { - // Required for NSBorderlessWindowMask windows - for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { - if (E->get().window_object == self) { - if (E->get().no_focus) { - return NO; - } - } +void DisplayServerOSX::window_resize(WindowID p_window, int p_width, int p_height) { +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->window_resize(p_window, p_width, p_height); } - return YES; -} - -- (BOOL)canBecomeMainWindow { - // Required for NSBorderlessWindowMask windows - for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { - if (E->get().window_object == self) { - if (E->get().no_focus) { - return NO; - } - } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->window_resize(p_window, p_width, p_height); } - return YES; +#endif } -@end - -/*************************************************************************/ -/* DisplayServerOSX */ -/*************************************************************************/ - bool DisplayServerOSX::has_feature(Feature p_feature) const { switch (p_feature) { case FEATURE_GLOBAL_MENU: @@ -1493,57 +648,13 @@ String DisplayServerOSX::get_name() const { return "OSX"; } -const NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) const { - const NSMenu *menu = nullptr; - if (p_menu_root == "") { - // Main menu.x - menu = [NSApp mainMenu]; - } else if (p_menu_root.to_lower() == "_dock") { - // macOS dock menu. - menu = dock_menu; - } else { - // Submenu. - if (submenu.has(p_menu_root)) { - menu = submenu[p_menu_root]; - } - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - -NSMenu *DisplayServerOSX::_get_menu_root(const String &p_menu_root) { - NSMenu *menu = nullptr; - if (p_menu_root == "") { - // Main menu. - menu = [NSApp mainMenu]; - } else if (p_menu_root.to_lower() == "_dock") { - // macOS dock menu. - menu = dock_menu; - } else { - // Submenu. - if (!submenu.has(p_menu_root)) { - NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; - submenu[p_menu_root] = n_menu; - } - menu = submenu[p_menu_root]; - } - if (menu == apple_menu) { - // Do not allow to change Apple menu. - return nullptr; - } - return menu; -} - void DisplayServerOSX::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; obj->checkable = false; @@ -1557,7 +668,7 @@ void DisplayServerOSX::global_menu_add_check_item(const String &p_menu_root, con NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { NSMenuItem *menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:@""]; - GlobalMenuItem *obj = [[[GlobalMenuItem alloc] init] autorelease]; + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; obj->checkable = true; @@ -1613,7 +724,7 @@ bool DisplayServerOSX::global_menu_is_item_checkable(const String &p_menu_root, if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->checkable; } @@ -1629,7 +740,7 @@ Callable DisplayServerOSX::global_menu_get_item_callback(const String &p_menu_ro if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->callback; } @@ -1645,7 +756,7 @@ Variant DisplayServerOSX::global_menu_get_item_tag(const String &p_menu_root, in if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; if (obj) { return obj->meta; } @@ -1661,10 +772,8 @@ String DisplayServerOSX::global_menu_get_item_text(const String &p_menu_root, in if (menu) { const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - char *utfs = strdup([[menu_item title] UTF8String]); String ret; - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[menu_item title] UTF8String]); return ret; } } @@ -1719,7 +828,7 @@ void DisplayServerOSX::global_menu_set_item_checkable(const String &p_menu_root, } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->checkable = p_checkable; } } @@ -1735,7 +844,7 @@ void DisplayServerOSX::global_menu_set_item_callback(const String &p_menu_root, } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->callback = p_callback; } } @@ -1751,7 +860,7 @@ void DisplayServerOSX::global_menu_set_item_tag(const String &p_menu_root, int p } NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { - GlobalMenuItem *obj = [menu_item representedObject]; + GodotMenuItem *obj = [menu_item representedObject]; obj->meta = p_tag; } } @@ -1868,7 +977,6 @@ Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector p_callback.call((const Variant **)&buttonp, 1, ret, ce); } - [window release]; return OK; } @@ -1890,10 +998,8 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, [window runModal]; - char *utfs = strdup([[input stringValue] UTF8String]); String ret; - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[input stringValue] UTF8String]); if (!p_callback.is_null()) { Variant text = ret; @@ -1903,7 +1009,6 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, p_callback.call((const Variant **)&textp, 1, ret, ce); } - [window release]; return OK; } @@ -1914,7 +1019,8 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { return; } - WindowData &wd = windows[MAIN_WINDOW_ID]; + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[window_id]; if (p_mode == MOUSE_MODE_CAPTURED) { // Apple Docs state that the display parameter is not used. // "This parameter is not used. By default, you may pass kCGDirectMainDisplay." @@ -1957,9 +1063,7 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { mouse_mode = p_mode; if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { - CursorShape p_shape = cursor_shape; - cursor_shape = DisplayServer::CURSOR_MAX; - cursor_set_shape(p_shape); + cursor_update_shape(); } } @@ -1967,24 +1071,82 @@ DisplayServer::MouseMode DisplayServerOSX::mouse_get_mode() const { return mouse_mode; } +bool DisplayServerOSX::update_mouse_wrap(WindowData &p_wd, NSPoint &r_delta, NSPoint &r_mpos, NSTimeInterval p_timestamp) { + _THREAD_SAFE_METHOD_ + + if (ignore_warp) { + // Discard late events, before warp. + if (p_timestamp < last_warp) { + return true; + } + ignore_warp = false; + return true; + } + + if (mouse_mode == DisplayServer::MOUSE_MODE_CONFINED || mouse_mode == DisplayServer::MOUSE_MODE_CONFINED_HIDDEN) { + // Discard late events. + if (p_timestamp < last_warp) { + return true; + } + + // Warp affects next event delta, subtract previous warp deltas. + List<WarpEvent>::Element *F = warp_events.front(); + while (F) { + if (F->get().timestamp < p_timestamp) { + List<DisplayServerOSX::WarpEvent>::Element *E = F; + r_delta.x -= E->get().delta.x; + r_delta.y -= E->get().delta.y; + F = F->next(); + warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta. + NSRect frame = [p_wd.window_object frame]; + NSPoint conf_pos = r_mpos; + conf_pos.x = CLAMP(conf_pos.x + r_delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - r_delta.y, 0.f, frame.size.height); + r_delta.x = conf_pos.x - r_mpos.x; + r_delta.y = r_mpos.y - conf_pos.y; + r_mpos = conf_pos; + + // Move mouse cursor. + NSRect point_in_window_rect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[p_wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data. + last_warp = [[NSProcessInfo processInfo] systemUptime]; + + DisplayServerOSX::WarpEvent ev; + ev.timestamp = last_warp; + ev.delta = r_delta; + warp_events.push_back(ev); + } + + return false; +} + void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { _THREAD_SAFE_METHOD_ - if (mouse_mode == MOUSE_MODE_CAPTURED) { - last_mouse_pos = p_to; - } else { - WindowData &wd = windows[MAIN_WINDOW_ID]; + if (mouse_mode != MOUSE_MODE_CAPTURED) { + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + WindowData &wd = windows[window_id]; - //local point in window coords + // Local point in window coords. const NSRect contentRect = [wd.window_view frame]; const float scale = screen_get_max_scale(); NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; - //point in scren coords + // Point in scren coords. CGPoint lMouseWarpPos = { pointOnScreen.x, CGDisplayBounds(CGMainDisplayID()).size.height - pointOnScreen.y }; - //do the warping + // Do the warping. CGEventSourceRef lEventRef = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState); CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); CGAssociateMouseAndMouseCursorPosition(false); @@ -2010,6 +1172,10 @@ Point2i DisplayServerOSX::mouse_get_position() const { return Vector2i(); } +void DisplayServerOSX::mouse_set_button_state(MouseButton p_state) { + last_button_state = p_state; +} + MouseButton DisplayServerOSX::mouse_get_button_state() const { return last_button_state; } @@ -2041,11 +1207,8 @@ String DisplayServerOSX::clipboard_get() const { NSArray *objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options]; NSString *string = [objectsToPaste objectAtIndex:0]; - char *utfs = strdup([string UTF8String]); String ret; - ret.parse_utf8(utfs); - free(utfs); - + ret.parse_utf8([string UTF8String]); return ret; } @@ -2056,48 +1219,6 @@ int DisplayServerOSX::get_screen_count() const { return [screenArray count]; } -// Returns the native top-left screen coordinate of the smallest rectangle -// that encompasses all screens. Needed in get_screen_position(), -// window_get_position, and window_set_position() -// to convert between OS X native screen coordinates and the ones expected by Godot - -static bool displays_arrangement_dirty = true; -static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { - displays_arrangement_dirty = true; -} - -Point2i DisplayServerOSX::_get_screens_origin() const { - static Point2i origin; - - if (displays_arrangement_dirty) { - origin = Point2i(); - - for (int i = 0; i < get_screen_count(); i++) { - Point2i position = _get_native_screen_position(i); - if (position.x < origin.x) { - origin.x = position.x; - } - if (position.y > origin.y) { - origin.y = position.y; - } - } - displays_arrangement_dirty = false; - } - - return origin; -} - -Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); - } - - return Point2i(); -} - Point2i DisplayServerOSX::screen_get_position(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -2107,7 +1228,7 @@ Point2i DisplayServerOSX::screen_get_position(int p_screen) const { Point2i position = _get_native_screen_position(p_screen) - _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot expects a positive value + // Godot expects a positive value. position.y *= -1; return position; } @@ -2121,7 +1242,7 @@ Size2i DisplayServerOSX::screen_get_size(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - // Note: Use frame to get the whole screen size + // Note: Use frame to get the whole screen size. NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale(); } @@ -2200,6 +1321,24 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { return Rect2i(); } +float DisplayServerOSX::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + if (p_screen == SCREEN_OF_MAIN_WINDOW) { + p_screen = window_get_current_screen(); + } + + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; + const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); + const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode); + return (float)displayRefreshRate; + } + ERR_PRINT("An error occurred while trying to get the screen refresh rate."); + return SCREEN_REFRESH_RATE_FALLBACK; +} + Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const { _THREAD_SAFE_METHOD_ @@ -2233,56 +1372,6 @@ void DisplayServerOSX::show_window(WindowID p_id) { } } -void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) { - _THREAD_SAFE_METHOD_ - - if (!wd.event_callback.is_null()) { - Variant event = int(p_event); - Variant *eventp = &event; - Variant ret; - Callable::CallError ce; - wd.event_callback.call((const Variant **)&eventp, 1, ret, ce); - } -} - -DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) { - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().window_object == p_window) { - return E->key(); - } - } - return INVALID_WINDOW_ID; -} - -void DisplayServerOSX::_update_window(WindowData p_wd) { - bool borderless_full = false; - - if (p_wd.borderless) { - NSRect frameRect = [p_wd.window_object frame]; - NSRect screenRect = [[p_wd.window_object screen] frame]; - - // Check if our window covers up the screen - if (frameRect.origin.x <= screenRect.origin.x && frameRect.origin.y <= frameRect.origin.y && - frameRect.size.width >= screenRect.size.width && frameRect.size.height >= screenRect.size.height) { - borderless_full = true; - } - } - - if (borderless_full) { - // If the window covers up the screen set the level to above the main menu and hide on deactivate - [p_wd.window_object setLevel:NSMainMenuWindowLevel + 1]; - [p_wd.window_object setHidesOnDeactivate:YES]; - } else { - // Reset these when our window is not a borderless window that covers up the screen - if (p_wd.on_top && !p_wd.fullscreen) { - [p_wd.window_object setLevel:NSFloatingWindowLevel]; - } else { - [p_wd.window_object setLevel:NSNormalWindowLevel]; - } - [p_wd.window_object setHidesOnDeactivate:NO]; - } -} - void DisplayServerOSX::delete_sub_window(WindowID p_id) { _THREAD_SAFE_METHOD_ @@ -2295,24 +1384,6 @@ void DisplayServerOSX::delete_sub_window(WindowID p_id) { [wd.window_object close]; } -void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[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_ @@ -2351,6 +1422,24 @@ void DisplayServerOSX::window_set_drop_files_callback(const Callable &p_callable wd.drop_files_callback = p_callable; } +void DisplayServerOSX::window_set_title(const String &p_title, WindowID p_window) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd = windows[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; +} + int DisplayServerOSX::window_get_current_screen(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), -1); @@ -2382,39 +1471,6 @@ void DisplayServerOSX::window_set_current_screen(int p_screen, WindowID p_window } } -void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) { - _THREAD_SAFE_METHOD_ - ERR_FAIL_COND(p_window == p_parent); - - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd_window = windows[p_window]; - - ERR_FAIL_COND(wd_window.transient_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)); - - WindowData &wd_parent = windows[wd_window.transient_parent]; - - wd_window.transient_parent = INVALID_WINDOW_ID; - wd_parent.transient_children.erase(p_window); - - [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } 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"); - WindowData &wd_parent = windows[p_parent]; - - wd_window.transient_parent = p_parent; - wd_parent.transient_children.insert(p_window); - - [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - } -} - Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { _THREAD_SAFE_METHOD_ @@ -2426,14 +1482,14 @@ Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { const NSRect nsrect = [wd.window_object convertRectToScreen:contentRect]; Point2i pos; - // Return the position of the top-left corner, for OS X the y starts at the bottom + // Return the position of the top-left corner, for OS X the y starts at the bottom. const float scale = screen_get_max_scale(); pos.x = nsrect.origin.x; pos.y = (nsrect.origin.y + nsrect.size.height); pos *= scale; pos -= _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot expects a positive value + // Godot expects a positive value. pos.y *= -1; return pos; } @@ -2446,7 +1502,7 @@ void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p Point2i position = p_position; // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot passes a positive value + // Godot passes a positive value. position.y *= -1; position += _get_screens_origin(); position /= screen_get_max_scale(); @@ -2462,8 +1518,41 @@ void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x - offset.x, position.y - offset.y)]; - _update_window(wd); - _ALLOW_DISCARD_ _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + _update_window_style(wd); + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); +} + +void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent) { + _THREAD_SAFE_METHOD_ + ERR_FAIL_COND(p_window == p_parent); + + ERR_FAIL_COND(!windows.has(p_window)); + WindowData &wd_window = windows[p_window]; + + ERR_FAIL_COND(wd_window.transient_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)); + + WindowData &wd_parent = windows[wd_window.transient_parent]; + + wd_window.transient_parent = INVALID_WINDOW_ID; + wd_parent.transient_children.erase(p_window); + + [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + } 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"); + WindowData &wd_parent = windows[p_parent]; + + wd_window.transient_parent = p_parent; + wd_parent.transient_children.insert(p_window); + + [wd_window.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + } } void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) { @@ -2544,7 +1633,7 @@ void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) { [wd.window_object setFrame:new_frame display:YES]; - _update_window(wd); + _update_window_style(wd); } Size2i DisplayServerOSX::window_get_size(WindowID p_window) const { @@ -2564,73 +1653,6 @@ Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); } -bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { - return true; -} - -void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window) { - ERR_FAIL_COND(!windows.has(p_window)); - WindowData &wd = windows[p_window]; - - if (!OS_OSX::get_singleton()->is_layered_allowed()) { - return; - } - if (wd.layered_window != p_enabled) { - if (p_enabled) { - [wd.window_object setBackgroundColor:[NSColor clearColor]]; - [wd.window_object setOpaque:NO]; - [wd.window_object setHasShadow:NO]; - CALayer *layer = [wd.window_view layer]; - if (layer) { - [layer setOpaque:NO]; - } -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif - wd.layered_window = true; - } else { - [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; - [wd.window_object setOpaque:YES]; - [wd.window_object setHasShadow:YES]; - CALayer *layer = [wd.window_view layer]; - if (layer) { - [layer setOpaque:YES]; - } -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif - wd.layered_window = false; - } -#if defined(GLES3_ENABLED) - if (gl_manager) { - //TODO - reimplement OpenGLES - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - //TODO - implement transparency for Vulkan - } -#endif - NSRect frameRect = [wd.window_object frame]; - [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:YES]; - [wd.window_object setFrame:frameRect display:YES]; - } -} - void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2639,22 +1661,21 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { WindowMode old_mode = window_get_mode(p_window); if (old_mode == p_mode) { - return; // do nothing + return; // Do nothing. } switch (old_mode) { case WINDOW_MODE_WINDOWED: { - //do nothing + // Do nothing. } break; case WINDOW_MODE_MINIMIZED: { [wd.window_object deminiaturize:nil]; } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { [wd.window_object setLevel:NSNormalWindowLevel]; - if (wd.layered_window) { - _set_window_per_pixel_transparency_enabled(true, p_window); - } - if (wd.resize_disabled) { //restore resize disabled + _set_window_per_pixel_transparency_enabled(true, p_window); + if (wd.resize_disabled) { // Restore resize disabled. [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; } if (wd.min_size != Size2i()) { @@ -2677,16 +1698,17 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { switch (p_mode) { case WINDOW_MODE_WINDOWED: { - //do nothing + // Do nothing. } break; case WINDOW_MODE_MINIMIZED: { [wd.window_object performMiniaturize:nil]; } break; + case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { - if (wd.layered_window) - _set_window_per_pixel_transparency_enabled(false, p_window); - if (wd.resize_disabled) //fullscreen window should be resizable to work + _set_window_per_pixel_transparency_enabled(false, p_window); + if (wd.resize_disabled) { // Fullscreen window should be resizable to work. [wd.window_object setStyleMask:[wd.window_object styleMask] | NSWindowStyleMaskResizable]; + } [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; [wd.window_object toggleFullScreen:nil]; @@ -2706,7 +1728,7 @@ DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) c ERR_FAIL_COND_V(!windows.has(p_window), WINDOW_MODE_WINDOWED); const WindowData &wd = windows[p_window]; - if (wd.fullscreen) { //if fullscreen, it's not in another mode + if (wd.fullscreen) { // If fullscreen, it's not in another mode. return WINDOW_MODE_FULLSCREEN; } if ([wd.window_object isZoomed] && !wd.resize_disabled) { @@ -2718,10 +1740,14 @@ DisplayServer::WindowMode DisplayServerOSX::window_get_mode(WindowID p_window) c } } - // all other discarded, return windowed. + // All other discarded, return windowed. return WINDOW_MODE_WINDOWED; } +bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { + return true; +} + void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2731,7 +1757,7 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { wd.resize_disabled = p_enabled; - if (wd.fullscreen) { //fullscreen window should be resizable, style will be applied on exiting fs + if (wd.fullscreen) { // Fullscreen window should be resizable, style will be applied on exiting fullscreen. return; } if (p_enabled) { @@ -2741,7 +1767,7 @@ 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 + // OrderOut prevents a lose focus bug with the window. if ([wd.window_object isVisible]) { [wd.window_object orderOut:nil]; } @@ -2749,15 +1775,14 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo if (p_enabled) { [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; } else { - if (wd.layered_window) - _set_window_per_pixel_transparency_enabled(false, p_window); + _set_window_per_pixel_transparency_enabled(false, p_window); [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; - // Force update of the window styles + // Force update of the window styles. NSRect frameRect = [wd.window_object frame]; [wd.window_object setFrame:NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width + 1, frameRect.size.height) display:NO]; [wd.window_object setFrame:frameRect display:NO]; } - _update_window(wd); + _update_window_style(wd); if ([wd.window_object isVisible]) { if (wd.no_focus) { [wd.window_object orderFront:nil]; @@ -2778,9 +1803,8 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } } break; case WINDOW_FLAG_TRANSPARENT: { - wd.layered_window = p_enabled; if (p_enabled) { - [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // force borderless + [wd.window_object setStyleMask:NSWindowStyleMaskBorderless]; // Force borderless. } else if (!wd.borderless) { [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; } @@ -2883,28 +1907,103 @@ void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_ wd.im_position = p_pos; } -bool DisplayServerOSX::get_swap_cancel_ok() { - return false; +DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { + Point2i position = p_position; + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if ([E->get().window_object windowNumber] == wnum) { + return E->key(); + } + } + return INVALID_WINDOW_ID; } -void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { +int64_t DisplayServerOSX::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].window_object; + } + case WINDOW_VIEW: { + return (int64_t)windows[p_window].window_view; + } + default: { + return 0; + } + } +} + +void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { _THREAD_SAFE_METHOD_ - ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + ERR_FAIL_COND(!windows.has(p_window)); + windows[p_window].instance_id = p_instance; +} - if (cursor_shape == p_shape) { - return; +ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); + return windows[p_window].instance_id; +} + +void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) { +#if defined(GLES3_ENABLED) + gl_manager->window_make_current(p_window_id); +#endif +} + +void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + gl_manager->set_use_vsync(p_vsync_mode); } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + context_vulkan->set_vsync_mode(p_window, p_vsync_mode); + } +#endif +} - if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { - cursor_shape = p_shape; - return; +DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const { + _THREAD_SAFE_METHOD_ +#if defined(GLES3_ENABLED) + if (gl_manager) { + return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); } +#endif +#if defined(VULKAN_ENABLED) + if (context_vulkan) { + return context_vulkan->get_vsync_mode(p_window); + } +#endif + return DisplayServer::VSYNC_ENABLED; +} + +Point2i DisplayServerOSX::ime_get_selection() const { + return im_selection; +} + +String DisplayServerOSX::ime_get_text() const { + return im_text; +} - if (cursors[p_shape] != nullptr) { - [cursors[p_shape] set]; +void DisplayServerOSX::cursor_update_shape() { + _THREAD_SAFE_METHOD_ + + if (cursors[cursor_shape] != nullptr) { + [cursors[cursor_shape] set]; } else { - switch (p_shape) { + switch (cursor_shape) { case CURSOR_ARROW: [[NSCursor arrowCursor] set]; break; @@ -2933,16 +2032,16 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { [[NSCursor operationNotAllowedCursor] set]; break; case CURSOR_VSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthSouthCursor), @selector(resizeUpDownCursor)) set]; break; case CURSOR_HSIZE: - [_cursorFromSelector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeEastWestCursor), @selector(resizeLeftRightCursor)) set]; break; case CURSOR_BDIAGSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthEastSouthWestCursor)) set]; break; case CURSOR_FDIAGSIZE: - [_cursorFromSelector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; + [_cursor_from_selector(@selector(_windowResizeNorthWestSouthEastCursor)) set]; break; case CURSOR_MOVE: [[NSCursor arrowCursor] set]; @@ -2954,14 +2053,30 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { [[NSCursor resizeLeftRightCursor] set]; break; case CURSOR_HELP: - [_cursorFromSelector(@selector(_helpCursor)) set]; + [_cursor_from_selector(@selector(_helpCursor)) set]; break; default: { } } } +} + +void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_INDEX(p_shape, CURSOR_MAX); + + if (cursor_shape == p_shape) { + return; + } cursor_shape = p_shape; + + if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { + return; + } + + cursor_update_shape(); } DisplayServerOSX::CursorShape DisplayServerOSX::cursor_get_shape() const { @@ -3056,7 +2171,6 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape NSCursor *cursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSMakePoint(p_hotspot.x, p_hotspot.y)]; - [cursors[p_shape] release]; cursors[p_shape] = cursor; Vector<Variant> params; @@ -3069,94 +2183,32 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape [cursor set]; } } - - [imgrep release]; - [nsimage release]; } else { - // Reset to default system cursor + // Reset to default system cursor. if (cursors[p_shape] != nullptr) { - [cursors[p_shape] release]; cursors[p_shape] = nullptr; } - CursorShape c = cursor_shape; - cursor_shape = CURSOR_MAX; - cursor_set_shape(c); + cursor_update_shape(); cursors_cache.erase(p_shape); } } -struct LayoutInfo { - String name; - String code; -}; - -static Vector<LayoutInfo> kbd_layouts; -static int current_layout = 0; -static bool keyboard_layout_dirty = true; -static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) { - kbd_layouts.clear(); - current_layout = 0; - keyboard_layout_dirty = true; -} - -void _update_keyboard_layouts() { - @autoreleasepool { - TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource(); - NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName); - CFRelease(cur_source); - - // Enum IME layouts - NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); - for (NSUInteger i = 0; i < [list_ime count]; i++) { - LayoutInfo ly; - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages); - ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); - kbd_layouts.push_back(ly); - - if ([name isEqualToString:cur_name]) { - current_layout = kbd_layouts.size() - 1; - } - } - [list_ime release]; - - // Enum plain keyboard layouts - NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; - NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); - for (NSUInteger i = 0; i < [list_kbd count]; i++) { - LayoutInfo ly; - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); - ly.name.parse_utf8([name UTF8String]); - - NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages); - ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]); - kbd_layouts.push_back(ly); - - if ([name isEqualToString:cur_name]) { - current_layout = kbd_layouts.size() - 1; - } - } - [list_kbd release]; - } - - keyboard_layout_dirty = false; +bool DisplayServerOSX::get_swap_cancel_ok() { + return false; } int DisplayServerOSX::keyboard_get_layout_count() const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts(); } return kbd_layouts.size(); } void DisplayServerOSX::keyboard_set_current_layout(int p_index) { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts(); } ERR_FAIL_INDEX(p_index, kbd_layouts.size()); @@ -3164,31 +2216,29 @@ void DisplayServerOSX::keyboard_set_current_layout(int p_index) { NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()]; NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout }; - NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false); + NSArray *list_kbd = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_kbd, false); for (NSUInteger i = 0; i < [list_kbd count]; i++) { - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName); if ([name isEqualToString:cur_name]) { - TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]); + TISSelectInputSource((__bridge TISInputSourceRef)[list_kbd objectAtIndex:i]); break; } } - [list_kbd release]; NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode }; - NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false); + NSArray *list_ime = (__bridge NSArray *)TISCreateInputSourceList((__bridge CFDictionaryRef)filter_ime, false); for (NSUInteger i = 0; i < [list_ime count]; i++) { - NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); + NSString *name = (__bridge NSString *)TISGetInputSourceProperty((__bridge TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName); if ([name isEqualToString:cur_name]) { - TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]); + TISSelectInputSource((__bridge TISInputSourceRef)[list_ime objectAtIndex:i]); break; } } - [list_ime release]; } int DisplayServerOSX::keyboard_get_current_layout() const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts(); } return current_layout; @@ -3196,7 +2246,7 @@ int DisplayServerOSX::keyboard_get_current_layout() const { String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts(); } ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); @@ -3205,7 +2255,7 @@ String DisplayServerOSX::keyboard_get_layout_language(int p_index) const { String DisplayServerOSX::keyboard_get_layout_name(int p_index) const { if (keyboard_layout_dirty) { - _update_keyboard_layouts(); + const_cast<DisplayServerOSX *>(this)->_update_keyboard_layouts(); } ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), ""); @@ -3219,124 +2269,8 @@ Key DisplayServerOSX::keyboard_get_keycode_from_physical(Key p_keycode) const { Key modifiers = p_keycode & KeyModifierMask::MODIFIER_MASK; Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK; - unsigned int osx_keycode = unmapKey((Key)keycode_no_mod); - return (Key)(remapKey(osx_keycode, 0) | modifiers); -} - -void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) { - Ref<InputEvent> ev = p_event; - Input::get_singleton()->parse_input_event(ev); -} - -void DisplayServerOSX::_release_pressed_events() { - _THREAD_SAFE_METHOD_ - if (Input::get_singleton()) { - Input::get_singleton()->release_pressed_events(); - } -} - -NSMenu *DisplayServerOSX::_get_dock_menu() const { - return dock_menu; -} - -void DisplayServerOSX::_menu_callback(id p_sender) { - if (![p_sender representedObject]) { - return; - } - - GlobalMenuItem *value = [p_sender representedObject]; - - if (value) { - if (value->checkable) { - if ([p_sender state] == NSControlStateValueOff) { - [p_sender setState:NSControlStateValueOn]; - } else { - [p_sender setState:NSControlStateValueOff]; - } - } - - if (value->callback != Callable()) { - Variant tag = value->meta; - Variant *tagp = &tag; - Variant ret; - Callable::CallError ce; - value->callback.call((const Variant **)&tagp, 1, ret, ce); - } - } -} - -void DisplayServerOSX::_send_event(NSEvent *p_event) { - // special case handling of command-period, which is traditionally a special - // shortcut in macOS and doesn't arrive at our regular keyDown handler. - if ([p_event type] == NSEventTypeKeyDown) { - if (([p_event modifierFlags] & NSEventModifierFlagCommand) && [p_event keyCode] == 0x2f) { - Ref<InputEventKey> k; - k.instantiate(); - - _get_key_modifier_state([p_event modifierFlags], k); - k->set_window_id(DisplayServerOSX::INVALID_WINDOW_ID); - k->set_pressed(true); - k->set_keycode(Key::PERIOD); - k->set_physical_keycode(Key::PERIOD); - k->set_echo([p_event isARepeat]); - - Input::get_singleton()->parse_input_event(k); - } - } -} - -void DisplayServerOSX::_process_key_events() { - Ref<InputEventKey> k; - for (int i = 0; i < key_event_pos; i++) { - const KeyEvent &ke = key_event_buffer[i]; - if (ke.raw) { - // Non IME input - no composite characters, pass events as is - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(ke.keycode); - k->set_physical_keycode((Key)ke.physical_keycode); - k->set_unicode(ke.unicode); - - _push_input(k); - } else { - // IME input - if ((i == 0 && ke.keycode == Key::NONE) || (i > 0 && key_event_buffer[i - 1].keycode == Key::NONE)) { - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(Key::NONE); - k->set_physical_keycode(Key::NONE); - k->set_unicode(ke.unicode); - - _push_input(k); - } - if (ke.keycode != Key::NONE) { - k.instantiate(); - - k->set_window_id(ke.window_id); - _get_key_modifier_state(ke.osx_state, k); - k->set_pressed(ke.pressed); - k->set_echo(ke.echo); - k->set_keycode(ke.keycode); - k->set_physical_keycode((Key)ke.physical_keycode); - - if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) { - k->set_unicode(key_event_buffer[i + 1].unicode); - } - - _push_input(k); - } - } - } - - key_event_pos = 0; + unsigned int osx_keycode = KeyMappingOSX::unmap_key((Key)keycode_no_mod); + return (Key)(KeyMappingOSX::remap_key(osx_keycode, 0) | modifiers); } void DisplayServerOSX::process_events() { @@ -3364,8 +2298,8 @@ void DisplayServerOSX::process_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)) { + update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + if (Geometry2D::is_point_in_polygon(wd.mouse_pos, wd.mpath)) { if ([wd.window_object ignoresMouseEvents]) { [wd.window_object setIgnoresMouseEvents:NO]; } @@ -3380,9 +2314,6 @@ void DisplayServerOSX::process_events() { } } } - - [autoreleasePool drain]; - autoreleasePool = [[NSAutoreleasePool alloc] init]; } void DisplayServerOSX::force_process_and_drop_events() { @@ -3393,6 +2324,18 @@ void DisplayServerOSX::force_process_and_drop_events() { drop_events = false; } +void DisplayServerOSX::release_rendering_thread() { +} + +void DisplayServerOSX::make_rendering_thread() { +} + +void DisplayServerOSX::swap_buffers() { +#if defined(GLES3_ENABLED) + gl_manager->swap_buffers(); +#endif +} + void DisplayServerOSX::set_native_icon(const String &p_filename) { _THREAD_SAFE_METHOD_ @@ -3405,10 +2348,10 @@ void DisplayServerOSX::set_native_icon(const String &p_filename) { f->get_buffer((uint8_t *)&data.write[0], len); memdelete(f); - NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease]; + NSData *icon_data = [[NSData alloc] initWithBytes:&data.write[0] length:len]; ERR_FAIL_COND_MSG(!icon_data, "Error reading icon data."); - NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease]; + NSImage *icon = [[NSImage alloc] initWithData:icon_data]; ERR_FAIL_COND_MSG(!icon, "Error loading icon."); [NSApp setApplicationIconImage:icon]; @@ -3451,94 +2394,6 @@ void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) { [nsimg addRepresentation:imgrep]; [NSApp setApplicationIconImage:nsimg]; - - [imgrep release]; - [nsimg release]; -} - -void DisplayServerOSX::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->swap_buffers(); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->set_vsync_mode(p_window, p_vsync_mode); - } -#endif -} - -DisplayServer::VSyncMode DisplayServerOSX::window_get_vsync_mode(WindowID p_window) const { - _THREAD_SAFE_METHOD_ -#if defined(GLES3_ENABLED) - if (gl_manager) { - return (gl_manager->is_using_vsync() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - return context_vulkan->get_vsync_mode(p_window); - } -#endif - return DisplayServer::VSYNC_ENABLED; -} - -Vector<String> DisplayServerOSX::get_rendering_drivers_func() { - Vector<String> drivers; - -#if defined(VULKAN_ENABLED) - drivers.push_back("vulkan"); -#endif -#if defined(GLES3_ENABLED) - drivers.push_back("opengl3"); -#endif - - return drivers; -} - -void DisplayServerOSX::gl_window_make_current(DisplayServer::WindowID p_window_id) { -#if defined(GLES3_ENABLED) - gl_manager->window_make_current(p_window_id); -#endif -} - -Point2i DisplayServerOSX::ime_get_selection() const { - return im_selection; -} - -String DisplayServerOSX::ime_get_text() const { - return im_text; -} - -DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { - Point2i position = p_position; - position.y *= -1; - position += _get_screens_origin(); - position /= screen_get_max_scale(); - - NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if ([E->get().window_object windowNumber] == wnum) { - return E->key(); - } - } - return INVALID_WINDOW_ID; -} - -void DisplayServerOSX::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND(!windows.has(p_window)); - windows[p_window].instance_id = p_instance; -} - -ObjectID DisplayServerOSX::window_get_attached_instance_id(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - - ERR_FAIL_COND_V(!windows.has(p_window), ObjectID()); - return windows[p_window].instance_id; } DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { @@ -3549,179 +2404,48 @@ DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, W return ds; } -DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect) { - WindowID id; - const float scale = screen_get_max_scale(); - { - WindowData wd; - - wd.window_delegate = [[GodotWindowDelegate alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); - [wd.window_delegate setWindowID:window_id_counter]; - - Point2i position = p_rect.position; - // OS X native y-coordinate relative to _get_screens_origin() is negative, - // Godot passes a positive value - position.y *= -1; - position += _get_screens_origin(); - - // initWithContentRect uses bottom-left corner of the window’s frame as origin. - wd.window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) - styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable - backing:NSBackingStoreBuffered - defer:NO]; - ERR_FAIL_COND_V_MSG(wd.window_object == nil, INVALID_WINDOW_ID, "Can't create a window"); - - wd.window_view = [[GodotContentView alloc] init]; - ERR_FAIL_COND_V_MSG(wd.window_view == nil, INVALID_WINDOW_ID, "Can't create a window view"); - [wd.window_view setWindowID:window_id_counter]; - [wd.window_view setWantsLayer:TRUE]; - - [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - [wd.window_object setContentView:wd.window_view]; - [wd.window_object setDelegate:wd.window_delegate]; - [wd.window_object setAcceptsMouseMovedEvents:YES]; - [wd.window_object setRestorable:NO]; - [wd.window_object setColorSpace:[NSColorSpace sRGBColorSpace]]; - - if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { - [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; - } - - CALayer *layer = [wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } +Vector<String> DisplayServerOSX::get_rendering_drivers_func() { + Vector<String> drivers; #if defined(VULKAN_ENABLED) - if (context_vulkan) { - Error err = context_vulkan->window_create(window_id_counter, p_vsync_mode, wd.window_view, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); - } -#endif -#if defined(GLES3_ENABLED) - if (gl_manager) { - Error err = gl_manager->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL context"); - } + drivers.push_back("vulkan"); #endif - id = window_id_counter++; - windows[id] = wd; - } - - WindowData &wd = windows[id]; - window_set_mode(p_mode, id); - - const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * scale; - wd.size.height = contentRect.size.height * scale; - - CALayer *layer = [wd.window_view layer]; - if (layer) { - layer.contentsScale = scale; - } - #if defined(GLES3_ENABLED) - if (gl_manager) { - gl_manager->window_resize(id, wd.size.width, wd.size.height); - } -#endif -#if defined(VULKAN_ENABLED) - if (context_vulkan) { - context_vulkan->window_resize(id, wd.size.width, wd.size.height); - } + drivers.push_back("opengl3"); #endif - return id; -} - -void DisplayServerOSX::_dispatch_input_events(const Ref<InputEvent> &p_event) { - ((DisplayServerOSX *)(get_singleton()))->_dispatch_input_event(p_event); -} - -void DisplayServerOSX::_dispatch_input_event(const Ref<InputEvent> &p_event) { - _THREAD_SAFE_METHOD_ - if (!in_dispatch_input_event) { - in_dispatch_input_event = true; - - Variant ev = p_event; - Variant *evp = &ev; - Variant ret; - Callable::CallError ce; - - Ref<InputEventFromWindow> event_from_window = p_event; - if (event_from_window.is_valid() && event_from_window->get_window_id() != INVALID_WINDOW_ID) { - //send to a window - if (windows.has(event_from_window->get_window_id())) { - Callable callable = windows[event_from_window->get_window_id()].input_event_callback; - if (callable.is_null()) { - return; - } - callable.call((const Variant **)&evp, 1, ret, ce); - } - } else { - //send to all windows - for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Callable callable = E->get().input_event_callback; - if (callable.is_null()) { - continue; - } - callable.call((const Variant **)&evp, 1, ret, ce); - } - } - - in_dispatch_input_event = false; - } -} - -void DisplayServerOSX::release_rendering_thread() { -} - -void DisplayServerOSX::make_rendering_thread() { + return drivers; } -void DisplayServerOSX::swap_buffers() { -#if defined(GLES3_ENABLED) - gl_manager->swap_buffers(); -#endif +void DisplayServerOSX::register_osx_driver() { + register_create_function("osx", create_func, get_rendering_drivers_func); } DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); r_error = OK; - drop_events = false; memset(cursors, 0, sizeof(cursors)); - cursor_shape = CURSOR_ARROW; - - key_event_pos = 0; - mouse_mode = MOUSE_MODE_VISIBLE; - autoreleasePool = [[NSAutoreleasePool alloc] init]; + event_source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + ERR_FAIL_COND(!event_source); - eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); - ERR_FAIL_COND(!eventSource); - - CGEventSourceSetLocalEventsSuppressionInterval(eventSource, 0.0); - - keyboard_layout_dirty = true; - displays_arrangement_dirty = true; + CGEventSourceSetLocalEventsSuppressionInterval(event_source, 0.0); int screen_count = get_screen_count(); for (int i = 0; i < screen_count; i++) { display_max_scale = fmax(display_max_scale, screen_get_scale(i)); } - // Register to be notified on keyboard layout changes + // Register to be notified on keyboard layout changes. CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), - nullptr, keyboard_layout_changed, + nullptr, _keyboard_layout_changed, kTISNotifySelectedKeyboardInputSourceChanged, nullptr, CFNotificationSuspensionBehaviorDeliverImmediately); - // Register to be notified on displays arrangement changes - CGDisplayRegisterReconfigurationCallback(displays_arrangement_changed, nullptr); + // Register to be notified on displays arrangement changes. + CGDisplayRegisterReconfigurationCallback(_displays_arrangement_changed, nullptr); NSMenuItem *menu_item; NSString *title; @@ -3731,11 +2455,11 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode nsappname = [[NSProcessInfo processInfo] processName]; } - // Setup Dock menu + // Setup Dock menu. dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; - // Setup Apple menu - apple_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + // Setup Apple menu. + apple_menu = [[NSMenu alloc] initWithTitle:@""]; title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""]; @@ -3745,7 +2469,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode menu_item = [apple_menu addItemWithTitle:NSLocalizedString(@"Services", nil) action:nil keyEquivalent:@""]; [apple_menu setSubmenu:services forItem:menu_item]; [NSApp setServicesMenu:services]; - [services release]; [apple_menu addItem:[NSMenuItem separatorItem]]; @@ -3762,7 +2485,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - // Add items to the menu bar + // Add items to the menu bar. NSMenu *main_menu = [NSApp mainMenu]; menu_item = [main_menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [main_menu setSubmenu:apple_menu forItem:menu_item]; @@ -3824,15 +2547,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode } DisplayServerOSX::~DisplayServerOSX() { - if (dock_menu) { - [dock_menu release]; - } - - for (Map<String, NSMenu *>::Element *E = submenu.front(); E; E = E->next()) { - [E->get() release]; - } - - //destroy all windows + // Destroy all windows. for (Map<WindowID, WindowData>::Element *E = windows.front(); E;) { Map<WindowID, WindowData>::Element *F = E; E = E->next(); @@ -3840,7 +2555,7 @@ DisplayServerOSX::~DisplayServerOSX() { [F->get().window_object close]; } - //destroy drivers + // Destroy drivers. #if defined(GLES3_ENABLED) if (gl_manager) { memdelete(gl_manager); @@ -3861,11 +2576,7 @@ DisplayServerOSX::~DisplayServerOSX() { #endif CFNotificationCenterRemoveObserver(CFNotificationCenterGetDistributedCenter(), nullptr, kTISNotifySelectedKeyboardInputSourceChanged, nullptr); - CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, nullptr); + CGDisplayRemoveReconfigurationCallback(_displays_arrangement_changed, nullptr); cursors_cache.clear(); } - -void DisplayServerOSX::register_osx_driver() { - register_create_function("osx", create_func, get_rendering_drivers_func); -} diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp index 4d1f72f5c9..f0b58efb63 100644 --- a/platform/osx/export/export_plugin.cpp +++ b/platform/osx/export/export_plugin.cpp @@ -777,6 +777,24 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources"); } + Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations"); + if (translations.size() > 0) { + { + String fname = tmp_app_path_name + "/Contents/Resources/en.lproj"; + tmp_app_dir->make_dir_recursive(fname); + FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + } + + for (const String &E : translations) { + Ref<Translation> tr = ResourceLoader::load(E); + if (tr.is_valid()) { + String fname = tmp_app_path_name + "/Contents/Resources/" + tr->get_locale() + ".lproj"; + tmp_app_dir->make_dir_recursive(fname); + FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE); + } + } + } + // Now process our template. bool found_binary = false; Vector<String> dylibs_found; diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h index 0c2ac90206..931ce7e41a 100644 --- a/platform/osx/export/export_plugin.h +++ b/platform/osx/export/export_plugin.h @@ -87,7 +87,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { for (int i = 0; i < pname.length(); i++) { char32_t c = pname[i]; - if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) { + if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) { if (r_error) { *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c)); } diff --git a/platform/osx/gl_manager_osx.h b/platform/osx/gl_manager_osx_legacy.h index 0229b672a2..b5a1b9dd98 100644 --- a/platform/osx/gl_manager_osx.h +++ b/platform/osx/gl_manager_osx_legacy.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gl_manager_osx.h */ +/* gl_manager_osx_legacy.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GL_MANAGER_OSX_H -#define GL_MANAGER_OSX_H +#ifndef GL_MANAGER_OSX_LEGACY_H +#define GL_MANAGER_OSX_LEGACY_H #if defined(OSX_ENABLED) && defined(GLES3_ENABLED) @@ -50,29 +50,21 @@ public: private: struct GLWindow { - GLWindow() { in_use = false; } - bool in_use; + int width = 0; + int height = 0; - DisplayServer::WindowID window_id; - int width; - int height; - - id window_view; - NSOpenGLContext *context; + id window_view = nullptr; + NSOpenGLContext *context = nullptr; }; - LocalVector<GLWindow> _windows; - - NSOpenGLContext *_shared_context = nullptr; - GLWindow *_current_window; + Map<DisplayServer::WindowID, GLWindow> windows; - Error _create_context(GLWindow &win); - void _internal_set_current_window(GLWindow *p_win); + NSOpenGLContext *shared_context = nullptr; + DisplayServer::WindowID current_window = DisplayServer::INVALID_WINDOW_ID; - GLWindow &get_window(unsigned int id) { return _windows[id]; } - const GLWindow &get_window(unsigned int id) const { return _windows[id]; } + Error create_context(GLWindow &win); - bool use_vsync; + bool use_vsync = false; ContextType context_type; public: @@ -80,7 +72,6 @@ public: void window_destroy(DisplayServer::WindowID p_window_id); void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); - // get directly from the cached GLWindow int window_get_width(DisplayServer::WindowID p_window_id = 0); int window_get_height(DisplayServer::WindowID p_window_id = 0); @@ -91,6 +82,7 @@ public: void window_make_current(DisplayServer::WindowID p_window_id); void window_update(DisplayServer::WindowID p_window_id); + void window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled); Error initialize(); @@ -101,6 +93,5 @@ public: ~GLManager_OSX(); }; -#endif // defined(OSX_ENABLED) && defined(GLES3_ENABLED) - -#endif // GL_MANAGER_OSX_H +#endif // OSX_ENABLED && GLES3_ENABLED +#endif // GL_MANAGER_OSX_LEGACY_H diff --git a/platform/osx/gl_manager_osx.mm b/platform/osx/gl_manager_osx_legacy.mm index 3e70de8523..fbe64e32a3 100644 --- a/platform/osx/gl_manager_osx.mm +++ b/platform/osx/gl_manager_osx_legacy.mm @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gl_manager_osx.mm */ +/* gl_manager_osx_legacy.mm */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gl_manager_osx.h" +#include "gl_manager_osx_legacy.h" #ifdef OSX_ENABLED #ifdef GLES3_ENABLED @@ -36,7 +36,7 @@ #include <stdio.h> #include <stdlib.h> -Error GLManager_OSX::_create_context(GLWindow &win) { +Error GLManager_OSX::create_context(GLWindow &win) { NSOpenGLPixelFormatAttribute attributes[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAClosestPolicy, @@ -50,10 +50,10 @@ Error GLManager_OSX::_create_context(GLWindow &win) { NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; ERR_FAIL_COND_V(pixel_format == nil, ERR_CANT_CREATE); - win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:_shared_context]; + win.context = [[NSOpenGLContext alloc] initWithFormat:pixel_format shareContext:shared_context]; ERR_FAIL_COND_V(win.context == nil, ERR_CANT_CREATE); - if (_shared_context == nullptr) { - _shared_context = win.context; + if (shared_context == nullptr) { + shared_context = win.context; } [win.context setView:win.window_view]; @@ -63,40 +63,27 @@ Error GLManager_OSX::_create_context(GLWindow &win) { } Error GLManager_OSX::window_create(DisplayServer::WindowID p_window_id, id p_view, int p_width, int p_height) { - if (p_window_id >= (int)_windows.size()) { - _windows.resize(p_window_id + 1); - } - - GLWindow &win = _windows[p_window_id]; - win.in_use = true; - win.window_id = p_window_id; + GLWindow win; win.width = p_width; win.height = p_height; win.window_view = p_view; - if (_create_context(win) != OK) { - _windows.remove_at(_windows.size() - 1); + if (create_context(win) != OK) { return FAILED; } - window_make_current(_windows.size() - 1); + windows[p_window_id] = win; + window_make_current(p_window_id); return OK; } -void GLManager_OSX::_internal_set_current_window(GLWindow *p_win) { - _current_window = p_win; -} - void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) { - if (p_window_id == -1) { + if (!windows.has(p_window_id)) { return; } - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { - return; - } + GLWindow &win = windows[p_window_id]; win.width = p_width; win.height = p_height; @@ -116,24 +103,37 @@ void GLManager_OSX::window_resize(DisplayServer::WindowID p_window_id, int p_wid } int GLManager_OSX::window_get_width(DisplayServer::WindowID p_window_id) { - return get_window(p_window_id).width; + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.width; } int GLManager_OSX::window_get_height(DisplayServer::WindowID p_window_id) { - return get_window(p_window_id).height; + if (!windows.has(p_window_id)) { + return 0; + } + + GLWindow &win = windows[p_window_id]; + return win.height; } void GLManager_OSX::window_destroy(DisplayServer::WindowID p_window_id) { - GLWindow &win = get_window(p_window_id); - win.in_use = false; + if (!windows.has(p_window_id)) { + return; + } - if (_current_window == &win) { - _current_window = nullptr; + if (current_window == p_window_id) { + current_window = DisplayServer::INVALID_WINDOW_ID; } + + windows.erase(p_window_id); } void GLManager_OSX::release_current() { - if (!_current_window) { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { return; } @@ -141,63 +141,59 @@ void GLManager_OSX::release_current() { } void GLManager_OSX::window_make_current(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) { - return; - } - - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { + if (current_window == p_window_id) { return; } - - if (&win == _current_window) { + if (!windows.has(p_window_id)) { return; } + GLWindow &win = windows[p_window_id]; [win.context makeCurrentContext]; - _internal_set_current_window(&win); + current_window = p_window_id; } void GLManager_OSX::make_current() { - if (!_current_window) { + if (current_window == DisplayServer::INVALID_WINDOW_ID) { return; } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); + if (!windows.has(current_window)) { return; } - [_current_window->context makeCurrentContext]; + + GLWindow &win = windows[current_window]; + [win.context makeCurrentContext]; } void GLManager_OSX::swap_buffers() { - // NO NEED TO CALL SWAP BUFFERS for each window... - // see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glXSwapBuffers.xml - - if (!_current_window) { - return; - } - if (!_current_window->in_use) { - WARN_PRINT("current window not in use!"); - return; + for (Map<DisplayServer::WindowID, GLWindow>::Element *E = windows.front(); E; E = E->next()) { + [E->get().context flushBuffer]; } - [_current_window->context flushBuffer]; } void GLManager_OSX::window_update(DisplayServer::WindowID p_window_id) { - if (p_window_id == -1) { + if (!windows.has(p_window_id)) { return; } - GLWindow &win = _windows[p_window_id]; - if (!win.in_use) { - return; - } + GLWindow &win = windows[p_window_id]; + [win.context update]; +} - if (&win == _current_window) { +void GLManager_OSX::window_set_per_pixel_transparency_enabled(DisplayServer::WindowID p_window_id, bool p_enabled) { + if (!windows.has(p_window_id)) { return; } + GLWindow &win = windows[p_window_id]; + if (p_enabled) { + GLint opacity = 0; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } else { + GLint opacity = 1; + [win.context setValues:&opacity forParameter:NSOpenGLContextParameterSurfaceOpacity]; + } [win.context update]; } @@ -207,6 +203,7 @@ Error GLManager_OSX::initialize() { void GLManager_OSX::set_use_vsync(bool p_use) { use_vsync = p_use; + CGLContextObj ctx = CGLGetCurrentContext(); if (ctx) { GLint swapInterval = p_use ? 1 : 0; @@ -221,8 +218,6 @@ bool GLManager_OSX::is_using_vsync() const { GLManager_OSX::GLManager_OSX(ContextType p_context_type) { context_type = p_context_type; - use_vsync = false; - _current_window = nullptr; } GLManager_OSX::~GLManager_OSX() { diff --git a/platform/osx/godot_application.h b/platform/osx/godot_application.h new file mode 100644 index 0000000000..8d48a659f3 --- /dev/null +++ b/platform/osx/godot_application.h @@ -0,0 +1,42 @@ +/*************************************************************************/ +/* godot_application.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_APPLICATION_H +#define GODOT_APPLICATION_H + +#include "core/os/os.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotApplication : NSApplication +@end + +#endif // GODOT_APPLICATION_H diff --git a/platform/osx/godot_application.mm b/platform/osx/godot_application.mm new file mode 100644 index 0000000000..00a58700e8 --- /dev/null +++ b/platform/osx/godot_application.mm @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* godot_application.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "godot_application.h" + +#include "display_server_osx.h" + +@implementation GodotApplication + +- (void)sendEvent:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->send_event(event); + } + + // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost + // This works around an AppKit bug, where key up events while holding + // down the command key don't get sent to the key window. + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { + [[self keyWindow] sendEvent:event]; + } else { + [super sendEvent:event]; + } +} + +@end diff --git a/platform/osx/godot_application_delegate.h b/platform/osx/godot_application_delegate.h new file mode 100644 index 0000000000..8eec762d8f --- /dev/null +++ b/platform/osx/godot_application_delegate.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* godot_application_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_APPLICATION_DELEGATE_H +#define GODOT_APPLICATION_DELEGATE_H + +#include "core/os/os.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotApplicationDelegate : NSObject +- (void)forceUnbundledWindowActivationHackStep1; +- (void)forceUnbundledWindowActivationHackStep2; +- (void)forceUnbundledWindowActivationHackStep3; +@end + +#endif // GODOT_APPLICATION_DELEGATE_H diff --git a/platform/osx/godot_application_delegate.mm b/platform/osx/godot_application_delegate.mm new file mode 100644 index 0000000000..be284ba543 --- /dev/null +++ b/platform/osx/godot_application_delegate.mm @@ -0,0 +1,132 @@ +/*************************************************************************/ +/* godot_application_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "godot_application_delegate.h" + +#include "display_server_osx.h" +#include "os_osx.h" + +@implementation GodotApplicationDelegate + +- (void)forceUnbundledWindowActivationHackStep1 { + // Step 1: Switch focus to macOS SystemUIServer process. + // Required to perform step 2, TransformProcessType will fail if app is already the in focus. + for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { + [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; + break; + } + [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) + withObject:nil + afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep2 { + // Step 2: Register app as foreground process. + ProcessSerialNumber psn = { 0, kCurrentProcess }; + (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); + [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; +} + +- (void)forceUnbundledWindowActivationHackStep3 { + // Step 3: Switch focus back to app window. + [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)notice { + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { + // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). + [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; + } +} + +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + +- (void)globalMenuCallback:(id)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + return ds->menu_callback(sender); + } +} + +- (NSMenu *)applicationDockMenu:(NSApplication *)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + return ds->get_dock_menu(); + } else { + return nullptr; + } +} + +- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + // Note: may be called called before main loop init! + OS_OSX *os = (OS_OSX *)OS::get_singleton(); + if (os) { + os->set_open_with_filename(String::utf8([filename UTF8String])); + } + +#ifdef TOOLS_ENABLED + // Open new instance. + if (os && os->get_main_loop()) { + List<String> args; + args.push_back(os->get_open_with_filename()); + String exec = os->get_executable_path(); + os->create_process(exec, args); + } +#endif + return YES; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (ds) { + ds->send_window_event(ds->get_window(DisplayServerOSX::MAIN_WINDOW_ID), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + } + return NSTerminateCancel; +} + +- (void)showAbout:(id)sender { + OS_OSX *os = (OS_OSX *)OS::get_singleton(); + if (os && os->get_main_loop()) { + os->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } +} + +@end diff --git a/platform/osx/godot_content_view.h b/platform/osx/godot_content_view.h new file mode 100644 index 0000000000..7942d716dc --- /dev/null +++ b/platform/osx/godot_content_view.h @@ -0,0 +1,65 @@ +/*************************************************************************/ +/* godot_content_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_CONTENT_VIEW_H +#define GODOT_CONTENT_VIEW_H + +#include "servers/display_server.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +#if defined(GLES3_ENABLED) +#import <AppKit/NSOpenGLView.h> +#define RootView NSOpenGLView +#else +#define RootView NSView +#endif + +#import <QuartzCore/CAMetalLayer.h> + +@interface GodotContentView : RootView <NSTextInputClient> { + DisplayServer::WindowID window_id; + NSTrackingArea *tracking_area; + NSMutableAttributedString *marked_text; + bool ime_input_event_in_progress; + bool mouse_down_control; + bool ignore_momentum_scroll; +} + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor; +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy; +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed; +- (void)setWindowID:(DisplayServer::WindowID)wid; +- (void)cancelComposition; + +@end + +#endif // GODOT_CONTENT_VIEW_H diff --git a/platform/osx/godot_content_view.mm b/platform/osx/godot_content_view.mm new file mode 100644 index 0000000000..4e831e1ccc --- /dev/null +++ b/platform/osx/godot_content_view.mm @@ -0,0 +1,760 @@ +/*************************************************************************/ +/* godot_content_view.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "godot_content_view.h" + +#include "display_server_osx.h" +#include "key_mapping_osx.h" + +@implementation GodotContentView + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + tracking_area = nil; + ime_input_event_in_progress = false; + mouse_down_control = false; + ignore_momentum_scroll = false; + [self updateTrackingAreas]; + + if (@available(macOS 10.13, *)) { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif + } + marked_text = [[NSMutableAttributedString alloc] init]; + return self; +} + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + +// MARK: Backing Layer + +- (CALayer *)makeBackingLayer { + return [[CAMetalLayer class] layer]; +} + +- (void)updateLayer { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + ds->window_update(window_id); + [super updateLayer]; +} + +- (BOOL)wantsUpdateLayer { + return YES; +} + +- (BOOL)isOpaque { + return YES; +} + +// MARK: IME + +- (BOOL)hasMarkedText { + return (marked_text.length > 0); +} + +- (NSRange)markedRange { + return NSMakeRange(0, marked_text.length); +} + +- (NSRange)selectedRange { + static const NSRange kEmptyRange = { NSNotFound, 0 }; + return kEmptyRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange { + if ([aString isKindOfClass:[NSAttributedString class]]) { + marked_text = [[NSMutableAttributedString alloc] initWithAttributedString:aString]; + } else { + marked_text = [[NSMutableAttributedString alloc] initWithString:aString]; + } + if (marked_text.length == 0) { + [self unmarkText]; + return; + } + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ime_input_event_in_progress = true; + ds->update_im_text(Point2i(selectedRange.location, selectedRange.length), String::utf8([[marked_text mutableString] UTF8String])); + } +} + +- (void)unmarkText { + ime_input_event_in_progress = false; + [[marked_text mutableString] setString:@""]; + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (wd.im_active) { + ds->update_im_text(Point2i(), String()); + } +} + +- (NSArray *)validAttributesForMarkedText { + return [NSArray array]; +} + +- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { + return 0; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NSMakeRect(0, 0, 0, 0); + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + NSRect point_in_window_rect = NSMakeRect(wd.im_position.x / scale, content_rect.size.height - (wd.im_position.y / scale) - 1, 0, 0); + NSPoint point_on_screen = [wd.window_object convertRectToScreen:point_in_window_rect].origin; + + return NSMakeRect(point_on_screen.x, point_on_screen.y, 0, 0); +} + +- (void)cancelComposition { + [self unmarkText]; + [[NSTextInputContext currentInputContext] discardMarkedText]; +} + +- (void)insertText:(id)aString { + [self insertText:aString replacementRange:NSMakeRange(0, 0)]; +} + +- (void)insertText:(id)aString replacementRange:(NSRange)replacementRange { + NSEvent *event = [NSApp currentEvent]; + + NSString *characters; + if ([aString isKindOfClass:[NSAttributedString class]]) { + characters = [aString string]; + } else { + characters = (NSString *)aString; + } + + NSCharacterSet *ctrl_chars = [NSCharacterSet controlCharacterSet]; + NSCharacterSet *wsnl_chars = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + if ([characters rangeOfCharacterFromSet:ctrl_chars].length && [characters rangeOfCharacterFromSet:wsnl_chars].length == 0) { + [[NSTextInputContext currentInputContext] discardMarkedText]; + [self cancelComposition]; + return; + } + + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + [self cancelComposition]; + return; + } + + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + if ((codepoint & 0xFF00) == 0xF700) { + continue; + } + + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = false; + ke.raw = false; // IME input event. + ke.keycode = Key::NONE; + ke.physical_keycode = Key::NONE; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + [self cancelComposition]; +} + +// MARK: Drag and drop + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return NO; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (!wd.drop_files_callback.is_null()) { + Vector<String> files; + NSPasteboard *pboard = [sender draggingPasteboard]; + + if (@available(macOS 10.13, *)) { + NSArray *items = pboard.pasteboardItems; + for (NSPasteboardItem *item in items) { + NSString *url = [item stringForType:NSPasteboardTypeFileURL]; + NSString *file = [NSURL URLWithString:url].path; + files.push_back(String::utf8([file UTF8String])); + } +#if !defined(__aarch64__) // Do not build deprectead 10.13 code on ARM. + } else { + NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString *file in filenames) { + files.push_back(String::utf8([file UTF8String])); + } +#endif + } + + Variant v = files; + Variant *vp = &v; + Variant ret; + Callable::CallError ce; + wd.drop_files_callback.call((const Variant **)&vp, 1, ret, ce); + } + + return NO; +} + +// MARK: Focus + +- (BOOL)canBecomeKeyView { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +- (BOOL)acceptsFirstResponder { + return YES; +} + +// MARK: Mouse + +- (void)cursorUpdate:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds) { + return; + } + + ds->cursor_update_shape(); +} + +- (void)processMouseEvent:(NSEvent *)event index:(MouseButton)index mask:(MouseButton)mask pressed:(bool)pressed { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + MouseButton last_button_state = ds->mouse_get_button_state(); + + if (pressed) { + last_button_state |= mask; + } else { + last_button_state &= (MouseButton)~mask; + } + ds->mouse_set_button_state(last_button_state); + + Ref<InputEventMouseButton> mb; + mb.instantiate(); + mb->set_window_id(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + ds->get_key_modifier_state([event modifierFlags], mb); + mb->set_button_index(index); + mb->set_pressed(pressed); + mb->set_position(wd.mouse_pos); + mb->set_global_position(wd.mouse_pos); + mb->set_button_mask(last_button_state); + if (index == MouseButton::LEFT && pressed) { + mb->set_double_click([event clickCount] == 2); + } + + Input::get_singleton()->parse_input_event(mb); +} + +- (void)mouseDown:(NSEvent *)event { + if (([event modifierFlags] & NSEventModifierFlagControl)) { + mouse_down_control = true; + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; + } else { + mouse_down_control = false; + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:true]; + } +} + +- (void)mouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)mouseUp:(NSEvent *)event { + if (mouse_down_control) { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; + } else { + [self processMouseEvent:event index:MouseButton::LEFT mask:MouseButton::MASK_LEFT pressed:false]; + } +} + +- (void)mouseMoved:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (ds->update_mouse_wrap(wd, delta, mpos, [event timestamp])) { + return; + } + + Ref<InputEventMouseMotion> mm; + mm.instantiate(); + + mm->set_window_id(window_id); + mm->set_button_mask(ds->mouse_get_button_state()); + ds->update_mouse_pos(wd, mpos); + mm->set_position(wd.mouse_pos); + mm->set_pressure([event pressure]); + if ([event subtype] == NSEventSubtypeTabletPoint) { + const NSPoint p = [event tilt]; + mm->set_tilt(Vector2(p.x, p.y)); + } + mm->set_global_position(wd.mouse_pos); + mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * ds->screen_get_max_scale(); + mm->set_relative(relativeMotion); + ds->get_key_modifier_state([event modifierFlags], mm); + + Input::get_singleton()->parse_input_event(mm); +} + +- (void)rightMouseDown:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:true]; +} + +- (void)rightMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent *)event { + [self processMouseEvent:event index:MouseButton::RIGHT mask:MouseButton::MASK_RIGHT pressed:false]; +} + +- (void)otherMouseDown:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:true]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:true]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:true]; + } else { + return; + } +} + +- (void)otherMouseDragged:(NSEvent *)event { + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent *)event { + if ((int)[event buttonNumber] == 2) { + [self processMouseEvent:event index:MouseButton::MIDDLE mask:MouseButton::MASK_MIDDLE pressed:false]; + } else if ((int)[event buttonNumber] == 3) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON1 mask:MouseButton::MASK_XBUTTON1 pressed:false]; + } else if ((int)[event buttonNumber] == 4) { + [self processMouseEvent:event index:MouseButton::MB_XBUTTON2 mask:MouseButton::MASK_XBUTTON2 pressed:false]; + } else { + return; + } +} + +- (void)mouseExited:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + } +} + +- (void)mouseEntered:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + if (ds->mouse_get_mode() != DisplayServer::MOUSE_MODE_CAPTURED) { + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + } + + ds->cursor_update_shape(); +} + +- (void)magnifyWithEvent:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + Ref<InputEventMagnifyGesture> ev; + ev.instantiate(); + ev->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], ev); + ds->update_mouse_pos(wd, [event locationInWindow]); + ev->set_position(wd.mouse_pos); + ev->set_factor([event magnification] + 1.0); + + Input::get_singleton()->parse_input_event(ev); +} + +- (void)updateTrackingAreas { + if (tracking_area != nil) { + [self removeTrackingArea:tracking_area]; + } + + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; + + [self addTrackingArea:tracking_area]; + [super updateTrackingAreas]; +} + +// MARK: Keyboard + +- (void)keyDown:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { + // Fallback unicode character handler used if IME is not active. + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = true; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = false; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } + + // Pass events to IME handler + if (wd.im_active) { + [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } +} + +- (void)flagsChanged:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + ignore_momentum_scroll = true; + + // Ignore all input if IME input is in progress + if (!ime_input_event_in_progress) { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.echo = false; + ke.raw = true; + + int key = [event keyCode]; + int mod = [event modifierFlags]; + + if (key == 0x36 || key == 0x37) { + if (mod & NSEventModifierFlagCommand) { + mod &= ~NSEventModifierFlagCommand; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x38 || key == 0x3c) { + if (mod & NSEventModifierFlagShift) { + mod &= ~NSEventModifierFlagShift; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3a || key == 0x3d) { + if (mod & NSEventModifierFlagOption) { + mod &= ~NSEventModifierFlagOption; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else if (key == 0x3b || key == 0x3e) { + if (mod & NSEventModifierFlagControl) { + mod &= ~NSEventModifierFlagControl; + ke.pressed = true; + } else { + ke.pressed = false; + } + } else { + return; + } + + ke.osx_state = mod; + ke.keycode = KeyMappingOSX::remap_key(key, mod); + ke.physical_keycode = KeyMappingOSX::translate_key(key); + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } +} + +- (void)keyUp:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + // Ignore all input if IME input is in progress. + if (!ime_input_event_in_progress) { + NSString *characters = [event characters]; + NSUInteger length = [characters length]; + + // Fallback unicode character handler used if IME is not active. + if (!wd.im_active && length > 0 && keycode_has_unicode(KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]))) { + Char16String text; + text.resize([characters length] + 1); + [characters getCharacters:(unichar *)text.ptrw() range:NSMakeRange(0, [characters length])]; + + String u32text; + u32text.parse_utf16(text.ptr(), text.length()); + + for (int i = 0; i < u32text.length(); i++) { + const char32_t codepoint = u32text[i]; + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = codepoint; + + ds->push_to_key_event_buffer(ke); + } + } else { + DisplayServerOSX::KeyEvent ke; + + ke.window_id = window_id; + ke.osx_state = [event modifierFlags]; + ke.pressed = false; + ke.echo = [event isARepeat]; + ke.keycode = KeyMappingOSX::remap_key([event keyCode], [event modifierFlags]); + ke.physical_keycode = KeyMappingOSX::translate_key([event keyCode]); + ke.raw = true; + ke.unicode = 0; + + ds->push_to_key_event_buffer(ke); + } + } +} + +// MARK: Scroll and pan + +- (void)processScrollEvent:(NSEvent *)event button:(MouseButton)button factor:(double)factor { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + MouseButton mask = mouse_button_to_mask(button); + + Ref<InputEventMouseButton> sc; + sc.instantiate(); + + sc->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], sc); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(true); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + MouseButton last_button_state = ds->mouse_get_button_state() | (MouseButton)mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); + + sc.instantiate(); + sc->set_window_id(window_id); + sc->set_button_index(button); + sc->set_factor(factor); + sc->set_pressed(false); + sc->set_position(wd.mouse_pos); + sc->set_global_position(wd.mouse_pos); + last_button_state &= (MouseButton)~mask; + sc->set_button_mask(last_button_state); + ds->mouse_set_button_state(last_button_state); + + Input::get_singleton()->parse_input_event(sc); +} + +- (void)processPanEvent:(NSEvent *)event dx:(double)dx dy:(double)dy { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + Ref<InputEventPanGesture> pg; + pg.instantiate(); + + pg->set_window_id(window_id); + ds->get_key_modifier_state([event modifierFlags], pg); + pg->set_position(wd.mouse_pos); + pg->set_delta(Vector2(-dx, -dy)); + + Input::get_singleton()->parse_input_event(pg); +} + +- (void)scrollWheel:(NSEvent *)event { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + ds->update_mouse_pos(wd, [event locationInWindow]); + + double delta_x = [event scrollingDeltaX]; + double delta_y = [event scrollingDeltaY]; + + if ([event hasPreciseScrollingDeltas]) { + delta_x *= 0.03; + delta_y *= 0.03; + } + + if ([event momentumPhase] != NSEventPhaseNone) { + if (ignore_momentum_scroll) { + return; + } + } else { + ignore_momentum_scroll = false; + } + + if ([event phase] != NSEventPhaseNone || [event momentumPhase] != NSEventPhaseNone) { + [self processPanEvent:event dx:delta_x dy:delta_y]; + } else { + if (fabs(delta_x)) { + [self processScrollEvent:event button:(0 > delta_x ? MouseButton::WHEEL_RIGHT : MouseButton::WHEEL_LEFT) factor:fabs(delta_x * 0.3)]; + } + if (fabs(delta_y)) { + [self processScrollEvent:event button:(0 < delta_y ? MouseButton::WHEEL_UP : MouseButton::WHEEL_DOWN) factor:fabs(delta_y * 0.3)]; + } + } +} + +@end diff --git a/platform/osx/godot_main_osx.mm b/platform/osx/godot_main_osx.mm index 64a93f7292..7fabfaa1b7 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 @@ -45,13 +45,14 @@ int main(int argc, char **argv) { const char *dbg_arg = "-NSDocumentRevisionsDebugMode"; printf("arguments\n"); for (int i = 0; i < argc; i++) { - if (strcmp(dbg_arg, argv[i]) == 0) + if (strcmp(dbg_arg, argv[i]) == 0) { first_arg = i + 2; + } printf("%i: %s\n", i, argv[i]); }; #ifdef DEBUG_ENABLED - // lets report the path we made current after all that + // Lets report the path we made current after all that. char cwd[4096]; getcwd(cwd, 4096); printf("Current path: %s\n", cwd); @@ -60,23 +61,25 @@ int main(int argc, char **argv) { OS_OSX os; Error err; - // We must override main when testing is enabled + // 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()); + if (os.get_open_with_filename() != "") { + char *argv_c = (char *)malloc(os.get_open_with_filename().utf8().size()); + memcpy(argv_c, os.get_open_with_filename().utf8().get_data(), os.get_open_with_filename().utf8().size()); err = Main::setup(argv[0], 1, &argv_c); free(argv_c); } else { err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); } - if (err != OK) + if (err != OK) { return 255; + } - if (Main::start()) - os.run(); // it is actually the OS that decides how to run + if (Main::start()) { + os.run(); // It is actually the OS that decides how to run. + } Main::cleanup(); diff --git a/platform/osx/godot_menu_item.h b/platform/osx/godot_menu_item.h new file mode 100644 index 0000000000..50c4709c18 --- /dev/null +++ b/platform/osx/godot_menu_item.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* godot_menu_item.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_MENU_ITEM_H +#define GODOT_MENU_ITEM_H + +#include "servers/display_server.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotMenuItem : NSObject { +@public + Callable callback; + Variant meta; + int id; + bool checkable; +} + +@end + +@implementation GodotMenuItem +@end + +#endif // GODOT_MENU_ITEM_H diff --git a/platform/osx/godot_window.h b/platform/osx/godot_window.h new file mode 100644 index 0000000000..16ff101142 --- /dev/null +++ b/platform/osx/godot_window.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_WINDOW_H +#define GODOT_WINDOW_H + +#include "servers/display_server.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotWindow : NSWindow { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_H diff --git a/platform/osx/godot_window.mm b/platform/osx/godot_window.mm new file mode 100644 index 0000000000..772a2ddb9f --- /dev/null +++ b/platform/osx/godot_window.mm @@ -0,0 +1,69 @@ +/*************************************************************************/ +/* godot_window.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "godot_window.h" + +#include "display_server_osx.h" + +@implementation GodotWindow + +- (id)init { + self = [super init]; + window_id = DisplayServer::INVALID_WINDOW_ID; + return self; +} + +- (void)setWindowID:(DisplayServerOSX::WindowID)wid { + window_id = wid; +} + +- (BOOL)canBecomeKeyWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSWindowStyleMaskBorderless windows. + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + return !wd.no_focus; +} + +@end diff --git a/platform/osx/godot_window_delegate.h b/platform/osx/godot_window_delegate.h new file mode 100644 index 0000000000..8a1f681fcd --- /dev/null +++ b/platform/osx/godot_window_delegate.h @@ -0,0 +1,47 @@ +/*************************************************************************/ +/* godot_window_delegate.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 GODOT_WINDOW_DELEGATE_H +#define GODOT_WINDOW_DELEGATE_H + +#include "servers/display_server.h" + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotWindowDelegate : NSObject <NSWindowDelegate> { + DisplayServer::WindowID window_id; +} + +- (void)setWindowID:(DisplayServer::WindowID)wid; + +@end + +#endif //GODOT_WINDOW_DELEGATE_H diff --git a/platform/osx/godot_window_delegate.mm b/platform/osx/godot_window_delegate.mm new file mode 100644 index 0000000000..1742be987d --- /dev/null +++ b/platform/osx/godot_window_delegate.mm @@ -0,0 +1,254 @@ +/*************************************************************************/ +/* godot_window_delegate.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "godot_window_delegate.h" + +#include "display_server_osx.h" + +@implementation GodotWindowDelegate + +- (void)setWindowID:(DisplayServer::WindowID)wid { + window_id = wid; +} + +- (BOOL)windowShouldClose:(id)sender { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return YES; + } + + ds->send_window_event(ds->get_window(window_id), DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); + return NO; +} + +- (void)windowWillClose:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + while (wd.transient_children.size()) { + ds->window_set_transient(wd.transient_children.front()->get(), DisplayServerOSX::INVALID_WINDOW_ID); + } + + if (wd.transient_parent != DisplayServerOSX::INVALID_WINDOW_ID) { + ds->window_set_transient(window_id, DisplayServerOSX::INVALID_WINDOW_ID); + } + + ds->window_destroy(window_id); +} + +- (void)windowDidEnterFullScreen:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = true; + // Reset window size limits. + [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; + [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidExitFullScreen:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + wd.fullscreen = false; + + // Set window size limits. + const float scale = ds->screen_get_max_scale(); + if (wd.min_size != Size2i()) { + Size2i size = wd.min_size / scale; + [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; + } + if (wd.max_size != Size2i()) { + Size2i size = wd.max_size / scale; + [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; + } + + // Restore resizability state. + if (wd.resize_disabled) { + [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } + + // Restore on-top state. + if (wd.on_top) { + [wd.window_object setLevel:NSFloatingWindowLevel]; + } + + // Force window resize event. + [self windowDidResize:notification]; +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + CGFloat new_scale_factor = [wd.window_object backingScaleFactor]; + CGFloat old_scale_factor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; + + if (new_scale_factor != old_scale_factor) { + // Set new display scale and window size. + const float scale = ds->screen_get_max_scale(); + const NSRect content_rect = [wd.window_view frame]; + + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + //Force window resize event + [self windowDidResize:notification]; + } +} + +- (void)windowDidResize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + const NSRect content_rect = [wd.window_view frame]; + const float scale = ds->screen_get_max_scale(); + wd.size.width = content_rect.size.width * scale; + wd.size.height = content_rect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + + ds->window_resize(window_id, wd.size.width, wd.size.height); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } +} + +- (void)windowDidMove:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + ds->release_pressed_events(); + + if (!wd.rect_changed_callback.is_null()) { + Variant size = Rect2i(ds->window_get_position(window_id), ds->window_get_size(window_id)); + Variant *sizep = &size; + Variant ret; + Callable::CallError ce; + wd.rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + } +} + +- (void)windowDidBecomeKey:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + if (ds->mouse_get_mode() == DisplayServer::MOUSE_MODE_CAPTURED) { + const NSRect content_rect = [wd.window_view frame]; + NSRect point_in_window_rect = NSMakeRect(content_rect.size.width / 2, content_rect.size.height / 2, 0, 0); + NSPoint point_on_screen = [[wd.window_view window] convertRectToScreen:point_in_window_rect].origin; + CGPoint mouse_warp_pos = { point_on_screen.x, CGDisplayBounds(CGMainDisplayID()).size.height - point_on_screen.y }; + CGWarpMouseCursorPosition(mouse_warp_pos); + } else { + ds->update_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); + } + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidMiniaturize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->release_pressed_events(); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_OUT); +} + +- (void)windowDidDeminiaturize:(NSNotification *)notification { + DisplayServerOSX *ds = (DisplayServerOSX *)DisplayServer::get_singleton(); + if (!ds || !ds->has_window(window_id)) { + return; + } + + DisplayServerOSX::WindowData &wd = ds->get_window(window_id); + + ds->set_last_focused_window(window_id); + ds->send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_FOCUS_IN); +} + +@end diff --git a/platform/osx/joypad_osx.cpp b/platform/osx/joypad_osx.cpp index c2356f12cd..d518206f04 100644 --- a/platform/osx/joypad_osx.cpp +++ b/platform/osx/joypad_osx.cpp @@ -80,7 +80,7 @@ int joypad::get_hid_element_state(rec_element *p_element) const { if (IOHIDDeviceGetValue(device_ref, p_element->ref, &valueRef) == kIOReturnSuccess) { value = (SInt32)IOHIDValueGetIntegerValue(valueRef); - /* record min and max for auto calibration */ + // Record min and max for auto calibration. if (value < p_element->min) { p_element->min = value; } @@ -179,7 +179,7 @@ void joypad::add_hid_element(IOHIDElementRef p_element) { break; } - if (list) { /* add to list */ + if (list) { // Add to list. rec_element element; element.ref = p_element; @@ -280,7 +280,7 @@ static String _hex_str(uint8_t p_byte) { bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { p_joy->device_ref = p_device_ref; - /* get device name */ + // Get device name. String name; char c_name[256]; CFTypeRef refCF = IOHIDDeviceGetProperty(p_device_ref, CFSTR(kIOHIDProductKey)); @@ -319,7 +319,7 @@ bool JoypadOSX::configure_joypad(IOHIDDeviceRef p_device_ref, joypad *p_joy) { sprintf(uid, "%08x%08x%08x%08x", OSSwapHostToBigInt32(3), OSSwapHostToBigInt32(vendor), OSSwapHostToBigInt32(product_id), OSSwapHostToBigInt32(version)); input->joy_connection_changed(id, true, name, uid); } else { - //bluetooth device + // Bluetooth device. String guid = "05000000"; for (int i = 0; i < 12; i++) { if (i < name.size()) @@ -445,7 +445,7 @@ static HatMask process_hat_value(int p_min, int p_max, int p_value, bool p_offse void JoypadOSX::poll_joypads() const { while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - /* no-op. Pending callbacks will fire. */ + // No-op. Pending callbacks will fire. } } @@ -568,7 +568,7 @@ void JoypadOSX::config_hid_manager(CFArrayRef p_matching_array) const { IOHIDManagerScheduleWithRunLoop(hid_manager, runloop, GODOT_JOY_LOOP_RUN_MODE); while (CFRunLoopRunInMode(GODOT_JOY_LOOP_RUN_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) { - /* no-op. Callback fires once per existing device. */ + // No-op. Callback fires once per existing device. } } diff --git a/platform/osx/joypad_osx.h b/platform/osx/joypad_osx.h index 8ea1033a77..4ca7fb1698 100644 --- a/platform/osx/joypad_osx.h +++ b/platform/osx/joypad_osx.h @@ -66,7 +66,7 @@ struct joypad { int id = 0; bool offset_hat = false; - io_service_t ffservice = 0; /* Interface for force feedback, 0 = no ff */ + io_service_t ffservice = 0; // Interface for force feedback, 0 = no ff. FFCONSTANTFORCE ff_constant_force; FFDeviceObjectReference ff_device = nullptr; FFEffectObjectReference ff_object = nullptr; diff --git a/platform/osx/key_mapping_osx.h b/platform/osx/key_mapping_osx.h new file mode 100644 index 0000000000..252cc907bb --- /dev/null +++ b/platform/osx/key_mapping_osx.h @@ -0,0 +1,52 @@ +/*************************************************************************/ +/* key_mapping_osx.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 KEY_MAPPING_OSX_H +#define KEY_MAPPING_OSX_H + +#include "core/os/keyboard.h" + +class KeyMappingOSX { + KeyMappingOSX() {} + + static bool is_numpad_key(unsigned int key); + +public: + // Mappings input. + static Key translate_key(unsigned int key); + static unsigned int unmap_key(Key key); + static Key remap_key(unsigned int key, unsigned int state); + + // Mapping for menu shortcuts. + static String keycode_get_native_string(Key p_keycode); + static unsigned int keycode_get_native_mask(Key p_keycode); +}; + +#endif // KEY_MAPPING_OSX_H diff --git a/platform/osx/key_mapping_osx.mm b/platform/osx/key_mapping_osx.mm new file mode 100644 index 0000000000..fde9206824 --- /dev/null +++ b/platform/osx/key_mapping_osx.mm @@ -0,0 +1,477 @@ +/*************************************************************************/ +/* key_mapping_osx.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "key_mapping_osx.h" + +#include <Carbon/Carbon.h> +#include <Cocoa/Cocoa.h> + +bool KeyMappingOSX::is_numpad_key(unsigned int key) { + static const unsigned int table[] = { + 0x41, /* kVK_ANSI_KeypadDecimal */ + 0x43, /* kVK_ANSI_KeypadMultiply */ + 0x45, /* kVK_ANSI_KeypadPlus */ + 0x47, /* kVK_ANSI_KeypadClear */ + 0x4b, /* kVK_ANSI_KeypadDivide */ + 0x4c, /* kVK_ANSI_KeypadEnter */ + 0x4e, /* kVK_ANSI_KeypadMinus */ + 0x51, /* kVK_ANSI_KeypadEquals */ + 0x52, /* kVK_ANSI_Keypad0 */ + 0x53, /* kVK_ANSI_Keypad1 */ + 0x54, /* kVK_ANSI_Keypad2 */ + 0x55, /* kVK_ANSI_Keypad3 */ + 0x56, /* kVK_ANSI_Keypad4 */ + 0x57, /* kVK_ANSI_Keypad5 */ + 0x58, /* kVK_ANSI_Keypad6 */ + 0x59, /* kVK_ANSI_Keypad7 */ + 0x5b, /* kVK_ANSI_Keypad8 */ + 0x5c, /* kVK_ANSI_Keypad9 */ + 0x5f, /* kVK_JIS_KeypadComma */ + 0x00 + }; + for (int i = 0; table[i] != 0; i++) { + if (key == table[i]) { + return true; + } + } + return false; +} + +// Keyboard symbol translation table. +static const Key _osx_to_godot_table[128] = { + /* 00 */ Key::A, + /* 01 */ Key::S, + /* 02 */ Key::D, + /* 03 */ Key::F, + /* 04 */ Key::H, + /* 05 */ Key::G, + /* 06 */ Key::Z, + /* 07 */ Key::X, + /* 08 */ Key::C, + /* 09 */ Key::V, + /* 0a */ Key::SECTION, /* ISO Section */ + /* 0b */ Key::B, + /* 0c */ Key::Q, + /* 0d */ Key::W, + /* 0e */ Key::E, + /* 0f */ Key::R, + /* 10 */ Key::Y, + /* 11 */ Key::T, + /* 12 */ Key::KEY_1, + /* 13 */ Key::KEY_2, + /* 14 */ Key::KEY_3, + /* 15 */ Key::KEY_4, + /* 16 */ Key::KEY_6, + /* 17 */ Key::KEY_5, + /* 18 */ Key::EQUAL, + /* 19 */ Key::KEY_9, + /* 1a */ Key::KEY_7, + /* 1b */ Key::MINUS, + /* 1c */ Key::KEY_8, + /* 1d */ Key::KEY_0, + /* 1e */ Key::BRACERIGHT, + /* 1f */ Key::O, + /* 20 */ Key::U, + /* 21 */ Key::BRACELEFT, + /* 22 */ Key::I, + /* 23 */ Key::P, + /* 24 */ Key::ENTER, + /* 25 */ Key::L, + /* 26 */ Key::J, + /* 27 */ Key::APOSTROPHE, + /* 28 */ Key::K, + /* 29 */ Key::SEMICOLON, + /* 2a */ Key::BACKSLASH, + /* 2b */ Key::COMMA, + /* 2c */ Key::SLASH, + /* 2d */ Key::N, + /* 2e */ Key::M, + /* 2f */ Key::PERIOD, + /* 30 */ Key::TAB, + /* 31 */ Key::SPACE, + /* 32 */ Key::QUOTELEFT, + /* 33 */ Key::BACKSPACE, + /* 34 */ Key::UNKNOWN, + /* 35 */ Key::ESCAPE, + /* 36 */ Key::META, + /* 37 */ Key::META, + /* 38 */ Key::SHIFT, + /* 39 */ Key::CAPSLOCK, + /* 3a */ Key::ALT, + /* 3b */ Key::CTRL, + /* 3c */ Key::SHIFT, + /* 3d */ Key::ALT, + /* 3e */ Key::CTRL, + /* 3f */ Key::UNKNOWN, /* Function */ + /* 40 */ Key::UNKNOWN, /* F17 */ + /* 41 */ Key::KP_PERIOD, + /* 42 */ Key::UNKNOWN, + /* 43 */ Key::KP_MULTIPLY, + /* 44 */ Key::UNKNOWN, + /* 45 */ Key::KP_ADD, + /* 46 */ Key::UNKNOWN, + /* 47 */ Key::NUMLOCK, /* Really KeypadClear... */ + /* 48 */ Key::VOLUMEUP, /* VolumeUp */ + /* 49 */ Key::VOLUMEDOWN, /* VolumeDown */ + /* 4a */ Key::VOLUMEMUTE, /* Mute */ + /* 4b */ Key::KP_DIVIDE, + /* 4c */ Key::KP_ENTER, + /* 4d */ Key::UNKNOWN, + /* 4e */ Key::KP_SUBTRACT, + /* 4f */ Key::UNKNOWN, /* F18 */ + /* 50 */ Key::UNKNOWN, /* F19 */ + /* 51 */ Key::EQUAL, /* KeypadEqual */ + /* 52 */ Key::KP_0, + /* 53 */ Key::KP_1, + /* 54 */ Key::KP_2, + /* 55 */ Key::KP_3, + /* 56 */ Key::KP_4, + /* 57 */ Key::KP_5, + /* 58 */ Key::KP_6, + /* 59 */ Key::KP_7, + /* 5a */ Key::UNKNOWN, /* F20 */ + /* 5b */ Key::KP_8, + /* 5c */ Key::KP_9, + /* 5d */ Key::YEN, /* JIS Yen */ + /* 5e */ Key::UNDERSCORE, /* JIS Underscore */ + /* 5f */ Key::COMMA, /* JIS KeypadComma */ + /* 60 */ Key::F5, + /* 61 */ Key::F6, + /* 62 */ Key::F7, + /* 63 */ Key::F3, + /* 64 */ Key::F8, + /* 65 */ Key::F9, + /* 66 */ Key::UNKNOWN, /* JIS Eisu */ + /* 67 */ Key::F11, + /* 68 */ Key::UNKNOWN, /* JIS Kana */ + /* 69 */ Key::F13, + /* 6a */ Key::F16, + /* 6b */ Key::F14, + /* 6c */ Key::UNKNOWN, + /* 6d */ Key::F10, + /* 6e */ Key::MENU, + /* 6f */ Key::F12, + /* 70 */ Key::UNKNOWN, + /* 71 */ Key::F15, + /* 72 */ Key::INSERT, /* Really Help... */ + /* 73 */ Key::HOME, + /* 74 */ Key::PAGEUP, + /* 75 */ Key::KEY_DELETE, + /* 76 */ Key::F4, + /* 77 */ Key::END, + /* 78 */ Key::F2, + /* 79 */ Key::PAGEDOWN, + /* 7a */ Key::F1, + /* 7b */ Key::LEFT, + /* 7c */ Key::RIGHT, + /* 7d */ Key::DOWN, + /* 7e */ Key::UP, + /* 7f */ Key::UNKNOWN, +}; + +// Translates a OS X keycode to a Godot keycode. +Key KeyMappingOSX::translate_key(unsigned int key) { + if (key >= 128) { + return Key::UNKNOWN; + } + + return _osx_to_godot_table[key]; +} + +// Translates a Godot keycode back to a OSX keycode. +unsigned int KeyMappingOSX::unmap_key(Key key) { + for (int i = 0; i <= 126; i++) { + if (_osx_to_godot_table[i] == key) { + return i; + } + } + return 127; +} + +struct _KeyCodeMap { + UniChar kchar; + Key kcode; +}; + +static const _KeyCodeMap _keycodes[55] = { + { '`', Key::QUOTELEFT }, + { '~', Key::ASCIITILDE }, + { '0', Key::KEY_0 }, + { '1', Key::KEY_1 }, + { '2', Key::KEY_2 }, + { '3', Key::KEY_3 }, + { '4', Key::KEY_4 }, + { '5', Key::KEY_5 }, + { '6', Key::KEY_6 }, + { '7', Key::KEY_7 }, + { '8', Key::KEY_8 }, + { '9', Key::KEY_9 }, + { '-', Key::MINUS }, + { '_', Key::UNDERSCORE }, + { '=', Key::EQUAL }, + { '+', Key::PLUS }, + { 'q', Key::Q }, + { 'w', Key::W }, + { 'e', Key::E }, + { 'r', Key::R }, + { 't', Key::T }, + { 'y', Key::Y }, + { 'u', Key::U }, + { 'i', Key::I }, + { 'o', Key::O }, + { 'p', Key::P }, + { '[', Key::BRACELEFT }, + { ']', Key::BRACERIGHT }, + { '{', Key::BRACELEFT }, + { '}', Key::BRACERIGHT }, + { 'a', Key::A }, + { 's', Key::S }, + { 'd', Key::D }, + { 'f', Key::F }, + { 'g', Key::G }, + { 'h', Key::H }, + { 'j', Key::J }, + { 'k', Key::K }, + { 'l', Key::L }, + { ';', Key::SEMICOLON }, + { ':', Key::COLON }, + { '\'', Key::APOSTROPHE }, + { '\"', Key::QUOTEDBL }, + { '\\', Key::BACKSLASH }, + { '#', Key::NUMBERSIGN }, + { 'z', Key::Z }, + { 'x', Key::X }, + { 'c', Key::C }, + { 'v', Key::V }, + { 'b', Key::B }, + { 'n', Key::N }, + { 'm', Key::M }, + { ',', Key::COMMA }, + { '.', Key::PERIOD }, + { '/', Key::SLASH } +}; + +// Remap key according to current keyboard layout. +Key KeyMappingOSX::remap_key(unsigned int key, unsigned int state) { + if (is_numpad_key(key)) { + return translate_key(key); + } + + TISInputSourceRef current_keyboard = TISCopyCurrentKeyboardInputSource(); + if (!current_keyboard) { + return translate_key(key); + } + + CFDataRef layout_data = (CFDataRef)TISGetInputSourceProperty(current_keyboard, kTISPropertyUnicodeKeyLayoutData); + if (!layout_data) { + return translate_key(key); + } + + const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *)CFDataGetBytePtr(layout_data); + + UInt32 keys_down = 0; + UniChar chars[4]; + UniCharCount real_length; + + OSStatus err = UCKeyTranslate(keyboard_layout, + key, + kUCKeyActionDisplay, + (state >> 8) & 0xFF, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &keys_down, + sizeof(chars) / sizeof(chars[0]), + &real_length, + chars); + + if (err != noErr) { + return translate_key(key); + } + + for (unsigned int i = 0; i < 55; i++) { + if (_keycodes[i].kchar == chars[0]) { + return _keycodes[i].kcode; + } + } + return translate_key(key); +} + +struct _KeyCodeText { + Key code; + char32_t text; +}; + +static const _KeyCodeText _native_keycodes[] = { + /* clang-format off */ + {Key::ESCAPE ,0x001B}, + {Key::TAB ,0x0009}, + {Key::BACKTAB ,0x007F}, + {Key::BACKSPACE ,0x0008}, + {Key::ENTER ,0x000D}, + {Key::INSERT ,NSInsertFunctionKey}, + {Key::KEY_DELETE ,0x007F}, + {Key::PAUSE ,NSPauseFunctionKey}, + {Key::PRINT ,NSPrintScreenFunctionKey}, + {Key::SYSREQ ,NSSysReqFunctionKey}, + {Key::CLEAR ,NSClearLineFunctionKey}, + {Key::HOME ,0x2196}, + {Key::END ,0x2198}, + {Key::LEFT ,0x001C}, + {Key::UP ,0x001E}, + {Key::RIGHT ,0x001D}, + {Key::DOWN ,0x001F}, + {Key::PAGEUP ,0x21DE}, + {Key::PAGEDOWN ,0x21DF}, + {Key::NUMLOCK ,NSClearLineFunctionKey}, + {Key::SCROLLLOCK ,NSScrollLockFunctionKey}, + {Key::F1 ,NSF1FunctionKey}, + {Key::F2 ,NSF2FunctionKey}, + {Key::F3 ,NSF3FunctionKey}, + {Key::F4 ,NSF4FunctionKey}, + {Key::F5 ,NSF5FunctionKey}, + {Key::F6 ,NSF6FunctionKey}, + {Key::F7 ,NSF7FunctionKey}, + {Key::F8 ,NSF8FunctionKey}, + {Key::F9 ,NSF9FunctionKey}, + {Key::F10 ,NSF10FunctionKey}, + {Key::F11 ,NSF11FunctionKey}, + {Key::F12 ,NSF12FunctionKey}, + {Key::F13 ,NSF13FunctionKey}, + {Key::F14 ,NSF14FunctionKey}, + {Key::F15 ,NSF15FunctionKey}, + {Key::F16 ,NSF16FunctionKey}, //* ... NSF35FunctionKey */ + {Key::MENU ,NSMenuFunctionKey}, + {Key::HELP ,NSHelpFunctionKey}, + {Key::STOP ,NSStopFunctionKey}, + {Key::LAUNCH0 ,NSUserFunctionKey}, + {Key::SPACE ,0x0020}, + {Key::EXCLAM ,'!'}, + {Key::QUOTEDBL ,'\"'}, + {Key::NUMBERSIGN ,'#'}, + {Key::DOLLAR ,'$'}, + {Key::PERCENT ,'\%'}, + {Key::AMPERSAND ,'&'}, + {Key::APOSTROPHE ,'\''}, + {Key::PARENLEFT ,'('}, + {Key::PARENRIGHT ,')'}, + {Key::ASTERISK ,'*'}, + {Key::PLUS ,'+'}, + {Key::COMMA ,','}, + {Key::MINUS ,'-'}, + {Key::PERIOD ,'.'}, + {Key::SLASH ,'/'}, + {Key::KEY_0 ,'0'}, + {Key::KEY_1 ,'1'}, + {Key::KEY_2 ,'2'}, + {Key::KEY_3 ,'3'}, + {Key::KEY_4 ,'4'}, + {Key::KEY_5 ,'5'}, + {Key::KEY_6 ,'6'}, + {Key::KEY_7 ,'7'}, + {Key::KEY_8 ,'8'}, + {Key::KEY_9 ,'9'}, + {Key::COLON ,':'}, + {Key::SEMICOLON ,';'}, + {Key::LESS ,'<'}, + {Key::EQUAL ,'='}, + {Key::GREATER ,'>'}, + {Key::QUESTION ,'?'}, + {Key::AT ,'@'}, + {Key::A ,'a'}, + {Key::B ,'b'}, + {Key::C ,'c'}, + {Key::D ,'d'}, + {Key::E ,'e'}, + {Key::F ,'f'}, + {Key::G ,'g'}, + {Key::H ,'h'}, + {Key::I ,'i'}, + {Key::J ,'j'}, + {Key::K ,'k'}, + {Key::L ,'l'}, + {Key::M ,'m'}, + {Key::N ,'n'}, + {Key::O ,'o'}, + {Key::P ,'p'}, + {Key::Q ,'q'}, + {Key::R ,'r'}, + {Key::S ,'s'}, + {Key::T ,'t'}, + {Key::U ,'u'}, + {Key::V ,'v'}, + {Key::W ,'w'}, + {Key::X ,'x'}, + {Key::Y ,'y'}, + {Key::Z ,'z'}, + {Key::BRACKETLEFT ,'['}, + {Key::BACKSLASH ,'\\'}, + {Key::BRACKETRIGHT ,']'}, + {Key::ASCIICIRCUM ,'^'}, + {Key::UNDERSCORE ,'_'}, + {Key::QUOTELEFT ,'`'}, + {Key::BRACELEFT ,'{'}, + {Key::BAR ,'|'}, + {Key::BRACERIGHT ,'}'}, + {Key::ASCIITILDE ,'~'}, + {Key::NONE ,0x0000} + /* clang-format on */ +}; + +String KeyMappingOSX::keycode_get_native_string(Key p_keycode) { + const _KeyCodeText *kct = &_native_keycodes[0]; + + while (kct->text) { + if (kct->code == p_keycode) { + return String::chr(kct->text); + } + kct++; + } + return String(); +} + +unsigned int KeyMappingOSX::keycode_get_native_mask(Key p_keycode) { + unsigned int mask = 0; + if ((p_keycode & KeyModifierMask::CTRL) != Key::NONE) { + mask |= NSEventModifierFlagControl; + } + if ((p_keycode & KeyModifierMask::ALT) != Key::NONE) { + mask |= NSEventModifierFlagOption; + } + if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) { + mask |= NSEventModifierFlagShift; + } + if ((p_keycode & KeyModifierMask::META) != Key::NONE) { + mask |= NSEventModifierFlagCommand; + } + if ((p_keycode & KeyModifierMask::KPAD) != Key::NONE) { + mask |= NSEventModifierFlagNumericPad; + } + return mask; +} diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 60dec1fe3f..5bb5b3320e 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -40,9 +40,7 @@ #include "servers/audio_server.h" class OS_OSX : public OS_Unix { - virtual void delete_main_loop() override; - - bool force_quit; + bool force_quit = false; JoypadOSX *joypad_osx = nullptr; @@ -55,13 +53,15 @@ class OS_OSX : public OS_Unix { CrashHandler crash_handler; - MainLoop *main_loop; + CFRunLoopObserverRef pre_wait_observer; - static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + MainLoop *main_loop = nullptr; -public: String open_with_filename; + static _FORCE_INLINE_ String get_framework_executable(const String &p_path); + static void pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context); + protected: virtual void initialize_core() override; virtual void initialize() override; @@ -70,8 +70,12 @@ protected: virtual void initialize_joypads() override; virtual void set_main_loop(MainLoop *p_main_loop) override; + virtual void delete_main_loop() override; public: + String get_open_with_filename() const; + void set_open_with_filename(const String &p_path); + virtual String get_name() const override; virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; @@ -89,26 +93,27 @@ public: virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override; - Error shell_open(String p_uri) override; + virtual Error shell_open(String p_uri) override; - String get_locale() const override; + virtual String get_locale() const override; virtual String get_executable_path() const override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) override; - virtual String get_unique_id() const override; //++ + virtual String get_unique_id() const override; virtual bool _check_internal_feature_support(const String &p_feature) override; - void run(); - virtual void disable_crash_handler() override; virtual bool is_disable_crash_handler() const override; virtual Error move_to_trash(const String &p_path) override; + void run(); + OS_OSX(); + ~OS_OSX(); }; #endif diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 32d0e6dd94..9288e658cf 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -31,241 +31,45 @@ #include "os_osx.h" #include "core/version_generated.gen.h" +#include "main/main.h" #include "dir_access_osx.h" #include "display_server_osx.h" -#include "main/main.h" +#include "godot_application.h" +#include "godot_application_delegate.h" +#include "osx_terminal_logger.h" #include <dlfcn.h> #include <libproc.h> #include <mach-o/dyld.h> -#include <os/log.h> - -#define DS_OSX ((DisplayServerOSX *)(DisplayServerOSX::get_singleton())) - -/*************************************************************************/ -/* GodotApplication */ -/*************************************************************************/ - -@interface GodotApplication : NSApplication -@end - -@implementation GodotApplication - -- (void)sendEvent:(NSEvent *)event { - if (DS_OSX) { - DS_OSX->_send_event(event); - } - - // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost - // This works around an AppKit bug, where key up events while holding - // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { - [[self keyWindow] sendEvent:event]; - } else { - [super sendEvent:event]; - } -} - -@end - -/*************************************************************************/ -/* GodotApplicationDelegate */ -/*************************************************************************/ - -@interface GodotApplicationDelegate : NSObject -- (void)forceUnbundledWindowActivationHackStep1; -- (void)forceUnbundledWindowActivationHackStep2; -- (void)forceUnbundledWindowActivationHackStep3; -@end - -@implementation GodotApplicationDelegate - -- (void)forceUnbundledWindowActivationHackStep1 { - // Step 1: Switch focus to macOS SystemUIServer process. - // Required to perform step 2, TransformProcessType will fail if app is already the in focus. - for (NSRunningApplication *app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.systemuiserver"]) { - [app activateWithOptions:NSApplicationActivateIgnoringOtherApps]; - break; - } - [self performSelector:@selector(forceUnbundledWindowActivationHackStep2) - withObject:nil - afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep2 { - // Step 2: Register app as foreground process. - ProcessSerialNumber psn = { 0, kCurrentProcess }; - (void)TransformProcessType(&psn, kProcessTransformToForegroundApplication); - [self performSelector:@selector(forceUnbundledWindowActivationHackStep3) withObject:nil afterDelay:0.02]; -} - -- (void)forceUnbundledWindowActivationHackStep3 { - // Step 3: Switch focus back to app window. - [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)notice { - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil || isatty(STDOUT_FILENO) || isatty(STDIN_FILENO) || isatty(STDERR_FILENO)) { - // If the executable is started from terminal or is not bundled, macOS WindowServer won't register and activate app window correctly (menu and title bar are grayed out and input ignored). - [self performSelector:@selector(forceUnbundledWindowActivationHackStep1) withObject:nil afterDelay:0.02]; - } -} - -- (void)applicationDidResignActive:(NSNotification *)notification { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } -} -- (void)applicationDidBecomeActive:(NSNotification *)notification { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } -} - -- (void)globalMenuCallback:(id)sender { - if (DS_OSX) { - return DS_OSX->_menu_callback(sender); - } -} - -- (NSMenu *)applicationDockMenu:(NSApplication *)sender { - if (DS_OSX) { - return DS_OSX->_get_dock_menu(); +_FORCE_INLINE_ String OS_OSX::get_framework_executable(const String &p_path) { + // Append framework executable name, or return as is if p_path is not a framework. + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { + return p_path.plus_file(p_path.get_file().get_basename()); } else { - return nullptr; - } -} - -- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { - // Note: may be called called before main loop init! - char *utfs = strdup([filename UTF8String]); - ((OS_OSX *)OS_OSX::get_singleton())->open_with_filename.parse_utf8(utfs); - free(utfs); - -#ifdef TOOLS_ENABLED - // Open new instance - if (OS_OSX::get_singleton()->get_main_loop()) { - List<String> args; - args.push_back(((OS_OSX *)OS_OSX::get_singleton())->open_with_filename); - String exec = OS_OSX::get_singleton()->get_executable_path(); - OS_OSX::get_singleton()->create_process(exec, args); - } -#endif - return YES; -} - -- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { - if (DS_OSX) { - DS_OSX->_send_window_event(DS_OSX->windows[DisplayServerOSX::MAIN_WINDOW_ID], DisplayServerOSX::WINDOW_EVENT_CLOSE_REQUEST); - } - return NSTerminateCancel; -} - -- (void)showAbout:(id)sender { - if (OS_OSX::get_singleton()->get_main_loop()) { - OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + return p_path; } } -@end - -/*************************************************************************/ -/* OSXTerminalLogger */ -/*************************************************************************/ - -class OSXTerminalLogger : public StdLogger { -public: - virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) { - if (!should_log(true)) { - return; - } - - const char *err_details; - if (p_rationale && p_rationale[0]) - err_details = p_rationale; - else - err_details = p_code; - - switch (p_type) { - case ERR_WARNING: - os_log_info(OS_LOG_DEFAULT, - "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_SCRIPT: - os_log_error(OS_LOG_DEFAULT, - "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_SHADER: - os_log_error(OS_LOG_DEFAULT, - "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - case ERR_ERROR: - default: - os_log_error(OS_LOG_DEFAULT, - "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", - err_details, p_function, p_file, p_line); - logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); - logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); - break; - } - } -}; - -/*************************************************************************/ -/* OS_OSX */ -/*************************************************************************/ - -String OS_OSX::get_unique_id() const { - static String serial_number; - - if (serial_number.is_empty()) { - io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); - CFStringRef serialNumberAsCFString = nullptr; - if (platformExpert) { - serialNumberAsCFString = (CFStringRef)IORegistryEntryCreateCFProperty(platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); - IOObjectRelease(platformExpert); - } +void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { + // Prevent main loop from sleeping and redraw window during resize / modal popups. - NSString *serialNumberAsNSString = nil; - if (serialNumberAsCFString) { - serialNumberAsNSString = [NSString stringWithString:(NSString *)serialNumberAsCFString]; - CFRelease(serialNumberAsCFString); + if (get_singleton()->get_main_loop()) { + Main::force_redraw(); + if (!Main::is_iterating()) { // Avoid cyclic loop. + Main::iteration(); } - - serial_number = [serialNumberAsNSString UTF8String]; } - return serial_number; + CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. } -void OS_OSX::alert(const String &p_alert, const String &p_title) { - NSAlert *window = [[NSAlert alloc] init]; - NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; - NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; - - [window addButtonWithTitle:@"OK"]; - [window setMessageText:ns_title]; - [window setInformativeText:ns_alert]; - [window setAlertStyle:NSAlertStyleWarning]; +void OS_OSX::initialize() { + crash_handler.initialize(); - id key_window = [[NSApplication sharedApplication] keyWindow]; - [window runModal]; - [window release]; - if (key_window) { - [key_window makeKeyAndOrderFront:nil]; - } + initialize_core(); } void OS_OSX::initialize_core() { @@ -276,17 +80,6 @@ void OS_OSX::initialize_core() { DirAccess::make_default<DirAccessOSX>(DirAccess::ACCESS_FILESYSTEM); } -void OS_OSX::initialize_joypads() { - joypad_osx = memnew(JoypadOSX(Input::get_singleton())); -} - -void OS_OSX::initialize() { - crash_handler.initialize(); - - initialize_core(); - //ensure_user_data_dir(); -} - void OS_OSX::finalize() { #ifdef COREMIDI_ENABLED midi_driver.close(); @@ -299,42 +92,63 @@ void OS_OSX::finalize() { } } +void OS_OSX::initialize_joypads() { + joypad_osx = memnew(JoypadOSX(Input::get_singleton())); +} + void OS_OSX::set_main_loop(MainLoop *p_main_loop) { main_loop = p_main_loop; } void OS_OSX::delete_main_loop() { - if (!main_loop) + if (!main_loop) { return; + } + memdelete(main_loop); main_loop = nullptr; } +String OS_OSX::get_open_with_filename() const { + return open_with_filename; +} + +void OS_OSX::set_open_with_filename(const String &p_path) { + open_with_filename = p_path; +} + String OS_OSX::get_name() const { return "macOS"; } -_FORCE_INLINE_ String _get_framework_executable(const String p_path) { - // Append framework executable name, or return as is if p_path is not a framework. - DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - if (da->dir_exists(p_path) && da->file_exists(p_path.plus_file(p_path.get_file().get_basename()))) { - return p_path.plus_file(p_path.get_file().get_basename()); - } else { - return p_path; +void OS_OSX::alert(const String &p_alert, const String &p_title) { + NSAlert *window = [[NSAlert alloc] init]; + NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()]; + NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()]; + + [window addButtonWithTitle:@"OK"]; + [window setMessageText:ns_title]; + [window setInformativeText:ns_alert]; + [window setAlertStyle:NSAlertStyleWarning]; + + id key_window = [[NSApplication sharedApplication] keyWindow]; + [window runModal]; + if (key_window) { + [key_window makeKeyAndOrderFront:nil]; } } Error OS_OSX::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) { - String path = _get_framework_executable(p_path); + String path = get_framework_executable(p_path); if (!FileAccess::exists(path)) { - // This code exists so gdnative can load .dylib files from within the executable path. - path = _get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); + // Load .dylib or framework from within the executable path. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file(p_path.get_file())); } if (!FileAccess::exists(path)) { - // This code exists so gdnative can load .dylib files from a standard macOS location. - path = _get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); + // Load .dylib or framework from a standard macOS location. + path = get_framework_executable(get_executable_path().get_base_dir().plus_file("../Frameworks").plus_file(p_path.get_file())); } p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); @@ -393,8 +207,8 @@ String OS_OSX::get_bundle_resource_dir() const { NSBundle *main = [NSBundle mainBundle]; if (main) { - NSString *resourcePath = [main resourcePath]; - ret.parse_utf8([resourcePath UTF8String]); + NSString *resource_path = [main resourcePath]; + ret.parse_utf8([resource_path UTF8String]); } return ret; } @@ -404,9 +218,9 @@ String OS_OSX::get_bundle_icon_path() const { NSBundle *main = [NSBundle mainBundle]; if (main) { - NSString *iconPath = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; - if (iconPath) { - ret.parse_utf8([iconPath UTF8String]); + NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"]; + if (icon_path) { + ret.parse_utf8([icon_path UTF8String]); } } return ret; @@ -449,9 +263,7 @@ String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { if (found) { NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES); if (paths && [paths count] >= 1) { - char *utfs = strdup([[paths firstObject] UTF8String]); - ret.parse_utf8(utfs); - free(utfs); + ret.parse_utf8([[paths firstObject] UTF8String]); } } @@ -475,12 +287,9 @@ String OS_OSX::get_locale() const { } String OS_OSX::get_executable_path() const { - int ret; - pid_t pid; char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; - - pid = getpid(); - ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + int pid = getpid(); + pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); if (ret <= 0) { return OS::get_executable_path(); } else { @@ -491,18 +300,6 @@ String OS_OSX::get_executable_path() const { } } -Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) { - // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. - NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname != nil) { - String path; - path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); - return create_process(path, p_arguments, r_child_id, false); - } else { - return create_process(get_executable_path(), p_arguments, r_child_id, false); - } -} - Error OS_OSX::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { if (@available(macOS 10.15, *)) { // Use NSWorkspace if path is an .app bundle. @@ -532,7 +329,6 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen dispatch_semaphore_signal(lock); }]; dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch. - dispatch_release(lock); if (err == OK) { if (r_child_id) { @@ -549,17 +345,66 @@ Error OS_OSX::create_process(const String &p_path, const List<String> &p_argumen } } -void OS_OSX::pre_wait_observer_cb(CFRunLoopObserverRef p_observer, CFRunLoopActivity p_activiy, void *p_context) { - // Prevent main loop from sleeping and redraw window during resize / modal popups. +Error OS_OSX::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) { + // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly. + NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; + if (nsappname != nil) { + String path; + path.parse_utf8([[[NSBundle mainBundle] bundlePath] UTF8String]); + return create_process(path, p_arguments, r_child_id, false); + } else { + return create_process(get_executable_path(), p_arguments, r_child_id, false); + } +} - if (get_singleton()->get_main_loop()) { - Main::force_redraw(); - if (!Main::is_iterating()) { // Avoid cyclic loop. - Main::iteration(); +String OS_OSX::get_unique_id() const { + static String serial_number; + + if (serial_number.is_empty()) { + io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice")); + CFStringRef serial_number_cf_string = nullptr; + if (platform_expert) { + serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0); + IOObjectRelease(platform_expert); + } + + NSString *serial_number_ns_string = nil; + if (serial_number_cf_string) { + serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string]; + CFRelease(serial_number_cf_string); + } + + if (serial_number_ns_string) { + serial_number.parse_utf8([serial_number_ns_string UTF8String]); } } - CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping. + return serial_number; +} + +bool OS_OSX::_check_internal_feature_support(const String &p_feature) { + return p_feature == "pc"; +} + +void OS_OSX::disable_crash_handler() { + crash_handler.disable(); +} + +bool OS_OSX::is_disable_crash_handler() const { + return crash_handler.is_disabled(); +} + +Error OS_OSX::move_to_trash(const String &p_path) { + NSFileManager *fm = [NSFileManager defaultManager]; + NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; + NSError *err; + + if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { + ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); + return FAILED; + } + + return OK; } void OS_OSX::run() { @@ -571,14 +416,11 @@ void OS_OSX::run() { main_loop->initialize(); - CFRunLoopObserverRef pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); - CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - bool quit = false; while (!force_quit && !quit) { @try { if (DisplayServer::get_singleton()) { - DisplayServer::get_singleton()->process_events(); // get rid of pending events + DisplayServer::get_singleton()->process_events(); // Get rid of pending events. } joypad_osx->process_joypads(); @@ -590,25 +432,9 @@ void OS_OSX::run() { } }; - CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); - CFRelease(pre_wait_observer); - main_loop->finalize(); } -Error OS_OSX::move_to_trash(const String &p_path) { - NSFileManager *fm = [NSFileManager defaultManager]; - NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())]; - NSError *err; - - if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) { - ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String)); - return FAILED; - } - - return OK; -} - OS_OSX::OS_OSX() { main_loop = nullptr; force_quit = false; @@ -623,17 +449,17 @@ OS_OSX::OS_OSX() { DisplayServerOSX::register_osx_driver(); - // Implicitly create shared NSApplication instance + // Implicitly create shared NSApplication instance. [GodotApplication sharedApplication]; - // In case we are unbundled, make us a proper UI application + // In case we are unbundled, make us a proper UI application. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; // Menu bar setup must go between sharedApplication above and // finishLaunching below, in order to properly emulate the behavior - // of NSApplicationMain + // of NSApplicationMain. - NSMenu *main_menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""]; [NSApp setMainMenu:main_menu]; [NSApp finishLaunching]; @@ -641,7 +467,10 @@ OS_OSX::OS_OSX() { ERR_FAIL_COND(!delegate); [NSApp setDelegate:delegate]; - //process application:openFile: event + pre_wait_observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, &pre_wait_observer_cb, nullptr); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + + // Process application:openFile: event. while (true) { NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny @@ -659,14 +488,7 @@ OS_OSX::OS_OSX() { [NSApp activateIgnoringOtherApps:YES]; } -bool OS_OSX::_check_internal_feature_support(const String &p_feature) { - return p_feature == "pc"; -} - -void OS_OSX::disable_crash_handler() { - crash_handler.disable(); -} - -bool OS_OSX::is_disable_crash_handler() const { - return crash_handler.is_disabled(); +OS_OSX::~OS_OSX() { + CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes); + CFRelease(pre_wait_observer); } diff --git a/platform/osx/osx_terminal_logger.h b/platform/osx/osx_terminal_logger.h new file mode 100644 index 0000000000..8413509c4b --- /dev/null +++ b/platform/osx/osx_terminal_logger.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* osx_terminal_logger.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 OSX_TERMINAL_LOGGER_H +#define OSX_TERMINAL_LOGGER_H + +#ifdef OSX_ENABLED + +#include "core/io/logger.h" + +class OSXTerminalLogger : public StdLogger { +public: + virtual void log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify = false, ErrorType p_type = ERR_ERROR) override; +}; + +#endif // OSX_ENABLED +#endif // OSX_TERMINAL_LOGGER_H diff --git a/platform/osx/osx_terminal_logger.mm b/platform/osx/osx_terminal_logger.mm new file mode 100644 index 0000000000..c1dca111a7 --- /dev/null +++ b/platform/osx/osx_terminal_logger.mm @@ -0,0 +1,81 @@ +/*************************************************************************/ +/* osx_terminal_logger.mm */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "osx_terminal_logger.h" + +#ifdef OSX_ENABLED + +#include <os/log.h> + +void OSXTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) { + if (!should_log(true)) { + return; + } + + const char *err_details; + if (p_rationale && p_rationale[0]) + err_details = p_rationale; + else + err_details = p_code; + + switch (p_type) { + case ERR_WARNING: + os_log_info(OS_LOG_DEFAULT, + "WARNING: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;33mWARNING:\E[0;93m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SCRIPT: + os_log_error(OS_LOG_DEFAULT, + "SCRIPT ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;35mSCRIPT ERROR:\E[0;95m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_SHADER: + os_log_error(OS_LOG_DEFAULT, + "SHADER ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;36mSHADER ERROR:\E[0;96m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + case ERR_ERROR: + default: + os_log_error(OS_LOG_DEFAULT, + "ERROR: %{public}s\nat: %{public}s (%{public}s:%i)", + err_details, p_function, p_file, p_line); + logf_error("\E[1;31mERROR:\E[0;91m %s\n", err_details); + logf_error("\E[0;90m at: %s (%s:%i)\E[0m\n", p_function, p_file, p_line); + break; + } +} + +#endif // OSX_ENABLED diff --git a/platform/osx/vulkan_context_osx.mm b/platform/osx/vulkan_context_osx.mm index f32fab1eee..bdabc24c28 100644 --- a/platform/osx/vulkan_context_osx.mm +++ b/platform/osx/vulkan_context_osx.mm @@ -44,7 +44,7 @@ Error VulkanContextOSX::window_create(DisplayServer::WindowID p_window_id, Displ createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; createInfo.pNext = nullptr; createInfo.flags = 0; - createInfo.pView = p_window; + createInfo.pView = (__bridge const void *)p_window; VkSurfaceKHR surface; VkResult err = vkCreateMacOSSurfaceMVK(get_instance(), &createInfo, nullptr, &surface); diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp index 71e9d9acbd..5064f6b97f 100644 --- a/platform/windows/crash_handler_windows.cpp +++ b/platform/windows/crash_handler_windows.cpp @@ -33,7 +33,6 @@ #include "core/config/project_settings.h" #include "core/os/os.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #ifdef CRASH_HANDLER_EXCEPTION @@ -179,10 +178,10 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { } // Print the engine version just before, so that people are reminded to include the version in backtrace reports. - if (String(VERSION_HASH).length() != 0) { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME " (" VERSION_HASH ")\n"); + if (String(VERSION_HASH).is_empty()) { + fprintf(stderr, "Engine version: %s\n", VERSION_FULL_NAME); } else { - fprintf(stderr, "Engine version: " VERSION_FULL_NAME "\n"); + fprintf(stderr, "Engine version: %s (%s)\n", VERSION_FULL_NAME, VERSION_HASH); } fprintf(stderr, "Dumping the backtrace. %s\n", msg.utf8().get_data()); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index bcddae45d8..20268b3f6a 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -97,7 +97,10 @@ String DisplayServerWindows::get_name() const { void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). - WindowData &wd = windows[MAIN_WINDOW_ID]; + + WindowID window_id = windows.has(last_focused_window) ? last_focused_window : MAIN_WINDOW_ID; + + WindowData &wd = windows[window_id]; RECT clipRect; GetClientRect(wd.hWnd, &clipRect); @@ -323,6 +326,12 @@ typedef struct { Rect2i rect; } EnumRectData; +typedef struct { + int count; + int screen; + float rate; +} EnumRefreshRateData; + static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumSizeData *data = (EnumSizeData *)dwData; if (data->count == data->screen) { @@ -360,6 +369,26 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito return TRUE; } +static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + EnumRefreshRateData *data = (EnumRefreshRateData *)dwData; + if (data->count == data->screen) { + MONITORINFOEXW minfo; + memset(&minfo, 0, sizeof(minfo)); + minfo.cbSize = sizeof(minfo); + GetMonitorInfoW(hMonitor, &minfo); + + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + + data->rate = dm.dmDisplayFrequency; + } + + data->count++; + return TRUE; +} + Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -443,6 +472,13 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const { EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data); return data.dpi; } +float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { + _THREAD_SAFE_METHOD_ + + EnumRefreshRateData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); + return data.rate; +} bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const { #ifndef _MSC_VER @@ -503,13 +539,22 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) { wd.borderless = true; } - if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.always_on_top = true; } if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) { wd.no_focus = true; } + // Inherit icons from MAIN_WINDOW for all sub windows. + HICON mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_SMALL, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_SMALL, (LPARAM)mainwindow_icon); + } + mainwindow_icon = (HICON)SendMessage(windows[MAIN_WINDOW_ID].hWnd, WM_GETICON, ICON_BIG, 0); + if (mainwindow_icon) { + SendMessage(windows[window_id].hWnd, WM_SETICON, ICON_BIG, (LPARAM)mainwindow_icon); + } return window_id; } @@ -570,6 +615,24 @@ void DisplayServerWindows::gl_window_make_current(DisplayServer::WindowID p_wind #endif } +int64_t DisplayServerWindows::window_get_native_handle(HandleType p_handle_type, WindowID p_window) const { + ERR_FAIL_COND_V(!windows.has(p_window), 0); + switch (p_handle_type) { + case DISPLAY_HANDLE: { + return 0; // Not supported. + } + case WINDOW_HANDLE: { + return (int64_t)windows[p_window].hWnd; + } + case WINDOW_VIEW: { + return 0; // Not supported. + } + default: { + return 0; + } + } +} + void DisplayServerWindows::window_attach_instance_id(ObjectID p_instance, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -688,6 +751,15 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi Vector2 ofs = window_get_position(p_window) - screen_get_position(window_get_current_screen(p_window)); window_set_position(ofs + screen_get_position(p_screen), p_window); } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { @@ -916,7 +988,7 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -929,6 +1001,9 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. + if (p_fullscreen && p_multiwindow_fs) { + r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen. + } } else { if (p_resizable) { if (p_maximized) { @@ -959,7 +1034,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -979,10 +1054,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) { + if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { RECT rect; wd.fullscreen = false; + wd.multiwindow_fs = false; wd.maximized = wd.was_maximized; if (wd.pre_fs_valid) { @@ -1021,7 +1097,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.minimized = true; } - if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) { + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + wd.multiwindow_fs = false; + _update_window_style(false); + } else { + wd.multiwindow_fs = true; + _update_window_style(false); + } + + if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) { if (wd.minimized) { ShowWindow(wd.hWnd, SW_RESTORE); } @@ -1050,6 +1134,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) SystemParametersInfoA(SPI_SETMOUSETRAILS, 0, 0, 0); } } + + // Don't let the mouse leave the window when resizing to a smaller resolution. + if (mouse_mode == MOUSE_MODE_CONFINED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN) { + RECT crect; + GetClientRect(wd.hWnd, &crect); + ClientToScreen(wd.hWnd, (POINT *)&crect.left); + ClientToScreen(wd.hWnd, (POINT *)&crect.right); + ClipCursor(&crect); + } } DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_window) const { @@ -1059,7 +1152,11 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo const WindowData &wd = windows[p_window]; if (wd.fullscreen) { - return WINDOW_MODE_FULLSCREEN; + if (wd.multiwindow_fs) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } } else if (wd.minimized) { return WINDOW_MODE_MINIMIZED; } else if (wd.maximized) { @@ -2072,7 +2169,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_position(c); mm->set_global_position(c); - Input::get_singleton()->set_mouse_position(c); mm->set_velocity(Vector2(0, 0)); if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) { @@ -2177,7 +2273,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { @@ -2319,7 +2414,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { @@ -2420,7 +2514,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA SetCursorPos(pos.x, pos.y); } - Input::get_singleton()->set_mouse_position(mm->get_position()); mm->set_velocity(Input::get_singleton()->get_last_mouse_velocity()); if (old_invalid) { @@ -2614,98 +2707,77 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } } break; - case WM_MOVE: { - if (!IsIconic(windows[window_id].hWnd)) { - int x = int16_t(LOWORD(lParam)); - int y = int16_t(HIWORD(lParam)); - windows[window_id].last_pos = Point2(x, y); - - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *sizep = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce); + + case WM_WINDOWPOSCHANGED: { + Rect2i window_client_rect; + Rect2i window_rect; + { + RECT rect; + GetClientRect(hWnd, &rect); + ClientToScreen(hWnd, (POINT *)&rect.left); + ClientToScreen(hWnd, (POINT *)&rect.right); + window_client_rect = Rect2i(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + + RECT wrect; + GetWindowRect(hWnd, &wrect); + window_rect = Rect2i(wrect.left, wrect.top, wrect.right - wrect.left, wrect.bottom - wrect.top); + } + + WINDOWPOS *window_pos_params = (WINDOWPOS *)lParam; + WindowData &window = windows[window_id]; + + bool rect_changed = false; + if (!(window_pos_params->flags & SWP_NOSIZE) || window_pos_params->flags & SWP_FRAMECHANGED) { + int screen_id = window_get_current_screen(window_id); + Size2i screen_size = screen_get_size(screen_id); + Point2i screen_position = screen_get_position(screen_id); + + window.maximized = false; + window.minimized = false; + window.fullscreen = false; + + if (IsIconic(hWnd)) { + window.minimized = true; + } else if (IsZoomed(hWnd)) { + window.maximized = true; + } else if (window_rect.position == screen_position && window_rect.size == screen_size) { + window.fullscreen = true; } - } - } break; - case WM_SIZE: { - // Ignore window size change when a SIZE_MINIMIZED event is triggered. - if (wParam != SIZE_MINIMIZED) { - // The new width and height of the client area. - int window_w = LOWORD(lParam); - int window_h = HIWORD(lParam); - - // Set new value to the size if it isn't preserved. - if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) { - windows[window_id].width = window_w; - windows[window_id].height = window_h; + + if (!window.minimized) { + window.width = window_client_rect.size.width; + window.height = window_client_rect.size.height; #if defined(VULKAN_ENABLED) if (context_vulkan && window_created) { - context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); + context_vulkan->window_resize(window_id, window.width, window.height); } #endif + rect_changed = true; + } + } - } else { // If the size is preserved. - windows[window_id].preserve_window_size = false; + if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) { + window.last_pos = window_client_rect.position; + rect_changed = true; + } - // Restore the old size. - window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id); + if (rect_changed) { + if (!window.rect_changed_callback.is_null()) { + Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height); + const Variant *args[] = { &size }; + Variant ret; + Callable::CallError ce; + window.rect_changed_callback.call(args, 1, ret, ce); } - } else { // When the window has been minimized, preserve its size. - windows[window_id].preserve_window_size = true; } - // Call windows rect change callback. - if (!windows[window_id].rect_changed_callback.is_null()) { - Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height); - Variant *size_ptr = &size; - Variant ret; - Callable::CallError ce; - windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce); - } - - // The window has been maximized. - if (wParam == SIZE_MAXIMIZED) { - windows[window_id].maximized = true; - windows[window_id].minimized = false; - } - // The window has been minimized. - else if (wParam == SIZE_MINIMIZED) { - windows[window_id].maximized = false; - windows[window_id].minimized = true; - windows[window_id].preserve_window_size = false; - } - // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies. - else if (wParam == SIZE_RESTORED) { - windows[window_id].maximized = false; - windows[window_id].minimized = false; - } -#if 0 - if (is_layered_allowed() && layered_window) { - DeleteObject(hBitmap); - - RECT r; - GetWindowRect(hWnd, &r); - dib_size = Size2i(r.right - r.left, r.bottom - r.top); - - BITMAPINFO bmi; - ZeroMemory(&bmi, sizeof(BITMAPINFO)); - bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); - bmi.bmiHeader.biWidth = dib_size.x; - bmi.bmiHeader.biHeight = dib_size.y; - bmi.bmiHeader.biPlanes = 1; - bmi.bmiHeader.biBitCount = 32; - bmi.bmiHeader.biCompression = BI_RGB; - bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4; - hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0); - SelectObject(hDC_dib, hBitmap); - - ZeroMemory(dib_data, dib_size.x * dib_size.y * 4); - } -#endif + // Return here to prevent WM_MOVE and WM_SIZE from being sent + // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks + return 0; + } break; + case WM_ENTERSIZEMOVE: { Input::get_singleton()->release_pressed_events(); windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); @@ -2883,6 +2955,9 @@ void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM alt_mem = false; control_mem = false; shift_mem = false; + + // Restore mouse mode. + _set_mouse_mode_impl(mouse_mode); } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); @@ -3050,7 +3125,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); RECT WindowRect; @@ -3059,7 +3134,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, WindowRect.top = p_rect.position.y; WindowRect.bottom = p_rect.position.y + p_rect.size.y; - if (p_mode == WINDOW_MODE_FULLSCREEN) { + if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { int nearest_area = 0; Rect2i screen_rect; for (int i = 0; i < get_screen_count(); i++) { @@ -3102,7 +3177,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, windows.erase(id); return INVALID_WINDOW_ID; } - if (p_mode != WINDOW_MODE_FULLSCREEN) { + if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { wd.pre_fs_valid = true; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 3593dc1a05..d36ca97ebe 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -326,12 +326,12 @@ class DisplayServerWindows : public DisplayServer { Vector<Vector2> mpath; - bool preserve_window_size = false; bool pre_fs_valid = false; RECT pre_fs_rect; bool maximized = false; bool minimized = false; bool fullscreen = false; + bool multiwindow_fs = false; bool borderless = false; bool resizable = true; bool window_focused = false; @@ -401,7 +401,7 @@ class DisplayServerWindows : public DisplayServer { WNDPROC user_proc = nullptr; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; @@ -458,6 +458,7 @@ public: 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_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override; @@ -472,6 +473,8 @@ public: virtual void show_window(WindowID p_window) override; virtual void delete_sub_window(WindowID p_window) override; + virtual int64_t window_get_native_handle(HandleType p_handle_type, WindowID p_window = MAIN_WINDOW_ID) const override; + virtual WindowID get_window_at_screen_position(const Point2i &p_position) const override; virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override; diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index 68762db3a9..d30d0afc5c 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -76,8 +76,8 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), "")); @@ -89,6 +89,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset> String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); if (rcedit_path.is_empty()) { + WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable."); return; } @@ -327,3 +328,46 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p return OK; } + +bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const { + String err = ""; + bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates); + + String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit"); + if (rcedit_path.is_empty()) { + err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n"; + } + + String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon")); + if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) { + err += TTR("Invalid icon path:") + " " + icon_path + "\n"; + } + + // Only non-negative integers can exist in the version string. + + String file_version = p_preset->get("application/file_version"); + if (!file_version.is_empty()) { + PackedStringArray version_array = file_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || file_version.find("-") > -1) { + err += TTR("Invalid file version:") + " " + file_version + "\n"; + } + } + + String product_version = p_preset->get("application/product_version"); + if (!product_version.is_empty()) { + PackedStringArray version_array = product_version.split(".", false); + if (version_array.size() != 4 || !version_array[0].is_valid_int() || + !version_array[1].is_valid_int() || !version_array[2].is_valid_int() || + !version_array[3].is_valid_int() || product_version.find("-") > -1) { + err += TTR("Invalid product version:") + " " + product_version + "\n"; + } + } + + if (!err.is_empty()) { + r_error = err; + } + + return valid; +} diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 351333aa42..89e5b1b635 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -47,6 +47,7 @@ public: virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override; virtual void get_export_options(List<ExportOption> *r_options) override; virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override; + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override; }; #endif diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index 7819ab9a32..618d5670d2 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -39,7 +39,7 @@ #ifndef TOOLS_ENABLED #if defined _MSC_VER #pragma section("pck", read) -__declspec(allocate("pck")) static char dummy[8] = { 0 }; +__declspec(allocate("pck")) static const char dummy[8] = { 0 }; #elif defined __GNUC__ static const char dummy[8] __attribute__((section("pck"), used)) = { 0 }; #endif @@ -140,6 +140,11 @@ int widechar_main(int argc, wchar_t **argv) { setlocale(LC_CTYPE, ""); +#ifndef TOOLS_ENABLED + // Workaround to prevent LTCG (MSVC LTO) from removing "pck" section + const char *dummy_guard = dummy; +#endif + char **argv_utf8 = new char *[argc]; for (int i = 0; i < argc; ++i) { diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 06b8fea681..d844531071 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -83,15 +83,23 @@ static String format_error_message(DWORD id) { return msg; } +void RedirectStream(const char *p_file_name, const char *p_mode, FILE *p_cpp_stream, const DWORD p_std_handle) { + const HANDLE h_existing = GetStdHandle(p_std_handle); + if (h_existing != INVALID_HANDLE_VALUE) { // Redirect only if attached console have a valid handle. + const HANDLE h_cpp = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(p_cpp_stream))); + if (h_cpp == INVALID_HANDLE_VALUE) { // Redirect only if it's not already redirected to the pipe or file. + FILE *fp = p_cpp_stream; + freopen_s(&fp, p_file_name, p_mode, p_cpp_stream); // Redirect stream. + setvbuf(p_cpp_stream, nullptr, _IONBF, 0); // Disable stream buffering. + } + } +} + void RedirectIOToConsole() { if (AttachConsole(ATTACH_PARENT_PROCESS)) { - FILE *fpstdin = stdin; - FILE *fpstdout = stdout; - FILE *fpstderr = stderr; - - freopen_s(&fpstdin, "CONIN$", "r", stdin); - freopen_s(&fpstdout, "CONOUT$", "w", stdout); - freopen_s(&fpstderr, "CONOUT$", "w", stderr); + RedirectStream("CONIN$", "r", stdin, STD_INPUT_HANDLE); + RedirectStream("CONOUT$", "w", stdout, STD_OUTPUT_HANDLE); + RedirectStream("CONOUT$", "w", stderr, STD_ERROR_HANDLE); printf("\n"); // Make sure our output is starting from the new line. } @@ -385,14 +393,14 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, } inherit_handles = true; } - DWORD creaton_flags = NORMAL_PRIORITY_CLASS; + DWORD creation_flags = NORMAL_PRIORITY_CLASS; if (p_open_console) { - creaton_flags |= CREATE_NEW_CONSOLE; + creation_flags |= CREATE_NEW_CONSOLE; } else { - creaton_flags |= CREATE_NO_WINDOW; + creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creaton_flags, nullptr, nullptr, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi); if (!ret && r_pipe) { CloseHandle(pipe[0]); // Cleanup pipe handles. CloseHandle(pipe[1]); @@ -446,14 +454,14 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg ZeroMemory(&pi.pi, sizeof(pi.pi)); LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; - DWORD creaton_flags = NORMAL_PRIORITY_CLASS; + DWORD creation_flags = NORMAL_PRIORITY_CLASS; if (p_open_console) { - creaton_flags |= CREATE_NEW_CONSOLE; + creation_flags |= CREATE_NEW_CONSOLE; } else { - creaton_flags |= CREATE_NO_WINDOW; + creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creaton_flags, nullptr, nullptr, si_w, &pi.pi); + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi); ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); ProcessID pid = pi.pi.dwProcessId; |