diff options
Diffstat (limited to 'platform')
39 files changed, 1316 insertions, 677 deletions
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index a663a847c2..474458b00f 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -862,6 +862,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { String tname = string_table[name]; uint32_t attrcount = decode_uint32(&p_manifest[iofs + 20]); iofs += 28; + bool is_focus_aware_metadata = false; for (uint32_t i = 0; i < attrcount; i++) { uint32_t attr_nspace = decode_uint32(&p_manifest[iofs]); @@ -929,9 +930,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { } } - if (tname == "meta-data" && attrname == "value" && value == "oculus_focus_aware_value") { + if (tname == "meta-data" && attrname == "value" && is_focus_aware_metadata) { // Update the focus awareness meta-data value - string_table.write[attr_value] = xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? "true" : "false"; + encode_uint32(xr_mode_index == /* XRMode.OVR */ 1 && focus_awareness ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); } if (tname == "meta-data" && attrname == "value" && value == "plugins_value" && !plugins_names.empty()) { @@ -939,6 +940,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { string_table.write[attr_value] = plugins_names; } + is_focus_aware_metadata = tname == "meta-data" && attrname == "name" && value == "com.oculus.vr.focusaware"; iofs += 20; } @@ -1332,7 +1334,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform { str = get_project_name(package_name); } else { - String lang = str.substr(str.find_last("-") + 1, str.length()).replace("-", "_"); + String lang = str.substr(str.rfind("-") + 1, str.length()).replace("-", "_"); String prop = "application/config/name_" + lang; if (ProjectSettings::get_singleton()->has_setting(prop)) { str = ProjectSettings::get_singleton()->get(prop); diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index dbf1dc0f3c..48c09552c1 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -45,8 +45,8 @@ android:resizeableActivity="false" tools:ignore="UnusedAttribute" > - <!-- Focus awareness metadata populated at export time if the user enables it in the 'Xr Features' section. --> - <meta-data android:name="com.oculus.vr.focusaware" android:value="oculus_focus_aware_value" /> + <!-- Focus awareness metadata is updated at export time if the user enables it in the 'Xr Features' section. --> + <meta-data android:name="com.oculus.vr.focusaware" android:value="false" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/platform/android/java/app/src/com/godot/game/GodotApp.java b/platform/android/java/app/src/com/godot/game/GodotApp.java index eb884404cd..1af5950cbe 100644 --- a/platform/android/java/app/src/com/godot/game/GodotApp.java +++ b/platform/android/java/app/src/com/godot/game/GodotApp.java @@ -30,11 +30,11 @@ package com.godot.game; -import org.godotengine.godot.Godot; +import org.godotengine.godot.FullScreenGodotApp; /** * Template activity for Godot Android custom builds. * Feel free to extend and modify this class for your custom logic. */ -public class GodotApp extends Godot { +public class GodotApp extends FullScreenGodotApp { } diff --git a/platform/android/java/lib/res/layout/godot_app_layout.xml b/platform/android/java/lib/res/layout/godot_app_layout.xml new file mode 100644 index 0000000000..386ded1c5d --- /dev/null +++ b/platform/android/java/lib/res/layout/godot_app_layout.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/godot_fragment_container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> diff --git a/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java new file mode 100644 index 0000000000..138c2de94c --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/FullScreenGodotApp.java @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* FullScreenGodotApp.java */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +package org.godotengine.godot; + +import android.content.Intent; +import android.os.Bundle; +import android.view.KeyEvent; + +import androidx.fragment.app.FragmentActivity; + +/** + * Base activity for Android apps intending to use Godot as the primary and only screen. + * + * It's also a reference implementation for how to setup and use the {@link Godot} fragment + * within an Android app. + */ +public abstract class FullScreenGodotApp extends FragmentActivity { + protected Godot godotFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.godot_app_layout); + godotFragment = new Godot(); + getSupportFragmentManager().beginTransaction().replace(R.id.godot_fragment_container, godotFragment).setPrimaryNavigationFragment(godotFragment).commitNowAllowingStateLoss(); + } + + @Override + public void onNewIntent(Intent intent) { + if (godotFragment != null) { + godotFragment.onNewIntent(intent); + } + } + + @Override + public void onBackPressed() { + if (godotFragment != null) { + godotFragment.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { + if (godotFragment != null && godotFragment.onKeyMultiple(inKeyCode, repeatCount, event)) { + return true; + } + return super.onKeyMultiple(inKeyCode, repeatCount, event); + } +} diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index fcbbc86100..1b55090451 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -30,6 +30,9 @@ package org.godotengine.godot; +import static android.content.Context.MODE_PRIVATE; +import static android.content.Context.WINDOW_SERVICE; + import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.GodotPluginRegistry; @@ -68,6 +71,7 @@ import android.os.Vibrator; import android.provider.Settings.Secure; import android.view.Display; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; @@ -84,7 +88,7 @@ import android.widget.TextView; import androidx.annotation.CallSuper; import androidx.annotation.Keep; import androidx.annotation.NonNull; -import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.Fragment; import com.google.android.vending.expansion.downloader.DownloadProgressInfo; import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller; @@ -102,7 +106,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; -public abstract class Godot extends FragmentActivity implements SensorEventListener, IDownloaderClient { +public class Godot extends Fragment implements SensorEventListener, IDownloaderClient { private IStub mDownloaderClientStub; private TextView mStatusText; private TextView mProgressFraction; @@ -130,7 +134,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe static private Intent mCurrentIntent; - @Override public void onNewIntent(Intent intent) { mCurrentIntent = intent; } @@ -156,6 +159,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe private String[] command_line; private boolean use_apk_expansion; + private ViewGroup containerLayout; public GodotRenderView mRenderView; private boolean godot_initialized = false; @@ -174,7 +178,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe public ResultCallback result_callback; @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + public void onActivityResult(int requestCode, int resultCode, Intent data) { if (result_callback != null) { result_callback.callback(requestCode, resultCode, data); result_callback = null; @@ -211,27 +215,28 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe */ @Keep private void onVideoInit() { - final FrameLayout layout = new FrameLayout(this); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - setContentView(layout); + final Activity activity = getActivity(); + containerLayout = new FrameLayout(activity); + containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); // GodotEditText layout - GodotEditText editText = new GodotEditText(this); + GodotEditText editText = new GodotEditText(activity); editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); // ...add to FrameLayout - layout.addView(editText); + containerLayout.addView(editText); GodotLib.setup(command_line); final String videoDriver = GodotLib.getGlobal("rendering/quality/driver/driver_name"); if (videoDriver.equals("Vulkan")) { - mRenderView = new GodotVulkanRenderView(this); + mRenderView = new GodotVulkanRenderView(activity, this); } else { - mRenderView = new GodotGLRenderView(this, xrMode, use_32_bits, use_debug_opengl); + mRenderView = new GodotGLRenderView(activity, this, xrMode, use_32_bits, + use_debug_opengl); } View view = mRenderView.getView(); - layout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + containerLayout.addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); editText.setView(mRenderView); io.setEdit(editText); @@ -239,7 +244,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void onGlobalLayout() { Point fullSize = new Point(); - getWindowManager().getDefaultDisplay().getSize(fullSize); + activity.getWindowManager().getDefaultDisplay().getSize(fullSize); Rect gameSize = new Rect(); mRenderView.getView().getWindowVisibleDisplayFrame(gameSize); @@ -262,9 +267,9 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe // Include the returned non-null views in the Godot view hierarchy. for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { - View pluginView = plugin.onMainCreate(this); + View pluginView = plugin.onMainCreate(activity); if (pluginView != null) { - layout.addView(pluginView); + containerLayout.addView(pluginView); } } } @@ -274,9 +279,9 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void run() { if (p_enabled) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } }); @@ -290,7 +295,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Keep private void vibrate(int durationMs) { if (requestPermission("VIBRATE")) { - Vibrator v = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE); + Vibrator v = (Vibrator)getContext().getSystemService(Context.VIBRATOR_SERVICE); if (v != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { v.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); @@ -314,13 +319,16 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe // Using instrumentation is a way of making the whole app process restart, because Android // will kill any process of the same package which was already running. // - Bundle args = new Bundle(); - args.putParcelable("intent", mCurrentIntent); - startInstrumentation(new ComponentName(this, GodotInstrumentation.class), null, args); + final Activity activity = getActivity(); + if (activity != null) { + Bundle args = new Bundle(); + args.putParcelable("intent", mCurrentIntent); + activity.startInstrumentation(new ComponentName(activity, GodotInstrumentation.class), null, args); + } } public void alert(final String message, final String title) { - final Activity activity = this; + final Activity activity = getActivity(); runOnUiThread(new Runnable() { @Override public void run() { @@ -340,7 +348,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public int getGLESVersionCode() { - ActivityManager am = (ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE); ConfigurationInfo deviceInfo = am.getDeviceConfigurationInfo(); return deviceInfo.reqGlEsVersion; } @@ -349,7 +357,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe protected String[] getCommandLine() { InputStream is; try { - is = getAssets().open("_cl_"); + is = getActivity().getAssets().open("_cl_"); byte[] len = new byte[4]; int r = is.read(len); if (r < 4) { @@ -426,11 +434,12 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe command_line = new_cmdline; } - io = new GodotIO(this); - io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID); + final Activity activity = getActivity(); + io = new GodotIO(activity); + io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID); GodotLib.io = io; - netUtils = new GodotNetUtils(this); - mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); + netUtils = new GodotNetUtils(activity); + mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); @@ -440,7 +449,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); - GodotLib.initialize(this, getAssets(), use_apk_expansion); + GodotLib.initialize(activity, this, activity.getAssets(), use_apk_expansion); result_callback = null; @@ -454,151 +463,152 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - Window window = getWindow(); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { + final Activity activity = getActivity(); + Window window = activity.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); - mClipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + mClipboard = (ClipboardManager)activity.getSystemService(Context.CLIPBOARD_SERVICE); pluginRegistry = GodotPluginRegistry.initializePluginRegistry(this); //check for apk expansion API - if (true) { - boolean md5mismatch = false; - command_line = getCommandLine(); - String main_pack_md5 = null; - String main_pack_key = null; - - List<String> new_args = new LinkedList<String>(); - - for (int i = 0; i < command_line.length; i++) { - boolean has_extra = i < command_line.length - 1; - if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { - xrMode = XRMode.REGULAR; - } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { - xrMode = XRMode.OVR; - } else if (command_line[i].equals("--use_depth_32")) { - use_32_bits = true; - } else if (command_line[i].equals("--debug_opengl")) { - use_debug_opengl = true; - } else if (command_line[i].equals("--use_immersive")) { - use_immersive = true; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - window.getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - - UiChangeListener(); - } - } else if (command_line[i].equals("--use_apk_expansion")) { - use_apk_expansion = true; - } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { - main_pack_md5 = command_line[i + 1]; - i++; - } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { - main_pack_key = command_line[i + 1]; - SharedPreferences prefs = getSharedPreferences("app_data_keys", MODE_PRIVATE); - Editor editor = prefs.edit(); - editor.putString("store_public_key", main_pack_key); - - editor.apply(); - i++; - } else if (command_line[i].trim().length() != 0) { - new_args.add(command_line[i]); + boolean md5mismatch = false; + command_line = getCommandLine(); + String main_pack_md5 = null; + String main_pack_key = null; + + List<String> new_args = new LinkedList<String>(); + + for (int i = 0; i < command_line.length; i++) { + boolean has_extra = i < command_line.length - 1; + if (command_line[i].equals(XRMode.REGULAR.cmdLineArg)) { + xrMode = XRMode.REGULAR; + } else if (command_line[i].equals(XRMode.OVR.cmdLineArg)) { + xrMode = XRMode.OVR; + } else if (command_line[i].equals("--use_depth_32")) { + use_32_bits = true; + } else if (command_line[i].equals("--debug_opengl")) { + use_debug_opengl = true; + } else if (command_line[i].equals("--use_immersive")) { + use_immersive = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ + window.getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | // hide nav bar + View.SYSTEM_UI_FLAG_FULLSCREEN | // hide status bar + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + + UiChangeListener(); } + } else if (command_line[i].equals("--use_apk_expansion")) { + use_apk_expansion = true; + } else if (has_extra && command_line[i].equals("--apk_expansion_md5")) { + main_pack_md5 = command_line[i + 1]; + i++; + } else if (has_extra && command_line[i].equals("--apk_expansion_key")) { + main_pack_key = command_line[i + 1]; + SharedPreferences prefs = activity.getSharedPreferences("app_data_keys", + MODE_PRIVATE); + Editor editor = prefs.edit(); + editor.putString("store_public_key", main_pack_key); + + editor.apply(); + i++; + } else if (command_line[i].trim().length() != 0) { + new_args.add(command_line[i]); } + } - if (new_args.isEmpty()) { - command_line = null; - } else { - command_line = new_args.toArray(new String[new_args.size()]); + if (new_args.isEmpty()) { + command_line = null; + } else { + command_line = new_args.toArray(new String[new_args.size()]); + } + if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { + //check that environment is ok! + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + //show popup and die } - if (use_apk_expansion && main_pack_md5 != null && main_pack_key != null) { - //check that environment is ok! - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - //show popup and die - } - // Build the full path to the app's expansion files - try { - expansion_pack_path = Helpers.getSaveFilePath(getApplicationContext()); - expansion_pack_path += "/main." + getPackageManager().getPackageInfo(getPackageName(), 0).versionCode + "." + this.getPackageName() + ".obb"; - } catch (Exception e) { - e.printStackTrace(); - } + // Build the full path to the app's expansion files + try { + expansion_pack_path = Helpers.getSaveFilePath(getContext()); + expansion_pack_path += "/main." + activity.getPackageManager().getPackageInfo(activity.getPackageName(), 0).versionCode + "." + activity.getPackageName() + ".obb"; + } catch (Exception e) { + e.printStackTrace(); + } - File f = new File(expansion_pack_path); + File f = new File(expansion_pack_path); - boolean pack_valid = true; + boolean pack_valid = true; - if (!f.exists()) { - pack_valid = false; + if (!f.exists()) { + pack_valid = false; - } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { - pack_valid = false; - try { - f.delete(); - } catch (Exception e) { - } + } else if (obbIsCorrupted(expansion_pack_path, main_pack_md5)) { + pack_valid = false; + try { + f.delete(); + } catch (Exception e) { } + } - if (!pack_valid) { - Intent notifierIntent = new Intent(this, this.getClass()); - notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (!pack_valid) { + Intent notifierIntent = new Intent(activity, activity.getClass()); + notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TOP); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, - notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, + notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); - int startResult; - try { - startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( - getApplicationContext(), - pendingIntent, + int startResult; + try { + startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( + getContext(), + pendingIntent, + GodotDownloaderService.class); + + if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { + // This is where you do set up to display the download + // progress (next step) + mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GodotDownloaderService.class); - if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { - // This is where you do set up to display the download - // progress (next step) - mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, - GodotDownloaderService.class); - - setContentView(R.layout.downloading_expansion); - mPB = (ProgressBar)findViewById(R.id.progressBar); - mStatusText = (TextView)findViewById(R.id.statusText); - mProgressFraction = (TextView)findViewById(R.id.progressAsFraction); - mProgressPercent = (TextView)findViewById(R.id.progressAsPercentage); - mAverageSpeed = (TextView)findViewById(R.id.progressAverageSpeed); - mTimeRemaining = (TextView)findViewById(R.id.progressTimeRemaining); - mDashboard = findViewById(R.id.downloaderDashboard); - mCellMessage = findViewById(R.id.approveCellular); - mPauseButton = (Button)findViewById(R.id.pauseButton); - mWiFiSettingsButton = (Button)findViewById(R.id.wifiSettingsButton); - - return; - } - } catch (NameNotFoundException e) { - // TODO Auto-generated catch block + View downloadingExpansionView = + inflater.inflate(R.layout.downloading_expansion, container, false); + mPB = (ProgressBar)downloadingExpansionView.findViewById(R.id.progressBar); + mStatusText = (TextView)downloadingExpansionView.findViewById(R.id.statusText); + mProgressFraction = (TextView)downloadingExpansionView.findViewById(R.id.progressAsFraction); + mProgressPercent = (TextView)downloadingExpansionView.findViewById(R.id.progressAsPercentage); + mAverageSpeed = (TextView)downloadingExpansionView.findViewById(R.id.progressAverageSpeed); + mTimeRemaining = (TextView)downloadingExpansionView.findViewById(R.id.progressTimeRemaining); + mDashboard = downloadingExpansionView.findViewById(R.id.downloaderDashboard); + mCellMessage = downloadingExpansionView.findViewById(R.id.approveCellular); + mPauseButton = (Button)downloadingExpansionView.findViewById(R.id.pauseButton); + mWiFiSettingsButton = (Button)downloadingExpansionView.findViewById(R.id.wifiSettingsButton); + + return downloadingExpansionView; } + } catch (NameNotFoundException e) { + // TODO Auto-generated catch block } } } - mCurrentIntent = getIntent(); + mCurrentIntent = activity.getIntent(); initializeGodot(); + return containerLayout; } @Override - protected void onDestroy() { + public void onDestroy() { for (GodotPlugin plugin : pluginRegistry.getAllPlugins()) { plugin.onMainDestroy(); } - GodotLib.ondestroy(this); + GodotLib.ondestroy(); super.onDestroy(); @@ -608,13 +618,13 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } @Override - protected void onPause() { + public void onPause() { super.onPause(); activityResumed = false; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.disconnect(this); + mDownloaderClientStub.disconnect(getActivity()); } return; } @@ -644,12 +654,12 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } @Override - protected void onResume() { + public void onResume() { super.onResume(); activityResumed = true; if (!godot_initialized) { if (null != mDownloaderClientStub) { - mDownloaderClientStub.connect(this); + mDownloaderClientStub.connect(getActivity()); } return; } @@ -662,7 +672,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); if (use_immersive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // check if the application runs on an android 4.4+ - Window window = getWindow(); + Window window = getActivity().getWindow(); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | @@ -678,7 +688,7 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public void UiChangeListener() { - final View decorView = getWindow().getDecorView(); + final View decorView = getActivity().getWindow().getDecorView(); decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { @@ -699,7 +709,8 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe @Override public void onSensorChanged(SensorEvent event) { - Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); + Display display = + ((WindowManager)getActivity().getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); int displayRotation = display.getRotation(); float[] adjustedValues = new float[3]; @@ -762,7 +773,6 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } */ - @Override public void onBackPressed() { boolean shouldQuit = true; @@ -793,6 +803,12 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } } + public final void runOnUiThread(@NonNull Runnable action) { + if (getActivity() != null) { + getActivity().runOnUiThread(action); + } + } + private void forceQuit() { System.exit(0); } @@ -895,18 +911,17 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe return true; } - @Override public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) { String s = event.getCharacters(); if (s == null || s.length() == 0) - return super.onKeyMultiple(inKeyCode, repeatCount, event); + return false; final char[] cc = s.toCharArray(); int cnt = 0; for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0) ; if (cnt == 0) - return super.onKeyMultiple(inKeyCode, repeatCount, event); + return false; mRenderView.queueOnRenderThread(new Runnable() { // This method will be called on the rendering thread: public void run() { @@ -924,15 +939,15 @@ public abstract class Godot extends FragmentActivity implements SensorEventListe } public boolean requestPermission(String p_name) { - return PermissionsUtil.requestPermission(p_name, this); + return PermissionsUtil.requestPermission(p_name, getActivity()); } public boolean requestPermissions() { - return PermissionsUtil.requestManifestPermissions(this); + return PermissionsUtil.requestManifestPermissions(getActivity()); } public String[] getGrantedPermissions() { - return PermissionsUtil.getGrantedPermissions(this); + return PermissionsUtil.getGrantedPermissions(getActivity()); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 14dd893faa..4da2f31250 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -42,6 +42,7 @@ import org.godotengine.godot.xr.regular.RegularContextFactory; import org.godotengine.godot.xr.regular.RegularFallbackConfigChooser; import android.annotation.SuppressLint; +import android.content.Context; import android.graphics.PixelFormat; import android.opengl.GLSurfaceView; import android.view.GestureDetector; @@ -68,19 +69,20 @@ import android.view.SurfaceView; * bit depths). Failure to do so would result in an EGL_BAD_MATCH error. */ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView { - private final Godot activity; + private final Godot godot; private final GodotInputHandler inputHandler; private final GestureDetector detector; private final GodotRenderer godotRenderer; - public GodotGLRenderView(Godot activity, XRMode xrMode, boolean p_use_32_bits, boolean p_use_debug_opengl) { - super(activity); + public GodotGLRenderView(Context context, Godot godot, XRMode xrMode, boolean p_use_32_bits, + boolean p_use_debug_opengl) { + super(context); GLUtils.use_32 = p_use_32_bits; GLUtils.use_debug_opengl = p_use_debug_opengl; - this.activity = activity; + this.godot = godot; this.inputHandler = new GodotInputHandler(this); - this.detector = new GestureDetector(activity, new GodotGestureHandler(this)); + this.detector = new GestureDetector(context, new GodotGestureHandler(this)); this.godotRenderer = new GodotRenderer(); init(xrMode, false, 16, 0); } @@ -112,7 +114,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Override public void onBackPressed() { - activity.onBackPressed(); + godot.onBackPressed(); } @SuppressLint("ClickableViewAccessibility") @@ -120,7 +122,7 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); this.detector.onTouchEvent(event); - return activity.gotTouchEvent(event); + return godot.gotTouchEvent(event); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 93f4786e83..4dd228e53b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -32,6 +32,7 @@ package org.godotengine.godot; import org.godotengine.godot.input.*; +import android.app.Activity; import android.content.*; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -51,7 +52,7 @@ import java.util.Locale; public class GodotIO { AssetManager am; - Godot activity; + final Activity activity; GodotEditText edit; final int SCREEN_LANDSCAPE = 0; @@ -314,7 +315,7 @@ public class GodotIO { dirs.remove(id); } - GodotIO(Godot p_activity) { + GodotIO(Activity p_activity) { am = p_activity.getAssets(); activity = p_activity; //streams = new HashMap<Integer, AssetData>(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 3693f36557..318e2816ff 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -50,13 +50,13 @@ public class GodotLib { /** * Invoked on the main thread to initialize Godot native layer. */ - public static native void initialize(Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); + public static native void initialize(Activity activity, Godot p_instance, Object p_asset_manager, boolean use_apk_expansion); /** * Invoked on the main thread to clean up Godot native layer. - * @see Activity#onDestroy() + * @see androidx.fragment.app.Fragment#onDestroy() */ - public static native void ondestroy(Godot p_instance); + public static native void ondestroy(); /** * Invoked on the GL thread to complete setup for the Godot native layer logic. @@ -161,14 +161,14 @@ public class GodotLib { public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); /** - * Invoked when the Android activity resumes. - * @see Activity#onResume() + * Invoked when the Android app resumes. + * @see androidx.fragment.app.Fragment#onResume() */ public static native void focusin(); /** - * Invoked when the Android activity pauses. - * @see Activity#onPause() + * Invoked when the Android app pauses. + * @see androidx.fragment.app.Fragment#onPause() */ public static native void focusout(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index e9872b58ff..aace593bae 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -36,23 +36,24 @@ import org.godotengine.godot.vulkan.VkRenderer; import org.godotengine.godot.vulkan.VkSurfaceView; import android.annotation.SuppressLint; +import android.content.Context; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { - private final Godot mActivity; + private final Godot godot; private final GodotInputHandler mInputHandler; private final GestureDetector mGestureDetector; private final VkRenderer mRenderer; - public GodotVulkanRenderView(Godot activity) { - super(activity); + public GodotVulkanRenderView(Context context, Godot godot) { + super(context); - mActivity = activity; + this.godot = godot; mInputHandler = new GodotInputHandler(this); - mGestureDetector = new GestureDetector(mActivity, new GodotGestureHandler(this)); + mGestureDetector = new GestureDetector(context, new GodotGestureHandler(this)); mRenderer = new VkRenderer(); setFocusableInTouchMode(true); @@ -86,7 +87,7 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Override public void onBackPressed() { - mActivity.onBackPressed(); + godot.onBackPressed(); } @SuppressLint("ClickableViewAccessibility") @@ -94,7 +95,7 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); mGestureDetector.onTouchEvent(event); - return mActivity.gotTouchEvent(event); + return godot.gotTouchEvent(event); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java index ce85880fa3..93c204935c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java @@ -98,7 +98,7 @@ public abstract class GodotPlugin { */ @Nullable protected Activity getActivity() { - return godot; + return godot.getActivity(); } /** diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java index 12d2ed09fb..1c2d1a6563 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java +++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPluginRegistry.java @@ -32,6 +32,7 @@ package org.godotengine.godot.plugin; import org.godotengine.godot.Godot; +import android.app.Activity; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; @@ -121,9 +122,11 @@ public final class GodotPluginRegistry { private void loadPlugins(Godot godot) { try { - ApplicationInfo appInfo = godot + final Activity activity = godot.getActivity(); + ApplicationInfo appInfo = activity .getPackageManager() - .getApplicationInfo(godot.getPackageName(), PackageManager.GET_META_DATA); + .getApplicationInfo(activity.getPackageName(), + PackageManager.GET_META_DATA); Bundle metaData = appInfo.metaData; if (metaData == null || metaData.isEmpty()) { return; diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java index 0832a9b965..c89118ad55 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/GodotNetUtils.java @@ -30,8 +30,7 @@ package org.godotengine.godot.utils; -import org.godotengine.godot.Godot; - +import android.app.Activity; import android.content.Context; import android.net.wifi.WifiManager; import android.util.Log; @@ -45,7 +44,7 @@ public class GodotNetUtils { /* A single, reference counted, multicast lock, or null if permission CHANGE_WIFI_MULTICAST_STATE is missing */ private WifiManager.MulticastLock multicastLock; - public GodotNetUtils(Godot p_activity) { + public GodotNetUtils(Activity p_activity) { if (PermissionsUtil.hasManifestPermission(p_activity, "android.permission.CHANGE_WIFI_MULTICAST_STATE")) { WifiManager wifi = (WifiManager)p_activity.getApplicationContext().getSystemService(Context.WIFI_SERVICE); multicastLock = wifi.createMulticastLock("GodotMulticastLock"); 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 6837e4f147..7104baf86e 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 @@ -30,9 +30,8 @@ package org.godotengine.godot.utils; -import org.godotengine.godot.Godot; - import android.Manifest; +import android.app.Activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PermissionInfo; @@ -65,7 +64,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if permission was granted otherwise returns "false". */ - public static boolean requestPermission(String name, Godot activity) { + public static boolean requestPermission(String name, Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // Not necessary, asked on install already return true; @@ -93,7 +92,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return true/false. "true" if all permissions were granted otherwise returns "false". */ - public static boolean requestManifestPermissions(Godot activity) { + public static boolean requestManifestPermissions(Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } @@ -138,7 +137,7 @@ public final class PermissionsUtil { * @param activity the caller activity for this method. * @return granted permissions list */ - public static String[] getGrantedPermissions(Godot activity) { + public static String[] getGrantedPermissions(Activity activity) { String[] manifestPermissions; try { manifestPermissions = getManifestPermissions(activity); @@ -172,7 +171,7 @@ public final class PermissionsUtil { * @param permission the permession to look for in the manifest file. * @return "true" if the permission is in the manifest file of the activity, "false" otherwise. */ - public static boolean hasManifestPermission(Godot activity, String permission) { + public static boolean hasManifestPermission(Activity activity, String permission) { try { for (String p : getManifestPermissions(activity)) { if (permission.equals(p)) @@ -190,7 +189,7 @@ public final class PermissionsUtil { * @return manifest permissions list * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static String[] getManifestPermissions(Godot activity) throws PackageManager.NameNotFoundException { + private static String[] getManifestPermissions(Activity activity) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); if (packageInfo.requestedPermissions == null) @@ -205,7 +204,7 @@ public final class PermissionsUtil { * @return permission info object * @throws PackageManager.NameNotFoundException the exception is thrown when a given package, application, or component name cannot be found. */ - private static PermissionInfo getPermissionInfo(Godot activity, String permission) throws PackageManager.NameNotFoundException { + private static PermissionInfo getPermissionInfo(Activity activity, String permission) throws PackageManager.NameNotFoundException { PackageManager packageManager = activity.getPackageManager(); return packageManager.getPermissionInfo(permission, 0); } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index 39de3cb642..9b44ac4b41 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -1150,7 +1150,7 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { JNIEnv *env = ThreadAndroid::get_env(); - jclass activityClass = env->FindClass("org/godotengine/godot/Godot"); + jclass activityClass = env->FindClass("android/app/Activity"); jmethodID getClassLoader = env->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); classLoader = env->CallObjectMethod(p_activity, getClassLoader); classLoader = (jclass)env->NewGlobalRef(classLoader); diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 8667727b1d..4610b94363 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -77,14 +77,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setVirtualKeyboardHei } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion) { initialized = true; JavaVM *jvm; env->GetJavaVM(&jvm); // create our wrapper classes - godot_java = new GodotJavaWrapper(env, activity); // our activity is our godot instance is our activity.. + godot_java = new GodotJavaWrapper(env, activity, godot_instance); godot_io_java = new GodotIOJavaWrapper(env, godot_java->get_member_object("io", "Lorg/godotengine/godot/GodotIO;", env)); ThreadAndroid::make_default(jvm); @@ -109,7 +109,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *en godot_java->on_video_init(env); } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity) { +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz) { // lets cleanup if (godot_io_java) { delete godot_io_java; @@ -457,7 +457,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNI return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_RESUMED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_RESUMED); } } @@ -466,7 +466,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE return; if (os_android->get_main_loop()) { - os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APP_PAUSED); + os_android->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_PAUSED); } } } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index e8be7be0d0..07584518e5 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -37,8 +37,8 @@ // These functions can be called from within JAVA and are the means by which our JAVA implementation calls back into our C++ code. // See java/src/org/godotengine/godot/GodotLib.java for the JAVA side of this (yes that's why we have the long names) extern "C" { -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz, jobject activity); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_initialize(JNIEnv *env, jclass clazz, jobject activity, jobject godot_instance, jobject p_asset_manager, jboolean p_use_apk_expansion); +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_setup(JNIEnv *env, jclass clazz, jobjectArray p_cmdline); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_resize(JNIEnv *env, jclass clazz, jobject p_surface, jint p_width, jint p_height); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_newcontext(JNIEnv *env, jclass clazz, jobject p_surface, jboolean p_32_bits); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 8ef99dfab0..cff591d903 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -37,36 +37,47 @@ // TODO we could probably create a base class for this... -GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) { +GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance) { godot_instance = p_env->NewGlobalRef(p_godot_instance); + activity = p_env->NewGlobalRef(p_activity); // get info about our Godot class so we can get pointers and stuff... - cls = p_env->FindClass("org/godotengine/godot/Godot"); - if (cls) { - cls = (jclass)p_env->NewGlobalRef(cls); + godot_class = p_env->FindClass("org/godotengine/godot/Godot"); + if (godot_class) { + godot_class = (jclass)p_env->NewGlobalRef(godot_class); + } else { + // this is a pretty serious fail.. bail... pointers will stay 0 + return; + } + activity_class = p_env->FindClass("android/app/Activity"); + if (activity_class) { + activity_class = (jclass)p_env->NewGlobalRef(activity_class); } else { // this is a pretty serious fail.. bail... pointers will stay 0 return; } - // get some method pointers... - _on_video_init = p_env->GetMethodID(cls, "onVideoInit", "()V"); - _restart = p_env->GetMethodID(cls, "restart", "()V"); - _finish = p_env->GetMethodID(cls, "forceQuit", "()V"); - _set_keep_screen_on = p_env->GetMethodID(cls, "setKeepScreenOn", "(Z)V"); - _alert = p_env->GetMethodID(cls, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); - _get_GLES_version_code = p_env->GetMethodID(cls, "getGLESVersionCode", "()I"); - _get_clipboard = p_env->GetMethodID(cls, "getClipboard", "()Ljava/lang/String;"); - _set_clipboard = p_env->GetMethodID(cls, "setClipboard", "(Ljava/lang/String;)V"); - _request_permission = p_env->GetMethodID(cls, "requestPermission", "(Ljava/lang/String;)Z"); - _request_permissions = p_env->GetMethodID(cls, "requestPermissions", "()Z"); - _get_granted_permissions = p_env->GetMethodID(cls, "getGrantedPermissions", "()[Ljava/lang/String;"); - _init_input_devices = p_env->GetMethodID(cls, "initInputDevices", "()V"); - _get_surface = p_env->GetMethodID(cls, "getSurface", "()Landroid/view/Surface;"); - _is_activity_resumed = p_env->GetMethodID(cls, "isActivityResumed", "()Z"); - _vibrate = p_env->GetMethodID(cls, "vibrate", "(I)V"); - _get_input_fallback_mapping = p_env->GetMethodID(cls, "getInputFallbackMapping", "()Ljava/lang/String;"); - _on_godot_main_loop_started = p_env->GetMethodID(cls, "onGodotMainLoopStarted", "()V"); + // get some Godot method pointers... + _on_video_init = p_env->GetMethodID(godot_class, "onVideoInit", "()V"); + _restart = p_env->GetMethodID(godot_class, "restart", "()V"); + _finish = p_env->GetMethodID(godot_class, "forceQuit", "()V"); + _set_keep_screen_on = p_env->GetMethodID(godot_class, "setKeepScreenOn", "(Z)V"); + _alert = p_env->GetMethodID(godot_class, "alert", "(Ljava/lang/String;Ljava/lang/String;)V"); + _get_GLES_version_code = p_env->GetMethodID(godot_class, "getGLESVersionCode", "()I"); + _get_clipboard = p_env->GetMethodID(godot_class, "getClipboard", "()Ljava/lang/String;"); + _set_clipboard = p_env->GetMethodID(godot_class, "setClipboard", "(Ljava/lang/String;)V"); + _request_permission = p_env->GetMethodID(godot_class, "requestPermission", "(Ljava/lang/String;)Z"); + _request_permissions = p_env->GetMethodID(godot_class, "requestPermissions", "()Z"); + _get_granted_permissions = p_env->GetMethodID(godot_class, "getGrantedPermissions", "()[Ljava/lang/String;"); + _init_input_devices = p_env->GetMethodID(godot_class, "initInputDevices", "()V"); + _get_surface = p_env->GetMethodID(godot_class, "getSurface", "()Landroid/view/Surface;"); + _is_activity_resumed = p_env->GetMethodID(godot_class, "isActivityResumed", "()Z"); + _vibrate = p_env->GetMethodID(godot_class, "vibrate", "(I)V"); + _get_input_fallback_mapping = p_env->GetMethodID(godot_class, "getInputFallbackMapping", "()Ljava/lang/String;"); + _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); + + // get some Activity method pointers... + _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -74,27 +85,25 @@ GodotJavaWrapper::~GodotJavaWrapper() { } jobject GodotJavaWrapper::get_activity() { - // our godot instance is our activity - return godot_instance; + return activity; } jobject GodotJavaWrapper::get_member_object(const char *p_name, const char *p_class, JNIEnv *p_env) { - if (cls) { + if (godot_class) { if (p_env == nullptr) p_env = ThreadAndroid::get_env(); - jfieldID fid = p_env->GetStaticFieldID(cls, p_name, p_class); - return p_env->GetStaticObjectField(cls, fid); + jfieldID fid = p_env->GetStaticFieldID(godot_class, p_name, p_class); + return p_env->GetStaticObjectField(godot_class, fid); } else { return nullptr; } } jobject GodotJavaWrapper::get_class_loader() { - if (cls) { + if (_get_class_loader) { JNIEnv *env = ThreadAndroid::get_env(); - jmethodID getClassLoader = env->GetMethodID(cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); - return env->CallObjectMethod(godot_instance, getClassLoader); + return env->CallObjectMethod(godot_instance, _get_class_loader); } else { return nullptr; } diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 89d6b6db46..e0c3809a64 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -43,7 +43,9 @@ class GodotJavaWrapper { private: jobject godot_instance; - jclass cls; + jobject activity; + jclass godot_class; + jclass activity_class; jmethodID _on_video_init = 0; jmethodID _restart = 0; @@ -62,9 +64,10 @@ private: jmethodID _vibrate = 0; jmethodID _get_input_fallback_mapping = 0; jmethodID _on_godot_main_loop_started = 0; + jmethodID _get_class_loader = 0; public: - GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance); + GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); ~GodotJavaWrapper(); jobject get_activity(); diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub index a48629f720..b72d29149c 100644 --- a/platform/iphone/SCsub +++ b/platform/iphone/SCsub @@ -20,6 +20,9 @@ iphone_lib = [ env_ios = env.Clone() ios_lib = env_ios.add_library("iphone", iphone_lib) +# (iOS) Enable module support +env_ios.Append(CCFLAGS=["-fmodules", "-fcxx-modules"]) + def combine_libs(target=None, source=None, env=None): lib_path = target[0].srcnode().abspath diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index aadc60175b..038f6e1038 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -92,7 +92,8 @@ class EditorExportPlatformIOS : public EditorExportPlatform { String _get_linker_flags(); String _get_cpp_code(); void _fix_config_file(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &pfile, const IOSConfigData &p_config, bool p_debug); - Error _export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); + Error _export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); + Error _export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir); Error _export_icons(const Ref<EditorExportPreset> &p_preset, const String &p_iconset_dir); Vector<ExportArchitecture> _get_supported_architectures(); @@ -255,6 +256,13 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_40x40", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "optional_icons/spotlight_80x80", PROPERTY_HINT_FILE, "*.png"), "")); // Spotlight on devices with retina display + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_launch_screen_storyboard"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "storyboard/image_scale_mode", PROPERTY_HINT_ENUM, "Same as Logo,Center,Scale To Fit,Scale To Fill,Scale"), 0)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@2x", PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "storyboard/custom_image@3x", PROPERTY_HINT_FILE, "*.png"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "storyboard/use_custom_bg_color"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::COLOR, "storyboard/custom_bg_color"), Color())); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "launch_screens/generate_missing"), false)); for (uint64_t i = 0; i < sizeof(loading_screen_infos) / sizeof(loading_screen_infos[0]); ++i) { @@ -274,6 +282,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ "ad-hoc", "enterprise" }; + static const String storyboard_image_scale_mode[] = { + "center", + "scaleAspectFit", + "scaleAspectFill", + "scaleToFill" + }; String str; String strnew; str.parse_utf8((const char *)pfile.ptr(), pfile.size()); @@ -343,6 +357,9 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } else if (lines[i].find("$push_notifications") != -1) { bool is_on = p_preset->get("capabilities/push_notifications"); strnew += lines[i].replace("$push_notifications", is_on ? "1" : "0") + "\n"; + } else if (lines[i].find("$entitlements_push_notifications") != -1) { + bool is_on = p_preset->get("capabilities/push_notifications"); + strnew += lines[i].replace("$entitlements_push_notifications", is_on ? "<key>aps-environment</key><string>development</string>" : "") + "\n"; } else if (lines[i].find("$required_device_capabilities") != -1) { String capabilities; @@ -387,6 +404,60 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ } else if (lines[i].find("$photolibrary_usage_description") != -1) { String description = p_preset->get("privacy/photolibrary_usage_description"); strnew += lines[i].replace("$photolibrary_usage_description", description) + "\n"; + } else if (lines[i].find("$plist_launch_screen_name") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "<key>UILaunchStoryboardName</key>\n<string>Launch Screen</string>" : ""; + strnew += lines[i].replace("$plist_launch_screen_name", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_file_reference") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9D24B36E8000717FE1 = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = \"Launch Screen.storyboard\"; sourceTree = \"<group>\"; };" : ""; + strnew += lines[i].replace("$pbx_launch_screen_file_reference", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_copy_files") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */," : ""; + strnew += lines[i].replace("$pbx_launch_screen_copy_files", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_build_phase") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */," : ""; + strnew += lines[i].replace("$pbx_launch_screen_build_phase", value) + "\n"; + } else if (lines[i].find("$pbx_launch_screen_build_reference") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "90DD2D9E24B36E8000717FE1 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90DD2D9D24B36E8000717FE1 /* Launch Screen.storyboard */; };" : ""; + strnew += lines[i].replace("$pbx_launch_screen_build_reference", value) + "\n"; + } else if (lines[i].find("$pbx_launch_image_usage_setting") != -1) { + bool is_on = p_preset->get("storyboard/use_launch_screen_storyboard"); + String value = is_on ? "" : "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;"; + strnew += lines[i].replace("$pbx_launch_image_usage_setting", value) + "\n"; + } else if (lines[i].find("$launch_screen_image_mode") != -1) { + int image_scale_mode = p_preset->get("storyboard/image_scale_mode"); + String value; + + switch (image_scale_mode) { + case 0: { + String logo_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + bool is_on = ProjectSettings::get_singleton()->get("application/boot_splash/fullsize"); + // If custom logo is not specified, Godot does not scale default one, so we should do the same. + value = (is_on && logo_path.length() > 0) ? "scaleAspectFit" : "center"; + } break; + default: { + value = storyboard_image_scale_mode[image_scale_mode - 1]; + } + } + + strnew += lines[i].replace("$launch_screen_image_mode", value) + "\n"; + } else if (lines[i].find("$launch_screen_background_color") != -1) { + bool use_custom = p_preset->get("storyboard/use_custom_bg_color"); + Color color = use_custom ? p_preset->get("storyboard/custom_bg_color") : ProjectSettings::get_singleton()->get("application/boot_splash/bg_color"); + const String value_format = "red=\"$red\" green=\"$green\" blue=\"$blue\" alpha=\"$alpha\""; + + Dictionary value_dictionary; + value_dictionary["red"] = color.r; + value_dictionary["green"] = color.g; + value_dictionary["blue"] = color.b; + value_dictionary["alpha"] = color.a; + String value = value_format.format(value_dictionary, "$_"); + + strnew += lines[i].replace("$launch_screen_background_color", value) + "\n"; } else { strnew += lines[i] + "\n"; } @@ -588,7 +659,75 @@ Error EditorExportPlatformIOS::_export_icons(const Ref<EditorExportPreset> &p_pr return OK; } -Error EditorExportPlatformIOS::_export_loading_screens(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { +Error EditorExportPlatformIOS::_export_loading_screen_file(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { + const String custom_launch_image_2x = p_preset->get("storyboard/custom_image@2x"); + const String custom_launch_image_3x = p_preset->get("storyboard/custom_image@3x"); + + if (custom_launch_image_2x.length() > 0 && custom_launch_image_3x.length() > 0) { + Ref<Image> image; + String image_path = p_dest_dir.plus_file("splash@2x.png"); + image.instance(); + Error err = image->load(custom_launch_image_2x); + + if (err) { + image.unref(); + return err; + } + + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } + + image.unref(); + image_path = p_dest_dir.plus_file("splash@3x.png"); + image.instance(); + err = image->load(custom_launch_image_3x); + + if (err) { + image.unref(); + return err; + } + + if (image->save_png(image_path) != OK) { + return ERR_FILE_CANT_WRITE; + } + } else { + Ref<Image> splash; + + const String splash_path = ProjectSettings::get_singleton()->get("application/boot_splash/image"); + + if (!splash_path.empty()) { + splash.instance(); + const Error err = splash->load(splash_path); + if (err) { + splash.unref(); + } + } + + if (splash.is_null()) { + splash = Ref<Image>(memnew(Image(boot_splash_png))); + } + + // Using same image for both @2x and @3x + // because Godot's own boot logo uses single image for all resolutions. + // Also not using @1x image, because devices using this image variant + // are not supported by iOS 9, which is minimal target. + const String splash_png_path_2x = p_dest_dir.plus_file("splash@2x.png"); + const String splash_png_path_3x = p_dest_dir.plus_file("splash@3x.png"); + + if (splash->save_png(splash_png_path_2x) != OK) { + return ERR_FILE_CANT_WRITE; + } + + if (splash->save_png(splash_png_path_3x) != OK) { + return ERR_FILE_CANT_WRITE; + } + } + + return OK; +} + +Error EditorExportPlatformIOS::_export_loading_screen_images(const Ref<EditorExportPreset> &p_preset, const String &p_dest_dir) { DirAccess *da = DirAccess::open(p_dest_dir); ERR_FAIL_COND_V_MSG(!da, ERR_CANT_OPEN, "Cannot open directory '" + p_dest_dir + "'."); @@ -790,17 +929,33 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese String pbx_frameworks_refs; String pbx_resources_build; String pbx_resources_refs; + String pbx_embeded_frameworks; const String file_info_format = String("$build_id = {isa = PBXBuildFile; fileRef = $ref_id; };\n") + "$ref_id = {isa = PBXFileReference; lastKnownFileType = $file_type; name = \"$name\"; path = \"$file_path\"; sourceTree = \"<group>\"; };\n"; + for (int i = 0; i < p_additional_assets.size(); ++i) { + String additional_asset_info_format = file_info_format; + String build_id = (++current_id).str(); String ref_id = (++current_id).str(); + String framework_id = ""; + const IOSExportAsset &asset = p_additional_assets[i]; String type; if (asset.exported_path.ends_with(".framework")) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + type = "wrapper.framework"; + } else if (asset.exported_path.ends_with(".xcframework")) { + additional_asset_info_format += "$framework_id = {isa = PBXBuildFile; fileRef = $ref_id; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };\n"; + framework_id = (++current_id).str(); + pbx_embeded_frameworks += framework_id + ",\n"; + + type = "wrapper.xcframework"; } else if (asset.exported_path.ends_with(".dylib")) { type = "compiled.mach-o.dylib"; } else if (asset.exported_path.ends_with(".a")) { @@ -825,7 +980,10 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese format_dict["name"] = asset.exported_path.get_file(); format_dict["file_path"] = asset.exported_path; format_dict["file_type"] = type; - pbx_files += file_info_format.format(format_dict, "$_"); + if (framework_id.length() > 0) { + format_dict["framework_id"] = framework_id; + } + pbx_files += additional_asset_info_format.format(format_dict, "$_"); } // Note, frameworks like gamekit are always included in our project.pbxprof file @@ -859,6 +1017,7 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese str = str.replace("$additional_pbx_frameworks_refs", pbx_frameworks_refs); str = str.replace("$additional_pbx_resources_build", pbx_resources_build); str = str.replace("$additional_pbx_resources_refs", pbx_resources_refs); + str = str.replace("$pbx_embeded_frameworks", pbx_embeded_frameworks); CharString cs = str.utf8(); p_project_data.resize(cs.size() - 1); @@ -869,6 +1028,8 @@ void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPrese Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets) { DirAccess *filesystem_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + String binary_name = p_out_dir.get_file().get_basename(); + ERR_FAIL_COND_V_MSG(!filesystem_da, ERR_CANT_CREATE, "Cannot create DirAccess for path '" + p_out_dir + "'."); for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { String asset = p_assets[f_idx]; @@ -889,8 +1050,42 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir memdelete(filesystem_da); return ERR_FILE_NOT_FOUND; } - String additional_dir = p_is_framework && asset.ends_with(".dylib") ? "/dylibs/" : "/"; - String destination_dir = p_out_dir + additional_dir + asset.get_base_dir().replace("res://", ""); + + String base_dir = asset.get_base_dir().replace("res://", ""); + String destination_dir; + String destination; + String asset_path; + + bool create_framework = false; + + if (p_is_framework && asset.ends_with(".dylib")) { + // For iOS we need to turn .dylib into .framework + // to be able to send application to AppStore + asset_path = String("dylibs").plus_file(base_dir); + + String file_name = asset.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + asset_path = asset_path.plus_file(framework_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir.plus_file(file_name); + create_framework = true; + } else if (p_is_framework && (asset.ends_with(".framework") || asset.ends_with(".xcframework"))) { + asset_path = String("dylibs").plus_file(base_dir); + + String file_name = asset.get_file(); + asset_path = asset_path.plus_file(file_name); + destination_dir = p_out_dir.plus_file(asset_path); + destination = destination_dir; + } else { + asset_path = base_dir; + + String file_name = asset.get_file(); + destination_dir = p_out_dir.plus_file(asset_path); + asset_path = asset_path.plus_file(file_name); + destination = p_out_dir.plus_file(asset_path); + } + if (!filesystem_da->dir_exists(destination_dir)) { Error make_dir_err = filesystem_da->make_dir_recursive(destination_dir); if (make_dir_err) { @@ -900,15 +1095,66 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir } } - String destination = destination_dir.plus_file(asset.get_file()); Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination); memdelete(da); if (err) { memdelete(filesystem_da); return err; } - IOSExportAsset exported_asset = { destination, p_is_framework }; + IOSExportAsset exported_asset = { binary_name.plus_file(asset_path), p_is_framework }; r_exported_assets.push_back(exported_asset); + + if (create_framework) { + String file_name = asset.get_basename().get_file(); + String framework_name = file_name + ".framework"; + + // Performing `install_name_tool -id @rpath/{name}.framework/{name} ./{name}` on dylib + { + List<String> install_name_args; + install_name_args.push_back("-id"); + install_name_args.push_back(String("@rpath").plus_file(framework_name).plus_file(file_name)); + install_name_args.push_back(destination); + + OS::get_singleton()->execute("install_name_tool", install_name_args, true); + } + + // Creating Info.plist + { + String info_plist_format = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">\n" + "<dict>\n" + "<key>CFBundleShortVersionString</key>\n" + "<string>1.0</string>\n" + "<key>CFBundleIdentifier</key>\n" + "<string>com.gdnative.framework.$name</string>\n" + "<key>CFBundleName</key>\n" + "<string>$name</string>\n" + "<key>CFBundleExecutable</key>\n" + "<string>$name</string>\n" + "<key>DTPlatformName</key>\n" + "<string>iphoneos</string>\n" + "<key>CFBundleInfoDictionaryVersion</key>\n" + "<string>6.0</string>\n" + "<key>CFBundleVersion</key>\n" + "<string>1</string>\n" + "<key>CFBundlePackageType</key>\n" + "<string>FMWK</string>\n" + "<key>MinimumOSVersion</key>\n" + "<string>10.0</string>\n" + "</dict>\n" + "</plist>"; + + String info_plist = info_plist_format.replace("$name", file_name); + + FileAccess *f = FileAccess::open(destination_dir.plus_file("Info.plist"), FileAccess::WRITE); + if (f) { + f->store_string(info_plist); + f->close(); + memdelete(f); + } + } + } } } memdelete(filesystem_da); @@ -1066,6 +1312,8 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p files_to_parse.insert("godot_ios/dummy.cpp"); files_to_parse.insert("godot_ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata"); files_to_parse.insert("godot_ios.xcodeproj/xcshareddata/xcschemes/godot_ios.xcscheme"); + files_to_parse.insert("godot_ios/godot_ios.entitlements"); + files_to_parse.insert("godot_ios/Launch Screen.storyboard"); IOSConfigData config_data = { pkg_name, @@ -1241,7 +1489,43 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p return err; } - err = _export_loading_screens(p_preset, dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"); + bool use_storyboard = p_preset->get("storyboard/use_launch_screen_storyboard"); + + String launch_image_path = dest_dir + binary_name + "/Images.xcassets/LaunchImage.launchimage/"; + String splash_image_path = dest_dir + binary_name + "/Images.xcassets/SplashImage.imageset/"; + + DirAccess *launch_screen_da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + + if (!launch_screen_da) { + return ERR_CANT_CREATE; + } + + if (use_storyboard) { + print_line("Using Launch Storyboard"); + + if (launch_screen_da->change_dir(launch_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(launch_image_path); + } + + err = _export_loading_screen_file(p_preset, splash_image_path); + } else { + print_line("Using Launch Images"); + + const String launch_screen_path = dest_dir + binary_name + "/Launch Screen.storyboard"; + + launch_screen_da->remove(launch_screen_path); + + if (launch_screen_da->change_dir(splash_image_path) == OK) { + launch_screen_da->erase_contents_recursive(); + launch_screen_da->remove(splash_image_path); + } + + err = _export_loading_screen_images(p_preset, launch_image_path); + } + + memdelete(launch_screen_da); + if (err) { return err; } diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm index a2efd6691b..548dcc549d 100644 --- a/platform/iphone/in_app_store.mm +++ b/platform/iphone/in_app_store.mm @@ -207,7 +207,7 @@ Error InAppStore::restore_purchases() { NSString *receipt_to_send = nil; if (receipt != nil) { - receipt_to_send = [receipt description]; + receipt_to_send = [receipt base64EncodedStringWithOptions:0]; } Dictionary receipt_ret; receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : ""); diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index b8914414e6..9604914b2c 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -36,6 +36,15 @@ AudioDriverJavaScript *AudioDriverJavaScript::singleton = nullptr; +bool AudioDriverJavaScript::is_available() { + return EM_ASM_INT({ + if (!(window.AudioContext || window.webkitAudioContext)) { + return 0; + } + return 1; + }) != 0; +} + const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } @@ -207,12 +216,14 @@ void AudioDriverJavaScript::finish_async() { /* clang-format off */ EM_ASM({ - var ref = Module.IDHandler.get($0); + const id = $0; + var ref = Module.IDHandler.get(id); Module.async_finish.push(new Promise(function(accept, reject) { if (!ref) { - console.log("Ref not found!", $0, Module.IDHandler); + console.log("Ref not found!", id, Module.IDHandler); setTimeout(accept, 0); } else { + Module.IDHandler.remove(id); const context = ref['context']; // Disconnect script and input. ref['script'].disconnect(); @@ -226,7 +237,6 @@ void AudioDriverJavaScript::finish_async() { }); } })); - Module.IDHandler.remove($0); }, id); /* clang-format on */ } @@ -293,9 +303,5 @@ Error AudioDriverJavaScript::capture_stop() { } AudioDriverJavaScript::AudioDriverJavaScript() { - _driver_id = 0; - internal_buffer = nullptr; - buffer_length = 0; - singleton = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index 9b26be001e..f029a91db0 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -34,12 +34,13 @@ #include "servers/audio_server.h" class AudioDriverJavaScript : public AudioDriver { - float *internal_buffer; + float *internal_buffer = nullptr; - int _driver_id; - int buffer_length; + int _driver_id = 0; + int buffer_length = 0; public: + static bool is_available(); void mix_to_js(); void process_capture(float sample); diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index 0312efb377..2f0a2faa83 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -44,18 +44,15 @@ #define DOM_BUTTON_XBUTTON1 3 #define DOM_BUTTON_XBUTTON2 4 +char DisplayServerJavaScript::canvas_id[256] = { 0 }; +static bool cursor_inside_canvas = true; + DisplayServerJavaScript *DisplayServerJavaScript::get_singleton() { return static_cast<DisplayServerJavaScript *>(DisplayServer::get_singleton()); } // Window (canvas) -extern "C" EMSCRIPTEN_KEEPALIVE void _set_canvas_id(uint8_t *p_data, int p_data_size) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); - display->canvas_id.parse_utf8((const char *)p_data, p_data_size); - display->canvas_id = "#" + display->canvas_id; -} - -static void focus_canvas() { +void DisplayServerJavaScript::focus_canvas() { /* clang-format off */ EM_ASM( Module['canvas'].focus(); @@ -63,7 +60,7 @@ static void focus_canvas() { /* clang-format on */ } -static bool is_canvas_focused() { +bool DisplayServerJavaScript::is_canvas_focused() { /* clang-format off */ return EM_ASM_INT_V( return document.activeElement == Module['canvas']; @@ -71,8 +68,21 @@ static bool is_canvas_focused() { /* clang-format on */ } -static Point2 compute_position_in_canvas(int x, int y) { - DisplayServerJavaScript *display = DisplayServerJavaScript::get_singleton(); +bool DisplayServerJavaScript::check_size_force_redraw() { + int canvas_width; + int canvas_height; + emscripten_get_canvas_element_size(DisplayServerJavaScript::canvas_id, &canvas_width, &canvas_height); + if (last_width != canvas_width || last_height != canvas_height) { + last_width = canvas_width; + last_height = canvas_height; + // Update the framebuffer size and for redraw. + emscripten_set_canvas_element_size(DisplayServerJavaScript::canvas_id, canvas_width, canvas_height); + return true; + } + return false; +} + +Point2 DisplayServerJavaScript::compute_position_in_canvas(int p_x, int p_y) { int canvas_x = EM_ASM_INT({ return Module['canvas'].getBoundingClientRect().x; }); @@ -81,23 +91,22 @@ static Point2 compute_position_in_canvas(int x, int y) { }); int canvas_width; int canvas_height; - emscripten_get_canvas_element_size(display->canvas_id.utf8().get_data(), &canvas_width, &canvas_height); + emscripten_get_canvas_element_size(canvas_id, &canvas_width, &canvas_height); double element_width; double element_height; - emscripten_get_element_css_size(display->canvas_id.utf8().get_data(), &element_width, &element_height); + emscripten_get_element_css_size(canvas_id, &element_width, &element_height); - return Point2((int)(canvas_width / element_width * (x - canvas_x)), - (int)(canvas_height / element_height * (y - canvas_y))); + return Point2((int)(canvas_width / element_width * (p_x - canvas_x)), + (int)(canvas_height / element_height * (p_y - canvas_y))); } -static bool cursor_inside_canvas = true; - EM_BOOL DisplayServerJavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) { DisplayServerJavaScript *display = get_singleton(); // Empty ID is canvas. String target_id = String::utf8(p_event->id); - if (target_id.empty() || "#" + target_id == display->canvas_id) { + String canvas_str_id = String::utf8(canvas_id); + if (target_id.empty() || target_id == canvas_str_id) { // This event property is the only reliable data on // browser fullscreen state. if (p_event->isFullscreen) { @@ -131,14 +140,14 @@ extern "C" EMSCRIPTEN_KEEPALIVE void _drop_files_callback(char *p_filev[], int p // Keys template <typename T> -static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { +void DisplayServerJavaScript::dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event) { godot_event->set_shift(emscripten_event_ptr->shiftKey); godot_event->set_alt(emscripten_event_ptr->altKey); godot_event->set_control(emscripten_event_ptr->ctrlKey); godot_event->set_metakey(emscripten_event_ptr->metaKey); } -static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { +Ref<InputEventKey> DisplayServerJavaScript::setup_key_event(const EmscriptenKeyboardEvent *emscripten_event) { Ref<InputEventKey> ev; ev.instance(); ev->set_echo(emscripten_event->repeat); @@ -289,7 +298,7 @@ EM_BOOL DisplayServerJavaScript::mousemove_callback(int p_event_type, const Emsc } // Cursor -static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { +const char *DisplayServerJavaScript::godot2dom_cursor(DisplayServer::CursorShape p_shape) { switch (p_shape) { case DisplayServer::CURSOR_ARROW: return "auto"; @@ -330,7 +339,7 @@ static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape) { } } -static void set_css_cursor(const char *p_cursor) { +void DisplayServerJavaScript::set_css_cursor(const char *p_cursor) { /* clang-format off */ EM_ASM_({ Module['canvas'].style.cursor = UTF8ToString($0); @@ -338,7 +347,7 @@ static void set_css_cursor(const char *p_cursor) { /* clang-format on */ } -static bool is_css_cursor_hidden() { +bool DisplayServerJavaScript::is_css_cursor_hidden() const { /* clang-format off */ return EM_ASM_INT({ return Module['canvas'].style.cursor === 'none'; @@ -820,23 +829,6 @@ DisplayServer *DisplayServerJavaScript::create_func(const String &p_rendering_dr } DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) { - /* clang-format off */ - EM_ASM({ - const canvas = Module['canvas']; - var enc = new TextEncoder("utf-8"); - var buffer = new Uint8Array(enc.encode(canvas.id)); - var len = buffer.byteLength; - var out = _malloc(len); - HEAPU8.set(buffer, out); - ccall("_set_canvas_id", - "void", - ["number", "number"], - [out, len] - ); - _free(out); - }); - /* clang-format on */ - RasterizerDummy::make_current(); // TODO GLES2 in Godot 4.0... or webgpu? #if 0 EmscriptenWebGLContextAttributes attributes; @@ -859,7 +851,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive gl_initialization_error = true; } - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id.utf8().get_data(), &attributes); + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(canvas_id, &attributes); if (emscripten_webgl_make_context_current(ctx) != EMSCRIPTEN_RESULT_SUCCESS) { gl_initialization_error = true; } @@ -881,7 +873,6 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive } EMSCRIPTEN_RESULT result; - CharString id = canvas_id.utf8(); #define EM_CHECK(ev) \ if (result != EMSCRIPTEN_RESULT_SUCCESS) \ ERR_PRINT("Error while setting " #ev " callback: Code " + itos(result)); @@ -895,16 +886,16 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive // JavaScript APIs. For APIs that are not (sufficiently) exposed, EM_ASM // is used below. SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mousemove, mousemove_callback) - SET_EM_CALLBACK(id.get_data(), mousedown, mouse_button_callback) + SET_EM_CALLBACK(canvas_id, mousedown, mouse_button_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_WINDOW, mouseup, mouse_button_callback) - SET_EM_CALLBACK(id.get_data(), wheel, wheel_callback) - SET_EM_CALLBACK(id.get_data(), touchstart, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchmove, touchmove_callback) - SET_EM_CALLBACK(id.get_data(), touchend, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), touchcancel, touch_press_callback) - SET_EM_CALLBACK(id.get_data(), keydown, keydown_callback) - SET_EM_CALLBACK(id.get_data(), keypress, keypress_callback) - SET_EM_CALLBACK(id.get_data(), keyup, keyup_callback) + SET_EM_CALLBACK(canvas_id, wheel, wheel_callback) + SET_EM_CALLBACK(canvas_id, touchstart, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchmove, touchmove_callback) + SET_EM_CALLBACK(canvas_id, touchend, touch_press_callback) + SET_EM_CALLBACK(canvas_id, touchcancel, touch_press_callback) + SET_EM_CALLBACK(canvas_id, keydown, keydown_callback) + SET_EM_CALLBACK(canvas_id, keypress, keypress_callback) + SET_EM_CALLBACK(canvas_id, keyup, keyup_callback) SET_EM_CALLBACK(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, fullscreenchange, fullscreen_change_callback) SET_EM_CALLBACK_NOTARGET(gamepadconnected, gamepad_change_callback) SET_EM_CALLBACK_NOTARGET(gamepaddisconnected, gamepad_change_callback) @@ -1012,7 +1003,7 @@ Size2i DisplayServerJavaScript::screen_get_size(int p_screen) const { Rect2i DisplayServerJavaScript::screen_get_usable_rect(int p_screen) const { int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); return Rect2i(0, 0, canvas[0], canvas[1]); } @@ -1103,12 +1094,14 @@ Size2i DisplayServerJavaScript::window_get_min_size(WindowID p_window) const { } void DisplayServerJavaScript::window_set_size(const Size2i p_size, WindowID p_window) { - emscripten_set_canvas_element_size(canvas_id.utf8().get_data(), p_size.x, p_size.y); + last_width = p_size.x; + last_height = p_size.y; + emscripten_set_canvas_element_size(canvas_id, p_size.x, p_size.y); } Size2i DisplayServerJavaScript::window_get_size(WindowID p_window) const { int canvas[2]; - emscripten_get_canvas_element_size(canvas_id.utf8().get_data(), canvas, canvas + 1); + emscripten_get_canvas_element_size(canvas_id, canvas, canvas + 1); return Size2(canvas[0], canvas[1]); } @@ -1134,7 +1127,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind strategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_STDDEF; strategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; strategy.canvasResizedCallback = nullptr; - EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id.utf8().get_data(), false, &strategy); + EMSCRIPTEN_RESULT result = emscripten_request_fullscreen_strategy(canvas_id, false, &strategy); ERR_FAIL_COND_MSG(result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); ERR_FAIL_COND_MSG(result != EMSCRIPTEN_RESULT_SUCCESS, "Enabling fullscreen is only possible from an input callback for the HTML5 platform."); } break; diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h index 9860ecdf98..b149665d67 100644 --- a/platform/javascript/display_server_javascript.h +++ b/platform/javascript/display_server_javascript.h @@ -53,6 +53,21 @@ class DisplayServerJavaScript : public DisplayServer { double last_click_ms = 0; int last_click_button_index = -1; + int last_width = 0; + int last_height = 0; + + // utilities + static Point2 compute_position_in_canvas(int p_x, int p_y); + static void focus_canvas(); + static bool is_canvas_focused(); + template <typename T> + static void dom2godot_mod(T *emscripten_event_ptr, Ref<InputEventWithModifiers> godot_event); + static Ref<InputEventKey> setup_key_event(const EmscriptenKeyboardEvent *emscripten_event); + static const char *godot2dom_cursor(DisplayServer::CursorShape p_shape); + static void set_css_cursor(const char *p_cursor); + bool is_css_cursor_hidden() const; + + // events static EM_BOOL fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data); static EM_BOOL keydown_callback(int p_event_type, const EmscriptenKeyboardEvent *p_event, void *p_user_data); @@ -81,17 +96,20 @@ protected: public: // Override return type to make writing static callbacks less tedious. static DisplayServerJavaScript *get_singleton(); + static char canvas_id[256]; WindowMode window_mode = WINDOW_MODE_WINDOWED; String clipboard; - String canvas_id; Callable window_event_callback; Callable input_event_callback; Callable input_text_callback; Callable drop_files_callback; + // utilities + bool check_size_force_redraw(); + // from DisplayServer virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual bool has_feature(Feature p_feature) const; diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index fd61c46e63..99672745e7 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -30,11 +30,13 @@ #include "core/io/resource_loader.h" #include "main/main.h" -#include "os_javascript.h" +#include "platform/javascript/display_server_javascript.h" +#include "platform/javascript/os_javascript.h" #include <emscripten/emscripten.h> static OS_JavaScript *os = nullptr; +static uint64_t target_ticks = 0; void exit_callback() { emscripten_cancel_main_loop(); // After this, we can exit! @@ -46,12 +48,32 @@ void exit_callback() { } void main_loop_callback() { + uint64_t current_ticks = os->get_ticks_usec(); + + bool force_draw = DisplayServerJavaScript::get_singleton()->check_size_force_redraw(); + if (force_draw) { + Main::force_redraw(); + } else if (current_ticks < target_ticks) { + return; // Skip frame. + } + + int target_fps = Engine::get_singleton()->get_target_fps(); + if (target_fps > 0) { + target_ticks += (uint64_t)(1000000 / target_fps); + } if (os->main_loop_iterate()) { emscripten_cancel_main_loop(); // Cancel current loop and wait for finalize_async. + /* clang-format off */ EM_ASM({ // This will contain the list of operations that need to complete before cleanup. - Module.async_finish = []; + Module.async_finish = [ + // Always contains at least one async promise, to avoid firing immediately if nothing is added. + new Promise(function(accept, reject) { + setTimeout(accept, 0); + }) + ]; }); + /* clang-format on */ os->get_main_loop()->finish(); os->finalize_async(); // Will add all the async finish functions. EM_ASM({ @@ -79,13 +101,35 @@ extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) { ResourceLoader::set_abort_on_missing_resources(false); Main::start(); os->get_main_loop()->init(); - emscripten_resume_main_loop(); // Immediately run the first iteration. // We are inside an animation frame, we want to immediately draw on the newly setup canvas. main_loop_callback(); + emscripten_resume_main_loop(); } int main(int argc, char *argv[]) { + // Create and mount userfs immediately. + EM_ASM({ + FS.mkdir('/userfs'); + FS.mount(IDBFS, {}, '/userfs'); + }); + + // Configure locale. + char locale_ptr[16]; + /* clang-format off */ + EM_ASM({ + stringToUTF8(Module['locale'], $0, 16); + }, locale_ptr); + /* clang-format on */ + setenv("LANG", locale_ptr, true); + + // Ensure the canvas ID. + /* clang-format off */ + EM_ASM({ + stringToUTF8("#" + Module['canvas'].id, $0, 255); + }, DisplayServerJavaScript::canvas_id); + /* clang-format on */ + os = new OS_JavaScript(); Main::setup(argv[0], argc - 1, &argv[1], false); emscripten_set_main_loop(main_loop_callback, -1, false); @@ -95,8 +139,6 @@ int main(int argc, char *argv[]) { // run the 'main_after_fs_sync' function. /* clang-format off */ EM_ASM({ - FS.mkdir('/userfs'); - FS.mount(IDBFS, {}, '/userfs'); FS.syncfs(true, function(err) { requestAnimationFrame(function() { ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]); diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index ad4b5a5afa..1ff4304bcf 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -74,18 +74,12 @@ void OS_JavaScript::initialize() { EngineDebugger::register_uri_handler("ws://", RemoteDebuggerPeerWebSocket::create); EngineDebugger::register_uri_handler("wss://", RemoteDebuggerPeerWebSocket::create); #endif - - char locale_ptr[16]; - /* clang-format off */ - EM_ASM({ - stringToUTF8(Module['locale'], $0, 16); - }, locale_ptr); - /* clang-format on */ - setenv("LANG", locale_ptr, true); } void OS_JavaScript::resume_audio() { - audio_driver_javascript.resume(); + if (audio_driver_javascript) { + audio_driver_javascript->resume(); + } } void OS_JavaScript::set_main_loop(MainLoop *p_main_loop) { @@ -133,11 +127,17 @@ void OS_JavaScript::delete_main_loop() { void OS_JavaScript::finalize_async() { finalizing = true; - audio_driver_javascript.finish_async(); + if (audio_driver_javascript) { + audio_driver_javascript->finish_async(); + } } void OS_JavaScript::finalize() { delete_main_loop(); + if (audio_driver_javascript) { + memdelete(audio_driver_javascript); + audio_driver_javascript = nullptr; + } } // Miscellaneous @@ -246,7 +246,10 @@ void OS_JavaScript::initialize_joypads() { } OS_JavaScript::OS_JavaScript() { - AudioDriverManager::add_driver(&audio_driver_javascript); + if (AudioDriverJavaScript::is_available()) { + audio_driver_javascript = memnew(AudioDriverJavaScript); + AudioDriverManager::add_driver(audio_driver_javascript); + } Vector<Logger *> loggers; loggers.push_back(memnew(StdLogger)); diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index f0f18b44f8..22234f9355 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -40,7 +40,7 @@ class OS_JavaScript : public OS_Unix { MainLoop *main_loop = nullptr; - AudioDriverJavaScript audio_driver_javascript; + AudioDriverJavaScript *audio_driver_javascript = nullptr; bool finalizing = false; bool idb_available = false; @@ -83,6 +83,9 @@ public: String get_executable_path() const; virtual Error shell_open(String p_uri); virtual String get_name() const; + // Override default OS implementation which would block the main thread with delay_usec. + // Implemented in javascript_main.cpp loop callback instead. + virtual void add_frame_delay(bool p_can_draw) {} virtual bool can_draw() const; virtual String get_cache_path() const; diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp index a0954600a2..c9b951f4d9 100644 --- a/platform/linuxbsd/display_server_x11.cpp +++ b/platform/linuxbsd/display_server_x11.cpp @@ -32,12 +32,12 @@ #ifdef X11_ENABLED -#include "detect_prime_x11.h" - -#include "core/os/dir_access.h" #include "core/print_string.h" -#include "errno.h" +#include "core/project_settings.h" +#include "detect_prime_x11.h" #include "key_mapping_x11.h" +#include "main/main.h" +#include "scene/resources/texture.h" #if defined(OPENGL_ENABLED) #include "drivers/gles2/rasterizer_gles2.h" @@ -47,20 +47,14 @@ #include "servers/rendering/rasterizer_rd/rasterizer_rd.h" #endif -#include "scene/resources/texture.h" - -#ifdef HAVE_MNTENT -#include <mntent.h> -#endif - #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "X11/Xutil.h" +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xinerama.h> -#include "X11/Xatom.h" -#include "X11/extensions/Xinerama.h" // ICCCM #define WM_NormalState 1L // window normal state #define WM_IconicState 3L // window minimized @@ -69,8 +63,6 @@ #define _NET_WM_STATE_ADD 1L // add/set property #define _NET_WM_STATE_TOGGLE 2L // toggle property -#include "main/main.h" - #include <dlfcn.h> #include <fcntl.h> #include <sys/stat.h> @@ -82,14 +74,9 @@ #undef KEY_TAB #endif -#include <X11/Xatom.h> - #undef CursorShape - #include <X11/XKBlib.h> -#include "core/project_settings.h" - // 2.2 is the first release with multitouch #define XINPUT_CLIENT_VERSION_MAJOR 2 #define XINPUT_CLIENT_VERSION_MINOR 2 @@ -417,17 +404,24 @@ void DisplayServerX11::mouse_warp_to_position(const Point2i &p_to) { if (mouse_mode == MOUSE_MODE_CAPTURED) { last_mouse_pos = p_to; } else { - /*XWindowAttributes xwa; - XGetWindowAttributes(x11_display, x11_window, &xwa); - printf("%d %d\n", xwa.x, xwa.y); needed? */ - XWarpPointer(x11_display, None, windows[MAIN_WINDOW_ID].x11_window, 0, 0, 0, 0, (int)p_to.x, (int)p_to.y); } } Point2i DisplayServerX11::mouse_get_position() const { - return last_mouse_pos; + int root_x, root_y; + int win_x, win_y; + unsigned int mask_return; + Window window_returned; + + Bool result = XQueryPointer(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), &window_returned, + &window_returned, &root_x, &root_y, &win_x, &win_y, + &mask_return); + if (result == True) { + return Point2i(root_x, root_y); + } + return Point2i(); } Point2i DisplayServerX11::mouse_get_absolute_position() const { @@ -735,6 +729,14 @@ ObjectID DisplayServerX11::window_get_attached_instance_id(WindowID p_window) co } DisplayServerX11::WindowID DisplayServerX11::get_window_at_screen_position(const Point2i &p_position) const { +#warning This is an incorrect implementation, if windows overlap, it should return the topmost visible one or none if occluded by a foreign window + + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key())); + if (win_rect.has_point(p_position)) { + return E->key(); + } + } return INVALID_WINDOW_ID; } @@ -1098,18 +1100,19 @@ Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { return Size2i(w, h); } -bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { - _THREAD_SAFE_METHOD_ - +// Just a helper to reduce code duplication in `window_is_maximize_allowed` +// and `_set_wm_maximized`. +bool DisplayServerX11::_window_maximize_check(WindowID p_window, const char *p_atom_name) const { ERR_FAIL_COND_V(!windows.has(p_window), false); const WindowData &wd = windows[p_window]; - Atom property = XInternAtom(x11_display, "_NET_WM_ALLOWED_ACTIONS", False); + Atom property = XInternAtom(x11_display, p_atom_name, False); Atom type; int format; unsigned long len; unsigned long remaining; unsigned char *data = nullptr; + bool retval = false; int result = XGetWindowProperty( x11_display, @@ -1141,13 +1144,20 @@ bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { } if (found_wm_act_max_horz || found_wm_act_max_vert) { - return true; + retval = true; + break; } } - XFree(atoms); + + XFree(data); } - return false; + return retval; +} + +bool DisplayServerX11::window_is_maximize_allowed(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + return _window_maximize_check(p_window, "_NET_WM_ALLOWED_ACTIONS"); } void DisplayServerX11::_set_wm_maximized(WindowID p_window, bool p_enabled) { @@ -1385,60 +1395,14 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c if (wd.fullscreen) { //if fullscreen, it's not in another mode return WINDOW_MODE_FULLSCREEN; } - { //test maximized - // Using EWMH -- Extended Window Manager Hints - Atom property = XInternAtom(x11_display, "_NET_WM_STATE", False); - Atom type; - int format; - unsigned long len; - unsigned long remaining; - unsigned char *data = nullptr; - bool retval = false; - int result = XGetWindowProperty( - x11_display, - wd.x11_window, - property, - 0, - 1024, - False, - XA_ATOM, - &type, - &format, - &len, - &remaining, - &data); - - if (result == Success && data) { - Atom *atoms = (Atom *)data; - Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False); - Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False); - bool found_wm_max_horz = false; - bool found_wm_max_vert = false; - - for (uint64_t i = 0; i < len; i++) { - if (atoms[i] == wm_max_horz) { - found_wm_max_horz = true; - } - if (atoms[i] == wm_max_vert) { - found_wm_max_vert = true; - } - - if (found_wm_max_horz && found_wm_max_vert) { - retval = true; - break; - } - } - - XFree(data); - } - - if (retval) { - return WINDOW_MODE_MAXIMIZED; - } + // Test maximized. + // Using EWMH -- Extended Window Manager Hints + if (_window_maximize_check(p_window, "_NET_WM_STATE")) { + return WINDOW_MODE_MAXIMIZED; } - { // test minimzed + { // Test minimized. // Using ICCCM -- Inter-Client Communication Conventions Manual Atom property = XInternAtom(x11_display, "WM_STATE", True); Atom type; @@ -1471,7 +1435,7 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c } } - // all other discarded, return windowed. + // All other discarded, return windowed. return WINDOW_MODE_WINDOWED; } @@ -2394,6 +2358,31 @@ void DisplayServerX11::_send_window_event(const WindowData &wd, WindowEvent p_ev void DisplayServerX11::process_events() { _THREAD_SAFE_METHOD_ + if (app_focused) { + //verify that one of the windows has focus, else send focus out notification + bool focus_found = false; + for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { + if (E->get().focused) { + focus_found = true; + break; + } + } + + if (!focus_found) { + uint64_t delta = OS::get_singleton()->get_ticks_msec() - time_since_no_focus; + + if (delta > 250) { + //X11 can go between windows and have no focus for a while, when creating them or something else. Use this as safety to avoid unnecesary focus in/outs. + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } + app_focused = false; + } + } else { + time_since_no_focus = OS::get_singleton()->get_ticks_msec(); + } + } + do_mouse_warp = false; // Is the current mouse mode one where it needs to be grabbed. @@ -2588,12 +2577,12 @@ void DisplayServerX11::process_events() { break; case NoExpose: - minimized = true; + windows[window_id].minimized = true; break; case VisibilityNotify: { XVisibilityEvent *visibility = (XVisibilityEvent *)&event; - minimized = (visibility->state == VisibilityFullyObscured); + windows[window_id].minimized = (visibility->state == VisibilityFullyObscured); } break; case LeaveNotify: { if (!mouse_mode_grab) { @@ -2607,10 +2596,8 @@ void DisplayServerX11::process_events() { } } break; case FocusIn: - minimized = false; - window_has_focus = true; + windows[window_id].focused = true; _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_IN); - window_focused = true; if (mouse_mode_grab) { // Show and update the cursor if confined and the window regained focus. @@ -2637,13 +2624,19 @@ void DisplayServerX11::process_events() { if (windows[window_id].xic) { XSetICFocus(windows[window_id].xic); } + + 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 FocusOut: - window_has_focus = false; + windows[window_id].focused = false; Input::get_singleton()->release_pressed_events(); _send_window_event(windows[window_id], WINDOW_EVENT_FOCUS_OUT); - window_focused = false; if (mouse_mode_grab) { for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { @@ -2782,7 +2775,7 @@ void DisplayServerX11::process_events() { Point2i new_center = pos; pos = last_mouse_pos + xi.relative_motion; center = new_center; - do_mouse_warp = window_has_focus; // warp the cursor if we're focused in + do_mouse_warp = windows[window_id].focused; // warp the cursor if we're focused in } if (!last_mouse_pos_valid) { @@ -2842,7 +2835,7 @@ void DisplayServerX11::process_events() { // Don't propagate the motion event unless we have focus // this is so that the relative motion doesn't get messed up // after we regain focus. - if (window_has_focus || !mouse_mode_grab) { + if (windows[window_id].focused || !mouse_mode_grab) { Input::get_singleton()->accumulate_input_event(mm); } @@ -3819,8 +3812,6 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode requested = None; - window_has_focus = true; // Set focus to true at init - /*if (p_desired.layered) { set_window_per_pixel_transparency_enabled(true); }*/ diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h index f01b9a2323..b5d2ea1c63 100644 --- a/platform/linuxbsd/display_server_x11.h +++ b/platform/linuxbsd/display_server_x11.h @@ -36,7 +36,6 @@ #include "servers/display_server.h" #include "core/input/input.h" - #include "drivers/alsa/audio_driver_alsa.h" #include "drivers/alsamidi/midi_driver_alsamidi.h" #include "drivers/pulseaudio/audio_driver_pulseaudio.h" @@ -140,6 +139,8 @@ class DisplayServerX11 : public DisplayServer { bool borderless = false; bool resize_disabled = false; Vector2i last_position_before_fs; + bool focused = false; + bool minimized = false; }; Map<WindowID, WindowData> windows; @@ -165,6 +166,8 @@ class DisplayServerX11 : public DisplayServer { uint64_t last_click_ms; int last_click_button_index; uint32_t last_button_state; + bool app_focused = false; + uint64_t time_since_no_focus = 0; struct { int opcode; @@ -196,8 +199,8 @@ class DisplayServerX11 : public DisplayServer { void _handle_key_event(WindowID p_window, XKeyEvent *p_event, bool p_echo = false); - bool minimized; - bool window_has_focus; + //bool minimized; + //bool window_has_focus; bool do_mouse_warp; const char *cursor_theme; @@ -211,7 +214,7 @@ class DisplayServerX11 : public DisplayServer { bool layered_window; String rendering_driver; - bool window_focused; + //bool window_focused; //void set_wm_border(bool p_enabled); void set_wm_fullscreen(bool p_enabled); void set_wm_above(bool p_enabled); @@ -231,6 +234,7 @@ class DisplayServerX11 : public DisplayServer { static Property _read_property(Display *p_display, Window p_window, Atom p_property); void _update_real_mouse_position(const WindowData &wd); + bool _window_maximize_check(WindowID p_window, const char *p_atom_name) const; void _set_wm_fullscreen(WindowID p_window, bool p_enabled); void _set_wm_maximized(WindowID p_window, bool p_enabled); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 09a5eca914..8c6f3b1167 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -31,8 +31,11 @@ #include "os_linuxbsd.h" #include "core/os/dir_access.h" -#include "core/print_string.h" -#include "errno.h" +#include "main/main.h" + +#ifdef X11_ENABLED +#include "display_server_x11.h" +#endif #ifdef HAVE_MNTENT #include <mntent.h> @@ -48,12 +51,6 @@ #include <sys/types.h> #include <unistd.h> -#include "main/main.h" - -#ifdef X11_ENABLED -#include "display_server_x11.h" -#endif - void OS_LinuxBSD::initialize() { crash_handler.initialize(); diff --git a/platform/osx/detect.py b/platform/osx/detect.py index 29aa8ece19..ff4c024551 100644 --- a/platform/osx/detect.py +++ b/platform/osx/detect.py @@ -87,8 +87,15 @@ def configure(env): env["osxcross"] = True if not "osxcross" in env: # regular native build - env.Append(CCFLAGS=["-arch", "x86_64"]) - env.Append(LINKFLAGS=["-arch", "x86_64"]) + if env["arch"] == "arm64": + print("Building for macOS 10.15+, platform arm64.") + env.Append(CCFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + env.Append(LINKFLAGS=["-arch", "arm64", "-mmacosx-version-min=10.15", "-target", "arm64-apple-macos10.15"]) + else: + print("Building for macOS 10.12+, platform x86-64.") + env.Append(CCFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + env.Append(LINKFLAGS=["-arch", "x86_64", "-mmacosx-version-min=10.12"]) + if env["macports_clang"] != "no": mpprefix = os.environ.get("MACPORTS_PREFIX", "/opt/local") mpclangver = env["macports_clang"] @@ -148,7 +155,8 @@ def configure(env): ## Dependencies if env["builtin_libtheora"]: - env["x86_libtheora_opt_gcc"] = True + if env["arch"] != "arm64": + env["x86_libtheora_opt_gcc"] = True ## Flags @@ -189,6 +197,3 @@ def configure(env): env.Append(LIBS=["vulkan"]) # env.Append(CPPDEFINES=['GLES_ENABLED', 'OPENGL_ENABLED']) - - env.Append(CCFLAGS=["-mmacosx-version-min=10.12"]) - env.Append(LINKFLAGS=["-mmacosx-version-min=10.12"]) diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h index fddb1d0ca6..3e6b59f58c 100644 --- a/platform/osx/display_server_osx.h +++ b/platform/osx/display_server_osx.h @@ -86,6 +86,14 @@ public: uint32_t unicode; }; + struct WarpEvent { + NSTimeInterval timestamp; + NSPoint delta; + }; + + List<WarpEvent> warp_events; + NSTimeInterval last_warp = 0; + Vector<KeyEvent> key_event_buffer; int key_event_pos; @@ -124,6 +132,7 @@ public: bool on_top = false; bool borderless = false; bool resize_disabled = false; + bool no_focus = false; }; Point2i im_selection; @@ -142,7 +151,6 @@ public: void _set_window_per_pixel_transparency_enabled(bool p_enabled, WindowID p_window); - float _display_scale(id screen) const; Point2i _get_screens_origin() const; Point2i _get_native_screen_position(int p_screen) const; @@ -216,11 +224,12 @@ public: virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const; virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual float screen_get_max_scale() const; virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const; virtual Vector<int> get_window_list() const; - virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i & = Rect2i()); + virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()); virtual void delete_sub_window(WindowID p_id); virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID); diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm index 920fd24c4a..07ecd5d2c6 100644 --- a/platform/osx/display_server_osx.mm +++ b/platform/osx/display_server_osx.mm @@ -47,6 +47,8 @@ #if defined(OPENGL_ENABLED) #include "drivers/gles2/rasterizer_gles2.h" //TODO - reimplement OpenGLES + +#import <AppKit/NSOpenGLView.h> #endif #if defined(VULKAN_ENABLED) @@ -68,11 +70,11 @@ static void _get_key_modifier_state(unsigned int p_osx_state, Ref<InputEventWith r_state->set_metakey((p_osx_state & NSEventModifierFlagCommand)); } -static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow, CGFloat p_backingScaleFactor) { +static Vector2i _get_mouse_pos(DisplayServerOSX::WindowData &p_wd, NSPoint p_locationInWindow) { const NSRect contentRect = [p_wd.window_view frame]; - const NSPoint p = p_locationInWindow; - p_wd.mouse_pos.x = p.x * p_backingScaleFactor; - p_wd.mouse_pos.y = (contentRect.size.height - p.y) * p_backingScaleFactor; + const float scale = DS_OSX->screen_get_max_scale(); + p_wd.mouse_pos.x = p_locationInWindow.x * scale; + p_wd.mouse_pos.y = (contentRect.size.height - p_locationInWindow.y) * scale; DS_OSX->last_mouse_pos = p_wd.mouse_pos; Input::get_singleton()->set_mouse_position(p_wd.mouse_pos); return p_wd.mouse_pos; @@ -131,10 +133,11 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { // From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost // This works around an AppKit bug, where key up events while holding // down the command key don't get sent to the key window. - if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) { [[self keyWindow] sendEvent:event]; - else + } else { [super sendEvent:event]; + } } @end @@ -197,9 +200,22 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } } +- (void)applicationDidResignActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_OUT); + } +} + +- (void)applicationDidBecomeActive:(NSNotification *)notification { + if (OS_OSX::get_singleton()->get_main_loop()) { + OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_APPLICATION_FOCUS_IN); + } +} + - (void)globalMenuCallback:(id)sender { - if (![sender representedObject]) + if (![sender representedObject]) { return; + } GlobalMenuItem *value = [sender representedObject]; @@ -252,8 +268,9 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)showAbout:(id)sender { - if (OS_OSX::get_singleton()->get_main_loop()) + if (OS_OSX::get_singleton()->get_main_loop()) { OS_OSX::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_WM_ABOUT); + } } @end @@ -306,6 +323,11 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { [pwd.window_object makeKeyAndOrderFront:nil]; // Move focus back to main window if there is no parent or other windows left. } +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #ifdef VULKAN_ENABLED if (DS_OSX->rendering_driver == "vulkan") { DS_OSX->context_vulkan->window_destroy(window_id); @@ -333,22 +355,25 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { wd.fullscreen = false; + const float scale = DS_OSX->screen_get_max_scale(); if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / DS_OSX->_display_scale([wd.window_object screen]); + Size2i size = wd.min_size / scale; [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / DS_OSX->_display_scale([wd.window_object screen]); + Size2i size = wd.max_size / scale; [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } - if (wd.resize_disabled) + if (wd.resize_disabled) { [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } } - (void)windowDidChangeBackingProperties:(NSNotification *)notification { - if (!DisplayServerOSX::get_singleton()) + if (!DisplayServerOSX::get_singleton()) { return; + } ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; @@ -356,34 +381,21 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { CGFloat newBackingScaleFactor = [wd.window_object backingScaleFactor]; CGFloat oldBackingScaleFactor = [[[notification userInfo] objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue]; -#if defined(OPENGL_ENABLED) - if (DS_OSX->rendering_driver == "opengl_es") { - //TODO - reimplement OpenGLES - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } - } -#endif - if (newBackingScaleFactor != oldBackingScaleFactor) { //Set new display scale and window size - float newDisplayScale = OS_OSX::get_singleton()->is_hidpi_allowed() ? newBackingScaleFactor : 1.0; - + const float scale = DS_OSX->screen_get_max_scale(); const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * newDisplayScale; - wd.size.height = contentRect.size.height * newDisplayScale; + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_DPI_CHANGE); -#if defined(VULKAN_ENABLED) - if (DS_OSX->rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = DS_OSX->_display_scale([wd.window_object screen]); + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; } -#endif + //Force window resize event [self windowDidResize:notification]; } @@ -395,22 +407,24 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + const NSRect contentRect = [wd.window_view frame]; + + const float scale = DS_OSX->screen_get_max_scale(); + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } + #if defined(OPENGL_ENABLED) if (DS_OSX->rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->update(); } #endif - const NSRect contentRect = [wd.window_view frame]; - - float displayScale = DS_OSX->_display_scale([wd.window_object screen]); - wd.size.width = contentRect.size.width * displayScale; - wd.size.height = contentRect.size.height * displayScale; - #if defined(VULKAN_ENABLED) if (DS_OSX->rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; DS_OSX->context_vulkan->window_resize(window_id, wd.size.width, wd.size.height); } #endif @@ -455,8 +469,7 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [wd.window_view backingScaleFactor] : 1.0; - _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], backingScaleFactor); + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); Input::get_singleton()->set_mouse_position(wd.mouse_pos); DS_OSX->window_focused = true; @@ -531,6 +544,12 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (CALayer *)makeBackingLayer { +#if defined(OPENGL_ENABLED) + if (DS_OSX->rendering_driver == "opengl_es") { + CALayer *layer = [[NSOpenGLLayer class] layer]; + return layer; + } +#endif #if defined(VULKAN_ENABLED) if (DS_OSX->rendering_driver == "vulkan") { CALayer *layer = [[CAMetalLayer class] layer]; @@ -541,20 +560,17 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { } - (void)updateLayer { -#if defined(VULKAN_ENABLED) - if (DS_OSX->rendering_driver == "vulkan") { - [super updateLayer]; - } -#endif #if defined(OPENGL_ENABLED) if (DS_OSX->rendering_driver == "opengl_es") { - ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); - DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - - wd.context_gles2->update(); + [super updateLayer]; //TODO - reimplement OpenGLES } #endif +#if defined(VULKAN_ENABLED) + if (DS_OSX->rendering_driver == "vulkan") { + [super updateLayer]; + } +#endif } - (BOOL)wantsUpdateLayer { @@ -566,7 +582,11 @@ static NSCursor *_cursorFromSelector(SEL selector, SEL fallback = nil) { trackingArea = nil; imeInputEventInProgress = false; [self updateTrackingAreas]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + [self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeFileURL]]; +#else [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; +#endif markedText = [[NSMutableAttributedString alloc] init]; return self; } @@ -615,8 +635,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } - (void)doCommandBySelector:(SEL)aSelector { - if ([self respondsToSelector:aSelector]) + if ([self respondsToSelector:aSelector]) { [self performSelector:aSelector]; + } } - (void)unmarkText { @@ -651,8 +672,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; const NSRect contentRect = [wd.window_view frame]; - float displayScale = DS_OSX->_display_scale([wd.window_object screen]); - NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / displayScale, contentRect.size.height - (wd.im_position.y / displayScale) - 1, 0, 0); + const float scale = DS_OSX->screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(wd.im_position.x / scale, contentRect.size.height - (wd.im_position.y / scale) - 1, 0, 0); NSPoint pointOnScreen = [wd.window_object convertRectToScreen:pointInWindowRect].origin; return NSMakeRect(pointOnScreen.x, pointOnScreen.y, 0, 0); @@ -691,8 +712,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; for (i = 0; i < length; i++) { const unichar codepoint = [characters characterAtIndex:i]; - if ((codepoint & 0xFF00) == 0xF700) + if ((codepoint & 0xFF00) == 0xF700) { continue; + } DisplayServerOSX::KeyEvent ke; @@ -723,11 +745,19 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; NSPasteboard *pboard = [sender draggingPasteboard]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSArray<NSURL *> *filenames = [pboard propertyListForType:NSPasteboardTypeFileURL]; +#else NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType]; +#endif Vector<String> files; for (NSUInteger i = 0; i < filenames.count; i++) { +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101400 + NSString *ns = [[filenames objectAtIndex:i] path]; +#else NSString *ns = [filenames objectAtIndex:i]; +#endif char *utfs = strdup([ns UTF8String]); String ret; ret.parse_utf8(utfs); @@ -751,6 +781,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 }; } - (BOOL)canBecomeKeyView { + if (DS_OSX->windows.has(window_id)) { + DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + if (wd.no_focus) { + return NO; + } + } return YES; } @@ -777,8 +813,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i Ref<InputEventMouseButton> mb; mb.instance(); mb->set_window_id(window_id); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + const Vector2 pos = _get_mouse_pos(wd, [event locationInWindow]); _get_key_modifier_state([event modifierFlags], mb); mb->set_button_index(index); mb->set_pressed(pressed); @@ -824,13 +859,58 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; + NSPoint delta = NSMakePoint([event deltaX], [event deltaY]); + NSPoint mpos = [event locationInWindow]; + + if (DS_OSX->mouse_mode == DisplayServer::MOUSE_MODE_CONFINED) { + // Discard late events + if (([event timestamp]) < DS_OSX->last_warp) { + return; + } + + // Warp affects next event delta, subtract previous warp deltas + List<DisplayServerOSX::WarpEvent>::Element *F = DS_OSX->warp_events.front(); + while (F) { + if (F->get().timestamp < [event timestamp]) { + List<DisplayServerOSX::WarpEvent>::Element *E = F; + delta.x -= E->get().delta.x; + delta.y -= E->get().delta.y; + F = F->next(); + DS_OSX->warp_events.erase(E); + } else { + F = F->next(); + } + } + + // Confine mouse position to the window, and update delta + NSRect frame = [wd.window_object frame]; + NSPoint conf_pos = mpos; + conf_pos.x = CLAMP(conf_pos.x + delta.x, 0.f, frame.size.width); + conf_pos.y = CLAMP(conf_pos.y - delta.y, 0.f, frame.size.height); + delta.x = conf_pos.x - mpos.x; + delta.y = mpos.y - conf_pos.y; + mpos = conf_pos; + + // Move mouse cursor + NSRect pointInWindowRect = NSMakeRect(conf_pos.x, conf_pos.y, 0, 0); + conf_pos = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; + conf_pos.y = CGDisplayBounds(CGMainDisplayID()).size.height - conf_pos.y; + CGWarpMouseCursorPosition(conf_pos); + + // Save warp data + DS_OSX->last_warp = [[NSProcessInfo processInfo] systemUptime]; + DisplayServerOSX::WarpEvent ev; + ev.timestamp = DS_OSX->last_warp; + ev.delta = delta; + DS_OSX->warp_events.push_back(ev); + } + Ref<InputEventMouseMotion> mm; mm.instance(); mm->set_window_id(window_id); mm->set_button_mask(DS_OSX->last_button_state); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - const Vector2i pos = _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + const Vector2i pos = _get_mouse_pos(wd, mpos); mm->set_position(pos); mm->set_pressure([event pressure]); if ([event subtype] == NSEventSubtypeTabletPoint) { @@ -839,9 +919,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i } mm->set_global_position(pos); mm->set_speed(Input::get_singleton()->get_last_mouse_speed()); - Vector2i relativeMotion = Vector2i(); - relativeMotion.x = [event deltaX] * backingScaleFactor; - relativeMotion.y = [event deltaY] * backingScaleFactor; + const Vector2i relativeMotion = Vector2i(delta.x, delta.y) * DS_OSX->screen_get_max_scale(); mm->set_relative(relativeMotion); _get_key_modifier_state([event modifierFlags], mm); @@ -893,16 +971,18 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_EXIT); + } } - (void)mouseEntered:(NSEvent *)event { ERR_FAIL_COND(!DS_OSX->windows.has(window_id)); DisplayServerOSX::WindowData &wd = DS_OSX->windows[window_id]; - if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) + if (DS_OSX->mouse_mode != DisplayServer::MOUSE_MODE_CAPTURED) { DS_OSX->_send_window_event(wd, DisplayServerOSX::WINDOW_EVENT_MOUSE_ENTER); + } DisplayServer::CursorShape p_shape = DS_OSX->cursor_shape; DS_OSX->cursor_shape = DisplayServer::CURSOR_MAX; @@ -917,8 +997,7 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i ev.instance(); ev->set_window_id(window_id); _get_key_modifier_state([event modifierFlags], ev); - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - ev->set_position(_get_mouse_pos(wd, [event locationInWindow], backingScaleFactor)); + ev->set_position(_get_mouse_pos(wd, [event locationInWindow])); ev->set_factor([event magnification] + 1.0); Input::get_singleton()->accumulate_input_event(ev); @@ -934,17 +1013,8 @@ static void _mouseDownEvent(DisplayServer::WindowID window_id, NSEvent *event, i [trackingArea release]; } - NSTrackingAreaOptions options = - NSTrackingMouseEnteredAndExited | - NSTrackingActiveInKeyWindow | - NSTrackingCursorUpdate | - NSTrackingInVisibleRect; - - trackingArea = [[NSTrackingArea alloc] - initWithRect:[self bounds] - options:options - owner:self - userInfo:nil]; + NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingCursorUpdate | NSTrackingInVisibleRect; + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options:options owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; @@ -974,8 +1044,9 @@ static bool isNumpadKey(unsigned int key) { 0x00 }; for (int i = 0; table[i] != 0; i++) { - if (key == table[i]) + if (key == table[i]) { return true; + } } return false; } @@ -1115,8 +1186,9 @@ static int translateKey(unsigned int key) { /* 7f */ KEY_UNKNOWN, }; - if (key >= 128) + if (key >= 128) { return KEY_UNKNOWN; + } return table[key]; } @@ -1185,16 +1257,19 @@ static const _KeyCodeMap _keycodes[55] = { }; static int remapKey(unsigned int key, unsigned int state) { - if (isNumpadKey(key)) + if (isNumpadKey(key)) { return translateKey(key); + } TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); - if (!currentKeyboard) + if (!currentKeyboard) { return translateKey(key); + } CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); - if (!layoutData) + if (!layoutData) { return translateKey(key); + } const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData); @@ -1267,8 +1342,9 @@ static int remapKey(unsigned int key, unsigned int state) { } // Pass events to IME handler - if (wd.im_active) + if (wd.im_active) { [self interpretKeyEvents:[NSArray arrayWithObject:event]]; + } } - (void)flagsChanged:(NSEvent *)event { @@ -1421,8 +1497,7 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy double deltaX, deltaY; - const CGFloat backingScaleFactor = (OS::get_singleton()->is_hidpi_allowed()) ? [[event window] backingScaleFactor] : 1.0; - _get_mouse_pos(wd, [event locationInWindow], backingScaleFactor); + _get_mouse_pos(wd, [event locationInWindow]); deltaX = [event scrollingDeltaX]; deltaY = [event scrollingDeltaY]; @@ -1459,6 +1534,25 @@ inline void sendPanEvent(DisplayServer::WindowID window_id, double dx, double dy - (BOOL)canBecomeKeyWindow { // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } + return YES; +} + +- (BOOL)canBecomeMainWindow { + // Required for NSBorderlessWindowMask windows + for (Map<DisplayServer::WindowID, DisplayServerOSX::WindowData>::Element *E = DS_OSX->windows.front(); E; E = E->next()) { + if (E->get().window_object == self) { + if (E->get().no_focus) { + return NO; + } + } + } return YES; } @@ -1485,6 +1579,7 @@ bool DisplayServerOSX::has_feature(Feature p_feature) const { case FEATURE_HIDPI: case FEATURE_ICON: case FEATURE_NATIVE_ICON: + //case FEATURE_KEEP_SCREEN_ON: case FEATURE_SWAP_BUFFERS: return true; default: { @@ -1930,8 +2025,9 @@ Error DisplayServerOSX::dialog_input_text(String p_title, String p_description, void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ - if (p_mode == mouse_mode) + if (p_mode == mouse_mode) { return; + } if (p_mode == MOUSE_MODE_CAPTURED) { // Apple Docs state that the display parameter is not used. @@ -1946,11 +2042,15 @@ void DisplayServerOSX::mouse_set_mode(MouseMode p_mode) { CGDisplayHideCursor(kCGDirectMainDisplay); } CGAssociateMouseAndMouseCursorPosition(true); + } else if (p_mode == MOUSE_MODE_CONFINED) { + CGDisplayShowCursor(kCGDirectMainDisplay); + CGAssociateMouseAndMouseCursorPosition(false); } else { CGDisplayShowCursor(kCGDirectMainDisplay); CGAssociateMouseAndMouseCursorPosition(true); } + warp_events.clear(); mouse_mode = p_mode; } @@ -1968,8 +2068,8 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { //local point in window coords const NSRect contentRect = [wd.window_view frame]; - float displayScale = _display_scale([wd.window_object screen]); - NSRect pointInWindowRect = NSMakeRect(p_to.x / displayScale, contentRect.size.height - (p_to.y / displayScale) - 1, 0, 0); + const float scale = screen_get_max_scale(); + NSRect pointInWindowRect = NSMakeRect(p_to.x / scale, contentRect.size.height - (p_to.y / scale - 1), 0, 0); NSPoint pointOnScreen = [[wd.window_view window] convertRectToScreen:pointInWindowRect].origin; //point in scren coords @@ -1980,7 +2080,9 @@ void DisplayServerOSX::mouse_warp_to_position(const Point2i &p_to) { CGEventSourceSetLocalEventsSuppressionInterval(lEventRef, 0.0); CGAssociateMouseAndMouseCursorPosition(false); CGWarpMouseCursorPosition(lMouseWarpPos); - CGAssociateMouseAndMouseCursorPosition(true); + if (mouse_mode != MOUSE_MODE_CONFINED) { + CGAssociateMouseAndMouseCursorPosition(true); + } } } @@ -1992,11 +2094,12 @@ Point2i DisplayServerOSX::mouse_get_absolute_position() const { _THREAD_SAFE_METHOD_ const NSPoint mouse_pos = [NSEvent mouseLocation]; + const float scale = screen_get_max_scale(); for (NSScreen *screen in [NSScreen screens]) { NSRect frame = [screen frame]; if (NSMouseInRect(mouse_pos, frame, NO)) { - return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) + _get_screens_origin(); + return Vector2i((int)mouse_pos.x, (int)-mouse_pos.y) * scale + _get_screens_origin(); } } return Vector2i(); @@ -2054,17 +2157,10 @@ int DisplayServerOSX::get_screen_count() const { // to convert between OS X native screen coordinates and the ones expected by Godot static bool displays_arrangement_dirty = true; +static bool displays_scale_dirty = true; static void displays_arrangement_changed(CGDirectDisplayID display_id, CGDisplayChangeSummaryFlags flags, void *user_info) { displays_arrangement_dirty = true; -} - -float DisplayServerOSX::_display_scale(id screen) const { - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - return fmax(1.0, [screen backingScaleFactor]); - } - } - return 1.0; + displays_scale_dirty = true; } Point2i DisplayServerOSX::_get_screens_origin() const { @@ -2091,10 +2187,9 @@ Point2i DisplayServerOSX::_get_screens_origin() const { Point2i DisplayServerOSX::_get_native_screen_position(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float display_scale = _display_scale([screenArray objectAtIndex:p_screen]); NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; // Return the top-left corner of the screen, for OS X the y starts at the bottom - return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * display_scale; + return Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * screen_get_max_scale(); } return Point2i(); @@ -2123,10 +2218,9 @@ Size2i DisplayServerOSX::screen_get_size(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); // Note: Use frame to get the whole screen size NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - return Size2i(nsrect.size.width, nsrect.size.height) * displayScale; + return Size2i(nsrect.size.width, nsrect.size.height) * screen_get_max_scale(); } return Size2i(); @@ -2141,13 +2235,9 @@ int DisplayServerOSX::screen_get_dpi(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription]; - NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - CGSize displayPhysicalSize = CGDisplayScreenSize( - [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - - return (displayPixelSize.width * 25.4f / displayPhysicalSize.width) * displayScale; + NSSize displayDPI = [[description objectForKey:NSDeviceResolution] sizeValue]; + return (displayDPI.width + displayDPI.height) / 2; } return 96; @@ -2159,14 +2249,32 @@ float DisplayServerOSX::screen_get_scale(int p_screen) const { if (p_screen == SCREEN_OF_MAIN_WINDOW) { p_screen = window_get_current_screen(); } - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - return _display_scale([screenArray objectAtIndex:p_screen]); + if (OS::get_singleton()->is_hidpi_allowed()) { + NSArray *screenArray = [NSScreen screens]; + if ((NSUInteger)p_screen < [screenArray count]) { + if ([[screenArray objectAtIndex:p_screen] respondsToSelector:@selector(backingScaleFactor)]) { + return fmax(1.0, [[screenArray objectAtIndex:p_screen] backingScaleFactor]); + } + } } return 1.f; } +float DisplayServerOSX::screen_get_max_scale() const { + _THREAD_SAFE_METHOD_ + + static float scale = 1.f; + if (displays_scale_dirty) { + int screen_count = get_screen_count(); + for (int i = 0; i < screen_count; i++) { + scale = fmax(scale, screen_get_scale(i)); + } + displays_scale_dirty = false; + } + return scale; +} + Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -2176,12 +2284,12 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const { NSArray *screenArray = [NSScreen screens]; if ((NSUInteger)p_screen < [screenArray count]) { - float displayScale = _display_scale([screenArray objectAtIndex:p_screen]); + const float scale = screen_get_max_scale(); NSRect nsrect = [[screenArray objectAtIndex:p_screen] visibleFrame]; - Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * displayScale - _get_screens_origin(); + Point2i position = Point2i(nsrect.origin.x, nsrect.origin.y + nsrect.size.height) * scale - _get_screens_origin(); position.y *= -1; - Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * displayScale; + Size2i size = Size2i(nsrect.size.width, nsrect.size.height) * scale; return Rect2i(position, size); } @@ -2209,7 +2317,11 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, u window_set_flag(WindowFlags(i), true, id); } } - [wd.window_object makeKeyAndOrderFront:nil]; + if (wd.no_focus) { + [wd.window_object orderFront:nil]; + } else { + [wd.window_object makeKeyAndOrderFront:nil]; + } return id; } @@ -2227,8 +2339,9 @@ void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_ev DisplayServerOSX::WindowID DisplayServerOSX::_find_window_id(id p_window) { for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - if (E->get().window_object == p_window) + if (E->get().window_object == p_window) { return E->key(); + } } return INVALID_WINDOW_ID; } @@ -2352,7 +2465,7 @@ void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent wd_window.transient_parent = INVALID_WINDOW_ID; wd_parent.transient_children.erase(p_window); - [wd_window.window_object setParentWindow:nil]; + [wd_parent.window_object removeChildWindow:wd_window.window_object]; } else { ERR_FAIL_COND(!windows.has(p_parent)); ERR_FAIL_COND_MSG(wd_window.transient_parent != INVALID_WINDOW_ID, "Window already has a transient parent"); @@ -2361,7 +2474,7 @@ void DisplayServerOSX::window_set_transient(WindowID p_window, WindowID p_parent wd_window.transient_parent = p_parent; wd_parent.transient_children.insert(p_window); - [wd_window.window_object setParentWindow:wd_parent.window_object]; + [wd_parent.window_object addChildWindow:wd_window.window_object ordered:NSWindowAbove]; } } @@ -2373,11 +2486,12 @@ Point2i DisplayServerOSX::window_get_position(WindowID p_window) const { NSRect nsrect = [wd.window_object frame]; Point2i pos; - float display_scale = _display_scale([wd.window_object screen]); // Return the position of the top-left corner, for OS X the y starts at the bottom - pos.x = nsrect.origin.x * display_scale; - pos.y = (nsrect.origin.y + nsrect.size.height) * display_scale; + const float scale = screen_get_max_scale(); + pos.x = nsrect.origin.x; + pos.y = (nsrect.origin.y + nsrect.size.height); + pos *= scale; pos -= _get_screens_origin(); // OS X native y-coordinate relative to _get_screens_origin() is negative, // Godot expects a positive value @@ -2396,17 +2510,12 @@ void DisplayServerOSX::window_set_position(const Point2i &p_position, WindowID p // Godot passes a positive value position.y *= -1; position += _get_screens_origin(); + position /= screen_get_max_scale(); - NSPoint pos; - float displayScale = _display_scale([wd.window_object screen]); - - pos.x = position.x / displayScale; - pos.y = position.y / displayScale; - - [wd.window_object setFrameTopLeftPoint:pos]; + [wd.window_object setFrameTopLeftPoint:NSMakePoint(position.x, position.y)]; _update_window(wd); - _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream], displayScale); + _get_mouse_pos(wd, [wd.window_object mouseLocationOutsideOfEventStream]); } void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_window) { @@ -2422,7 +2531,7 @@ void DisplayServerOSX::window_set_max_size(const Size2i p_size, WindowID p_windo wd.max_size = p_size; if ((wd.max_size != Size2i()) && !wd.fullscreen) { - Size2i size = wd.max_size / _display_scale([wd.window_object screen]); + Size2i size = wd.max_size / screen_get_max_scale(); [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } else { [wd.window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)]; @@ -2450,7 +2559,7 @@ void DisplayServerOSX::window_set_min_size(const Size2i p_size, WindowID p_windo wd.min_size = p_size; if ((wd.min_size != Size2i()) && !wd.fullscreen) { - Size2i size = wd.min_size / _display_scale([wd.window_object screen]); + Size2i size = wd.min_size / screen_get_max_scale(); [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } else { [wd.window_object setContentMinSize:NSMakeSize(0, 0)]; @@ -2472,7 +2581,7 @@ void DisplayServerOSX::window_set_size(const Size2i p_size, WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - Size2i size = p_size / _display_scale([wd.window_object screen]); + Size2i size = p_size / screen_get_max_scale(); if (!wd.borderless) { // NSRect used by setFrame includes the title bar, so add it to our size.y @@ -2502,7 +2611,7 @@ Size2i DisplayServerOSX::window_get_real_size(WindowID p_window) const { ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; NSRect frame = [wd.window_object frame]; - return Size2i(frame.size.width, frame.size.height) * _display_scale([wd.window_object screen]); + return Size2i(frame.size.width, frame.size.height) * screen_get_max_scale(); } bool DisplayServerOSX::window_is_maximize_allowed(WindowID p_window) const { @@ -2513,24 +2622,26 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; - if (!OS_OSX::get_singleton()->is_layered_allowed()) + if (!OS_OSX::get_singleton()->is_layered_allowed()) { return; + } if (wd.layered_window != p_enabled) { if (p_enabled) { [wd.window_object setBackgroundColor:[NSColor clearColor]]; [wd.window_object setOpaque:NO]; [wd.window_object setHasShadow:NO]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:NO]; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - [layer setOpaque:NO]; //TODO - implement transparency for Vulkan } #endif #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->set_opacity(0); } #endif wd.layered_window = true; @@ -2538,17 +2649,18 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled [wd.window_object setBackgroundColor:[NSColor colorWithCalibratedWhite:1 alpha:1]]; [wd.window_object setOpaque:YES]; [wd.window_object setHasShadow:YES]; + CALayer *layer = [wd.window_view layer]; + if (layer) { + [layer setOpaque:YES]; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - [layer setOpaque:YES]; //TODO - implement transparency for Vulkan } #endif #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->set_opacity(1); } #endif wd.layered_window = false; @@ -2556,7 +2668,11 @@ void DisplayServerOSX::_set_window_per_pixel_transparency_enabled(bool p_enabled #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2->update(); + } +#endif +#if defined(VULKAN_ENABLED) + if (rendering_driver == "vulkan") { + //TODO - implement transparency for Vulkan } #endif NSRect frameRect = [wd.window_object frame]; @@ -2584,16 +2700,18 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) { [wd.window_object deminiaturize:nil]; } break; case WINDOW_MODE_FULLSCREEN: { - if (wd.layered_window) + if (wd.layered_window) { _set_window_per_pixel_transparency_enabled(true, p_window); - if (wd.resize_disabled) //restore resize disabled + } + if (wd.resize_disabled) { //restore resize disabled [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; + } if (wd.min_size != Size2i()) { - Size2i size = wd.min_size / _display_scale([wd.window_object screen]); + Size2i size = wd.min_size / screen_get_max_scale(); [wd.window_object setContentMinSize:NSMakeSize(size.x, size.y)]; } if (wd.max_size != Size2i()) { - Size2i size = wd.max_size / _display_scale([wd.window_object screen]); + Size2i size = wd.max_size / screen_get_max_scale(); [wd.window_object setContentMaxSize:NSMakeSize(size.x, size.y)]; } [wd.window_object toggleFullScreen:nil]; @@ -2662,8 +2780,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo switch (p_flag) { case WINDOW_FLAG_RESIZE_DISABLED: { wd.resize_disabled = p_enabled; - if (wd.fullscreen) //fullscreen window should be resizable, style will be applyed on exiting fs + if (wd.fullscreen) { //fullscreen window should be resizable, style will be applyed on exiting fs return; + } if (p_enabled) { [wd.window_object setStyleMask:[wd.window_object styleMask] & ~NSWindowStyleMaskResizable]; } else { @@ -2704,7 +2823,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo [wd.window_object setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | (wd.resize_disabled ? 0 : NSWindowStyleMaskResizable)]; } _set_window_per_pixel_transparency_enabled(p_enabled, p_window); - + } break; + case WINDOW_FLAG_NO_FOCUS: { + wd.no_focus = p_enabled; } break; default: { } @@ -2730,6 +2851,9 @@ bool DisplayServerOSX::window_get_flag(WindowFlags p_flag, WindowID p_window) co case WINDOW_FLAG_TRANSPARENT: { return wd.layered_window; } break; + case WINDOW_FLAG_NO_FOCUS: { + return wd.no_focus; + } break; default: { } } @@ -2775,8 +2899,9 @@ void DisplayServerOSX::window_set_ime_active(const bool p_active, WindowID p_win wd.im_active = p_active; - if (!p_active) + if (!p_active) { [wd.window_view cancelComposition]; + } } void DisplayServerOSX::window_set_ime_position(const Point2i &p_pos, WindowID p_window) { @@ -2797,8 +2922,9 @@ void DisplayServerOSX::cursor_set_shape(CursorShape p_shape) { ERR_FAIL_INDEX(p_shape, CURSOR_MAX); - if (cursor_shape == p_shape) + if (cursor_shape == p_shape) { return; + } if (mouse_mode != MOUSE_MODE_VISIBLE && mouse_mode != MOUSE_MODE_CONFINED) { cursor_shape = p_shape; @@ -3192,8 +3318,9 @@ void DisplayServerOSX::process_events() { inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) + if (event == nil) { break; + } [NSApp sendEvent:event]; } @@ -3281,11 +3408,11 @@ void DisplayServerOSX::set_icon(const Ref<Image> &p_icon) { Vector<String> DisplayServerOSX::get_rendering_drivers_func() { Vector<String> drivers; -#ifdef VULKAN_ENABLED +#if defined(VULKAN_ENABLED) drivers.push_back("vulkan"); #endif -#ifdef OPENGL_ENABLED - drivers.push_back("opengl"); +#if defined(OPENGL_ENABLED) + drivers.push_back("opengl_es"); #endif return drivers; @@ -3300,9 +3427,14 @@ String DisplayServerOSX::ime_get_text() const { } DisplayServer::WindowID DisplayServerOSX::get_window_at_screen_position(const Point2i &p_position) const { + Point2i position = p_position; + position.y *= -1; + position += _get_screens_origin(); + position /= screen_get_max_scale(); + + NSInteger wnum = [NSWindow windowNumberAtPoint:NSMakePoint(position.x, position.y) belowWindowWithWindowNumber:0 /*topmost*/]; for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { - Rect2i win_rect = Rect2i(window_get_position(E->key()), window_get_size(E->key())); - if (win_rect.has_point(p_position)) { + if ([E->get().window_object windowNumber] == wnum) { return E->key(); } } @@ -3329,18 +3461,10 @@ DisplayServer *DisplayServerOSX::create_func(const String &p_rendering_driver, W DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, const Rect2i &p_rect) { WindowID id; + const float scale = screen_get_max_scale(); { WindowData wd; - float displayScale = 1.0; - if (OS_OSX::get_singleton()->is_hidpi_allowed()) { - // note that mainScreen is not screen #0 but the one with the keyboard focus. - NSScreen *screen = [NSScreen mainScreen]; - if ([screen respondsToSelector:@selector(backingScaleFactor)]) { - displayScale = fmax(displayScale, [screen backingScaleFactor]); - } - } - wd.window_delegate = [[GodotWindowDelegate alloc] init]; ERR_FAIL_COND_V_MSG(wd.window_delegate == nil, INVALID_WINDOW_ID, "Can't create a window delegate"); [wd.window_delegate setWindowID:window_id_counter]; @@ -3353,7 +3477,7 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c // initWithContentRect uses bottom-left corner of the window’s frame as origin. wd.window_object = [[GodotWindow alloc] - initWithContentRect:NSMakeRect(position.x / displayScale, (position.y - p_rect.size.height) / displayScale, p_rect.size.width / displayScale, p_rect.size.height / displayScale) + initWithContentRect:NSMakeRect(position.x / scale, (position.y - p_rect.size.height) / scale, p_rect.size.width / scale, p_rect.size.height / scale) styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO]; @@ -3366,54 +3490,32 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c [wd.window_view setWantsLayer:TRUE]; } - if (displayScale > 1.0) { -#if defined(OPENGL_ENABLED) - if (rendering_driver == "opengl_es") { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } -#endif - [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } else { -#if defined(OPENGL_ENABLED) - if (rendering_driver == "opengl_es") { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } -#endif - } - [wd.window_object setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; [wd.window_object setContentView:wd.window_view]; [wd.window_object setDelegate:wd.window_delegate]; [wd.window_object setAcceptsMouseMovedEvents:YES]; [wd.window_object setRestorable:NO]; - if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) + if ([wd.window_object respondsToSelector:@selector(setTabbingMode:)]) { [wd.window_object setTabbingMode:NSWindowTabbingModeDisallowed]; + } + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { if (context_vulkan) { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; Error err = context_vulkan->window_create(window_id_counter, wd.window_view, p_rect.size.width, p_rect.size.height); ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a Vulkan context"); } } #endif -#ifdef OPENGL_ENABLED +#if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { //TODO - reimplement OpenGLES - wd.context_gles2 = memnew(ContextGL_OSX(wd.window_view, false)); - - if (wd.context_gles2->initialize() != OK) { - memdelete(wd.context_gles2); - ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create a OpenGL context"); - } - - //if (RasterizerGLES2::is_viable() == OK) { - // RasterizerGLES2::register_config(); - // RasterizerGLES2::make_current(); - //} } #endif id = window_id_counter++; @@ -3423,25 +3525,22 @@ DisplayServerOSX::WindowID DisplayServerOSX::_create_window(WindowMode p_mode, c WindowData &wd = windows[id]; window_set_mode(p_mode, id); - float displayScale = _display_scale([wd.window_object screen]); const NSRect contentRect = [wd.window_view frame]; - wd.size.width = contentRect.size.width * displayScale; - wd.size.height = contentRect.size.height * displayScale; + wd.size.width = contentRect.size.width * scale; + wd.size.height = contentRect.size.height * scale; + + CALayer *layer = [wd.window_view layer]; + if (layer) { + layer.contentsScale = scale; + } #if defined(OPENGL_ENABLED) if (rendering_driver == "opengl_es") { - if (OS_OSX::singleton->is_hidpi_allowed()) { - [wd.window_view setWantsBestResolutionOpenGLSurface:YES]; - } else { - [wd.window_view setWantsBestResolutionOpenGLSurface:NO]; - } - wd.context_gles2->update(); + //TODO - reimplement OpenGLES } #endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { - CALayer *layer = [wd.window_view layer]; - layer.contentsScale = displayScale; context_vulkan->window_resize(id, wd.size.width, wd.size.height); } #endif @@ -3536,6 +3635,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode keyboard_layout_dirty = true; displays_arrangement_dirty = true; + displays_scale_dirty = true; // Register to be notified on keyboard layout changes CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), @@ -3553,8 +3653,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode NSString *title; NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]; - if (nsappname == nil) + if (nsappname == nil) { nsappname = [[NSProcessInfo processInfo] processName]; + } // Setup Dock menu dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"]; @@ -3607,8 +3708,9 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode inMode:NSDefaultRunLoopMode dequeue:YES]; - if (event == nil) + if (event == nil) { break; + } [NSApp sendEvent:event]; } @@ -3640,8 +3742,8 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode #endif Point2i window_position( - (screen_get_size(0).width - p_resolution.width) / 2, - (screen_get_size(0).height - p_resolution.height) / 2); + screen_get_position(0).x + (screen_get_size(0).width - p_resolution.width) / 2, + screen_get_position(0).y + (screen_get_size(0).height - p_resolution.height) / 2); WindowID main_window = _create_window(p_mode, Rect2i(window_position, p_resolution)); for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { @@ -3650,6 +3752,11 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode } [windows[main_window].window_object makeKeyAndOrderFront:nil]; +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { rendering_device_vulkan = memnew(RenderingDeviceVulkan); @@ -3660,14 +3767,6 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode #endif [NSApp activateIgnoringOtherApps:YES]; - - /* - visual_server = memnew(VisualServerRaster); - if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) { - visual_server = memnew(VisualServerWrapMT(visual_server, get_render_thread_mode() == RENDER_SEPARATE_THREAD)); - } - visual_server->init(); - */ } DisplayServerOSX::~DisplayServerOSX() { @@ -3686,6 +3785,11 @@ DisplayServerOSX::~DisplayServerOSX() { } //destroy drivers +#if defined(OPENGL_ENABLED) + if (rendering_driver == "opengl_es") { + //TODO - reimplement OpenGLES + } +#endif #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { if (rendering_device_vulkan) { @@ -3693,8 +3797,9 @@ DisplayServerOSX::~DisplayServerOSX() { memdelete(rendering_device_vulkan); } - if (context_vulkan) + if (context_vulkan) { memdelete(context_vulkan); + } } #endif @@ -3702,9 +3807,6 @@ DisplayServerOSX::~DisplayServerOSX() { CGDisplayRemoveReconfigurationCallback(displays_arrangement_changed, NULL); cursors_cache.clear(); - - //visual_server->finish(); - //memdelete(visual_server); } void DisplayServerOSX::register_osx_driver() { diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 9af7c02351..916816325d 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -825,14 +825,15 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.min; - zipfi.tmz_date.tm_mon = date.month; + zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.sec; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; // 0100000: regular file type // 0000755: permissions rwxr-xr-x // 0000644: permissions rw-r--r-- - zipfi.external_fa = (is_executable ? 0100755 : 0100644) << 16L; + uint32_t _mode = (is_executable ? 0100755 : 0100644); + zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); zipfi.internal_fa = 0; zipOpenNewFileInZip4(p_zip, diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index dba96ccfcd..4ca89ff4b2 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -272,7 +272,13 @@ String OS_OSX::get_system_dir(SystemDir p_dir) const { } Error OS_OSX::shell_open(String p_uri) { - [[NSWorkspace sharedWorkspace] openURL:[[NSURL alloc] initWithString:[[NSString stringWithUTF8String:p_uri.utf8().get_data()] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]]; + NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()]; + NSURL *uri = [[NSURL alloc] initWithString:string]; + // Escape special characters in filenames + if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) { + uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]]; + } + [[NSWorkspace sharedWorkspace] openURL:uri]; return OK; } diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index f47afcc4e5..103e858d97 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -476,6 +476,7 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod _THREAD_SAFE_METHOD_ WindowID window_id = _create_window(p_mode, p_flags, p_rect); + ERR_FAIL_COND_V_MSG(window_id == INVALID_WINDOW_ID, INVALID_WINDOW_ID, "Failed to create sub window."); WindowData &wd = windows[window_id]; @@ -1773,14 +1774,22 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA }; WindowID window_id = INVALID_WINDOW_ID; + bool window_created = false; for (Map<WindowID, WindowData>::Element *E = windows.front(); E; E = E->next()) { if (E->get().hWnd == hWnd) { window_id = E->key(); + window_created = true; break; } } + if (!window_created) { + // Window creation in progress. + window_id = window_id_counter; + ERR_FAIL_COND_V(!windows.has(window_id), 0); + } + switch (uMsg) // Check For Windows Messages { case WM_SETFOCUS: { @@ -1790,6 +1799,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // 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: { @@ -1805,6 +1820,19 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } touch_state.clear(); + bool self_steal = false; + HWND new_hwnd = (HWND)wParam; + if (IsWindow(new_hwnd)) { + self_steal = true; + } + + 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; + } + break; } case WM_ACTIVATE: // Watch For Window Activate Message @@ -2493,7 +2521,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA windows[window_id].height = window_h; #if defined(VULKAN_ENABLED) - if (rendering_driver == "vulkan") { + if ((rendering_driver == "vulkan") && window_created) { context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height); } #endif @@ -2845,11 +2873,32 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, WindowRect.top = p_rect.position.y; WindowRect.bottom = p_rect.position.y + p_rect.size.y; + if (p_mode == WINDOW_MODE_FULLSCREEN) { + int nearest_area = 0; + Rect2i screen_rect; + for (int i = 0; i < get_screen_count(); i++) { + Rect2i r; + r.position = screen_get_position(i); + r.size = screen_get_size(i); + Rect2 inters = r.clip(p_rect); + int area = inters.size.width * inters.size.height; + if (area >= nearest_area) { + screen_rect = r; + nearest_area = area; + } + } + + WindowRect.left = screen_rect.position.x; + WindowRect.right = screen_rect.position.x + screen_rect.size.x; + WindowRect.top = screen_rect.position.y; + WindowRect.bottom = screen_rect.position.y + screen_rect.size.y; + } + AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); WindowID id = window_id_counter; { - WindowData wd; + WindowData &wd = windows[id]; wd.hWnd = CreateWindowExW( dwExStyle, @@ -2864,6 +2913,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, nullptr, nullptr, hInstance, nullptr); if (!wd.hWnd) { MessageBoxW(nullptr, L"Window Creation Error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION); + windows.erase(id); return INVALID_WINDOW_ID; } #ifdef VULKAN_ENABLED @@ -2872,7 +2922,8 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, if (context_vulkan->window_create(id, wd.hWnd, hInstance, WindowRect.right - WindowRect.left, WindowRect.bottom - WindowRect.top) == -1) { memdelete(context_vulkan); context_vulkan = nullptr; - ERR_FAIL_V(INVALID_WINDOW_ID); + windows.erase(id); + ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Failed to create Vulkan Window."); } } #endif @@ -2930,8 +2981,6 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, wd.width = p_rect.size.width; wd.height = p_rect.size.height; - windows[id] = wd; - window_id_counter++; } @@ -3062,7 +3111,10 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win Point2i window_position( (screen_get_size(0).width - p_resolution.width) / 2, (screen_get_size(0).height - p_resolution.height) / 2); + WindowID main_window = _create_window(p_mode, 0, Rect2i(window_position, p_resolution)); + ERR_FAIL_COND_MSG(main_window == INVALID_WINDOW_ID, "Failed to create main window."); + for (int i = 0; i < WINDOW_FLAG_MAX; i++) { if (p_flags & (1 << i)) { window_set_flag(WindowFlags(i), true, main_window); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 995ced0809..8433bb449b 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -317,6 +317,7 @@ private: int pressrc; HINSTANCE hInstance; // Holds The Instance Of The Application String rendering_driver; + bool app_focused = false; struct WindowData { HWND hWnd; |