summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/SCsub4
-rw-r--r--platform/android/detect.py16
-rw-r--r--platform/android/export/export.cpp9
-rw-r--r--platform/android/java/src/org/godotengine/godot/Godot.java26
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotRenderer.java61
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotView.java720
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java360
-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_wrapper.cpp20
-rw-r--r--platform/android/java_godot_wrapper.h4
-rw-r--r--platform/android/os_android.cpp6
-rw-r--r--platform/android/os_android.h3
-rw-r--r--platform/haiku/detect.py2
-rw-r--r--platform/haiku/os_haiku.cpp4
-rw-r--r--platform/haiku/os_haiku.h2
-rw-r--r--platform/iphone/SCsub1
-rw-r--r--platform/iphone/camera_ios.h45
-rw-r--r--platform/iphone/camera_ios.mm420
-rw-r--r--platform/iphone/detect.py2
-rw-r--r--platform/iphone/export/export.cpp39
-rw-r--r--platform/iphone/gl_view.mm2
-rw-r--r--platform/iphone/icloud.mm2
-rw-r--r--platform/iphone/os_iphone.cpp7
-rw-r--r--platform/iphone/os_iphone.h3
-rw-r--r--platform/javascript/audio_driver_javascript.cpp2
-rw-r--r--platform/javascript/detect.py6
-rw-r--r--platform/javascript/export/export.cpp2
-rw-r--r--platform/javascript/http_client.h.inc2
-rw-r--r--platform/javascript/os_javascript.cpp27
-rw-r--r--platform/javascript/os_javascript.h3
-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/detect.py2
-rw-r--r--platform/osx/export/export.cpp2
-rw-r--r--platform/osx/os_osx.h11
-rw-r--r--platform/osx/os_osx.mm104
-rw-r--r--platform/server/detect.py8
-rw-r--r--platform/uwp/export/export.cpp4
-rw-r--r--platform/uwp/os_uwp.cpp6
-rw-r--r--platform/uwp/os_uwp.h3
-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/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.cpp93
-rw-r--r--platform/windows/os_windows.h12
-rw-r--r--platform/windows/power_windows.cpp2
-rw-r--r--platform/windows/windows_terminal_logger.cpp4
-rw-r--r--platform/x11/detect.py14
-rw-r--r--platform/x11/detect_prime.cpp14
-rw-r--r--platform/x11/godot_x11.cpp7
-rw-r--r--platform/x11/joypad_linux.cpp11
-rw-r--r--platform/x11/os_x11.cpp179
-rw-r--r--platform/x11/os_x11.h10
-rw-r--r--platform/x11/power_x11.cpp5
66 files changed, 2832 insertions, 797 deletions
diff --git a/platform/android/SCsub b/platform/android/SCsub
index 22ed476c6f..1562714d76 100644
--- a/platform/android/SCsub
+++ b/platform/android/SCsub
@@ -37,9 +37,7 @@ android_objects.append(env_thirdparty.SharedObject('#thirdparty/misc/ifaddrs-and
lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"])
lib_arch_dir = ''
-if env['android_arch'] == 'armv6':
- lib_arch_dir = 'armeabi'
-elif env['android_arch'] == 'armv7':
+if env['android_arch'] == 'armv7':
lib_arch_dir = 'armeabi-v7a'
elif env['android_arch'] == 'arm64v8':
lib_arch_dir = 'arm64-v8a'
diff --git a/platform/android/detect.py b/platform/android/detect.py
index b7641172e4..eed51c4d30 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -26,7 +26,7 @@ def get_opts():
return [
('ANDROID_NDK_ROOT', 'Path to the Android NDK', os.environ.get("ANDROID_NDK_ROOT", 0)),
('ndk_platform', 'Target platform (android-<api>, e.g. "android-18")', "android-18"),
- EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'armv6', 'arm64v8', 'x86', 'x86_64')),
+ EnumVariable('android_arch', 'Target architecture', "armv7", ('armv7', 'arm64v8', 'x86', 'x86_64')),
BoolVariable('android_neon', 'Enable NEON support (armv7 only)', True),
BoolVariable('android_stl', 'Enable Android STL support (for modules)', True)
]
@@ -93,7 +93,7 @@ def configure(env):
## Architecture
- if env['android_arch'] not in ['armv7', 'armv6', 'arm64v8', 'x86', 'x86_64']:
+ if env['android_arch'] not in ['armv7', 'arm64v8', 'x86', 'x86_64']:
env['android_arch'] = 'armv7'
neon_text = ""
@@ -119,13 +119,6 @@ def configure(env):
abi_subpath = "x86_64-linux-android"
arch_subpath = "x86_64"
env["x86_libtheora_opt_gcc"] = True
- elif env['android_arch'] == 'armv6':
- env['ARCH'] = 'arch-arm'
- env.extra_suffix = ".armv6" + env.extra_suffix
- target_subpath = "arm-linux-androideabi-4.9"
- abi_subpath = "arm-linux-androideabi"
- arch_subpath = "armeabi"
- can_vectorize = False
elif env["android_arch"] == "armv7":
env['ARCH'] = 'arch-arm'
target_subpath = "arm-linux-androideabi-4.9"
@@ -249,11 +242,6 @@ def configure(env):
elif env['android_arch'] == 'x86_64':
target_opts = ['-target', 'x86_64-none-linux-android']
- elif env["android_arch"] == "armv6":
- target_opts = ['-target', 'armv6-none-linux-androideabi']
- env.Append(CCFLAGS='-march=armv6 -mfpu=vfp -mfloat-abi=softfp'.split())
- env.Append(CPPFLAGS=['-D__ARM_ARCH_6__'])
-
elif env["android_arch"] == "armv7":
target_opts = ['-target', 'armv7-none-linux-androideabi']
env.Append(CCFLAGS='-march=armv7-a -mfloat-abi=softfp'.split())
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index b987b3aebd..b37cf642db 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -208,7 +208,7 @@ static const LauncherIcon launcher_icons[] = {
class EditorExportPlatformAndroid : public EditorExportPlatform {
- GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform)
+ GDCLASS(EditorExportPlatformAndroid, EditorExportPlatform);
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
@@ -553,9 +553,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
static Vector<String> get_abis() {
Vector<String> abis;
- // We can still build armv6 in theory, but it doesn't make much
- // sense for games, so disabling for now.
- //abis.push_back("armeabi");
abis.push_back("armeabi-v7a");
abis.push_back("arm64-v8a");
abis.push_back("x86");
@@ -597,7 +594,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
if (abi_index != -1) {
exported = true;
String abi = abis[abi_index];
- String dst_path = "lib/" + abi + "/" + p_so.path.get_file();
+ String dst_path = String("lib").plus_file(abi).plus_file(p_so.path.get_file());
Vector<uint8_t> array = FileAccess::get_file_as_array(p_so.path);
Error store_err = store_in_apk(ed, dst_path, array);
ERR_FAIL_COND_V(store_err, store_err);
@@ -790,7 +787,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;
}
diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java
index 0eeaf0701c..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() {
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 ab28d9ec33..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,57 +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);
-
- mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext());
- mInputManager.registerInputDeviceListener(this, null);
- init(false, 16, 0);
- }
-
- public GodotView(Context context) {
- super(context);
- ctx = context;
- }
+ 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;
- public GodotView(Context context, boolean translucent, int depth, int stencil) {
- super(context);
- init(translucent, depth, stencil);
+ this.activity = activity;
+ this.inputHandler = new GodotInputHandler(this);
+ init(xrMode, false, 16, 0);
}
public void initInputDevices() {
- /* initially add input devices*/
- int[] deviceIds = mInputManager.getInputDeviceIds();
- for (int deviceId : deviceIds) {
- InputDevice device = mInputManager.getInputDevice(deviceId);
- if (DEBUG) {
- Log.v("GodotView", String.format("init() deviceId:%d, Name:%s\n", deviceId, device.getName()));
- }
- onInputDeviceAdded(deviceId);
- }
+ this.inputHandler.initInputDevices();
}
@SuppressLint("ClickableViewAccessibility")
@@ -130,617 +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);
- //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 = 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) {
- onInputDeviceRemoved(deviceId);
- onInputDeviceAdded(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..a443a0ad90
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/GodotInputHandler.java
@@ -0,0 +1,360 @@
+/*************************************************************************/
+/* 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);
+ }
+
+ private boolean isKeyEvent_GameDevice(int source) {
+ // Note that keyboards are often (SOURCE_KEYBOARD | SOURCE_DPAD)
+ if (source == (InputDevice.SOURCE_KEYBOARD | InputDevice.SOURCE_DPAD))
+ return false;
+
+ return (source & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK || (source & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD || (source & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD;
+ }
+
+ 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 (isKeyEvent_GameDevice(source)) {
+
+ 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 (isKeyEvent_GameDevice(source)) {
+
+ 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_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index e92d4437b1..339b14974c 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -60,6 +60,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_godot_instance) {
_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() {
@@ -191,3 +193,21 @@ void GodotJavaWrapper::init_input_devices() {
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 be4f109d8c..82c2a5d122 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -55,6 +55,8 @@ private:
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);
@@ -78,6 +80,8 @@ public:
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 f8076dfd2d..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);
}
diff --git a/platform/android/os_android.h b/platform/android/os_android.h
index 4dbc96f4da..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;
diff --git a/platform/haiku/detect.py b/platform/haiku/detect.py
index f33c77a407..5a708cdaca 100644
--- a/platform/haiku/detect.py
+++ b/platform/haiku/detect.py
@@ -80,7 +80,7 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng --cflags --libs')
+ env.ParseConfig('pkg-config libpng16 --cflags --libs')
if not env['builtin_bullet']:
# We need at least version 2.88
diff --git a/platform/haiku/os_haiku.cpp b/platform/haiku/os_haiku.cpp
index 438b50053f..9c07535c85 100644
--- a/platform/haiku/os_haiku.cpp
+++ b/platform/haiku/os_haiku.cpp
@@ -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 e1d4cf8d87..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;
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..ceabdba6a3
--- /dev/null
+++ b/platform/iphone/camera_ios.h
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* 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
+
+#include "servers/camera_server.h"
+
+class CameraIOS : public CameraServer {
+private:
+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..5a54eaee9b
--- /dev/null
+++ b/platform/iphone/camera_ios.mm
@@ -0,0 +1,420 @@
+/*************************************************************************/
+/* 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:
+ 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();
+};
+
+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;
+ [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 (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, AVCaptureDeviceTypeBuiltInWideAngleCamera] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
+ Ref<CameraFeedIOS> feed(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } 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(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } else if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedIOS> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+ add_feed(newfeed);
+ };
+ };
+};
+
+CameraIOS::CameraIOS() {
+ print_line("Requesting Camera permissions");
+
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
+ completionHandler:^(BOOL granted) {
+ if (granted) {
+ print_line("Access to cameras 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 d9f710e456..b448cbe097 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -144,6 +144,7 @@ def configure(env):
'-framework', 'CoreAudio',
'-framework', 'CoreGraphics',
'-framework', 'CoreMedia',
+ '-framework', 'CoreVideo',
'-framework', 'CoreMotion',
'-framework', 'Foundation',
'-framework', 'GameController',
@@ -153,6 +154,7 @@ def configure(env):
'-framework', 'Security',
'-framework', 'SystemConfiguration',
'-framework', 'UIKit',
+ '-framework', 'ARKit',
])
# Feature options
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index ba405ab7ae..99ce2e8f87 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -95,7 +95,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
Vector<ExportArchitecture> _get_supported_architectures();
Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset);
- void _add_assets_to_project(Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
+ void _add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets);
Error _export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, Vector<IOSExportAsset> &r_exported_assets);
Error _export_additional_assets(const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets);
@@ -125,7 +125,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
first = true;
continue;
}
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
@@ -137,7 +137,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
return false;
}
- if (first && c == '_') {
+ if (first && c == '-') {
if (r_error) {
*r_error = vformat(TTR("The character '%s' cannot be the first character in a Identifier segment."), String::chr(c));
}
@@ -564,7 +564,7 @@ Error EditorExportPlatformIOS::_walk_dir_recursive(DirAccess *p_da, FileHandler
dirs.push_back(path);
}
} else {
- Error err = p_handler(current_dir + "/" + path, p_userdata);
+ Error err = p_handler(current_dir.plus_file(path), p_userdata);
if (err) {
p_da->list_dir_end();
return err;
@@ -656,7 +656,7 @@ struct ExportLibsData {
String dest_dir;
};
-void EditorExportPlatformIOS::_add_assets_to_project(Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
+void EditorExportPlatformIOS::_add_assets_to_project(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_project_data, const Vector<IOSExportAsset> &p_additional_assets) {
Vector<Ref<EditorExportPlugin> > export_plugins = EditorExport::get_singleton()->get_export_plugins();
Vector<String> frameworks;
for (int i = 0; i < export_plugins.size(); ++i) {
@@ -714,7 +714,28 @@ void EditorExportPlatformIOS::_add_assets_to_project(Vector<uint8_t> &p_project_
// Note, frameworks like gamekit are always included in our project.pbxprof file
// even if turned off in capabilities.
- // Frameworks that are used by modules (like arkit) we may need to optionally add here.
+
+ // We do need our ARKit framework
+ if ((bool)p_preset->get("capabilities/arkit")) {
+ String build_id = (++current_id).str();
+ String ref_id = (++current_id).str();
+
+ if (pbx_frameworks_build.length() > 0) {
+ pbx_frameworks_build += ",\n";
+ pbx_frameworks_refs += ",\n";
+ }
+
+ pbx_frameworks_build += build_id;
+ pbx_frameworks_refs += ref_id;
+
+ Dictionary format_dict;
+ format_dict["build_id"] = build_id;
+ format_dict["ref_id"] = ref_id;
+ format_dict["name"] = "ARKit.framework";
+ format_dict["file_path"] = "System/Library/Frameworks/ARKit.framework";
+ format_dict["file_type"] = "wrapper.framework";
+ pbx_files += file_info_format.format(format_dict, "$_");
+ }
String str = String::utf8((const char *)p_project_data.ptr(), p_project_data.size());
str = str.replace("$additional_pbx_files", pbx_files);
@@ -763,7 +784,7 @@ Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir
}
}
- String destination = destination_dir + "/" + asset.get_file();
+ String destination = destination_dir.plus_file(asset.get_file());
Error err = dir_exists ? da->copy_dir(asset, destination) : da->copy(asset, destination);
memdelete(da);
if (err) {
@@ -914,7 +935,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;
@@ -1045,7 +1066,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
print_line("Exporting additional assets");
Vector<IOSExportAsset> assets;
_export_additional_assets(dest_dir + binary_name, libraries, assets);
- _add_assets_to_project(project_file_data, assets);
+ _add_assets_to_project(p_preset, project_file_data, assets);
String project_file_name = dest_dir + binary_name + ".xcodeproj/project.pbxproj";
FileAccess *f = FileAccess::open(project_file_name, FileAccess::WRITE);
if (!f) {
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
index 1cb8d0e44e..4641b2c4ac 100644
--- a/platform/iphone/gl_view.mm
+++ b/platform/iphone/gl_view.mm
@@ -494,7 +494,7 @@ static void clear_touches() {
#ifdef DEBUG_ENABLED
GLenum err = glGetError();
if (err)
- NSLog(@"%x error", err);
+ NSLog(@"DrawView: %x error", err);
#endif
}
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 6a65cadf09..f5fce66059 100644
--- a/platform/iphone/os_iphone.cpp
+++ b/platform/iphone/os_iphone.cpp
@@ -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);
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 017125209c..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
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 145ac42863..c6afa02c6d 100644
--- a/platform/javascript/detect.py
+++ b/platform/javascript/detect.py
@@ -69,9 +69,9 @@ def configure(env):
exec(f.read(), em_config)
except StandardError as e:
raise RuntimeError("Emscripten configuration file '%s' is invalid:\n%s" % (em_config_file, e))
- if 'EMSCRIPTEN_ROOT' not in em_config:
- raise RuntimeError("'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file)
- env.PrependENVPath('PATH', em_config['EMSCRIPTEN_ROOT'])
+ if 'BINARYEN_ROOT' not in em_config and 'EMSCRIPTEN_ROOT' not in em_config:
+ raise RuntimeError("'BINARYEN_ROOT' or 'EMSCRIPTEN_ROOT' missing in Emscripten configuration file '%s'" % em_config_file)
+ env.PrependENVPath('PATH', em_config.get('BINARYEN_ROOT', em_config.get('EMSCRIPTEN_ROOT')))
env['CC'] = 'emcc'
env['CXX'] = 'em++'
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 487da77b10..c68b420c61 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -40,7 +40,7 @@
class EditorExportPlatformJavaScript : public EditorExportPlatform {
- GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform)
+ GDCLASS(EditorExportPlatformJavaScript, EditorExportPlatform);
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
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 502463a6f1..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();
@@ -942,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) \
@@ -1076,6 +1092,7 @@ void OS_JavaScript::delete_main_loop() {
void OS_JavaScript::finalize() {
+ memdelete(camera_server);
memdelete(input);
}
@@ -1144,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)
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 27b23a4673..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);
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/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 9dabbb12fc..8cabc45250 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -570,7 +570,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
if (export_format == "dmg") {
// write it into our application bundle
- file = tmp_app_path_name + "/" + file;
+ file = tmp_app_path_name.plus_file(file);
// write the file, need to add chmod
FileAccess *f = FileAccess::open(file, FileAccess::WRITE);
diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h
index eed230ba89..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"
@@ -73,6 +74,8 @@ public:
//Rasterizer *rasterizer;
VisualServer *visual_server;
+ CameraServer *camera_server;
+
List<String> args;
MainLoop *main_loop;
@@ -134,6 +137,9 @@ public:
String im_text;
Point2 im_selection;
+ Size2 min_size;
+ Size2 max_size;
+
PowerOSX *power_manager;
CrashHandler crash_handler;
@@ -182,6 +188,7 @@ 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;
@@ -232,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 567d24de3e..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];
}
@@ -1002,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);
@@ -1024,7 +1037,7 @@ static int remapKey(unsigned int key) {
OSStatus err = UCKeyTranslate(keyboardLayout,
key,
kUCKeyActionDisplay,
- 0,
+ (state >> 8) & 0xFF,
LMGetKbdType(),
kUCKeyTranslateNoDeadKeysBit,
&keysDown,
@@ -1051,7 +1064,7 @@ static int remapKey(unsigned int key) {
NSString *characters = [event characters];
NSUInteger length = [characters length];
- if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode]))) {
+ 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;
@@ -1059,7 +1072,7 @@ static int remapKey(unsigned int key) {
ke.osx_state = [event modifierFlags];
ke.pressed = true;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode]);
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
ke.raw = true;
ke.unicode = [characters characterAtIndex:i];
@@ -1071,7 +1084,7 @@ static int remapKey(unsigned int key) {
ke.osx_state = [event modifierFlags];
ke.pressed = true;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode]);
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
ke.raw = false;
ke.unicode = 0;
@@ -1129,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);
@@ -1144,14 +1157,14 @@ static int remapKey(unsigned int key) {
NSUInteger length = [characters length];
// Fallback unicode character handler used if IME is not active
- if (!OS_OSX::singleton->im_active && length > 0 && keycode_has_unicode(remapKey([event keyCode]))) {
+ 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 = [event isARepeat];
- ke.scancode = remapKey([event keyCode]);
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
ke.raw = true;
ke.unicode = [characters characterAtIndex:i];
@@ -1163,7 +1176,7 @@ static int remapKey(unsigned int key) {
ke.osx_state = [event modifierFlags];
ke.pressed = false;
ke.echo = [event isARepeat];
- ke.scancode = remapKey([event keyCode]);
+ ke.scancode = remapKey([event keyCode], [event modifierFlags]);
ke.raw = true;
ke.unicode = 0;
@@ -1542,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);
@@ -1551,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;
}
@@ -1573,6 +1591,11 @@ void OS_OSX::finalize() {
delete_main_loop();
+ if (camera_server) {
+ memdelete(camera_server);
+ camera_server = NULL;
+ }
+
memdelete(joypad_osx);
memdelete(input);
@@ -1887,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);
@@ -2337,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 {
@@ -2350,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();
@@ -2379,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;
diff --git a/platform/server/detect.py b/platform/server/detect.py
index 08c2eb6aaf..a325395d6d 100644
--- a/platform/server/detect.py
+++ b/platform/server/detect.py
@@ -142,15 +142,15 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng --cflags --libs')
+ env.ParseConfig('pkg-config libpng16 --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/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index cdcad33f6d..ec43a4c26f 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -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) {
@@ -606,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);
@@ -622,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++) {
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index f9d22481dd..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;
}
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index 1bb68bc75d..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;
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/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 6df2ad4821..e535f6a148 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);
@@ -828,8 +843,8 @@ LRESULT OS_Windows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
if (wParam==VK_WIN) TODO wtf is this?
meta_mem=uMsg==WM_KEYDOWN;
*/
-
- } //fallthrough
+ FALLTHROUGH;
+ }
case WM_CHAR: {
ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
@@ -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) {
@@ -1932,6 +1979,17 @@ bool OS_Windows::is_window_always_on_top() const {
return video_mode.always_on_top;
}
+void OS_Windows::set_console_visible(bool p_enabled) {
+ if (console_visible == p_enabled)
+ return;
+ ShowWindow(GetConsoleWindow(), p_enabled ? SW_SHOW : SW_HIDE);
+ console_visible = p_enabled;
+}
+
+bool OS_Windows::is_console_visible() const {
+ return console_visible;
+}
+
bool OS_Windows::get_window_per_pixel_transparency_enabled() const {
if (!is_layered_allowed()) return false;
@@ -2193,6 +2251,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 +2438,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;
@@ -2531,9 +2591,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);
@@ -3182,6 +3242,7 @@ OS_Windows::OS_Windows(HINSTANCE _hInstance) {
control_mem = false;
meta_mem = false;
minimized = false;
+ console_visible = IsWindowVisible(GetConsoleWindow());
hInstance = _hInstance;
pressrc = 0;
diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h
index 0aacbcb9ff..fc8ad1b188 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"
@@ -108,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
@@ -124,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;
@@ -205,6 +210,7 @@ protected:
bool maximized;
bool minimized;
bool borderless;
+ bool console_visible;
public:
LRESULT WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
@@ -236,6 +242,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;
@@ -247,6 +257,8 @@ public:
virtual bool is_window_maximized() const;
virtual void set_window_always_on_top(bool p_enabled);
virtual bool is_window_always_on_top() const;
+ virtual void set_console_visible(bool p_enabled);
+ virtual bool is_console_visible() const;
virtual void request_attention();
virtual void set_borderless_window(bool p_borderless);
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/detect.py b/platform/x11/detect.py
index 933ee6b72e..9614104750 100644
--- a/platform/x11/detect.py
+++ b/platform/x11/detect.py
@@ -75,11 +75,7 @@ def get_opts():
def get_flags():
- return [
- ('builtin_freetype', False),
- ('builtin_libpng', False),
- ('builtin_zlib', False),
- ]
+ return []
def configure(env):
@@ -216,15 +212,15 @@ def configure(env):
env.ParseConfig('pkg-config freetype2 --cflags --libs')
if not env['builtin_libpng']:
- env.ParseConfig('pkg-config libpng --cflags --libs')
+ env.ParseConfig('pkg-config libpng16 --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/detect_prime.cpp b/platform/x11/detect_prime.cpp
index 0fde2a0c04..26008feade 100644
--- a/platform/x11/detect_prime.cpp
+++ b/platform/x11/detect_prime.cpp
@@ -159,10 +159,11 @@ int detect_prime() {
if (!stat_loc) {
// No need to do anything complicated here. Anything less than
// PIPE_BUF will be delivered in one read() call.
- read(fdset[0], string, sizeof(string) - 1);
-
- vendors[i] = string;
- renderers[i] = string + strlen(string) + 1;
+ // Leave it 'Unknown' otherwise.
+ if (read(fdset[0], string, sizeof(string) - 1) > 0) {
+ vendors[i] = string;
+ renderers[i] = string + strlen(string) + 1;
+ }
}
close(fdset[0]);
@@ -190,8 +191,9 @@ int detect_prime() {
memcpy(&string, vendor, vendor_len);
memcpy(&string[vendor_len], renderer, renderer_len);
- write(fdset[1], string, vendor_len + renderer_len);
-
+ if (write(fdset[1], string, vendor_len + renderer_len) == -1) {
+ print_verbose("Couldn't write vendor/renderer string.");
+ }
close(fdset[1]);
exit(0);
}
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..e6328ee14d 100644
--- a/platform/x11/joypad_linux.cpp
+++ b/platform/x11/joypad_linux.cpp
@@ -101,7 +101,6 @@ void JoypadLinux::joy_thread_func(void *p_user) {
JoypadLinux *joy = (JoypadLinux *)p_user;
joy->run_joypad_thread();
}
- return;
}
void JoypadLinux::run_joypad_thread() {
@@ -414,7 +413,9 @@ void JoypadLinux::joypad_vibration_start(int p_id, float p_weak_magnitude, float
play.type = EV_FF;
play.code = effect.id;
play.value = 1;
- write(joy.fd, (const void *)&play, sizeof(play));
+ if (write(joy.fd, (const void *)&play, sizeof(play)) == -1) {
+ print_verbose("Couldn't write to Joypad device.");
+ }
joy.ff_effect_id = effect.id;
joy.ff_effect_timestamp = p_timestamp;
@@ -444,10 +445,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 311b42be22..624efe8815 100644
--- a/platform/x11/os_x11.cpp
+++ b/platform/x11/os_x11.cpp
@@ -146,7 +146,7 @@ Error OS_X11::initialize(const VideoMode &p_desired, int p_video_driver, int p_a
if (is_stdout_verbose()) {
WARN_PRINT("IME is disabled");
}
- modifiers = XSetLocaleModifiers("@im=none");
+ XSetLocaleModifiers("@im=none");
WARN_PRINT("Error setting locale modifiers");
}
@@ -583,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
@@ -593,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);
}
@@ -783,6 +786,8 @@ void OS_X11::finalize() {
memdelete(input);
+ memdelete(camera_server);
+
visual_server->finish();
memdelete(visual_server);
//memdelete(rasterizer);
@@ -1001,28 +1006,38 @@ 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
+ // 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,7 +1199,7 @@ Point2 OS_X11::get_window_position() const {
void OS_X11::set_window_position(const Point2 &p_position) {
int x = 0;
int y = 0;
- if (get_borderless_window() == false) {
+ if (!get_borderless_window()) {
//exclude window decorations
XSync(x11_display, False);
Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True);
@@ -1239,6 +1254,72 @@ Size2 OS_X11::get_real_window_size() const {
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)
@@ -1251,17 +1332,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);
@@ -1307,20 +1400,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 {
@@ -1531,7 +1641,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)
@@ -1551,7 +1661,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() {
@@ -2870,7 +2997,7 @@ void OS_X11::alert(const String &p_alert, const String &p_title) {
for (int i = 0; i < path_elems.size(); i++) {
for (unsigned int k = 0; k < sizeof(message_programs) / sizeof(char *); k++) {
- String tested_path = path_elems[i] + "/" + message_programs[k];
+ String tested_path = path_elems[i].plus_file(message_programs[k]);
if (FileAccess::exists(tested_path)) {
program = tested_path;
@@ -2922,8 +3049,6 @@ void OS_X11::alert(const String &p_alert, const String &p_title) {
} else {
print_line(p_alert);
}
-
- return;
}
bool g_set_icon_error = false;
diff --git a/platform/x11/os_x11.h b/platform/x11/os_x11.h
index ad35cdb4f9..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;
@@ -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;
diff --git a/platform/x11/power_x11.cpp b/platform/x11/power_x11.cpp
index 50da6a4967..758bd84114 100644
--- a/platform/x11/power_x11.cpp
+++ b/platform/x11/power_x11.cpp
@@ -202,7 +202,10 @@ void PowerX11::check_proc_acpi_battery(const char *node, bool *have_battery, boo
* We pick the battery that claims to have the most minutes left.
* (failing a report of minutes, we'll take the highest percent.)
*/
- if ((secs < 0) && (this->nsecs_left < 0)) {
+ // -- GODOT start --
+ //if ((secs < 0) && (this->nsecs_left < 0)) {
+ if (this->nsecs_left < 0) {
+ // -- GODOT end --
if ((pct < 0) && (this->percent_left < 0)) {
choose = true; /* at least we know there's a battery. */
}