diff options
Diffstat (limited to 'platform')
47 files changed, 1028 insertions, 240 deletions
diff --git a/platform/SCsub b/platform/SCsub index b07023efed..cdaa6074ba 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -15,12 +15,12 @@ def export_icon_builder(target, source, env): src_path = Path(str(source[0])) src_name = src_path.stem platform = src_path.parent.parent.stem - with open(str(source[0]), "rb") as file: - svg = "".join([f"\\{hex(x)[1:]}" for x in file.read()]) + with open(str(source[0]), "r") as file: + svg = file.read() with methods.generated_wrapper(target, prefix=platform) as file: file.write( f"""\ -static const char *_{platform}_{src_name}_svg = "{svg}"; +static const char *_{platform}_{src_name}_svg = {methods.to_raw_cstring(svg)}; """ ) diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 8dc0e869d0..5bb520bd73 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win } void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) { - // Not supported on Android. + OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN); } DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const { - return WINDOW_MODE_FULLSCREEN; + if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_MAXIMIZED; + } } bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const { diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 0fdaca4839..f8ac591a78 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const { return devices[p_index].architecture; } -Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String can_export_error; @@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, } const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug"); - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug; if (use_reverse) { - p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; + p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); } String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); @@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, OS::get_singleton()->execute(adb, args, &output, &rv, true); print_verbose(output); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { int dbg_port = EDITOR_GET("network/debug/remote_port"); args.clear(); args.push_back("-s"); @@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, print_line("Reverse result: " + itos(rv)); } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int fs_port = EDITOR_GET("filesystem/file_server/port"); args.clear(); @@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor return err; } -void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { +void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) { String cmdline = p_preset->get("command_line/extra_args"); Vector<String> command_line_strings = cmdline.strip_edges().split(" "); for (int i = 0; i < command_line_strings.size(); i++) { @@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP } } - gen_export_flags(command_line_strings, p_flags); + command_line_strings.append_array(gen_export_flags(p_flags)); bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { @@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP bool immersive = p_preset->get("screen/immersive_mode"); if (immersive) { - command_line_strings.push_back("--use_immersive"); + command_line_strings.push_back("--fullscreen"); } bool debug_opengl = p_preset->get("graphics/opengl_debug"); @@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor return have_plugins_changed || has_build_dir_changed || first_build; } -Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags); } -Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) { +Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; - bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); + bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<ABI> enabled_abis = get_enabled_abis(p_preset); @@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP user_data.assets_directory = assets_directory; user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); } else { err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); @@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } err = OK; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 97bbd0c7bc..708288fbf4 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -214,7 +214,7 @@ public: virtual String get_device_architecture(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; @@ -242,7 +242,7 @@ public: Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); - void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags); + void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags); Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep); @@ -253,9 +253,9 @@ public: static String join_list(const List<String> &p_parts, const String &p_separator); static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch); - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; - Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags); + Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags); virtual void get_platform_features(List<String> *r_features) const override; diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index f9a3e10680..b8b4233636 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -37,7 +37,7 @@ ext { // Return the keystore file used for signing the release build. getGodotKeystoreFile = { -> def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE") - if (keyStore == null) { + if (keyStore == null || keyStore.isEmpty()) { return null } return file(keyStore) diff --git a/platform/android/java/editor/src/main/AndroidManifest.xml b/platform/android/java/editor/src/main/AndroidManifest.xml index c7d14a3f49..a875745860 100644 --- a/platform/android/java/editor/src/main/AndroidManifest.xml +++ b/platform/android/java/editor/src/main/AndroidManifest.xml @@ -42,6 +42,7 @@ android:name=".GodotEditor" android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:exported="true" + android:icon="@mipmap/icon" android:launchMode="singleTask" android:screenOrientation="userLandscape"> <layout @@ -59,9 +60,11 @@ android:name=".GodotGame" android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" android:exported="false" - android:label="@string/godot_project_name_string" + android:icon="@mipmap/ic_play_window" + android:label="@string/godot_game_activity_name" android:launchMode="singleTask" android:process=":GodotGame" + android:supportsPictureInPicture="true" android:screenOrientation="userLandscape"> <layout android:defaultWidth="@dimen/editor_default_window_width" diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt new file mode 100644 index 0000000000..b16e62149a --- /dev/null +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt @@ -0,0 +1,192 @@ +/**************************************************************************/ +/* EditorMessageDispatcher.kt */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +package org.godotengine.editor + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.os.Handler +import android.os.Message +import android.os.Messenger +import android.os.RemoteException +import android.util.Log +import java.util.concurrent.ConcurrentHashMap + +/** + * Used by the [GodotEditor] classes to dispatch messages across processes. + */ +internal class EditorMessageDispatcher(private val editor: GodotEditor) { + + companion object { + private val TAG = EditorMessageDispatcher::class.java.simpleName + + /** + * Extra used to pass the message dispatcher payload through an [Intent] + */ + const val EXTRA_MSG_DISPATCHER_PAYLOAD = "message_dispatcher_payload" + + /** + * Key used to pass the editor id through a [Bundle] + */ + private const val KEY_EDITOR_ID = "editor_id" + + /** + * Key used to pass the editor messenger through a [Bundle] + */ + private const val KEY_EDITOR_MESSENGER = "editor_messenger" + + /** + * Requests the recipient to quit right away. + */ + private const val MSG_FORCE_QUIT = 0 + + /** + * Requests the recipient to store the passed [android.os.Messenger] instance. + */ + private const val MSG_REGISTER_MESSENGER = 1 + } + + private val recipientsMessengers = ConcurrentHashMap<Int, Messenger>() + + @SuppressLint("HandlerLeak") + private val dispatcherHandler = object : Handler() { + override fun handleMessage(msg: Message) { + when (msg.what) { + MSG_FORCE_QUIT -> editor.finish() + + MSG_REGISTER_MESSENGER -> { + val editorId = msg.arg1 + val messenger = msg.replyTo + registerMessenger(editorId, messenger) + } + + else -> super.handleMessage(msg) + } + } + } + + /** + * Request the window with the given [editorId] to force quit. + */ + fun requestForceQuit(editorId: Int): Boolean { + val messenger = recipientsMessengers[editorId] ?: return false + return try { + Log.v(TAG, "Requesting 'forceQuit' for $editorId") + val msg = Message.obtain(null, MSG_FORCE_QUIT) + messenger.send(msg) + true + } catch (e: RemoteException) { + Log.e(TAG, "Error requesting 'forceQuit' to $editorId", e) + recipientsMessengers.remove(editorId) + false + } + } + + /** + * Utility method to register a receiver messenger. + */ + private fun registerMessenger(editorId: Int, messenger: Messenger?, messengerDeathCallback: Runnable? = null) { + try { + if (messenger == null) { + Log.w(TAG, "Invalid 'replyTo' payload") + } else if (messenger.binder.isBinderAlive) { + messenger.binder.linkToDeath({ + Log.v(TAG, "Removing messenger for $editorId") + recipientsMessengers.remove(editorId) + messengerDeathCallback?.run() + }, 0) + recipientsMessengers[editorId] = messenger + } + } catch (e: RemoteException) { + Log.e(TAG, "Unable to register messenger from $editorId", e) + recipientsMessengers.remove(editorId) + } + } + + /** + * Utility method to register a [Messenger] attached to this handler with a host. + * + * This is done so that the host can send request to the editor instance attached to this handle. + * + * Note that this is only done when the editor instance is internal (not exported) to prevent + * arbitrary apps from having the ability to send requests. + */ + private fun registerSelfTo(pm: PackageManager, host: Messenger?, selfId: Int) { + try { + if (host == null || !host.binder.isBinderAlive) { + Log.v(TAG, "Host is unavailable") + return + } + + val activityInfo = pm.getActivityInfo(editor.componentName, 0) + if (activityInfo.exported) { + Log.v(TAG, "Not registering self to host as we're exported") + return + } + + Log.v(TAG, "Registering self $selfId to host") + val msg = Message.obtain(null, MSG_REGISTER_MESSENGER) + msg.arg1 = selfId + msg.replyTo = Messenger(dispatcherHandler) + host.send(msg) + } catch (e: RemoteException) { + Log.e(TAG, "Unable to register self with host", e) + } + } + + /** + * Parses the starting intent and retrieve an editor messenger if available + */ + fun parseStartIntent(pm: PackageManager, intent: Intent) { + val messengerBundle = intent.getBundleExtra(EXTRA_MSG_DISPATCHER_PAYLOAD) ?: return + + // Retrieve the sender messenger payload and store it. This can be used to communicate back + // to the sender. + val senderId = messengerBundle.getInt(KEY_EDITOR_ID) + val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER) + registerMessenger(senderId, senderMessenger) + + // Register ourselves to the sender so that it can communicate with us. + registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId) + } + + /** + * Returns the payload used by the [EditorMessageDispatcher] class to establish an IPC bridge + * across editor instances. + */ + fun getMessageDispatcherPayload(): Bundle { + return Bundle().apply { + putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId) + putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler)) + } + } +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt index 0da1d01aed..d3daa1dbbc 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt @@ -31,23 +31,24 @@ package org.godotengine.editor /** - * Specifies the policy for adjacent launches. + * Specifies the policy for launches. */ -enum class LaunchAdjacentPolicy { +enum class LaunchPolicy { /** - * Adjacent launches are disabled. + * Launch policy is determined by the editor settings or based on the device and screen metrics. */ - DISABLED, + AUTO, + /** - * Adjacent launches are enabled / disabled based on the device and screen metrics. + * Launches happen in the same window. */ - AUTO, + SAME, /** * Adjacent launches are enabled. */ - ENABLED + ADJACENT } /** @@ -57,12 +58,14 @@ data class EditorWindowInfo( val windowClassName: String, val windowId: Int, val processNameSuffix: String, - val launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED + val launchPolicy: LaunchPolicy = LaunchPolicy.SAME, + val supportsPiPMode: Boolean = false ) { constructor( windowClass: Class<*>, windowId: Int, processNameSuffix: String, - launchAdjacentPolicy: LaunchAdjacentPolicy = LaunchAdjacentPolicy.DISABLED - ) : this(windowClass.name, windowId, processNameSuffix, launchAdjacentPolicy) + launchPolicy: LaunchPolicy = LaunchPolicy.SAME, + supportsPiPMode: Boolean = false + ) : this(windowClass.name, windowId, processNameSuffix, launchPolicy, supportsPiPMode) } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 9cc133046b..1995a38c2a 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -32,6 +32,7 @@ package org.godotengine.editor import android.Manifest import android.app.ActivityManager +import android.app.ActivityOptions import android.content.ComponentName import android.content.Context import android.content.Intent @@ -39,6 +40,7 @@ import android.content.pm.PackageManager import android.os.* import android.util.Log import android.view.View +import android.view.WindowManager import android.widget.Toast import androidx.annotation.CallSuper import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -69,17 +71,26 @@ open class GodotEditor : GodotActivity() { private const val WAIT_FOR_DEBUGGER = false - private const val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" + @JvmStatic + protected val EXTRA_COMMAND_LINE_PARAMS = "command_line_params" + @JvmStatic + protected val EXTRA_PIP_AVAILABLE = "pip_available" + @JvmStatic + protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested" // Command line arguments + private const val FULLSCREEN_ARG = "--fullscreen" + private const val FULLSCREEN_ARG_SHORT = "-f" private const val EDITOR_ARG = "--editor" private const val EDITOR_ARG_SHORT = "-e" private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager" private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p" + private const val BREAKPOINTS_ARG = "--breakpoints" + private const val BREAKPOINTS_ARG_SHORT = "-b" // Info for the various classes used by the editor internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "") - internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchAdjacentPolicy.AUTO) + internal val RUN_GAME_INFO = EditorWindowInfo(GodotGame::class.java, 667, ":GodotGame", LaunchPolicy.AUTO, true) /** * Sets of constants to specify the window to use to run the project. @@ -90,16 +101,34 @@ open class GodotEditor : GodotActivity() { private const val ANDROID_WINDOW_AUTO = 0 private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1 private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2 + + /** + * Sets of constants to specify the Play window PiP mode. + * + * Should match the values in `editor/editor_settings.cpp'` for the + * 'run/window_placement/play_window_pip_mode' setting. + */ + private const val PLAY_WINDOW_PIP_DISABLED = 0 + private const val PLAY_WINDOW_PIP_ENABLED = 1 + private const val PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR = 2 } + private val editorMessageDispatcher = EditorMessageDispatcher(this) private val commandLineParams = ArrayList<String>() private val editorLoadingIndicator: View? by lazy { findViewById(R.id.editor_loading_indicator) } override fun getGodotAppLayout() = R.layout.godot_editor_layout + internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() + // Prevent the editor window from showing in the display cutout + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) { + window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + } + // We exclude certain permissions from the set we request at startup, as they'll be // requested on demand based on use-cases. PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) @@ -108,6 +137,8 @@ open class GodotEditor : GodotActivity() { Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") updateCommandLineParams(params?.asList() ?: emptyList()) + editorMessageDispatcher.parseStartIntent(packageManager, intent) + if (BuildConfig.BUILD_TYPE == "dev" && WAIT_FOR_DEBUGGER) { Debug.waitForDebugger() } @@ -189,35 +220,81 @@ open class GodotEditor : GodotActivity() { } } - override fun onNewGodotInstanceRequested(args: Array<String>): Int { - val editorWindowInfo = getEditorWindowInfo(args) + protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent { + val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO && + godot?.isInImmersiveMode() == true && + !args.contains(FULLSCREEN_ARG) && + !args.contains(FULLSCREEN_ARG_SHORT) + ) { + // If we're launching an editor window (project manager or editor) and we're in + // fullscreen mode, we want to remain in fullscreen mode. + // This doesn't apply to the play / game window since for that window fullscreen is + // controlled by the game logic. + args + FULLSCREEN_ARG + } else { + args + } - // Launch a new activity val newInstance = Intent() .setComponent(ComponentName(this, editorWindowInfo.windowClassName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(EXTRA_COMMAND_LINE_PARAMS, args) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - if (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.ENABLED || - (editorWindowInfo.launchAdjacentPolicy == LaunchAdjacentPolicy.AUTO && shouldGameLaunchAdjacent())) { + .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs) + + val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy) + val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) { + val pipMode = getPlayWindowPiPMode() + pipMode == PLAY_WINDOW_PIP_ENABLED || + (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR && launchPolicy == LaunchPolicy.SAME) + } else { + false + } + newInstance.putExtra(EXTRA_PIP_AVAILABLE, isPiPAvailable) + + if (launchPolicy == LaunchPolicy.ADJACENT) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Log.v(TAG, "Adding flag for adjacent launch") newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) } + } else if (launchPolicy == LaunchPolicy.SAME) { + if (isPiPAvailable && + (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) { + Log.v(TAG, "Launching in PiP mode because of breakpoints") + newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true) + } + } + + return newInstance + } + + override fun onNewGodotInstanceRequested(args: Array<String>): Int { + val editorWindowInfo = getEditorWindowInfo(args) + + // Launch a new activity + val sourceView = godotFragment?.view + val activityOptions = if (sourceView == null) { + null + } else { + val startX = sourceView.width / 2 + val startY = sourceView.height / 2 + ActivityOptions.makeScaleUpAnimation(sourceView, startX, startY, 0, 0) } + + val newInstance = getNewGodotInstanceIntent(editorWindowInfo, args) if (editorWindowInfo.windowClassName == javaClass.name) { Log.d(TAG, "Restarting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") val godot = godot if (godot != null) { godot.destroyAndKillProcess { - ProcessPhoenix.triggerRebirth(this, newInstance) + ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) } } else { - ProcessPhoenix.triggerRebirth(this, newInstance) + ProcessPhoenix.triggerRebirth(this, activityOptions?.toBundle(), newInstance) } } else { Log.d(TAG, "Starting ${editorWindowInfo.windowClassName} with parameters ${args.contentToString()}") newInstance.putExtra(EXTRA_NEW_LAUNCH, true) - startActivity(newInstance) + .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, editorMessageDispatcher.getMessageDispatcherPayload()) + startActivity(newInstance, activityOptions?.toBundle()) } return editorWindowInfo.windowId } @@ -231,6 +308,12 @@ open class GodotEditor : GodotActivity() { return true } + // Send an inter-process message to request the target editor window to force quit. + if (editorMessageDispatcher.requestForceQuit(editorWindowInfo.windowId)) { + return true + } + + // Fallback to killing the target process. val processName = packageName + editorWindowInfo.processNameSuffix val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val runningProcesses = activityManager.runningAppProcesses @@ -285,29 +368,65 @@ open class GodotEditor : GodotActivity() { java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) /** - * Whether we should launch the new godot instance in an adjacent window - * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT + * Retrieves the play window pip mode editor setting. */ - private fun shouldGameLaunchAdjacent(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - try { - when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { - ANDROID_WINDOW_SAME_AS_EDITOR -> false - ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> true - else -> { - // ANDROID_WINDOW_AUTO - isInMultiWindowMode || isLargeScreen + private fun getPlayWindowPiPMode(): Int { + return try { + Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/play_window_pip_mode")) + } catch (e: NumberFormatException) { + PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR + } + } + + /** + * If the launch policy is [LaunchPolicy.AUTO], resolve it into a specific policy based on the + * editor setting or device and screen metrics. + * + * If the launch policy is [LaunchPolicy.PIP] but PIP is not supported, fallback to the default + * launch policy. + */ + private fun resolveLaunchPolicyIfNeeded(policy: LaunchPolicy): LaunchPolicy { + val inMultiWindowMode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + isInMultiWindowMode + } else { + false + } + val defaultLaunchPolicy = if (inMultiWindowMode || isLargeScreen) { + LaunchPolicy.ADJACENT + } else { + LaunchPolicy.SAME + } + + return when (policy) { + LaunchPolicy.AUTO -> { + try { + when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { + ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME + ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT + else -> { + // ANDROID_WINDOW_AUTO + defaultLaunchPolicy + } } + } catch (e: NumberFormatException) { + Log.w(TAG, "Error parsing the Android window placement editor setting", e) + // Fall-back to the default launch policy + defaultLaunchPolicy } - } catch (e: NumberFormatException) { - // Fall-back to the 'Auto' behavior - isInMultiWindowMode || isLargeScreen } - } else { - false + + else -> { + policy + } } } + /** + * Returns true the if the device supports picture-in-picture (PiP) + */ + protected open fun hasPiPSystemFeature() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) // Check if we got the MANAGE_EXTERNAL_STORAGE permission diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index 2bcfba559c..6b4bf255f2 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -30,6 +30,14 @@ package org.godotengine.editor +import android.annotation.SuppressLint +import android.app.PictureInPictureParams +import android.content.Intent +import android.graphics.Rect +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.view.View import org.godotengine.godot.GodotLib /** @@ -37,7 +45,90 @@ import org.godotengine.godot.GodotLib */ class GodotGame : GodotEditor() { - override fun getGodotAppLayout() = org.godotengine.godot.R.layout.godot_app_layout + companion object { + private val TAG = GodotGame::class.java.simpleName + } + + private val gameViewSourceRectHint = Rect() + private val pipButton: View? by lazy { + findViewById(R.id.godot_pip_button) + } + + private var pipAvailable = false + + @SuppressLint("ClickableViewAccessibility") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val gameView = findViewById<View>(R.id.godot_fragment_container) + gameView?.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + gameView.getGlobalVisibleRect(gameViewSourceRectHint) + } + } + + pipButton?.setOnClickListener { enterPiPMode() } + + handleStartIntent(intent) + } + + override fun onNewIntent(newIntent: Intent) { + super.onNewIntent(newIntent) + handleStartIntent(newIntent) + } + + private fun handleStartIntent(intent: Intent) { + pipAvailable = intent.getBooleanExtra(EXTRA_PIP_AVAILABLE, pipAvailable) + updatePiPButtonVisibility() + + val pipLaunchRequested = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false) + if (pipLaunchRequested) { + enterPiPMode() + } + } + + private fun updatePiPButtonVisibility() { + pipButton?.visibility = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable && !isInPictureInPictureMode) { + View.VISIBLE + } else { + View.GONE + } + } + + private fun enterPiPMode() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && pipAvailable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val builder = PictureInPictureParams.Builder().setSourceRectHint(gameViewSourceRectHint) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setSeamlessResizeEnabled(false) + } + setPictureInPictureParams(builder.build()) + } + + Log.v(TAG, "Entering PiP mode") + enterPictureInPictureMode() + } + } + + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode) + Log.v(TAG, "onPictureInPictureModeChanged: $isInPictureInPictureMode") + updatePiPButtonVisibility() + } + + override fun onStop() { + super.onStop() + + val isInPiPMode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode + if (isInPiPMode && !isFinishing) { + // We get in this state when PiP is closed, so we terminate the activity. + finish() + } + } + + override fun getGodotAppLayout() = R.layout.godot_game_layout + + override fun getEditorWindowInfo() = RUN_GAME_INFO override fun overrideOrientationRequest() = false diff --git a/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml new file mode 100644 index 0000000000..41bc5475c8 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/ic_play_window_foreground.xml @@ -0,0 +1,25 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:tint="#FFFFFF" + android:viewportWidth="24" + android:viewportHeight="24"> + <group + android:scaleX="0.522" + android:scaleY="0.522" + android:translateX="5.736" + android:translateY="5.736"> + <path + android:fillColor="@android:color/white" + android:pathData="M21.58,16.09l-1.09,-7.66C20.21,6.46 18.52,5 16.53,5H7.47C5.48,5 3.79,6.46 3.51,8.43l-1.09,7.66C2.2,17.63 3.39,19 4.94,19h0c0.68,0 1.32,-0.27 1.8,-0.75L9,16h6l2.25,2.25c0.48,0.48 1.13,0.75 1.8,0.75h0C20.61,19 21.8,17.63 21.58,16.09zM19.48,16.81C19.4,16.9 19.27,17 19.06,17c-0.15,0 -0.29,-0.06 -0.39,-0.16L15.83,14H8.17l-2.84,2.84C5.23,16.94 5.09,17 4.94,17c-0.21,0 -0.34,-0.1 -0.42,-0.19c-0.08,-0.09 -0.16,-0.23 -0.13,-0.44l1.09,-7.66C5.63,7.74 6.48,7 7.47,7h9.06c0.99,0 1.84,0.74 1.98,1.72l1.09,7.66C19.63,16.58 19.55,16.72 19.48,16.81z" /> + <path + android:fillColor="@android:color/white" + android:pathData="M9,8l-1,0l0,2l-2,0l0,1l2,0l0,2l1,0l0,-2l2,0l0,-1l-2,0z" /> + <path + android:fillColor="@android:color/white" + android:pathData="M17,12m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" /> + <path + android:fillColor="@android:color/white" + android:pathData="M15,9m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" /> + </group> +</vector> diff --git a/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml new file mode 100644 index 0000000000..c8b5a15d19 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/outline_fullscreen_exit_48.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:tint="#FFFFFF" + android:viewportWidth="24" + android:viewportHeight="24"> + + <path + android:fillColor="@android:color/white" + android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z" /> + +</vector> diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml new file mode 100644 index 0000000000..aeaa96ce54 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_activated_bg_drawable.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <size + android:width="60dp" + android:height="60dp" /> + + <solid android:color="#44000000" /> +</shape> diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml new file mode 100644 index 0000000000..e9b2959275 --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_bg_drawable.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + + <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_pressed="true" /> + <item android:drawable="@drawable/pip_button_activated_bg_drawable" android:state_hovered="true" /> + + <item android:drawable="@drawable/pip_button_default_bg_drawable" /> + +</selector> diff --git a/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml new file mode 100644 index 0000000000..a8919689fe --- /dev/null +++ b/platform/android/java/editor/src/main/res/drawable/pip_button_default_bg_drawable.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <size + android:width="60dp" + android:height="60dp" /> + + <solid android:color="#13000000" /> +</shape> diff --git a/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml new file mode 100644 index 0000000000..d53787c87e --- /dev/null +++ b/platform/android/java/editor/src/main/res/layout/godot_game_layout.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <FrameLayout + android:id="@+id/godot_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <ImageView + android:id="@+id/godot_pip_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="36dp" + android:contentDescription="@string/pip_button_description" + android:background="@drawable/pip_button_bg_drawable" + android:scaleType="center" + android:src="@drawable/outline_fullscreen_exit_48" + android:visibility="gone" + android:layout_gravity="end|top" + tools:visibility="visible" /> + +</FrameLayout> diff --git a/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml new file mode 100644 index 0000000000..a3aabf2ee0 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-anydpi-v26/ic_play_window.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/icon_background"/> + <foreground android:drawable="@drawable/ic_play_window_foreground"/> +</adaptive-icon> diff --git a/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png Binary files differnew file mode 100644 index 0000000000..a5ce40241f --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-hdpi/ic_play_window.png diff --git a/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png Binary files differnew file mode 100644 index 0000000000..147adb6127 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-mdpi/ic_play_window.png diff --git a/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png Binary files differnew file mode 100644 index 0000000000..0b1db1b923 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-xhdpi/ic_play_window.png diff --git a/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png Binary files differnew file mode 100644 index 0000000000..39d7450390 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-xxhdpi/ic_play_window.png diff --git a/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png Binary files differnew file mode 100644 index 0000000000..b7a09a15b5 --- /dev/null +++ b/platform/android/java/editor/src/main/res/mipmap-xxxhdpi/ic_play_window.png diff --git a/platform/android/java/editor/src/main/res/values/dimens.xml b/platform/android/java/editor/src/main/res/values/dimens.xml index 98bfe40179..1e486872e6 100644 --- a/platform/android/java/editor/src/main/res/values/dimens.xml +++ b/platform/android/java/editor/src/main/res/values/dimens.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <dimen name="editor_default_window_height">600dp</dimen> + <dimen name="editor_default_window_height">640dp</dimen> <dimen name="editor_default_window_width">1024dp</dimen> </resources> diff --git a/platform/android/java/editor/src/main/res/values/strings.xml b/platform/android/java/editor/src/main/res/values/strings.xml index 909711ab18..0ad54ac3a1 100644 --- a/platform/android/java/editor/src/main/res/values/strings.xml +++ b/platform/android/java/editor/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> + <string name="godot_game_activity_name">Godot Play window</string> <string name="denied_storage_permission_error_msg">Missing storage access permission!</string> + <string name="pip_button_description">Button used to toggle picture-in-picture mode for the Play window</string> </resources> diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 49e8ffb008..38bd336e2d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -42,13 +42,16 @@ import android.hardware.Sensor import android.hardware.SensorManager import android.os.* import android.util.Log +import android.util.TypedValue import android.view.* import android.widget.FrameLayout import androidx.annotation.Keep import androidx.annotation.StringRes import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import com.google.android.vending.expansion.downloader.* import org.godotengine.godot.error.Error import org.godotengine.godot.input.GodotEditText @@ -105,36 +108,26 @@ class Godot(private val context: Context) { GodotPluginRegistry.getPluginRegistry() } - private val accelerometer_enabled = AtomicBoolean(false) + private val accelerometerEnabled = AtomicBoolean(false) private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } - private val gravity_enabled = AtomicBoolean(false) + private val gravityEnabled = AtomicBoolean(false) private val mGravity: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) } - private val magnetometer_enabled = AtomicBoolean(false) + private val magnetometerEnabled = AtomicBoolean(false) private val mMagnetometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } - private val gyroscope_enabled = AtomicBoolean(false) + private val gyroscopeEnabled = AtomicBoolean(false) private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } - private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - val decorView = requireActivity().window.decorView - decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - }} - val tts = GodotTTS(context) val directoryAccessHandler = DirectoryAccessHandler(context) val fileAccessHandler = FileAccessHandler(context) @@ -185,7 +178,7 @@ class Godot(private val context: Context) { private var xrMode = XRMode.REGULAR private var expansionPackPath: String = "" private var useApkExpansion = false - private var useImmersive = false + private val useImmersive = AtomicBoolean(false) private var useDebugOpengl = false private var darkMode = false @@ -254,15 +247,9 @@ class Godot(private val context: Context) { xrMode = XRMode.OPENXR } else if (commandLine[i] == "--debug_opengl") { useDebugOpengl = true - } else if (commandLine[i] == "--use_immersive") { - useImmersive = true - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - registerUiChangeListener() + } else if (commandLine[i] == "--fullscreen") { + useImmersive.set(true) + newArgs.add(commandLine[i]) } else if (commandLine[i] == "--use_apk_expansion") { useApkExpansion = true } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { @@ -336,6 +323,54 @@ class Godot(private val context: Context) { } /** + * Toggle immersive mode. + * Must be called from the UI thread. + */ + private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) { + val activity = getActivity() ?: return + val window = activity.window ?: return + + if (!useImmersive.compareAndSet(!enabled, enabled) && !override) { + return + } + + WindowCompat.setDecorFitsSystemWindows(window, !enabled) + val controller = WindowInsetsControllerCompat(window, window.decorView) + if (enabled) { + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + val fullScreenThemeValue = TypedValue() + val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) { + fullScreenThemeValue.data == 0 + } else { + // Fallback to checking the editor build + !isEditorBuild() + } + + val types = if (hasStatusBar) { + WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars() + } else { + WindowInsetsCompat.Type.navigationBars() + } + controller.show(types) + } + } + + /** + * Invoked from the render thread to toggle the immersive mode. + */ + @Keep + private fun nativeEnableImmersiveMode(enabled: Boolean) { + runOnUiThread { + enableImmersiveMode(enabled) + } + } + + @Keep + fun isInImmersiveMode() = useImmersive.get() + + /** * Initializes the native layer of the Godot engine. * * This must be preceded by [onCreate] and followed by [onInitRenderView] to complete @@ -552,15 +587,7 @@ class Godot(private val context: Context) { renderView?.onActivityResumed() registerSensorsIfNeeded() - if (useImmersive) { - val window = requireActivity().window - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - } + enableImmersiveMode(useImmersive.get(), true) for (plugin in pluginRegistry.allPlugins) { plugin.onMainResume() } @@ -571,16 +598,16 @@ class Godot(private val context: Context) { return } - if (accelerometer_enabled.get() && mAccelerometer != null) { + if (accelerometerEnabled.get() && mAccelerometer != null) { mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) } - if (gravity_enabled.get() && mGravity != null) { + if (gravityEnabled.get() && mGravity != null) { mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME) } - if (magnetometer_enabled.get() && mMagnetometer != null) { + if (magnetometerEnabled.get() && mMagnetometer != null) { mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) } - if (gyroscope_enabled.get() && mGyroscope != null) { + if (gyroscopeEnabled.get() && mGyroscope != null) { mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME) } } @@ -696,10 +723,10 @@ class Godot(private val context: Context) { Log.v(TAG, "OnGodotMainLoopStarted") godotMainLoopStarted.set(true) - accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) - gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) - gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) - magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) + accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) + gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) + gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) + magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) runOnUiThread { registerSensorsIfNeeded() @@ -724,11 +751,6 @@ class Godot(private val context: Context) { primaryHost?.onGodotRestartRequested(this) } - private fun registerUiChangeListener() { - val decorView = requireActivity().window.decorView - decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) - } - fun alert( @StringRes messageResId: Int, @StringRes titleResId: Int, diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt index 913e3d04c5..474c6e9b2f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -53,8 +53,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { private val TAG = GodotActivity::class.java.simpleName @JvmStatic - protected val EXTRA_FORCE_QUIT = "force_quit_requested" - @JvmStatic protected val EXTRA_NEW_LAUNCH = "new_launch_requested" } @@ -128,12 +126,6 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { } private fun handleStartIntent(intent: Intent, newLaunch: Boolean) { - val forceQuitRequested = intent.getBooleanExtra(EXTRA_FORCE_QUIT, false) - if (forceQuitRequested) { - Log.d(TAG, "Force quit requested, terminating..") - ProcessPhoenix.forceQuit(this) - return - } if (!newLaunch) { val newLaunchRequested = intent.getBooleanExtra(EXTRA_NEW_LAUNCH, false) if (newLaunchRequested) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java index b1bce45fbb..d9afdf90b1 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/ProcessPhoenix.java @@ -24,6 +24,7 @@ package org.godotengine.godot.utils; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.os.Bundle; @@ -44,6 +45,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; */ public final class ProcessPhoenix extends Activity { private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents"; + // -- GODOT start -- + private static final String KEY_RESTART_ACTIVITY_OPTIONS = "phoenix_restart_activity_options"; + // -- GODOT end -- private static final String KEY_MAIN_PROCESS_PID = "phoenix_main_process_pid"; /** @@ -56,12 +60,23 @@ public final class ProcessPhoenix extends Activity { triggerRebirth(context, getRestartIntent(context)); } + // -- GODOT start -- /** * Call to restart the application process using the specified intents. * <p> * Behavior of the current process after invoking this method is undefined. */ public static void triggerRebirth(Context context, Intent... nextIntents) { + triggerRebirth(context, null, nextIntents); + } + + /** + * Call to restart the application process using the specified intents launched with the given + * {@link ActivityOptions}. + * <p> + * Behavior of the current process after invoking this method is undefined. + */ + public static void triggerRebirth(Context context, Bundle activityOptions, Intent... nextIntents) { if (nextIntents.length < 1) { throw new IllegalArgumentException("intents cannot be empty"); } @@ -72,10 +87,12 @@ public final class ProcessPhoenix extends Activity { intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context. intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents))); intent.putExtra(KEY_MAIN_PROCESS_PID, Process.myPid()); + if (activityOptions != null) { + intent.putExtra(KEY_RESTART_ACTIVITY_OPTIONS, activityOptions); + } context.startActivity(intent); } - // -- GODOT start -- /** * Finish the activity and kill its process */ @@ -112,9 +129,11 @@ public final class ProcessPhoenix extends Activity { super.onCreate(savedInstanceState); // -- GODOT start -- - ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS); - startActivities(intents.toArray(new Intent[intents.size()])); - forceQuit(this, getIntent().getIntExtra(KEY_MAIN_PROCESS_PID, -1)); + Intent launchIntent = getIntent(); + ArrayList<Intent> intents = launchIntent.getParcelableArrayListExtra(KEY_RESTART_INTENTS); + Bundle activityOptions = launchIntent.getBundleExtra(KEY_RESTART_ACTIVITY_OPTIONS); + startActivities(intents.toArray(new Intent[intents.size()]), activityOptions); + forceQuit(this, launchIntent.getIntExtra(KEY_MAIN_PROCESS_PID, -1)); // -- GODOT end -- } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index f1759af54a..d3b30e4589 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); _sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); _verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I"); + _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V"); + _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) { return ERR_UNCONFIGURED; } } + +void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) { + if (_enable_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled); + } +} + +bool GodotJavaWrapper::is_in_immersive_mode() { + if (_is_in_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 6b66565981..51d7f98541 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -77,6 +77,8 @@ private: jmethodID _has_feature = nullptr; jmethodID _sign_apk = nullptr; jmethodID _verify_apk = nullptr; + jmethodID _enable_immersive_mode = nullptr; + jmethodID _is_in_immersive_mode = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -122,6 +124,9 @@ public: // Sign and verify apks Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); Error verify_apk(const String &p_apk_path); + + void enable_immersive_mode(bool p_enabled); + bool is_in_immersive_mode(); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index e4b5392c4e..b99e825540 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false); } -Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) { +Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String dest_dir = p_path.get_base_dir() + "/"; @@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() { } #endif -Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { #ifdef MACOS_ENABLED ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); @@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int String host = EDITOR_GET("network/debug/remote_host"); int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { host = "localhost"; } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int port = EDITOR_GET("filesystem/file_server/port"); String passwd = EDITOR_GET("filesystem/file_server/password"); cmd_args_list.push_back("--remote-fs"); @@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { cmd_args_list.push_back("--remote-debug"); cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); @@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { cmd_args_list.push_back("--debug-collisions"); } - if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { cmd_args_list.push_back("--debug-navigation"); } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 1964906c27..db7c0553dd 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug); - Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick); + Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; @@ -169,7 +169,7 @@ public: virtual Ref<ImageTexture> get_option_icon(int p_index) const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual bool poll_export() override { bool dc = devices_changed.is_set(); @@ -202,7 +202,7 @@ public: return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 0032b898d2..69ba742f72 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP return OK; } -Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index bbc55b82ce..1d9ef01d1a 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -76,7 +76,7 @@ public: virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; virtual bool is_executable(const String &p_path) const override; @@ -87,7 +87,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformLinuxBSD(); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 671da7fc2a..94a748e414 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, append_dbus_string(&struct_iter, p_filter_names[i]); dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); - const String &flt = p_filter_exts[i]; + const String &flt_orig = p_filter_exts[i]; + String flt; + for (int j = 0; j < flt_orig.length(); j++) { + if (is_unicode_letter(flt_orig[j])) { + flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j])); + } else { + flt += flt_orig[j]; + } + } int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); @@ -377,17 +385,26 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo String flt = tokens[0].strip_edges(); if (!flt.is_empty()) { if (tokens.size() == 2) { - filter_exts.push_back(flt); + if (flt == "*.*") { + filter_exts.push_back("*"); + } else { + filter_exts.push_back(flt); + } filter_names.push_back(tokens[1]); } else { - filter_exts.push_back(flt); - filter_names.push_back(flt); + if (flt == "*.*") { + filter_exts.push_back("*"); + filter_names.push_back(RTR("All Files")); + } else { + filter_exts.push_back(flt); + filter_names.push_back(flt); + } } } } } if (filter_names.is_empty()) { - filter_exts.push_back("*.*"); + filter_exts.push_back("*"); filter_names.push_back(RTR("All Files")); } diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 290b0082fc..8372600ae9 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1505,7 +1505,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres return OK; } -Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -2511,7 +2511,7 @@ void EditorExportPlatformMacOS::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -2573,8 +2573,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -2583,7 +2582,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index 062a2e5f95..5457c687d3 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -147,7 +147,7 @@ public: virtual bool is_executable(const String &p_path) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; @@ -167,7 +167,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformMacOS(); diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index d8c1b6033d..5faab74d7b 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_ } } -void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { +void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { // Engine.js config Dictionary config; Array libs; for (int i = 0; i < p_shared_objects.size(); i++) { libs.push_back(p_shared_objects[i].path.get_file()); } - Vector<String> flags; - gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); + Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); Array args; for (int i = 0; i < flags.size(); i++) { args.push_back(flags[i]); @@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo return list; } -Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String custom_debug = p_preset->get("custom_template/debug"); @@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const { return ""; } -Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { +Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { const uint16_t bind_port = EDITOR_GET("export/web/http_port"); // Resolve host if needed. const String bind_host = EDITOR_GET("export/web/http_host"); diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 2f67d8107f..3c743e2e74 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template); - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); @@ -120,14 +120,14 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool poll_export() override; virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; virtual Ref<ImageTexture> get_option_icon(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; virtual void get_platform_features(List<String> *r_features) const override { diff --git a/platform/windows/console_wrapper_windows.cpp b/platform/windows/console_wrapper_windows.cpp index 133711a9ea..1ba09b236b 100644 --- a/platform/windows/console_wrapper_windows.cpp +++ b/platform/windows/console_wrapper_windows.cpp @@ -40,8 +40,8 @@ int main(int argc, char *argv[]) { // Get executable name. - WCHAR exe_name[MAX_PATH] = {}; - if (!GetModuleFileNameW(nullptr, exe_name, MAX_PATH)) { + WCHAR exe_name[32767] = {}; + if (!GetModuleFileNameW(nullptr, exe_name, 32767)) { wprintf(L"GetModuleFileName failed, error %d\n", GetLastError()); return -1; } diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp index 2abe285d31..a6015092e8 100644 --- a/platform/windows/crash_handler_windows_seh.cpp +++ b/platform/windows/crash_handler_windows_seh.cpp @@ -118,7 +118,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { HANDLE process = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); DWORD offset_from_symbol = 0; - IMAGEHLP_LINE64 line = { 0 }; + IMAGEHLP_LINE64 line = {}; std::vector<module_data> modules; DWORD cbNeeded; std::vector<HMODULE> module_handles(1); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 11dd4548f1..d2a9c2315f 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -13,40 +13,33 @@ if TYPE_CHECKING: # To match other platforms STACK_SIZE = 8388608 +STACK_SIZE_SANITIZERS = 30 * 1024 * 1024 def get_name(): return "Windows" -def try_cmd(test, prefix, arch): +def try_cmd(test, prefix, arch, check_clang=False): + archs = ["x86_64", "x86_32", "arm64", "arm32"] if arch: + archs = [arch] + + for a in archs: try: out = subprocess.Popen( - get_mingw_bin_prefix(prefix, arch) + test, + get_mingw_bin_prefix(prefix, a) + test, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) - out.communicate() + outs, errs = out.communicate() if out.returncode == 0: + if check_clang and not outs.startswith(b"clang"): + return False return True except Exception: pass - else: - for a in ["x86_64", "x86_32", "arm64", "arm32"]: - try: - out = subprocess.Popen( - get_mingw_bin_prefix(prefix, a) + test, - shell=True, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, - ) - out.communicate() - if out.returncode == 0: - return True - except Exception: - pass return False @@ -203,6 +196,7 @@ def get_opts(): BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), + BoolVariable("use_ubsan", "Use LLVM compiler undefined behavior sanitizer (UBSAN)", False), BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False), BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False), BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting any errors to stderr.", True), @@ -387,6 +381,15 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): ## Compile/link flags + if env["use_llvm"]: + env["CC"] = "clang-cl" + env["CXX"] = "clang-cl" + env["LINK"] = "lld-link" + env["AR"] = "llvm-lib" + + env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"]) + env.extra_suffix = ".llvm" + env.extra_suffix + env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable. if env["silence_msvc"] and not env.GetOption("clean"): @@ -471,7 +474,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. - env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ # Once it was thought that only debug builds would be too large, # but this has recently stopped being true. See the mingw function # for notes on why this shouldn't be enabled for gcc @@ -507,6 +509,7 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): if env["use_asan"]: env.extra_suffix += ".san" prebuilt_lib_extra_suffix = ".san" + env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"]) env.Append(CCFLAGS=["/fsanitize=address"]) env.Append(LINKFLAGS=["/INFERASANLIBS"]) @@ -595,6 +598,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): if env["target"] in ["editor", "template_debug"]: LIBS += ["psapi", "dbghelp"] + if env["use_llvm"]: + LIBS += [f"clang_rt.builtins-{env['arch']}"] + env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) if vcvars_msvc_config: @@ -610,14 +616,22 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): if env["lto"] != "none": if env["lto"] == "thin": - print_error("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") - sys.exit(255) - env.AppendUnique(CCFLAGS=["/GL"]) - env.AppendUnique(ARFLAGS=["/LTCG"]) - if env["progress"]: - env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) + if not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + sys.exit(255) + + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["use_llvm"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) else: - env.AppendUnique(LINKFLAGS=["/LTCG"]) + env.AppendUnique(CCFLAGS=["/GL"]) + env.AppendUnique(ARFLAGS=["/LTCG"]) + if env["progress"]: + env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) + else: + env.AppendUnique(LINKFLAGS=["/LTCG"]) if vcvars_msvc_config: env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")]) @@ -628,7 +642,11 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): env["BUILDERS"]["Program"] = methods.precious_program env.Append(LINKFLAGS=["/NATVIS:platform\\windows\\godot.natvis"]) - env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)]) + + if env["use_asan"]: + env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE_SANITIZERS)]) + else: + env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)]) def configure_mingw(env: "SConsEnvironment"): @@ -644,6 +662,10 @@ def configure_mingw(env: "SConsEnvironment"): if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): env["use_llvm"] = False + if not env["use_llvm"] and try_cmd("gcc --version", env["mingw_prefix"], env["arch"], True): + print("Detected GCC to be a wrapper for Clang.") + env["use_llvm"] = True + # TODO: Re-evaluate the need for this / streamline with common config. if env["target"] == "template_release": if env["arch"] != "arm64": @@ -721,7 +743,10 @@ def configure_mingw(env: "SConsEnvironment"): env.Append(CCFLAGS=["-flto"]) env.Append(LINKFLAGS=["-flto"]) - env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)]) + if env["use_asan"]: + env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)]) + else: + env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)]) ## Compile flags @@ -732,6 +757,32 @@ def configure_mingw(env: "SConsEnvironment"): if not env["use_llvm"]: env.Append(CCFLAGS=["-mwindows"]) + if env["use_asan"] or env["use_ubsan"]: + if not env["use_llvm"]: + print("GCC does not support sanitizers on Windows.") + sys.exit(255) + if env["arch"] not in ["x86_32", "x86_64"]: + print("Sanitizers are only supported for x86_32 and x86_64.") + sys.exit(255) + + env.extra_suffix += ".san" + env.AppendUnique(CPPDEFINES=["SANITIZERS_ENABLED"]) + san_flags = [] + if env["use_asan"]: + san_flags.append("-fsanitize=address") + if env["use_ubsan"]: + san_flags.append("-fsanitize=undefined") + # Disable the vptr check since it gets triggered on any COM interface calls. + san_flags.append("-fno-sanitize=vptr") + env.Append(CFLAGS=san_flags) + env.Append(CCFLAGS=san_flags) + env.Append(LINKFLAGS=san_flags) + + if env["use_llvm"] and os.name == "nt" and methods._colorize: + env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"]) + + env.Append(ARFLAGS=["--thin"]) + env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"]) env.Append( CPPDEFINES=[ diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 6fa3f2c9d6..8bcd556c22 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -133,9 +133,17 @@ String DisplayServerWindows::get_name() const { } void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { + if (p_mode == MOUSE_MODE_HIDDEN || p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) { + // Hide cursor before moving. + if (hCursor == nullptr) { + hCursor = SetCursor(nullptr); + } else { + SetCursor(nullptr); + } + } + if (windows.has(MAIN_WINDOW_ID) && (p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED || p_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Mouse is grabbed (captured or confined). - WindowID window_id = _get_focused_window_or_popup(); if (!windows.has(window_id)) { window_id = MAIN_WINDOW_ID; @@ -165,13 +173,8 @@ void DisplayServerWindows::_set_mouse_mode_impl(MouseMode p_mode) { _register_raw_input_devices(INVALID_WINDOW_ID); } - if (p_mode == MOUSE_MODE_HIDDEN || p_mode == MOUSE_MODE_CAPTURED || p_mode == MOUSE_MODE_CONFINED_HIDDEN) { - if (hCursor == nullptr) { - hCursor = SetCursor(nullptr); - } else { - SetCursor(nullptr); - } - } else { + if (p_mode == MOUSE_MODE_VISIBLE || p_mode == MOUSE_MODE_CONFINED) { + // Show cursor. CursorShape c = cursor_shape; cursor_shape = CURSOR_MAX; cursor_set_shape(c); @@ -314,7 +317,7 @@ public: if (!lpw_path) { return S_FALSE; } - String path = String::utf16((const char16_t *)lpw_path).simplify_path(); + String path = String::utf16((const char16_t *)lpw_path).replace("\\", "/").trim_prefix(R"(\\?\)").simplify_path(); if (!path.begins_with(root.simplify_path())) { return S_FALSE; } @@ -539,7 +542,26 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { pfd->SetOptions(flags | FOS_FORCEFILESYSTEM); pfd->SetTitle((LPCWSTR)fd->title.utf16().ptr()); - String dir = fd->current_directory.replace("/", "\\"); + String dir = ProjectSettings::get_singleton()->globalize_path(fd->current_directory); + if (dir == ".") { + dir = OS::get_singleton()->get_executable_path().get_base_dir(); + } + if (dir.is_relative_path() || dir == ".") { + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + if (dir == ".") { + dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/"); + } else { + dir = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(dir); + } + } + dir = dir.simplify_path(); + dir = dir.replace("/", "\\"); + if (!dir.is_network_share_path() && !dir.begins_with(R"(\\?\)")) { + dir = R"(\\?\)" + dir; + } IShellItem *shellitem = nullptr; hr = SHCreateItemFromParsingName((LPCWSTR)dir.utf16().ptr(), nullptr, IID_IShellItem, (void **)&shellitem); @@ -582,7 +604,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { PWSTR file_path = nullptr; hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); if (SUCCEEDED(hr)) { - file_names.push_back(String::utf16((const char16_t *)file_path)); + file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)")); CoTaskMemFree(file_path); } result->Release(); @@ -596,7 +618,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { PWSTR file_path = nullptr; hr = result->GetDisplayName(SIGDN_FILESYSPATH, &file_path); if (SUCCEEDED(hr)) { - file_names.push_back(String::utf16((const char16_t *)file_path)); + file_names.push_back(String::utf16((const char16_t *)file_path).replace("\\", "/").trim_prefix(R"(\\?\)")); CoTaskMemFree(file_path); } result->Release(); diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index b465bd4ecd..8d3f4bb269 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres } } -Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { if (p_preset->get("application/modify_resources")) { _rcedit_add_data(p_preset, p_path, false); String wrapper_path = p_path.get_basename() + ".console.exe"; @@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 6ccb4a15a7..e86aac83d4 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { String _get_exe_arch(const String &p_path) const; public: - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override; virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual void get_export_options(List<ExportOption> *r_options) const override; @@ -95,7 +95,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformWindows(); diff --git a/platform/windows/godot_res_wrap.rc b/platform/windows/godot_res_wrap.rc index 27ad26cbc5..61e6100497 100644 --- a/platform/windows/godot_res_wrap.rc +++ b/platform/windows/godot_res_wrap.rc @@ -1,6 +1,11 @@ #include "core/version.h" +#ifndef RT_MANIFEST +#define RT_MANIFEST 24 +#endif + GODOT_ICON ICON platform/windows/godot_console.ico +1 RT_MANIFEST "godot.manifest" 1 VERSIONINFO FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH,0 diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 40b265785f..47836788e1 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -90,6 +90,23 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #define GetProcAddress (void *)GetProcAddress #endif +static String fix_path(const String &p_path) { + String path = p_path; + if (p_path.is_relative_path()) { + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace("\\", "/").path_join(path); + } + path = path.simplify_path(); + path = path.replace("/", "\\"); + if (!path.is_network_share_path() && !path.begins_with(R"(\\?\)")) { + path = R"(\\?\)" + path; + } + return path; +} + static String format_error_message(DWORD id) { LPWSTR messageBuffer = nullptr; size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, @@ -166,15 +183,9 @@ void OS_Windows::initialize_debugging() { static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) { String err_str; if (p_errorexp && p_errorexp[0]) { - err_str = String::utf8(p_errorexp); + err_str = String::utf8(p_errorexp) + "\n"; } else { - err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error); - } - - if (p_editor_notify) { - err_str += " (User)\n"; - } else { - err_str += "\n"; + err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error) + "\n"; } OutputDebugStringW((LPCWSTR)err_str.utf16().ptr()); @@ -306,7 +317,7 @@ Error OS_Windows::get_entropy(uint8_t *r_buffer, int p_bytes) { } #ifdef DEBUG_ENABLED -void debug_dynamic_library_check_dependencies(const String &p_root_path, const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) { +void debug_dynamic_library_check_dependencies(const String &p_path, HashSet<String> &r_checked, HashSet<String> &r_missing) { if (r_checked.has(p_path)) { return; } @@ -348,15 +359,15 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S const IMAGE_IMPORT_DESCRIPTOR *import_desc = (const IMAGE_IMPORT_DESCRIPTOR *)ImageDirectoryEntryToData((HMODULE)loaded_image.MappedAddress, false, IMAGE_DIRECTORY_ENTRY_IMPORT, &size); if (import_desc) { for (; import_desc->Name && import_desc->FirstThunk; import_desc++) { - char16_t full_name_wc[MAX_PATH]; + char16_t full_name_wc[32767]; const char *name_cs = (const char *)ImageRvaToVa(loaded_image.FileHeader, loaded_image.MappedAddress, import_desc->Name, nullptr); String name = String(name_cs); if (name.begins_with("api-ms-win-")) { r_checked.insert(name); - } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) { - debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing); - } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, MAX_PATH, (LPWSTR)full_name_wc, nullptr)) { - debug_dynamic_library_check_dependencies(p_root_path, String::utf16(full_name_wc), r_checked, r_missing); + } else if (SearchPathW(nullptr, (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) { + debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing); + } else if (SearchPathW((LPCWSTR)(p_path.get_base_dir().utf16().get_data()), (LPCWSTR)name.utf16().get_data(), nullptr, 32767, (LPWSTR)full_name_wc, nullptr)) { + debug_dynamic_library_check_dependencies(String::utf16(full_name_wc), r_checked, r_missing); } else { r_missing.insert(name); } @@ -373,7 +384,7 @@ void debug_dynamic_library_check_dependencies(const String &p_root_path, const S #endif Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) { - String path = p_path.replace("/", "\\"); + String path = p_path; if (!FileAccess::exists(path)) { //this code exists so gdextension can load .dll files from within the executable path @@ -419,11 +430,13 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha bool has_dll_directory_api = ((add_dll_directory != nullptr) && (remove_dll_directory != nullptr)); DLL_DIRECTORY_COOKIE cookie = nullptr; + String dll_path = fix_path(load_path); + String dll_dir = fix_path(ProjectSettings::get_singleton()->globalize_path(load_path.get_base_dir())); if (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) { - cookie = add_dll_directory((LPCWSTR)(load_path.get_base_dir().utf16().get_data())); + cookie = add_dll_directory((LPCWSTR)(dll_dir.utf16().get_data())); } - p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(load_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); + p_library_handle = (void *)LoadLibraryExW((LPCWSTR)(dll_path.utf16().get_data()), nullptr, (p_data != nullptr && p_data->also_set_library_path && has_dll_directory_api) ? LOAD_LIBRARY_SEARCH_DEFAULT_DIRS : 0); if (!p_library_handle) { if (p_data != nullptr && p_data->generate_temp_files) { DirAccess::remove_absolute(load_path); @@ -434,7 +447,7 @@ Error OS_Windows::open_dynamic_library(const String &p_path, void *&p_library_ha HashSet<String> checked_libs; HashSet<String> missing_libs; - debug_dynamic_library_check_dependencies(load_path, load_path, checked_libs, missing_libs); + debug_dynamic_library_check_dependencies(dll_path, checked_libs, missing_libs); if (!missing_libs.is_empty()) { String missing; for (const String &E : missing_libs) { @@ -622,6 +635,72 @@ Vector<String> OS_Windows::get_video_adapter_driver_info() const { return info; } +bool OS_Windows::get_user_prefers_integrated_gpu() const { + // On Windows 10, the preferred GPU configured in Windows Settings is + // stored in the registry under the key + // `HKEY_CURRENT_USER\SOFTWARE\Microsoft\DirectX\UserGpuPreferences` + // with the name being the app ID or EXE path. The value is in the form of + // `GpuPreference=1;`, with the value being 1 for integrated GPU and 2 + // for discrete GPU. On Windows 11, there may be more flags, separated + // by semicolons. + + // If this is a packaged app, use the "application user model ID". + // Otherwise, use the EXE path. + WCHAR value_name[32768]; + bool is_packaged = false; + { + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + if (kernel32) { + using GetCurrentApplicationUserModelIdPtr = LONG(WINAPI *)(UINT32 * length, PWSTR id); + GetCurrentApplicationUserModelIdPtr GetCurrentApplicationUserModelId = (GetCurrentApplicationUserModelIdPtr)GetProcAddress(kernel32, "GetCurrentApplicationUserModelId"); + + if (GetCurrentApplicationUserModelId) { + UINT32 length = sizeof(value_name) / sizeof(value_name[0]); + LONG result = GetCurrentApplicationUserModelId(&length, value_name); + if (result == ERROR_SUCCESS) { + is_packaged = true; + } + } + } + } + if (!is_packaged && GetModuleFileNameW(nullptr, value_name, sizeof(value_name) / sizeof(value_name[0])) >= sizeof(value_name) / sizeof(value_name[0])) { + // Paths should never be longer than 32767, but just in case. + return false; + } + + LPCWSTR subkey = L"SOFTWARE\\Microsoft\\DirectX\\UserGpuPreferences"; + HKEY hkey = nullptr; + LSTATUS result = RegOpenKeyExW(HKEY_CURRENT_USER, subkey, 0, KEY_READ, &hkey); + if (result != ERROR_SUCCESS) { + return false; + } + + DWORD size = 0; + result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, nullptr, &size); + if (result != ERROR_SUCCESS || size == 0) { + RegCloseKey(hkey); + return false; + } + + Vector<WCHAR> buffer; + buffer.resize(size / sizeof(WCHAR)); + result = RegGetValueW(hkey, nullptr, value_name, RRF_RT_REG_SZ, nullptr, (LPBYTE)buffer.ptrw(), &size); + if (result != ERROR_SUCCESS) { + RegCloseKey(hkey); + return false; + } + + RegCloseKey(hkey); + const String flags = String::utf16((const char16_t *)buffer.ptr(), size / sizeof(WCHAR)); + + for (const String &flag : flags.split(";", false)) { + if (flag == "GpuPreference=1") { + return true; + } + } + return false; +} + OS::DateTime OS_Windows::get_datetime(bool p_utc) const { SYSTEMTIME systemtime; if (p_utc) { @@ -822,7 +901,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String Dictionary ret; - String path = p_path.replace("/", "\\"); + String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path; String command = _quote_command_line_argument(path); for (const String &E : p_arguments) { command += " " + _quote_command_line_argument(E); @@ -871,7 +950,19 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW; - if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) { + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + if (current_dir_name.size() >= MAX_PATH) { + Char16String current_short_dir_name; + str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0); + current_short_dir_name.resize(str_len); + GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size()); + current_dir_name = current_short_dir_name; + } + + if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi)) { CLEAN_PIPES ERR_FAIL_V_MSG(ret, "Could not create child process: " + command); } @@ -901,7 +992,7 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String } Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) { - String path = p_path.replace("/", "\\"); + String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path; String command = _quote_command_line_argument(path); for (const String &E : p_arguments) { command += " " + _quote_command_line_argument(E); @@ -939,7 +1030,19 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, nullptr, si_w, &pi.pi); + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + if (current_dir_name.size() >= MAX_PATH) { + Char16String current_short_dir_name; + str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0); + current_short_dir_name.resize(str_len); + GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size()); + current_dir_name = current_short_dir_name; + } + + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, inherit_handles, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi); if (!ret && r_pipe) { CloseHandle(pipe[0]); // Cleanup pipe handles. CloseHandle(pipe[1]); @@ -1003,7 +1106,7 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, } Error OS_Windows::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { - String path = p_path.replace("/", "\\"); + String path = p_path.is_absolute_path() ? fix_path(p_path) : p_path; String command = _quote_command_line_argument(path); for (const String &E : p_arguments) { command += " " + _quote_command_line_argument(E); @@ -1022,7 +1125,19 @@ Error OS_Windows::create_process(const String &p_path, const List<String> &p_arg creation_flags |= CREATE_NO_WINDOW; } - int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, nullptr, si_w, &pi.pi); + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + if (current_dir_name.size() >= MAX_PATH) { + Char16String current_short_dir_name; + str_len = GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), nullptr, 0); + current_short_dir_name.resize(str_len); + GetShortPathNameW((LPCWSTR)current_dir_name.ptr(), (LPWSTR)current_short_dir_name.ptrw(), current_short_dir_name.size()); + current_dir_name = current_short_dir_name; + } + + int ret = CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, false, creation_flags, nullptr, (LPWSTR)current_dir_name.ptr(), si_w, &pi.pi); ERR_FAIL_COND_V_MSG(ret == 0, ERR_CANT_FORK, "Could not create child process: " + command); ProcessID pid = pi.pi.dwProcessId; @@ -1390,8 +1505,8 @@ Vector<String> OS_Windows::get_system_font_path_for_text(const String &p_font_na continue; } - WCHAR file_path[MAX_PATH]; - hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH); + WCHAR file_path[32767]; + hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767); if (FAILED(hr)) { continue; } @@ -1469,8 +1584,8 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight, continue; } - WCHAR file_path[MAX_PATH]; - hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], MAX_PATH); + WCHAR file_path[32767]; + hr = loader->GetFilePathFromKey(reference_key, reference_key_size, &file_path[0], 32767); if (FAILED(hr)) { continue; } @@ -1576,7 +1691,7 @@ Error OS_Windows::shell_show_in_file_manager(String p_path, bool p_open_folder) if (!p_path.is_quoted()) { p_path = p_path.quote(); } - p_path = p_path.replace("/", "\\"); + p_path = fix_path(p_path); INT_PTR ret = OK; if (open_folder) { @@ -1901,6 +2016,19 @@ String OS_Windows::get_system_ca_certificates() { OS_Windows::OS_Windows(HINSTANCE _hInstance) { hInstance = _hInstance; + // Reset CWD to ensure long path is used. + Char16String current_dir_name; + size_t str_len = GetCurrentDirectoryW(0, nullptr); + current_dir_name.resize(str_len + 1); + GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw()); + + Char16String new_current_dir_name; + str_len = GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), nullptr, 0); + new_current_dir_name.resize(str_len + 1); + GetLongPathNameW((LPCWSTR)current_dir_name.get_data(), (LPWSTR)new_current_dir_name.ptrw(), new_current_dir_name.size()); + + SetCurrentDirectoryW((LPCWSTR)new_current_dir_name.get_data()); + #ifndef WINDOWS_SUBSYSTEM_CONSOLE RedirectIOToConsole(); #endif diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index b6a21ed42d..9c7b98d7fd 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -172,6 +172,7 @@ public: virtual String get_version() const override; virtual Vector<String> get_video_adapter_driver_info() const override; + virtual bool get_user_prefers_integrated_gpu() const override; virtual void initialize_joypads() override {} |
