diff options
Diffstat (limited to 'platform')
47 files changed, 695 insertions, 384 deletions
diff --git a/platform/SCsub b/platform/SCsub index e432cebd48..ca282e3e68 100644 --- a/platform/SCsub +++ b/platform/SCsub @@ -1,5 +1,7 @@ #!/usr/bin/env python +import methods + Import("env") env.platform_sources = [] @@ -18,12 +20,7 @@ reg_apis_inc += "\n" reg_apis += "}\n\n" unreg_apis += "}\n" -# NOTE: It is safe to generate this file here, since this is still execute serially -with open("register_platform_apis.gen.cpp", "w", encoding="utf-8", newline="\n") as f: - f.write(reg_apis_inc) - f.write(reg_apis) - f.write(unreg_apis) - +methods.write_file_if_needed("register_platform_apis.gen.cpp", reg_apis_inc + reg_apis + unreg_apis) env.add_source_files(env.platform_sources, "register_platform_apis.gen.cpp") lib = env.add_library("platform", env.platform_sources) diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 90759810b1..c6f2f82117 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -71,6 +71,8 @@ bool DisplayServerAndroid::has_feature(Feature p_feature) const { case FEATURE_MOUSE: //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: + //case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_ICON: //case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml index 7fce5359ae..b49475b0f2 100644 --- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml +++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml @@ -595,6 +595,7 @@ Application version visible to the user. Falls back to [member ProjectSettings.application/config/version] if left empty. </member> <member name="xr_features/xr_mode" type="int" setter="" getter=""> + The extended reality (XR) mode for this application. </member> </members> </class> diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 894c13cc0b..19e215bfaf 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1920,7 +1920,15 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor bool advanced_options_enabled = p_preset->are_advanced_options_enabled(); if (p_option == "graphics/opengl_debug" || p_option == "command_line/extra_args" || - p_option == "permissions/custom_permissions") { + p_option == "permissions/custom_permissions" || + p_option == "gradle_build/compress_native_libraries" || + p_option == "package/retain_data_on_uninstall" || + p_option == "package/exclude_from_recents" || + p_option == "package/show_in_app_library" || + p_option == "package/show_as_launcher_app" || + p_option == "apk_expansion/enable" || + p_option == "apk_expansion/SALT" || + p_option == "apk_expansion/public_key") { return advanced_options_enabled; } if (p_option == "gradle_build/gradle_build_directory" || p_option == "gradle_build/android_source_template") { @@ -3096,7 +3104,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP return err; } if (user_data.libs.size() > 0) { - Ref<FileAccess> fa = FileAccess::open(GDEXTENSION_LIBS_PATH, FileAccess::WRITE); + Ref<FileAccess> fa = FileAccess::open(gdextension_libs_path, FileAccess::WRITE); fa->store_string(JSON::stringify(user_data.libs, "\t")); } } else { diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index f2c4a5d1b6..d27e75b07a 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -194,17 +194,17 @@ final String VALUE_SEPARATOR_REGEX = "\\|" // get the list of ABIs the project should be exported to ext.getExportEnabledABIs = { -> - String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : ""; + String enabledABIs = project.hasProperty("export_enabled_abis") ? project.property("export_enabled_abis") : "" if (enabledABIs == null || enabledABIs.isEmpty()) { enabledABIs = "armeabi-v7a|arm64-v8a|x86|x86_64|" } - Set<String> exportAbiFilter = []; + Set<String> exportAbiFilter = [] for (String abi_name : enabledABIs.split(VALUE_SEPARATOR_REGEX)) { if (!abi_name.trim().isEmpty()){ - exportAbiFilter.add(abi_name); + exportAbiFilter.add(abi_name) } } - return exportAbiFilter; + return exportAbiFilter } ext.getExportPath = { diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index 0f7ffeecae..c5ef086152 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -20,7 +20,7 @@ ext { String versionStatus = System.getenv("GODOT_VERSION_STATUS") if (versionStatus != null && !versionStatus.isEmpty()) { try { - buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", "")); + buildNumber = Integer.parseInt(versionStatus.replaceAll("[^0-9]", "")) } catch (NumberFormatException ignored) { buildNumber = 0 } 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 caf64bc933..c9a62d24b7 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 @@ -127,7 +127,7 @@ open class GodotEditor : GodotActivity() { */ protected open fun checkForProjectPermissionsToEnable() { // Check for RECORD_AUDIO permission - val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input")); + val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input")) if (audioInputEnabled) { PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this) } diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index ed967b9660..81ab598b90 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -11,6 +11,8 @@ apply from: "../scripts/publish-module.gradle" dependencies { implementation "androidx.fragment:fragment:$versions.fragmentVersion" + + testImplementation "junit:junit:4.13.2" } def pathToRootDir = "../../../../" @@ -74,6 +76,7 @@ android { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] + test.java.srcDirs = ['srcTest/java'] res.srcDirs = ['res'] aidl.srcDirs = ['aidl'] assets.srcDirs = ['assets'] @@ -118,7 +121,7 @@ android { case "dev": default: sconsTarget += "_debug" - break; + break } } 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 e2e77e7796..ce53aeebcb 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -56,6 +56,7 @@ import org.godotengine.godot.io.directory.DirectoryAccessHandler import org.godotengine.godot.io.file.FileAccessHandler import org.godotengine.godot.plugin.GodotPluginRegistry import org.godotengine.godot.tts.GodotTTS +import org.godotengine.godot.utils.CommandLineFileParser import org.godotengine.godot.utils.GodotNetUtils import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.PermissionsUtil.requestPermission @@ -68,7 +69,7 @@ import org.godotengine.godot.xr.XRMode import java.io.File import java.io.FileInputStream import java.io.InputStream -import java.nio.charset.StandardCharsets +import java.lang.Exception import java.security.MessageDigest import java.util.* @@ -84,6 +85,9 @@ class Godot(private val context: Context) : SensorEventListener { private val TAG = Godot::class.java.simpleName } + private val windowManager: WindowManager by lazy { + requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager + } private val pluginRegistry: GodotPluginRegistry by lazy { GodotPluginRegistry.getPluginRegistry() } @@ -120,6 +124,7 @@ class Godot(private val context: Context) : SensorEventListener { val directoryAccessHandler = DirectoryAccessHandler(context) val fileAccessHandler = FileAccessHandler(context) val netUtils = GodotNetUtils(context) + private val commandLineFileParser = CommandLineFileParser() /** * Tracks whether [onCreate] was completed successfully. @@ -150,7 +155,7 @@ class Godot(private val context: Context) : SensorEventListener { private var useApkExpansion = false private var useImmersive = false private var useDebugOpengl = false - private var darkMode = false; + private var darkMode = false private var containerLayout: FrameLayout? = null var renderView: GodotRenderView? = null @@ -290,7 +295,7 @@ class Godot(private val context: Context) : SensorEventListener { initializationStarted = false throw e } finally { - endBenchmarkMeasure("Startup", "Godot::onCreate"); + endBenchmarkMeasure("Startup", "Godot::onCreate") } } @@ -396,16 +401,19 @@ class Godot(private val context: Context) : SensorEventListener { } if (host == primaryHost) { - renderView!!.startRenderer() + renderView?.startRenderer() } - val view: View = renderView!!.view - containerLayout?.addView( - view, + + renderView?.let { + containerLayout?.addView( + it.view, ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) - ) + ) + } + editText.setView(renderView) io?.setEdit(editText) @@ -448,20 +456,23 @@ class Godot(private val context: Context) : SensorEventListener { }) } else { // Infer the virtual keyboard height using visible area. - view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { + renderView?.view?.viewTreeObserver?.addOnGlobalLayoutListener(object : OnGlobalLayoutListener { // Don't allocate a new Rect every time the callback is called. val visibleSize = Rect() override fun onGlobalLayout() { - val surfaceView = renderView!!.view - surfaceView.getWindowVisibleDisplayFrame(visibleSize) - val keyboardHeight = surfaceView.height - visibleSize.bottom - GodotLib.setVirtualKeyboardHeight(keyboardHeight) + renderView?.let { + val surfaceView = it.view + + surfaceView.getWindowVisibleDisplayFrame(visibleSize) + val keyboardHeight = surfaceView.height - visibleSize.bottom + GodotLib.setVirtualKeyboardHeight(keyboardHeight) + } } }) } if (host == primaryHost) { - renderView!!.queueOnRenderThread { + renderView?.queueOnRenderThread { for (plugin in pluginRegistry.allPlugins) { plugin.onRegisterPluginWithGodotNative() } @@ -495,7 +506,7 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView!!.onActivityStarted() + renderView?.onActivityStarted() } fun onResume(host: GodotHost) { @@ -503,7 +514,7 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView!!.onActivityResumed() + renderView?.onActivityResumed() if (mAccelerometer != null) { mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) } @@ -535,7 +546,7 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView!!.onActivityPaused() + renderView?.onActivityPaused() mSensorManager.unregisterListener(this) for (plugin in pluginRegistry.allPlugins) { plugin.onMainPause() @@ -547,7 +558,7 @@ class Godot(private val context: Context) : SensorEventListener { return } - renderView!!.onActivityStopped() + renderView?.onActivityStopped() } fun onDestroy(primaryHost: GodotHost) { @@ -569,7 +580,7 @@ class Godot(private val context: Context) : SensorEventListener { * Configuration change callback */ fun onConfigurationChanged(newConfig: Configuration) { - var newDarkMode = newConfig.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + val newDarkMode = newConfig.uiMode.and(Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES if (darkMode != newDarkMode) { darkMode = newDarkMode GodotLib.onNightModeChanged() @@ -613,7 +624,7 @@ class Godot(private val context: Context) : SensorEventListener { // These properties are defined after Godot setup completion, so we retrieve them here. val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")); + val rotaryInputAxis = java.lang.Integer.parseInt(GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis")) runOnUiThread { renderView?.inputHandler?.apply { @@ -686,9 +697,7 @@ class Godot(private val context: Context) : SensorEventListener { * This must be called after the render thread has started. */ fun runOnRenderThread(action: Runnable) { - if (renderView != null) { - renderView!!.queueOnRenderThread(action) - } + renderView?.queueOnRenderThread(action) } /** @@ -765,7 +774,7 @@ class Godot(private val context: Context) : SensorEventListener { return mClipboard.hasPrimaryClip() } - fun getClipboard(): String? { + fun getClipboard(): String { val clipData = mClipboard.primaryClip ?: return "" val text = clipData.getItemAt(0).text ?: return "" return text.toString() @@ -782,15 +791,14 @@ class Godot(private val context: Context) : SensorEventListener { @Keep private fun forceQuit(instanceId: Int): Boolean { - if (primaryHost == null) { - return false - } - return if (instanceId == 0) { - primaryHost!!.onGodotForceQuit(this) - true - } else { - primaryHost!!.onGodotForceQuit(instanceId) - } + primaryHost?.let { + if (instanceId == 0) { + it.onGodotForceQuit(this) + return true + } else { + return it.onGodotForceQuit(instanceId) + } + } ?: return false } fun onBackPressed(host: GodotHost) { @@ -804,20 +812,17 @@ class Godot(private val context: Context) : SensorEventListener { shouldQuit = false } } - if (shouldQuit && renderView != null) { - renderView!!.queueOnRenderThread { GodotLib.back() } + if (shouldQuit) { + renderView?.queueOnRenderThread { GodotLib.back() } } } private fun getRotatedValues(values: FloatArray?): FloatArray? { if (values == null || values.size != 3) { - return values + return null } - val display = - (requireActivity().getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay - val displayRotation = display.rotation val rotatedValues = FloatArray(3) - when (displayRotation) { + when (windowManager.defaultDisplay.rotation) { Surface.ROTATION_0 -> { rotatedValues[0] = values[0] rotatedValues[1] = values[1] @@ -846,37 +851,36 @@ class Godot(private val context: Context) : SensorEventListener { if (renderView == null) { return } + + val rotatedValues = getRotatedValues(event.values) + when (event.sensor.type) { Sensor.TYPE_ACCELEROMETER -> { - val rotatedValues = getRotatedValues(event.values) - renderView!!.queueOnRenderThread { - GodotLib.accelerometer( - -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] - ) + rotatedValues?.let { + renderView?.queueOnRenderThread { + GodotLib.accelerometer(-it[0], -it[1], -it[2]) + } } } Sensor.TYPE_GRAVITY -> { - val rotatedValues = getRotatedValues(event.values) - renderView!!.queueOnRenderThread { - GodotLib.gravity( - -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] - ) + rotatedValues?.let { + renderView?.queueOnRenderThread { + GodotLib.gravity(-it[0], -it[1], -it[2]) + } } } Sensor.TYPE_MAGNETIC_FIELD -> { - val rotatedValues = getRotatedValues(event.values) - renderView!!.queueOnRenderThread { - GodotLib.magnetometer( - -rotatedValues!![0], -rotatedValues[1], -rotatedValues[2] - ) + rotatedValues?.let { + renderView?.queueOnRenderThread { + GodotLib.magnetometer(-it[0], -it[1], -it[2]) + } } } Sensor.TYPE_GYROSCOPE -> { - val rotatedValues = getRotatedValues(event.values) - renderView!!.queueOnRenderThread { - GodotLib.gyroscope( - rotatedValues!![0], rotatedValues[1], rotatedValues[2] - ) + rotatedValues?.let { + renderView?.queueOnRenderThread { + GodotLib.gyroscope(it[0], it[1], it[2]) + } } } } @@ -908,47 +912,18 @@ class Godot(private val context: Context) : SensorEventListener { } private fun getCommandLine(): MutableList<String> { - val original: MutableList<String> = parseCommandLine() + val commandLine = try { + commandLineFileParser.parseCommandLine(requireActivity().assets.open("_cl_")) + } catch (ignored: Exception) { + mutableListOf() + } + val hostCommandLine = primaryHost?.commandLine if (!hostCommandLine.isNullOrEmpty()) { - original.addAll(hostCommandLine) + commandLine.addAll(hostCommandLine) } - return original - } - private fun parseCommandLine(): MutableList<String> { - val inputStream: InputStream - return try { - inputStream = requireActivity().assets.open("_cl_") - val len = ByteArray(4) - var r = inputStream.read(len) - if (r < 4) { - return mutableListOf() - } - val argc = - (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF) - val cmdline = ArrayList<String>(argc) - for (i in 0 until argc) { - r = inputStream.read(len) - if (r < 4) { - return mutableListOf() - } - val strlen = - (len[3].toInt() and 0xFF) shl 24 or ((len[2].toInt() and 0xFF) shl 16) or ((len[1].toInt() and 0xFF) shl 8) or (len[0].toInt() and 0xFF) - if (strlen > 65535) { - return mutableListOf() - } - val arg = ByteArray(strlen) - r = inputStream.read(arg) - if (r == strlen) { - cmdline.add(String(arg, StandardCharsets.UTF_8)) - } - } - cmdline - } catch (e: Exception) { - // The _cl_ file can be missing with no adverse effect - mutableListOf() - } + return commandLine } /** @@ -1039,7 +1014,7 @@ class Godot(private val context: Context) : SensorEventListener { @Keep private fun initInputDevices() { - renderView!!.initInputDevices() + renderView?.initInputDevices() } @Keep 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 e01c5481d5..7b8fad8952 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt @@ -83,8 +83,9 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { override fun onDestroy() { Log.v(TAG, "Destroying Godot app...") super.onDestroy() - if (godotFragment != null) { - terminateGodotInstance(godotFragment!!.godot) + + godotFragment?.let { + terminateGodotInstance(it.godot) } } @@ -93,22 +94,26 @@ abstract class GodotActivity : FragmentActivity(), GodotHost { } private fun terminateGodotInstance(instance: Godot) { - if (godotFragment != null && instance === godotFragment!!.godot) { - Log.v(TAG, "Force quitting Godot instance") - ProcessPhoenix.forceQuit(this) + godotFragment?.let { + if (instance === it.godot) { + Log.v(TAG, "Force quitting Godot instance") + ProcessPhoenix.forceQuit(this) + } } } override fun onGodotRestartRequested(instance: Godot) { runOnUiThread { - if (godotFragment != null && instance === godotFragment!!.godot) { - // It's very hard to properly de-initialize Godot on Android to restart the game - // from scratch. Therefore, we need to kill the whole app process and relaunch it. - // - // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including - // releasing and reloading native libs or resetting their state somehow and clearing static data). - Log.v(TAG, "Restarting Godot instance...") - ProcessPhoenix.triggerRebirth(this) + godotFragment?.let { + if (instance === it.godot) { + // It's very hard to properly de-initialize Godot on Android to restart the game + // from scratch. Therefore, we need to kill the whole app process and relaunch it. + // + // Restarting only the activity, wouldn't be enough unless it did proper cleanup (including + // releasing and reloading native libs or resetting their state somehow and clearing static data). + Log.v(TAG, "Restarting Godot instance...") + ProcessPhoenix.triggerRebirth(this) + } } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 984bf607d0..50741c1aab 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -56,7 +56,9 @@ class FileAccessHandler(val context: Context) { } return try { - DataAccess.fileExists(storageScope, context, path!!) + path?.let { + DataAccess.fileExists(storageScope, context, it) + } ?: false } catch (e: SecurityException) { false } @@ -69,20 +71,22 @@ class FileAccessHandler(val context: Context) { } return try { - DataAccess.removeFile(storageScope, context, path!!) + path?.let { + DataAccess.removeFile(storageScope, context, it) + } ?: false } catch (e: Exception) { false } } - internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String?, to: String?): Boolean { + internal fun renameFile(context: Context, storageScopeIdentifier: StorageScope.Identifier, from: String, to: String): Boolean { val storageScope = storageScopeIdentifier.identifyStorageScope(from) if (storageScope == StorageScope.UNKNOWN) { return false } return try { - DataAccess.renameFile(storageScope, context, from!!, to!!) + DataAccess.renameFile(storageScope, context, from, to) } catch (e: Exception) { false } @@ -106,16 +110,18 @@ class FileAccessHandler(val context: Context) { return INVALID_FILE_ID } - try { - val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID + return try { + path?.let { + val dataAccess = DataAccess.generateDataAccess(storageScope, context, it, accessFlag) ?: return INVALID_FILE_ID - files.put(++lastFileId, dataAccess) - return lastFileId + files.put(++lastFileId, dataAccess) + lastFileId + } ?: INVALID_FILE_ID } catch (e: FileNotFoundException) { - return FILE_NOT_FOUND_ERROR_ID + FILE_NOT_FOUND_ERROR_ID } catch (e: Exception) { Log.w(TAG, "Error while opening $path", e) - return INVALID_FILE_ID + INVALID_FILE_ID } } @@ -176,7 +182,9 @@ class FileAccessHandler(val context: Context) { } return try { - DataAccess.fileLastModified(storageScope, context, filepath!!) + filepath?.let { + DataAccess.fileLastModified(storageScope, context, it) + } ?: 0L } catch (e: SecurityException) { 0L } diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt new file mode 100644 index 0000000000..ce5c5b6714 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* CommandLineFileParser.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.godot.utils + +import java.io.InputStream +import java.nio.charset.StandardCharsets +import java.util.ArrayList + +/** + * A class that parses the content of file storing command line params. Usually, this file is saved + * in `assets/_cl_` on exporting an apk + * + * Returns a mutable list of command lines + */ +internal class CommandLineFileParser { + fun parseCommandLine(inputStream: InputStream): MutableList<String> { + return try { + val headerBytes = ByteArray(4) + var argBytes = inputStream.read(headerBytes) + if (argBytes < 4) { + return mutableListOf() + } + val argc = decodeHeaderIntValue(headerBytes) + + val cmdline = ArrayList<String>(argc) + for (i in 0 until argc) { + argBytes = inputStream.read(headerBytes) + if (argBytes < 4) { + return mutableListOf() + } + val strlen = decodeHeaderIntValue(headerBytes) + + if (strlen > 65535) { + return mutableListOf() + } + + val arg = ByteArray(strlen) + argBytes = inputStream.read(arg) + if (argBytes == strlen) { + cmdline.add(String(arg, StandardCharsets.UTF_8)) + } + } + cmdline + } catch (e: Exception) { + // The _cl_ file can be missing with no adverse effect + mutableListOf() + } + } + + private fun decodeHeaderIntValue(headerBytes: ByteArray): Int = + (headerBytes[3].toInt() and 0xFF) shl 24 or + ((headerBytes[2].toInt() and 0xFF) shl 16) or + ((headerBytes[1].toInt() and 0xFF) shl 8) or + (headerBytes[0].toInt() and 0xFF) +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java index 9a6b6d5037..9df890e6bd 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java @@ -293,15 +293,15 @@ public final class PermissionsUtil { /** * Returns the permissions defined in the AndroidManifest.xml file. * @param context the caller context for this method. - * @return manifest permissions list + * @return mutable copy of manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - public static List<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException { + public static ArrayList<String> getManifestPermissions(Context context) throws PackageManager.NameNotFoundException { PackageManager packageManager = context.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) - return Collections.emptyList(); - return Arrays.asList(packageInfo.requestedPermissions); + return new ArrayList<String>(); + return new ArrayList<>(Arrays.asList(packageInfo.requestedPermissions)); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt index 4aba0c370d..8c0065b31e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt @@ -142,7 +142,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk fun onSurfaceChanged(width: Int, height: Int) { lock.withLock { hasSurface = true - surfaceChanged = true; + surfaceChanged = true this.width = width this.height = height @@ -179,7 +179,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk // blocking the thread lifecycle by holding onto the lock. if (eventQueue.isNotEmpty()) { event = eventQueue.removeAt(0) - break; + break } if (readyToDraw) { @@ -199,7 +199,7 @@ internal class VkThread(private val vkSurfaceView: VkSurfaceView, private val vk } // Break out of the loop so drawing can occur without holding onto the lock. - break; + break } else if (rendererResumed) { // If we aren't ready to draw but are resumed, that means we either lost a surface // or the app was paused. diff --git a/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt new file mode 100644 index 0000000000..8b0466848a --- /dev/null +++ b/platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt @@ -0,0 +1,104 @@ +/**************************************************************************/ +/* CommandLineFileParserTest.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.godot.utils + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.ByteArrayInputStream +import java.io.InputStream + +// Godot saves command line params in the `assets/_cl_` file on exporting an apk. By default, +// without any other commands specified in `command_line/extra_args` in Export window, the content +// of that _cl_ file consists of only the `--xr_mode_regular` and `--use_immersive` flags. +// The `CL_` prefix here refers to that file +private val CL_DEFAULT_NO_EXTRA_ARGS = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101) +private val CL_ONE_EXTRA_ARG = byteArrayOf(3, 0, 0, 0, 15, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101) +private val CL_TWO_EXTRA_ARGS = byteArrayOf(4, 0, 0, 0, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 49, 16, 0, 0, 0, 45, 45, 117, 110, 105, 116, 95, 116, 101, 115, 116, 95, 97, 114, 103, 50, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101) +private val CL_EMPTY = byteArrayOf() +private val CL_HEADER_TOO_SHORT = byteArrayOf(0, 0, 0) +private val CL_INCOMPLETE_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0) +private val CL_LENGTH_TOO_LONG_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 114, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101) +private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG = byteArrayOf(2, 0, 0, 0, 10, 0, 0, 0, 45, 45, 120, 114) +private val CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG = byteArrayOf(2, 0, 0, 0, 17, 0, 0, 0, 45, 45, 120, 114, 95, 109, 111, 100, 101, 95, 114, 101, 103, 117, 108, 97, 15, 0, 0, 0, 45, 45, 117, 115, 101, 95, 105, 109, 109, 101, 114, 115, 105, 118, 101) + +@RunWith(Parameterized::class) +class CommandLineFileParserTest( + private val inputStreamArg: InputStream, + private val expectedResult: List<String>, +) { + + private val commandLineFileParser = CommandLineFileParser() + + companion object { + @JvmStatic + @Parameterized.Parameters + fun data() = listOf( + arrayOf(ByteArrayInputStream(CL_EMPTY), listOf<String>()), + arrayOf(ByteArrayInputStream(CL_HEADER_TOO_SHORT), listOf<String>()), + + arrayOf(ByteArrayInputStream(CL_DEFAULT_NO_EXTRA_ARGS), listOf( + "--xr_mode_regular", + "--use_immersive", + )), + + arrayOf(ByteArrayInputStream(CL_ONE_EXTRA_ARG), listOf( + "--unit_test_arg", + "--xr_mode_regular", + "--use_immersive", + )), + + arrayOf(ByteArrayInputStream(CL_TWO_EXTRA_ARGS), listOf( + "--unit_test_arg1", + "--unit_test_arg2", + "--xr_mode_regular", + "--use_immersive", + )), + + arrayOf(ByteArrayInputStream(CL_INCOMPLETE_FIRST_ARG), listOf<String>()), + arrayOf(ByteArrayInputStream(CL_LENGTH_TOO_LONG_IN_FIRST_ARG), listOf<String>()), + arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_ONE_ARG), listOf<String>()), + arrayOf(ByteArrayInputStream(CL_MISMATCHED_ARG_LENGTH_AND_HEADER_IN_FIRST_ARG), listOf<String>()), + ) + } + + @Test + fun `Given inputStream, When parsing command line, Then a correct list is returned`() { + // given + val inputStream = inputStreamArg + + // when + val result = commandLineFileParser.parseCommandLine(inputStream) + + // then + assert(result == expectedResult) { "Expected: $expectedResult Actual: $result" } + } +} diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 85d5cf2796..2215f706c5 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -250,7 +250,7 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, } if (step.get() == 1) { - if (!Main::start()) { + if (Main::start() != EXIT_SUCCESS) { return true; // should exit instead and print the error } diff --git a/platform/ios/app_delegate.mm b/platform/ios/app_delegate.mm index 5a0c57be93..37d2696434 100644 --- a/platform/ios/app_delegate.mm +++ b/platform/ios/app_delegate.mm @@ -116,6 +116,8 @@ static ViewController *mainViewController = nil; } else if (sessionCategorySetting == SESSION_CATEGORY_PLAY_AND_RECORD) { category = AVAudioSessionCategoryPlayAndRecord; options |= AVAudioSessionCategoryOptionDefaultToSpeaker; + options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; + options |= AVAudioSessionCategoryOptionAllowAirPlay; } else if (sessionCategorySetting == SESSION_CATEGORY_PLAYBACK) { category = AVAudioSessionCategoryPlayback; } else if (sessionCategorySetting == SESSION_CATEGORY_RECORD) { diff --git a/platform/ios/detect.py b/platform/ios/detect.py index cd303295ad..0c9b7b3204 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -140,7 +140,6 @@ def configure(env: "SConsEnvironment"): ) ) env.Append(ASFLAGS=["-arch", "arm64"]) - env.Append(CPPDEFINES=["NEED_LONG_INT"]) # Temp fix for ABS/MAX/MIN macros in iOS SDK blocking compilation env.Append(CCFLAGS=["-Wno-ambiguous-macro"]) diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index e1c3dcd372..f84fb01ad0 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -328,6 +328,8 @@ bool DisplayServerIOS::has_feature(Feature p_feature) const { // case FEATURE_MOUSE: // case FEATURE_MOUSE_WARP: // case FEATURE_NATIVE_DIALOG: + // case FEATURE_NATIVE_DIALOG_INPUT: + // case FEATURE_NATIVE_DIALOG_FILE: // case FEATURE_NATIVE_ICON: // case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_CLIPBOARD: diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 35ef6d6a78..0c0ded5fea 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -12,7 +12,7 @@ <members> <member name="application/additional_plist_content" type="String" setter="" getter=""> Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.: - [codeblock] + [codeblock lang=text] <key>key_name</key> <string>value</string> [/codeblock] diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index 82b4f6f904..dff4f844c7 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -1316,7 +1316,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const Ref<EditorExportP if (asset.begins_with("res://")) { Error err = _copy_asset(p_preset, p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); - } else if (ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) { + } else if (asset.is_absolute_path() && ProjectSettings::get_singleton()->localize_path(asset).begins_with("res://")) { Error err = _copy_asset(p_preset, p_out_dir, ProjectSettings::get_singleton()->localize_path(asset), nullptr, p_is_framework, p_should_embed, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); } else { diff --git a/platform/ios/godot_ios.mm b/platform/ios/godot_ios.mm index 5e66c8b47b..9d35d43344 100644 --- a/platform/ios/godot_ios.mm +++ b/platform/ios/godot_ios.mm @@ -102,15 +102,16 @@ int ios_main(int argc, char **argv) { Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false); - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return 0; - } else if (err != OK) { - return 255; + if (err != OK) { + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return EXIT_SUCCESS; + } + return EXIT_FAILURE; } os->initialize_modules(); - return 0; + return os->get_exit_code(); } void ios_finish() { diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index cdebed58b2..214725832f 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -367,6 +367,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo } ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED); + ERR_FAIL_NULL_V(monitor_connection, FAILED); Vector<String> filter_names; Vector<String> filter_exts; @@ -406,24 +407,16 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo Error rng_err = rng.get_random_bytes(uuid, 64); ERR_FAIL_COND_V_MSG(rng_err, rng_err, "Failed to generate unique token."); - fd.connection = dbus_bus_get(DBUS_BUS_SESSION, &err); - if (dbus_error_is_set(&err)) { - ERR_PRINT(vformat("Failed to open DBus connection: %s", err.message)); - dbus_error_free(&err); - unsupported = true; - return FAILED; - } - - String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(fd.connection)); + String dbus_unique_name = String::utf8(dbus_bus_get_unique_name(monitor_connection)); String token = String::hex_encode_buffer(uuid, 64); String path = vformat("/org/freedesktop/portal/desktop/request/%s/%s", dbus_unique_name.replace(".", "_").replace(":", ""), token); - fd.path = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name); - dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + fd.path = path; + fd.filter = vformat("type='signal',sender='org.freedesktop.portal.Desktop',path='%s',interface='org.freedesktop.portal.Request',member='Response',destination='%s'", path, dbus_unique_name); + dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err); if (dbus_error_is_set(&err)) { ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); dbus_error_free(&err); - dbus_connection_unref(fd.connection); return FAILED; } @@ -460,14 +453,13 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo dbus_message_iter_close_container(&iter, &arr_iter); } - DBusMessage *reply = dbus_connection_send_with_reply_and_block(fd.connection, message, DBUS_TIMEOUT_INFINITE, &err); + DBusMessage *reply = dbus_connection_send_with_reply_and_block(monitor_connection, message, DBUS_TIMEOUT_INFINITE, &err); dbus_message_unref(message); if (!reply || dbus_error_is_set(&err)) { ERR_PRINT(vformat("Failed to send DBus message: %s", err.message)); dbus_error_free(&err); - dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); - dbus_connection_unref(fd.connection); + dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err); return FAILED; } @@ -479,19 +471,17 @@ Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_windo const char *new_path = nullptr; dbus_message_iter_get_basic(&iter, &new_path); if (String::utf8(new_path) != path) { - dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err); if (dbus_error_is_set(&err)) { ERR_PRINT(vformat("Failed to remove DBus match: %s", err.message)); dbus_error_free(&err); - dbus_connection_unref(fd.connection); return FAILED; } - fd.path = String::utf8(new_path); - dbus_bus_add_match(fd.connection, fd.path.utf8().get_data(), &err); + fd.filter = String::utf8(new_path); + dbus_bus_add_match(monitor_connection, fd.filter.utf8().get_data(), &err); if (dbus_error_is_set(&err)) { ERR_PRINT(vformat("Failed to add DBus match: %s", err.message)); dbus_error_free(&err); - dbus_connection_unref(fd.connection); return FAILED; } } @@ -532,57 +522,9 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) { FreeDesktopPortalDesktop *portal = (FreeDesktopPortalDesktop *)p_ud; while (!portal->monitor_thread_abort.is_set()) { - { - MutexLock lock(portal->file_dialog_mutex); - for (int i = portal->file_dialogs.size() - 1; i >= 0; i--) { - bool remove = false; - { - FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; - if (fd.connection) { - while (true) { - DBusMessage *msg = dbus_connection_pop_message(fd.connection); - if (!msg) { - break; - } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { - DBusMessageIter iter; - if (dbus_message_iter_init(msg, &iter)) { - bool cancel = false; - Vector<String> uris; - Dictionary options; - int index = 0; - file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options); - - if (fd.callback.is_valid()) { - callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb); - } - if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { - callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); - } - } - dbus_message_unref(msg); - - DBusError err; - dbus_error_init(&err); - dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); - dbus_error_free(&err); - dbus_connection_unref(fd.connection); - remove = true; - break; - } - dbus_message_unref(msg); - } - dbus_connection_read_write(fd.connection, 0); - } - } - if (remove) { - portal->file_dialogs.remove_at(i); - } - } - } - - if (portal->theme_connection) { + if (portal->monitor_connection) { while (true) { - DBusMessage *msg = dbus_connection_pop_message(portal->theme_connection); + DBusMessage *msg = dbus_connection_pop_message(portal->monitor_connection); if (!msg) { break; } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Settings", "SettingChanged")) { @@ -599,12 +541,41 @@ void FreeDesktopPortalDesktop::_thread_monitor(void *p_ud) { callable_mp(portal, &FreeDesktopPortalDesktop::_system_theme_changed_callback).call_deferred(); } } - dbus_message_unref(msg); - break; + } else if (dbus_message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) { + String path = String::utf8(dbus_message_get_path(msg)); + MutexLock lock(portal->file_dialog_mutex); + for (int i = 0; i < portal->file_dialogs.size(); i++) { + FreeDesktopPortalDesktop::FileDialogData &fd = portal->file_dialogs.write[i]; + if (fd.path == path) { + DBusMessageIter iter; + if (dbus_message_iter_init(msg, &iter)) { + bool cancel = false; + Vector<String> uris; + Dictionary options; + int index = 0; + file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index, options); + + if (fd.callback.is_valid()) { + callable_mp(portal, &FreeDesktopPortalDesktop::_file_dialog_callback).call_deferred(fd.callback, !cancel, uris, index, options, fd.opt_in_cb); + } + if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) { + callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus); + } + } + + DBusError err; + dbus_error_init(&err); + dbus_bus_remove_match(portal->monitor_connection, fd.filter.utf8().get_data(), &err); + dbus_error_free(&err); + + portal->file_dialogs.remove_at(i); + break; + } + } } dbus_message_unref(msg); } - dbus_connection_read_write(portal->theme_connection, 0); + dbus_connection_read_write(portal->monitor_connection, 0); } usleep(50000); @@ -647,18 +618,18 @@ FreeDesktopPortalDesktop::FreeDesktopPortalDesktop() { DBusError err; dbus_error_init(&err); - theme_connection = dbus_bus_get(DBUS_BUS_SESSION, &err); + monitor_connection = dbus_bus_get(DBUS_BUS_SESSION, &err); if (dbus_error_is_set(&err)) { dbus_error_free(&err); } else { theme_path = "type='signal',sender='org.freedesktop.portal.Desktop',interface='org.freedesktop.portal.Settings',member='SettingChanged'"; - dbus_bus_add_match(theme_connection, theme_path.utf8().get_data(), &err); + dbus_bus_add_match(monitor_connection, theme_path.utf8().get_data(), &err); if (dbus_error_is_set(&err)) { dbus_error_free(&err); - dbus_connection_unref(theme_connection); - theme_connection = nullptr; + dbus_connection_unref(monitor_connection); + monitor_connection = nullptr; } - dbus_connection_read_write(theme_connection, 0); + dbus_connection_read_write(monitor_connection, 0); } if (!unsupported) { @@ -673,21 +644,17 @@ FreeDesktopPortalDesktop::~FreeDesktopPortalDesktop() { monitor_thread.wait_to_finish(); } - for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) { - if (fd.connection) { - DBusError err; + if (monitor_connection) { + DBusError err; + for (FreeDesktopPortalDesktop::FileDialogData &fd : file_dialogs) { dbus_error_init(&err); - dbus_bus_remove_match(fd.connection, fd.path.utf8().get_data(), &err); + dbus_bus_remove_match(monitor_connection, fd.filter.utf8().get_data(), &err); dbus_error_free(&err); - dbus_connection_unref(fd.connection); } - } - if (theme_connection) { - DBusError err; dbus_error_init(&err); - dbus_bus_remove_match(theme_connection, theme_path.utf8().get_data(), &err); + dbus_bus_remove_match(monitor_connection, theme_path.utf8().get_data(), &err); dbus_error_free(&err); - dbus_connection_unref(theme_connection); + dbus_connection_unref(monitor_connection); } } diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h index 75afe02a26..ba9c352e62 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.h +++ b/platform/linuxbsd/freedesktop_portal_desktop.h @@ -60,9 +60,9 @@ private: struct FileDialogData { Vector<String> filter_names; - DBusConnection *connection = nullptr; DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID; Callable callback; + String filter; String path; bool opt_in_cb = false; }; @@ -71,8 +71,8 @@ private: Vector<FileDialogData> file_dialogs; Thread monitor_thread; SafeFlag monitor_thread_abort; + DBusConnection *monitor_connection = nullptr; - DBusConnection *theme_connection = nullptr; String theme_path; Callable system_theme_changed; void _system_theme_changed_callback(); diff --git a/platform/linuxbsd/godot_linuxbsd.cpp b/platform/linuxbsd/godot_linuxbsd.cpp index a2b6fbeb25..b0880c86b8 100644 --- a/platform/linuxbsd/godot_linuxbsd.cpp +++ b/platform/linuxbsd/godot_linuxbsd.cpp @@ -72,18 +72,19 @@ int main(int argc, char *argv[]) { char *ret = getcwd(cwd, PATH_MAX); Error err = Main::setup(argv[0], argc - 1, &argv[1]); + if (err != OK) { free(cwd); - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return 0; + return EXIT_SUCCESS; } - return 255; + return EXIT_FAILURE; } - if (Main::start()) { - os.set_exit_code(EXIT_SUCCESS); - os.run(); // it is actually the OS that decides how to run + if (Main::start() == EXIT_SUCCESS) { + os.run(); + } else { + os.set_exit_code(EXIT_FAILURE); } Main::cleanup(); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index f29275c910..68b4cd7f5a 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -62,6 +62,10 @@ #include <mntent.h> #endif +#if defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif + void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { const char *message_programs[] = { "zenity", "kdialog", "Xdialog", "xmessage" }; @@ -145,17 +149,36 @@ void OS_LinuxBSD::initialize_joypads() { String OS_LinuxBSD::get_unique_id() const { static String machine_id; if (machine_id.is_empty()) { +#if defined(__FreeBSD__) + const int mib[2] = { CTL_KERN, KERN_HOSTUUID }; + char buf[4096]; + memset(buf, 0, sizeof(buf)); + size_t len = sizeof(buf) - 1; + if (sysctl(mib, 2, buf, &len, 0x0, 0) != -1) { + machine_id = String::utf8(buf).replace("-", ""); + } +#else Ref<FileAccess> f = FileAccess::open("/etc/machine-id", FileAccess::READ); if (f.is_valid()) { while (machine_id.is_empty() && !f->eof_reached()) { machine_id = f->get_line().strip_edges(); } } +#endif } return machine_id; } String OS_LinuxBSD::get_processor_name() const { +#if defined(__FreeBSD__) + const int mib[2] = { CTL_HW, HW_MODEL }; + char buf[4096]; + memset(buf, 0, sizeof(buf)); + size_t len = sizeof(buf) - 1; + if (sysctl(mib, 2, buf, &len, 0x0, 0) != -1) { + return String::utf8(buf); + } +#else Ref<FileAccess> f = FileAccess::open("/proc/cpuinfo", FileAccess::READ); ERR_FAIL_COND_V_MSG(f.is_null(), "", String("Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string.")); @@ -165,8 +188,9 @@ String OS_LinuxBSD::get_processor_name() const { return line.split(":")[1].strip_edges(); } } +#endif - ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string.")); + ERR_FAIL_V_MSG("", String("Couldn't get the CPU model. Returning an empty string.")); } bool OS_LinuxBSD::is_sandboxed() const { diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 80b6029c9d..d00d5deb2c 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -210,8 +210,10 @@ bool DisplayServerWayland::has_feature(Feature p_feature) const { return true; } break; + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: #ifdef DBUS_ENABLED - case FEATURE_NATIVE_DIALOG: { + case FEATURE_NATIVE_DIALOG_FILE: { return true; } break; #endif diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index 5040f5dd45..7f9008e952 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -1563,7 +1563,7 @@ void WaylandThread::_wl_pointer_on_frame(void *data, struct wl_pointer *wl_point } if (old_pd.pressed_button_mask != pd.pressed_button_mask) { - BitField<MouseButtonMask> pressed_mask_delta = BitField<MouseButtonMask>((uint32_t)old_pd.pressed_button_mask ^ (uint32_t)pd.pressed_button_mask); + BitField<MouseButtonMask> pressed_mask_delta = old_pd.pressed_button_mask ^ pd.pressed_button_mask; const MouseButton buttons_to_test[] = { MouseButton::LEFT, @@ -2454,7 +2454,7 @@ void WaylandThread::_wp_tablet_tool_on_frame(void *data, struct zwp_tablet_tool_ mm->set_relative_screen_position(mm->get_relative()); Vector2i pos_delta = td.position - old_td.position; - uint32_t time_delta = td.motion_time - td.motion_time; + uint32_t time_delta = td.motion_time - old_td.motion_time; mm->set_velocity((Vector2)pos_delta / time_delta); Ref<InputEventMessage> inputev_msg; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 34aa15abbb..9069dd423f 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -128,8 +128,10 @@ bool DisplayServerX11::has_feature(Feature p_feature) const { //case FEATURE_HIDPI: case FEATURE_ICON: #ifdef DBUS_ENABLED - case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_FILE: #endif + //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: //case FEATURE_NATIVE_ICON: case FEATURE_SWAP_BUFFERS: #ifdef DBUS_ENABLED @@ -1995,8 +1997,7 @@ void DisplayServerX11::window_set_current_screen(int p_screen, WindowID p_window Size2i wsize = window_get_size(p_window); wpos += srect.position; if (srect != Rect2i()) { - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3); } window_set_position(wpos, p_window); } @@ -2224,8 +2225,7 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); Size2i size = p_size; - size.x = MAX(1, size.x); - size.y = MAX(1, size.y); + size = size.max(Size2i(1, 1)); WindowData &wd = windows[p_window]; @@ -5212,7 +5212,7 @@ void DisplayServerX11::set_icon(const Ref<Image> &p_icon) { if (g_set_icon_error) { g_set_icon_error = false; - WARN_PRINT("Icon too large, attempting to resize icon."); + WARN_PRINT(vformat("Icon too large (%dx%d), attempting to downscale icon.", w, h)); int new_width, new_height; if (w > h) { @@ -5451,8 +5451,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V } else { Rect2i srect = screen_get_usable_rect(rq_screen); Point2i wpos = p_rect.position; - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); win_rect.position = wpos; } diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 09073a8030..7c9d073afc 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -241,6 +241,8 @@ public: NSImage *_convert_to_nsimg(Ref<Image> &p_image) const; Point2i _get_screens_origin() const; + void set_menu_delegate(NSMenu *p_menu); + void send_event(NSEvent *p_event); void send_window_event(const WindowData &p_wd, WindowEvent p_event); void release_pressed_events(); diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index c52ed00b3d..b7a9fb1bbd 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -83,8 +83,7 @@ DisplayServerMacOS::WindowID DisplayServerMacOS::_create_window(WindowMode p_mod Rect2i srect = screen_get_usable_rect(rq_screen); Point2i wpos = p_rect.position; if (srect != Rect2i()) { - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); } // macOS native y-coordinate relative to _get_screens_origin() is negative, // Godot passes a positive value. @@ -291,6 +290,10 @@ void DisplayServerMacOS::_update_displays_arrangement() { displays_arrangement_dirty = false; } +void DisplayServerMacOS::set_menu_delegate(NSMenu *p_menu) { + [p_menu setDelegate:menu_delegate]; +} + Point2i DisplayServerMacOS::_get_screens_origin() const { // Returns the native top-left screen coordinate of the smallest rectangle // that encompasses all screens. Needed in get_screen_position(), @@ -756,6 +759,8 @@ bool DisplayServerMacOS::has_feature(Feature p_feature) const { case FEATURE_CURSOR_SHAPE: case FEATURE_CUSTOM_CURSOR_SHAPE: case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_INPUT: + case FEATURE_NATIVE_DIALOG_FILE: case FEATURE_IME: case FEATURE_WINDOW_TRANSPARENCY: case FEATURE_HIDPI: @@ -1877,8 +1882,7 @@ void DisplayServerMacOS::window_set_current_screen(int p_screen, WindowID p_wind Size2i wsize = window_get_size(p_window); wpos += srect.position; - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3); window_set_position(wpos, p_window); if (was_fullscreen) { @@ -1991,7 +1995,7 @@ void DisplayServerMacOS::window_set_position(const Point2i &p_position, WindowID ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (NSEqualRects([wd.window_object frame], [[wd.window_object screen] visibleFrame])) { + if (wd.fullscreen) { return; } @@ -2080,12 +2084,21 @@ Size2i DisplayServerMacOS::window_get_max_size(WindowID p_window) const { } void DisplayServerMacOS::update_presentation_mode() { + bool has_fs_windows = false; for (const KeyValue<WindowID, WindowData> &wd : windows) { - if (wd.value.fullscreen && wd.value.exclusive_fullscreen) { - return; + if (wd.value.fullscreen) { + if (wd.value.exclusive_fullscreen) { + return; + } else { + has_fs_windows = true; + } } } - [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + if (has_fs_windows) { + [NSApp setPresentationOptions:NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock | NSApplicationPresentationFullScreen]; + } else { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + } } void DisplayServerMacOS::window_set_min_size(const Size2i p_size, WindowID p_window) { @@ -2309,8 +2322,7 @@ void DisplayServerMacOS::window_set_window_buttons_offset(const Vector2i &p_offs WindowData &wd = windows[p_window]; float scale = screen_get_max_scale(); wd.wb_offset = p_offset / scale; - wd.wb_offset.x = MAX(wd.wb_offset.x, 12); - wd.wb_offset.y = MAX(wd.wb_offset.y, 12); + wd.wb_offset = wd.wb_offset.max(Vector2i(12, 12)); if (wd.window_button_view) { [wd.window_button_view setOffset:NSMakePoint(wd.wb_offset.x, wd.wb_offset.y)]; } diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 506b0dffb8..7355042a48 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -12,7 +12,7 @@ <members> <member name="application/additional_plist_content" type="String" setter="" getter=""> Additional data added to the root [code]<dict>[/code] section of the [url=https://developer.apple.com/documentation/bundleresources/information_property_list]Info.plist[/url] file. The value should be an XML section with pairs of key-value elements, e.g.: - [codeblock] + [codeblock lang=text] <key>key_name</key> <string>value</string> [/codeblock] diff --git a/platform/macos/godot_main_macos.mm b/platform/macos/godot_main_macos.mm index 3959fb686c..942c351ac0 100644 --- a/platform/macos/godot_main_macos.mm +++ b/platform/macos/godot_main_macos.mm @@ -69,18 +69,21 @@ int main(int argc, char **argv) { err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]); } - if (err == ERR_HELP) { // Returned by --help and --version, so success. - return 0; - } else if (err != OK) { - return 255; + if (err != OK) { + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return EXIT_SUCCESS; + } + return EXIT_FAILURE; } - bool ok; + int ret; @autoreleasepool { - ok = Main::start(); + ret = Main::start(); } - if (ok) { - os.run(); // It is actually the OS that decides how to run. + if (ret == EXIT_SUCCESS) { + os.run(); + } else { + os.set_exit_code(EXIT_FAILURE); } @autoreleasepool { diff --git a/platform/macos/native_menu_macos.h b/platform/macos/native_menu_macos.h index c00a510fd5..1d9feb64a7 100644 --- a/platform/macos/native_menu_macos.h +++ b/platform/macos/native_menu_macos.h @@ -33,7 +33,7 @@ #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "servers/native_menu.h" +#include "servers/display/native_menu.h" #import <AppKit/AppKit.h> #import <ApplicationServices/ApplicationServices.h> diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index 250b64dc04..8c2dd98862 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -223,6 +223,11 @@ RID NativeMenuMacOS::get_system_menu(SystemMenus p_menu_id) const { RID NativeMenuMacOS::create_menu() { MenuData *md = memnew(MenuData); md->menu = [[NSMenu alloc] initWithTitle:@""]; + [md->menu setAutoenablesItems:NO]; + DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton(); + if (ds) { + ds->set_menu_delegate(md->menu); + } RID rid = menus.make_rid(md); menu_lookup[md->menu] = rid; return rid; diff --git a/platform/web/detect.py b/platform/web/detect.py index e692c79a20..2d2cc288a1 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -86,7 +86,7 @@ def configure(env: "SConsEnvironment"): supported_arches = ["wasm32"] if env["arch"] not in supported_arches: print( - 'Unsupported CPU architecture "%s" for iOS. Supported architectures are: %s.' + 'Unsupported CPU architecture "%s" for Web. Supported architectures are: %s.' % (env["arch"], ", ".join(supported_arches)) ) sys.exit() diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index 281f312000..06f5eb82f7 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -1128,6 +1128,8 @@ bool DisplayServerWeb::has_feature(Feature p_feature) const { return true; //case FEATURE_MOUSE_WARP: //case FEATURE_NATIVE_DIALOG: + //case FEATURE_NATIVE_DIALOG_INPUT: + //case FEATURE_NATIVE_DIALOG_FILE: //case FEATURE_NATIVE_ICON: //case FEATURE_WINDOW_TRANSPARENCY: //case FEATURE_KEEP_SCREEN_ON: diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index 45671ca491..4b93ce9e75 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -105,6 +105,10 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str return create_process(p_path, p_arguments); } +Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { + ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform."); +} + Error OS_Web::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) { Array args; for (const String &E : p_arguments) { diff --git a/platform/web/os_web.h b/platform/web/os_web.h index e578c93925..16f8ea8550 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -80,6 +80,7 @@ public: bool main_loop_iterate(); Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; Error kill(const ProcessID &p_pid) override; int get_process_id() const override; diff --git a/platform/web/web_main.cpp b/platform/web/web_main.cpp index ad2a801881..04513f6d57 100644 --- a/platform/web/web_main.cpp +++ b/platform/web/web_main.cpp @@ -109,24 +109,22 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) { // Proper shutdown in case of setup failure. if (err != OK) { - int exit_code = (int)err; - if (err == ERR_HELP) { - exit_code = 0; // Called with --help. - } - os->set_exit_code(exit_code); // Will only exit after sync. emscripten_set_main_loop(exit_callback, -1, false); godot_js_os_finish_async(cleanup_after_sync); - return exit_code; + if (err == ERR_HELP) { // Returned by --help and --version, so success. + return EXIT_SUCCESS; + } + return EXIT_FAILURE; } - os->set_exit_code(0); main_started = true; // Ease up compatibility. ResourceLoader::set_abort_on_missing_resources(false); - Main::start(); + int ret = Main::start(); + os->set_exit_code(ret); os->get_main_loop()->initialize(); #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_project_manager_hint() && FileAccess::exists("/tmp/preload.zip")) { @@ -140,5 +138,5 @@ extern EMSCRIPTEN_KEEPALIVE int godot_web_main(int argc, char *argv[]) { // We are inside an animation frame, we want to immediately draw on the newly setup canvas. main_loop_callback(); - return 0; + return os->get_exit_code(); } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 074d40bf4b..74665664b1 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -114,6 +114,8 @@ bool DisplayServerWindows::has_feature(Feature p_feature) const { case FEATURE_ICON: case FEATURE_NATIVE_ICON: case FEATURE_NATIVE_DIALOG: + case FEATURE_NATIVE_DIALOG_INPUT: + case FEATURE_NATIVE_DIALOG_FILE: case FEATURE_SWAP_BUFFERS: case FEATURE_KEEP_SCREEN_ON: case FEATURE_TEXT_TO_SPEECH: @@ -903,8 +905,7 @@ static BOOL CALLBACK _MonitorEnumProcPos(HMONITOR hMonitor, HDC hdcMonitor, LPRE static BOOL CALLBACK _MonitorEnumProcOrigin(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { EnumPosData *data = (EnumPosData *)dwData; - data->pos.x = MIN(data->pos.x, lprcMonitor->left); - data->pos.y = MIN(data->pos.y, lprcMonitor->top); + data->pos = data->pos.min(Point2(lprcMonitor->left, lprcMonitor->top)); return TRUE; } @@ -1637,8 +1638,7 @@ void DisplayServerWindows::window_set_current_screen(int p_screen, WindowID p_wi Size2i wsize = window_get_size(p_window); wpos += srect.position; - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - wsize.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - wsize.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - wsize / 3); window_set_position(wpos, p_window); } } @@ -1895,7 +1895,7 @@ Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) return Size2(); } -void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { +void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) { // Windows docs for window styles: // https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles // https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles @@ -1909,7 +1909,17 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre if (p_fullscreen || p_borderless) { r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past. - if ((p_fullscreen && p_multiwindow_fs) || p_maximized) { + if (p_maximized) { + r_style |= WS_MAXIMIZE; + } + if (!p_fullscreen) { + r_style |= WS_SYSMENU | WS_MINIMIZEBOX; + + if (p_resizable) { + r_style |= WS_MAXIMIZEBOX; + } + } + if ((p_fullscreen && p_multiwindow_fs) || p_maximized_fs) { r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen. } } else { @@ -1945,7 +1955,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain DWORD style = 0; DWORD style_ex = 0; - _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus || wd.is_popup, style, style_ex); + _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.maximized_fs, wd.no_focus || wd.is_popup, style, style_ex); SetWindowLongPtr(wd.hWnd, GWL_STYLE, style); SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex); @@ -1988,6 +1998,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) wd.pre_fs_valid = true; } + ShowWindow(wd.hWnd, SW_RESTORE); MoveWindow(wd.hWnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, TRUE); if (restore_mouse_trails > 1) { @@ -2023,7 +2034,7 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) } if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) { - if (wd.minimized) { + if (wd.minimized || wd.maximized) { ShowWindow(wd.hWnd, SW_RESTORE); } wd.was_maximized = wd.maximized; @@ -3709,63 +3720,18 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA return MA_NOACTIVATE; // Do not activate, but process mouse messages. } } break; - case WM_SETFOCUS: { - windows[window_id].window_has_focus = true; - last_focused_window = window_id; - - // Restore mouse mode. - _set_mouse_mode_impl(mouse_mode); - - if (!app_focused) { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); - } - app_focused = true; - } - } break; - case WM_KILLFOCUS: { - windows[window_id].window_has_focus = false; - last_focused_window = window_id; - - // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. - ReleaseCapture(); - - // Release every touch to avoid sticky points. - for (const KeyValue<int, Vector2> &E : touch_state) { - _touch_event(window_id, false, E.value.x, E.value.y, E.key); - } - touch_state.clear(); - - bool self_steal = false; - HWND new_hwnd = (HWND)wParam; - if (IsWindow(new_hwnd)) { - self_steal = true; + case WM_ACTIVATEAPP: { + bool new_app_focused = (bool)wParam; + if (new_app_focused == app_focused) { + break; } - - if (!self_steal) { - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); - } - app_focused = false; + app_focused = new_app_focused; + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(app_focused ? MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN : MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); } } break; - case WM_ACTIVATE: { // Watch for window activate message. - if (!windows[window_id].window_focused) { - _process_activate_event(window_id, wParam, lParam); - } else { - windows[window_id].saved_wparam = wParam; - windows[window_id].saved_lparam = lParam; - - // Run a timer to prevent event catching warning if the focused window is closing. - windows[window_id].focus_timer_id = SetTimer(windows[window_id].hWnd, 2, USER_TIMER_MINIMUM, (TIMERPROC) nullptr); - } - if (wParam != WA_INACTIVE) { - track_mouse_leave_event(hWnd); - - if (!IsIconic(hWnd)) { - SetFocus(hWnd); - } - } + case WM_ACTIVATE: { + _process_activate_event(window_id, wParam, lParam); return 0; // Return to the message loop. } break; case WM_GETMINMAXINFO: { @@ -3782,6 +3748,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA min_max_info->ptMaxTrackSize.x = windows[window_id].max_size.x + decor.x; min_max_info->ptMaxTrackSize.y = windows[window_id].max_size.y + decor.y; } + if (windows[window_id].borderless) { + Rect2i screen_rect = screen_get_usable_rect(window_get_current_screen(window_id)); + + // Set the size of (borderless) maximized mode to exclude taskbar (or any other panel) if present. + min_max_info->ptMaxPosition.x = screen_rect.position.x; + min_max_info->ptMaxPosition.y = screen_rect.position.y; + min_max_info->ptMaxSize.x = screen_rect.size.x; + min_max_info->ptMaxSize.y = screen_rect.size.y; + } return 0; } } break; @@ -3833,9 +3808,15 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case SC_MONITORPOWER: // Monitor trying to enter powersave? return 0; // Prevent from happening. case SC_KEYMENU: - if ((lParam >> 16) <= 0) { + Engine *engine = Engine::get_singleton(); + if (((lParam >> 16) <= 0) && !engine->is_project_manager_hint() && !engine->is_editor_hint() && !GLOBAL_GET("application/run/enable_alt_space_menu")) { + return 0; + } + if (!alt_mem || !(GetAsyncKeyState(VK_SPACE) & (1 << 15))) { return 0; } + SendMessage(windows[window_id].hWnd, WM_SYSKEYUP, VK_SPACE, 0); + SendMessage(windows[window_id].hWnd, WM_SYSKEYUP, VK_MENU, 0); } } break; case WM_INDICATOR_CALLBACK_MESSAGE: { @@ -3864,9 +3845,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_CLOSE: // Did we receive a close message? { - if (windows[window_id].focus_timer_id != 0U) { - KillTimer(windows[window_id].hWnd, windows[window_id].focus_timer_id); - } _send_window_event(windows[window_id], WINDOW_EVENT_CLOSE_REQUEST); return 0; // Jump back. @@ -3978,7 +3956,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } mm->set_relative_screen_position(mm->get_relative()); - if ((windows[window_id].window_has_focus || windows[window_id].is_popup) && mm->get_relative() != Vector2()) { + if ((windows[window_id].window_focused || windows[window_id].is_popup) && mm->get_relative() != Vector2()) { Input::get_singleton()->parse_input_event(mm); } } @@ -4026,7 +4004,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].last_pen_inverted = inverted; // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) { + if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) { break; } @@ -4076,7 +4054,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; - if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { + if (windows[window_id].window_focused || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); } } @@ -4162,7 +4140,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) { + if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) { break; } @@ -4225,7 +4203,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA mm->set_relative_screen_position(mm->get_relative()); old_x = mm->get_position().x; old_y = mm->get_position().y; - if (windows[window_id].window_has_focus || window_get_active_popup() == window_id) { + if (windows[window_id].window_focused || window_get_active_popup() == window_id) { Input::get_singleton()->parse_input_event(mm); } @@ -4277,7 +4255,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } // Don't calculate relative mouse movement if we don't have focus in CAPTURED mode. - if (!windows[window_id].window_has_focus && mouse_mode == MOUSE_MODE_CAPTURED) { + if (!windows[window_id].window_focused && mouse_mode == MOUSE_MODE_CAPTURED) { break; } @@ -4569,10 +4547,23 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA window.minimized = true; } else if (IsZoomed(hWnd)) { window.maximized = true; + + // If maximized_window_size == screen_size add 1px border to prevent switching to exclusive_fs. + if (!window.maximized_fs && window.borderless && window_rect.position == screen_position && window_rect.size == screen_size) { + // Window (borderless) was just maximized and the covers the entire screen. + window.maximized_fs = true; + _update_window_style(window_id, false); + } } else if (window_rect.position == screen_position && window_rect.size == screen_size) { window.fullscreen = true; } + if (window.maximized_fs && !window.maximized) { + // Window (maximized and covering fullscreen) was just non-maximized. + window.maximized_fs = false; + _update_window_style(window_id, false); + } + if (!window.minimized) { window.width = window_client_rect.size.width; window.height = window_client_rect.size.height; @@ -4625,10 +4616,6 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA if (!Main::is_iterating()) { Main::iteration(); } - } else if (wParam == windows[window_id].focus_timer_id) { - _process_activate_event(window_id, windows[window_id].saved_wparam, windows[window_id].saved_lparam); - KillTimer(windows[window_id].hWnd, wParam); - windows[window_id].focus_timer_id = 0U; } } break; case WM_SYSKEYUP: @@ -4777,7 +4764,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) { - if (windows[window_id].window_has_focus && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) { + if (windows[window_id].window_focused && (mouse_mode == MOUSE_MODE_HIDDEN || mouse_mode == MOUSE_MODE_CAPTURED || mouse_mode == MOUSE_MODE_CONFINED_HIDDEN)) { // Hide the cursor. if (hCursor == nullptr) { hCursor = SetCursor(nullptr); @@ -4834,20 +4821,25 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { void DisplayServerWindows::_process_activate_event(WindowID p_window_id, WPARAM wParam, LPARAM lParam) { if (LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) { - _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN); - windows[p_window_id].window_focused = true; + last_focused_window = p_window_id; alt_mem = false; control_mem = false; shift_mem = false; gr_mem = false; - - // Restore mouse mode. _set_mouse_mode_impl(mouse_mode); + if (!IsIconic(windows[p_window_id].hWnd)) { + SetFocus(windows[p_window_id].hWnd); + } + windows[p_window_id].window_focused = true; + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_IN); } else { // WM_INACTIVE. Input::get_singleton()->release_pressed_events(); - _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); - windows[p_window_id].window_focused = false; + track_mouse_leave_event(windows[p_window_id].hWnd); + // Release capture unconditionally because it can be set due to dragging, in addition to captured mode. + ReleaseCapture(); alt_mem = false; + windows[p_window_id].window_focused = false; + _send_window_event(windows[p_window_id], WINDOW_EVENT_FOCUS_OUT); } if ((tablet_get_current_driver() == "wintab") && wintab_available && windows[p_window_id].wtctx) { @@ -5051,7 +5043,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, false, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); RECT WindowRect; @@ -5076,8 +5068,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, Rect2i srect = screen_get_usable_rect(rq_screen); Point2i wpos = p_rect.position; if (srect != Rect2i()) { - wpos.x = CLAMP(wpos.x, srect.position.x, srect.position.x + srect.size.width - p_rect.size.width / 3); - wpos.y = CLAMP(wpos.y, srect.position.y, srect.position.y + srect.size.height - p_rect.size.height / 3); + wpos = wpos.clamp(srect.position, srect.position + srect.size - p_rect.size / 3); } WindowRect.left = wpos.x; @@ -5285,6 +5276,12 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.height = p_rect.size.height; } + // Set size of maximized borderless window (by default it covers the entire screen). + if (p_mode == WINDOW_MODE_MAXIMIZED && (p_flags & WINDOW_FLAG_BORDERLESS_BIT)) { + Rect2i srect = screen_get_usable_rect(rq_screen); + SetWindowPos(wd.hWnd, HWND_TOP, srect.position.x, srect.position.y, srect.size.width, srect.size.height, SWP_NOZORDER | SWP_NOACTIVATE); + } + window_id_counter++; } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 1191f22968..4792b65801 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -382,6 +382,7 @@ class DisplayServerWindows : public DisplayServer { bool pre_fs_valid = false; RECT pre_fs_rect; bool maximized = false; + bool maximized_fs = false; bool minimized = false; bool fullscreen = false; bool multiwindow_fs = false; @@ -391,7 +392,6 @@ class DisplayServerWindows : public DisplayServer { bool was_maximized = false; bool always_on_top = false; bool no_focus = false; - bool window_has_focus = false; bool exclusive = false; bool context_created = false; bool mpass = false; @@ -402,7 +402,6 @@ class DisplayServerWindows : public DisplayServer { // Timers. uint32_t move_timer_id = 0U; - uint32_t focus_timer_id = 0U; HANDLE wtctx; LOGCONTEXTW wtlc; @@ -472,7 +471,7 @@ class DisplayServerWindows : public DisplayServer { HashMap<IndicatorID, IndicatorData> indicators; void _send_window_event(const WindowData &wd, WindowEvent p_event); - void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); + void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_maximized_fs, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex); MouseMode mouse_mode; int restore_mouse_trails = 0; diff --git a/platform/windows/doc_classes/EditorExportPlatformWindows.xml b/platform/windows/doc_classes/EditorExportPlatformWindows.xml index 1239a2b32f..06b272c10e 100644 --- a/platform/windows/doc_classes/EditorExportPlatformWindows.xml +++ b/platform/windows/doc_classes/EditorExportPlatformWindows.xml @@ -4,6 +4,7 @@ Exporter for Windows. </brief_description> <description> + The Windows exporter customizes how a Windows build is handled. In the editor's "Export" window, it is created when adding a new "Windows" preset. </description> <tutorials> <link title="Exporting for Windows">$DOCS_URL/tutorials/export/exporting_for_windows.html</link> diff --git a/platform/windows/godot_windows.cpp b/platform/windows/godot_windows.cpp index f86ecd87fb..5f41b4e568 100644 --- a/platform/windows/godot_windows.cpp +++ b/platform/windows/godot_windows.cpp @@ -171,13 +171,15 @@ int widechar_main(int argc, wchar_t **argv) { delete[] argv_utf8; if (err == ERR_HELP) { // Returned by --help and --version, so success. - return 0; + return EXIT_SUCCESS; } - return 255; + return EXIT_FAILURE; } - if (Main::start()) { + if (Main::start() == EXIT_SUCCESS) { os.run(); + } else { + os.set_exit_code(EXIT_FAILURE); } Main::cleanup(); diff --git a/platform/windows/native_menu_windows.h b/platform/windows/native_menu_windows.h index 6eab56d8b6..74fd231903 100644 --- a/platform/windows/native_menu_windows.h +++ b/platform/windows/native_menu_windows.h @@ -33,7 +33,7 @@ #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" -#include "servers/native_menu.h" +#include "servers/display/native_menu.h" #define WIN32_LEAN_AND_MEAN #include <windows.h> diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 152b9ac10f..c39b327953 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -42,6 +42,7 @@ #include "drivers/unix/net_socket_posix.h" #include "drivers/windows/dir_access_windows.h" #include "drivers/windows/file_access_windows.h" +#include "drivers/windows/file_access_windows_pipe.h" #include "main/main.h" #include "servers/audio_server.h" #include "servers/rendering/rendering_server_default.h" @@ -178,6 +179,7 @@ void OS_Windows::initialize() { FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA); FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM); + FileAccess::make_default<FileAccessWindowsPipe>(FileAccess::ACCESS_PIPE); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA); DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM); @@ -727,6 +729,105 @@ Dictionary OS_Windows::get_memory_info() const { return meminfo; } +Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +#define CLEAN_PIPES \ + if (pipe_in[0] != 0) { \ + CloseHandle(pipe_in[0]); \ + } \ + if (pipe_in[1] != 0) { \ + CloseHandle(pipe_in[1]); \ + } \ + if (pipe_out[0] != 0) { \ + CloseHandle(pipe_out[0]); \ + } \ + if (pipe_out[1] != 0) { \ + CloseHandle(pipe_out[1]); \ + } \ + if (pipe_err[0] != 0) { \ + CloseHandle(pipe_err[0]); \ + } \ + if (pipe_err[1] != 0) { \ + CloseHandle(pipe_err[1]); \ + } + + Dictionary ret; + + String path = p_path.replace("/", "\\"); + String command = _quote_command_line_argument(path); + for (const String &E : p_arguments) { + command += " " + _quote_command_line_argument(E); + } + + // Create pipes. + HANDLE pipe_in[2] = { 0, 0 }; + HANDLE pipe_out[2] = { 0, 0 }; + HANDLE pipe_err[2] = { 0, 0 }; + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = nullptr; + + ERR_FAIL_COND_V(!CreatePipe(&pipe_in[0], &pipe_in[1], &sa, 0), ret); + if (!SetHandleInformation(pipe_in[1], HANDLE_FLAG_INHERIT, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!CreatePipe(&pipe_out[0], &pipe_out[1], &sa, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!SetHandleInformation(pipe_out[0], HANDLE_FLAG_INHERIT, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + if (!CreatePipe(&pipe_err[0], &pipe_err[1], &sa, 0)) { + CLEAN_PIPES + ERR_FAIL_V(ret); + } + ERR_FAIL_COND_V(!SetHandleInformation(pipe_err[0], HANDLE_FLAG_INHERIT, 0), ret); + + // Create process. + ProcessInfo pi; + ZeroMemory(&pi.si, sizeof(pi.si)); + pi.si.cb = sizeof(pi.si); + ZeroMemory(&pi.pi, sizeof(pi.pi)); + LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si; + + pi.si.dwFlags |= STARTF_USESTDHANDLES; + pi.si.hStdInput = pipe_in[0]; + pi.si.hStdOutput = pipe_out[1]; + pi.si.hStdError = pipe_err[1]; + + 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)) { + CLEAN_PIPES + ERR_FAIL_V_MSG(ret, "Could not create child process: " + command); + } + CloseHandle(pipe_in[0]); + CloseHandle(pipe_out[1]); + CloseHandle(pipe_err[1]); + + ProcessID pid = pi.pi.dwProcessId; + process_map->insert(pid, pi); + + Ref<FileAccessWindowsPipe> main_pipe; + main_pipe.instantiate(); + main_pipe->open_existing(pipe_out[0], pipe_in[1]); + + Ref<FileAccessWindowsPipe> err_pipe; + err_pipe.instantiate(); + err_pipe->open_existing(pipe_err[0], 0); + + ret["stdio"] = main_pipe; + ret["stderr"] = err_pipe; + ret["pid"] = pid; + +#undef CLEAN_PIPES + return ret; +} + 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 command = _quote_command_line_argument(path); diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 351cb32c9b..c703a3ddca 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -181,6 +181,7 @@ public: virtual Dictionary get_memory_info() const override; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; |
