summaryrefslogtreecommitdiffstats
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/SCsub9
-rw-r--r--platform/android/doc_classes/EditorExportPlatformAndroid.xml1
-rw-r--r--platform/android/export/export_plugin.cpp10
-rw-r--r--platform/android/java/app/config.gradle8
-rw-r--r--platform/android/java/editor/build.gradle2
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt2
-rw-r--r--platform/android/java/lib/build.gradle5
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt171
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt31
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt30
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/CommandLineFileParser.kt83
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/vulkan/VkThread.kt6
-rw-r--r--platform/android/java/lib/srcTest/java/org/godotengine/godot/utils/CommandLineFileParserTest.kt104
-rw-r--r--platform/ios/detect.py1
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp143
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h4
-rw-r--r--platform/macos/display_server_macos.mm17
-rw-r--r--platform/windows/display_server_windows.cpp144
-rw-r--r--platform/windows/display_server_windows.h4
-rw-r--r--platform/windows/doc_classes/EditorExportPlatformWindows.xml1
20 files changed, 467 insertions, 309 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/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 64ef1397ba..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") {
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/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/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/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/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index d06eab8531..b7a9fb1bbd 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -1995,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;
}
@@ -2084,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) {
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index e540b7617f..2d98feb81b 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -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.
@@ -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:
@@ -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;
@@ -5284,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..2f1309176d 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;
@@ -402,7 +403,6 @@ class DisplayServerWindows : public DisplayServer {
// Timers.
uint32_t move_timer_id = 0U;
- uint32_t focus_timer_id = 0U;
HANDLE wtctx;
LOGCONTEXTW wtlc;
@@ -472,7 +472,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>