Merge pull request #3428 from Hinsbart/android-gamepad

support gamepad remapping on android
This commit is contained in:
punto- 2016-02-08 20:09:02 -03:00
commit b6b33e8886
11 changed files with 701 additions and 180 deletions

View file

@ -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;
}

View file

@ -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();
};

View file

@ -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();

View file

@ -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;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
button = 6;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
button = 5;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
button = 7;
break;
case KeyEvent.KEYCODE_BUTTON_SELECT:
button = 10;
break;
case KeyEvent.KEYCODE_BUTTON_START:
button = 11;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
button = 8;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
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 = 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);

View file

@ -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();
}
}
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -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) {

View file

@ -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();