From 1dec6e8154610699e99a479a01e26ed03ea044df Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sat, 6 Jul 2013 19:07:48 +0200 Subject: [PATCH] Work towards full android joystick support. --- android/app-android.cpp | 105 +++++++--- android/app-android.h | 30 --- .../libnative/InputDeviceState.java | 103 ++++++++++ .../libnative/NativeActivity.java | 187 +++++++++++++----- .../henrikrydgard/libnative/NativeApp.java | 10 +- base/NativeApp.h | 4 + base/PCMain.cpp | 17 +- input/input_state.cpp | 100 +--------- input/input_state.h | 49 +++-- input/keycodes.h | 36 +++- native.vcxproj | 1 + native.vcxproj.filters | 1 + ui/screen.cpp | 15 +- ui/screen.h | 4 + 14 files changed, 431 insertions(+), 231 deletions(-) create mode 100644 android/src/com/henrikrydgard/libnative/InputDeviceState.java diff --git a/android/app-android.cpp b/android/app-android.cpp index 9f1cbbf048..c1ad471908 100644 --- a/android/app-android.cpp +++ b/android/app-android.cpp @@ -39,7 +39,8 @@ static float left_joystick_x_async; static float left_joystick_y_async; static float right_joystick_x_async; static float right_joystick_y_async; -static int key_queue_async[MAX_KEYQUEUESIZE]; +static float hat_joystick_x_async; +static float hat_joystick_y_async; static uint32_t pad_buttons_down; @@ -117,10 +118,11 @@ extern "C" jboolean Java_com_henrikrydgard_libnative_NativeApp_isLandscape(JNIEn // For the Back button to work right. extern "C" jboolean Java_com_henrikrydgard_libnative_NativeApp_isAtTopLevel(JNIEnv *env, jclass) { + bool isAtTop = NativeIsAtTopLevel(); if (extraLog) { - ILOG("isAtTopLevel"); + ILOG("isAtTopLevel %i", (int)isAtTop); } - return NativeIsAtTopLevel(); + return isAtTop; } extern "C" void Java_com_henrikrydgard_libnative_NativeApp_audioConfig @@ -148,8 +150,8 @@ extern "C" void Java_com_henrikrydgard_libnative_NativeApp_init left_joystick_y_async = 0; right_joystick_x_async = 0; right_joystick_y_async = 0; - - KeyQueueBlank(key_queue_async); + hat_joystick_x_async = 0; + hat_joystick_y_async = 0; std::string apkPath = GetJavaString(env, japkpath); ILOG("NativeApp::Init: APK path: %s", apkPath.c_str()); @@ -262,8 +264,9 @@ extern "C" void Java_com_henrikrydgard_libnative_NativeRenderer_displayRender(JN lock_guard guard(input_state.lock); pad_buttons_down |= pad_buttons_async_set; pad_buttons_down &= ~pad_buttons_async_clear; + input_state.pad_buttons = pad_buttons_down; - KeyQueueCopyQueue(key_queue_async, input_state.key_queue); + UpdateInputState(&input_state); } @@ -271,6 +274,8 @@ extern "C" void Java_com_henrikrydgard_libnative_NativeRenderer_displayRender(JN lock_guard guard(input_state.lock); input_state.pad_lstick_x = left_joystick_x_async; input_state.pad_lstick_y = left_joystick_y_async; + input_state.pad_rstick_x = right_joystick_x_async; + input_state.pad_rstick_y = right_joystick_y_async; } NativeUpdate(input_state); @@ -372,49 +377,96 @@ static inline void AsyncUp(int padbutton) { pad_buttons_async_clear |= padbutton; } -extern "C" void Java_com_henrikrydgard_libnative_NativeApp_keyDown(JNIEnv *, jclass, jint key) { - KeyQueueAddKey(key_queue_async, key); +extern "C" void Java_com_henrikrydgard_libnative_NativeApp_keyDown(JNIEnv *, jclass, jint deviceId, jint key) { + KeyInput keyInput; + keyInput.deviceId = deviceId; + keyInput.keyCode = key; + keyInput.flags = KEY_DOWN; + NativeKey(keyInput); + // Pad mapping switch (key) { - case 1: AsyncDown (PAD_BUTTON_BACK); break; // Back - case 2: AsyncDown (PAD_BUTTON_MENU); break; // Menu + case KEYCODE_BACK: AsyncDown(PAD_BUTTON_BACK); break; // Back + case KEYCODE_MENU: AsyncDown(PAD_BUTTON_MENU); break; // Menu case KEYCODE_BUTTON_CROSS_PS3: AsyncDown(PAD_BUTTON_A); break; case KEYCODE_BUTTON_CIRCLE_PS3: - AsyncDown (PAD_BUTTON_B); + AsyncDown(PAD_BUTTON_B); break; default: break; } } -extern "C" void Java_com_henrikrydgard_libnative_NativeApp_keyUp(JNIEnv *, jclass, jint key) { - KeyQueueRemoveKey(key_queue_async, key); +extern "C" void Java_com_henrikrydgard_libnative_NativeApp_keyUp(JNIEnv *, jclass, jint deviceId, jint key) { + KeyInput keyInput; + keyInput.deviceId = deviceId; + keyInput.keyCode = key; + keyInput.flags = KEY_UP; + NativeKey(keyInput); + // Pad mapping switch (key) { - case 1: AsyncUp (PAD_BUTTON_BACK); break; // Back - case 2: AsyncUp (PAD_BUTTON_MENU); break; // Menu + case KEYCODE_BACK: + AsyncUp(PAD_BUTTON_BACK); + break; // Back + case KEYCODE_MENU: + AsyncUp(PAD_BUTTON_MENU); + break; // Menu case KEYCODE_BUTTON_CROSS_PS3: AsyncUp(PAD_BUTTON_A); break; case KEYCODE_BUTTON_CIRCLE_PS3: - AsyncUp (PAD_BUTTON_B); + AsyncUp(PAD_BUTTON_B); + break; + default: break; - default: break; } } -extern "C" void Java_com_henrikrydgard_libnative_NativeApp_joystickEvent( - JNIEnv *env, jclass, jint stick, jfloat x, jfloat y) { - if (stick == 0) { - left_joystick_x_async = x; - left_joystick_y_async = y; - } else if (stick == 1) { - right_joystick_x_async = x; - right_joystick_y_async = y; - } +extern "C" void Java_com_henrikrydgard_libnative_NativeApp_beginJoystickEvent( + JNIEnv *env, jclass) { + // mutex lock? } +extern "C" void Java_com_henrikrydgard_libnative_NativeApp_joystickEvent( + JNIEnv *env, jclass, jint deviceId, jint axisId, jfloat value) { + switch (axisId) { + case JOYSTICK_AXIS_X: + left_joystick_x_async = value; + break; + case JOYSTICK_AXIS_Y: + left_joystick_y_async = -value; + break; + case JOYSTICK_AXIS_Z: + right_joystick_x_async = value; + break; + case JOYSTICK_AXIS_RZ: + right_joystick_y_async = -value; + break; + case JOYSTICK_AXIS_HAT_X: + hat_joystick_x_async = value; + break; + case JOYSTICK_AXIS_HAT_Y: + hat_joystick_y_async = -value; + break; + } + + // Hat is just stupid. Force translate it to dpad events. TODO + + AxisInput axis; + axis.axisId = axisId; + axis.deviceId = deviceId; + axis.value = value; + NativeAxis(axis); +} + +extern "C" void Java_com_henrikrydgard_libnative_NativeApp_endJoystickEvent( + JNIEnv *env, jclass) { + // mutex unlock? +} + + extern "C" void Java_com_henrikrydgard_libnative_NativeApp_mouseWheelEvent( JNIEnv *env, jclass, jint stick, jfloat x, jfloat y) { // TODO @@ -437,4 +489,3 @@ extern "C" void Java_com_henrikrydgard_libnative_NativeApp_sendMessage(JNIEnv *e ILOG("Message received: %s %s", msg.c_str(), prm.c_str()); NativeMessageReceived(msg.c_str(), prm.c_str()); } - diff --git a/android/app-android.h b/android/app-android.h index fbe4d59932..bf842ac54a 100644 --- a/android/app-android.h +++ b/android/app-android.h @@ -6,33 +6,3 @@ // since native's keycodes are based on // android keycodes. typedef enum _keycode_t AndroidKeyCodes; - -enum AndroidJoystickAxis { - // Field descriptor #15 I - JOYSTICK_AXIS_X = 0, - JOYSTICK_AXIS_Y = 1, - JOYSTICK_AXIS_PRESSURE = 2, - JOYSTICK_AXIS_SIZE = 3, - JOYSTICK_AXIS_TOUCH_MAJOR = 4, - JOYSTICK_AXIS_TOUCH_MINOR = 5, - JOYSTICK_AXIS_TOOL_MAJOR = 6, - JOYSTICK_AXIS_TOOL_MINOR = 7, - JOYSTICK_AXIS_ORIENTATION = 8, - JOYSTICK_AXIS_VSCROLL = 9, - JOYSTICK_AXIS_HSCROLL = 10, - JOYSTICK_AXIS_Z = 11, - JOYSTICK_AXIS_RX = 12, - JOYSTICK_AXIS_RY = 13, - JOYSTICK_AXIS_RZ = 14, - JOYSTICK_AXIS_HAT_X = 15, - JOYSTICK_AXIS_HAT_Y = 16, - JOYSTICK_AXIS_LTRIGGER = 17, - JOYSTICK_AXIS_RTRIGGER = 18, - JOYSTICK_AXIS_THROTTLE = 19, - JOYSTICK_AXIS_RUDDER = 20, - JOYSTICK_AXIS_WHEEL = 21, - JOYSTICK_AXIS_GAS = 22, - JOYSTICK_AXIS_BRAKE = 23, - JOYSTICK_AXIS_DISTANCE = 24, - JOYSTICK_AXIS_TILT = 25, -}; diff --git a/android/src/com/henrikrydgard/libnative/InputDeviceState.java b/android/src/com/henrikrydgard/libnative/InputDeviceState.java new file mode 100644 index 0000000000..77b83560aa --- /dev/null +++ b/android/src/com/henrikrydgard/libnative/InputDeviceState.java @@ -0,0 +1,103 @@ +package com.henrikrydgard.libnative; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.SparseIntArray; +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyEvent; +import android.view.MotionEvent; + +@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) +public class InputDeviceState { + // DEVICE_ID_PAD_0 from the cpp code. TODO: allocate these sequentially if we get more controllers. + private static int deviceId = 10; + + private InputDevice mDevice; + private int[] mAxes; + + InputDevice getDevice() { return mDevice; } + + public InputDeviceState(InputDevice device) { + mDevice = device; + int numAxes = 0; + for (MotionRange range : device.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + numAxes += 1; + } + } + + mAxes = new int[numAxes]; + + int i = 0; + for (MotionRange range : device.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + mAxes[i++] = range.getAxis(); + } + } + } + + public static float ProcessAxis(InputDevice.MotionRange range, float axisvalue) { + float absaxisvalue = Math.abs(axisvalue); + float deadzone = range.getFlat(); + if (absaxisvalue <= deadzone) { + return 0.0f; + } + float normalizedvalue; + if (axisvalue < 0.0f) { + normalizedvalue = absaxisvalue / range.getMin(); + } else { + normalizedvalue = absaxisvalue / range.getMax(); + } + + return normalizedvalue; + } + + private static boolean isGameKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return true; + default: + return KeyEvent.isGamepadButton(keyCode); + } + } + + public boolean onKeyDown(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (event.getRepeatCount() == 0) { + if (isGameKey(keyCode)) { + NativeApp.keyDown(deviceId, keyCode); + return true; + } + } + return false; + } + + public boolean onKeyUp(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (isGameKey(keyCode)) { + NativeApp.keyUp(deviceId, keyCode); + return true; + } + return false; + } + + public boolean onJoystickMotion(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0) { + return false; + } + NativeApp.beginJoystickEvent(); + for (int i = 0; i < mAxes.length; i++) { + int axisId = mAxes[i]; + float value = event.getAxisValue(axisId); + // TODO: Use processAxis or move that to the C++ code + NativeApp.joystickAxis(deviceId, axisId, value); + } + NativeApp.endJoystickEvent(); + + return true; + } +} diff --git a/android/src/com/henrikrydgard/libnative/NativeActivity.java b/android/src/com/henrikrydgard/libnative/NativeActivity.java index a47fd9838d..5a21c14083 100644 --- a/android/src/com/henrikrydgard/libnative/NativeActivity.java +++ b/android/src/com/henrikrydgard/libnative/NativeActivity.java @@ -5,6 +5,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; +import java.util.List; import java.util.UUID; import android.annotation.SuppressLint; @@ -32,6 +33,7 @@ import android.util.Log; import android.view.Display; import android.view.Gravity; import android.view.InputDevice; +import android.view.InputEvent; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; @@ -275,54 +277,91 @@ public class NativeActivity extends Activity { public static boolean inputBoxCancelled; - @SuppressLint("NewApi") - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - // Eat these keys, to avoid accidental exits / other screwups. - // Maybe there's even more we need to eat on tablets? - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - if (event.isAltPressed()) { - NativeApp.keyDown(1004); // special custom keycode - } else { - NativeApp.keyDown(1); + + InputDeviceState inputPlayerA; + String inputPlayerADesc; + + // We simply grab the first input device to produce an event and ignore all others that are connected. + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + private InputDeviceState getInputDeviceState(InputEvent event) { + InputDevice device = event.getDevice(); + if (device == null) { + return null; + } + if (inputPlayerA == null) { + inputPlayerA = new InputDeviceState(device); + inputPlayerADesc = getInputDesc(device); + } + + if (inputPlayerA.getDevice() == device) { + return inputPlayerA; + } + + /* + if (inputPlayerB == null) { + inputPlyerB = new InputDeviceStats(device); + } + + if (inputPlayerB.getDevice() == device) { + return inputPlayerB; + }*/ + + return null; + } + + // We grab the keys before onKeyDown/... even see them. This is also better because it lets us + // distinguish devices. + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { + InputDeviceState state = getInputDeviceState(event); + if (state == null) { + return super.dispatchKeyEvent(event); } - return true; - case KeyEvent.KEYCODE_MENU: - NativeApp.keyDown(2); - return true; - case KeyEvent.KEYCODE_SEARCH: - NativeApp.keyDown(3); - return true; - case KeyEvent.KEYCODE_VOLUME_DOWN: - case KeyEvent.KEYCODE_VOLUME_UP: - return false; - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_DPAD_RIGHT: - // Joysticks are supported in Honeycomb MR1 and later via the onGenericMotionEvent method. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && event.getSource() == InputDevice.SOURCE_JOYSTICK) { - return false; + + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + if (state.onKeyDown(event)) { + return true; + } + break; + + case KeyEvent.ACTION_UP: + if (state.onKeyUp(event)) { + return true; + } + break; } - default: - // send the rest of the keys through. - // TODO: get rid of the three special cases above by adjusting the native side of the code. - Log.d("JAMIE", "Key code: " + keyCode + ", KeyEvent: " + event); - NativeApp.keyDown(keyCode); - return true; + } + + // Let's go through the old path (onKeyUp, onKeyDown). + return super.dispatchKeyEvent(event); + } + + @TargetApi(16) + static public String getInputDesc(InputDevice input) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return input.getDescriptor(); + } else { + List motions = input.getMotionRanges(); + String fakeid = ""; + for (InputDevice.MotionRange range : motions) + fakeid += range.getAxis(); + return fakeid; } } @Override @TargetApi(12) public boolean onGenericMotionEvent(MotionEvent event) { - Log.d("JAMIE", "onGenericMotionEvent: " + event); - if (event.getSource() == InputDevice.SOURCE_JOYSTICK) { - float leftJoystickX = event.getAxisValue(MotionEvent.AXIS_X) * 1f; - float leftJoystickY = event.getAxisValue(MotionEvent.AXIS_Y) * -1f; - NativeApp.joystickEvent(0, leftJoystickX, leftJoystickY); + Log.d(TAG, "onGenericMotionEvent: " + event); + if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { + if (Build.VERSION.SDK_INT >= 12) { + InputDeviceState state = getInputDeviceState(event); + state.onJoystickMotion(event); + } } + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_HOVER_MOVE: @@ -335,49 +374,90 @@ public class NativeActivity extends Activity { } return super.onGenericMotionEvent(event); } - + @SuppressLint("NewApi") @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { + public boolean onKeyDown(int keyCode, KeyEvent event) { // Eat these keys, to avoid accidental exits / other screwups. // Maybe there's even more we need to eat on tablets? - Log.d("JAMIE", "KeyEvent: " + event); switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (event.isAltPressed()) { - NativeApp.keyUp(1004); // special custom keycode + NativeApp.keyDown(0, 1004); // special custom keycode } else if (NativeApp.isAtTopLevel()) { - return false; + Log.i(TAG, "IsAtTopLevel returned false."); + return super.onKeyUp(keyCode, event); } else { - NativeApp.keyUp(1); + NativeApp.keyDown(0, keyCode); } return true; case KeyEvent.KEYCODE_MENU: - // Menu should be ignored from SDK 11 forwards. We send it to the app. - NativeApp.keyUp(2); - return true; case KeyEvent.KEYCODE_SEARCH: - // Search probably should also be ignored. We send it to the app. - NativeApp.keyUp(3); + NativeApp.keyDown(0, keyCode); return true; case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_VOLUME_UP: - return false; + // NativeApp should ignore these. + return super.onKeyDown(keyCode, event); + case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: // Joysticks are supported in Honeycomb MR1 and later via the onGenericMotionEvent method. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && event.getSource() == InputDevice.SOURCE_JOYSTICK) { - return false; + return super.onKeyUp(keyCode, event); } + // Fall through default: // send the rest of the keys through. // TODO: get rid of the three special cases above by adjusting the native side of the code. - NativeApp.keyUp(keyCode); + // Log.d(TAG, "Key down: " + keyCode + ", KeyEvent: " + event); + NativeApp.keyDown(0, keyCode); return true; } } + + @SuppressLint("NewApi") + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + if (event.isAltPressed()) { + NativeApp.keyUp(0, 1004); // special custom keycode + } else if (NativeApp.isAtTopLevel()) { + Log.i(TAG, "IsAtTopLevel returned false."); + return super.onKeyUp(keyCode, event); + } else { + NativeApp.keyUp(0, keyCode); + } + return true; + case KeyEvent.KEYCODE_MENU: + case KeyEvent.KEYCODE_SEARCH: + // Search probably should also be ignored. We send it to the app. + NativeApp.keyUp(0, keyCode); + return true; + case KeyEvent.KEYCODE_VOLUME_DOWN: + case KeyEvent.KEYCODE_VOLUME_UP: + return super.onKeyUp(keyCode, event); + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + // Joysticks are supported in Honeycomb MR1 and later via the onGenericMotionEvent method. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && event.getSource() == InputDevice.SOURCE_JOYSTICK) { + return super.onKeyUp(keyCode, event); + } + // Fall through + default: + // send the rest of the keys through. + // TODO: get rid of the three special cases above by adjusting the native side of the code. + // Log.d(TAG, "Key down: " + keyCode + ", KeyEvent: " + event); + NativeApp.keyUp(0, keyCode); + return true; + } + } + public String inputBox(String title, String defaultText, String defaultAction) { final FrameLayout fl = new FrameLayout(this); final EditText input = new EditText(this); @@ -428,6 +508,7 @@ public class NativeActivity extends Activity { send.setData(uri); startActivity(Intent.createChooser(send, "E-mail the app author!")); } else if (command.equals("launchMarket")) { + // Don't need this, can just use launchBrowser with a market: // http://stackoverflow.com/questions/3442366/android-link-to-market-from-inside-another-app // http://developer.android.com/guide/publishing/publishing.html#marketintent } else if (command.equals("toast")) { diff --git a/android/src/com/henrikrydgard/libnative/NativeApp.java b/android/src/com/henrikrydgard/libnative/NativeApp.java index 2612bec744..25cec55bf6 100644 --- a/android/src/com/henrikrydgard/libnative/NativeApp.java +++ b/android/src/com/henrikrydgard/libnative/NativeApp.java @@ -15,11 +15,13 @@ public class NativeApp { public static native void shutdown(); - public static native void keyDown(int key); + public static native void keyDown(int deviceId, int key); + public static native void keyUp(int deviceId, int key); - public static native void keyUp(int key); - - public static native void joystickEvent(int stick, float x, float y); + public static native void beginJoystickEvent(); + public static native void joystickAxis(int deviceId, int axis, float value); + public static native void endJoystickEvent(); + public static native void mouseWheelEvent(float x, float y); // will only be called between init() and shutdown() diff --git a/base/NativeApp.h b/base/NativeApp.h index cd807d449e..a00d9af144 100644 --- a/base/NativeApp.h +++ b/base/NativeApp.h @@ -11,6 +11,8 @@ // This is defined in input/input_state.h. struct InputState; struct TouchInput; +struct KeyInput; +struct AxisInput; // The first function to get called, just write strings to the two pointers. // This might get called multiple times in some implementations, you must be able to handle that. @@ -47,6 +49,8 @@ void NativeUpdate(InputState &input); // If you don't care about touch latency, just do a no-op implementation of this. // time is not yet implemented. finger can be from 0 to 7, inclusive. void NativeTouch(const TouchInput &touch); +void NativeKey(const KeyInput &key); +void NativeAxis(const AxisInput &axis); // Called when it's time to render. If the device can keep up, this // will also be called sixty times per second. Main thread. diff --git a/base/PCMain.cpp b/base/PCMain.cpp index d26a225163..4c10aae547 100644 --- a/base/PCMain.cpp +++ b/base/PCMain.cpp @@ -538,14 +538,17 @@ int main(int argc, char *argv[]) { if (event.key.keysym.sym == SDLK_ESCAPE) { quitRequested = 1; } - - KeyQueueAttemptTranslatedAdd(input_state.key_queue, - KeyMapRawSDLtoNative, - event.key.keysym.sym); + KeyInput key; + key.flags = KEY_DOWN; + key.keyCode = KeyMapRawSDLtoNative[event.key.keysym.sym]; + key.deviceId = 0; + NativeKey(key); } else if (event.type == SDL_KEYUP) { - KeyQueueAttemptTranslatedRemove(input_state.key_queue, - KeyMapRawSDLtoNative, - event.key.keysym.sym); + KeyInput key; + key.flags = KEY_UP; + key.keyCode = KeyMapRawSDLtoNative[event.key.keysym.sym]; + key.deviceId = 0; + NativeKey(key); } else if (event.type == SDL_MOUSEBUTTONDOWN) { if (event.button.button == SDL_BUTTON_LEFT) { input_state.pointer_x[0] = mx; diff --git a/input/input_state.cpp b/input/input_state.cpp index 949828bc08..51a262c88d 100644 --- a/input/input_state.cpp +++ b/input/input_state.cpp @@ -1,96 +1,14 @@ #include "input_state.h" #include -#define max_queue MAX_KEYQUEUESIZE - -void MakeRoomInQueueFront(int queue[]) -{ - // shift queue elements from front towards back - for (int i = max_queue - 1; i > 0; i--) { - queue[i] = queue[i - 1]; +const char *GetDeviceName(int deviceId) { + switch (deviceId) { + case DEVICE_ID_DEFAULT: return "built-in"; + case DEVICE_ID_KEYBOARD: return "kbd"; + case DEVICE_ID_PAD_0: return "pad"; + case DEVICE_ID_X360_0: return "x360"; + case DEVICE_ID_ACCELEROMETER: return "accelerometer"; + default: + return "unknown"; } - queue[0] = 0; -} - -void FillGap(int queue[], int gap_loc) -{ - // shift elements from back towards front - // until gap location is filled - for (int i = gap_loc; i < max_queue; i++) { - queue[i] = queue[i + 1]; - } - queue[max_queue - 1] = 0; -} - -void KeyQueueAddKey(int queue[], int key) -{ - for (int i = 0; i < max_queue; i++) { - if (queue[i] == key) - return; - if (queue[i] == 0) { - queue[i] = key; - return; - } - } - - // queue was full - MakeRoomInQueueFront(queue); - queue[0] = key; -} - -void KeyQueueRemoveKey(int queue[], int key) -{ - for (int i = 0; i < max_queue; i++) - if (queue[i] == key) - FillGap(queue, i); -} - -void KeyQueueCopyQueue(int src[], int dst[]) -{ - for (int i = 0; i < max_queue; i++) - dst[i] = src[i]; -} -void KeyQueueBlank(int queue[]) -{ - for (int i = 0; i < max_queue; i++) - queue[i] = 0; -} - -int AttemptTranslate(const std::map trans_table, int plat_key) -{ - std::map::const_iterator iter; - iter = trans_table.find(plat_key); - if (iter == trans_table.end()) - return 0; - - return iter->second; -} - - -void KeyQueueAttemptTranslatedAdd(int queue[], - const std::map translation_table, - int platform_key) -{ - int key = AttemptTranslate(translation_table, platform_key); - - if (key == 0) { - std::cerr << "Warning: Platform key code translation table missing " - << "(" << platform_key << ")\n"; - return; - } - - KeyQueueAddKey(queue, key); -} - - -void KeyQueueAttemptTranslatedRemove(int queue[], - const std::map translation_table, - int platform_key) -{ - int key = AttemptTranslate(translation_table, platform_key); - - if (key == 0) - return; - - KeyQueueRemoveKey(queue, key); } diff --git a/input/input_state.h b/input/input_state.h index 1bb054bf2a..e9e4c84bc7 100644 --- a/input/input_state.h +++ b/input/input_state.h @@ -1,10 +1,30 @@ #pragma once +// InputState is the simple way of getting input. All the input we have is collected +// to a canonical Xbox360-style pad fully automatically. +// +// Recommended for use in game UIs and games that don't have advanced needs. +// +// For more detailed and configurable input, implement NativeTouch, NativeKey and NativeAxis and do your +// own mapping. Might later move the mapping system from PPSSPP to native. + #include "math/lin/vec3.h" #include "base/mutex.h" #include "base/basictypes.h" #include +// Default device IDs + +enum { + DEVICE_ID_DEFAULT = 0, // Old Android + DEVICE_ID_KEYBOARD = 1, // PC keyboard, android keyboards + DEVICE_ID_PAD_0 = 10, // Generic joypads + DEVICE_ID_X360_0 = 20, // XInput joypads + DEVICE_ID_ACCELEROMETER = 30, +}; + +const char *GetDeviceName(int deviceId); + enum { PAD_BUTTON_A = 1, PAD_BUTTON_B = 2, @@ -44,7 +64,7 @@ enum { #ifndef MAX_KEYQUEUESIZE #define MAX_KEYQUEUESIZE 20 #endif - + // Collection of all possible inputs, and automatically computed // deltas where applicable. struct InputState { @@ -60,7 +80,7 @@ struct InputState { memset(pointer_down, 0, sizeof(pointer_down)); } - // Gamepad style input + // Gamepad style input. For ease of use. int pad_buttons; // bitfield int pad_last_buttons; int pad_buttons_down; // buttons just pressed this frame @@ -84,8 +104,6 @@ struct InputState { bool accelerometer_valid; Vec3 acc; - int key_queue[MAX_KEYQUEUESIZE]; - private: DISALLOW_COPY_AND_ASSIGN(InputState); }; @@ -118,11 +136,20 @@ struct TouchInput { double timestamp; }; +enum { + KEY_DOWN = 1, + KEY_UP = 2, +}; -// Key Queue Helpers -void KeyQueueAddKey(int [], int); -void KeyQueueRemoveKey(int [], int); -void KeyQueueCopyQueue(int src[], int dst[]); -void KeyQueueBlank(int []); -void KeyQueueAttemptTranslatedAdd(int [], const std::map, int); -void KeyQueueAttemptTranslatedRemove(int [], const std::map, int); +struct KeyInput { + int deviceId; + int keyCode; // Android keycodes are the canonical keycodes, everyone else map to them. + int flags; +}; + +struct AxisInput { + int deviceId; + int axisId; // Android axis Ids are the canonical ones. + float value; + int flags; +}; diff --git a/input/keycodes.h b/input/keycodes.h index b8ec82b9ec..1bf90a8814 100644 --- a/input/keycodes.h +++ b/input/keycodes.h @@ -216,12 +216,8 @@ typedef enum _keycode_t { // Not native to android // Xbox 360 controller - KEYCODE_XBOX_A = 5001, - KEYCODE_XBOX_B = 5002, - KEYCODE_XBOX_X = 5003, - KEYCODE_XBOX_Y = 5004, KEYCODE_XBOX_START = 5005, - KEYCODE_XBOX_SELECT = 5006, + KEYCODE_XBOX_BACK = 5006, KEYCODE_XBOX_BUMPER_L = 5007, KEYCODE_XBOX_BUMPER_R = 5008, KEYCODE_XBOX_DPAD_UP = 5009, @@ -234,3 +230,33 @@ typedef enum _keycode_t { // KEYCODE_XBOX_HOME = NEWKEY, // this can't be read on PC, right? } keycode_t; + +enum AndroidJoystickAxis { + // Field descriptor #15 I + JOYSTICK_AXIS_X = 0, + JOYSTICK_AXIS_Y = 1, + JOYSTICK_AXIS_PRESSURE = 2, + JOYSTICK_AXIS_SIZE = 3, + JOYSTICK_AXIS_TOUCH_MAJOR = 4, + JOYSTICK_AXIS_TOUCH_MINOR = 5, + JOYSTICK_AXIS_TOOL_MAJOR = 6, + JOYSTICK_AXIS_TOOL_MINOR = 7, + JOYSTICK_AXIS_ORIENTATION = 8, + JOYSTICK_AXIS_VSCROLL = 9, + JOYSTICK_AXIS_HSCROLL = 10, + JOYSTICK_AXIS_Z = 11, + JOYSTICK_AXIS_RX = 12, + JOYSTICK_AXIS_RY = 13, + JOYSTICK_AXIS_RZ = 14, + JOYSTICK_AXIS_HAT_X = 15, + JOYSTICK_AXIS_HAT_Y = 16, + JOYSTICK_AXIS_LTRIGGER = 17, + JOYSTICK_AXIS_RTRIGGER = 18, + JOYSTICK_AXIS_THROTTLE = 19, + JOYSTICK_AXIS_RUDDER = 20, + JOYSTICK_AXIS_WHEEL = 21, + JOYSTICK_AXIS_GAS = 22, + JOYSTICK_AXIS_BRAKE = 23, + JOYSTICK_AXIS_DISTANCE = 24, + JOYSTICK_AXIS_TILT = 25, +}; diff --git a/native.vcxproj b/native.vcxproj index 3980989fea..0883830a00 100644 --- a/native.vcxproj +++ b/native.vcxproj @@ -230,6 +230,7 @@ + diff --git a/native.vcxproj.filters b/native.vcxproj.filters index dabb7db32e..ed978de906 100644 --- a/native.vcxproj.filters +++ b/native.vcxproj.filters @@ -278,6 +278,7 @@ math + diff --git a/ui/screen.cpp b/ui/screen.cpp index 12b9da6118..a79128d9ce 100644 --- a/ui/screen.cpp +++ b/ui/screen.cpp @@ -54,12 +54,21 @@ void ScreenManager::switchToNext() { } void ScreenManager::touch(const TouchInput &touch) { - if (!stack_.empty()) { + if (!stack_.empty()) stack_.back().screen->touch(touch); - return; - } } +void ScreenManager::key(const KeyInput &key) { + if (!stack_.empty()) + stack_.back().screen->key(key); +} + +void ScreenManager::axis(const AxisInput &axis) { + if (!stack_.empty()) + stack_.back().screen->axis(axis); +} + + void ScreenManager::render() { if (!stack_.empty()) { switch (stack_.back().flags) { diff --git a/ui/screen.h b/ui/screen.h index 80c8d36650..4c2b2af5cc 100644 --- a/ui/screen.h +++ b/ui/screen.h @@ -44,6 +44,8 @@ public: virtual void deviceLost() {} virtual void dialogFinished(const Screen *dialog, DialogResult result) {} virtual void touch(const TouchInput &touch) {} + virtual void key(const KeyInput &key) {} + virtual void axis(const AxisInput &touch) {} virtual void sendMessage(const char *msg, const char *value) {} ScreenManager *screenManager() { return screenManager_; } @@ -91,6 +93,8 @@ public: // Instant touch, separate from the update() mechanism. void touch(const TouchInput &touch); + void key(const KeyInput &key); + void axis(const AxisInput &touch); // Generic facility for gross hacks :P void sendMessage(const char *msg, const char *value);