diff options
Diffstat (limited to 'platform')
42 files changed, 986 insertions, 429 deletions
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp index 428135de56..4f1ac16975 100644 --- a/platform/android/dir_access_jandroid.cpp +++ b/platform/android/dir_access_jandroid.cpp @@ -135,6 +135,13 @@ String DirAccessJAndroid::get_drive(int p_drive) { } } +String DirAccessJAndroid::_get_root_string() const { + if (get_access_type() == ACCESS_FILESYSTEM) { + return "/"; + } + return DirAccessUnix::_get_root_string(); +} + String DirAccessJAndroid::get_current_dir(bool p_include_drive) const { String base = _get_root_path(); String bd = current_dir; @@ -142,10 +149,13 @@ String DirAccessJAndroid::get_current_dir(bool p_include_drive) const { bd = current_dir.replace_first(base, ""); } - if (bd.begins_with("/")) { - return _get_root_string() + bd.substr(1, bd.length()); + String root_string = _get_root_string(); + if (bd.begins_with(root_string)) { + return bd; + } else if (bd.begins_with("/")) { + return root_string + bd.substr(1, bd.length()); } else { - return _get_root_string() + bd; + return root_string + bd; } } @@ -169,7 +179,7 @@ String DirAccessJAndroid::get_absolute_path(String p_path) { } if (p_path.is_relative_path()) { - p_path = get_current_dir().plus_file(p_path); + p_path = get_current_dir().path_join(p_path); } p_path = fix_path(p_path); diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h index 5b7b4a9c4d..5c4f1852a9 100644 --- a/platform/android/dir_access_jandroid.h +++ b/platform/android/dir_access_jandroid.h @@ -91,6 +91,9 @@ public: DirAccessJAndroid(); ~DirAccessJAndroid(); +protected: + String _get_root_string() const override; + private: int id = 0; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 685b1f01af..0f8ef3f7d6 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -624,7 +624,7 @@ Vector<String> EditorExportPlatformAndroid::list_gdap_files(const String &p_path Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_plugins() { Vector<PluginConfigAndroid> loaded_plugins; - String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/plugins"); + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().path_join("android/plugins"); // Add the prebuilt plugins loaded_plugins.append_array(PluginConfigAndroid::get_prebuilt_plugins(plugins_dir)); @@ -635,7 +635,7 @@ Vector<PluginConfigAndroid> EditorExportPlatformAndroid::get_plugins() { if (!plugins_filenames.is_empty()) { Ref<ConfigFile> config_file = memnew(ConfigFile); for (int i = 0; i < plugins_filenames.size(); i++) { - PluginConfigAndroid config = PluginConfigAndroid::load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + PluginConfigAndroid config = PluginConfigAndroid::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i])); if (config.valid_config) { loaded_plugins.push_back(config); } else { @@ -696,7 +696,7 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj if (abi_index != -1) { exported = true; String abi = abis[abi_index]; - String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file()); + String dst_path = String("lib").path_join(abi).path_join(p_so.path.get_file()); Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path); Error store_err = store_in_apk(ed, dst_path, array); ERR_FAIL_COND_V_MSG(store_err, store_err, "Cannot store in apk file '" + dst_path + "'."); @@ -737,7 +737,7 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared String type = export_data->debug ? "debug" : "release"; String abi = abis[abi_index]; String filename = p_so.path.get_file(); - String dst_path = base.plus_file(type).plus_file(abi).plus_file(filename); + String dst_path = base.path_join(type).path_join(abi).path_join(filename); Vector<uint8_t> data = FileAccess::get_file_as_array(p_so.path); print_verbose("Copying .so file from " + p_so.path + " to " + dst_path); Error err = store_file_at_path(dst_path, data); @@ -1851,7 +1851,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; } - String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); + String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ @@ -2004,7 +2004,7 @@ String EditorExportPlatformAndroid::get_adb_path() { exe_ext = ".exe"; } String sdk_path = EditorSettings::get_singleton()->get("export/android/android_sdk_path"); - return sdk_path.plus_file("platform-tools/adb" + exe_ext); + return sdk_path.path_join("platform-tools/adb" + exe_ext); } String EditorExportPlatformAndroid::get_apksigner_path() { @@ -2017,7 +2017,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() { String apksigner_path = ""; Error errn; - String build_tools_dir = sdk_path.plus_file("build-tools"); + String build_tools_dir = sdk_path.path_join("build-tools"); Ref<DirAccess> da = DirAccess::open(build_tools_dir, &errn); if (errn != OK) { print_error("Unable to open Android 'build-tools' directory."); @@ -2030,7 +2030,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() { while (!sub_dir.is_empty()) { if (!sub_dir.begins_with(".") && da->current_is_dir()) { // Check if the tool is here. - String tool_path = build_tools_dir.plus_file(sub_dir).plus_file(apksigner_command_name); + String tool_path = build_tools_dir.path_join(sub_dir).path_join(apksigner_command_name); if (FileAccess::exists(tool_path)) { apksigner_path = tool_path; break; @@ -2135,7 +2135,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } else { Error errn; // Check for the platform-tools directory. - Ref<DirAccess> da = DirAccess::open(sdk_path.plus_file("platform-tools"), &errn); + Ref<DirAccess> da = DirAccess::open(sdk_path.path_join("platform-tools"), &errn); if (errn != OK) { err += TTR("Invalid Android SDK path in Editor Settings."); err += TTR("Missing 'platform-tools' directory!"); @@ -2153,7 +2153,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito } // Check for the build-tools directory. - Ref<DirAccess> build_tools_da = DirAccess::open(sdk_path.plus_file("build-tools"), &errn); + Ref<DirAccess> build_tools_da = DirAccess::open(sdk_path.path_join("build-tools"), &errn); if (errn != OK) { err += TTR("Invalid Android SDK path in Editor Settings."); err += TTR("Missing 'build-tools' directory!"); @@ -2310,7 +2310,7 @@ String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorE int version_code = p_preset->get("version/code"); String package_name = p_preset->get("package/unique_name"); String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb"; - String fullpath = p_path.get_base_dir().plus_file(apk_file_name); + String fullpath = p_path.get_base_dir().path_join(apk_file_name); return fullpath; } @@ -2671,8 +2671,8 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP build_command = "gradlew"; #endif - String build_path = ProjectSettings::get_singleton()->get_resource_path().plus_file("android/build"); - build_command = build_path.plus_file(build_command); + String build_path = ProjectSettings::get_singleton()->get_resource_path().path_join("android/build"); + build_command = build_path.path_join(build_command); String package_name = get_package_name(p_preset->get("package/unique_name")); String version_code = itos(p_preset->get("version/code")); @@ -2742,7 +2742,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP debug_user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user"); } if (debug_keystore.is_relative_path()) { - debug_keystore = OS::get_singleton()->get_resource_dir().plus_file(debug_keystore).simplify_path(); + debug_keystore = OS::get_singleton()->get_resource_dir().path_join(debug_keystore).simplify_path(); } if (!FileAccess::exists(debug_keystore)) { add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export.")); @@ -2758,7 +2758,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String release_username = p_preset->get("keystore/release_user"); String release_password = p_preset->get("keystore/release_password"); if (release_keystore.is_relative_path()) { - release_keystore = OS::get_singleton()->get_resource_dir().plus_file(release_keystore).simplify_path(); + release_keystore = OS::get_singleton()->get_resource_dir().path_join(release_keystore).simplify_path(); } if (!FileAccess::exists(release_keystore)) { add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export.")); @@ -2793,7 +2793,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP String export_filename = p_path.get_file(); String export_path = p_path.get_base_dir(); if (export_path.is_relative_path()) { - export_path = OS::get_singleton()->get_resource_dir().plus_file(export_path); + export_path = OS::get_singleton()->get_resource_dir().path_join(export_path); } export_path = ProjectSettings::get_singleton()->globalize_path(export_path).simplify_path(); @@ -2852,7 +2852,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP Ref<FileAccess> io2_fa; zlib_filefunc_def io2 = zipio_create_io(&io2_fa); - String tmp_unaligned_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); + String tmp_unaligned_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); #define CLEANUP_AND_RETURN(m_err) \ { \ diff --git a/platform/android/export/godot_plugin_config.cpp b/platform/android/export/godot_plugin_config.cpp index 3daf6e44cd..21580ae907 100644 --- a/platform/android/export/godot_plugin_config.cpp +++ b/platform/android/export/godot_plugin_config.cpp @@ -50,7 +50,7 @@ String PluginConfigAndroid::resolve_local_dependency_path(String plugin_config_d if (dependency_path.is_absolute_path()) { absolute_path = ProjectSettings::get_singleton()->globalize_path(dependency_path); } else { - absolute_path = plugin_config_dir.plus_file(dependency_path); + absolute_path = plugin_config_dir.path_join(dependency_path); } } diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index f94614c741..142dc54c45 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -362,7 +362,7 @@ void OS_Android::vibrate_handheld(int p_duration_ms) { } String OS_Android::get_config_path() const { - return get_user_data_dir().plus_file("config"); + return get_user_data_dir().path_join("config"); } bool OS_Android::_check_internal_feature_support(const String &p_feature) { diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index f49cf7a88d..7aacb2de85 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -649,7 +649,7 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) { Ref<Image> image; - String image_path = p_dest_dir.plus_file("splash@2x.png"); + String image_path = p_dest_dir.path_join("splash@2x.png"); image.instantiate(); Error err = image->load(custom_launch_image_2x); @@ -663,7 +663,7 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor } image.unref(); - image_path = p_dest_dir.plus_file("splash@3x.png"); + image_path = p_dest_dir.path_join("splash@3x.png"); image.instantiate(); err = image->load(custom_launch_image_3x); @@ -696,8 +696,8 @@ Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExpor // because Godot's own boot logo uses single image for all resolutions. // Also not using @1x image, because devices using this image variant // are not supported by iOS 9, which is minimal target. - const String splash_png_path_2x = p_dest_dir.plus_file("splash@2x.png"); - const String splash_png_path_3x = p_dest_dir.plus_file("splash@3x.png"); + const String splash_png_path_2x = p_dest_dir.path_join("splash@2x.png"); + const String splash_png_path_3x = p_dest_dir.path_join("splash@3x.png"); if (splash->save_png(splash_png_path_2x) != OK) { return ERR_FILE_CANT_WRITE; @@ -812,7 +812,7 @@ Error EditorExportPlatformIOS::_walk_dir_recursive(Ref<DirAccess> &p_da, FileHan dirs.push_back(path); } } else { - Error err = p_handler(current_dir.plus_file(path), p_userdata); + Error err = p_handler(current_dir.path_join(path), p_userdata); if (err) { p_da->list_dir_end(); return err; @@ -1028,7 +1028,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String if (p_is_framework && p_asset.ends_with(".dylib")) { // For iOS we need to turn .dylib into .framework // to be able to send application to AppStore - asset_path = String("dylibs").plus_file(base_dir); + asset_path = String("dylibs").path_join(base_dir); String file_name; @@ -1040,12 +1040,12 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String String framework_name = file_name + ".framework"; - asset_path = asset_path.plus_file(framework_name); - destination_dir = p_out_dir.plus_file(asset_path); - destination = destination_dir.plus_file(file_name); + asset_path = asset_path.path_join(framework_name); + destination_dir = p_out_dir.path_join(asset_path); + destination = destination_dir.path_join(file_name); create_framework = true; } else if (p_is_framework && (p_asset.ends_with(".framework") || p_asset.ends_with(".xcframework"))) { - asset_path = String("dylibs").plus_file(base_dir); + asset_path = String("dylibs").path_join(base_dir); String file_name; @@ -1055,8 +1055,8 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String file_name = *p_custom_file_name; } - asset_path = asset_path.plus_file(file_name); - destination_dir = p_out_dir.plus_file(asset_path); + asset_path = asset_path.path_join(file_name); + destination_dir = p_out_dir.path_join(asset_path); destination = destination_dir; } else { asset_path = base_dir; @@ -1069,9 +1069,9 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String file_name = *p_custom_file_name; } - destination_dir = p_out_dir.plus_file(asset_path); - asset_path = asset_path.plus_file(file_name); - destination = p_out_dir.plus_file(asset_path); + destination_dir = p_out_dir.path_join(asset_path); + asset_path = asset_path.path_join(file_name); + destination = p_out_dir.path_join(asset_path); } Ref<DirAccess> filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -1088,7 +1088,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String if (err) { return err; } - IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework, p_should_embed }; + IOSExportAsset exported_asset = { binary_name.path_join(asset_path), p_is_framework, p_should_embed }; r_exported_assets.push_back(exported_asset); if (create_framework) { @@ -1106,7 +1106,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String { List<String> install_name_args; install_name_args.push_back("-id"); - install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); + install_name_args.push_back(String("@rpath").path_join(framework_name).path_join(file_name)); install_name_args.push_back(destination); OS::get_singleton()->execute("install_name_tool", install_name_args); @@ -1141,7 +1141,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String String info_plist = info_plist_format.replace("$name", file_name); - Ref<FileAccess> f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + Ref<FileAccess> f = FileAccess::open(destination_dir.path_join("Info.plist"), FileAccess::WRITE); if (f.is_valid()) { f->store_string(info_plist); } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index abda8e218a..639f2416a5 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -238,9 +238,9 @@ public: if (da->current_is_dir()) { if (p_check_directories) { - Vector<String> directory_files = list_plugin_config_files(p_path.plus_file(file), false); + Vector<String> directory_files = list_plugin_config_files(p_path.path_join(file), false); for (int i = 0; i < directory_files.size(); ++i) { - dir_files.push_back(file.plus_file(directory_files[i])); + dir_files.push_back(file.path_join(directory_files[i])); } } @@ -260,7 +260,7 @@ public: static Vector<PluginConfigIOS> get_plugins() { Vector<PluginConfigIOS> loaded_plugins; - String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().plus_file("ios/plugins"); + String plugins_dir = ProjectSettings::get_singleton()->get_resource_path().path_join("ios/plugins"); if (DirAccess::exists(plugins_dir)) { Vector<String> plugins_filenames = list_plugin_config_files(plugins_dir, true); @@ -268,7 +268,7 @@ public: if (!plugins_filenames.is_empty()) { Ref<ConfigFile> config_file = memnew(ConfigFile); for (int i = 0; i < plugins_filenames.size(); i++) { - PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.plus_file(plugins_filenames[i])); + PluginConfigIOS config = PluginConfigIOS::load_plugin_config(config_file, plugins_dir.path_join(plugins_filenames[i])); if (config.valid_config) { loaded_plugins.push_back(config); } else { diff --git a/platform/ios/export/godot_plugin_config.cpp b/platform/ios/export/godot_plugin_config.cpp index 24a95a11a4..42797d449b 100644 --- a/platform/ios/export/godot_plugin_config.cpp +++ b/platform/ios/export/godot_plugin_config.cpp @@ -46,7 +46,7 @@ String PluginConfigIOS::resolve_local_dependency_path(String plugin_config_dir, } String res_path = ProjectSettings::get_singleton()->globalize_path("res://"); - absolute_path = plugin_config_dir.plus_file(dependency_path); + absolute_path = plugin_config_dir.path_join(dependency_path); return absolute_path.replace(res_path, "res://"); } @@ -64,7 +64,7 @@ String PluginConfigIOS::resolve_system_dependency_path(String dependency_path) { String system_path = "/System/Library/Frameworks"; - return system_path.plus_file(dependency_path); + return system_path.path_join(dependency_path); } Vector<String> PluginConfigIOS::resolve_local_dependencies(String plugin_config_dir, Vector<String> p_paths) { @@ -121,8 +121,8 @@ bool PluginConfigIOS::validate_plugin(PluginConfigIOS &plugin_config) { String file_path = plugin_config.binary.get_base_dir(); String file_name = plugin_config.binary.get_basename().get_file(); String file_extension = plugin_config.binary.get_extension(); - String release_file_name = file_path.plus_file(file_name + ".release." + file_extension); - String debug_file_name = file_path.plus_file(file_name + ".debug." + file_extension); + String release_file_name = file_path.path_join(file_name + ".release." + file_extension); + String debug_file_name = file_path.path_join(file_name + ".debug." + file_extension); if ((plugin_extension == "a" && FileAccess::exists(release_file_name) && FileAccess::exists(debug_file_name)) || (plugin_extension == "xcframework" && DirAccess::exists(release_file_name) && DirAccess::exists(debug_file_name))) { @@ -144,7 +144,7 @@ String PluginConfigIOS::get_plugin_main_binary(PluginConfigIOS &plugin_config, b String plugin_extension = plugin_config.binary.get_extension(); String plugin_file = plugin_name_prefix + "." + (p_debug ? "debug" : "release") + "." + plugin_extension; - return plugin_binary_dir.plus_file(plugin_file); + return plugin_binary_dir.path_join(plugin_file); } uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &plugin_config, const String &config_path) { @@ -156,8 +156,8 @@ uint64_t PluginConfigIOS::get_plugin_modification_time(const PluginConfigIOS &pl String file_path = plugin_config.binary.get_base_dir(); String file_name = plugin_config.binary.get_basename().get_file(); String plugin_extension = plugin_config.binary.get_extension(); - String release_file_name = file_path.plus_file(file_name + ".release." + plugin_extension); - String debug_file_name = file_path.plus_file(file_name + ".debug." + plugin_extension); + String release_file_name = file_path.path_join(file_name + ".release." + plugin_extension); + String debug_file_name = file_path.path_join(file_name + ".debug." + plugin_extension); last_updated = MAX(last_updated, FileAccess::get_modified_time(release_file_name)); last_updated = MAX(last_updated, FileAccess::get_modified_time(debug_file_name)); diff --git a/platform/linuxbsd/SCsub b/platform/linuxbsd/SCsub index 35c41556ee..91d45627b9 100644 --- a/platform/linuxbsd/SCsub +++ b/platform/linuxbsd/SCsub @@ -9,6 +9,7 @@ common_linuxbsd = [ "crash_handler_linuxbsd.cpp", "os_linuxbsd.cpp", "joypad_linux.cpp", + "freedesktop_portal_desktop.cpp", "freedesktop_screensaver.cpp", ] diff --git a/platform/linuxbsd/detect.py b/platform/linuxbsd/detect.py index dd829bdb9b..f5f7e65417 100644 --- a/platform/linuxbsd/detect.py +++ b/platform/linuxbsd/detect.py @@ -40,7 +40,7 @@ def get_opts(): BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False), BoolVariable("use_msan", "Use LLVM compiler memory sanitizer (MSAN)", False), BoolVariable("pulseaudio", "Detect and use PulseAudio", True), - BoolVariable("dbus", "Detect and use D-Bus to handle screensaver", True), + BoolVariable("dbus", "Detect and use D-Bus to handle screensaver and portal desktop settings", True), BoolVariable("speechd", "Detect and use Speech Dispatcher for Text-to-Speech support", True), BoolVariable("fontconfig", "Detect and use fontconfig for system fonts support", True), BoolVariable("udev", "Use udev for gamepad connection callbacks", True), diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index cacb9d2a27..c619e8eceb 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -349,6 +349,28 @@ void DisplayServerX11::tts_stop() { #endif +#ifdef DBUS_ENABLED + +bool DisplayServerX11::is_dark_mode_supported() const { + return portal_desktop->is_supported(); +} + +bool DisplayServerX11::is_dark_mode() const { + switch (portal_desktop->get_appearance_color_scheme()) { + case 1: + // Prefers dark theme. + return true; + case 2: + // Prefers light theme. + return false; + default: + // Preference unknown. + return false; + } +} + +#endif + void DisplayServerX11::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ @@ -1823,6 +1845,47 @@ bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_a return retval; } +bool DisplayServerX11::_window_minimize_check(WindowID p_window) const { + const WindowData &wd = windows[p_window]; + + // Using ICCCM -- Inter-Client Communication Conventions Manual + Atom property = XInternAtom(x11_display, "WM_STATE", True); + if (property == None) { + return false; + } + + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + + int result = XGetWindowProperty( + x11_display, + wd.x11_window, + property, + 0, + 32, + False, + AnyPropertyType, + &type, + &format, + &len, + &remaining, + &data); + + if (result == Success && data) { + long *state = (long *)data; + if (state[0] == WM_IconicState) { + XFree(data); + return true; + } + XFree(data); + } + + return false; +} + bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; @@ -1869,6 +1932,18 @@ bool DisplayServerX11::_window_fullscreen_check(WindowID p_window) const { return retval; } +void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { + // Check if we applied any window modes that didn't take effect while unmapped + const WindowData &wd = windows[p_window]; + if (wd.fullscreen && !_window_fullscreen_check(p_window)) { + _set_wm_fullscreen(p_window, true); + } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { + _set_wm_maximized(p_window, true); + } else if (wd.minimized && !_window_minimize_check(p_window)) { + _set_wm_minimized(p_window, true); + } +} + bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { _THREAD_SAFE_METHOD_ return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS"); @@ -1903,6 +1978,37 @@ void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { usleep(10000); } } + wd.maximized = p_enabled; +} + +void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { + WindowData &wd = windows[p_window]; + // Using ICCCM -- Inter-Client Communication Conventions Manual + XEvent xev; + Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_change; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + + Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); + Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = wd.x11_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; + xev.xclient.data.l[1] = wm_hidden; + + XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + wd.minimized = p_enabled; } void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { @@ -1984,32 +2090,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { //do nothing } break; case WINDOW_MODE_MINIMIZED: { - //Un-Minimize - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = WM_NormalState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_hidden; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + _set_wm_minimized(p_window, false); } break; case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { @@ -2038,31 +2119,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { //do nothing } break; case WINDOW_MODE_MINIMIZED: { - // Using ICCCM -- Inter-Client Communication Conventions Manual - XEvent xev; - Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_change; - xev.xclient.format = 32; - xev.xclient.data.l[0] = WM_IconicState; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); - - Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom wm_hidden = XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False); - - memset(&xev, 0, sizeof(xev)); - xev.type = ClientMessage; - xev.xclient.window = wd.x11_window; - xev.xclient.message_type = wm_state; - xev.xclient.format = 32; - xev.xclient.data.l[0] = _NET_WM_STATE_ADD; - xev.xclient.data.l[1] = wm_hidden; - - XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + _set_wm_minimized(p_window, true); } break; case WINDOW_MODE_EXCLUSIVE_FULLSCREEN: case WINDOW_MODE_FULLSCREEN: { @@ -2097,40 +2154,9 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c return WINDOW_MODE_MAXIMIZED; } - { // Test minimized. - // Using ICCCM -- Inter-Client Communication Conventions Manual - Atom property = XInternAtom(x11_display, "WM_STATE", True); - if (property == None) { - return WINDOW_MODE_WINDOWED; - } - - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 32, - False, - AnyPropertyType, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - long *state = (long *)data; - if (state[0] == WM_IconicState) { - XFree(data); - return WINDOW_MODE_MINIMIZED; - } - XFree(data); + { + if (_window_minimize_check(p_window)) { + return WINDOW_MODE_MINIMIZED; } } @@ -2195,7 +2221,7 @@ void DisplayServerX11::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo } break; case WINDOW_FLAG_TRANSPARENT: { - //todo reimplement + wd.layered_window = p_enabled; } break; case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; @@ -2248,7 +2274,7 @@ bool DisplayServerX11::window_get_flag(WindowFlags p_flag, WindowID p_window) co return wd.on_top; } break; case WINDOW_FLAG_TRANSPARENT: { - //todo reimplement + return wd.layered_window; } break; case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; @@ -3660,6 +3686,9 @@ void DisplayServerX11::process_events() { if ((xwa.map_state == IsViewable) && !wd.no_focus && !wd.is_popup) { XSetInputFocus(x11_display, wd.x11_window, RevertToPointerRoot, CurrentTime); } + + // Have we failed to set fullscreen while the window was unmapped? + _validate_mode_on_map(window_id); } break; case Expose: { @@ -3679,8 +3708,7 @@ void DisplayServerX11::process_events() { case VisibilityNotify: { DEBUG_LOG_X11("[%u] VisibilityNotify window=%lu (%u), state=%u \n", frame, event.xvisibility.window, window_id, event.xvisibility.state); - XVisibilityEvent *visibility = (XVisibilityEvent *)&event; - windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); + windows[window_id].minimized = _window_minimize_check(window_id); } break; case LeaveNotify: { @@ -4398,13 +4426,41 @@ DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, W DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect) { //Create window - long visualMask = VisualScreenMask; - int numberOfVisuals; - XVisualInfo vInfoTemplate = {}; - vInfoTemplate.screen = DefaultScreen(x11_display); - XVisualInfo *visualInfo = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); + XVisualInfo visualInfo; + bool vi_selected = false; - Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, vInfoTemplate.screen), visualInfo->visual, AllocNone); +#ifdef GLES3_ENABLED + if (gl_manager) { + visualInfo = gl_manager->get_vi(x11_display); + vi_selected = true; + } +#endif + + if (!vi_selected) { + long visualMask = VisualScreenMask; + int numberOfVisuals; + XVisualInfo vInfoTemplate = {}; + vInfoTemplate.screen = DefaultScreen(x11_display); + XVisualInfo *vi_list = XGetVisualInfo(x11_display, visualMask, &vInfoTemplate, &numberOfVisuals); + ERR_FAIL_COND_V(!vi_list, INVALID_WINDOW_ID); + + visualInfo = vi_list[0]; + if (OS::get_singleton()->is_layered_allowed()) { + for (int i = 0; i < numberOfVisuals; i++) { + XRenderPictFormat *pict_format = XRenderFindVisualFormat(x11_display, vi_list[i].visual); + if (!pict_format) { + continue; + } + visualInfo = vi_list[i]; + if (pict_format->direct.alphaMask > 0) { + break; + } + } + } + XFree(vi_list); + } + + Colormap colormap = XCreateColormap(x11_display, RootWindow(x11_display, visualInfo.screen), visualInfo.visual, AllocNone); XSetWindowAttributes windowAttributes = {}; windowAttributes.colormap = colormap; @@ -4414,6 +4470,13 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V unsigned long valuemask = CWBorderPixel | CWColormap | CWEventMask; + if (OS::get_singleton()->is_layered_allowed()) { + windowAttributes.background_pixmap = None; + windowAttributes.background_pixel = 0; + windowAttributes.border_pixmap = None; + valuemask |= CWBackPixel; + } + WindowID id = window_id_counter++; WindowData &wd = windows[id]; @@ -4437,7 +4500,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } { - wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes); + wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes); // Enable receiving notification when the window is initialized (MapNotify) // so the focus can be set at the right time. @@ -4574,8 +4637,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V XSync(x11_display, False); //XSetErrorHandler(oldHandler); - - XFree(visualInfo); } window_set_mode(p_mode, id); @@ -4839,6 +4900,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } show_window(main_window); + XSync(x11_display, False); + _validate_mode_on_map(main_window); #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { @@ -4985,14 +5048,13 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } cursor_set_shape(CURSOR_BUSY); - XEvent xevent; - while (XPending(x11_display) > 0) { - XNextEvent(x11_display, &xevent); - if (xevent.type == ConfigureNotify) { - _window_changed(&xevent); - } + // Search the X11 event queue for ConfigureNotify events and process all + // that are currently queued early, so we can get the final window size + // for correctly drawing of the bootsplash. + XEvent config_event; + while (XCheckTypedEvent(x11_display, ConfigureNotify, &config_event)) { + _window_changed(&config_event); } - events_thread.start(_poll_events_thread, this); _update_real_mouse_position(windows[MAIN_WINDOW_ID]); @@ -5000,6 +5062,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode #ifdef DBUS_ENABLED screensaver = memnew(FreeDesktopScreenSaver); screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); + + portal_desktop = memnew(FreeDesktopPortalDesktop); #endif r_error = OK; @@ -5085,6 +5149,7 @@ DisplayServerX11::~DisplayServerX11() { #ifdef DBUS_ENABLED memdelete(screensaver); + memdelete(portal_desktop); #endif } diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index 0174cfb881..ea03b2328c 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -60,6 +60,7 @@ #endif #if defined(DBUS_ENABLED) +#include "freedesktop_portal_desktop.h" #include "freedesktop_screensaver.h" #endif @@ -120,6 +121,10 @@ class DisplayServerX11 : public DisplayServer { TTS_Linux *tts = nullptr; #endif +#if defined(DBUS_ENABLED) + FreeDesktopPortalDesktop *portal_desktop = nullptr; +#endif + struct WindowData { Window x11_window; ::XIC xic; @@ -152,7 +157,9 @@ class DisplayServerX11 : public DisplayServer { Vector2i last_position_before_fs; bool focused = true; bool minimized = false; + bool maximized = false; bool is_popup = false; + bool layered_window = false; Rect2i parent_safe_rect; @@ -245,8 +252,6 @@ class DisplayServerX11 : public DisplayServer { CursorShape current_cursor = CURSOR_ARROW; HashMap<CursorShape, Vector<Variant>> cursors_cache; - bool layered_window = false; - String rendering_driver; void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -268,9 +273,12 @@ class DisplayServerX11 : public DisplayServer { void _update_real_mouse_position(const WindowData &wd); bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; bool _window_fullscreen_check(WindowID p_window) const; + bool _window_minimize_check(WindowID p_window) const; + void _validate_mode_on_map(WindowID p_window); void _update_size_hints(WindowID p_window); void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); + void _set_wm_minimized(WindowID p_window, bool p_enabled); void _update_context(WindowData &wd); @@ -316,6 +324,11 @@ public: virtual void tts_stop() override; #endif +#if defined(DBUS_ENABLED) + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; +#endif + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp new file mode 100644 index 0000000000..ed54084694 --- /dev/null +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -0,0 +1,135 @@ +/*************************************************************************/ +/* freedesktop_portal_desktop.cpp */ +/*************************************************************************/ +/* 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 "freedesktop_portal_desktop.h" + +#ifdef DBUS_ENABLED + +#include "core/error/error_macros.h" +#include "core/os/os.h" +#include "core/string/ustring.h" + +#include "dbus-so_wrap.h" + +#include "core/variant/variant.h" + +#define BUS_OBJECT_NAME "org.freedesktop.portal.Desktop" +#define BUS_OBJECT_PATH "/org/freedesktop/portal/desktop" + +#define BUS_INTERFACE_SETTINGS "org.freedesktop.portal.Settings" + +static bool try_parse_variant(DBusMessage *p_reply_message, int p_type, void *r_value) { + DBusMessageIter iter[3]; + + dbus_message_iter_init(p_reply_message, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) { + return false; + } + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != p_type) { + return false; + } + + dbus_message_iter_get_basic(&iter[2], r_value); + return true; +} + +bool FreeDesktopPortalDesktop::read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value) { + if (unsupported) { + return false; + } + + DBusError error; + dbus_error_init(&error); + + DBusConnection *bus = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + unsupported = true; + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(String() + "Error opening D-Bus connection: " + error.message); + } + return false; + } + + DBusMessage *message = dbus_message_new_method_call( + BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_SETTINGS, + "Read"); + dbus_message_append_args( + message, + DBUS_TYPE_STRING, &p_namespace, + DBUS_TYPE_STRING, &p_key, + DBUS_TYPE_INVALID); + + DBusMessage *reply = dbus_connection_send_with_reply_and_block(bus, message, 50, &error); + dbus_message_unref(message); + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + dbus_connection_unref(bus); + if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(String() + "Error on D-Bus communication: " + error.message); + } + return false; + } + + bool success = try_parse_variant(reply, p_type, r_value); + + dbus_message_unref(reply); + dbus_connection_unref(bus); + + return success; +} + +uint32_t FreeDesktopPortalDesktop::get_appearance_color_scheme() { + if (unsupported) { + return 0; + } + + uint32_t value = 0; + read_setting("org.freedesktop.appearance", "color-scheme", DBUS_TYPE_UINT32, &value); + return value; +} + +FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { +#ifdef DEBUG_ENABLED + int dylibloader_verbose = 1; +#else + int dylibloader_verbose = 0; +#endif + unsupported = (initialize_dbus(dylibloader_verbose) != 0); +} + +#endif // DBUS_ENABLED diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h new file mode 100644 index 0000000000..3d976b1ede --- /dev/null +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -0,0 +1,59 @@ +/*************************************************************************/ +/* freedesktop_portal_desktop.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 FREEDESKTOP_PORTAL_DESKTOP_H +#define FREEDESKTOP_PORTAL_DESKTOP_H + +#ifdef DBUS_ENABLED + +#include <stdint.h> + +class FreeDesktopPortalDesktop { +private: + bool unsupported = false; + + // Read a setting from org.freekdesktop.portal.Settings + bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value); + +public: + FreeDesktopPortalDesktop(); + + bool is_supported() { return !unsupported; } + + // Retrieve the system's preferred color scheme. + // 0: No preference or unknown. + // 1: Prefer dark appearance. + // 2: Prefer light appearance. + uint32_t get_appearance_color_scheme(); +}; + +#endif // DBUS_ENABLED + +#endif // FREEDESKTOP_PORTAL_DESKTOP_H diff --git a/platform/linuxbsd/gl_manager_x11.cpp b/platform/linuxbsd/gl_manager_x11.cpp index d3fb1d6705..04c1df71fb 100644 --- a/platform/linuxbsd/gl_manager_x11.cpp +++ b/platform/linuxbsd/gl_manager_x11.cpp @@ -127,10 +127,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { GLXFBConfig fbconfig = nullptr; XVisualInfo *vi = nullptr; - gl_display.x_swa.event_mask = StructureNotifyMask; - gl_display.x_swa.border_pixel = 0; - gl_display.x_valuemask = CWBorderPixel | CWColormap | CWEventMask; - if (OS::get_singleton()->is_layered_allowed()) { GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs_layered, &fbcount); ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); @@ -156,12 +152,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { XFree(fbc); ERR_FAIL_COND_V(!fbconfig, ERR_UNCONFIGURED); - - gl_display.x_swa.background_pixmap = None; - gl_display.x_swa.background_pixel = 0; - gl_display.x_swa.border_pixmap = None; - gl_display.x_valuemask |= CWBackPixel; - } else { GLXFBConfig *fbc = glXChooseFBConfig(x11_display, DefaultScreen(x11_display), visual_attribs, &fbcount); ERR_FAIL_COND_V(!fbc, ERR_UNCONFIGURED); @@ -189,8 +179,6 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { } break; } - gl_display.x_swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone); - XSync(x11_display, False); XSetErrorHandler(oldHandler); @@ -205,6 +193,10 @@ Error GLManager_X11::_create_context(GLDisplay &gl_display) { return OK; } +XVisualInfo GLManager_X11::get_vi(Display *p_display) { + return _displays[_find_or_create_display(p_display)].x_vi; +} + Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { // make sure vector is big enough... // we can mirror the external vector, it is simpler @@ -223,8 +215,6 @@ Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window // the display could be invalid .. check NYI GLDisplay &gl_display = _displays[win.gldisplay_id]; - //const XVisualInfo &vi = gl_display.x_vi; - //XSetWindowAttributes &swa = gl_display.x_swa; ::Display *x11_display = gl_display.x11_display; ::Window &x11_window = win.x11_window; @@ -315,6 +305,16 @@ void GLManager_X11::swap_buffers() { return; } + // On X11, when enabled, transparancy is always active, so clear alpha manually. + if (OS::get_singleton()->is_layered_allowed()) { + if (!DisplayServer::get_singleton()->window_get_flag(DisplayServer::WINDOW_FLAG_TRANSPARENT, _current_window->window_id)) { + glColorMask(false, false, false, true); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glColorMask(true, true, true, true); + } + } + // print_line("\tswap_buffers"); // only for debugging without drawing anything diff --git a/platform/linuxbsd/gl_manager_x11.h b/platform/linuxbsd/gl_manager_x11.h index fb2c74a2b6..4f78c45c88 100644 --- a/platform/linuxbsd/gl_manager_x11.h +++ b/platform/linuxbsd/gl_manager_x11.h @@ -68,8 +68,6 @@ private: GLManager_X11_Private *context = nullptr; ::Display *x11_display; XVisualInfo x_vi; - XSetWindowAttributes x_swa; - unsigned long x_valuemask; }; // just for convenience, window and display struct @@ -102,6 +100,7 @@ private: Error _create_context(GLDisplay &gl_display); public: + XVisualInfo get_vi(Display *p_display); Error window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height); void window_destroy(DisplayServer::WindowID p_window_id); void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index a74ca155a4..61faf3061c 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -65,7 +65,7 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { for (int i = 0; i < path_elems.size(); i++) { for (uint64_t k = 0; k < sizeof(message_programs) / sizeof(char *); k++) { - String tested_path = path_elems[i].plus_file(message_programs[k]); + String tested_path = path_elems[i].path_join(message_programs[k]); if (FileAccess::exists(tested_path)) { program = tested_path; @@ -432,10 +432,10 @@ String OS_LinuxBSD::get_config_path() const { return get_environment("XDG_CONFIG_HOME"); } else { WARN_PRINT_ONCE("`XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.config` or `.` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".config") : "."; + return has_environment("HOME") ? get_environment("HOME").path_join(".config") : "."; } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".config"); + return get_environment("HOME").path_join(".config"); } else { return "."; } @@ -447,10 +447,10 @@ String OS_LinuxBSD::get_data_path() const { return get_environment("XDG_DATA_HOME"); } else { WARN_PRINT_ONCE("`XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.local/share` or `get_config_path()` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".local/share") : get_config_path(); + return has_environment("HOME") ? get_environment("HOME").path_join(".local/share") : get_config_path(); } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".local/share"); + return get_environment("HOME").path_join(".local/share"); } else { return get_config_path(); } @@ -462,10 +462,10 @@ String OS_LinuxBSD::get_cache_path() const { return get_environment("XDG_CACHE_HOME"); } else { WARN_PRINT_ONCE("`XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.cache` or `get_config_path()` per the XDG Base Directory specification."); - return has_environment("HOME") ? get_environment("HOME").plus_file(".cache") : get_config_path(); + return has_environment("HOME") ? get_environment("HOME").path_join(".cache") : get_config_path(); } } else if (has_environment("HOME")) { - return get_environment("HOME").plus_file(".cache"); + return get_environment("HOME").path_join(".cache"); } else { return get_config_path(); } diff --git a/platform/macos/SCsub b/platform/macos/SCsub index d0856c709a..bbd461fba9 100644 --- a/platform/macos/SCsub +++ b/platform/macos/SCsub @@ -17,6 +17,8 @@ files = [ "godot_window.mm", "key_mapping_macos.mm", "godot_main_macos.mm", + "godot_menu_delegate.mm", + "godot_menu_item.mm", "dir_access_macos.mm", "tts_macos.mm", "joypad_macos.cpp", diff --git a/platform/macos/detect.py b/platform/macos/detect.py index e5bcb46b02..cfd3789b41 100644 --- a/platform/macos/detect.py +++ b/platform/macos/detect.py @@ -54,11 +54,13 @@ def get_mvk_sdk_path(): return [int_or_zero(i) for i in a.split(".")] dirname = os.path.expanduser("~/VulkanSDK") - files = os.listdir(dirname) + if not os.path.exists(dirname): + return "" ver_file = "0.0.0.0" ver_num = ver_parse(ver_file) + files = os.listdir(dirname) for file in files: if os.path.isdir(os.path.join(dirname, file)): ver_comp = ver_parse(file) @@ -145,7 +147,7 @@ def configure(env): env.Append(LINKFLAGS=["-isysroot", "$MACOS_SDK_PATH"]) else: # osxcross build - root = os.environ.get("OSXCROSS_ROOT", 0) + root = os.environ.get("OSXCROSS_ROOT", "") if env["arch"] == "arm64": basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" else: @@ -248,7 +250,7 @@ def configure(env): env.Append(LINKFLAGS=["-L" + mvk_path]) if not mvk_found: mvk_path = get_mvk_sdk_path() - if os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): + if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")): mvk_found = True env.Append(LINKFLAGS=["-L" + mvk_path]) if not mvk_found: diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm index 94d937a7dc..3373cada1f 100644 --- a/platform/macos/dir_access_macos.mm +++ b/platform/macos/dir_access_macos.mm @@ -69,7 +69,7 @@ String DirAccessMacOS::get_drive(int p_drive) { } bool DirAccessMacOS::is_hidden(const String &p_name) { - String f = get_current_dir().plus_file(p_name); + String f = get_current_dir().path_join(p_name); NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; NSNumber *hidden = nil; if (![url getResourceValue:&hidden forKey:NSURLIsHiddenKey error:nil]) { diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index a08667a259..769cba2de5 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -140,6 +140,7 @@ private: int key_event_pos = 0; id tts = nullptr; + id menu_delegate = nullptr; Point2i im_selection; String im_text; @@ -227,14 +228,14 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; + virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override; @@ -244,6 +245,7 @@ public: virtual bool global_menu_is_item_checkable(const String &p_menu_root, int p_idx) const override; virtual bool global_menu_is_item_radio_checkable(const String &p_menu_root, int p_idx) const override; virtual Callable global_menu_get_item_callback(const String &p_menu_root, int p_idx) const override; + virtual Callable global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const override; virtual Variant global_menu_get_item_tag(const String &p_menu_root, int p_idx) const override; virtual String global_menu_get_item_text(const String &p_menu_root, int p_idx) const override; virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override; @@ -259,6 +261,7 @@ public: virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override; + virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override; virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override; virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override; virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override; @@ -284,6 +287,10 @@ public: virtual void tts_resume() override; virtual void tts_stop() override; + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; + virtual Color get_accent_color() const override; + virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) override; virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback) override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index b06ae9f27c..b009007d73 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -31,6 +31,7 @@ #include "display_server_macos.h" #include "godot_content_view.h" +#include "godot_menu_delegate.h" #include "godot_menu_item.h" #include "godot_window.h" #include "godot_window_delegate.h" @@ -95,6 +96,7 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { if (!submenu.has(p_menu_root)) { NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]]; [n_menu setAutoenablesItems:NO]; + [n_menu setDelegate:menu_delegate]; submenu[p_menu_root] = n_menu; } menu = submenu[p_menu_root]; @@ -754,7 +756,7 @@ NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const return nullptr; } -int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -762,6 +764,7 @@ int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const St if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_NONE; obj->max_states = 0; @@ -772,7 +775,7 @@ int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const St return out; } -int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -780,6 +783,7 @@ int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, co if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; obj->max_states = 0; @@ -790,7 +794,7 @@ int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, co return out; } -int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -798,6 +802,7 @@ int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, con if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_NONE; obj->max_states = 0; @@ -817,7 +822,7 @@ int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, con return out; } -int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -825,6 +830,7 @@ int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_roo if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_CHECK_BOX; obj->max_states = 0; @@ -844,7 +850,7 @@ int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_roo return out; } -int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -852,6 +858,7 @@ int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_ro if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; obj->max_states = 0; @@ -862,7 +869,7 @@ int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_ro return out; } -int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ int out = -1; @@ -870,6 +877,7 @@ int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_me if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_RADIO_BUTTON; obj->max_states = 0; @@ -889,30 +897,15 @@ int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_me return out; } -int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Callable &p_key_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); int out = -1; - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; - if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { - p_index = [menu numberOfItems]; - } else if (p_index < 0) { - p_index = item_count; - } else { - if (menu == [NSApp mainMenu]) { // Skip Apple menu. - p_index++; - } - p_index = CLAMP(p_index, 0, item_count); - } - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; - + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; + obj->key_callback = p_key_callback; obj->meta = p_tag; obj->checkable_type = CHECKABLE_TYPE_NONE; obj->max_states = p_max_states; @@ -1104,6 +1097,27 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_ return Callable(); } +Callable DisplayServerMacOS::global_menu_get_item_key_callback(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND_V(p_idx < 0, Callable()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable()); + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + if (obj) { + return obj->key_callback; + } + } + } + return Callable(); +} + Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root, int p_idx) const { _THREAD_SAFE_METHOD_ @@ -1401,6 +1415,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root } } +void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); + obj->key_callback = p_key_callback; + } + } +} + void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) { _THREAD_SAFE_METHOD_ @@ -1682,6 +1715,41 @@ void DisplayServerMacOS::tts_stop() { [tts stopSpeaking]; } +bool DisplayServerMacOS::is_dark_mode_supported() const { + if (@available(macOS 10.14, *)) { + return true; + } else { + return false; + } +} + +bool DisplayServerMacOS::is_dark_mode() const { + if (@available(macOS 10.14, *)) { + if (![[NSUserDefaults standardUserDefaults] objectForKey:@"AppleInterfaceStyle"]) { + return false; + } else { + return ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"] isEqual:@"Dark"]); + } + } else { + return false; + } +} + +Color DisplayServerMacOS::get_accent_color() const { + if (@available(macOS 10.14, *)) { + NSColor *color = [[NSColor controlAccentColor] colorUsingColorSpace:[NSColorSpace genericRGBColorSpace]]; + if (color) { + CGFloat components[4]; + [color getRed:&components[0] green:&components[1] blue:&components[2] alpha:&components[3]]; + return Color(components[0], components[1], components[2], components[3]); + } else { + return Color(0, 0, 0, 0); + } + } else { + return Color(0, 0, 0, 0); + } +} + Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) { _THREAD_SAFE_METHOD_ @@ -3477,6 +3545,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM [main_menu setSubmenu:apple_menu forItem:menu_item]; [main_menu setAutoenablesItems:NO]; + menu_delegate = [[GodotMenuDelegate alloc] init]; + //!!!!!!!!!!!!!!!!!!!!!!!!!! //TODO - do Vulkan and OpenGL support checks, driver selection and fallback rendering_driver = p_rendering_driver; diff --git a/platform/macos/export/codesign.cpp b/platform/macos/export/codesign.cpp index fd044c00cc..c2bdf555d0 100644 --- a/platform/macos/export/codesign.cpp +++ b/platform/macos/export/codesign.cpp @@ -172,7 +172,7 @@ bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path f.name = p_path; f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); f.nested = false; - f.hash = hash_sha1_base64(p_root.plus_file(p_path)); + f.hash = hash_sha1_base64(p_root.path_join(p_path)); print_verbose(vformat("CodeSign/CodeResources: File(V1) %s hash1:%s", f.name, f.hash)); files1.push_back(f); @@ -182,7 +182,7 @@ bool CodeSignCodeResources::add_file1(const String &p_root, const String &p_path bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path) { CRMatch found = match_rules2(p_path); if (found == CRMatch::CR_MATCH_NESTED) { - return add_nested_file(p_root, p_path, p_root.plus_file(p_path)); + return add_nested_file(p_root, p_path, p_root.path_join(p_path)); } if (found != CRMatch::CR_MATCH_YES && found != CRMatch::CR_MATCH_OPTIONAL) { return true; // No match. @@ -192,8 +192,8 @@ bool CodeSignCodeResources::add_file2(const String &p_root, const String &p_path f.name = p_path; f.optional = (found == CRMatch::CR_MATCH_OPTIONAL); f.nested = false; - f.hash = hash_sha1_base64(p_root.plus_file(p_path)); - f.hash2 = hash_sha256_base64(p_root.plus_file(p_path)); + f.hash = hash_sha1_base64(p_root.path_join(p_path)); + f.hash2 = hash_sha256_base64(p_root.path_join(p_path)); print_verbose(vformat("CodeSign/CodeResources: File(V2) %s hash1:%s hash2:%s", f.name, f.hash, f.hash2)); @@ -214,17 +214,17 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String & Vector<String> files_to_add; if (LipO::is_lipo(p_exepath)) { - String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); + String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo"); Error err = da->make_dir_recursive(tmp_path_name); ERR_FAIL_COND_V_MSG(err != OK, false, vformat("CodeSign/CodeResources: Failed to create \"%s\" subfolder.", tmp_path_name)); LipO lip; if (lip.open_file(p_exepath)) { for (int i = 0; i < lip.get_arch_count(); i++) { - if (!lip.extract_arch(i, tmp_path_name.plus_file("_rqexe_" + itos(i)))) { + if (!lip.extract_arch(i, tmp_path_name.path_join("_rqexe_" + itos(i)))) { CLEANUP(); ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Failed to extract thin binary."); } - files_to_add.push_back(tmp_path_name.plus_file("_rqexe_" + itos(i))); + files_to_add.push_back(tmp_path_name.path_join("_rqexe_" + itos(i))); } } } else if (MachO::is_macho(p_exepath)) { @@ -285,7 +285,7 @@ bool CodeSignCodeResources::add_nested_file(const String &p_root, const String & bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const String &p_path, const String &p_main_exe_path) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); ERR_FAIL_COND_V(da.is_null(), false); - Error err = da->change_dir(p_root.plus_file(p_path)); + Error err = da->change_dir(p_root.path_join(p_path)); ERR_FAIL_COND_V(err != OK, false); bool ret = true; @@ -293,27 +293,27 @@ bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const Str String n = da->get_next(); while (n != String()) { if (n != "." && n != "..") { - String path = p_root.plus_file(p_path).plus_file(n); + String path = p_root.path_join(p_path).path_join(n); if (path == p_main_exe_path) { n = da->get_next(); continue; // Skip main executable. } if (da->current_is_dir()) { - CRMatch found = match_rules2(p_path.plus_file(n)); + CRMatch found = match_rules2(p_path.path_join(n)); String fmw_ver = "Current"; // Framework version (default). String info_path; String main_exe; bool bundle = false; - if (da->file_exists(path.plus_file("Contents/Info.plist"))) { - info_path = path.plus_file("Contents/Info.plist"); - main_exe = path.plus_file("Contents/MacOS"); + if (da->file_exists(path.path_join("Contents/Info.plist"))) { + info_path = path.path_join("Contents/Info.plist"); + main_exe = path.path_join("Contents/MacOS"); bundle = true; - } else if (da->file_exists(path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { - info_path = path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); - main_exe = path.plus_file(vformat("Versions/%s", fmw_ver)); + } else if (da->file_exists(path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { + info_path = path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); + main_exe = path.path_join(vformat("Versions/%s", fmw_ver)); bundle = true; - } else if (da->file_exists(path.plus_file("Info.plist"))) { - info_path = path.plus_file("Info.plist"); + } else if (da->file_exists(path.path_join("Info.plist"))) { + info_path = path.path_join("Info.plist"); main_exe = path; bundle = true; } @@ -322,20 +322,20 @@ bool CodeSignCodeResources::add_folder_recursive(const String &p_root, const Str PList info_plist; if (info_plist.load_file(info_path)) { if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { - main_exe = main_exe.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); + main_exe = main_exe.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); } else { ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, no exe name."); } } else { ERR_FAIL_V_MSG(false, "CodeSign/CodeResources: Invalid Info.plist, can't load."); } - ret = ret && add_nested_file(p_root, p_path.plus_file(n), main_exe); + ret = ret && add_nested_file(p_root, p_path.path_join(n), main_exe); } else { - ret = ret && add_folder_recursive(p_root, p_path.plus_file(n), p_main_exe_path); + ret = ret && add_folder_recursive(p_root, p_path.path_join(n), p_main_exe_path); } } else { - ret = ret && add_file1(p_root, p_path.plus_file(n)); - ret = ret && add_file2(p_root, p_path.plus_file(n)); + ret = ret && add_file1(p_root, p_path.path_join(n)); + ret = ret && add_file2(p_root, p_path.path_join(n)); } } @@ -1222,7 +1222,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const } if (info_plist.get_root()->data_type == PList::PLNodeType::PL_NODE_TYPE_DICT && info_plist.get_root()->data_dict.has("CFBundleExecutable")) { - main_exe = p_exe_path.plus_file(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); + main_exe = p_exe_path.path_join(String::utf8(info_plist.get_root()->data_dict["CFBundleExecutable"]->data_string.get_data())); } else { r_error_msg = TTR("Invalid Info.plist, no exe name."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Invalid Info.plist, no exe name."); @@ -1244,7 +1244,7 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const Vector<String> files_to_sign; if (LipO::is_lipo(main_exe)) { print_verbose(vformat("CodeSign: Executable is fat, extracting...")); - String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file("_lipo"); + String tmp_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join("_lipo"); Error err = da->make_dir_recursive(tmp_path_name); if (err != OK) { r_error_msg = vformat(TTR("Failed to create \"%s\" subfolder."), tmp_path_name); @@ -1253,12 +1253,12 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const LipO lip; if (lip.open_file(main_exe)) { for (int i = 0; i < lip.get_arch_count(); i++) { - if (!lip.extract_arch(i, tmp_path_name.plus_file("_exe_" + itos(i)))) { + if (!lip.extract_arch(i, tmp_path_name.path_join("_exe_" + itos(i)))) { CLEANUP(); r_error_msg = TTR("Failed to extract thin binary."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to extract thin binary."); } - files_to_sign.push_back(tmp_path_name.plus_file("_exe_" + itos(i))); + files_to_sign.push_back(tmp_path_name.path_join("_exe_" + itos(i))); } } } else if (MachO::is_macho(main_exe)) { @@ -1338,15 +1338,15 @@ Error CodeSign::_codesign_file(bool p_use_hardened_runtime, bool p_force, const r_error_msg = TTR("Failed to process nested resources."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to process nested resources."); } - Error err = da->make_dir_recursive(p_bundle_path.plus_file("_CodeSignature")); + Error err = da->make_dir_recursive(p_bundle_path.path_join("_CodeSignature")); if (err != OK) { CLEANUP(); r_error_msg = TTR("Failed to create _CodeSignature subfolder."); ERR_FAIL_V_MSG(FAILED, "CodeSign: Failed to create _CodeSignature subfolder."); } - cr.save_to_file(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); - res_hash1 = file_hash_sha1(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); - res_hash2 = file_hash_sha256(p_bundle_path.plus_file("_CodeSignature").plus_file("CodeResources")); + cr.save_to_file(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); + res_hash1 = file_hash_sha1(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); + res_hash2 = file_hash_sha256(p_bundle_path.path_join("_CodeSignature").path_join("CodeResources")); if (res_hash1.is_empty() || res_hash2.is_empty()) { CLEANUP(); r_error_msg = TTR("Failed to get CodeResources hash."); @@ -1530,18 +1530,18 @@ Error CodeSign::codesign(bool p_use_hardened_runtime, bool p_force, const String String bundle_path; bool bundle = false; bool ios_bundle = false; - if (da->file_exists(p_path.plus_file("Contents/Info.plist"))) { - info_path = p_path.plus_file("Contents/Info.plist"); - main_exe = p_path.plus_file("Contents/MacOS"); - bundle_path = p_path.plus_file("Contents"); + if (da->file_exists(p_path.path_join("Contents/Info.plist"))) { + info_path = p_path.path_join("Contents/Info.plist"); + main_exe = p_path.path_join("Contents/MacOS"); + bundle_path = p_path.path_join("Contents"); bundle = true; - } else if (da->file_exists(p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { - info_path = p_path.plus_file(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); - main_exe = p_path.plus_file(vformat("Versions/%s", fmw_ver)); - bundle_path = p_path.plus_file(vformat("Versions/%s", fmw_ver)); + } else if (da->file_exists(p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)))) { + info_path = p_path.path_join(vformat("Versions/%s/Resources/Info.plist", fmw_ver)); + main_exe = p_path.path_join(vformat("Versions/%s", fmw_ver)); + bundle_path = p_path.path_join(vformat("Versions/%s", fmw_ver)); bundle = true; - } else if (da->file_exists(p_path.plus_file("Info.plist"))) { - info_path = p_path.plus_file("Info.plist"); + } else if (da->file_exists(p_path.path_join("Info.plist"))) { + info_path = p_path.path_join("Info.plist"); main_exe = p_path; bundle_path = p_path; bundle = true; diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 4b453add5a..50104aced5 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -305,7 +305,7 @@ void EditorExportPlatformMacOS::_make_icon(const Ref<Image> &p_icon, Vector<uint if (icon_infos[i].is_png) { // Encode PNG icon. it->set_image(copy); - String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png"); + String path = EditorPaths::get_singleton()->get_cache_dir().path_join("icon.png"); ResourceSaver::save(it, path); { @@ -766,7 +766,7 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres dir_access->list_dir_begin(); String current_file{ dir_access->get_next() }; while (!current_file.is_empty()) { - String current_file_path{ p_path.plus_file(current_file) }; + String current_file_path{ p_path.path_join(current_file) }; if (current_file == ".." || current_file == ".") { current_file = dir_access->get_next(); @@ -980,9 +980,9 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p tmp_app_path_name = p_path; scr_path = p_path.get_basename() + ".command"; } else { - tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name); - tmp_app_path_name = tmp_base_path_name.plus_file(tmp_app_dir_name); - scr_path = tmp_base_path_name.plus_file(pkg_name + ".command"); + tmp_base_path_name = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name); + tmp_app_path_name = tmp_base_path_name.path_join(tmp_app_dir_name); + scr_path = tmp_base_path_name.path_join(pkg_name + ".command"); } print_verbose("Exporting to " + tmp_app_path_name); @@ -1189,7 +1189,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!")); #endif // Handle symlinks in the archive. - file = tmp_app_path_name.plus_file(file); + file = tmp_app_path_name.path_join(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); if (err != OK) { @@ -1273,7 +1273,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p print_verbose("ADDING: " + file + " size: " + itos(data.size())); // Write it into our application bundle. - file = tmp_app_path_name.plus_file(file); + file = tmp_app_path_name.path_join(file); if (err == OK) { err = tmp_app_dir->make_dir_recursive(file.get_base_dir()); if (err != OK) { @@ -1332,9 +1332,9 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p bool sign_enabled = (p_preset->get("codesign/codesign").operator int() > 0); String ent_path = p_preset->get("codesign/entitlements/custom_file"); - String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + "_helper.entitlements"); + String hlp_ent_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name + "_helper.entitlements"); if (sign_enabled && (ent_path.is_empty())) { - ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements"); + ent_path = EditorPaths::get_singleton()->get_cache_dir().path_join(pkg_name + ".entitlements"); Ref<FileAccess> ent_f = FileAccess::open(ent_path, FileAccess::WRITE); if (ent_f.is_valid()) { @@ -1529,7 +1529,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p String path_in_app = tmp_app_path_name + "/Contents/Frameworks/" + src_path.get_file(); err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, true); } else { - String path_in_app = tmp_app_path_name.plus_file(shared_objects[i].target).plus_file(src_path.get_file()); + String path_in_app = tmp_app_path_name.path_join(shared_objects[i].target).path_join(src_path.get_file()); err = _copy_and_sign_files(da, src_path, path_in_app, sign_enabled, p_preset, ent_path, false); } if (err != OK) { @@ -1630,7 +1630,7 @@ Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p } void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { - String dir = p_folder.is_empty() ? p_root_path : p_root_path.plus_file(p_folder); + String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder); Ref<DirAccess> da = DirAccess::open(dir); da->list_dir_begin(); @@ -1660,7 +1660,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri zipfi.internal_fa = 0; zipOpenNewFileInZip4(p_zip, - p_folder.plus_file(f).utf8().get_data(), + p_folder.path_join(f).utf8().get_data(), &zipfi, nullptr, 0, @@ -1682,7 +1682,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); zipCloseFileInZip(p_zip); } else if (da->current_is_dir()) { - _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f), p_pkg_name); + _zip_folder_recursive(p_zip, p_root_path, p_folder.path_join(f), p_pkg_name); } else { bool is_executable = (p_folder.ends_with("MacOS") && (f == p_pkg_name)) || p_folder.ends_with("Helpers") || f.ends_with(".command"); @@ -1705,7 +1705,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri zipfi.internal_fa = 0; zipOpenNewFileInZip4(p_zip, - p_folder.plus_file(f).utf8().get_data(), + p_folder.path_join(f).utf8().get_data(), &zipfi, nullptr, 0, @@ -1723,9 +1723,9 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions 0); - Ref<FileAccess> fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); + Ref<FileAccess> fa = FileAccess::open(dir.path_join(f), FileAccess::READ); if (fa.is_null()) { - add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); + add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.path_join(f))); return; } const int bufsize = 16384; diff --git a/platform/macos/godot_menu_delegate.h b/platform/macos/godot_menu_delegate.h new file mode 100644 index 0000000000..805ac0c4a3 --- /dev/null +++ b/platform/macos/godot_menu_delegate.h @@ -0,0 +1,44 @@ +/*************************************************************************/ +/* godot_menu_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_MENU_DELEGATE_H +#define GODOT_MENU_DELEGATE_H + +#import <AppKit/AppKit.h> +#import <Foundation/Foundation.h> + +@interface GodotMenuDelegate : NSObject <NSMenuDelegate> { +} + +- (void)doNothing:(id)sender; + +@end + +#endif // GODOT_MENU_DELEGATE_H diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm new file mode 100644 index 0000000000..376f28d1d0 --- /dev/null +++ b/platform/macos/godot_menu_delegate.mm @@ -0,0 +1,76 @@ +/*************************************************************************/ +/* godot_menu_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_menu_delegate.h" + +#include "display_server_macos.h" +#include "godot_menu_item.h" +#include "key_mapping_macos.h" + +@implementation GodotMenuDelegate + +- (void)doNothing:(id)sender { +} + +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { + NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString]; + NSUInteger ev_modifiers = [event modifierFlags] & NSDeviceIndependentModifierFlagsMask; + for (int i = 0; i < [menu numberOfItems]; i++) { + const NSMenuItem *menu_item = [menu itemAtIndex:i]; + if ([menu_item isEnabled] && [[menu_item keyEquivalent] compare:ev_key] == NSOrderedSame) { + NSUInteger item_modifiers = [menu_item keyEquivalentModifierMask]; + + if (ev_modifiers == item_modifiers) { + GodotMenuItem *value = [menu_item representedObject]; + if (value->key_callback != Callable()) { + // If custom callback is set, use it. + Variant tag = value->meta; + Variant *tagp = &tag; + Variant ret; + Callable::CallError ce; + value->key_callback.callp((const Variant **)&tagp, 1, ret, ce); + } else { + // Otherwise redirect event to the engine. + if (DisplayServer::get_singleton()) { + [[[NSApplication sharedApplication] keyWindow] sendEvent:event]; + } + } + + // Suppress default menu action. + *target = self; + *action = @selector(doNothing:); + return YES; + } + } + } + return NO; +} + +@end diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h index e0b9f41632..e96f5dc1cf 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -45,6 +45,7 @@ enum GlobalMenuCheckType { @interface GodotMenuItem : NSObject { @public Callable callback; + Callable key_callback; Variant meta; GlobalMenuCheckType checkable_type; int max_states; @@ -54,7 +55,4 @@ enum GlobalMenuCheckType { @end -@implementation GodotMenuItem -@end - #endif // GODOT_MENU_ITEM_H diff --git a/platform/macos/godot_menu_item.mm b/platform/macos/godot_menu_item.mm new file mode 100644 index 0000000000..ea35e35d19 --- /dev/null +++ b/platform/macos/godot_menu_item.mm @@ -0,0 +1,34 @@ +/*************************************************************************/ +/* godot_menu_item.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_menu_item.h" + +@implementation GodotMenuItem +@end diff --git a/platform/macos/os_macos.mm b/platform/macos/os_macos.mm index 47b53bba69..35c4e4b03d 100644 --- a/platform/macos/os_macos.mm +++ b/platform/macos/os_macos.mm @@ -48,8 +48,8 @@ _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) { // Append framework executable name, or return as is if p_path is not a framework. Ref<DirAccess> 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()); + if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) { + return p_path.path_join(p_path.get_file().get_basename()); } else { return p_path; } @@ -155,12 +155,12 @@ Error OS_MacOS::open_dynamic_library(const String p_path, void *&p_library_handl if (!FileAccess::exists(path)) { // 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())); + path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file())); } if (!FileAccess::exists(path)) { // 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())); + path = get_framework_executable(get_executable_path().get_base_dir().path_join("../Frameworks").path_join(p_path.get_file())); } p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW); @@ -187,7 +187,7 @@ String OS_MacOS::get_config_path() const { } } if (has_environment("HOME")) { - return get_environment("HOME").plus_file("Library/Application Support"); + return get_environment("HOME").path_join("Library/Application Support"); } return "."; } @@ -214,7 +214,7 @@ String OS_MacOS::get_cache_path() const { } } if (has_environment("HOME")) { - return get_environment("HOME").plus_file("Library/Caches"); + return get_environment("HOME").path_join("Library/Caches"); } return get_config_path(); } diff --git a/platform/uwp/export/app_packager.cpp b/platform/uwp/export/app_packager.cpp index 09717b9d69..87224d38b8 100644 --- a/platform/uwp/export/app_packager.cpp +++ b/platform/uwp/export/app_packager.cpp @@ -408,7 +408,7 @@ void AppxPackager::finish() { // Create and add block map file EditorNode::progress_task_step("export", "Creating block map...", 4); - const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml"); + const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpblockmap.xml"); make_block_map(tmp_blockmap_file_path); { @@ -425,7 +425,7 @@ void AppxPackager::finish() { EditorNode::progress_task_step("export", "Setting content types...", 5); - const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml"); + const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpcontenttypes.xml"); make_content_types(tmp_content_types_file_path); { diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h index 71d0479543..b0427d1a65 100644 --- a/platform/uwp/export/export_plugin.h +++ b/platform/uwp/export/export_plugin.h @@ -329,7 +329,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform { return data; } - String tmp_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png"); + String tmp_path = EditorPaths::get_singleton()->get_cache_dir().path_join("uwp_tmp_logo.png"); Error err = texture->get_image()->save_png(tmp_path); diff --git a/platform/web/SCsub b/platform/web/SCsub index 78d7ce4074..ae9d628857 100644 --- a/platform/web/SCsub +++ b/platform/web/SCsub @@ -35,18 +35,17 @@ for ext in env["JS_EXTERNS"]: sys_env["ENV"]["EMCC_CLOSURE_ARGS"] += " --externs " + ext.abspath build = [] -if env["gdnative_enabled"]: - build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] - if env["threads_enabled"]: - build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") +build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm", "#bin/godot${PROGSUFFIX}.worker.js"] +if env["dlink_enabled"]: # Reset libraries. The main runtime will only link emscripten libraries, not godot ones. sys_env["LIBS"] = [] # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. sys_env.Append(LIBS=["idbfs.js"]) # Configure it as a main module (dynamic linking support). + sys_env["CCFLAGS"].remove("SIDE_MODULE=2") + sys_env["LINKFLAGS"].remove("SIDE_MODULE=2") sys_env.Append(CCFLAGS=["-s", "MAIN_MODULE=1"]) sys_env.Append(LINKFLAGS=["-s", "MAIN_MODULE=1"]) - sys_env.Append(CCFLAGS=["-s", "EXPORT_ALL=1"]) sys_env.Append(LINKFLAGS=["-s", "EXPORT_ALL=1"]) sys_env.Append(LINKFLAGS=["-s", "WARN_ON_UNDEFINED_SYMBOLS=0"]) # Force exporting the standard library (printf, malloc, etc.) @@ -55,16 +54,9 @@ if env["gdnative_enabled"]: sys = sys_env.Program(build_targets, ["web_runtime.cpp"]) # The side library, containing all Godot code. - wasm_env = env.Clone() - wasm_env.Append(CPPDEFINES=["WASM_GDNATIVE"]) # So that OS knows it can run GDNative libraries. - wasm_env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"]) - wasm_env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"]) - wasm = wasm_env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", web_files) + wasm = env.add_program("#bin/godot.side${PROGSUFFIX}.wasm", web_files) build = sys + [wasm[0]] else: - build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] - if env["threads_enabled"]: - build_targets.append("#bin/godot${PROGSUFFIX}.worker.js") # We use IDBFS. Since Emscripten 1.39.1 it needs to be linked explicitly. sys_env.Append(LIBS=["idbfs.js"]) build = sys_env.Program(build_targets, web_files + ["web_runtime.cpp"]) @@ -88,6 +80,8 @@ wrap_list = [ ] js_wrapped = env.Textfile("#bin/godot", [env.File(f) for f in wrap_list], TEXTFILESUFFIX="${PROGSUFFIX}.wrapped.js") -# Extra will be the thread worker, or the GDNative side, or None -extra = build[2:] if len(build) > 2 else None -env.CreateTemplateZip(js_wrapped, build[1], extra) +# 0 - unwrapped js file (use wrapped one instead) +# 1 - wasm file +# 2 - worker file +# 3 - wasm side (when dlink is enabled). +env.CreateTemplateZip(js_wrapped, build[1], build[2], build[3] if len(build) > 3 else None) diff --git a/platform/web/api/web_tools_editor_plugin.cpp b/platform/web/api/web_tools_editor_plugin.cpp index 6f684a73c2..46fcb2d452 100644 --- a/platform/web/api/web_tools_editor_plugin.cpp +++ b/platform/web/api/web_tools_editor_plugin.cpp @@ -76,7 +76,7 @@ void WebToolsEditorPlugin::_download_zip(Variant p_v) { const String datetime_safe = Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); - const String output_path = String("/tmp").plus_file(output_name); + const String output_path = String("/tmp").path_join(output_name); zipFile zip = zipOpen2(output_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io); const String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; @@ -131,7 +131,7 @@ void WebToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zip String cur = dir->get_next(); String project_data_dir_name = ProjectSettings::get_singleton()->get_project_data_dir_name(); while (!cur.is_empty()) { - String cs = p_path.plus_file(cur); + String cs = p_path.path_join(cur); if (cur == "." || cur == ".." || cur == project_data_dir_name) { // Skip } else if (dir->current_is_dir()) { diff --git a/platform/web/detect.py b/platform/web/detect.py index ae0ff2c4ea..b1c1dd48a9 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -38,8 +38,9 @@ def get_opts(): BoolVariable("use_safe_heap", "Use Emscripten SAFE_HEAP sanitizer", False), # eval() can be a security concern, so it can be disabled. BoolVariable("javascript_eval", "Enable JavaScript eval interface", True), - BoolVariable("threads_enabled", "Enable WebAssembly Threads support (limited browser support)", True), - BoolVariable("gdnative_enabled", "Enable WebAssembly GDNative support (produces bigger binaries)", False), + BoolVariable( + "dlink_enabled", "Enable WebAssembly dynamic linking (GDExtension support). Produces bigger binaries", False + ), BoolVariable("use_closure_compiler", "Use closure compiler to minimize JavaScript code", False), ] @@ -50,6 +51,13 @@ def get_flags(): ("tools", False), ("builtin_pcre2_with_jit", False), ("vulkan", False), + # Use -Os to prioritize optimizing for reduced file size. This is + # particularly valuable for the web platform because it directly + # decreases download time. + # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about + # 100 KiB over -Os, which does not justify the negative impact on + # run-time performance. + ("optimize", "size"), ] @@ -71,15 +79,12 @@ def configure(env): ## Build type if env["target"].startswith("release"): - # Use -Os to prioritize optimizing for reduced file size. This is - # particularly valuable for the web platform because it directly - # decreases download time. - # -Os reduces file size by around 5 MiB over -O3. -Oz only saves about - # 100 KiB over -Os, which does not justify the negative impact on - # run-time performance. - if env["optimize"] != "none": + if env["optimize"] == "size": env.Append(CCFLAGS=["-Os"]) env.Append(LINKFLAGS=["-Os"]) + elif env["optimize"] == "speed": + env.Append(CCFLAGS=["-O3"]) + env.Append(LINKFLAGS=["-O3"]) if env["target"] == "release_debug": # Retain function names for backtraces at the cost of file size. @@ -93,21 +98,11 @@ def configure(env): env.Append(LINKFLAGS=["-s", "ASSERTIONS=1"]) if env["tools"]: - if not env["threads_enabled"]: - print('Note: Forcing "threads_enabled=yes" as it is required for the web editor.') - env["threads_enabled"] = "yes" if env["initial_memory"] < 64: print('Note: Forcing "initial_memory=64" as it is required for the web editor.') env["initial_memory"] = 64 - env.Append(CCFLAGS=["-frtti"]) - elif env["builtin_icu"]: - env.Append(CCFLAGS=["-fno-exceptions", "-frtti"]) else: - # Disable exceptions and rtti on non-tools (template) builds - # These flags help keep the file size down. - env.Append(CCFLAGS=["-fno-exceptions", "-fno-rtti"]) - # Don't use dynamic_cast, necessary with no-rtti. - env.Append(CPPDEFINES=["NO_SAFE_CAST"]) + env.Append(CPPFLAGS=["-fno-exceptions"]) env.Append(LINKFLAGS=["-s", "INITIAL_MEMORY=%sMB" % env["initial_memory"]]) @@ -171,9 +166,9 @@ def configure(env): env["ARCOM_POSIX"] = env["ARCOM"].replace("$TARGET", "$TARGET.posix").replace("$SOURCES", "$SOURCES.posix") env["ARCOM"] = "${TEMPFILE(ARCOM_POSIX)}" - # All intermediate files are just LLVM bitcode. + # All intermediate files are just object files. env["OBJPREFIX"] = "" - env["OBJSUFFIX"] = ".bc" + env["OBJSUFFIX"] = ".o" env["PROGPREFIX"] = "" # Program() output consists of multiple files, so specify suffixes manually at builder. env["PROGSUFFIX"] = "" @@ -196,31 +191,22 @@ def configure(env): env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"]) # Thread support (via SharedArrayBuffer). - if env["threads_enabled"]: - env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) - env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) - env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) - env.extra_suffix = ".threads" + env.extra_suffix - else: - env.Append(CPPDEFINES=["NO_THREADS"]) + env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) + env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) + env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) + env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) - if env["gdnative_enabled"]: + if env["dlink_enabled"]: cc_version = get_compiler_version(env) cc_semver = (int(cc_version["major"]), int(cc_version["minor"]), int(cc_version["patch"])) - if cc_semver < (2, 0, 10): - print("GDNative support requires emscripten >= 2.0.10, detected: %s.%s.%s" % cc_semver) + if cc_semver < (3, 1, 14): + print("GDExtension support requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver) sys.exit(255) - if env["threads_enabled"] and cc_semver < (3, 1, 14): - print("Threads and GDNative requires emscripten >= 3.1.14, detected: %s.%s.%s" % cc_semver) - sys.exit(255) - env.Append(CCFLAGS=["-s", "RELOCATABLE=1"]) - env.Append(LINKFLAGS=["-s", "RELOCATABLE=1"]) - # Weak symbols are broken upstream: https://github.com/emscripten-core/emscripten/issues/12819 - env.Append(CPPDEFINES=["ZSTD_HAVE_WEAK_SYMBOLS=0"]) - env.extra_suffix = ".gdnative" + env.extra_suffix + env.Append(CCFLAGS=["-s", "SIDE_MODULE=2"]) + env.Append(LINKFLAGS=["-s", "SIDE_MODULE=2"]) + env.extra_suffix = ".dlink" + env.extra_suffix # Reduce code size by generating less support code (e.g. skip NodeJS support). env.Append(LINKFLAGS=["-s", "ENVIRONMENT=web,worker"]) diff --git a/platform/web/emscripten_helpers.py b/platform/web/emscripten_helpers.py index b7b1026ef7..6045bc6fbd 100644 --- a/platform/web/emscripten_helpers.py +++ b/platform/web/emscripten_helpers.py @@ -37,26 +37,25 @@ def create_engine_file(env, target, source, externs): return env.Textfile(target, [env.File(s) for s in source]) -def create_template_zip(env, js, wasm, extra): +def create_template_zip(env, js, wasm, worker, side): binary_name = "godot.tools" if env["tools"] else "godot" zip_dir = env.Dir("#bin/.web_zip") in_files = [ js, wasm, + worker, "#platform/web/js/libs/audio.worklet.js", ] out_files = [ zip_dir.File(binary_name + ".js"), zip_dir.File(binary_name + ".wasm"), + zip_dir.File(binary_name + ".worker.js"), zip_dir.File(binary_name + ".audio.worklet.js"), ] - # GDNative/Threads specific - if env["gdnative_enabled"]: - in_files.append(extra.pop()) # Runtime + # Dynamic linking (extensions) specific. + if env["dlink_enabled"]: + in_files.append(side) # Side wasm (contains the actual Godot code). out_files.append(zip_dir.File(binary_name + ".side.wasm")) - if env["threads_enabled"]: - in_files.append(extra.pop()) # Worker - out_files.append(zip_dir.File(binary_name + ".worker.js")) service_worker = "#misc/dist/html/service-worker.js" if env["tools"]: diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h index 1e50e60a28..38b9a66d7e 100644 --- a/platform/web/export/editor_http_server.h +++ b/platform/web/export/editor_http_server.h @@ -62,8 +62,8 @@ private: void _set_internal_certs(Ref<Crypto> p_crypto) { const String cache_path = EditorPaths::get_singleton()->get_cache_dir(); - const String key_path = cache_path.plus_file("html5_server.key"); - const String crt_path = cache_path.plus_file("html5_server.crt"); + const String key_path = cache_path.path_join("html5_server.key"); + const String crt_path = cache_path.path_join("html5_server.crt"); bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path); if (!regen) { key = Ref<CryptoKey>(CryptoKey::create()); @@ -139,8 +139,8 @@ public: const String req_file = path.get_file(); const String req_ext = path.get_extension(); - const String cache_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("web"); - const String filepath = cache_path.plus_file(req_file); + const String cache_path = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); + const String filepath = cache_path.path_join(req_file); if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) { String s = "HTTP/1.1 404 Not Found\r\n"; diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index 996d1db2e7..9971481459 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -75,7 +75,7 @@ Error EditorExportPlatformWeb::_extract_template(const String &p_template, const unzCloseCurrentFile(pkg); //write - String dst = p_dir.plus_file(file.replace("godot", p_name)); + String dst = p_dir.path_join(file.replace("godot", p_name)); Ref<FileAccess> f = FileAccess::open(dst, FileAccess::WRITE); if (f.is_null()) { add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst)); @@ -162,7 +162,7 @@ void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<Edito Error EditorExportPlatformWeb::_add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr) { const String name = p_path.get_file().get_basename(); const String icon_name = vformat("%s.%dx%d.png", name, p_size, p_size); - const String icon_dest = p_path.get_base_dir().plus_file(icon_name); + const String icon_dest = p_path.get_base_dir().path_join(icon_name); Ref<Image> icon; if (!p_icon.is_empty()) { @@ -201,7 +201,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese // 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"); + bool extensions = (bool)p_preset->get("variant/extensions_support"); HashMap<String, String> replaces; 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); @@ -216,17 +216,15 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese cache_files.push_back(name + ".icon.png"); cache_files.push_back(name + ".apple-touch-icon.png"); } - if (mode & EXPORT_MODE_THREADS) { - cache_files.push_back(name + ".worker.js"); - cache_files.push_back(name + ".audio.worklet.js"); - } + 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) { + if (extensions) { opt_cache_files.push_back(name + ".side.wasm"); for (int i = 0; i < p_shared_objects.size(); i++) { opt_cache_files.push_back(p_shared_objects[i].path.get_file()); @@ -234,7 +232,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese } replaces["@GODOT_OPT_CACHE@"] = Variant(opt_cache_files).to_json_string(); - const String sw_path = dir.plus_file(name + ".service.worker.js"); + const String sw_path = dir.path_join(name + ".service.worker.js"); Vector<uint8_t> sw; { Ref<FileAccess> f = FileAccess::open(sw_path, FileAccess::READ); @@ -246,7 +244,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese f->get_buffer(sw.ptrw(), sw.size()); } _replace_strings(replaces, sw); - Error err = _write_or_error(sw.ptr(), sw.size(), dir.plus_file(name + ".service.worker.js")); + Error err = _write_or_error(sw.ptr(), sw.size(), dir.path_join(name + ".service.worker.js")); if (err != OK) { return err; } @@ -255,7 +253,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese const String offline_page = p_preset->get("progressive_web_app/offline_page"); if (!offline_page.is_empty()) { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - const String offline_dest = dir.plus_file(name + ".offline.html"); + const String offline_dest = dir.path_join(name + ".offline.html"); err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest); if (err != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), offline_dest)); @@ -295,7 +293,7 @@ Error EditorExportPlatformWeb::_build_pwa(const Ref<EditorExportPreset> &p_prese manifest["icons"] = icons_arr; CharString cs = Variant(manifest).to_json_string().utf8(); - err = _write_or_error((const uint8_t *)cs.get_data(), cs.length(), dir.plus_file(name + ".manifest.json")); + err = _write_or_error((const uint8_t *)cs.get_data(), cs.length(), dir.path_join(name + ".manifest.json")); if (err != OK) { return err; } @@ -317,20 +315,14 @@ void EditorExportPlatformWeb::get_preset_features(const Ref<EditorExportPreset> r_features->push_back("etc2"); } } - ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); - if (mode & EXPORT_MODE_THREADS) { - r_features->push_back("threads"); - } - if (mode & EXPORT_MODE_GDNATIVE) { - r_features->push_back("wasm32"); - } + r_features->push_back("wasm32"); } void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options) { r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "variant/export_type", PROPERTY_HINT_ENUM, "Regular,Threads,GDNative"), 0)); // Export type. + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type. r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer @@ -374,11 +366,11 @@ bool EditorExportPlatformWeb::has_valid_export_configuration(const Ref<EditorExp String err; bool valid = false; - ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); + bool extensions = (bool)p_preset->get("variant/extensions_support"); // Look for export templates (first official, and if defined custom templates). - bool dvalid = exists_export_template(_get_template_name(mode, true), &err); - bool rvalid = exists_export_template(_get_template_name(mode, false), &err); + bool dvalid = exists_export_template(_get_template_name(extensions, true), &err); + bool rvalid = exists_export_template(_get_template_name(extensions, false), &err); if (p_preset->get("custom_template/debug") != "") { dvalid = FileAccess::exists(p_preset->get("custom_template/debug")); @@ -456,8 +448,8 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p String template_path = p_debug ? custom_debug : custom_release; template_path = template_path.strip_edges(); if (template_path.is_empty()) { - ExportMode mode = (ExportMode)(int)p_preset->get("variant/export_type"); - template_path = find_export_template(_get_template_name(mode, p_debug)); + bool extensions = (bool)p_preset->get("variant/extensions_support"); + template_path = find_export_template(_get_template_name(extensions, p_debug)); } if (!DirAccess::exists(base_dir)) { @@ -481,7 +473,7 @@ Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_p { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); for (int i = 0; i < shared_objects.size(); i++) { - String dst = base_dir.plus_file(shared_objects[i].path.get_file()); + String dst = base_dir.path_join(shared_objects[i].path.get_file()); error = da->copy(shared_objects[i].path, dst); if (error != OK) { add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), shared_objects[i].path.get_file())); @@ -601,7 +593,7 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int return OK; } - const String dest = EditorPaths::get_singleton()->get_cache_dir().plus_file("web"); + const String dest = EditorPaths::get_singleton()->get_cache_dir().path_join("web"); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); if (!da->dir_exists(dest)) { Error err = da->make_dir_recursive(dest); @@ -611,7 +603,7 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int } } - const String basepath = dest.plus_file("tmp_js_export"); + const String basepath = dest.path_join("tmp_js_export"); Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags); if (err != OK) { // Export generates several files, clean them up on failure. diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 9c1bf2a5ef..5b7ce5f708 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -57,20 +57,10 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Mutex server_lock; Thread server_thread; - enum ExportMode { - EXPORT_MODE_NORMAL = 0, - EXPORT_MODE_THREADS = 1, - EXPORT_MODE_GDNATIVE = 2, - EXPORT_MODE_THREADS_GDNATIVE = 3, - }; - - String _get_template_name(ExportMode p_mode, bool p_debug) const { - String name = "webassembly"; - if (p_mode & EXPORT_MODE_GDNATIVE) { - name += "_gdnative"; - } - if (p_mode & EXPORT_MODE_THREADS) { - name += "_threads"; + String _get_template_name(bool p_extension, bool p_debug) const { + String name = "web"; + if (p_extension) { + name += "_dlink"; } if (p_debug) { name += "_debug.zip"; diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index 461dc71119..f9714f25e7 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -140,26 +140,9 @@ int OS_Web::get_processor_count() const { } bool OS_Web::_check_internal_feature_support(const String &p_feature) { - if (p_feature == "html5" || p_feature == "web") { - return true; - } - -#ifdef JAVASCRIPT_EVAL_ENABLED if (p_feature == "web") { return true; } -#endif -#ifndef NO_THREADS - if (p_feature == "threads") { - return true; - } -#endif -#if WASM_GDNATIVE - if (p_feature == "wasm32") { - return true; - } -#endif - return false; } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 7eb61b3038..b4949de3f7 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -37,6 +37,11 @@ #include "scene/resources/texture.h" #include <avrt.h> +#include <dwmapi.h> + +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" @@ -1307,7 +1312,28 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W _update_window_style(p_window); } break; case WINDOW_FLAG_TRANSPARENT: { - // FIXME: Implement. + if (p_enabled) { + //enable per-pixel alpha + + DWM_BLURBEHIND bb = { 0 }; + HRGN hRgn = CreateRectRgn(0, 0, -1, -1); + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = hRgn; + bb.fEnable = TRUE; + DwmEnableBlurBehindWindow(wd.hWnd, &bb); + + wd.layered_window = true; + } else { + //disable per-pixel alpha + wd.layered_window = false; + + DWM_BLURBEHIND bb = { 0 }; + HRGN hRgn = CreateRectRgn(0, 0, -1, -1); + bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION; + bb.hRgnBlur = hRgn; + bb.fEnable = FALSE; + DwmEnableBlurBehindWindow(wd.hWnd, &bb); + } } break; case WINDOW_FLAG_NO_FOCUS: { wd.no_focus = p_enabled; @@ -1339,7 +1365,7 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window return wd.always_on_top; } break; case WINDOW_FLAG_TRANSPARENT: { - // FIXME: Implement. + return wd.layered_window; } break; case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; @@ -2391,6 +2417,20 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_PAINT: { Main::force_redraw(); } break; + case WM_SETTINGCHANGE: { + if (lParam && CompareStringOrdinal(reinterpret_cast<LPCWCH>(lParam), -1, L"ImmersiveColorSet", -1, true) == CSTR_EQUAL) { + if (is_dark_mode_supported()) { + BOOL value = is_dark_mode(); + ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + } + } + } break; + case WM_THEMECHANGED: { + if (is_dark_mode_supported()) { + BOOL value = is_dark_mode(); + ::DwmSetWindowAttribute(windows[window_id].hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + } + } break; case WM_SYSCOMMAND: // Intercept system commands. { switch (wParam) // Check system calls. @@ -3501,6 +3541,11 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.pre_fs_valid = true; } + if (is_dark_mode_supported()) { + BOOL value = is_dark_mode(); + ::DwmSetWindowAttribute(wd.hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + } + #ifdef VULKAN_ENABLED if (context_vulkan) { if (context_vulkan->window_create(id, p_vsync_mode, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { @@ -3587,6 +3632,14 @@ WTInfoPtr DisplayServerWindows::wintab_WTInfo = nullptr; WTPacketPtr DisplayServerWindows::wintab_WTPacket = nullptr; WTEnablePtr DisplayServerWindows::wintab_WTEnable = nullptr; +// UXTheme API. +bool DisplayServerWindows::ux_theme_available = false; +IsDarkModeAllowedForAppPtr DisplayServerWindows::IsDarkModeAllowedForApp = nullptr; +ShouldAppsUseDarkModePtr DisplayServerWindows::ShouldAppsUseDarkMode = nullptr; +GetImmersiveColorFromColorSetExPtr DisplayServerWindows::GetImmersiveColorFromColorSetEx = nullptr; +GetImmersiveColorTypeFromNamePtr DisplayServerWindows::GetImmersiveColorTypeFromName = nullptr; +GetImmersiveUserColorSetPreferencePtr DisplayServerWindows::GetImmersiveUserColorSetPreference = nullptr; + // Windows Ink API. bool DisplayServerWindows::winink_available = false; GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr; @@ -3598,6 +3651,23 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS { SHC_PROCESS_PER_MONITOR_DPI_AWARE = 2 } SHC_PROCESS_DPI_AWARENESS; +bool DisplayServerWindows::is_dark_mode_supported() const { + return ux_theme_available && IsDarkModeAllowedForApp(); +} + +bool DisplayServerWindows::is_dark_mode() const { + return ux_theme_available && ShouldAppsUseDarkMode(); +} + +Color DisplayServerWindows::get_accent_color() const { + if (!ux_theme_available) { + return Color(0, 0, 0, 0); + } + + int argb = GetImmersiveColorFromColorSetEx((UINT)GetImmersiveUserColorSetPreference(false, false), GetImmersiveColorTypeFromName(L"ImmersiveSystemAccent"), false, 0); + return Color((argb & 0xFF) / 255.f, ((argb & 0xFF00) >> 8) / 255.f, ((argb & 0xFF0000) >> 16) / 255.f, ((argb & 0xFF000000) >> 24) / 255.f); +} + int DisplayServerWindows::tablet_get_driver_count() const { return tablet_drivers.size(); } @@ -3655,6 +3725,18 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win // Enforce default keep screen on value. screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); + // Load UXTheme + HMODULE ux_theme_lib = LoadLibraryW(L"uxtheme.dll"); + if (ux_theme_lib) { + IsDarkModeAllowedForApp = (IsDarkModeAllowedForAppPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(136)); + ShouldAppsUseDarkMode = (ShouldAppsUseDarkModePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(132)); + GetImmersiveColorFromColorSetEx = (GetImmersiveColorFromColorSetExPtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(95)); + GetImmersiveColorTypeFromName = (GetImmersiveColorTypeFromNamePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(96)); + GetImmersiveUserColorSetPreference = (GetImmersiveUserColorSetPreferencePtr)GetProcAddress(ux_theme_lib, MAKEINTRESOURCEA(98)); + + ux_theme_available = IsDarkModeAllowedForApp && ShouldAppsUseDarkMode && GetImmersiveColorFromColorSetEx && GetImmersiveColorTypeFromName && GetImmersiveUserColorSetPreference; + } + // Note: Wacom WinTab driver API for pen input, for devices incompatible with Windows Ink. HMODULE wintab_lib = LoadLibraryW(L"wintab32.dll"); if (wintab_lib) { diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 556ce9ff5d..dbc9821970 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -152,6 +152,12 @@ typedef UINT(WINAPI *WTInfoPtr)(UINT p_category, UINT p_index, LPVOID p_output); typedef BOOL(WINAPI *WTPacketPtr)(HANDLE p_ctx, UINT p_param, LPVOID p_packets); typedef BOOL(WINAPI *WTEnablePtr)(HANDLE p_ctx, BOOL p_enable); +typedef bool(WINAPI *IsDarkModeAllowedForAppPtr)(); +typedef bool(WINAPI *ShouldAppsUseDarkModePtr)(); +typedef DWORD(WINAPI *GetImmersiveColorFromColorSetExPtr)(UINT dwImmersiveColorSet, UINT dwImmersiveColorType, bool bIgnoreHighContrast, UINT dwHighContrastCacheMode); +typedef int(WINAPI *GetImmersiveColorTypeFromNamePtr)(const WCHAR *name); +typedef int(WINAPI *GetImmersiveUserColorSetPreferencePtr)(bool bForceCheckRegistry, bool bSkipCheckOnFail); + // Windows Ink API #ifndef POINTER_STRUCTURES @@ -278,6 +284,14 @@ class DisplayServerWindows : public DisplayServer { _THREAD_SAFE_CLASS_ + // UXTheme API + static bool ux_theme_available; + static IsDarkModeAllowedForAppPtr IsDarkModeAllowedForApp; + static ShouldAppsUseDarkModePtr ShouldAppsUseDarkMode; + static GetImmersiveColorFromColorSetExPtr GetImmersiveColorFromColorSetEx; + static GetImmersiveColorTypeFromNamePtr GetImmersiveColorTypeFromName; + static GetImmersiveUserColorSetPreferencePtr GetImmersiveUserColorSetPreference; + // WinTab API static bool wintab_available; static WTOpenPtr wintab_WTOpen; @@ -338,7 +352,6 @@ class DisplayServerWindows : public DisplayServer { struct WindowData { HWND hWnd; - //layered window Vector<Vector2> mpath; @@ -378,10 +391,6 @@ class DisplayServerWindows : public DisplayServer { Vector2 last_tilt; bool last_pen_inverted = false; - HBITMAP hBitmap; //DIB section for layered window - uint8_t *dib_data = nullptr; - Size2 dib_size; - HDC hDC_dib; Size2 min_size; Size2 max_size; int width = 0, height = 0; @@ -485,6 +494,10 @@ public: virtual void tts_resume() override; virtual void tts_stop() override; + virtual bool is_dark_mode_supported() const override; + virtual bool is_dark_mode() const override; + virtual Color get_accent_color() const override; + virtual void mouse_set_mode(MouseMode p_mode) override; virtual MouseMode mouse_get_mode() const override; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 0e4d5f79b9..403d53ae53 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -237,7 +237,7 @@ Error OS_Windows::open_dynamic_library(const String p_path, void *&p_library_han if (!FileAccess::exists(path)) { //this code exists so gdnative can load .dll files from within the executable path - path = get_executable_path().get_base_dir().plus_file(p_path.get_file()); + path = get_executable_path().get_base_dir().path_join(p_path.get_file()); } typedef DLL_DIRECTORY_COOKIE(WINAPI * PAddDllDirectory)(PCWSTR); @@ -1071,13 +1071,13 @@ String OS_Windows::get_user_data_dir() const { if (custom_dir.is_empty()) { custom_dir = appname; } - return get_data_path().plus_file(custom_dir).replace("\\", "/"); + return get_data_path().path_join(custom_dir).replace("\\", "/"); } else { - return get_data_path().plus_file(get_godot_dir_name()).plus_file("app_userdata").plus_file(appname).replace("\\", "/"); + return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/"); } } - return get_data_path().plus_file(get_godot_dir_name()).plus_file("app_userdata").plus_file("[unnamed project]"); + return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]"); } String OS_Windows::get_unique_id() const { |