summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/detect.py7
-rw-r--r--platform/android/export/export.cpp48
-rw-r--r--platform/android/java/AndroidManifest.xml2
-rw-r--r--platform/android/java/gradlew.bat168
-rw-r--r--platform/android/java/src/org/godotengine/godot/Godot.java29
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotLib.java4
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotRenderer.java61
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotView.java702
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java352
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/Joystick.java44
-rw-r--r--platform/android/java/src/org/godotengine/godot/utils/GLUtils.java157
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/XRMode.java39
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java112
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java58
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java60
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java151
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java81
-rw-r--r--platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java61
-rw-r--r--platform/android/java_godot_io_wrapper.cpp6
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/android/java_godot_lib_jni.h2
-rw-r--r--platform/android/java_godot_wrapper.cpp28
-rw-r--r--platform/android/java_godot_wrapper.h6
-rw-r--r--platform/android/os_android.cpp12
-rw-r--r--platform/android/os_android.h5
-rw-r--r--platform/haiku/os_haiku.cpp6
-rw-r--r--platform/haiku/os_haiku.h4
-rw-r--r--platform/iphone/SCsub1
-rw-r--r--platform/iphone/camera_ios.h47
-rw-r--r--platform/iphone/camera_ios.mm429
-rw-r--r--platform/iphone/detect.py11
-rw-r--r--platform/iphone/export/export.cpp24
-rw-r--r--platform/iphone/icloud.mm2
-rw-r--r--platform/iphone/os_iphone.cpp15
-rw-r--r--platform/iphone/os_iphone.h9
-rw-r--r--platform/javascript/audio_driver_javascript.cpp2
-rw-r--r--platform/javascript/detect.py10
-rw-r--r--platform/javascript/http_client.h.inc2
-rw-r--r--platform/javascript/os_javascript.cpp77
-rw-r--r--platform/javascript/os_javascript.h10
-rw-r--r--platform/osx/SCsub1
-rw-r--r--platform/osx/camera_osx.h47
-rw-r--r--platform/osx/camera_osx.mm362
-rw-r--r--platform/osx/crash_handler_osx.mm7
-rw-r--r--platform/osx/detect.py2
-rw-r--r--platform/osx/export/export.cpp40
-rw-r--r--platform/osx/os_osx.h15
-rw-r--r--platform/osx/os_osx.mm247
-rw-r--r--platform/server/detect.py6
-rw-r--r--platform/server/os_server.cpp2
-rw-r--r--platform/server/os_server.h2
-rw-r--r--platform/uwp/export/export.cpp50
-rw-r--r--platform/uwp/os_uwp.cpp10
-rw-r--r--platform/uwp/os_uwp.h7
-rw-r--r--platform/windows/SCsub1
-rw-r--r--platform/windows/camera_win.cpp94
-rw-r--r--platform/windows/camera_win.h46
-rw-r--r--platform/windows/crash_handler_windows.cpp9
-rw-r--r--platform/windows/export/export.cpp4
-rw-r--r--platform/windows/godot_res.rc2
-rw-r--r--platform/windows/joypad_windows.cpp18
-rw-r--r--platform/windows/os_windows.cpp200
-rw-r--r--platform/windows/os_windows.h32
-rw-r--r--platform/windows/power_windows.cpp2
-rw-r--r--platform/windows/windows_terminal_logger.cpp4
-rw-r--r--platform/x11/context_gl_x11.cpp1
-rw-r--r--platform/x11/crash_handler_x11.cpp7
-rw-r--r--platform/x11/detect.py6
-rw-r--r--platform/x11/godot_x11.cpp7
-rw-r--r--platform/x11/joypad_linux.cpp6
-rw-r--r--platform/x11/os_x11.cpp251
-rw-r--r--platform/x11/os_x11.h12
72 files changed, 3386 insertions, 960 deletions
diff --git a/platform/android/detect.py b/platform/android/detect.py
index 8d35617d20..b7641172e4 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -214,13 +214,14 @@ def configure(env):
lib_sysroot = env["ANDROID_NDK_ROOT"] + "/platforms/" + env['ndk_platform'] + "/" + env['ARCH']
## Compile flags
-
- if env['android_stl']:
+ # Disable exceptions and rtti on non-tools (template) builds
+ if env['tools'] or env['android_stl']:
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++/include"])
env.Append(CPPFLAGS=["-isystem", env["ANDROID_NDK_ROOT"] + "/sources/cxx-stl/llvm-libc++abi/include"])
env.Append(CXXFLAGS=['-frtti', "-std=gnu++14"])
else:
env.Append(CXXFLAGS=['-fno-rtti', '-fno-exceptions'])
+ # Don't use dynamic_cast, necessary with no-rtti.
env.Append(CPPFLAGS=['-DNO_SAFE_CAST'])
ndk_version = get_ndk_version(env["ANDROID_NDK_ROOT"])
@@ -301,7 +302,7 @@ def configure(env):
env.Prepend(CPPPATH=['#platform/android'])
env.Append(CPPFLAGS=['-DANDROID_ENABLED', '-DUNIX_ENABLED', '-DNO_FCNTL'])
- env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'android', 'log', 'z', 'dl'])
+ env.Append(LIBS=['OpenSLES', 'EGL', 'GLESv3', 'GLESv2', 'android', 'log', 'z', 'dl'])
# Return NDK version string in source.properties (adapted from the Chromium project).
def get_ndk_version(path):
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index f45644f1eb..30267aa968 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -617,7 +617,9 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
String dst_path = p_path.replace_first("res://", "assets/");
store_in_apk(ed, dst_path, p_data, _should_compress_asset(p_path, p_data) ? Z_DEFLATED : 0);
- ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total);
+ if (ed->ep->step("File: " + p_path, 3 + p_file * 100 / p_total)) {
+ return ERR_SKIP;
+ }
return OK;
}
@@ -788,7 +790,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
if (tname == "manifest" && attrname == "versionName") {
if (attr_value == 0xFFFFFFFF) {
- WARN_PRINT("Version name in a resource, should be plaintext")
+ WARN_PRINT("Version name in a resource, should be plain text");
} else
string_table.write[attr_value] = version_name;
}
@@ -1255,7 +1257,9 @@ public:
}
//export_temp
- ep.step("Exporting APK", 0);
+ if (ep.step("Exporting APK", 0)) {
+ return ERR_SKIP;
+ }
const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT);
const bool use_reverse = devices[p_device].api_level >= 21;
@@ -1278,7 +1282,9 @@ public:
String package_name = p_preset->get("package/unique_name");
if (remove_prev) {
- ep.step("Uninstalling...", 1);
+ if (ep.step("Uninstalling...", 1)) {
+ return ERR_SKIP;
+ }
print_line("Uninstalling previous version: " + devices[p_device].name);
@@ -1291,7 +1297,9 @@ public:
}
print_line("Installing to device (please wait...): " + devices[p_device].name);
- ep.step("Installing to device (please wait...)", 2);
+ if (ep.step("Installing to device (please wait...)", 2)) {
+ return ERR_SKIP;
+ }
args.clear();
args.push_back("-s");
@@ -1357,7 +1365,9 @@ public:
}
}
- ep.step("Running on Device...", 3);
+ if (ep.step("Running on Device...", 3)) {
+ return ERR_SKIP;
+ }
args.clear();
args.push_back("-s");
args.push_back(devices[p_device].id);
@@ -1763,7 +1773,7 @@ public:
String src_apk;
- EditorProgress ep("export", "Exporting for Android", 105);
+ EditorProgress ep("export", "Exporting for Android", 105, true);
if (bool(p_preset->get("custom_package/use_custom_build"))) { //custom build
//re-generate build.gradle and AndroidManifest.xml
@@ -1855,7 +1865,9 @@ public:
FileAccess *src_f = NULL;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- ep.step("Creating APK", 0);
+ if (ep.step("Creating APK", 0)) {
+ return ERR_SKIP;
+ }
unzFile pkg = unzOpen2(src_apk.utf8().get_data(), &io);
if (!pkg) {
@@ -1997,7 +2009,9 @@ public:
ret = unzGoToNextFile(pkg);
}
- ep.step("Adding Files...", 1);
+ if (ep.step("Adding Files...", 1)) {
+ return ERR_SKIP;
+ }
Error err = OK;
Vector<String> cl = cmdline.strip_edges().split(" ");
for (int i = 0; i < cl.size(); i++) {
@@ -2135,14 +2149,18 @@ public:
user = EditorSettings::get_singleton()->get("export/android/debug_keystore_user");
}
- ep.step("Signing debug APK...", 103);
+ if (ep.step("Signing debug APK...", 103)) {
+ return ERR_SKIP;
+ }
} else {
keystore = release_keystore;
password = release_password;
user = release_username;
- ep.step("Signing release APK...", 103);
+ if (ep.step("Signing release APK...", 103)) {
+ return ERR_SKIP;
+ }
}
if (!FileAccess::exists(keystore)) {
@@ -2174,7 +2192,9 @@ public:
return ERR_CANT_CREATE;
}
- ep.step("Verifying APK...", 104);
+ if (ep.step("Verifying APK...", 104)) {
+ return ERR_SKIP;
+ }
args.clear();
args.push_back("-verify");
@@ -2194,7 +2214,9 @@ public:
static const int ZIP_ALIGNMENT = 4;
- ep.step("Aligning APK...", 105);
+ if (ep.step("Aligning APK...", 105)) {
+ return ERR_SKIP;
+ }
unzFile tmp_unaligned = unzOpen2(unaligned_path.utf8().get_data(), &io);
if (!tmp_unaligned) {
diff --git a/platform/android/java/AndroidManifest.xml b/platform/android/java/AndroidManifest.xml
index 613d24fbd2..9997950137 100644
--- a/platform/android/java/AndroidManifest.xml
+++ b/platform/android/java/AndroidManifest.xml
@@ -22,7 +22,7 @@
<!--Anything in this line after the icon will be erased when doing custom build. If you want to add tags manually, do before it.-->
<application android:label="@string/godot_project_name_string" android:allowBackup="false" tools:ignore="GoogleAppIndexingWarning" android:icon="@drawable/icon">
-<!--The following values are replaced when Godot exports, modifying them here has no effect. Do theses changes in the-->
+<!--The following values are replaced when Godot exports, modifying them here has no effect. Do these changes in the-->
<!--export preset. Adding new ones is fine.-->
<activity android:name="org.godotengine.godot.Godot"
diff --git a/platform/android/java/gradlew.bat b/platform/android/java/gradlew.bat
index e95643d6a2..f9553162f1 100644
--- a/platform/android/java/gradlew.bat
+++ b/platform/android/java/gradlew.bat
@@ -1,84 +1,84 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java
index 374d40463a..751e885118 100644
--- a/platform/android/java/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/src/org/godotengine/godot/Godot.java
@@ -30,8 +30,6 @@
package org.godotengine.godot;
-//import android.R;
-
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
@@ -64,6 +62,7 @@ import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
import android.view.MotionEvent;
+import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
@@ -73,7 +72,6 @@ import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
-import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
@@ -94,6 +92,7 @@ import java.util.Locale;
import javax.microedition.khronos.opengles.GL10;
import org.godotengine.godot.input.GodotEditText;
import org.godotengine.godot.payments.PaymentsManager;
+import org.godotengine.godot.xr.XRMode;
public class Godot extends Activity implements SensorEventListener, IDownloaderClient {
@@ -120,6 +119,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
private boolean use_immersive = false;
private boolean use_debug_opengl = false;
private boolean mStatePaused;
+ private boolean activityResumed;
private int mState;
static private Intent mCurrentIntent;
@@ -282,7 +282,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
// ...add to FrameLayout
layout.addView(edittext);
- mView = new GodotView(getApplication(), io, use_gl3, use_32_bits, use_debug_opengl, this);
+ mView = new GodotView(this, XRMode.PANCAKE, use_gl3, use_32_bits, use_debug_opengl);
layout.addView(mView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
edittext.setView(mView);
io.setEdit(edittext);
@@ -402,6 +402,20 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
}
}
+ /**
+ * Used by the native code (java_godot_wrapper.h) to check whether the activity is resumed or paused.
+ */
+ private boolean isActivityResumed() {
+ return activityResumed;
+ }
+
+ /**
+ * Used by the native code (java_godot_wrapper.h) to access the Android surface.
+ */
+ private Surface getSurface() {
+ return mView.getHolder().getSurface();
+ }
+
String expansion_pack_path;
private void initializeGodot() {
@@ -612,6 +626,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
@Override
protected void onPause() {
super.onPause();
+ activityResumed = false;
+
if (!godot_initialized) {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
@@ -687,6 +703,8 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
singletons[i].onMainResume();
}
+
+ activityResumed = true;
}
public void UiChangeListener() {
@@ -1059,4 +1077,7 @@ public class Godot extends Activity implements SensorEventListener, IDownloaderC
mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress,
progress.mOverallTotal));
}
+ public void initInputDevices() {
+ mView.initInputDevices();
+ }
}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java
index 31ca9a8500..81c98bcc79 100644
--- a/platform/android/java/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/src/org/godotengine/godot/GodotLib.java
@@ -68,8 +68,8 @@ public class GodotLib {
public static native void singleton(String p_name, Object p_object);
public static native void method(String p_sname, String p_name, String p_ret, String[] p_params);
public static native String getGlobal(String p_key);
- public static native void callobject(int p_ID, String p_method, Object[] p_params);
- public static native void calldeferred(int p_ID, String p_method, Object[] p_params);
+ public static native void callobject(int p_id, String p_method, Object[] p_params);
+ public static native void calldeferred(int p_id, String p_method, Object[] p_params);
public static native void requestPermissionResult(String p_permission, boolean p_result);
public static native void setVirtualKeyboardHeight(int p_height);
diff --git a/platform/android/java/src/org/godotengine/godot/GodotRenderer.java b/platform/android/java/src/org/godotengine/godot/GodotRenderer.java
new file mode 100644
index 0000000000..8e3775c2a9
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotRenderer.java
@@ -0,0 +1,61 @@
+/*************************************************************************/
+/* GodotRenderer.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.opengl.GLSurfaceView;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+import org.godotengine.godot.utils.GLUtils;
+
+/**
+ * Godot's renderer implementation.
+ */
+class GodotRenderer implements GLSurfaceView.Renderer {
+
+ public void onDrawFrame(GL10 gl) {
+ GodotLib.step();
+ for (int i = 0; i < Godot.singleton_count; i++) {
+ Godot.singletons[i].onGLDrawFrame(gl);
+ }
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+
+ GodotLib.resize(width, height);
+ for (int i = 0; i < Godot.singleton_count; i++) {
+ Godot.singletons[i].onGLSurfaceChanged(gl, width, height);
+ }
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ GodotLib.newcontext(GLUtils.use_32);
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java
index d7cd5b4360..1c189a1579 100644
--- a/platform/android/java/src/org/godotengine/godot/GodotView.java
+++ b/platform/android/java/src/org/godotengine/godot/GodotView.java
@@ -30,28 +30,20 @@
package org.godotengine.godot;
import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.ContextWrapper;
import android.graphics.PixelFormat;
-import android.hardware.input.InputManager;
import android.opengl.GLSurfaceView;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.opengles.GL10;
-import org.godotengine.godot.input.InputManagerCompat;
-import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
+import org.godotengine.godot.input.GodotInputHandler;
+import org.godotengine.godot.utils.GLUtils;
+import org.godotengine.godot.xr.XRMode;
+import org.godotengine.godot.xr.ovr.OvrConfigChooser;
+import org.godotengine.godot.xr.ovr.OvrContextFactory;
+import org.godotengine.godot.xr.ovr.OvrWindowSurfaceFactory;
+import org.godotengine.godot.xr.pancake.PancakeConfigChooser;
+import org.godotengine.godot.xr.pancake.PancakeContextFactory;
+import org.godotengine.godot.xr.pancake.PancakeFallbackConfigChooser;
+
/**
* A simple GLSurfaceView sub-class that demonstrate how to perform
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
@@ -70,45 +62,26 @@ import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
* that matches it exactly (with regards to red/green/blue/alpha channels
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
-public class GodotView extends GLSurfaceView implements InputDeviceListener {
-
- private static String TAG = "GodotView";
- private static final boolean DEBUG = false;
- private Context ctx;
-
- private GodotIO io;
- private static boolean use_gl3 = false;
- private static boolean use_32 = false;
- private static boolean use_debug_opengl = false;
+public class GodotView extends GLSurfaceView {
- private Godot activity;
+ private static String TAG = GodotView.class.getSimpleName();
- private InputManagerCompat mInputManager;
- public GodotView(Context context, GodotIO p_io, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl, Godot p_activity) {
- super(context);
- ctx = context;
- io = p_io;
- use_gl3 = p_use_gl3;
- use_32 = p_use_32_bits;
- use_debug_opengl = p_use_debug_opengl;
+ private final Godot activity;
+ private final GodotInputHandler inputHandler;
- activity = p_activity;
-
- setPreserveEGLContextOnPause(true);
+ public GodotView(Godot activity, XRMode xrMode, boolean p_use_gl3, boolean p_use_32_bits, boolean p_use_debug_opengl) {
+ super(activity);
+ GLUtils.use_gl3 = p_use_gl3;
+ GLUtils.use_32 = p_use_32_bits;
+ GLUtils.use_debug_opengl = p_use_debug_opengl;
- mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext());
- mInputManager.registerInputDeviceListener(this, null);
- init(false, 16, 0);
+ this.activity = activity;
+ this.inputHandler = new GodotInputHandler(this);
+ init(xrMode, false, 16, 0);
}
- public GodotView(Context context) {
- super(context);
- ctx = context;
- }
-
- public GodotView(Context context, boolean translucent, int depth, int stencil) {
- super(context);
- init(translucent, depth, stencil);
+ public void initInputDevices() {
+ this.inputHandler.initInputDevices();
}
@SuppressLint("ClickableViewAccessibility")
@@ -118,609 +91,80 @@ public class GodotView extends GLSurfaceView implements InputDeviceListener {
return activity.gotTouchEvent(event);
}
- public int get_godot_button(int keyCode) {
-
- int button;
- switch (keyCode) {
- case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B
- button = 0;
- break;
- case KeyEvent.KEYCODE_BUTTON_B:
- button = 1;
- break;
- case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y
- button = 2;
- break;
- case KeyEvent.KEYCODE_BUTTON_Y:
- button = 3;
- break;
- case KeyEvent.KEYCODE_BUTTON_L1:
- button = 9;
- break;
- case KeyEvent.KEYCODE_BUTTON_L2:
- button = 15;
- break;
- case KeyEvent.KEYCODE_BUTTON_R1:
- button = 10;
- break;
- case KeyEvent.KEYCODE_BUTTON_R2:
- button = 16;
- break;
- case KeyEvent.KEYCODE_BUTTON_SELECT:
- button = 4;
- break;
- case KeyEvent.KEYCODE_BUTTON_START:
- button = 6;
- break;
- case KeyEvent.KEYCODE_BUTTON_THUMBL:
- button = 7;
- break;
- case KeyEvent.KEYCODE_BUTTON_THUMBR:
- button = 8;
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- button = 11;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- button = 12;
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- button = 13;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- button = 14;
- break;
- case KeyEvent.KEYCODE_BUTTON_C:
- button = 17;
- break;
- case KeyEvent.KEYCODE_BUTTON_Z:
- button = 18;
- break;
-
- default:
- button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20;
- break;
- }
- return button;
- };
-
- private static class joystick {
- public int device_id;
- public String name;
- public ArrayList<InputDevice.MotionRange> axes;
- public ArrayList<InputDevice.MotionRange> hats;
- }
-
- private static class RangeComparator implements Comparator<InputDevice.MotionRange> {
- @Override
- public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
- return arg0.getAxis() - arg1.getAxis();
- }
- }
-
- ArrayList<joystick> joy_devices = new ArrayList<joystick>();
-
- private int find_joy_device(int device_id) {
- for (int i = 0; i < joy_devices.size(); i++) {
- if (joy_devices.get(i).device_id == device_id) {
- return i;
- }
- }
-
- return -1;
- }
-
- @Override
- public void onInputDeviceAdded(int deviceId) {
- int id = find_joy_device(deviceId);
-
- // Check if the device has not been already added
- if (id < 0) {
- InputDevice device = mInputManager.getInputDevice(deviceId);
-
- id = joy_devices.size();
-
- joystick joy = new joystick();
- joy.device_id = deviceId;
- joy.name = device.getName();
- joy.axes = new ArrayList<InputDevice.MotionRange>();
- joy.hats = new ArrayList<InputDevice.MotionRange>();
-
- List<InputDevice.MotionRange> ranges = device.getMotionRanges();
- Collections.sort(ranges, new RangeComparator());
-
- for (InputDevice.MotionRange range : ranges) {
- if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
- joy.hats.add(range);
- } else {
- joy.axes.add(range);
- }
- }
-
- joy_devices.add(joy);
-
- final int device_id = id;
- final String name = joy.name;
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyconnectionchanged(device_id, true, name);
- }
- });
- }
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- final int device_id = find_joy_device(deviceId);
-
- // Check if the evice has not been already removed
- if (device_id > -1) {
- joy_devices.remove(device_id);
-
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyconnectionchanged(device_id, false, "");
- }
- });
- }
- }
-
- @Override
- public void onInputDeviceChanged(int deviceId) {
- }
@Override
public boolean onKeyUp(final int keyCode, KeyEvent event) {
-
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- return true;
- }
-
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
- return super.onKeyUp(keyCode, event);
- };
-
- int source = event.getSource();
- if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
-
- final int button = get_godot_button(keyCode);
- final int device_id = find_joy_device(event.getDeviceId());
-
- // Check if the device exists
- if (device_id > -1) {
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joybutton(device_id, button, false);
- }
- });
- return true;
- }
- } else {
- final int chr = event.getUnicodeChar(0);
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.key(keyCode, chr, false);
- }
- });
- };
-
- return super.onKeyUp(keyCode, event);
- };
+ return inputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
+ }
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
-
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- activity.onBackPressed();
- // press 'back' button should not terminate program
- //normal handle 'back' event in game logic
- return true;
- }
-
- if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
- return super.onKeyDown(keyCode, event);
- };
-
- int source = event.getSource();
- //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
-
- if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
-
- if (event.getRepeatCount() > 0) // ignore key echo
- return true;
-
- final int button = get_godot_button(keyCode);
- final int device_id = find_joy_device(event.getDeviceId());
-
- // Check if the device exists
- if (device_id > -1) {
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joybutton(device_id, button, true);
- }
- });
- return true;
- }
- } else {
- final int chr = event.getUnicodeChar(0);
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.key(keyCode, chr, true);
- }
- });
- };
-
- return super.onKeyDown(keyCode, event);
+ return inputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
-
- if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
-
- final int device_id = find_joy_device(event.getDeviceId());
-
- // Check if the device exists
- if (device_id > -1) {
- joystick joy = joy_devices.get(device_id);
-
- for (int i = 0; i < joy.axes.size(); i++) {
- InputDevice.MotionRange range = joy.axes.get(i);
- final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
- final int idx = i;
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyaxis(device_id, idx, value);
- }
- });
- }
-
- for (int i = 0; i < joy.hats.size(); i += 2) {
- final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
- final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
- queueEvent(new Runnable() {
- @Override
- public void run() {
- GodotLib.joyhat(device_id, hatX, hatY);
- }
- });
- }
- return true;
- }
- };
-
- return super.onGenericMotionEvent(event);
- };
-
- private void init(boolean translucent, int depth, int stencil) {
-
- this.setFocusableInTouchMode(true);
- /* By default, GLSurfaceView() creates a RGB_565 opaque surface.
- * If we want a translucent one, we should change the surface's
- * format here, using PixelFormat.TRANSLUCENT for GL Surfaces
- * is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
- */
- if (translucent) {
- this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
- }
-
- /* Setup the context factory for 2.0 rendering.
- * See ContextFactory class definition below
- */
- setEGLContextFactory(new ContextFactory());
-
- /* We need to choose an EGLConfig that matches the format of
- * our surface exactly. This is going to be done in our
- * custom config chooser. See ConfigChooser class definition
- * below.
- */
-
- if (use_32) {
- setEGLConfigChooser(translucent ?
- new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(8, 8, 8, 8, 16, stencil)) :
- new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(5, 6, 5, 0, 16, stencil)));
-
- } else {
- setEGLConfigChooser(translucent ?
- new ConfigChooser(8, 8, 8, 8, 16, stencil) :
- new ConfigChooser(5, 6, 5, 0, 16, stencil));
- }
-
- /* Set the renderer responsible for frame rendering */
- setRenderer(new Renderer());
- }
-
- private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC;
- private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001;
-
- private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
- private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
- String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name");
- if (use_gl3 && !driver_name.equals("GLES3")) {
- use_gl3 = false;
- }
- if (use_gl3)
- Log.w(TAG, "creating OpenGL ES 3.0 context :");
- else
- Log.w(TAG, "creating OpenGL ES 2.0 context :");
-
- checkEglError("Before eglCreateContext", egl);
- EGLContext context;
- if (use_debug_opengl) {
- int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
- int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
- context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl3 ? attrib_list3 : attrib_list2);
- } else {
- int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
- int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE };
- context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl3 ? attrib_list3 : attrib_list2);
- }
- checkEglError("After eglCreateContext", egl);
- return context;
- }
-
- public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
- egl.eglDestroyContext(display, context);
- }
- }
-
- private static void checkEglError(String prompt, EGL10 egl) {
- int error;
- while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
- Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
- }
- }
- /* Fallback if 32bit View is not supported*/
- private static class FallbackConfigChooser extends ConfigChooser {
- private ConfigChooser fallback;
-
- public FallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, ConfigChooser fallback) {
- super(r, g, b, a, depth, stencil);
- this.fallback = fallback;
- }
-
- @Override
- public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
- EGLConfig ec = super.chooseConfig(egl, display, configs);
- if (ec == null) {
- Log.w(TAG, "Trying ConfigChooser fallback");
- ec = fallback.chooseConfig(egl, display, configs);
- use_32 = false;
- }
- return ec;
- }
+ return inputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event);
}
- private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
-
- public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
- mRedSize = r;
- mGreenSize = g;
- mBlueSize = b;
- mAlphaSize = a;
- mDepthSize = depth;
- mStencilSize = stencil;
- }
-
- /* This EGL config specification is used to specify 2.0 rendering.
- * We use a minimum size of 4 bits for red/green/blue, but will
- * perform actual matching in chooseConfig() below.
- */
- private static int EGL_OPENGL_ES2_BIT = 4;
- private static int[] s_configAttribs2 = {
- EGL10.EGL_RED_SIZE, 4,
- EGL10.EGL_GREEN_SIZE, 4,
- EGL10.EGL_BLUE_SIZE, 4,
- // EGL10.EGL_DEPTH_SIZE, 16,
- // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL10.EGL_NONE
- };
- private static int[] s_configAttribs3 = {
- EGL10.EGL_RED_SIZE, 4,
- EGL10.EGL_GREEN_SIZE, 4,
- EGL10.EGL_BLUE_SIZE, 4,
- // EGL10.EGL_DEPTH_SIZE, 16,
- // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
- EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT
- EGL10.EGL_NONE
- };
-
- public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
-
- /* Get the number of minimally matching EGL configurations
- */
- int[] num_config = new int[1];
- egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config);
-
- int numConfigs = num_config[0];
-
- if (numConfigs <= 0) {
- throw new IllegalArgumentException("No configs match configSpec");
- }
-
- /* Allocate then read the array of minimally matching EGL configs
- */
- EGLConfig[] configs = new EGLConfig[numConfigs];
- egl.eglChooseConfig(display, use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config);
+ private void init(XRMode xrMode, boolean translucent, int depth, int stencil) {
- if (DEBUG) {
- printConfigs(egl, display, configs);
- }
- /* Now return the "best" one
- */
- return chooseConfig(egl, display, configs);
- }
+ setPreserveEGLContextOnPause(true);
+ setFocusableInTouchMode(true);
+ switch (xrMode) {
- public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
- EGLConfig[] configs) {
- for (EGLConfig config : configs) {
- int d = findConfigAttrib(egl, display, config,
- EGL10.EGL_DEPTH_SIZE, 0);
- int s = findConfigAttrib(egl, display, config,
- EGL10.EGL_STENCIL_SIZE, 0);
+ case OVR:
+ // Replace the default egl config chooser.
+ setEGLConfigChooser(new OvrConfigChooser());
- // We need at least mDepthSize and mStencilSize bits
- if (d < mDepthSize || s < mStencilSize)
- continue;
+ // Replace the default context factory.
+ setEGLContextFactory(new OvrContextFactory());
- // We want an *exact* match for red/green/blue/alpha
- int r = findConfigAttrib(egl, display, config,
- EGL10.EGL_RED_SIZE, 0);
- int g = findConfigAttrib(egl, display, config,
- EGL10.EGL_GREEN_SIZE, 0);
- int b = findConfigAttrib(egl, display, config,
- EGL10.EGL_BLUE_SIZE, 0);
- int a = findConfigAttrib(egl, display, config,
- EGL10.EGL_ALPHA_SIZE, 0);
+ // Replace the default window surface factory.
+ setEGLWindowSurfaceFactory(new OvrWindowSurfaceFactory());
+ break;
- if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
- return config;
- }
- return null;
- }
+ case PANCAKE:
+ default:
+ /* By default, GLSurfaceView() creates a RGB_565 opaque surface.
+ * If we want a translucent one, we should change the surface's
+ * format here, using PixelFormat.TRANSLUCENT for GL Surfaces
+ * is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
+ */
+ if (translucent) {
+ this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ }
- private int findConfigAttrib(EGL10 egl, EGLDisplay display,
- EGLConfig config, int attribute, int defaultValue) {
+ /* Setup the context factory for 2.0 rendering.
+ * See ContextFactory class definition below
+ */
+ setEGLContextFactory(new PancakeContextFactory());
- if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
- return mValue[0];
- }
- return defaultValue;
- }
+ /* We need to choose an EGLConfig that matches the format of
+ * our surface exactly. This is going to be done in our
+ * custom config chooser. See ConfigChooser class definition
+ * below.
+ */
- private void printConfigs(EGL10 egl, EGLDisplay display,
- EGLConfig[] configs) {
- int numConfigs = configs.length;
- Log.w(TAG, String.format("%d configurations", numConfigs));
- for (int i = 0; i < numConfigs; i++) {
- Log.w(TAG, String.format("Configuration %d:\n", i));
- printConfig(egl, display, configs[i]);
- }
- }
+ if (GLUtils.use_32) {
+ setEGLConfigChooser(translucent ?
+ new PancakeFallbackConfigChooser(8, 8, 8, 8, 24, stencil,
+ new PancakeConfigChooser(8, 8, 8, 8, 16, stencil)) :
+ new PancakeFallbackConfigChooser(8, 8, 8, 8, 24, stencil,
+ new PancakeConfigChooser(5, 6, 5, 0, 16, stencil)));
- private void printConfig(EGL10 egl, EGLDisplay display,
- EGLConfig config) {
- int[] attributes = {
- EGL10.EGL_BUFFER_SIZE,
- EGL10.EGL_ALPHA_SIZE,
- EGL10.EGL_BLUE_SIZE,
- EGL10.EGL_GREEN_SIZE,
- EGL10.EGL_RED_SIZE,
- EGL10.EGL_DEPTH_SIZE,
- EGL10.EGL_STENCIL_SIZE,
- EGL10.EGL_CONFIG_CAVEAT,
- EGL10.EGL_CONFIG_ID,
- EGL10.EGL_LEVEL,
- EGL10.EGL_MAX_PBUFFER_HEIGHT,
- EGL10.EGL_MAX_PBUFFER_PIXELS,
- EGL10.EGL_MAX_PBUFFER_WIDTH,
- EGL10.EGL_NATIVE_RENDERABLE,
- EGL10.EGL_NATIVE_VISUAL_ID,
- EGL10.EGL_NATIVE_VISUAL_TYPE,
- 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
- EGL10.EGL_SAMPLES,
- EGL10.EGL_SAMPLE_BUFFERS,
- EGL10.EGL_SURFACE_TYPE,
- EGL10.EGL_TRANSPARENT_TYPE,
- EGL10.EGL_TRANSPARENT_RED_VALUE,
- EGL10.EGL_TRANSPARENT_GREEN_VALUE,
- EGL10.EGL_TRANSPARENT_BLUE_VALUE,
- 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
- 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
- 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
- 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
- EGL10.EGL_LUMINANCE_SIZE,
- EGL10.EGL_ALPHA_MASK_SIZE,
- EGL10.EGL_COLOR_BUFFER_TYPE,
- EGL10.EGL_RENDERABLE_TYPE,
- 0x3042 // EGL10.EGL_CONFORMANT
- };
- String[] names = {
- "EGL_BUFFER_SIZE",
- "EGL_ALPHA_SIZE",
- "EGL_BLUE_SIZE",
- "EGL_GREEN_SIZE",
- "EGL_RED_SIZE",
- "EGL_DEPTH_SIZE",
- "EGL_STENCIL_SIZE",
- "EGL_CONFIG_CAVEAT",
- "EGL_CONFIG_ID",
- "EGL_LEVEL",
- "EGL_MAX_PBUFFER_HEIGHT",
- "EGL_MAX_PBUFFER_PIXELS",
- "EGL_MAX_PBUFFER_WIDTH",
- "EGL_NATIVE_RENDERABLE",
- "EGL_NATIVE_VISUAL_ID",
- "EGL_NATIVE_VISUAL_TYPE",
- "EGL_PRESERVED_RESOURCES",
- "EGL_SAMPLES",
- "EGL_SAMPLE_BUFFERS",
- "EGL_SURFACE_TYPE",
- "EGL_TRANSPARENT_TYPE",
- "EGL_TRANSPARENT_RED_VALUE",
- "EGL_TRANSPARENT_GREEN_VALUE",
- "EGL_TRANSPARENT_BLUE_VALUE",
- "EGL_BIND_TO_TEXTURE_RGB",
- "EGL_BIND_TO_TEXTURE_RGBA",
- "EGL_MIN_SWAP_INTERVAL",
- "EGL_MAX_SWAP_INTERVAL",
- "EGL_LUMINANCE_SIZE",
- "EGL_ALPHA_MASK_SIZE",
- "EGL_COLOR_BUFFER_TYPE",
- "EGL_RENDERABLE_TYPE",
- "EGL_CONFORMANT"
- };
- int[] value = new int[1];
- for (int i = 0; i < attributes.length; i++) {
- int attribute = attributes[i];
- String name = names[i];
- if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
- Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
} else {
- // Log.w(TAG, String.format(" %s: failed\n", name));
- while (egl.eglGetError() != EGL10.EGL_SUCCESS)
- ;
+ setEGLConfigChooser(translucent ?
+ new PancakeConfigChooser(8, 8, 8, 8, 16, stencil) :
+ new PancakeConfigChooser(5, 6, 5, 0, 16, stencil));
}
- }
+ break;
}
- // Subclasses can adjust these values:
- protected int mRedSize;
- protected int mGreenSize;
- protected int mBlueSize;
- protected int mAlphaSize;
- protected int mDepthSize;
- protected int mStencilSize;
- private int[] mValue = new int[1];
+ /* Set the renderer responsible for frame rendering */
+ setRenderer(new GodotRenderer());
}
- private static class Renderer implements GLSurfaceView.Renderer {
-
- public void onDrawFrame(GL10 gl) {
- GodotLib.step();
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLDrawFrame(gl);
- }
- }
-
- public void onSurfaceChanged(GL10 gl, int width, int height) {
-
- GodotLib.resize(width, height);
- for (int i = 0; i < Godot.singleton_count; i++) {
- Godot.singletons[i].onGLSurfaceChanged(gl, width, height);
- }
- }
-
- public void onSurfaceCreated(GL10 gl, EGLConfig config) {
- GodotLib.newcontext(use_32);
- }
+ public void onBackPressed() {
+ activity.onBackPressed();
}
}
diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java
new file mode 100644
index 0000000000..d01f958123
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -0,0 +1,352 @@
+/*************************************************************************/
+/* GodotInputHandler.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.input;
+
+import static org.godotengine.godot.utils.GLUtils.DEBUG;
+
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.InputDevice.MotionRange;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.GodotView;
+import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
+
+/**
+ * Handles input related events for the {@link GodotView} view.
+ */
+public class GodotInputHandler implements InputDeviceListener {
+
+ private final ArrayList<Joystick> joysticksDevices = new ArrayList<Joystick>();
+
+ private final GodotView godotView;
+ private final InputManagerCompat inputManager;
+
+ public GodotInputHandler(GodotView godotView) {
+ this.godotView = godotView;
+ this.inputManager = InputManagerCompat.Factory.getInputManager(godotView.getContext());
+ this.inputManager.registerInputDeviceListener(this, null);
+ }
+
+ private void queueEvent(Runnable task) {
+ godotView.queueEvent(task);
+ }
+
+ public boolean onKeyUp(final int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ return false;
+ };
+
+ int source = event.getSource();
+ if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
+
+ final int button = getGodotButton(keyCode);
+ final int device_id = findJoystickDevice(event.getDeviceId());
+
+ // Check if the device exists
+ if (device_id > -1) {
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joybutton(device_id, button, false);
+ }
+ });
+ return true;
+ }
+ } else {
+ final int chr = event.getUnicodeChar(0);
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.key(keyCode, chr, false);
+ }
+ });
+ };
+
+ return false;
+ }
+
+ public boolean onKeyDown(final int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ godotView.onBackPressed();
+ // press 'back' button should not terminate program
+ //normal handle 'back' event in game logic
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ return false;
+ };
+
+ int source = event.getSource();
+ //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
+
+ if ((source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
+
+ if (event.getRepeatCount() > 0) // ignore key echo
+ return true;
+
+ final int button = getGodotButton(keyCode);
+ final int device_id = findJoystickDevice(event.getDeviceId());
+
+ // Check if the device exists
+ if (device_id > -1) {
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joybutton(device_id, button, true);
+ }
+ });
+ return true;
+ }
+ } else {
+ final int chr = event.getUnicodeChar(0);
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.key(keyCode, chr, true);
+ }
+ });
+ };
+
+ return false;
+ }
+
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
+
+ final int device_id = findJoystickDevice(event.getDeviceId());
+
+ // Check if the device exists
+ if (device_id > -1) {
+ Joystick joy = joysticksDevices.get(device_id);
+
+ for (int i = 0; i < joy.axes.size(); i++) {
+ InputDevice.MotionRange range = joy.axes.get(i);
+ final float value = (event.getAxisValue(range.getAxis()) - range.getMin()) / range.getRange() * 2.0f - 1.0f;
+ final int idx = i;
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joyaxis(device_id, idx, value);
+ }
+ });
+ }
+
+ for (int i = 0; i < joy.hats.size(); i += 2) {
+ final int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
+ final int hatY = Math.round(event.getAxisValue(joy.hats.get(i + 1).getAxis()));
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joyhat(device_id, hatX, hatY);
+ }
+ });
+ }
+ return true;
+ }
+ };
+
+ return false;
+ }
+
+ public void initInputDevices() {
+ /* initially add input devices*/
+ int[] deviceIds = inputManager.getInputDeviceIds();
+ for (int deviceId : deviceIds) {
+ InputDevice device = inputManager.getInputDevice(deviceId);
+ if (DEBUG) {
+ Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
+ }
+ onInputDeviceAdded(deviceId);
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ int id = findJoystickDevice(deviceId);
+
+ // Check if the device has not been already added
+ if (id < 0) {
+ InputDevice device = inputManager.getInputDevice(deviceId);
+ //device can be null if deviceId is not found
+ if (device != null) {
+ int sources = device.getSources();
+ if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) ||
+ ((sources & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK)) {
+ id = joysticksDevices.size();
+
+ Joystick joy = new Joystick();
+ joy.device_id = deviceId;
+ joy.name = device.getName();
+ joy.axes = new ArrayList<InputDevice.MotionRange>();
+ joy.hats = new ArrayList<InputDevice.MotionRange>();
+
+ List<InputDevice.MotionRange> ranges = device.getMotionRanges();
+ Collections.sort(ranges, new RangeComparator());
+
+ for (InputDevice.MotionRange range : ranges) {
+ if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ joy.hats.add(range);
+ } else {
+ joy.axes.add(range);
+ }
+ }
+
+ joysticksDevices.add(joy);
+
+ final int device_id = id;
+ final String name = joy.name;
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joyconnectionchanged(device_id, true, name);
+ }
+ });
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ final int device_id = findJoystickDevice(deviceId);
+
+ // Check if the evice has not been already removed
+ if (device_id > -1) {
+ joysticksDevices.remove(device_id);
+
+ queueEvent(new Runnable() {
+ @Override
+ public void run() {
+ GodotLib.joyconnectionchanged(device_id, false, "");
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ onInputDeviceRemoved(deviceId);
+ onInputDeviceAdded(deviceId);
+ }
+
+ private static class RangeComparator implements Comparator<MotionRange> {
+ @Override
+ public int compare(MotionRange arg0, MotionRange arg1) {
+ return arg0.getAxis() - arg1.getAxis();
+ }
+ }
+
+ public static int getGodotButton(int keyCode) {
+ int button;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B
+ button = 0;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_B:
+ button = 1;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y
+ button = 2;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_Y:
+ button = 3;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_L1:
+ button = 9;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_L2:
+ button = 15;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_R1:
+ button = 10;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_R2:
+ button = 16;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_SELECT:
+ button = 4;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_START:
+ button = 6;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_THUMBL:
+ button = 7;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_THUMBR:
+ button = 8;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ button = 11;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ button = 12;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ button = 13;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ button = 14;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_C:
+ button = 17;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_Z:
+ button = 18;
+ break;
+
+ default:
+ button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20;
+ break;
+ }
+ return button;
+ }
+
+ private int findJoystickDevice(int device_id) {
+ for (int i = 0; i < joysticksDevices.size(); i++) {
+ if (joysticksDevices.get(i).device_id == device_id) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/input/Joystick.java b/platform/android/java/src/org/godotengine/godot/input/Joystick.java
new file mode 100644
index 0000000000..ff95bfb0c5
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/Joystick.java
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* Joystick.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.input;
+
+import android.view.InputDevice.MotionRange;
+import java.util.ArrayList;
+
+/**
+ * POJO class to represent a Joystick input device.
+ */
+class Joystick {
+ int device_id;
+ String name;
+ ArrayList<MotionRange> axes;
+ ArrayList<MotionRange> hats;
+}
diff --git a/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java b/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java
new file mode 100644
index 0000000000..6c95494f8b
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/utils/GLUtils.java
@@ -0,0 +1,157 @@
+/*************************************************************************/
+/* GLUtils.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.utils;
+
+import android.util.Log;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/**
+ * Contains GL utilities methods.
+ */
+public class GLUtils {
+
+ private static final String TAG = GLUtils.class.getSimpleName();
+
+ public static final boolean DEBUG = false;
+
+ public static boolean use_gl3 = false;
+ public static boolean use_32 = false;
+ public static boolean use_debug_opengl = false;
+
+ private static final String[] ATTRIBUTES_NAMES = new String[] {
+ "EGL_BUFFER_SIZE",
+ "EGL_ALPHA_SIZE",
+ "EGL_BLUE_SIZE",
+ "EGL_GREEN_SIZE",
+ "EGL_RED_SIZE",
+ "EGL_DEPTH_SIZE",
+ "EGL_STENCIL_SIZE",
+ "EGL_CONFIG_CAVEAT",
+ "EGL_CONFIG_ID",
+ "EGL_LEVEL",
+ "EGL_MAX_PBUFFER_HEIGHT",
+ "EGL_MAX_PBUFFER_PIXELS",
+ "EGL_MAX_PBUFFER_WIDTH",
+ "EGL_NATIVE_RENDERABLE",
+ "EGL_NATIVE_VISUAL_ID",
+ "EGL_NATIVE_VISUAL_TYPE",
+ "EGL_PRESERVED_RESOURCES",
+ "EGL_SAMPLES",
+ "EGL_SAMPLE_BUFFERS",
+ "EGL_SURFACE_TYPE",
+ "EGL_TRANSPARENT_TYPE",
+ "EGL_TRANSPARENT_RED_VALUE",
+ "EGL_TRANSPARENT_GREEN_VALUE",
+ "EGL_TRANSPARENT_BLUE_VALUE",
+ "EGL_BIND_TO_TEXTURE_RGB",
+ "EGL_BIND_TO_TEXTURE_RGBA",
+ "EGL_MIN_SWAP_INTERVAL",
+ "EGL_MAX_SWAP_INTERVAL",
+ "EGL_LUMINANCE_SIZE",
+ "EGL_ALPHA_MASK_SIZE",
+ "EGL_COLOR_BUFFER_TYPE",
+ "EGL_RENDERABLE_TYPE",
+ "EGL_CONFORMANT"
+ };
+
+ private static final int[] ATTRIBUTES = new int[] {
+ EGL10.EGL_BUFFER_SIZE,
+ EGL10.EGL_ALPHA_SIZE,
+ EGL10.EGL_BLUE_SIZE,
+ EGL10.EGL_GREEN_SIZE,
+ EGL10.EGL_RED_SIZE,
+ EGL10.EGL_DEPTH_SIZE,
+ EGL10.EGL_STENCIL_SIZE,
+ EGL10.EGL_CONFIG_CAVEAT,
+ EGL10.EGL_CONFIG_ID,
+ EGL10.EGL_LEVEL,
+ EGL10.EGL_MAX_PBUFFER_HEIGHT,
+ EGL10.EGL_MAX_PBUFFER_PIXELS,
+ EGL10.EGL_MAX_PBUFFER_WIDTH,
+ EGL10.EGL_NATIVE_RENDERABLE,
+ EGL10.EGL_NATIVE_VISUAL_ID,
+ EGL10.EGL_NATIVE_VISUAL_TYPE,
+ 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+ EGL10.EGL_SAMPLES,
+ EGL10.EGL_SAMPLE_BUFFERS,
+ EGL10.EGL_SURFACE_TYPE,
+ EGL10.EGL_TRANSPARENT_TYPE,
+ EGL10.EGL_TRANSPARENT_RED_VALUE,
+ EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+ EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+ 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+ 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+ 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+ 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+ EGL10.EGL_LUMINANCE_SIZE,
+ EGL10.EGL_ALPHA_MASK_SIZE,
+ EGL10.EGL_COLOR_BUFFER_TYPE,
+ EGL10.EGL_RENDERABLE_TYPE,
+ 0x3042 // EGL10.EGL_CONFORMANT
+ };
+
+ private GLUtils() {}
+
+ public static void checkEglError(String tag, String prompt, EGL10 egl) {
+ int error;
+ while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+ Log.e(tag, String.format("%s: EGL error: 0x%x", prompt, error));
+ }
+ }
+
+ public static void printConfigs(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ int numConfigs = configs.length;
+ Log.v(TAG, String.format("%d configurations", numConfigs));
+ for (int i = 0; i < numConfigs; i++) {
+ Log.v(TAG, String.format("Configuration %d:\n", i));
+ printConfig(egl, display, configs[i]);
+ }
+ }
+
+ private static void printConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig config) {
+ int[] value = new int[1];
+ for (int i = 0; i < ATTRIBUTES.length; i++) {
+ int attribute = ATTRIBUTES[i];
+ String name = ATTRIBUTES_NAMES[i];
+ if (egl.eglGetConfigAttrib(display, config, attribute, value)) {
+ Log.i(TAG, String.format(" %s: %d\n", name, value[0]));
+ } else {
+ // Log.w(TAG, String.format(" %s: failed\n", name));
+ while (egl.eglGetError() != EGL10.EGL_SUCCESS)
+ ;
+ }
+ }
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/XRMode.java b/platform/android/java/src/org/godotengine/godot/xr/XRMode.java
new file mode 100644
index 0000000000..cbc8a1e902
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/XRMode.java
@@ -0,0 +1,39 @@
+/*************************************************************************/
+/* XRMode.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr;
+
+/**
+ * Godot available XR modes.
+ */
+public enum XRMode {
+ PANCAKE, // Regular/flatscreen
+ OVR, // Oculus mobile VR SDK
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
new file mode 100644
index 0000000000..ff836a31ca
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrConfigChooser.java
@@ -0,0 +1,112 @@
+/*************************************************************************/
+/* OvrConfigChooser.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.ovr;
+
+import android.opengl.EGLExt;
+import android.opengl.GLSurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/**
+ * EGL config chooser for the Oculus Mobile VR SDK.
+ */
+public class OvrConfigChooser implements GLSurfaceView.EGLConfigChooser {
+
+ private static final int[] CONFIG_ATTRIBS = {
+ EGL10.EGL_RED_SIZE, 8,
+ EGL10.EGL_GREEN_SIZE, 8,
+ EGL10.EGL_BLUE_SIZE, 8,
+ EGL10.EGL_ALPHA_SIZE, 8, // Need alpha for the multi-pass timewarp compositor
+ EGL10.EGL_DEPTH_SIZE, 0,
+ EGL10.EGL_STENCIL_SIZE, 0,
+ EGL10.EGL_SAMPLES, 0,
+ EGL10.EGL_NONE
+ };
+
+ @Override
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+ // Do NOT use eglChooseConfig, because the Android EGL code pushes in
+ // multisample flags in eglChooseConfig if the user has selected the "force 4x
+ // MSAA" option in settings, and that is completely wasted for our warp
+ // target.
+ int[] numConfig = new int[1];
+ if (!egl.eglGetConfigs(display, null, 0, numConfig)) {
+ throw new IllegalArgumentException("eglGetConfigs failed.");
+ }
+
+ int configsCount = numConfig[0];
+ if (configsCount <= 0) {
+ throw new IllegalArgumentException("No configs match configSpec");
+ }
+
+ EGLConfig[] configs = new EGLConfig[configsCount];
+ if (!egl.eglGetConfigs(display, configs, configsCount, numConfig)) {
+ throw new IllegalArgumentException("eglGetConfigs #2 failed.");
+ }
+
+ int[] value = new int[1];
+ for (EGLConfig config : configs) {
+ egl.eglGetConfigAttrib(display, config, EGL10.EGL_RENDERABLE_TYPE, value);
+ if ((value[0] & EGLExt.EGL_OPENGL_ES3_BIT_KHR) != EGLExt.EGL_OPENGL_ES3_BIT_KHR) {
+ continue;
+ }
+
+ // The pbuffer config also needs to be compatible with normal window rendering
+ // so it can share textures with the window context.
+ egl.eglGetConfigAttrib(display, config, EGL10.EGL_SURFACE_TYPE, value);
+ if ((value[0] & (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) != (EGL10.EGL_WINDOW_BIT | EGL10.EGL_PBUFFER_BIT)) {
+ continue;
+ }
+
+ // Check each attribute in CONFIG_ATTRIBS (which are the attributes we care about)
+ // and ensure the value in config matches.
+ int attribIndex = 0;
+ while (CONFIG_ATTRIBS[attribIndex] != EGL10.EGL_NONE) {
+ egl.eglGetConfigAttrib(display, config, CONFIG_ATTRIBS[attribIndex], value);
+ if (value[0] != CONFIG_ATTRIBS[attribIndex + 1]) {
+ // Attribute key's value does not match the configs value.
+ // Start checking next config.
+ break;
+ }
+
+ // Step by two because CONFIG_ATTRIBS is in key/value pairs.
+ attribIndex += 2;
+ }
+
+ if (CONFIG_ATTRIBS[attribIndex] == EGL10.EGL_NONE) {
+ // All relevant attributes match, set the config and stop checking the rest.
+ return config;
+ }
+ }
+ return null;
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
new file mode 100644
index 0000000000..5f6da8c672
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrContextFactory.java
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* OvrContextFactory.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.ovr;
+
+import android.opengl.EGL14;
+import android.opengl.GLSurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+
+/**
+ * EGL Context factory for the Oculus mobile VR SDK.
+ */
+public class OvrContextFactory implements GLSurfaceView.EGLContextFactory {
+
+ private static final int[] CONTEXT_ATTRIBS = {
+ EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE
+ };
+
+ @Override
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+ return egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, CONTEXT_ATTRIBS);
+ }
+
+ @Override
+ public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+ egl.eglDestroyContext(display, context);
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
new file mode 100644
index 0000000000..f1e38c35d8
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/ovr/OvrWindowSurfaceFactory.java
@@ -0,0 +1,60 @@
+/*************************************************************************/
+/* OvrWindowSurfaceFactory.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.ovr;
+
+import android.opengl.GLSurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+/**
+ * EGL window surface factory for the Oculus mobile VR SDK.
+ */
+public class OvrWindowSurfaceFactory implements GLSurfaceView.EGLWindowSurfaceFactory {
+
+ private final static int[] SURFACE_ATTRIBS = {
+ EGL10.EGL_WIDTH, 16,
+ EGL10.EGL_HEIGHT, 16,
+ EGL10.EGL_NONE
+ };
+
+ @Override
+ public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
+ Object nativeWindow) {
+ return egl.eglCreatePbufferSurface(display, config, SURFACE_ATTRIBS);
+ }
+
+ @Override
+ public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
+ egl.eglDestroySurface(display, surface);
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java
new file mode 100644
index 0000000000..ac19a09e76
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeConfigChooser.java
@@ -0,0 +1,151 @@
+/*************************************************************************/
+/* PancakeConfigChooser.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.pancake;
+
+import android.opengl.GLSurfaceView;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+import org.godotengine.godot.utils.GLUtils;
+
+/**
+ * Used to select the egl config for pancake games.
+ */
+public class PancakeConfigChooser implements GLSurfaceView.EGLConfigChooser {
+
+ private static final String TAG = PancakeConfigChooser.class.getSimpleName();
+
+ private int[] mValue = new int[1];
+
+ /* This EGL config specification is used to specify 2.0 rendering.
+ * We use a minimum size of 4 bits for red/green/blue, but will
+ * perform actual matching in chooseConfig() below.
+ */
+ private static int EGL_OPENGL_ES2_BIT = 4;
+ private static int[] s_configAttribs2 = {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ // EGL10.EGL_DEPTH_SIZE, 16,
+ // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+ private static int[] s_configAttribs3 = {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ // EGL10.EGL_DEPTH_SIZE, 16,
+ // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //apparently there is no EGL_OPENGL_ES3_BIT
+ EGL10.EGL_NONE
+ };
+
+ public PancakeConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+ mRedSize = r;
+ mGreenSize = g;
+ mBlueSize = b;
+ mAlphaSize = a;
+ mDepthSize = depth;
+ mStencilSize = stencil;
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+
+ /* Get the number of minimally matching EGL configurations
+ */
+ int[] num_config = new int[1];
+ egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, null, 0, num_config);
+
+ int numConfigs = num_config[0];
+
+ if (numConfigs <= 0) {
+ throw new IllegalArgumentException("No configs match configSpec");
+ }
+
+ /* Allocate then read the array of minimally matching EGL configs
+ */
+ EGLConfig[] configs = new EGLConfig[numConfigs];
+ egl.eglChooseConfig(display, GLUtils.use_gl3 ? s_configAttribs3 : s_configAttribs2, configs, numConfigs, num_config);
+
+ if (GLUtils.DEBUG) {
+ GLUtils.printConfigs(egl, display, configs);
+ }
+ /* Now return the "best" one
+ */
+ return chooseConfig(egl, display, configs);
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ for (EGLConfig config : configs) {
+ int d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0);
+ int s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0);
+
+ // We need at least mDepthSize and mStencilSize bits
+ if (d < mDepthSize || s < mStencilSize)
+ continue;
+
+ // We want an *exact* match for red/green/blue/alpha
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+
+ if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
+ return config;
+ }
+ return null;
+ }
+
+ private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+ EGLConfig config, int attribute, int defaultValue) {
+
+ if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ return mValue[0];
+ }
+ return defaultValue;
+ }
+
+ // Subclasses can adjust these values:
+ protected int mRedSize;
+ protected int mGreenSize;
+ protected int mBlueSize;
+ protected int mAlphaSize;
+ protected int mDepthSize;
+ protected int mStencilSize;
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java
new file mode 100644
index 0000000000..aca6ffdba6
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeContextFactory.java
@@ -0,0 +1,81 @@
+/*************************************************************************/
+/* PancakeContextFactory.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.pancake;
+
+import android.opengl.GLSurfaceView;
+import android.util.Log;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.utils.GLUtils;
+
+/**
+ * Factory used to setup the opengl context for pancake games.
+ */
+public class PancakeContextFactory implements GLSurfaceView.EGLContextFactory {
+ private static final String TAG = PancakeContextFactory.class.getSimpleName();
+
+ private static final int _EGL_CONTEXT_FLAGS_KHR = 0x30FC;
+ private static final int _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR = 0x00000001;
+
+ private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+ String driver_name = GodotLib.getGlobal("rendering/quality/driver/driver_name");
+ if (GLUtils.use_gl3 && !driver_name.equals("GLES3")) {
+ GLUtils.use_gl3 = false;
+ }
+ if (GLUtils.use_gl3)
+ Log.w(TAG, "creating OpenGL ES 3.0 context :");
+ else
+ Log.w(TAG, "creating OpenGL ES 2.0 context :");
+
+ GLUtils.checkEglError(TAG, "Before eglCreateContext", egl);
+ EGLContext context;
+ if (GLUtils.use_debug_opengl) {
+ int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
+ int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, _EGL_CONTEXT_FLAGS_KHR, _EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL10.EGL_NONE };
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2);
+ } else {
+ int[] attrib_list2 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+ int[] attrib_list3 = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL10.EGL_NONE };
+ context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, GLUtils.use_gl3 ? attrib_list3 : attrib_list2);
+ }
+ GLUtils.checkEglError(TAG, "After eglCreateContext", egl);
+ return context;
+ }
+
+ public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+ egl.eglDestroyContext(display, context);
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java
new file mode 100644
index 0000000000..e19f218916
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/xr/pancake/PancakeFallbackConfigChooser.java
@@ -0,0 +1,61 @@
+/*************************************************************************/
+/* PancakeFallbackConfigChooser.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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.xr.pancake;
+
+import android.util.Log;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLDisplay;
+import org.godotengine.godot.utils.GLUtils;
+
+/* Fallback if 32bit View is not supported*/
+public class PancakeFallbackConfigChooser extends PancakeConfigChooser {
+
+ private static final String TAG = PancakeFallbackConfigChooser.class.getSimpleName();
+
+ private PancakeConfigChooser fallback;
+
+ public PancakeFallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, PancakeConfigChooser fallback) {
+ super(r, g, b, a, depth, stencil);
+ this.fallback = fallback;
+ }
+
+ @Override
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
+ EGLConfig ec = super.chooseConfig(egl, display, configs);
+ if (ec == null) {
+ Log.w(TAG, "Trying ConfigChooser fallback");
+ ec = fallback.chooseConfig(egl, display, configs);
+ GLUtils.use_32 = false;
+ }
+ return ec;
+ }
+}
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 0c41b85939..ade7c03d58 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -52,7 +52,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
- _get_unique_ID = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
+ _get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
@@ -122,9 +122,9 @@ int GodotIOJavaWrapper::get_screen_dpi() {
}
String GodotIOJavaWrapper::get_unique_id() {
- if (_get_unique_ID) {
+ if (_get_unique_id) {
JNIEnv *env = ThreadAndroid::get_env();
- jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_ID);
+ jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_unique_id);
return jstring_to_string(s, env);
} else {
return String();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 920c433b08..100e50fd66 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -50,7 +50,7 @@ private:
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
- jmethodID _get_unique_ID = 0;
+ jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
jmethodID _hide_keyboard = 0;
jmethodID _set_screen_orientation = 0;
diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h
index 3a03294b08..f99935bf7c 100644
--- a/platform/android/java_godot_lib_jni.h
+++ b/platform/android/java_godot_lib_jni.h
@@ -35,7 +35,7 @@
#include <jni.h>
// 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 thats why we have the long names)
+// 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, jobject obj, jobject activity, jobject p_asset_manager, jboolean p_use_apk_expansion);
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_ondestroy(JNIEnv *env);
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 101a1d76c6..339b14974c 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -59,6 +59,9 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_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");
+ _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");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -183,3 +186,28 @@ bool GodotJavaWrapper::request_permission(const String &p_name) {
return false;
}
}
+
+void GodotJavaWrapper::init_input_devices() {
+ if (_init_input_devices) {
+ JNIEnv *env = ThreadAndroid::get_env();
+ env->CallVoidMethod(godot_instance, _init_input_devices);
+ }
+}
+
+jobject GodotJavaWrapper::get_surface() {
+ if (_get_surface) {
+ JNIEnv *env = ThreadAndroid::get_env();
+ return env->CallObjectMethod(godot_instance, _get_surface);
+ } else {
+ return NULL;
+ }
+}
+
+bool GodotJavaWrapper::is_activity_resumed() {
+ if (_is_activity_resumed) {
+ JNIEnv *env = ThreadAndroid::get_env();
+ return env->CallBooleanMethod(godot_instance, _is_activity_resumed);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index 438aee019b..82c2a5d122 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -54,6 +54,9 @@ private:
jmethodID _get_clipboard = 0;
jmethodID _set_clipboard = 0;
jmethodID _request_permission = 0;
+ jmethodID _init_input_devices = 0;
+ jmethodID _get_surface = 0;
+ jmethodID _is_activity_resumed = 0;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance);
@@ -76,6 +79,9 @@ public:
bool has_set_clipboard();
void set_clipboard(const String &p_text);
bool request_permission(const String &p_name);
+ void init_input_devices();
+ jobject get_surface();
+ bool is_activity_resumed();
};
#endif /* !JAVA_GODOT_WRAPPER_H */
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 93d39859f2..ebc319e57d 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -176,6 +176,9 @@ Error OS_Android::initialize(const VideoMode &p_desired, int p_video_driver, int
input = memnew(InputDefault);
input->set_fallback_mapping("Default Android Gamepad");
+ ///@TODO implement a subclass for Android and instantiate that instead
+ camera_server = memnew(CameraServer);
+
//power_manager = memnew(PowerAndroid);
return OK;
@@ -193,6 +196,9 @@ void OS_Android::delete_main_loop() {
}
void OS_Android::finalize() {
+
+ memdelete(camera_server);
+
memdelete(input);
}
@@ -251,6 +257,10 @@ int OS_Android::get_mouse_button_state() const {
}
void OS_Android::set_window_title(const String &p_title) {
+ //This queries/updates the currently connected devices/joypads
+ //Set_window_title is called when initializing the main loop (main.cpp)
+ //therefore this place is found to be suitable (I found no better).
+ godot_java->init_input_devices();
}
void OS_Android::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
@@ -277,7 +287,7 @@ Size2 OS_Android::get_window_size() const {
return Vector2(default_videomode.width, default_videomode.height);
}
-String OS_Android::get_name() {
+String OS_Android::get_name() const {
return "Android";
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index d2198b0579..e74d4cfd43 100644
--- a/platform/android/os_android.h
+++ b/platform/android/os_android.h
@@ -39,6 +39,7 @@
#include "main/input_default.h"
//#include "power_android.h"
#include "servers/audio_server.h"
+#include "servers/camera_server.h"
#include "servers/visual/rasterizer.h"
class GodotJavaWrapper;
@@ -77,6 +78,8 @@ private:
VisualServer *visual_server;
+ CameraServer *camera_server;
+
mutable String data_dir_cache;
//AudioDriverAndroid audio_driver_android;
@@ -139,7 +142,7 @@ public:
virtual Size2 get_window_size() const;
- virtual String get_name();
+ virtual String get_name() const;
virtual MainLoop *get_main_loop() const;
virtual bool can_draw() const;
diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp
index f3fed6669b..9c07535c85 100644
--- a/platform/haiku/os_haiku.cpp
+++ b/platform/haiku/os_haiku.cpp
@@ -69,7 +69,7 @@ void OS_Haiku::run() {
main_loop->finish();
}
-String OS_Haiku::get_name() {
+String OS_Haiku::get_name() const {
return "Haiku";
}
@@ -133,6 +133,8 @@ Error OS_Haiku::initialize(const VideoMode &p_desired, int p_video_driver, int p
window->Show();
visual_server->init();
+ camera_server = memnew(CameraServer);
+
AudioDriverManager::initialize(p_audio_driver);
return OK;
@@ -148,6 +150,8 @@ void OS_Haiku::finalize() {
visual_server->finish();
memdelete(visual_server);
+ memdelete(camera_server);
+
memdelete(input);
#if defined(OPENGL_ENABLED)
diff --git a/platform/haiku/os_haiku.h b/platform/haiku/os_haiku.h
index 6ab006843a..70d78a1978 100644
--- a/platform/haiku/os_haiku.h
+++ b/platform/haiku/os_haiku.h
@@ -38,6 +38,7 @@
#include "haiku_direct_window.h"
#include "main/input_default.h"
#include "servers/audio_server.h"
+#include "servers/camera_server.h"
#include "servers/visual_server.h"
class OS_Haiku : public OS_Unix {
@@ -49,6 +50,7 @@ private:
VisualServer *visual_server;
VideoMode current_video_mode;
int video_driver_index;
+ CameraServer *camera_server;
#ifdef MEDIA_KIT_ENABLED
AudioDriverMediaKit driver_media_kit;
@@ -74,7 +76,7 @@ public:
OS_Haiku();
void run();
- virtual String get_name();
+ virtual String get_name() const;
virtual MainLoop *get_main_loop() const;
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index fa1b124561..85ba56165b 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -14,6 +14,7 @@ iphone_lib = [
'in_app_store.mm',
'icloud.mm',
'ios.mm',
+ 'camera_ios.mm',
]
env_ios = env.Clone()
diff --git a/platform/iphone/camera_ios.h b/platform/iphone/camera_ios.h
new file mode 100644
index 0000000000..cf747283e1
--- /dev/null
+++ b/platform/iphone/camera_ios.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* camera_ios.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef CAMERAIOS_H
+#define CAMERAIOS_H
+
+///@TODO this is a near duplicate of CameraOSX, we should find a way to combine those to minimise code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "servers/camera_server.h"
+
+class CameraIOS : public CameraServer {
+public:
+ CameraIOS();
+ ~CameraIOS();
+
+ void update_feeds();
+};
+
+#endif /* CAMERAIOS_H */ \ No newline at end of file
diff --git a/platform/iphone/camera_ios.mm b/platform/iphone/camera_ios.mm
new file mode 100644
index 0000000000..4c11701fdd
--- /dev/null
+++ b/platform/iphone/camera_ios.mm
@@ -0,0 +1,429 @@
+/*************************************************************************/
+/* camera_ios.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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. */
+/*************************************************************************/
+
+///@TODO this is a near duplicate of CameraOSX, we should find a way to combine those to minimise code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "camera_ios.h"
+#include "servers/camera/camera_feed.h"
+
+#import <AVFoundation/AVFoundation.h>
+#import <UIKit/UIKit.h>
+
+//////////////////////////////////////////////////////////////////////////
+// MyCaptureSession - This is a little helper class so we can capture our frames
+
+@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
+ Ref<CameraFeed> feed;
+ size_t width[2];
+ size_t height[2];
+ PoolVector<uint8_t> img_data[2];
+
+ AVCaptureDeviceInput *input;
+ AVCaptureVideoDataOutput *output;
+}
+
+@end
+
+@implementation MyCaptureSession
+
+- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
+ if (self = [super init]) {
+ NSError *error;
+ feed = p_feed;
+ width[0] = 0;
+ height[0] = 0;
+ width[1] = 0;
+ height[1] = 0;
+
+ // prepare our device
+ [p_device lockForConfiguration:&error];
+
+ [p_device setFocusMode:AVCaptureFocusModeLocked];
+ [p_device setExposureMode:AVCaptureExposureModeLocked];
+ [p_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeLocked];
+
+ [p_device unlockForConfiguration];
+
+ [self beginConfiguration];
+
+ // setup our capture
+ self.sessionPreset = AVCaptureSessionPreset1280x720;
+
+ input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
+ if (!input) {
+ print_line("Couldn't get input device for camera");
+ } else {
+ [self addInput:input];
+ }
+
+ output = [AVCaptureVideoDataOutput new];
+ if (!output) {
+ print_line("Couldn't get output device for camera");
+ } else {
+ NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
+ output.videoSettings = settings;
+
+ // discard if the data output queue is blocked (as we process the still image)
+ [output setAlwaysDiscardsLateVideoFrames:YES];
+
+ // now set ourselves as the delegate to receive new frames. Note that we're doing this on the main thread at the moment, we may need to change this..
+ [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
+
+ [self addOutput:output];
+ }
+
+ [self commitConfiguration];
+
+ // kick off our session..
+ [self startRunning];
+ };
+ return self;
+}
+
+- (void)cleanup {
+ // stop running
+ [self stopRunning];
+
+ // cleanup
+ [self beginConfiguration];
+
+ if (input) {
+ [self removeInput:input];
+ // don't release this
+ input = nil;
+ }
+
+ if (output) {
+ [self removeOutput:output];
+ [output setSampleBufferDelegate:nil queue:NULL];
+ [output release];
+ output = nil;
+ }
+
+ [self commitConfiguration];
+}
+
+- (void)dealloc {
+ // bye bye
+ [super dealloc];
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
+ // This gets called every time our camera has a new image for us to process.
+ // May need to investigate in a way to throttle this if we get more images then we're rendering frames..
+
+ // For now, version 1, we're just doing the bare minimum to make this work...
+
+ CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+ // int width = CVPixelBufferGetWidth(pixelBuffer);
+ // int height = CVPixelBufferGetHeight(pixelBuffer);
+
+ // It says that we need to lock this on the documentation pages but it's not in the samples
+ // need to lock our base address so we can access our pixel buffers, better safe then sorry?
+ CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+
+ // get our buffers
+ unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
+ unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
+ if (dataY == NULL) {
+ print_line("Couldn't access Y pixel buffer data");
+ } else if (dataCbCr == NULL) {
+ print_line("Couldn't access CbCr pixel buffer data");
+ } else {
+ UIDeviceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ Ref<Image> img[2];
+
+ {
+ // do Y
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+ int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+
+ if ((width[0] != new_width) || (height[0] != new_height)) {
+ // printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
+
+ width[0] = new_width;
+ height[0] = new_height;
+ img_data[0].resize(new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[0].write();
+ memcpy(w.ptr(), dataY, new_width * new_height);
+
+ img[0].instance();
+ img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
+ }
+
+ {
+ // do CbCr
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+ int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
+
+ if ((width[1] != new_width) || (height[1] != new_height)) {
+ // printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
+
+ width[1] = new_width;
+ height[1] = new_height;
+ img_data[1].resize(2 * new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[1].write();
+ memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+
+ ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
+ img[1].instance();
+ img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
+ }
+
+ // set our texture...
+ feed->set_YCbCr_imgs(img[0], img[1]);
+
+ // update our matrix to match the orientation, note, before changing anything
+ // here, be aware that the project orientation settings must match your xcode
+ // settings or this will go wrong!
+ Transform2D display_transform;
+ switch (orientation) {
+ case UIInterfaceOrientationPortrait: {
+ display_transform = Transform2D(0.0, -1.0, -1.0, 0.0, 1.0, 1.0);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ display_transform = Transform2D(1.0, 0.0, 0.0, -1.0, 0.0, 1.0);
+ } break;
+ case UIInterfaceOrientationLandscapeLeft: {
+ display_transform = Transform2D(-1.0, 0.0, 0.0, 1.0, 1.0, 0.0);
+ } break;
+ default: {
+ display_transform = Transform2D(0.0, 1.0, 1.0, 0.0, 0.0, 0.0);
+ } break;
+ }
+
+ //TODO: this is correct for the camera on the back, I have a feeling this needs to be inversed for the camera on the front!
+ feed->set_transform(display_transform);
+ }
+
+ // and unlock
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+}
+
+@end
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedIOS - Subclass for camera feeds in iOS
+
+class CameraFeedIOS : public CameraFeed {
+private:
+ bool is_arkit; // if true this feed is updated through ARKit (should only have one and not yet implemented)
+ AVCaptureDevice *device;
+ MyCaptureSession *capture_session;
+
+public:
+ bool get_is_arkit() const;
+ AVCaptureDevice *get_device() const;
+
+ CameraFeedIOS();
+ ~CameraFeedIOS();
+
+ void set_device(AVCaptureDevice *p_device);
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+bool CameraFeedIOS::get_is_arkit() const {
+ return is_arkit;
+};
+
+AVCaptureDevice *CameraFeedIOS::get_device() const {
+ return device;
+};
+
+CameraFeedIOS::CameraFeedIOS() {
+ capture_session = NULL;
+ device = NULL;
+ transform = Transform2D(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); /* should re-orientate this based on device orientation */
+};
+
+void CameraFeedIOS::set_device(AVCaptureDevice *p_device) {
+ device = p_device;
+ if (device == NULL) {
+ ///@TODO finish this!
+ is_arkit = true;
+ name = "ARKit";
+ position = CameraFeed::FEED_BACK;
+ } else {
+ is_arkit = false;
+ [device retain];
+
+ // get some info
+ NSString *device_name = p_device.localizedName;
+ name = device_name.UTF8String;
+ position = CameraFeed::FEED_UNSPECIFIED;
+ if ([p_device position] == AVCaptureDevicePositionBack) {
+ position = CameraFeed::FEED_BACK;
+ } else if ([p_device position] == AVCaptureDevicePositionFront) {
+ position = CameraFeed::FEED_FRONT;
+ };
+ };
+};
+
+CameraFeedIOS::~CameraFeedIOS() {
+ if (capture_session != NULL) {
+ [capture_session release];
+ capture_session = NULL;
+ };
+
+ if (device != NULL) {
+ [device release];
+ device = NULL;
+ };
+};
+
+bool CameraFeedIOS::activate_feed() {
+ if (is_arkit) {
+ ///@TODO to implement;
+ } else {
+ if (capture_session) {
+ // already recording!
+ } else {
+ // start camera capture
+ capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
+ };
+ };
+
+ return true;
+};
+
+void CameraFeedIOS::deactivate_feed() {
+ // end camera capture if we have one
+ if (capture_session) {
+ [capture_session cleanup];
+ [capture_session release];
+ capture_session = NULL;
+ };
+};
+
+//////////////////////////////////////////////////////////////////////////
+// MyDeviceNotifications - This is a little helper class gets notifications
+// when devices are connected/disconnected
+
+@interface MyDeviceNotifications : NSObject {
+ CameraIOS *camera_server;
+}
+
+@end
+
+@implementation MyDeviceNotifications
+
+- (void)devices_changed:(NSNotification *)notification {
+ camera_server->update_feeds();
+}
+
+- (id)initForServer:(CameraIOS *)p_server {
+ if (self = [super init]) {
+ camera_server = p_server;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+ };
+ return self;
+}
+
+- (void)dealloc {
+ // remove notifications
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+
+ [super dealloc];
+}
+
+@end
+
+MyDeviceNotifications *device_notifications = nil;
+
+//////////////////////////////////////////////////////////////////////////
+// CameraIOS - Subclass for our camera server on iPhone
+
+void CameraIOS::update_feeds() {
+ // this way of doing things is deprecated but still works,
+ // rewrite to using AVCaptureDeviceDiscoverySession
+
+ AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
+ Ref<CameraFeedIOS> feed = (Ref<CameraFeedIOS>)feeds[i];
+
+ if (feed->get_is_arkit()) {
+ // ignore, this is our arkit entry
+ } else if (![session.devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
+ };
+ };
+
+ // add new devices..
+ for (AVCaptureDevice *device in session.devices) {
+ bool found = false;
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedIOS> feed = (Ref<CameraFeedIOS>)feeds[i];
+ if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedIOS> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+ add_feed(newfeed);
+ };
+ };
+};
+
+CameraIOS::CameraIOS() {
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
+ completionHandler:^(BOOL granted) {
+ if (granted) {
+ // Find available cameras we have at this time
+ update_feeds();
+
+ // should only have one of these....
+ device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
+ } else {
+ print_line("No access to cameras!");
+ }
+ }];
+};
+
+CameraIOS::~CameraIOS() {
+ [device_notifications release];
+};
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index 3ed0a4ade7..daf4f405fd 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -115,10 +115,12 @@ def configure(env):
env.Append(CPPFLAGS=['-DNEED_LONG_INT'])
env.Append(CPPFLAGS=['-DLIBYUV_DISABLE_NEON'])
- if env['ios_exceptions']:
- env.Append(CCFLAGS=['-fexceptions'])
- else:
- env.Append(CCFLAGS=['-fno-exceptions'])
+ # Disable exceptions on non-tools (template) builds
+ if not env['tools']:
+ if env['ios_exceptions']:
+ env.Append(CCFLAGS=['-fexceptions'])
+ else:
+ env.Append(CCFLAGS=['-fno-exceptions'])
## Link flags
@@ -142,6 +144,7 @@ def configure(env):
'-framework', 'CoreAudio',
'-framework', 'CoreGraphics',
'-framework', 'CoreMedia',
+ '-framework', 'CoreVideo',
'-framework', 'CoreMotion',
'-framework', 'Foundation',
'-framework', 'GameController',
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index e1083aeefc..7ca83320d0 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -819,7 +819,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
String dest_dir = p_path.get_base_dir() + "/";
String binary_name = p_path.get_file().get_basename();
- EditorProgress ep("export", "Exporting for iOS", 5);
+ EditorProgress ep("export", "Exporting for iOS", 5, true);
String team_id = p_preset->get("application/app_store_team_id");
ERR_EXPLAIN("App Store Team ID not specified - cannot configure the project.");
@@ -868,14 +868,18 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
memdelete(da);
}
- ep.step("Making .pck", 0);
+ if (ep.step("Making .pck", 0)) {
+ return ERR_SKIP;
+ }
String pack_path = dest_dir + binary_name + ".pck";
Vector<SharedObject> libraries;
Error err = save_pack(p_preset, pack_path, &libraries);
if (err)
return err;
- ep.step("Extracting and configuring Xcode project", 1);
+ if (ep.step("Extracting and configuring Xcode project", 1)) {
+ return ERR_SKIP;
+ }
String library_to_use = "libgodot.iphone." + String(p_debug ? "debug" : "release") + ".fat.a";
@@ -910,7 +914,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
};
DirAccess *tmp_app_path = DirAccess::create_for_path(dest_dir);
- ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE)
+ ERR_FAIL_COND_V(!tmp_app_path, ERR_CANT_CREATE);
print_line("Unzipping...");
FileAccess *src_f = NULL;
@@ -1053,7 +1057,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
memdelete(f);
#ifdef OSX_ENABLED
- ep.step("Code-signing dylibs", 2);
+ if (ep.step("Code-signing dylibs", 2)) {
+ return ERR_SKIP;
+ }
DirAccess *dylibs_dir = DirAccess::open(dest_dir + binary_name + "/dylibs");
ERR_FAIL_COND_V(!dylibs_dir, ERR_CANT_OPEN);
CodesignData codesign_data(p_preset, p_debug);
@@ -1061,7 +1067,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
memdelete(dylibs_dir);
ERR_FAIL_COND_V(err, err);
- ep.step("Making .xcarchive", 3);
+ if (ep.step("Making .xcarchive", 3)) {
+ return ERR_SKIP;
+ }
String archive_path = p_path.get_basename() + ".xcarchive";
List<String> archive_args;
archive_args.push_back("-project");
@@ -1080,7 +1088,9 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
err = OS::get_singleton()->execute("xcodebuild", archive_args, true);
ERR_FAIL_COND_V(err, err);
- ep.step("Making .ipa", 4);
+ if (ep.step("Making .ipa", 4)) {
+ return ERR_SKIP;
+ }
List<String> export_args;
export_args.push_back("-exportArchive");
export_args.push_back("-archivePath");
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
index e32618e8f6..c60db3d661 100644
--- a/platform/iphone/icloud.mm
+++ b/platform/iphone/icloud.mm
@@ -138,7 +138,7 @@ Variant nsobject_to_variant(NSObject *object) {
//this is a type that icloud supports...but how did you submit it in the first place?
//I guess this is a type that *might* show up, if you were, say, trying to make your game
//compatible with existing cloud data written by another engine's version of your game
- WARN_PRINT("NSDate unsupported, returning null Variant")
+ WARN_PRINT("NSDate unsupported, returning null Variant");
return Variant();
} else if ([object isKindOfClass:[NSNull class]] or object == nil) {
return Variant();
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
index e25efc813b..f5fce66059 100644
--- a/platform/iphone/os_iphone.cpp
+++ b/platform/iphone/os_iphone.cpp
@@ -84,14 +84,14 @@ void OSIPhone::set_data_dir(String p_dir) {
memdelete(da);
};
-void OSIPhone::set_unique_id(String p_ID) {
+void OSIPhone::set_unique_id(String p_id) {
- unique_ID = p_ID;
+ unique_id = p_id;
};
String OSIPhone::get_unique_id() const {
- return unique_ID;
+ return unique_id;
};
void OSIPhone::initialize_core() {
@@ -167,6 +167,8 @@ Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p
input = memnew(InputDefault);
+ camera_server = memnew(CameraIOS);
+
#ifdef GAME_CENTER_ENABLED
game_center = memnew(GameCenter);
Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
@@ -361,6 +363,11 @@ void OSIPhone::finalize() {
if (main_loop) // should not happen?
memdelete(main_loop);
+ if (camera_server) {
+ memdelete(camera_server);
+ camera_server = NULL;
+ }
+
visual_server->finish();
memdelete(visual_server);
// memdelete(rasterizer);
@@ -495,7 +502,7 @@ String OSIPhone::get_user_data_dir() const {
return data_dir;
};
-String OSIPhone::get_name() {
+String OSIPhone::get_name() const {
return "iOS";
};
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 49c6475cf9..c16c29a858 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -37,6 +37,7 @@
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
+#include "camera_ios.h"
#include "game_center.h"
#include "icloud.h"
#include "in_app_store.h"
@@ -60,6 +61,8 @@ private:
AudioDriverCoreAudio audio_driver;
+ CameraServer *camera_server;
+
#ifdef GAME_CENTER_ENABLED
GameCenter *game_center;
#endif
@@ -107,7 +110,7 @@ private:
void queue_event(const Ref<InputEvent> &p_event);
String data_dir;
- String unique_ID;
+ String unique_id;
String locale_code;
InputDefault *input;
@@ -174,7 +177,7 @@ public:
void set_data_dir(String p_dir);
- virtual String get_name();
+ virtual String get_name() const;
Error shell_open(String p_uri);
@@ -183,7 +186,7 @@ public:
void set_locale(String p_locale);
String get_locale() const;
- void set_unique_id(String p_ID);
+ void set_unique_id(String p_id);
String get_unique_id() const;
virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp
index 11104007e2..163826f828 100644
--- a/platform/javascript/audio_driver_javascript.cpp
+++ b/platform/javascript/audio_driver_javascript.cpp
@@ -99,7 +99,7 @@ Error AudioDriverJavaScript::init() {
return FAILED;
}
- if (!internal_buffer || memarr_len(internal_buffer) != buffer_length * channel_count) {
+ if (!internal_buffer || (int)memarr_len(internal_buffer) != buffer_length * channel_count) {
if (internal_buffer)
memdelete_arr(internal_buffer);
internal_buffer = memnew_arr(float, buffer_length *channel_count);
diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py
index 145ce8d83d..145ac42863 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -110,10 +110,12 @@ def configure(env):
# once feasible also consider memory buffer size issues.
env.Append(CPPDEFINES=['NO_THREADS'])
- # These flags help keep the file size down.
- env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
- # Don't use dynamic_cast, necessary with no-rtti.
- env.Append(CPPDEFINES=['NO_SAFE_CAST'])
+ # Disable exceptions and rtti on non-tools (template) builds
+ if not env['tools']:
+ # These flags help keep the file size down.
+ env.Append(CCFLAGS=['-fno-exceptions', '-fno-rtti'])
+ # Don't use dynamic_cast, necessary with no-rtti.
+ env.Append(CPPDEFINES=['NO_SAFE_CAST'])
if env['javascript_eval']:
env.Append(CPPDEFINES=['JAVASCRIPT_EVAL_ENABLED'])
diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc
index d707d623ab..c034069ab2 100644
--- a/platform/javascript/http_client.h.inc
+++ b/platform/javascript/http_client.h.inc
@@ -3,7 +3,7 @@
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
-/* http://www.godotengine.org */
+/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp
index 34781ce365..d96ffc3a55 100644
--- a/platform/javascript/os_javascript.cpp
+++ b/platform/javascript/os_javascript.cpp
@@ -70,6 +70,20 @@ static bool is_canvas_focused() {
/* clang-format on */
}
+static Point2 correct_canvas_position(int x, int y) {
+ int canvas_width;
+ int canvas_height;
+ emscripten_get_canvas_element_size(NULL, &canvas_width, &canvas_height);
+
+ double element_width;
+ double element_height;
+ emscripten_get_element_css_size(NULL, &element_width, &element_height);
+
+ x = (int)(canvas_width / element_width * x);
+ y = (int)(canvas_height / element_height * y);
+ return Point2(x, y);
+}
+
static bool cursor_inside_canvas = true;
EM_BOOL OS_JavaScript::fullscreen_change_callback(int p_event_type, const EmscriptenFullscreenChangeEvent *p_event, void *p_user_data) {
@@ -285,7 +299,7 @@ EM_BOOL OS_JavaScript::mouse_button_callback(int p_event_type, const EmscriptenM
Ref<InputEventMouseButton> ev;
ev.instance();
ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_MOUSEDOWN);
- ev->set_position(Point2(p_event->canvasX, p_event->canvasY));
+ ev->set_position(correct_canvas_position(p_event->canvasX, p_event->canvasY));
ev->set_global_position(ev->get_position());
dom2godot_mod(p_event, ev);
switch (p_event->button) {
@@ -349,7 +363,7 @@ EM_BOOL OS_JavaScript::mousemove_callback(int p_event_type, const EmscriptenMous
OS_JavaScript *os = get_singleton();
int input_mask = os->input->get_mouse_button_mask();
- Point2 pos = Point2(p_event->canvasX, p_event->canvasY);
+ Point2 pos = correct_canvas_position(p_event->canvasX, p_event->canvasY);
// For motion outside the canvas, only read mouse movement if dragging
// started inside the canvas; imitating desktop app behaviour.
if (!cursor_inside_canvas && !input_mask)
@@ -666,7 +680,7 @@ EM_BOOL OS_JavaScript::touch_press_callback(int p_event_type, const EmscriptenTo
if (!touch.isChanged)
continue;
ev->set_index(touch.identifier);
- ev->set_position(Point2(touch.canvasX, touch.canvasY));
+ ev->set_position(correct_canvas_position(touch.canvasX, touch.canvasY));
os->touches[i] = ev->get_position();
ev->set_pressed(p_event_type == EMSCRIPTEN_EVENT_TOUCHSTART);
@@ -691,7 +705,7 @@ EM_BOOL OS_JavaScript::touchmove_callback(int p_event_type, const EmscriptenTouc
if (!touch.isChanged)
continue;
ev->set_index(touch.identifier);
- ev->set_position(Point2(touch.canvasX, touch.canvasY));
+ ev->set_position(correct_canvas_position(touch.canvasX, touch.canvasY));
Point2 &prev = os->touches[i];
ev->set_relative(ev->get_position() - prev);
prev = ev->get_position();
@@ -795,6 +809,47 @@ const char *OS_JavaScript::get_audio_driver_name(int p_driver) const {
return "JavaScript";
}
+// Clipboard
+extern "C" EMSCRIPTEN_KEEPALIVE void update_clipboard(const char *p_text) {
+ // Only call set_clipboard from OS (sets local clipboard)
+ OS::get_singleton()->OS::set_clipboard(p_text);
+}
+
+void OS_JavaScript::set_clipboard(const String &p_text) {
+ OS::set_clipboard(p_text);
+ /* clang-format off */
+ int err = EM_ASM_INT({
+ var text = UTF8ToString($0);
+ if (!navigator.clipboard || !navigator.clipboard.writeText)
+ return 1;
+ navigator.clipboard.writeText(text).catch(e => {
+ // Setting OS clipboard is only possible from an input callback.
+ console.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e);
+ });
+ return 0;
+ }, p_text.utf8().get_data());
+ /* clang-format on */
+ ERR_EXPLAIN("Clipboard API is not supported.");
+ ERR_FAIL_COND(err);
+}
+
+String OS_JavaScript::get_clipboard() const {
+ /* clang-format off */
+ EM_ASM({
+ try {
+ navigator.clipboard.readText().then(function (result) {
+ ccall('update_clipboard', 'void', ['string'], [result]);
+ }).catch(function (e) {
+ // Fail graciously.
+ });
+ } catch (e) {
+ // Fail graciously.
+ }
+ });
+ /* clang-format on */
+ return this->OS::get_clipboard();
+}
+
// Lifecycle
int OS_JavaScript::get_current_video_driver() const {
return video_driver_index;
@@ -901,6 +956,8 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
VisualServer *visual_server = memnew(VisualServerRaster());
input = memnew(InputDefault);
+ camera_server = memnew(CameraServer);
+
EMSCRIPTEN_RESULT result;
#define EM_CHECK(ev) \
if (result != EMSCRIPTEN_RESULT_SUCCESS) \
@@ -939,6 +996,11 @@ Error OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver,
(['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, index) {
Module.canvas.addEventListener(event, send_notification.bind(null, notifications[index]));
});
+ // Clipboard
+ const update_clipboard = cwrap('update_clipboard', null, ['string']);
+ window.addEventListener('paste', function(evt) {
+ update_clipboard(evt.clipboardData.getData('text'));
+ }, true);
},
MainLoop::NOTIFICATION_WM_MOUSE_ENTER,
MainLoop::NOTIFICATION_WM_MOUSE_EXIT,
@@ -1030,12 +1092,13 @@ void OS_JavaScript::delete_main_loop() {
void OS_JavaScript::finalize() {
+ memdelete(camera_server);
memdelete(input);
}
// Miscellaneous
-Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) {
+Error OS_JavaScript::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
ERR_EXPLAIN("OS::execute() is not available on the HTML5 platform");
ERR_FAIL_V(ERR_UNAVAILABLE);
@@ -1098,7 +1161,7 @@ void OS_JavaScript::set_icon(const Ref<Image> &p_icon) {
Ref<Image> icon = p_icon;
if (icon->is_compressed()) {
icon = icon->duplicate();
- ERR_FAIL_COND(icon->decompress() != OK)
+ ERR_FAIL_COND(icon->decompress() != OK);
}
if (icon->get_format() != Image::FORMAT_RGBA8) {
if (icon == p_icon)
@@ -1159,7 +1222,7 @@ Error OS_JavaScript::shell_open(String p_uri) {
return OK;
}
-String OS_JavaScript::get_name() {
+String OS_JavaScript::get_name() const {
return "HTML5";
}
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index a9f9e23463..9635465c0d 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -35,6 +35,7 @@
#include "drivers/unix/os_unix.h"
#include "main/input_default.h"
#include "servers/audio_server.h"
+#include "servers/camera_server.h"
#include "servers/visual/rasterizer.h"
#include <emscripten/html5.h>
@@ -65,6 +66,8 @@ class OS_JavaScript : public OS_Unix {
int64_t sync_wait_time;
int64_t last_sync_check_time;
+ CameraServer *camera_server;
+
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);
@@ -133,11 +136,14 @@ public:
virtual int get_audio_driver_count() const;
virtual const char *get_audio_driver_name(int p_driver) const;
+ virtual void set_clipboard(const String &p_text);
+ virtual String get_clipboard() const;
+
virtual MainLoop *get_main_loop() const;
void run_async();
bool main_loop_iterate();
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false);
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
virtual Error kill(const ProcessID &p_pid);
virtual int get_process_id() const;
@@ -146,7 +152,7 @@ public:
virtual void set_icon(const Ref<Image> &p_icon);
String get_executable_path() const;
virtual Error shell_open(String p_uri);
- virtual String get_name();
+ virtual String get_name() const;
virtual bool can_draw() const;
virtual String get_resource_dir() const;
diff --git a/platform/osx/SCsub b/platform/osx/SCsub
index e15b4339a7..9620863b96 100644
--- a/platform/osx/SCsub
+++ b/platform/osx/SCsub
@@ -13,6 +13,7 @@ files = [
'dir_access_osx.mm',
'joypad_osx.cpp',
'power_osx.cpp',
+ 'camera_osx.mm',
]
prog = env.add_program('#bin/godot', files)
diff --git a/platform/osx/camera_osx.h b/platform/osx/camera_osx.h
new file mode 100644
index 0000000000..80ca3759ba
--- /dev/null
+++ b/platform/osx/camera_osx.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* camera_osx.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef CAMERAOSX_H
+#define CAMERAOSX_H
+
+///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimise code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "servers/camera_server.h"
+
+class CameraOSX : public CameraServer {
+public:
+ CameraOSX();
+ ~CameraOSX();
+
+ void update_feeds();
+};
+
+#endif /* CAMERAOSX_H */ \ No newline at end of file
diff --git a/platform/osx/camera_osx.mm b/platform/osx/camera_osx.mm
new file mode 100644
index 0000000000..f13cf76beb
--- /dev/null
+++ b/platform/osx/camera_osx.mm
@@ -0,0 +1,362 @@
+/*************************************************************************/
+/* camera_osx.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 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. */
+/*************************************************************************/
+
+///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimise code duplication!!!!
+// If you fix something here, make sure you fix it there as wel!
+
+#include "camera_osx.h"
+#include "servers/camera/camera_feed.h"
+#import <AVFoundation/AVFoundation.h>
+
+//////////////////////////////////////////////////////////////////////////
+// MyCaptureSession - This is a little helper class so we can capture our frames
+
+@interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
+ Ref<CameraFeed> feed;
+ size_t width[2];
+ size_t height[2];
+ PoolVector<uint8_t> img_data[2];
+
+ AVCaptureDeviceInput *input;
+ AVCaptureVideoDataOutput *output;
+}
+
+@end
+
+@implementation MyCaptureSession
+
+- (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
+ if (self = [super init]) {
+ NSError *error;
+ feed = p_feed;
+ width[0] = 0;
+ height[0] = 0;
+ width[1] = 0;
+ height[1] = 0;
+
+ [self beginConfiguration];
+
+ input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
+ if (!input) {
+ print_line("Couldn't get input device for camera");
+ } else {
+ [self addInput:input];
+ }
+
+ output = [AVCaptureVideoDataOutput new];
+ if (!output) {
+ print_line("Couldn't get output device for camera");
+ } else {
+ NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
+ output.videoSettings = settings;
+
+ // discard if the data output queue is blocked (as we process the still image)
+ [output setAlwaysDiscardsLateVideoFrames:YES];
+
+ // now set ourselves as the delegate to receive new frames.
+ [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
+
+ // this takes ownership
+ [self addOutput:output];
+ }
+
+ [self commitConfiguration];
+
+ // kick off our session..
+ [self startRunning];
+ };
+ return self;
+}
+
+- (void)cleanup {
+ // stop running
+ [self stopRunning];
+
+ // cleanup
+ [self beginConfiguration];
+
+ // remove input
+ if (input) {
+ [self removeInput:input];
+ // don't release this
+ input = NULL;
+ }
+
+ // free up our output
+ if (output) {
+ [self removeOutput:output];
+ [output setSampleBufferDelegate:nil queue:NULL];
+ [output release];
+ output = NULL;
+ }
+
+ [self commitConfiguration];
+}
+
+- (void)dealloc {
+ // bye bye
+ [super dealloc];
+}
+
+- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
+ // This gets called every time our camera has a new image for us to process.
+ // May need to investigate in a way to throttle this if we get more images then we're rendering frames..
+
+ // For now, version 1, we're just doing the bare minimum to make this work...
+ CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
+ // int _width = CVPixelBufferGetWidth(pixelBuffer);
+ // int _height = CVPixelBufferGetHeight(pixelBuffer);
+
+ // It says that we need to lock this on the documentation pages but it's not in the samples
+ // need to lock our base address so we can access our pixel buffers, better safe then sorry?
+ CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+
+ // get our buffers
+ unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
+ unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
+ if (dataY == NULL) {
+ print_line("Couldn't access Y pixel buffer data");
+ } else if (dataCbCr == NULL) {
+ print_line("Couldn't access CbCr pixel buffer data");
+ } else {
+ Ref<Image> img[2];
+
+ {
+ // do Y
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+
+ if ((width[0] != new_width) || (height[0] != new_height)) {
+ width[0] = new_width;
+ height[0] = new_height;
+ img_data[0].resize(new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[0].write();
+ memcpy(w.ptr(), dataY, new_width * new_height);
+
+ img[0].instance();
+ img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
+ }
+
+ {
+ // do CbCr
+ int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+
+ if ((width[1] != new_width) || (height[1] != new_height)) {
+ width[1] = new_width;
+ height[1] = new_height;
+ img_data[1].resize(2 * new_width * new_height);
+ }
+
+ PoolVector<uint8_t>::Write w = img_data[1].write();
+ memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+
+ ///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
+ img[1].instance();
+ img[1]->create(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
+ }
+
+ // set our texture...
+ feed->set_YCbCr_imgs(img[0], img[1]);
+ }
+
+ // and unlock
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
+}
+
+@end
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedOSX - Subclass for camera feeds in OSX
+
+class CameraFeedOSX : public CameraFeed {
+private:
+ AVCaptureDevice *device;
+ MyCaptureSession *capture_session;
+
+public:
+ AVCaptureDevice *get_device() const;
+
+ CameraFeedOSX();
+ ~CameraFeedOSX();
+
+ void set_device(AVCaptureDevice *p_device);
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+AVCaptureDevice *CameraFeedOSX::get_device() const {
+ return device;
+};
+
+CameraFeedOSX::CameraFeedOSX() {
+ device = NULL;
+ capture_session = NULL;
+};
+
+void CameraFeedOSX::set_device(AVCaptureDevice *p_device) {
+ device = p_device;
+ [device retain];
+
+ // get some info
+ NSString *device_name = p_device.localizedName;
+ name = device_name.UTF8String;
+ position = CameraFeed::FEED_UNSPECIFIED;
+ if ([p_device position] == AVCaptureDevicePositionBack) {
+ position = CameraFeed::FEED_BACK;
+ } else if ([p_device position] == AVCaptureDevicePositionFront) {
+ position = CameraFeed::FEED_FRONT;
+ };
+};
+
+CameraFeedOSX::~CameraFeedOSX() {
+ if (capture_session != NULL) {
+ [capture_session release];
+ capture_session = NULL;
+ };
+
+ if (device != NULL) {
+ [device release];
+ device = NULL;
+ };
+};
+
+bool CameraFeedOSX::activate_feed() {
+ if (capture_session) {
+ // already recording!
+ } else {
+ // start camera capture
+ capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
+ };
+
+ return true;
+};
+
+void CameraFeedOSX::deactivate_feed() {
+ // end camera capture if we have one
+ if (capture_session) {
+ [capture_session cleanup];
+ [capture_session release];
+ capture_session = NULL;
+ };
+};
+
+//////////////////////////////////////////////////////////////////////////
+// MyDeviceNotifications - This is a little helper class gets notifications
+// when devices are connected/disconnected
+
+@interface MyDeviceNotifications : NSObject {
+ CameraOSX *camera_server;
+}
+
+@end
+
+@implementation MyDeviceNotifications
+
+- (void)devices_changed:(NSNotification *)notification {
+ camera_server->update_feeds();
+}
+
+- (id)initForServer:(CameraOSX *)p_server {
+ if (self = [super init]) {
+ camera_server = p_server;
+
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+ };
+ return self;
+}
+
+- (void)dealloc {
+ // remove notifications
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
+
+ [super dealloc];
+}
+
+@end
+
+MyDeviceNotifications *device_notifications = nil;
+
+//////////////////////////////////////////////////////////////////////////
+// CameraOSX - Subclass for our camera server on OSX
+
+void CameraOSX::update_feeds() {
+ NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
+ Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i];
+
+ if (![devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
+ };
+ };
+
+ // add new devices..
+ for (AVCaptureDevice *device in devices) {
+ bool found = false;
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedOSX> feed = (Ref<CameraFeedOSX>)feeds[i];
+ if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedOSX> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+
+ // assume display camera so inverse
+ Transform2D transform = Transform2D(-1.0, 0.0, 0.0, -1.0, 1.0, 1.0);
+ newfeed->set_transform(transform);
+
+ add_feed(newfeed);
+ };
+ };
+};
+
+CameraOSX::CameraOSX() {
+ // Find available cameras we have at this time
+ update_feeds();
+
+ // should only have one of these....
+ device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
+};
+
+CameraOSX::~CameraOSX() {
+ [device_notifications release];
+};
diff --git a/platform/osx/crash_handler_osx.mm b/platform/osx/crash_handler_osx.mm
index ed8a955ae5..e19fdf1b9f 100644
--- a/platform/osx/crash_handler_osx.mm
+++ b/platform/osx/crash_handler_osx.mm
@@ -77,7 +77,12 @@ static void handle_crash(int sig) {
void *bt_buffer[256];
size_t size = backtrace(bt_buffer, 256);
String _execpath = OS::get_singleton()->get_executable_path();
- String msg = GLOBAL_GET("debug/settings/crash_handler/message");
+
+ String msg;
+ const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
+ if (proj_settings) {
+ msg = proj_settings->get("debug/settings/crash_handler/message");
+ }
// Dump the backtrace to stderr with a message to the user
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
diff --git a/platform/osx/detect.py b/platform/osx/detect.py
index 4c88f91d13..2175797dec 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -128,7 +128,7 @@ def configure(env):
env.Prepend(CPPPATH=['#platform/osx'])
env.Append(CPPFLAGS=['-DOSX_ENABLED', '-DUNIX_ENABLED', '-DGLES_ENABLED', '-DAPPLE_STYLE_KEYS', '-DCOREAUDIO_ENABLED', '-DCOREMIDI_ENABLED'])
- env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'CoreVideo'])
+ env.Append(LINKFLAGS=['-framework', 'Cocoa', '-framework', 'Carbon', '-framework', 'OpenGL', '-framework', 'AGL', '-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreMIDI', '-lz', '-framework', 'IOKit', '-framework', 'ForceFeedback', '-framework', 'AVFoundation', '-framework', 'CoreMedia', '-framework', 'CoreVideo'])
env.Append(LIBS=['pthread'])
env.Append(CCFLAGS=['-mmacosx-version-min=10.9'])
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index 89212b0e5e..9dabbb12fc 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -121,7 +121,7 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine"));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version"), "1.0"));
@@ -409,7 +409,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String src_pkg_name;
- EditorProgress ep("export", "Exporting for OSX", 3);
+ EditorProgress ep("export", "Exporting for OSX", 3, true);
if (p_debug)
src_pkg_name = p_preset->get("custom_package/debug");
@@ -432,7 +432,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
FileAccess *src_f = NULL;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- ep.step("Creating app", 0);
+ if (ep.step("Creating app", 0)) {
+ return ERR_SKIP;
+ }
unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io);
if (!src_pkg_zip) {
@@ -542,11 +544,21 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
iconpath = ProjectSettings::get_singleton()->get("application/config/icon");
if (iconpath != "") {
- Ref<Image> icon;
- icon.instance();
- icon->load(iconpath);
- if (!icon->empty()) {
- _make_icon(icon, data);
+ if (iconpath.get_extension() == "icns") {
+ FileAccess *icon = FileAccess::open(iconpath, FileAccess::READ);
+ if (icon) {
+ data.resize(icon->get_len());
+ icon->get_buffer(&data.write[0], icon->get_len());
+ icon->close();
+ memdelete(icon);
+ }
+ } else {
+ Ref<Image> icon;
+ icon.instance();
+ icon->load(iconpath);
+ if (!icon->empty()) {
+ _make_icon(icon, data);
+ }
}
}
//bleh?
@@ -616,7 +628,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (err == OK) {
- ep.step("Making PKG", 1);
+ if (ep.step("Making PKG", 1)) {
+ return ERR_SKIP;
+ }
if (export_format == "dmg") {
String pack_path = tmp_app_path_name + "/Contents/Resources/" + pkg_name + ".pck";
@@ -638,7 +652,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
if (err == OK && identity != "") {
- ep.step("Code signing bundle", 2);
+ if (ep.step("Code signing bundle", 2)) {
+ return ERR_SKIP;
+ }
// the order in which we code sign is important, this is a bit of a shame or we could do this in our loop that extracts the files from our ZIP
@@ -663,7 +679,9 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
// and finally create a DMG
if (err == OK) {
- ep.step("Making DMG", 3);
+ if (ep.step("Making DMG", 3)) {
+ return ERR_SKIP;
+ }
err = _create_dmg(p_path, pkg_name, tmp_app_path_name);
}
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index 125a88ab6d..1e996608af 100644
--- a/platform/osx/os_osx.h
+++ b/platform/osx/os_osx.h
@@ -31,6 +31,7 @@
#ifndef OS_OSX_H
#define OS_OSX_H
+#include "camera_osx.h"
#include "core/os/input.h"
#include "crash_handler_osx.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
@@ -60,6 +61,7 @@ public:
unsigned int osx_state;
bool pressed;
bool echo;
+ bool raw;
uint32_t scancode;
uint32_t unicode;
};
@@ -72,6 +74,8 @@ public:
//Rasterizer *rasterizer;
VisualServer *visual_server;
+ CameraServer *camera_server;
+
List<String> args;
MainLoop *main_loop;
@@ -133,6 +137,9 @@ public:
String im_text;
Point2 im_selection;
+ Size2 min_size;
+ Size2 max_size;
+
PowerOSX *power_manager;
CrashHandler crash_handler;
@@ -165,7 +172,7 @@ public:
void wm_minimized(bool p_minimized);
- virtual String get_name();
+ virtual String get_name() const;
virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
@@ -181,11 +188,13 @@ public:
virtual void warp_mouse_position(const Point2 &p_to);
virtual Point2 get_mouse_position() const;
virtual int get_mouse_button_state() const;
+ void update_real_mouse_position();
virtual void set_window_title(const String &p_title);
virtual Size2 get_window_size() const;
virtual Size2 get_real_window_size() const;
+ virtual void set_native_icon(const String &p_filename);
virtual void set_icon(const Ref<Image> &p_icon);
virtual MainLoop *get_main_loop() const;
@@ -230,6 +239,10 @@ public:
virtual Point2 get_window_position() const;
virtual void set_window_position(const Point2 &p_position);
+ virtual Size2 get_max_window_size() const;
+ virtual Size2 get_min_window_size() const;
+ virtual void set_min_window_size(const Size2 p_size);
+ virtual void set_max_window_size(const Size2 p_size);
virtual void set_window_size(const Size2 p_size);
virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;
diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm
index fec524c04b..4f84ae9c50 100644
--- a/platform/osx/os_osx.mm
+++ b/platform/osx/os_osx.mm
@@ -267,10 +267,23 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
- (void)windowDidEnterFullScreen:(NSNotification *)notification {
OS_OSX::singleton->zoomed = true;
+
+ [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(0, 0)];
+ [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
}
- (void)windowDidExitFullScreen:(NSNotification *)notification {
OS_OSX::singleton->zoomed = false;
+
+ if (OS_OSX::singleton->min_size != Size2()) {
+ Size2 size = OS_OSX::singleton->min_size / OS_OSX::singleton->_display_scale();
+ [OS_OSX::singleton->window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ }
+ if (OS_OSX::singleton->max_size != Size2()) {
+ Size2 size = OS_OSX::singleton->max_size / OS_OSX::singleton->_display_scale();
+ [OS_OSX::singleton->window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ }
+
if (!OS_OSX::singleton->resizable)
[OS_OSX::singleton->window_object setStyleMask:[OS_OSX::singleton->window_object styleMask] & ~NSWindowStyleMaskResizable];
}
@@ -392,7 +405,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
@interface GodotContentView : NSView <NSTextInputClient> {
NSTrackingArea *trackingArea;
NSMutableAttributedString *markedText;
- bool imeMode;
+ bool imeInputEventInProgress;
}
- (void)cancelComposition;
- (BOOL)wantsUpdateLayer;
@@ -418,7 +431,7 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
- (id)init {
self = [super init];
trackingArea = nil;
- imeMode = false;
+ imeInputEventInProgress = false;
[self updateTrackingAreas];
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
markedText = [[NSMutableAttributedString alloc] init];
@@ -452,7 +465,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[markedText initWithString:aString];
}
if (OS_OSX::singleton->im_active) {
- imeMode = true;
+ imeInputEventInProgress = true;
OS_OSX::singleton->im_text.parse_utf8([[markedText mutableString] UTF8String]);
OS_OSX::singleton->im_selection = Point2(selectedRange.location, selectedRange.length);
@@ -467,7 +480,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
}
- (void)unmarkText {
- imeMode = false;
+ imeInputEventInProgress = false;
[[markedText mutableString] setString:@""];
if (OS_OSX::singleton->im_active) {
OS_OSX::singleton->im_text = String();
@@ -540,6 +553,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
ke.osx_state = [event modifierFlags];
ke.pressed = true;
ke.echo = false;
+ ke.raw = false; // IME input event
ke.scancode = 0;
ke.unicode = codepoint;
@@ -1001,7 +1015,7 @@ static const _KeyCodeMap _keycodes[55] = {
{ '/', KEY_SLASH }
};
-static int remapKey(unsigned int key) {
+static int remapKey(unsigned int key, unsigned int state) {
if (isNumpadKey(key))
return translateKey(key);
@@ -1023,7 +1037,7 @@ static int remapKey(unsigned int key) {
OSStatus err = UCKeyTranslate(keyboardLayout,
key,
kUCKeyActionDisplay,
- 0,
+ (state >> 8) & 0xFF,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&keysDown,
@@ -1045,29 +1059,52 @@ static int remapKey(unsigned int key) {
- (void)keyDown:(NSEvent *)event {
- //disable raw input in IME mode
- if (!imeMode) {
- OS_OSX::KeyEvent ke;
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
- ke.osx_state = [event modifierFlags];
- ke.pressed = true;
- ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode]);
- ke.unicode = 0;
+ if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+ // Fallback unicode character handler used if IME is not active
+ for (NSUInteger i = 0; i < length; i++) {
+ OS_OSX::KeyEvent ke;
- push_to_key_event_buffer(ke);
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.raw = true;
+ ke.unicode = [characters characterAtIndex:i];
+
+ push_to_key_event_buffer(ke);
+ }
+ } else {
+ OS_OSX::KeyEvent ke;
+
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = true;
+ ke.echo = [event isARepeat];
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.raw = false;
+ ke.unicode = 0;
+
+ push_to_key_event_buffer(ke);
+ }
}
- if (OS_OSX::singleton->im_active == true)
+ // Pass events to IME handler
+ if (OS_OSX::singleton->im_active)
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
- (void)flagsChanged:(NSEvent *)event {
- if (!imeMode) {
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
OS_OSX::KeyEvent ke;
ke.echo = false;
+ ke.raw = true;
int key = [event keyCode];
int mod = [event modifierFlags];
@@ -1105,7 +1142,7 @@ static int remapKey(unsigned int key) {
}
ke.osx_state = mod;
- ke.scancode = remapKey(key);
+ ke.scancode = remapKey(key, mod);
ke.unicode = 0;
push_to_key_event_buffer(ke);
@@ -1114,17 +1151,37 @@ static int remapKey(unsigned int key) {
- (void)keyUp:(NSEvent *)event {
- if (!imeMode) {
+ // Ignore all input if IME input is in progress
+ if (!imeInputEventInProgress) {
+ NSString *characters = [event characters];
+ NSUInteger length = [characters length];
- OS_OSX::KeyEvent ke;
+ // Fallback unicode character handler used if IME is not active
+ if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode], [event modifierFlags]))) {
+ for (NSUInteger i = 0; i < length; i++) {
+ OS_OSX::KeyEvent ke;
- ke.osx_state = [event modifierFlags];
- ke.pressed = false;
- ke.echo = false;
- ke.scancode = remapKey([event keyCode]);
- ke.unicode = 0;
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.raw = true;
+ ke.unicode = [characters characterAtIndex:i];
- push_to_key_event_buffer(ke);
+ push_to_key_event_buffer(ke);
+ }
+ } else {
+ OS_OSX::KeyEvent ke;
+
+ ke.osx_state = [event modifierFlags];
+ ke.pressed = false;
+ ke.echo = [event isARepeat];
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
+ ke.raw = true;
+ ke.unicode = 0;
+
+ push_to_key_event_buffer(ke);
+ }
}
}
@@ -1498,6 +1555,8 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
visual_server->init();
AudioDriverManager::initialize(p_audio_driver);
+ camera_server = memnew(CameraOSX);
+
input = memnew(InputDefault);
joypad_osx = memnew(JoypadOSX);
@@ -1507,9 +1566,12 @@ Error OS_OSX::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
restore_rect = Rect2(get_window_position(), get_window_size());
- if (p_desired.layered_splash) {
+ if (p_desired.layered) {
set_window_per_pixel_transparency_enabled(true);
}
+
+ update_real_mouse_position();
+
return OK;
}
@@ -1529,6 +1591,11 @@ void OS_OSX::finalize() {
delete_main_loop();
+ if (camera_server) {
+ memdelete(camera_server);
+ camera_server = NULL;
+ }
+
memdelete(joypad_osx);
memdelete(input);
@@ -1551,7 +1618,7 @@ void OS_OSX::delete_main_loop() {
main_loop = NULL;
}
-String OS_OSX::get_name() {
+String OS_OSX::get_name() const {
return "OSX";
}
@@ -1843,6 +1910,12 @@ void OS_OSX::warp_mouse_position(const Point2 &p_to) {
}
}
+void OS_OSX::update_real_mouse_position() {
+
+ get_mouse_pos([window_object mouseLocationOutsideOfEventStream], [window_view backingScaleFactor]);
+ input->set_mouse_position(Point2(mouse_x, mouse_y));
+}
+
Point2 OS_OSX::get_mouse_position() const {
return Vector2(mouse_x, mouse_y);
@@ -1858,6 +1931,31 @@ void OS_OSX::set_window_title(const String &p_title) {
[window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
}
+void OS_OSX::set_native_icon(const String &p_filename) {
+
+ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND(!f);
+
+ Vector<uint8_t> data;
+ uint32_t len = f->get_len();
+ data.resize(len);
+ f->get_buffer((uint8_t *)&data.write[0], len);
+ memdelete(f);
+
+ NSData *icon_data = [[[NSData alloc] initWithBytes:&data.write[0] length:len] autorelease];
+ if (!icon_data) {
+ ERR_EXPLAIN("Error reading icon data");
+ ERR_FAIL();
+ }
+ NSImage *icon = [[[NSImage alloc] initWithData:icon_data] autorelease];
+ if (!icon) {
+ ERR_EXPLAIN("Error loading icon");
+ ERR_FAIL();
+ }
+
+ [NSApp setApplicationIconImage:icon];
+}
+
void OS_OSX::set_icon(const Ref<Image> &p_icon) {
Ref<Image> img = p_icon;
@@ -2268,6 +2366,8 @@ void OS_OSX::set_window_position(const Point2 &p_position) {
// Godot passes a positive value
position.y *= -1;
set_native_window_position(get_screens_origin() + position);
+
+ update_real_mouse_position();
};
Size2 OS_OSX::get_window_size() const {
@@ -2281,6 +2381,46 @@ Size2 OS_OSX::get_real_window_size() const {
return Size2(frame.size.width, frame.size.height) * _display_scale();
}
+Size2 OS_OSX::get_max_window_size() const {
+ return max_size;
+}
+
+Size2 OS_OSX::get_min_window_size() const {
+ return min_size;
+}
+
+void OS_OSX::set_min_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
+ WARN_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ min_size = p_size;
+
+ if ((min_size != Size2()) && !zoomed) {
+ Size2 size = min_size / _display_scale();
+ [window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ } else {
+ [window_object setContentMinSize:NSMakeSize(0, 0)];
+ }
+}
+
+void OS_OSX::set_max_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
+ WARN_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ max_size = p_size;
+
+ if ((max_size != Size2()) && !zoomed) {
+ Size2 size = max_size / _display_scale();
+ [window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ } else {
+ [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ }
+}
+
void OS_OSX::set_window_size(const Size2 p_size) {
Size2 size = p_size / _display_scale();
@@ -2310,6 +2450,19 @@ void OS_OSX::set_window_fullscreen(bool p_enabled) {
set_window_per_pixel_transparency_enabled(false);
if (!resizable)
[window_object setStyleMask:[window_object styleMask] | NSWindowStyleMaskResizable];
+ if (p_enabled) {
+ [window_object setContentMinSize:NSMakeSize(0, 0)];
+ [window_object setContentMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
+ } else {
+ if (min_size != Size2()) {
+ Size2 size = min_size / _display_scale();
+ [window_object setContentMinSize:NSMakeSize(size.x, size.y)];
+ }
+ if (max_size != Size2()) {
+ Size2 size = max_size / _display_scale();
+ [window_object setContentMaxSize:NSMakeSize(size.x, size.y)];
+ }
+ }
[window_object toggleFullScreen:nil];
}
zoomed = p_enabled;
@@ -2587,30 +2740,44 @@ void OS_OSX::process_key_events() {
const KeyEvent &ke = key_event_buffer[i];
- if ((i == 0 && ke.scancode == 0) || (i > 0 && key_event_buffer[i - 1].scancode == 0)) {
+ if (ke.raw) {
+ // Non IME input - no composite characters, pass events as is
k.instance();
get_key_modifier_state(ke.osx_state, k);
k->set_pressed(ke.pressed);
k->set_echo(ke.echo);
- k->set_scancode(0);
+ k->set_scancode(ke.scancode);
k->set_unicode(ke.unicode);
push_input(k);
- }
- if (ke.scancode != 0) {
- k.instance();
+ } else {
+ // IME input
+ if ((i == 0 && ke.scancode == 0) || (i > 0 && key_event_buffer[i - 1].scancode == 0)) {
+ k.instance();
- get_key_modifier_state(ke.osx_state, k);
- k->set_pressed(ke.pressed);
- k->set_echo(ke.echo);
- k->set_scancode(ke.scancode);
+ get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_scancode(0);
+ k->set_unicode(ke.unicode);
- if (i + 1 < key_event_pos && key_event_buffer[i + 1].scancode == 0) {
- k->set_unicode(key_event_buffer[i + 1].unicode);
+ push_input(k);
}
+ if (ke.scancode != 0) {
+ k.instance();
- push_input(k);
+ get_key_modifier_state(ke.osx_state, k);
+ k->set_pressed(ke.pressed);
+ k->set_echo(ke.echo);
+ k->set_scancode(ke.scancode);
+
+ if (i + 1 < key_event_pos && key_event_buffer[i + 1].scancode == 0) {
+ k->set_unicode(key_event_buffer[i + 1].unicode);
+ }
+
+ push_input(k);
+ }
}
}
diff --git a/platform/server/detect.py b/platform/server/detect.py
index 08c2eb6aaf..a5648d8d9d 100644
--- a/platform/server/detect.py
+++ b/platform/server/detect.py
@@ -145,12 +145,12 @@ def configure(env):
env.ParseConfig('pkg-config libpng --cflags --libs')
if not env['builtin_bullet']:
- # We need at least version 2.88
+ # We need at least version 2.89
import subprocess
bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip()
- if str(bullet_version) < "2.88":
+ if str(bullet_version) < "2.89":
# Abort as system bullet was requested but too old
- print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88"))
+ print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.89"))
sys.exit(255)
env.ParseConfig('pkg-config bullet --cflags --libs')
diff --git a/platform/server/os_server.cpp b/platform/server/os_server.cpp
index 53f2a65c8e..12e53054bc 100644
--- a/platform/server/os_server.cpp
+++ b/platform/server/os_server.cpp
@@ -190,7 +190,7 @@ bool OS_Server::can_draw() const {
return false; //can never draw
};
-String OS_Server::get_name() {
+String OS_Server::get_name() const {
return "Server";
}
diff --git a/platform/server/os_server.h b/platform/server/os_server.h
index 7441064790..e3488a693d 100644
--- a/platform/server/os_server.h
+++ b/platform/server/os_server.h
@@ -93,7 +93,7 @@ protected:
virtual void set_main_loop(MainLoop *p_main_loop);
public:
- virtual String get_name();
+ virtual String get_name() const;
virtual void set_mouse_show(bool p_show);
virtual void set_mouse_grab(bool p_grab);
diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index a0ab398f89..ec43a4c26f 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -187,7 +187,7 @@ class AppxPackager {
public:
void set_progress_task(String p_task) { progress_task = p_task; }
void init(FileAccess *p_fa);
- void add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false);
+ Error add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress = false);
void finish();
AppxPackager();
@@ -241,7 +241,6 @@ void AppxPackager::make_block_map() {
tmp_file->close();
memdelete(tmp_file);
- tmp_file = NULL;
}
String AppxPackager::content_type(String p_extension) {
@@ -291,7 +290,6 @@ void AppxPackager::make_content_types() {
tmp_file->close();
memdelete(tmp_file);
- tmp_file = NULL;
}
Vector<uint8_t> AppxPackager::make_file_header(FileMeta p_file_meta) {
@@ -468,10 +466,12 @@ void AppxPackager::init(FileAccess *p_fa) {
tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml");
}
-void AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) {
+Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t p_len, int p_file_no, int p_total_files, bool p_compress) {
if (p_file_no >= 1 && p_total_files >= 1) {
- EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files);
+ if (EditorNode::progress_task_step(progress_task, "File: " + p_file_name, (p_file_no * 100) / p_total_files)) {
+ return ERR_SKIP;
+ }
}
FileMeta meta;
@@ -584,6 +584,8 @@ void AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t
package->store_buffer(file_buffer.ptr(), file_buffer.size());
file_metadata.push_back(meta);
+
+ return OK;
}
void AppxPackager::finish() {
@@ -602,7 +604,6 @@ void AppxPackager::finish() {
blockmap_file->close();
memdelete(blockmap_file);
- blockmap_file = NULL;
// Add content types
EditorNode::progress_task_step("export", "Setting content types...", 5);
@@ -618,7 +619,6 @@ void AppxPackager::finish() {
types_file->close();
memdelete(types_file);
- types_file = NULL;
// Pre-process central directory before signing
for (int i = 0; i < file_metadata.size(); i++) {
@@ -1008,9 +1008,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
AppxPackager *packager = (AppxPackager *)p_userdata;
String dst_path = p_path.replace_first("res://", "game/");
- packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data));
-
- return OK;
+ return packager->add_file(dst_path, p_data.ptr(), p_data.size(), p_file, p_total, _should_compress_asset(p_path, p_data));
}
public:
@@ -1230,7 +1228,7 @@ public:
String src_appx;
- EditorProgress ep("export", "Exporting for Windows Universal", 7);
+ EditorProgress ep("export", "Exporting for Windows Universal", 7, true);
if (p_debug)
src_appx = p_preset->get("custom_template/debug");
@@ -1280,7 +1278,9 @@ public:
FileAccess *src_f = NULL;
zlib_filefunc_def io = zipio_create_io_from_file(&src_f);
- ep.step("Creating package...", 0);
+ if (ep.step("Creating package...", 0)) {
+ return ERR_SKIP;
+ }
unzFile pkg = unzOpen2(src_appx.utf8().get_data(), &io);
@@ -1292,7 +1292,9 @@ public:
int ret = unzGoToFirstFile(pkg);
- ep.step("Copying template files...", 1);
+ if (ep.step("Copying template files...", 1)) {
+ return ERR_SKIP;
+ }
EditorNode::progress_add_task("template_files", "Template files", 100);
packager.set_progress_task("template_files");
@@ -1341,14 +1343,19 @@ public:
print_line("ADDING: " + path);
- packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data));
+ err = packager.add_file(path, data.ptr(), data.size(), template_file_no++, template_files_amount, _should_compress_asset(path, data));
+ if (err != OK) {
+ return err;
+ }
ret = unzGoToNextFile(pkg);
}
EditorNode::progress_end_task("template_files");
- ep.step("Creating command line...", 2);
+ if (ep.step("Creating command line...", 2)) {
+ return ERR_SKIP;
+ }
Vector<String> cl = ((String)p_preset->get("command_line/extra_args")).strip_edges().split(" ");
for (int i = 0; i < cl.size(); i++) {
@@ -1382,9 +1389,14 @@ public:
print_line(itos(i) + " param: " + cl[i]);
}
- packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false);
+ err = packager.add_file("__cl__.cl", clf.ptr(), clf.size(), -1, -1, false);
+ if (err != OK) {
+ return err;
+ }
- ep.step("Adding project files...", 3);
+ if (ep.step("Adding project files...", 3)) {
+ return ERR_SKIP;
+ }
EditorNode::progress_add_task("project_files", "Project Files", 100);
packager.set_progress_task("project_files");
@@ -1393,7 +1405,9 @@ public:
EditorNode::progress_end_task("project_files");
- ep.step("Closing package...", 7);
+ if (ep.step("Closing package...", 7)) {
+ return ERR_SKIP;
+ }
unzClose(pkg);
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index 82f09032f5..9d9be44ce5 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -302,6 +302,10 @@ Error OS_UWP::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
}
visual_server->init();
+
+ ///@TODO implement a subclass for UWP and instantiate that instead
+ camera_server = memnew(CameraServer);
+
input = memnew(InputDefault);
joypad = ref new JoypadUWP(input);
@@ -400,6 +404,8 @@ void OS_UWP::finalize() {
memdelete(input);
+ memdelete(camera_server);
+
joypad = nullptr;
}
@@ -530,7 +536,7 @@ OS::VideoMode OS_UWP::get_video_mode(int p_screen) const {
void OS_UWP::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
}
-String OS_UWP::get_name() {
+String OS_UWP::get_name() const {
return "UWP";
}
@@ -713,7 +719,7 @@ void OS_UWP::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, c
// TODO
}
-Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr) {
+Error OS_UWP::execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex) {
return FAILED;
};
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 00f79efb04..b7a7248f19 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -41,6 +41,7 @@
#include "main/input_default.h"
#include "power_uwp.h"
#include "servers/audio_server.h"
+#include "servers/camera_server.h"
#include "servers/visual/rasterizer.h"
#include "servers/visual_server.h"
@@ -95,6 +96,8 @@ private:
VisualServer *visual_server;
int pressrc;
+ CameraServer *camera_server;
+
ContextEGL_UWP *gl_context;
Windows::UI::Core::CoreWindow ^ window;
@@ -195,7 +198,7 @@ public:
virtual MainLoop *get_main_loop() const;
- virtual String get_name();
+ virtual String get_name() const;
virtual Date get_date(bool utc) const;
virtual Time get_time(bool utc) const;
@@ -208,7 +211,7 @@ public:
virtual void delay_usec(uint32_t p_usec) const;
virtual uint64_t get_ticks_usec() const;
- virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false);
+ virtual Error execute(const String &p_path, const List<String> &p_arguments, bool p_blocking, ProcessID *r_child_id = NULL, String *r_pipe = NULL, int *r_exitcode = NULL, bool read_stderr = false, Mutex *p_pipe_mutex = NULL);
virtual Error kill(const ProcessID &p_pid);
virtual bool has_environment(const String &p_var) const;
diff --git a/platform/windows/SCsub b/platform/windows/SCsub
index 892d734734..8426ccbb89 100644
--- a/platform/windows/SCsub
+++ b/platform/windows/SCsub
@@ -8,6 +8,7 @@ import platform_windows_builders
common_win = [
"godot_windows.cpp",
+ "camera_win.cpp",
"context_gl_windows.cpp",
"crash_handler_windows.cpp",
"os_windows.cpp",
diff --git a/platform/windows/camera_win.cpp b/platform/windows/camera_win.cpp
new file mode 100644
index 0000000000..b97796fe89
--- /dev/null
+++ b/platform/windows/camera_win.cpp
@@ -0,0 +1,94 @@
+/*************************************************************************/
+/* camera_win.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "camera_win.h"
+
+///@TODO sorry guys, I got about 80% through implementing this using DirectShow only to find out Microsoft deprecated half the API and its replacement is as confusing as they could make it
+// Joey suggested looking into libuvc which offers a more direct route to webcams over USB and this is very promissing but it wouldn't compile on windows for me...
+// I've gutted the classes I implemented DirectShow in just to have a skeleton for someone to work on, mail me for more details or if you want a copy....
+
+//////////////////////////////////////////////////////////////////////////
+// CameraFeedWindows - Subclass for our camera feed on windows
+
+/// @TODO need to implement this
+
+class CameraFeedWindows : public CameraFeed {
+private:
+protected:
+public:
+ CameraFeedWindows();
+ virtual ~CameraFeedWindows();
+
+ bool activate_feed();
+ void deactivate_feed();
+};
+
+CameraFeedWindows::CameraFeedWindows(){
+ ///@TODO implement this, should store information about our available camera
+};
+
+CameraFeedWindows::~CameraFeedWindows() {
+ // make sure we stop recording if we are!
+ if (is_active()) {
+ deactivate_feed();
+ };
+
+ ///@TODO free up anything used by this
+};
+
+bool CameraFeedWindows::activate_feed() {
+ ///@TODO this should activate our camera and start the process of capturing frames
+
+ return true;
+};
+
+///@TODO we should probably have a callback method here that is being called by the camera API which provides frames and call back into the CameraServer to update our texture
+
+void CameraFeedWindows::deactivate_feed(){
+ ///@TODO this should deactivate our camera and stop the process of capturing frames
+};
+
+//////////////////////////////////////////////////////////////////////////
+// CameraWindows - Subclass for our camera server on windows
+
+void CameraWindows::add_active_cameras(){
+ ///@TODO scan through any active cameras and create CameraFeedWindows objects for them
+};
+
+CameraWindows::CameraWindows() {
+ // Find cameras active right now
+ add_active_cameras();
+
+ // need to add something that will react to devices being connected/removed...
+};
+
+CameraWindows::~CameraWindows(){
+
+};
diff --git a/platform/windows/camera_win.h b/platform/windows/camera_win.h
new file mode 100644
index 0000000000..22ce9aa43f
--- /dev/null
+++ b/platform/windows/camera_win.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* camera_win.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef CAMERAWIN_H
+#define CAMERAWIN_H
+
+#include "servers/camera/camera_feed.h"
+#include "servers/camera_server.h"
+
+class CameraWindows : public CameraServer {
+private:
+ void add_active_cameras();
+
+public:
+ CameraWindows();
+ ~CameraWindows();
+};
+
+#endif /* CAMERAWIN_H */
diff --git a/platform/windows/crash_handler_windows.cpp b/platform/windows/crash_handler_windows.cpp
index 4006c4c60e..0716ee67f4 100644
--- a/platform/windows/crash_handler_windows.cpp
+++ b/platform/windows/crash_handler_windows.cpp
@@ -166,11 +166,16 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
line.SizeOfStruct = sizeof(line);
IMAGE_NT_HEADERS *h = ImageNtHeader(base);
DWORD image_type = h->FileHeader.Machine;
- int n = 0;
- String msg = GLOBAL_GET("debug/settings/crash_handler/message");
+
+ String msg;
+ const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
+ if (proj_settings) {
+ msg = proj_settings->get("debug/settings/crash_handler/message");
+ }
fprintf(stderr, "Dumping the backtrace. %ls\n", msg.c_str());
+ int n = 0;
do {
if (skip_first) {
skip_first = false;
diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp
index 141ab96370..4a72d07adc 100644
--- a/platform/windows/export/export.cpp
+++ b/platform/windows/export/export.cpp
@@ -138,8 +138,8 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
EditorExportPlatformPC::get_export_options(r_options);
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));
diff --git a/platform/windows/godot_res.rc b/platform/windows/godot_res.rc
index f2dca10d55..1fa8957f15 100644
--- a/platform/windows/godot_res.rc
+++ b/platform/windows/godot_res.rc
@@ -21,7 +21,7 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Godot Engine"
- VALUE "FileDescription", VERSION_NAME " Editor"
+ VALUE "FileDescription", VERSION_NAME
VALUE "FileVersion", VERSION_NUMBER
VALUE "ProductName", VERSION_NAME
VALUE "Licence", "MIT"
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 5a399cdf90..53ce342e8c 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -103,17 +103,17 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
PRAWINPUTDEVICELIST dev_list = NULL;
unsigned int dev_list_count = 0;
- if (GetRawInputDeviceList(NULL, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
+ if (GetRawInputDeviceList(NULL, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
return false;
}
dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count);
if (!dev_list) return false;
- if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
+ if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
free(dev_list);
return false;
}
- for (int i = 0; i < dev_list_count; i++) {
+ for (unsigned int i = 0; i < dev_list_count; i++) {
RID_DEVICE_INFO rdi;
char dev_name[128];
@@ -334,9 +334,9 @@ void JoypadWindows::process_joypads() {
if (joy.state.dwPacketNumber != joy.last_packet) {
int button_mask = XINPUT_GAMEPAD_DPAD_UP;
- for (int i = 0; i <= 16; i++) {
+ for (int j = 0; j <= 16; j++) {
- input->joy_button(joy.id, i, joy.state.Gamepad.wButtons & button_mask);
+ input->joy_button(joy.id, j, joy.state.Gamepad.wButtons & button_mask);
button_mask = button_mask * 2;
}
@@ -406,7 +406,7 @@ void JoypadWindows::process_joypads() {
// on mingw, these constants are not constants
int count = 6;
- int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ };
+ unsigned int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ };
int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz };
for (int j = 0; j < joy->joy_axis.size(); j++) {
@@ -426,7 +426,11 @@ void JoypadWindows::post_hat(int p_device, DWORD p_dpad) {
int dpad_val = 0;
- if (p_dpad == -1) {
+ // Should be -1 when centered, but according to docs:
+ // "Some drivers report the centered position of the POV indicator as 65,535. Determine whether the indicator is centered as follows:
+ // BOOL POVCentered = (LOWORD(dwPOV) == 0xFFFF);"
+ // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee416628(v%3Dvs.85)#remarks
+ if (LOWORD(p_dpad) == 0xFFFF) {
dpad_val = InputDefault::HAT_MASK_CENTER;
}
if (p_dpad == 0) {
diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp
index 0b61770d87..6e6df08f02 100644
--- a/platform/windows/os_windows.cpp
+++ b/platform/windows/os_windows.cpp
@@ -94,6 +94,7 @@ static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPR
return TRUE;
}
+#ifdef DEBUG_ENABLED
static String format_error_message(DWORD id) {
LPWSTR messageBuffer = NULL;
@@ -106,6 +107,7 @@ static String format_error_message(DWORD id) {
return msg;
}
+#endif // DEBUG_ENABLED
extern HINSTANCE godot_hinstance;
@@ -353,7 +355,23 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
return 0; // Return To The Message Loop
}
-
+ case WM_GETMINMAXINFO: {
+ if (video_mode.resizable && !video_mode.fullscreen) {
+ Size2 decor = get_real_window_size() - get_window_size(); // Size of window decorations
+ MINMAXINFO *min_max_info = (MINMAXINFO *)lParam;
+ if (min_size != Size2()) {
+ min_max_info->ptMinTrackSize.x = min_size.x + decor.x;
+ min_max_info->ptMinTrackSize.y = min_size.y + decor.y;
+ }
+ if (max_size != Size2()) {
+ min_max_info->ptMaxTrackSize.x = max_size.x + decor.x;
+ min_max_info->ptMaxTrackSize.y = max_size.y + decor.y;
+ }
+ return 0;
+ } else {
+ break;
+ }
+ }
case WM_PAINT:
Main::force_redraw();
@@ -555,6 +573,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
break;
}
}
+ FALLTHROUGH;
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
@@ -583,7 +602,6 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
case WM_MBUTTONDOWN: {
mb->set_pressed(true);
mb->set_button_index(3);
-
} break;
case WM_MBUTTONUP: {
mb->set_pressed(false);
@@ -598,19 +616,16 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
mb->set_button_index(2);
} break;
case WM_LBUTTONDBLCLK: {
-
mb->set_pressed(true);
mb->set_button_index(1);
mb->set_doubleclick(true);
} break;
case WM_RBUTTONDBLCLK: {
-
mb->set_pressed(true);
mb->set_button_index(2);
mb->set_doubleclick(true);
} break;
case WM_MBUTTONDBLCLK: {
-
mb->set_pressed(true);
mb->set_button_index(3);
mb->set_doubleclick(true);
@@ -705,7 +720,7 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
}
}
} else if (mouse_mode != MOUSE_MODE_CAPTURED) {
- // for reasons unknown to mankind, wheel comes in screen cordinates
+ // for reasons unknown to mankind, wheel comes in screen coordinates
POINT coords;
coords.x = mb->get_position().x;
coords.y = mb->get_position().y;
@@ -1358,6 +1373,8 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
power_manager = memnew(PowerWindows);
+ camera_server = memnew(CameraWindows);
+
AudioDriverManager::initialize(p_audio_driver);
TRACKMOUSEEVENT tme;
@@ -1381,7 +1398,7 @@ Error OS_Windows::initialize(const VideoMode &p_desired, int p_video_driver, int
SetFocus(hWnd); // Sets Keyboard Focus To
}
- if (p_desired.layered_splash) {
+ if (p_desired.layered) {
set_window_per_pixel_transparency_enabled(true);
}
@@ -1517,6 +1534,7 @@ void OS_Windows::finalize() {
memdelete(joypad);
memdelete(input);
+ memdelete(camera_server);
touch_state.clear();
visual_server->finish();
@@ -1769,6 +1787,7 @@ void OS_Windows::set_window_position(const Point2 &p_position) {
last_pos = p_position;
update_real_mouse_position();
}
+
Size2 OS_Windows::get_window_size() const {
if (minimized) {
@@ -1781,6 +1800,33 @@ Size2 OS_Windows::get_window_size() const {
}
return Size2();
}
+
+Size2 OS_Windows::get_max_window_size() const {
+ return max_size;
+}
+
+Size2 OS_Windows::get_min_window_size() const {
+ return min_size;
+}
+
+void OS_Windows::set_min_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
+ WARN_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ min_size = p_size;
+}
+
+void OS_Windows::set_max_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
+ WARN_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ max_size = p_size;
+}
+
Size2 OS_Windows::get_real_window_size() const {
RECT r;
@@ -1789,6 +1835,7 @@ Size2 OS_Windows::get_real_window_size() const {
}
return Size2();
}
+
void OS_Windows::set_window_size(const Size2 p_size) {
int w = p_size.width;
@@ -1816,11 +1863,11 @@ void OS_Windows::set_window_size(const Size2 p_size) {
// Don't let the mouse leave the window when resizing to a smaller resolution
if (mouse_mode == MOUSE_MODE_CONFINED) {
- RECT rect;
- GetClientRect(hWnd, &rect);
- ClientToScreen(hWnd, (POINT *)&rect.left);
- ClientToScreen(hWnd, (POINT *)&rect.right);
- ClipCursor(&rect);
+ RECT crect;
+ GetClientRect(hWnd, &crect);
+ ClientToScreen(hWnd, (POINT *)&crect.left);
+ ClientToScreen(hWnd, (POINT *)&crect.right);
+ ClipCursor(&crect);
}
}
void OS_Windows::set_window_fullscreen(bool p_enabled) {
@@ -2119,7 +2166,7 @@ void OS_Windows::request_attention() {
FlashWindowEx(&info);
}
-String OS_Windows::get_name() {
+String OS_Windows::get_name() const {
return "Windows";
}
@@ -2193,6 +2240,8 @@ uint64_t OS_Windows::get_unix_time() const {
FILETIME fep;
SystemTimeToFileTime(&ep, &fep);
+ // FIXME: dereferencing type-punned pointer will break strict-aliasing rules (GCC warning)
+ // https://docs.microsoft.com/en-us/windows/desktop/api/minwinbase/ns-minwinbase-filetime#remarks
return (*(uint64_t *)&ft - *(uint64_t *)&fep) / 10000000;
};
@@ -2378,7 +2427,7 @@ void OS_Windows::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shap
}
// Finally, create the icon
- ICONINFO iconinfo = { 0 };
+ ICONINFO iconinfo;
iconinfo.fIcon = FALSE;
iconinfo.xHotspot = p_hotspot.x;
iconinfo.yHotspot = p_hotspot.y;
@@ -2475,7 +2524,13 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
for (const List<String>::Element *E = p_arguments.front(); E; E = E->next()) {
- argss += String(" \"") + E->get() + "\"";
+ argss += " \"" + E->get() + "\"";
+ }
+
+ argss += "\"";
+
+ if (read_stderr) {
+ argss += " 2>&1"; // Read stderr too
}
FILE *f = _wpopen(argss.c_str(), L"r");
@@ -2525,9 +2580,9 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
if (p_blocking) {
- DWORD ret = WaitForSingleObject(pi.pi.hProcess, INFINITE);
+ DWORD ret2 = WaitForSingleObject(pi.pi.hProcess, INFINITE);
if (r_exitcode)
- *r_exitcode = ret;
+ *r_exitcode = ret2;
CloseHandle(pi.pi.hProcess);
CloseHandle(pi.pi.hThread);
@@ -2577,6 +2632,117 @@ String OS_Windows::get_executable_path() const {
return s;
}
+void OS_Windows::set_native_icon(const String &p_filename) {
+
+ FileAccess *f = FileAccess::open(p_filename, FileAccess::READ);
+ ERR_FAIL_COND(!f);
+
+ ICONDIR *icon_dir = (ICONDIR *)memalloc(sizeof(ICONDIR));
+ int pos = 0;
+
+ icon_dir->idReserved = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ icon_dir->idType = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ if (icon_dir->idType != 1) {
+ ERR_EXPLAIN("Invalid icon file format!");
+ ERR_FAIL();
+ }
+
+ icon_dir->idCount = f->get_32();
+ pos += sizeof(WORD);
+ f->seek(pos);
+
+ icon_dir = (ICONDIR *)memrealloc(icon_dir, 3 * sizeof(WORD) + icon_dir->idCount * sizeof(ICONDIRENTRY));
+ f->get_buffer((uint8_t *)&icon_dir->idEntries[0], icon_dir->idCount * sizeof(ICONDIRENTRY));
+
+ int small_icon_index = -1; // Select 16x16 with largest color count
+ int small_icon_cc = 0;
+ int big_icon_index = -1; // Select largest
+ int big_icon_width = 16;
+ int big_icon_cc = 0;
+
+ for (int i = 0; i < icon_dir->idCount; i++) {
+ int colors = (icon_dir->idEntries[i].bColorCount == 0) ? 32768 : icon_dir->idEntries[i].bColorCount;
+ int width = (icon_dir->idEntries[i].bWidth == 0) ? 256 : icon_dir->idEntries[i].bWidth;
+ if (width == 16) {
+ if (colors >= small_icon_cc) {
+ small_icon_index = i;
+ small_icon_cc = colors;
+ }
+ }
+ if (width >= big_icon_width) {
+ if (colors >= big_icon_cc) {
+ big_icon_index = i;
+ big_icon_width = width;
+ big_icon_cc = colors;
+ }
+ }
+ }
+
+ if (big_icon_index == -1) {
+ ERR_EXPLAIN("No valid icons found!");
+ ERR_FAIL();
+ }
+
+ if (small_icon_index == -1) {
+ WARN_PRINTS("No small icon found, reusing " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon!");
+ small_icon_index = big_icon_index;
+ small_icon_cc = big_icon_cc;
+ }
+
+ // Read the big icon
+ DWORD bytecount_big = icon_dir->idEntries[big_icon_index].dwBytesInRes;
+ Vector<uint8_t> data_big;
+ data_big.resize(bytecount_big);
+ pos = icon_dir->idEntries[big_icon_index].dwImageOffset;
+ f->seek(pos);
+ f->get_buffer((uint8_t *)&data_big.write[0], bytecount_big);
+ HICON icon_big = CreateIconFromResource((PBYTE)&data_big.write[0], bytecount_big, TRUE, 0x00030000);
+ if (!icon_big) {
+ ERR_EXPLAIN("Could not create " + itos(big_icon_width) + "x" + itos(big_icon_width) + " @" + itos(big_icon_cc) + " icon, error: " + format_error_message(GetLastError()));
+ ERR_FAIL();
+ }
+
+ // Read the small icon
+ DWORD bytecount_small = icon_dir->idEntries[small_icon_index].dwBytesInRes;
+ Vector<uint8_t> data_small;
+ data_small.resize(bytecount_small);
+ pos = icon_dir->idEntries[small_icon_index].dwImageOffset;
+ f->seek(pos);
+ f->get_buffer((uint8_t *)&data_small.write[0], bytecount_small);
+ HICON icon_small = CreateIconFromResource((PBYTE)&data_small.write[0], bytecount_small, TRUE, 0x00030000);
+ if (!icon_small) {
+ ERR_EXPLAIN("Could not create 16x16 @" + itos(small_icon_cc) + " icon, error: " + format_error_message(GetLastError()));
+ ERR_FAIL();
+ }
+
+ // Online tradition says to be sure last error is cleared and set the small icon first
+ int err = 0;
+ SetLastError(err);
+
+ SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)icon_small);
+ err = GetLastError();
+ if (err) {
+ ERR_EXPLAIN("Error setting ICON_SMALL: " + format_error_message(err));
+ ERR_FAIL();
+ }
+
+ SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)icon_big);
+ err = GetLastError();
+ if (err) {
+ ERR_EXPLAIN("Error setting ICON_BIG: " + format_error_message(err));
+ ERR_FAIL();
+ }
+
+ memdelete(f);
+ memdelete(icon_dir);
+}
+
void OS_Windows::set_icon(const Ref<Image> &p_icon) {
ERR_FAIL_COND(!p_icon.is_valid());
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 0e0b9bf3f6..4660c16b97 100644
--- a/platform/windows/os_windows.h
+++ b/platform/windows/os_windows.h
@@ -31,6 +31,7 @@
#ifndef OS_WINDOWS_H
#define OS_WINDOWS_H
+#include "camera_win.h"
#include "context_gl_windows.h"
#include "core/os/input.h"
#include "core/os/os.h"
@@ -58,6 +59,25 @@
/**
@author Juan Linietsky <reduzio@gmail.com>
*/
+
+typedef struct {
+ BYTE bWidth; // Width, in pixels, of the image
+ BYTE bHeight; // Height, in pixels, of the image
+ BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
+ BYTE bReserved; // Reserved ( must be 0)
+ WORD wPlanes; // Color Planes
+ WORD wBitCount; // Bits per pixel
+ DWORD dwBytesInRes; // How many bytes in this resource?
+ DWORD dwImageOffset; // Where in the file is this image?
+} ICONDIRENTRY, *LPICONDIRENTRY;
+
+typedef struct {
+ WORD idReserved; // Reserved (must be 0)
+ WORD idType; // Resource Type (1 for icons)
+ WORD idCount; // How many images?
+ ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
+} ICONDIR, *LPICONDIR;
+
class JoypadWindows;
class OS_Windows : public OS {
@@ -89,6 +109,7 @@ class OS_Windows : public OS {
ContextGL_Windows *gl_context;
#endif
VisualServer *visual_server;
+ CameraWindows *camera_server;
int pressrc;
HDC hDC; // Private GDI Device Context
HINSTANCE hInstance; // Holds The Instance Of The Application
@@ -105,6 +126,9 @@ class OS_Windows : public OS {
HCURSOR hCursor;
+ Size2 min_size;
+ Size2 max_size;
+
Size2 window_rect;
VideoMode video_mode;
bool preserve_window_size = false;
@@ -217,6 +241,10 @@ public:
virtual void set_window_position(const Point2 &p_position);
virtual Size2 get_window_size() const;
virtual Size2 get_real_window_size() const;
+ virtual Size2 get_max_window_size() const;
+ virtual Size2 get_min_window_size() const;
+ virtual void set_min_window_size(const Size2 p_size);
+ virtual void set_max_window_size(const Size2 p_size);
virtual void set_window_size(const Size2 p_size);
virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;
@@ -246,7 +274,7 @@ public:
virtual MainLoop *get_main_loop() const;
- virtual String get_name();
+ virtual String get_name() const;
virtual Date get_date(bool utc) const;
virtual Time get_time(bool utc) const;
@@ -276,6 +304,8 @@ public:
CursorShape get_cursor_shape() const;
virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
void GetMaskBitmaps(HBITMAP hSourceBitmap, COLORREF clrTransparent, OUT HBITMAP &hAndMaskBitmap, OUT HBITMAP &hXorMaskBitmap);
+
+ void set_native_icon(const String &p_filename);
void set_icon(const Ref<Image> &p_icon);
virtual String get_executable_path() const;
diff --git a/platform/windows/power_windows.cpp b/platform/windows/power_windows.cpp
index b96ae51132..0efd88c216 100644
--- a/platform/windows/power_windows.cpp
+++ b/platform/windows/power_windows.cpp
@@ -89,7 +89,7 @@ bool PowerWindows::GetPowerInfo_Windows() {
if (pct != 255) { /* 255 == unknown */
percent_left = (pct > 100) ? 100 : pct; /* clamp between 0%, 100% */
}
- if (secs != 0xFFFFFFFF) { /* ((DWORD)-1) == unknown */
+ if (secs != (int)0xFFFFFFFF) { /* ((DWORD)-1) == unknown */
nsecs_left = secs;
}
}
diff --git a/platform/windows/windows_terminal_logger.cpp b/platform/windows/windows_terminal_logger.cpp
index 7def419103..adbdafb07e 100644
--- a/platform/windows/windows_terminal_logger.cpp
+++ b/platform/windows/windows_terminal_logger.cpp
@@ -45,7 +45,7 @@ void WindowsTerminalLogger::logv(const char *p_format, va_list p_list, bool p_er
int len = vsnprintf(buf, BUFFER_SIZE, p_format, p_list);
if (len <= 0)
return;
- if (len >= BUFFER_SIZE)
+ if ((unsigned int)len >= BUFFER_SIZE)
len = BUFFER_SIZE; // Output is too big, will be truncated
buf[len] = 0;
@@ -154,4 +154,4 @@ void WindowsTerminalLogger::log_error(const char *p_function, const char *p_file
WindowsTerminalLogger::~WindowsTerminalLogger() {}
-#endif \ No newline at end of file
+#endif
diff --git a/platform/x11/context_gl_x11.cpp b/platform/x11/context_gl_x11.cpp
index aadf7ee36d..9718b03164 100644
--- a/platform/x11/context_gl_x11.cpp
+++ b/platform/x11/context_gl_x11.cpp
@@ -191,6 +191,7 @@ Error ContextGL_X11::initialize() {
swa.colormap = XCreateColormap(x11_display, RootWindow(x11_display, vi->screen), vi->visual, AllocNone);
x11_window = XCreateWindow(x11_display, RootWindow(x11_display, vi->screen), 0, 0, OS::get_singleton()->get_video_mode().width, OS::get_singleton()->get_video_mode().height, 0, vi->depth, InputOutput, vi->visual, valuemask, &swa);
+ XStoreName(x11_display, x11_window, "Godot Engine");
ERR_FAIL_COND_V(!x11_window, ERR_UNCONFIGURED);
set_class_hint(x11_display, x11_window);
diff --git a/platform/x11/crash_handler_x11.cpp b/platform/x11/crash_handler_x11.cpp
index 44d3cf1910..ca7251078f 100644
--- a/platform/x11/crash_handler_x11.cpp
+++ b/platform/x11/crash_handler_x11.cpp
@@ -53,7 +53,12 @@ static void handle_crash(int sig) {
void *bt_buffer[256];
size_t size = backtrace(bt_buffer, 256);
String _execpath = OS::get_singleton()->get_executable_path();
- String msg = GLOBAL_GET("debug/settings/crash_handler/message");
+
+ String msg;
+ const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
+ if (proj_settings) {
+ msg = proj_settings->get("debug/settings/crash_handler/message");
+ }
// Dump the backtrace to stderr with a message to the user
fprintf(stderr, "%s: Program crashed with signal %d\n", __FUNCTION__, sig);
diff --git a/platform/x11/detect.py b/platform/x11/detect.py
index 933ee6b72e..a502308eee 100644
--- a/platform/x11/detect.py
+++ b/platform/x11/detect.py
@@ -219,12 +219,12 @@ def configure(env):
env.ParseConfig('pkg-config libpng --cflags --libs')
if not env['builtin_bullet']:
- # We need at least version 2.88
+ # We need at least version 2.89
import subprocess
bullet_version = subprocess.check_output(['pkg-config', 'bullet', '--modversion']).strip()
- if str(bullet_version) < "2.88":
+ if str(bullet_version) < "2.89":
# Abort as system bullet was requested but too old
- print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.88"))
+ print("Bullet: System version {0} does not match minimal requirements ({1}). Aborting.".format(bullet_version, "2.89"))
sys.exit(255)
env.ParseConfig('pkg-config bullet --cflags --libs')
diff --git a/platform/x11/godot_x11.cpp b/platform/x11/godot_x11.cpp
index 79407cd9dc..9baa4d6dca 100644
--- a/platform/x11/godot_x11.cpp
+++ b/platform/x11/godot_x11.cpp
@@ -55,8 +55,11 @@ int main(int argc, char *argv[]) {
os.run(); // it is actually the OS that decides how to run
Main::cleanup();
- if (ret)
- chdir(cwd);
+ if (ret) { // Previous getcwd was successful
+ if (chdir(cwd) != 0) {
+ ERR_PRINT("Couldn't return to previous working directory.");
+ }
+ }
free(cwd);
return os.get_exit_code();
diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp
index c4dd8fe0e0..3e9e8033e8 100644
--- a/platform/x11/joypad_linux.cpp
+++ b/platform/x11/joypad_linux.cpp
@@ -444,10 +444,10 @@ InputDefault::JoyAxis JoypadLinux::axis_correct(const input_absinfo *p_abs, int
jx.min = -1;
if (p_value < 0) {
jx.value = (float)-p_value / min;
+ } else {
+ jx.value = (float)p_value / max;
}
- jx.value = (float)p_value / max;
- }
- if (min == 0) {
+ } else if (min == 0) {
jx.min = 0;
jx.value = 0.0f + (float)p_value / max;
}
diff --git a/platform/x11/os_x11.cpp b/platform/x11/os_x11.cpp
index d5ca3742db..6421dc270f 100644
--- a/platform/x11/os_x11.cpp
+++ b/platform/x11/os_x11.cpp
@@ -534,22 +534,26 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
}
{
- Pixmap cursormask;
- XGCValues xgc;
- GC gc;
- XColor col;
- Cursor cursor;
+ // Creating an empty/transparent cursor
+
+ // Create 1x1 bitmap
+ Pixmap cursormask = XCreatePixmap(x11_display,
+ RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
- cursormask = XCreatePixmap(x11_display, RootWindow(x11_display, DefaultScreen(x11_display)), 1, 1, 1);
+ // Fill with zero
+ XGCValues xgc;
xgc.function = GXclear;
- gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
+ GC gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
- col.pixel = 0;
- col.red = 0;
- col.flags = 4;
- cursor = XCreatePixmapCursor(x11_display,
- cursormask, cursormask,
+
+ // Color value doesn't matter. Mask zero means no foreground or background will be drawn
+ XColor col = {};
+
+ Cursor cursor = XCreatePixmapCursor(x11_display,
+ cursormask, // source (using cursor mask as placeholder, since it'll all be ignored)
+ cursormask, // mask
&col, &col, 0, 0);
+
XFreePixmap(x11_display, cursormask);
XFreeGC(x11_display, gc);
@@ -579,6 +583,9 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
AudioDriverManager::initialize(p_audio_driver);
+ ///@TODO implement a subclass for Linux and instantiate that instead
+ camera_server = memnew(CameraServer);
+
input = memnew(InputDefault);
window_has_focus = true; // Set focus to true at init
@@ -589,7 +596,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
power_manager = memnew(PowerX11);
- if (p_desired.layered_splash) {
+ if (p_desired.layered) {
set_window_per_pixel_transparency_enabled(true);
}
@@ -779,6 +786,8 @@ void OS_X11::finalize() {
memdelete(input);
+ memdelete(camera_server);
+
visual_server->finish();
memdelete(visual_server);
//memdelete(rasterizer);
@@ -997,28 +1006,40 @@ void OS_X11::set_wm_fullscreen(bool p_enabled) {
XFlush(x11_display);
- if (!p_enabled && !is_window_resizable()) {
+ if (!p_enabled) {
// Reset the non-resizable flags if we un-set these before.
Size2 size = get_window_size();
XSizeHints *xsh;
-
xsh = XAllocSizeHints();
- xsh->flags = PMinSize | PMaxSize;
- xsh->min_width = size.x;
- xsh->max_width = size.x;
- xsh->min_height = size.y;
- xsh->max_height = size.y;
-
+ if (!is_window_resizable()) {
+ xsh->flags = PMinSize | PMaxSize;
+ xsh->min_width = size.x;
+ xsh->max_width = size.x;
+ xsh->min_height = size.y;
+ xsh->max_height = size.y;
+ } else {
+ xsh->flags = 0L;
+ if (min_size != Size2()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = min_size.x;
+ xsh->min_height = min_size.y;
+ }
+ if (max_size != Size2()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = max_size.x;
+ xsh->max_height = max_size.y;
+ }
+ }
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
}
- if (!p_enabled && !get_borderless_window()) {
- // put decorations back if the window wasn't suppoesed to be borderless
+ if (!p_enabled) {
+ // put back or remove decorations according to the last set borderless state
Hints hints;
Atom property;
hints.flags = 2;
- hints.decorations = 1;
+ hints.decorations = current_videomode.borderless_window ? 0 : 1;
property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
}
@@ -1184,18 +1205,20 @@ void OS_X11::set_window_position(const Point2 &p_position) {
//exclude window decorations
XSync(x11_display, False);
Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
- if (format == 32 && len == 4) {
- long *extents = (long *)data;
- x = extents[0];
- y = extents[2];
+ if (prop != None) {
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (format == 32 && len == 4) {
+ long *extents = (long *)data;
+ x = extents[0];
+ y = extents[2];
+ }
+ XFree(data);
}
- XFree(data);
}
}
XMoveWindow(x11_display, x11_window, p_position.x - x, p_position.y - y);
@@ -1215,22 +1238,90 @@ Size2 OS_X11::get_real_window_size() const {
int w = xwa.width;
int h = xwa.height;
Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
- Atom type;
- int format;
- unsigned long len;
- unsigned long remaining;
- unsigned char *data = NULL;
- if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
- if (format == 32 && len == 4) {
- long *extents = (long *)data;
- w += extents[0] + extents[1]; // left, right
- h += extents[2] + extents[3]; // top, bottom
+ if (prop != None) {
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(x11_display, x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (format == 32 && len == 4) {
+ long *extents = (long *)data;
+ w += extents[0] + extents[1]; // left, right
+ h += extents[2] + extents[3]; // top, bottom
+ }
+ XFree(data);
}
- XFree(data);
}
return Size2(w, h);
}
+Size2 OS_X11::get_max_window_size() const {
+ return max_size;
+}
+
+Size2 OS_X11::get_min_window_size() const {
+ return min_size;
+}
+
+void OS_X11::set_min_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && (max_size != Size2()) && ((p_size.x > max_size.x) || (p_size.y > max_size.y))) {
+ WARN_PRINT("Minimum window size can't be larger than maximum window size!");
+ return;
+ }
+ min_size = p_size;
+
+ if (is_window_resizable()) {
+ XSizeHints *xsh;
+ xsh = XAllocSizeHints();
+ xsh->flags = 0L;
+ if (min_size != Size2()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = min_size.x;
+ xsh->min_height = min_size.y;
+ }
+ if (max_size != Size2()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = max_size.x;
+ xsh->max_height = max_size.y;
+ }
+ XSetWMNormalHints(x11_display, x11_window, xsh);
+ XFree(xsh);
+
+ XFlush(x11_display);
+ }
+}
+
+void OS_X11::set_max_window_size(const Size2 p_size) {
+
+ if ((p_size != Size2()) && ((p_size.x < min_size.x) || (p_size.y < min_size.y))) {
+ WARN_PRINT("Maximum window size can't be smaller than minimum window size!");
+ return;
+ }
+ max_size = p_size;
+
+ if (is_window_resizable()) {
+ XSizeHints *xsh;
+ xsh = XAllocSizeHints();
+ xsh->flags = 0L;
+ if (min_size != Size2()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = min_size.x;
+ xsh->min_height = min_size.y;
+ }
+ if (max_size != Size2()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = max_size.x;
+ xsh->max_height = max_size.y;
+ }
+ XSetWMNormalHints(x11_display, x11_window, xsh);
+ XFree(xsh);
+
+ XFlush(x11_display);
+ }
+}
+
void OS_X11::set_window_size(const Size2 p_size) {
if (current_videomode.width == p_size.width && current_videomode.height == p_size.height)
@@ -1243,17 +1334,29 @@ void OS_X11::set_window_size(const Size2 p_size) {
int old_h = xwa.height;
// If window resizable is disabled we need to update the attributes first
+ XSizeHints *xsh;
+ xsh = XAllocSizeHints();
if (!is_window_resizable()) {
- XSizeHints *xsh;
- xsh = XAllocSizeHints();
xsh->flags = PMinSize | PMaxSize;
xsh->min_width = p_size.x;
xsh->max_width = p_size.x;
xsh->min_height = p_size.y;
xsh->max_height = p_size.y;
- XSetWMNormalHints(x11_display, x11_window, xsh);
- XFree(xsh);
+ } else {
+ xsh->flags = 0L;
+ if (min_size != Size2()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = min_size.x;
+ xsh->min_height = min_size.y;
+ }
+ if (max_size != Size2()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = max_size.x;
+ xsh->max_height = max_size.y;
+ }
}
+ XSetWMNormalHints(x11_display, x11_window, xsh);
+ XFree(xsh);
// Resize the window
XResizeWindow(x11_display, x11_window, p_size.x, p_size.y);
@@ -1299,20 +1402,37 @@ bool OS_X11::is_window_fullscreen() const {
}
void OS_X11::set_window_resizable(bool p_enabled) {
- XSizeHints *xsh;
- Size2 size = get_window_size();
+ XSizeHints *xsh;
xsh = XAllocSizeHints();
- xsh->flags = p_enabled ? 0L : PMinSize | PMaxSize;
if (!p_enabled) {
+ Size2 size = get_window_size();
+
+ xsh->flags = PMinSize | PMaxSize;
xsh->min_width = size.x;
xsh->max_width = size.x;
xsh->min_height = size.y;
xsh->max_height = size.y;
+ } else {
+ xsh->flags = 0L;
+ if (min_size != Size2()) {
+ xsh->flags |= PMinSize;
+ xsh->min_width = min_size.x;
+ xsh->min_height = min_size.y;
+ }
+ if (max_size != Size2()) {
+ xsh->flags |= PMaxSize;
+ xsh->max_width = max_size.x;
+ xsh->max_height = max_size.y;
+ }
}
+
XSetWMNormalHints(x11_display, x11_window, xsh);
XFree(xsh);
+
current_videomode.resizable = p_enabled;
+
+ XFlush(x11_display);
}
bool OS_X11::is_window_resizable() const {
@@ -1523,7 +1643,7 @@ bool OS_X11::is_window_always_on_top() const {
void OS_X11::set_borderless_window(bool p_borderless) {
- if (current_videomode.borderless_window == p_borderless)
+ if (get_borderless_window() == p_borderless)
return;
if (!p_borderless && layered_window)
@@ -1543,7 +1663,24 @@ void OS_X11::set_borderless_window(bool p_borderless) {
}
bool OS_X11::get_borderless_window() {
- return current_videomode.borderless_window;
+
+ bool borderless = current_videomode.borderless_window;
+ Atom prop = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
+ if (prop != None) {
+
+ Atom type;
+ int format;
+ unsigned long len;
+ unsigned long remaining;
+ unsigned char *data = NULL;
+ if (XGetWindowProperty(x11_display, x11_window, prop, 0, sizeof(Hints), False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) {
+ if (data && (format == 32) && (len >= 5)) {
+ borderless = !((Hints *)data)->decorations;
+ }
+ XFree(data);
+ }
+ }
+ return borderless;
}
void OS_X11::request_attention() {
@@ -2370,7 +2507,7 @@ void OS_X11::process_xevents() {
Vector<String> files = String((char *)p.data).split("\n", false);
for (int i = 0; i < files.size(); i++) {
- files.write[i] = files[i].replace("file://", "").http_unescape().strip_escapes();
+ files.write[i] = files[i].replace("file://", "").http_unescape().strip_edges();
}
main_loop->drop_files(files);
@@ -2588,7 +2725,7 @@ String OS_X11::get_clipboard() const {
return ret;
}
-String OS_X11::get_name() {
+String OS_X11::get_name() const {
return "X11";
}
diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h
index a54851d4e7..510487b599 100644
--- a/platform/x11/os_x11.h
+++ b/platform/x11/os_x11.h
@@ -42,6 +42,7 @@
#include "main/input_default.h"
#include "power_x11.h"
#include "servers/audio_server.h"
+#include "servers/camera_server.h"
#include "servers/visual/rasterizer.h"
#include "servers/visual_server.h"
//#include "servers/visual/visual_server_wrap_mt.h"
@@ -119,6 +120,9 @@ class OS_X11 : public OS_Unix {
bool im_active;
Vector2 im_position;
+ Size2 min_size;
+ Size2 max_size;
+
Point2 last_mouse_pos;
bool last_mouse_pos_valid;
Point2i last_click_pos;
@@ -146,6 +150,8 @@ class OS_X11 : public OS_Unix {
void get_key_modifier_state(unsigned int p_x11_state, Ref<InputEventWithModifiers> state);
void flush_mouse_motion();
+ CameraServer *camera_server;
+
MouseMode mouse_mode;
Point2i center;
@@ -216,7 +222,7 @@ protected:
bool is_window_maximize_allowed();
public:
- virtual String get_name();
+ virtual String get_name() const;
virtual void set_cursor_shape(CursorShape p_shape);
virtual CursorShape get_cursor_shape() const;
@@ -265,6 +271,10 @@ public:
virtual void set_window_position(const Point2 &p_position);
virtual Size2 get_window_size() const;
virtual Size2 get_real_window_size() const;
+ virtual Size2 get_max_window_size() const;
+ virtual Size2 get_min_window_size() const;
+ virtual void set_min_window_size(const Size2 p_size);
+ virtual void set_max_window_size(const Size2 p_size);
virtual void set_window_size(const Size2 p_size);
virtual void set_window_fullscreen(bool p_enabled);
virtual bool is_window_fullscreen() const;