diff options
author | punto- <ariel@godotengine.org> | 2016-02-08 20:09:02 -0300 |
---|---|---|
committer | punto- <ariel@godotengine.org> | 2016-02-08 20:09:02 -0300 |
commit | b6b33e8886c4e1515164925dddf55d9ee687456f (patch) | |
tree | 73062a8182bc82829791845585b460252c56113e | |
parent | bbb31bd8356b19580e594dc1fcdd7fdd19e57f66 (diff) | |
parent | e7b6e3f20baa42663b0b6c1009d3cde97d7aa393 (diff) |
Merge pull request #3428 from Hinsbart/android-gamepad
support gamepad remapping on android
-rw-r--r-- | main/input_default.cpp | 19 | ||||
-rw-r--r-- | main/input_default.h | 8 | ||||
-rw-r--r-- | platform/android/java/src/org/godotengine/godot/GodotLib.java | 2 | ||||
-rw-r--r-- | platform/android/java/src/org/godotengine/godot/GodotView.java | 237 | ||||
-rw-r--r-- | platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java | 140 | ||||
-rw-r--r-- | platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java | 107 | ||||
-rw-r--r-- | platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java | 211 | ||||
-rw-r--r-- | platform/android/java_glue.cpp | 75 | ||||
-rw-r--r-- | platform/android/java_glue.h | 2 | ||||
-rw-r--r-- | platform/android/os_android.cpp | 33 | ||||
-rw-r--r-- | platform/android/os_android.h | 21 |
11 files changed, 688 insertions, 167 deletions
diff --git a/main/input_default.cpp b/main/input_default.cpp index f62f794b33..260af7528b 100644 --- a/main/input_default.cpp +++ b/main/input_default.cpp @@ -157,7 +157,7 @@ void InputDefault::joy_connection_changed(int p_idx, bool p_connected, String p_ }; js.uid = uidname; //printf("looking for mappings for guid %ls\n", uidname.c_str()); - int mapping = -1; + int mapping = fallback_mapping; for (int i=0; i < map_db.size(); i++) { if (js.uid == map_db[i].uid) { mapping = i; @@ -499,6 +499,7 @@ static const char *s_ControllerMappings [] = #endif #if defined(__ANDROID__) + "Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,", "4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", #endif @@ -536,6 +537,8 @@ InputDefault::InputDefault() { hat_map_default[HAT_LEFT].index = JOY_DPAD_LEFT; hat_map_default[HAT_LEFT].value = 0; + fallback_mapping = -1; + String env_mapping = OS::get_singleton()->get_environment("SDL_GAMECONTROLLERCONFIG"); if (env_mapping != "") { @@ -876,6 +879,16 @@ void InputDefault::remove_joy_mapping(String p_guid) { } } +void InputDefault::set_fallback_mapping(String p_guid) { + + for (int i = 0; i < map_db.size(); i++) { + if (map_db[i].uid == p_guid) { + fallback_mapping = i; + return; + } + } +} + //Defaults to simple implementation for platforms with a fixed gamepad layout, like consoles. bool InputDefault::is_joy_known(int p_device) { @@ -888,10 +901,10 @@ String InputDefault::get_joy_guid(int p_device) const { //platforms that use the remapping system can override and call to these ones bool InputDefault::is_joy_mapped(int p_device) { - return joy_names[p_device].mapping != -1 ? true : false; + int mapping = joy_names[p_device].mapping; + return mapping != -1 ? (mapping == fallback_mapping) : false; } String InputDefault::get_joy_guid_remapped(int p_device) const { return joy_names[p_device].uid; } - diff --git a/main/input_default.h b/main/input_default.h index a75865e0cb..2f98d279d6 100644 --- a/main/input_default.h +++ b/main/input_default.h @@ -18,7 +18,6 @@ class InputDefault : public Input { MainLoop *main_loop; bool emulate_touch; - struct SpeedTrack { uint64_t last_tick; @@ -36,7 +35,7 @@ class InputDefault : public Input { struct Joystick { StringName name; StringName uid; - bool last_buttons[JOY_BUTTON_MAX + 2]; //html5 needs support for 18 buttons to map some devices correctly + bool last_buttons[JOY_BUTTON_MAX + 19]; //apparently SDL specifies 35 possible buttons on android float last_axis[JOY_AXIS_MAX]; float filter; int last_hat; @@ -50,7 +49,7 @@ class InputDefault : public Input { last_axis[i] = 0.0f; } - for (int i = 0; i < JOY_BUTTON_MAX + 2; i++) { + for (int i = 0; i < JOY_BUTTON_MAX + 19; i++) { last_buttons[i] = false; } @@ -62,6 +61,7 @@ class InputDefault : public Input { SpeedTrack mouse_speed_track; Map<int, Joystick> joy_names; + int fallback_mapping; RES custom_cursor; public: enum HatMask { @@ -169,7 +169,7 @@ public: bool is_joy_mapped(int p_device); String get_joy_guid_remapped(int p_device) const; - + void set_fallback_mapping(String p_guid); InputDefault(); }; diff --git a/platform/android/java/src/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java index aef6591864..7c5ac33c85 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/src/org/godotengine/godot/GodotLib.java @@ -54,6 +54,8 @@ public class GodotLib { public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed); public static native void joybutton(int p_device, int p_but, boolean p_pressed); public static native void joyaxis(int p_device, int p_axis, float p_value); + public static native void joyhat(int p_device, int p_hat_x, int p_hat_y); + public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name); public static native void focusin(); public static native void focusout(); public static native void audio(); diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java index 492eb4cb54..e210161e8b 100644 --- a/platform/android/java/src/org/godotengine/godot/GodotView.java +++ b/platform/android/java/src/org/godotengine/godot/GodotView.java @@ -36,14 +36,21 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.content.ContextWrapper; import android.view.InputDevice; +import android.hardware.input.InputManager; 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; /** * A simple GLSurfaceView sub-class that demonstrate how to perform * OpenGL ES 2.0 rendering into a GL Surface. Note the following important @@ -62,7 +69,7 @@ import javax.microedition.khronos.opengles.GL10; * 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 { +public class GodotView extends GLSurfaceView implements InputDeviceListener { private static String TAG = "GodotView"; private static final boolean DEBUG = false; @@ -75,6 +82,8 @@ public class GodotView extends GLSurfaceView { private Godot activity; + + private InputManagerCompat mInputManager; public GodotView(Context context,GodotIO p_io,boolean p_use_gl2, boolean p_use_32_bits, Godot p_activity) { super(context); ctx=context; @@ -88,7 +97,8 @@ public class GodotView extends GLSurfaceView { //will only work on SDK 11+!! setPreserveEGLContextOnPause(true); } - + mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext()); + mInputManager.registerInputDeviceListener(this, null); init(false, 16, 0); } @@ -119,50 +129,112 @@ public class GodotView extends GLSurfaceView { button = 3; break; case KeyEvent.KEYCODE_BUTTON_L1: - button = 4; + button = 9; break; case KeyEvent.KEYCODE_BUTTON_L2: - button = 6; + button = 15; break; case KeyEvent.KEYCODE_BUTTON_R1: - button = 5; + button = 10; break; case KeyEvent.KEYCODE_BUTTON_R2: - button = 7; + button = 16; break; case KeyEvent.KEYCODE_BUTTON_SELECT: - button = 10; + button = 4; break; case KeyEvent.KEYCODE_BUTTON_START: - button = 11; + button = 6; break; case KeyEvent.KEYCODE_BUTTON_THUMBL: - button = 8; + button = 7; break; case KeyEvent.KEYCODE_BUTTON_THUMBR: - button = 9; + button = 8; break; case KeyEvent.KEYCODE_DPAD_UP: - button = 12; + button = 11; break; case KeyEvent.KEYCODE_DPAD_DOWN: - button = 13; + button = 12; break; case KeyEvent.KEYCODE_DPAD_LEFT: - button = 14; + button = 13; break; case KeyEvent.KEYCODE_DPAD_RIGHT: - button = 15; + 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; + 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; + } + } + onInputDeviceAdded(device_id); + return joy_devices.size() - 1; + } + + @Override public void onInputDeviceAdded(int deviceId) { + joystick joy = new joystick(); + joy.device_id = deviceId; + int id = joy_devices.size(); + InputDevice device = mInputManager.getInputDevice(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); + GodotLib.joyconnectionchanged(id, true, joy.name); + } + + @Override public void onInputDeviceRemoved(int deviceId) { + int id = find_joy_device(deviceId); + joy_devices.remove(id); + GodotLib.joyconnectionchanged(id, false, ""); + } + + @Override public void onInputDeviceChanged(int deviceId) { + + } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { @@ -177,7 +249,7 @@ public class GodotView extends GLSurfaceView { if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) { int button = get_godot_button(keyCode); - int device = event.getDeviceId(); + int device = find_joy_device(event.getDeviceId()); GodotLib.joybutton(device, button, false); return true; @@ -209,7 +281,8 @@ public class GodotView extends GLSurfaceView { if (event.getRepeatCount() > 0) // ignore key echo return true; int button = get_godot_button(keyCode); - int device = event.getDeviceId(); + int device = find_joy_device(event.getDeviceId()); + //Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device)); GodotLib.joybutton(device, button, true); @@ -221,125 +294,27 @@ public class GodotView extends GLSurfaceView { return super.onKeyDown(keyCode, event); } - public float axis_value(MotionEvent p_event, InputDevice p_device, int p_axis, int p_pos) { - - final InputDevice.MotionRange range = p_device.getMotionRange(p_axis, p_event.getSource()); - if (range == null) - return 0; - - //Log.e(TAG, String.format("axis ranges %f, %f, %f", range.getRange(), range.getMin(), range.getMax())); - - final float flat = range.getFlat(); - final float value = - p_pos < 0 ? p_event.getAxisValue(p_axis): - p_event.getHistoricalAxisValue(p_axis, p_pos); - - final float absval = Math.abs(value); - if (absval <= flat) { - return 0; - }; - - final float ret = (value - range.getMin()) / range.getRange() * 2 - 1.0f; - - return ret; - }; - - float[] last_axis_values = { 0, 0, 0, 0, -1, -1 }; - boolean[] last_axis_buttons = { false, false, false, false, false, false }; // dpad up down left right, ltrigger, rtrigger - - public void process_axis_state(MotionEvent p_event, int p_pos) { - - int device_id = p_event.getDeviceId(); - InputDevice device = p_event.getDevice(); - float val; - - val = axis_value(p_event, device, MotionEvent.AXIS_X, p_pos); - if (val != last_axis_values[0]) { - last_axis_values[0] = val; - //Log.e(TAG, String.format("axis moved! axis %d, value %f", 0, val)); - GodotLib.joyaxis(device_id, 0, val); - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_Y, p_pos); - if (val != last_axis_values[1]) { - last_axis_values[1] = val; - //Log.e(TAG, String.format("axis moved! axis %d, value %f", 1, val)); - GodotLib.joyaxis(device_id, 1, val); - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_Z, p_pos); - if (val != last_axis_values[2]) { - last_axis_values[2] = val; - //Log.e(TAG, String.format("axis moved! axis %d, value %f", 2, val)); - GodotLib.joyaxis(device_id, 2, val); - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_RZ, p_pos); - if (val != last_axis_values[3]) { - last_axis_values[3] = val; - //Log.e(TAG, String.format("axis moved! axis %d, value %f", 3, val)); - GodotLib.joyaxis(device_id, 3, val); - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_LTRIGGER, p_pos); - if (val != last_axis_values[4]) { - last_axis_values[4] = val; - if ((val != 0) != (last_axis_buttons[4])) { - last_axis_buttons[4] = (val != 0); - GodotLib.joybutton(device_id, 6, (val != 0)); - }; - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_RTRIGGER, p_pos); - if (val != last_axis_values[5]) { - last_axis_values[5] = val; - if ((val != 0) != (last_axis_buttons[5])) { - last_axis_buttons[5] = (val != 0); - GodotLib.joybutton(device_id, 7, (val != 0)); - }; - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_HAT_Y, p_pos); - - if (last_axis_buttons[0] != (val > 0)) { - last_axis_buttons[0] = val > 0; - GodotLib.joybutton(device_id, 12, val > 0); - }; - if (last_axis_buttons[1] != (val < 0)) { - last_axis_buttons[1] = val < 0; - GodotLib.joybutton(device_id, 13, val > 0); - }; - - val = axis_value(p_event, device, MotionEvent.AXIS_HAT_X, p_pos); - if (last_axis_buttons[2] != (val < 0)) { - last_axis_buttons[2] = val < 0; - GodotLib.joybutton(device_id, 14, val < 0); - }; - if (last_axis_buttons[3] != (val > 0)) { - last_axis_buttons[3] = val > 0; - GodotLib.joybutton(device_id, 15, val > 0); - }; - }; - @Override public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) { - // Process all historical movement samples in the batch - final int historySize = event.getHistorySize(); + int device_id = find_joy_device(event.getDeviceId()); + joystick joy = joy_devices.get(device_id); - // Process the movements starting from the - // earliest historical position in the batch - for (int i = 0; i < historySize; i++) { - // Process the event at historical position i - process_axis_state(event, i); + for (int i = 0; i < joy.axes.size(); i++) { + InputDevice.MotionRange range = joy.axes.get(i); + float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; + //Log.e(TAG, String.format("axis event: %d, value %f", i, value)); + GodotLib.joyaxis(device_id, i, value); } - // Process the current movement sample in the batch (position -1) - process_axis_state(event, -1); + for (int i = 0; i < joy.hats.size(); i+=2) { + int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis())); + int hatY = Math.round(event.getAxisValue(joy.hats.get(i+1).getAxis())); + //Log.e(TAG, String.format("HAT EVENT %d, %d", hatX, hatY)); + GodotLib.joyhat(device_id, hatX, hatY); + } return true; - - }; return super.onGenericMotionEvent(event); @@ -413,12 +388,12 @@ public class GodotView extends GLSurfaceView { /* 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); diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java new file mode 100644 index 0000000000..4615d2fbb5 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.godotengine.godot.input; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.view.InputDevice; +import android.view.MotionEvent; + +public interface InputManagerCompat { + /** + * Gets information about the input device with the specified id. + * + * @param id The device id + * @return The input device or null if not found + */ + public InputDevice getInputDevice(int id); + + /** + * Gets the ids of all input devices in the system. + * + * @return The input device ids. + */ + public int[] getInputDeviceIds(); + + /** + * Registers an input device listener to receive notifications about when + * input devices are added, removed or changed. + * + * @param listener The listener to register. + * @param handler The handler on which the listener should be invoked, or + * null if the listener should be invoked on the calling thread's + * looper. + */ + public void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener, + Handler handler); + + /** + * Unregisters an input device listener. + * + * @param listener The listener to unregister. + */ + public void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener); + + /* + * The following three calls are to simulate V16 behavior on pre-Jellybean + * devices. If you don't call them, your callback will never be called + * pre-API 16. + */ + + /** + * Pass the motion events to the InputManagerCompat. This is used to + * optimize for polling for controllers. If you do not pass these events in, + * polling will cause regular object creation. + * + * @param event the motion event from the app + */ + public void onGenericMotionEvent(MotionEvent event); + + /** + * Tell the V9 input manager that it should stop polling for disconnected + * devices. You can call this during onPause in your activity, although you + * might want to call it whenever your game is not active (or whenever you + * don't care about being notified of new input devices) + */ + public void onPause(); + + /** + * Tell the V9 input manager that it should start polling for disconnected + * devices. You can call this during onResume in your activity, although you + * might want to call it less often (only when the gameplay is actually + * active) + */ + public void onResume(); + + public interface InputDeviceListener { + /** + * Called whenever the input manager detects that a device has been + * added. This will only be called in the V9 version when a motion event + * is detected. + * + * @param deviceId The id of the input device that was added. + */ + void onInputDeviceAdded(int deviceId); + + /** + * Called whenever the properties of an input device have changed since + * they were last queried. This will not be called for the V9 version of + * the API. + * + * @param deviceId The id of the input device that changed. + */ + void onInputDeviceChanged(int deviceId); + + /** + * Called whenever the input manager detects that a device has been + * removed. For the V9 version, this can take some time depending on the + * poll rate. + * + * @param deviceId The id of the input device that was removed. + */ + void onInputDeviceRemoved(int deviceId); + } + + /** + * Use this to construct a compatible InputManager. + */ + public static class Factory { + + /** + * Constructs and returns a compatible InputManger + * + * @param context the Context that will be used to get the system + * service from + * @return a compatible implementation of InputManager + */ + public static InputManagerCompat getInputManager(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return new InputManagerV16(context); + } else { + return new InputManagerV9(); + } + } + } +} diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java new file mode 100644 index 0000000000..f05701f455 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.godotengine.godot.input; + +import android.annotation.TargetApi; +import android.content.Context; +import android.hardware.input.InputManager; +import android.os.Build; +import android.os.Handler; +import android.view.InputDevice; +import android.view.MotionEvent; + +import java.util.HashMap; +import java.util.Map; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public class InputManagerV16 implements InputManagerCompat { + + private final InputManager mInputManager; + private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners; + + public InputManagerV16(Context context) { + mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + mListeners = new HashMap<InputManagerCompat.InputDeviceListener, V16InputDeviceListener>(); + } + + @Override + public InputDevice getInputDevice(int id) { + return mInputManager.getInputDevice(id); + } + + @Override + public int[] getInputDeviceIds() { + return mInputManager.getInputDeviceIds(); + } + + static class V16InputDeviceListener implements InputManager.InputDeviceListener { + final InputManagerCompat.InputDeviceListener mIDL; + + public V16InputDeviceListener(InputDeviceListener idl) { + mIDL = idl; + } + + @Override + public void onInputDeviceAdded(int deviceId) { + mIDL.onInputDeviceAdded(deviceId); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + mIDL.onInputDeviceChanged(deviceId); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + mIDL.onInputDeviceRemoved(deviceId); + } + + } + + @Override + public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) { + V16InputDeviceListener v16Listener = new V16InputDeviceListener(listener); + mInputManager.registerInputDeviceListener(v16Listener, handler); + mListeners.put(listener, v16Listener); + } + + @Override + public void unregisterInputDeviceListener(InputDeviceListener listener) { + V16InputDeviceListener curListener = mListeners.remove(listener); + if (null != curListener) + { + mInputManager.unregisterInputDeviceListener(curListener); + } + + } + + @Override + public void onGenericMotionEvent(MotionEvent event) { + // unused in V16 + } + + @Override + public void onPause() { + // unused in V16 + } + + @Override + public void onResume() { + // unused in V16 + } + +} diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java new file mode 100644 index 0000000000..0334c00997 --- /dev/null +++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.godotengine.godot.input; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; +import android.view.InputDevice; +import android.view.MotionEvent; + +import java.lang.ref.WeakReference; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; +import java.util.Queue; + +public class InputManagerV9 implements InputManagerCompat { + private static final String LOG_TAG = "InputManagerV9"; + private static final int MESSAGE_TEST_FOR_DISCONNECT = 101; + private static final long CHECK_ELAPSED_TIME = 3000L; + + private static final int ON_DEVICE_ADDED = 0; + private static final int ON_DEVICE_CHANGED = 1; + private static final int ON_DEVICE_REMOVED = 2; + + private final SparseArray<long[]> mDevices; + private final Map<InputDeviceListener, Handler> mListeners; + private final Handler mDefaultHandler; + + private static class PollingMessageHandler extends Handler { + private final WeakReference<InputManagerV9> mInputManager; + + PollingMessageHandler(InputManagerV9 im) { + mInputManager = new WeakReference<InputManagerV9>(im); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case MESSAGE_TEST_FOR_DISCONNECT: + InputManagerV9 imv = mInputManager.get(); + if (null != imv) { + long time = SystemClock.elapsedRealtime(); + int size = imv.mDevices.size(); + for (int i = 0; i < size; i++) { + long[] lastContact = imv.mDevices.valueAt(i); + if (null != lastContact) { + if (time - lastContact[0] > CHECK_ELAPSED_TIME) { + // check to see if the device has been + // disconnected + int id = imv.mDevices.keyAt(i); + if (null == InputDevice.getDevice(id)) { + // disconnected! + imv.notifyListeners(ON_DEVICE_REMOVED, id); + imv.mDevices.remove(id); + } else { + lastContact[0] = time; + } + } + } + } + sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT, + CHECK_ELAPSED_TIME); + } + break; + } + } + + } + + public InputManagerV9() { + mDevices = new SparseArray<long[]>(); + mListeners = new HashMap<InputDeviceListener, Handler>(); + mDefaultHandler = new PollingMessageHandler(this); + // as a side-effect, populates our collection of watched + // input devices + getInputDeviceIds(); + } + + @Override + public InputDevice getInputDevice(int id) { + return InputDevice.getDevice(id); + } + + @Override + public int[] getInputDeviceIds() { + // add any hitherto unknown devices to our + // collection of watched input devices + int[] activeDevices = InputDevice.getDeviceIds(); + long time = SystemClock.elapsedRealtime(); + for ( int id : activeDevices ) { + long[] lastContact = mDevices.get(id); + if ( null == lastContact ) { + // we have a new device + mDevices.put(id, new long[] { time }); + } + } + return activeDevices; + } + + @Override + public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) { + mListeners.remove(listener); + if (handler == null) { + handler = mDefaultHandler; + } + mListeners.put(listener, handler); + } + + @Override + public void unregisterInputDeviceListener(InputDeviceListener listener) { + mListeners.remove(listener); + } + + private void notifyListeners(int why, int deviceId) { + // the state of some device has changed + if (!mListeners.isEmpty()) { + // yes... this will cause an object to get created... hopefully + // it won't happen very often + for (InputDeviceListener listener : mListeners.keySet()) { + Handler handler = mListeners.get(listener); + DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, listener); + handler.post(odc); + } + } + } + + private static class DeviceEvent implements Runnable { + private int mMessageType; + private int mId; + private InputDeviceListener mListener; + private static Queue<DeviceEvent> sEventQueue = new ArrayDeque<DeviceEvent>(); + + private DeviceEvent() { + } + + static DeviceEvent getDeviceEvent(int messageType, int id, + InputDeviceListener listener) { + DeviceEvent curChanged = sEventQueue.poll(); + if (null == curChanged) { + curChanged = new DeviceEvent(); + } + curChanged.mMessageType = messageType; + curChanged.mId = id; + curChanged.mListener = listener; + return curChanged; + } + + @Override + public void run() { + switch (mMessageType) { + case ON_DEVICE_ADDED: + mListener.onInputDeviceAdded(mId); + break; + case ON_DEVICE_CHANGED: + mListener.onInputDeviceChanged(mId); + break; + case ON_DEVICE_REMOVED: + mListener.onInputDeviceRemoved(mId); + break; + default: + Log.e(LOG_TAG, "Unknown Message Type"); + break; + } + // dump this runnable back in the queue + sEventQueue.offer(this); + } + } + + @Override + public void onGenericMotionEvent(MotionEvent event) { + // detect new devices + int id = event.getDeviceId(); + long[] timeArray = mDevices.get(id); + if (null == timeArray) { + notifyListeners(ON_DEVICE_ADDED, id); + timeArray = new long[1]; + mDevices.put(id, timeArray); + } + long time = SystemClock.elapsedRealtime(); + timeArray[0] = time; + } + + @Override + public void onPause() { + mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT); + } + + @Override + public void onResume() { + mDefaultHandler.sendEmptyMessage(MESSAGE_TEST_FOR_DISCONNECT); + } + +} diff --git a/platform/android/java_glue.cpp b/platform/android/java_glue.cpp index 75c1d78151..b5beb8fa2c 100644 --- a/platform/android/java_glue.cpp +++ b/platform/android/java_glue.cpp @@ -41,6 +41,7 @@ #include "core/os/keyboard.h" #include "java_class_wrapper.h" #include "android/asset_manager_jni.h" +#include "main/input_default.h" static JavaClassWrapper *java_class_wrapper=NULL; static OS_Android *os_android=NULL; @@ -639,6 +640,7 @@ struct JAndroidPointerEvent { static List<JAndroidPointerEvent> pointer_events; static List<InputEvent> key_events; +static List<OS_Android::JoystickEvent> joy_events; static bool initialized=false; static Mutex *input_mutex=NULL; static Mutex *suspend_mutex=NULL; @@ -1067,6 +1069,14 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv * env, jo key_events.pop_front(); }; + while (joy_events.size()) { + + OS_Android::JoystickEvent event = joy_events.front()->get(); + os_android->process_joy_event(event); + + joy_events.pop_front(); + } + if (quit_request) { os_android->main_loop_request_quit(); @@ -1380,48 +1390,57 @@ static unsigned int android_get_keysym(unsigned int p_code) { return KEY_UNKNOWN; } -static int find_device(int p_device) { - - for (int i=0; i<joy_device_ids.size(); i++) { - - if (joy_device_ids[i] == p_device) { - //print_line("found device at "+String::num(i)); - return i; - }; - }; - - //print_line("adding a device at" + String::num(joy_device_ids.size())); - joy_device_ids.push_back(p_device); - - return joy_device_ids.size() - 1; -}; - JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv * env, jobject obj, jint p_device, jint p_button, jboolean p_pressed) { - InputEvent ievent; - ievent.type = InputEvent::JOYSTICK_BUTTON; - ievent.device = find_device(p_device); - ievent.joy_button.button_index = p_button; - ievent.joy_button.pressed = p_pressed; + OS_Android::JoystickEvent jevent; + jevent.device = p_device; + jevent.type = OS_Android::JOY_EVENT_BUTTON; + jevent.index = p_button; + jevent.pressed = p_pressed; input_mutex->lock(); - key_events.push_back(ievent); + joy_events.push_back(jevent); input_mutex->unlock(); }; JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv * env, jobject obj, jint p_device, jint p_axis, jfloat p_value) { - InputEvent ievent; - ievent.type = InputEvent::JOYSTICK_MOTION; - ievent.device = find_device(p_device); - ievent.joy_motion.axis = p_axis; - ievent.joy_motion.axis_value = p_value; + OS_Android::JoystickEvent jevent; + jevent.device = p_device; + jevent.type = OS_Android::JOY_EVENT_AXIS; + jevent.index = p_axis; + jevent.value = p_value; input_mutex->lock(); - key_events.push_back(ievent); + joy_events.push_back(jevent); input_mutex->unlock(); }; +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv * env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y) { + OS_Android::JoystickEvent jevent; + jevent.device = p_device; + jevent.type = OS_Android::JOY_EVENT_HAT; + int hat = 0; + if (p_hat_x != 0) { + if (p_hat_x < 0) hat |= InputDefault::HAT_MASK_LEFT; + else hat |= InputDefault::HAT_MASK_RIGHT; + } + if (p_hat_y != 0) { + if (p_hat_y < 0) hat |= InputDefault::HAT_MASK_UP; + else hat |= InputDefault::HAT_MASK_DOWN; + } + jevent.hat = hat; + input_mutex->lock(); + joy_events.push_back(jevent); + input_mutex->unlock(); +} + +JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv * env, jobject obj, jint p_device, jboolean p_connected, jstring p_name) { + if (os_android) { + String name = env->GetStringUTFChars( p_name, NULL ); + os_android->joy_connection_changed(p_device, p_connected, name); + } +} JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv * env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed) { diff --git a/platform/android/java_glue.h b/platform/android/java_glue.h index efa5b2839d..1d65d21251 100644 --- a/platform/android/java_glue.h +++ b/platform/android/java_glue.h @@ -45,6 +45,8 @@ extern "C" { JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_key(JNIEnv * env, jobject obj, jint p_scancode, jint p_unicode_char, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joybutton(JNIEnv * env, jobject obj, jint p_device, jint p_button, jboolean p_pressed); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyaxis(JNIEnv * env, jobject obj, jint p_device, jint p_axis, jfloat p_value); + JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyhat(JNIEnv * env, jobject obj, jint p_device, jint p_hat_x, jint p_hat_y); + JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_joyconnectionchanged(JNIEnv * env, jobject obj, jint p_device, jboolean p_connected, jstring p_name); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_audio(JNIEnv * env, jobject obj); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_accelerometer(JNIEnv * env, jobject obj, jfloat x, jfloat y, jfloat z); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_focusin(JNIEnv * env, jobject obj); diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 03177317af..1751334c9b 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -178,7 +178,7 @@ void OS_Android::initialize(const VideoMode& p_desired,int p_video_driver,int p_ physics_2d_server->init(); input = memnew( InputDefault ); - + input->set_fallback_mapping("Default Android Gamepad"); } void OS_Android::set_main_loop( MainLoop * p_main_loop ) { @@ -370,6 +370,25 @@ void OS_Android::main_loop_focusin(){ } +void OS_Android::process_joy_event(OS_Android::JoystickEvent p_event) { + + switch (p_event.type) { + case JOY_EVENT_BUTTON: + last_id = input->joy_button(last_id, p_event.device, p_event.index, p_event.pressed); + break; + case JOY_EVENT_AXIS: + InputDefault::JoyAxis value; + value.min = -1; + value.value = p_event.value; + last_id = input->joy_axis(last_id, p_event.device, p_event.index, value); + break; + case JOY_EVENT_HAT: + last_id = input->joy_hat(last_id, p_event.device, p_event.hat); + break; + default: + return; + } +} void OS_Android::process_event(InputEvent p_event) { @@ -742,6 +761,18 @@ void OS_Android::set_context_is_16_bits(bool p_is_16) { rasterizer->set_force_16_bits_fbo(p_is_16); } +void OS_Android::joy_connection_changed(int p_device, bool p_connected, String p_name) { + return input->joy_connection_changed(p_device, p_connected, p_name, ""); +} + +bool OS_Android::is_joy_known(int p_device) { + return input->is_joy_mapped(p_device); +} + +String OS_Android::get_joy_guid(int p_device) const { + return input->get_joy_guid_remapped(p_device); +} + OS_Android::OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id,GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, bool p_use_apk_expansion) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 1ae42e9cc7..5075e766bc 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -83,6 +83,22 @@ public: Point2 pos; }; + enum { + JOY_EVENT_BUTTON = 0, + JOY_EVENT_AXIS = 1, + JOY_EVENT_HAT = 2 + }; + + struct JoystickEvent { + + int device; + int type; + int index; + bool pressed; + float value; + int hat; + }; + private: Vector<TouchPos> touch; @@ -224,6 +240,7 @@ public: void process_accelerometer(const Vector3& p_accelerometer); void process_touch(int p_what,int p_pointer, const Vector<TouchPos>& p_points); + void process_joy_event(JoystickEvent p_event); void process_event(InputEvent p_event); void init_video_mode(int p_video_width,int p_video_height); @@ -232,6 +249,10 @@ public: virtual void native_video_pause(); virtual void native_video_stop(); + virtual bool is_joy_known(int p_device); + virtual String get_joy_guid(int p_device) const; + void joy_connection_changed(int p_device, bool p_connected, String p_name); + OS_Android(GFXInitFunc p_gfx_init_func,void*p_gfx_init_ud, OpenURIFunc p_open_uri_func, GetDataDirFunc p_get_data_dir_func,GetLocaleFunc p_get_locale_func,GetModelFunc p_get_model_func, ShowVirtualKeyboardFunc p_show_vk, HideVirtualKeyboardFunc p_hide_vk, SetScreenOrientationFunc p_screen_orient,GetUniqueIDFunc p_get_unique_id,GetSystemDirFunc p_get_sdir_func, VideoPlayFunc p_video_play_func, VideoIsPlayingFunc p_video_is_playing_func, VideoPauseFunc p_video_pause_func, VideoStopFunc p_video_stop_func, SetKeepScreenOnFunc p_set_keep_screen_on_func, bool p_use_apk_expansion); ~OS_Android(); |