diff options
Diffstat (limited to 'platform')
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. */ } |