Merge pull request #3536 from davilla/joystick
authordavilla <davilla@xbmc.org>
Sat, 2 Nov 2013 16:59:55 +0000 (09:59 -0700)
committerdavilla <davilla@xbmc.org>
Sat, 2 Nov 2013 16:59:55 +0000 (09:59 -0700)
Android Joystick Handling

19 files changed:
xbmc/Application.cpp
xbmc/Application.h
xbmc/android/activity/AndroidExtra.h
xbmc/android/activity/AndroidJoyStick.cpp [new file with mode: 0644]
xbmc/android/activity/AndroidJoyStick.h [new file with mode: 0644]
xbmc/android/activity/AndroidKey.cpp
xbmc/android/activity/AndroidKey.h
xbmc/android/activity/AndroidMouse.h
xbmc/android/activity/AndroidTouch.h
xbmc/android/activity/EventLoop.cpp
xbmc/android/activity/IInputHandler.h
xbmc/android/activity/Makefile.in
xbmc/android/jni/List.cpp
xbmc/android/jni/View.cpp
xbmc/android/jni/View.h
xbmc/windowing/XBMC_events.h
xbmc/windowing/android/WinEventsAndroid.cpp
xbmc/windowing/android/WinEventsAndroid.h
xbmc/windowing/osx/WinEventsIOS.mm

index e1465df..955df80 100644 (file)
@@ -3094,7 +3094,7 @@ bool CApplication::ProcessEventServer(float frameTime)
           m_lastAxisMap[joystickName].erase(wKeyID);
       }
 
-      return ProcessJoystickEvent(joystickName, wKeyID, isAxis, fAmount);
+      return ProcessJoystickEvent(joystickName, wKeyID, isAxis ? JACTIVE_AXIS : JACTIVE_BUTTON, fAmount);
     }
     else
     {
@@ -3138,7 +3138,7 @@ bool CApplication::ProcessEventServer(float frameTime)
     for (map<std::string, map<int, float> >::iterator iter = m_lastAxisMap.begin(); iter != m_lastAxisMap.end(); ++iter)
     {
       for (map<int, float>::iterator iterAxis = (*iter).second.begin(); iterAxis != (*iter).second.end(); ++iterAxis)
-        ProcessJoystickEvent((*iter).first, (*iterAxis).first, true, (*iterAxis).second);
+        ProcessJoystickEvent((*iter).first, (*iterAxis).first, JACTIVE_AXIS, (*iterAxis).second);
     }
   }
 
@@ -3162,7 +3162,7 @@ bool CApplication::ProcessEventServer(float frameTime)
   return false;
 }
 
-bool CApplication::ProcessJoystickEvent(const std::string& joystickName, int wKeyID, bool isAxis, float fAmount, unsigned int holdTime /*=0*/)
+bool CApplication::ProcessJoystickEvent(const std::string& joystickName, int wKeyID, short inputType, float fAmount, unsigned int holdTime /*=0*/)
 {
 #if defined(HAS_EVENT_SERVER)
   m_idleTimer.StartZero();
@@ -3183,7 +3183,7 @@ bool CApplication::ProcessJoystickEvent(const std::string& joystickName, int wKe
    bool fullRange = false;
 
    // Translate using regular joystick translator.
-   if (CButtonTranslator::GetInstance().TranslateJoystickString(iWin, joystickName.c_str(), wKeyID, isAxis ? JACTIVE_AXIS : JACTIVE_BUTTON, actionID, actionName, fullRange))
+   if (CButtonTranslator::GetInstance().TranslateJoystickString(iWin, joystickName.c_str(), wKeyID, inputType, actionID, actionName, fullRange))
      return ExecuteInputAction( CAction(actionID, fAmount, 0.0f, actionName, holdTime) );
    else
      CLog::Log(LOGDEBUG, "ERROR mapping joystick action. Joystick: %s %i",joystickName.c_str(), wKeyID);
index 37eae3f..f90446c 100644 (file)
@@ -373,6 +373,9 @@ protected:
 #if defined(TARGET_DARWIN_IOS)
   friend class CWinEventsIOS;
 #endif
+#if defined(TARGET_ANDROID)
+  friend class CWinEventsAndroid;
+#endif
   // screensaver
   bool m_bScreenSave;
   ADDON::AddonPtr m_screenSaver;
@@ -442,7 +445,7 @@ protected:
   bool ProcessGamepad(float frameTime);
   bool ProcessEventServer(float frameTime);
   bool ProcessPeripherals(float frameTime);
-  bool ProcessJoystickEvent(const std::string& joystickName, int button, bool isAxis, float fAmount, unsigned int holdTime = 0);
+  bool ProcessJoystickEvent(const std::string& joystickName, int button, short inputType, float fAmount, unsigned int holdTime = 0);
   bool ExecuteInputAction(const CAction &action);
   int  GetActiveWindowID(void);
 
index 55c2a18..178fb07 100644 (file)
 #define AKEYCODE_FORWARD 125
 #define AKEYCODE_MEDIA_PLAY 126
 #define AKEYCODE_MEDIA_EJECT 129
+
+#define AINPUT_SOURCE_CLASS_JOYSTICK 0x00000010
+
+#define AINPUT_SOURCE_GAMEPAD  (0x00000400 | AINPUT_SOURCE_CLASS_BUTTON)
+#define AINPUT_SOURCE_JOYSTICK (0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK)
+
+// 1st stick X, Y
+#define AMOTION_EVENT_AXIS_X 0
+#define AMOTION_EVENT_AXIS_Y 1
+// 2nd stick X, Y
+#define AMOTION_EVENT_AXIS_Z  11
+#define AMOTION_EVENT_AXIS_RZ 14
+// d-pad X, Y
+#define AMOTION_EVENT_AXIS_HAT_X 15
+#define AMOTION_EVENT_AXIS_HAT_Y 16
+// trigger left, right
+#define AMOTION_EVENT_AXIS_LTRIGGER 17
+#define AMOTION_EVENT_AXIS_RTRIGGER 18
diff --git a/xbmc/android/activity/AndroidJoyStick.cpp b/xbmc/android/activity/AndroidJoyStick.cpp
new file mode 100644 (file)
index 0000000..ba9efcf
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ *      Copyright (C) 2012-2013 Team XBMC
+ *      http://xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "AndroidJoyStick.h"
+#include "AndroidExtra.h"
+#include "XBMCApp.h"
+#include "android/jni/View.h"
+#include "android/activity/AndroidFeatures.h"
+#include "utils/log.h"
+#include "windowing/WinEvents.h"
+#include "windowing/XBMC_events.h"
+#include "utils/TimeUtils.h"
+
+#include <android/input.h>
+
+
+#include <math.h>
+#include <dlfcn.h>
+
+//#define DEBUG_VERBOSE
+
+// mapping to axis IDs codes in keymaps.xmls
+enum {
+  AXIS_LEFT_STICK_L_R  = 1,
+  AXIS_LEFT_STICK_U_D  = 2,
+  AXIS_TRIGGER         = 3,
+  AXIS_RIGHT_STICK_L_R = 4,
+  AXIS_RIGHT_STICK_U_D = 5,
+};
+
+typedef struct {
+  int32_t nativeKey;
+  int16_t xbmcID;
+} KeyMap;
+
+// mapping to button codes in keymaps.xmls
+static const KeyMap ButtonMap[] = {
+  { AKEYCODE_BUTTON_A        , 1 },
+  { AKEYCODE_BUTTON_B        , 2 },
+  { AKEYCODE_BUTTON_X        , 3 },
+  { AKEYCODE_BUTTON_Y        , 4 },
+  { AKEYCODE_BUTTON_L1       , 5 },
+  { AKEYCODE_BUTTON_R1       , 6 },
+  { AKEYCODE_BUTTON_SELECT   , 7 },
+  { AKEYCODE_BUTTON_START    , 8 },
+  { AKEYCODE_BUTTON_THUMBL   , 9 },
+  { AKEYCODE_BUTTON_THUMBR   , 10 },
+  { AKEYCODE_DPAD_UP         , 11 },
+  { AKEYCODE_DPAD_DOWN       , 12 },
+  { AKEYCODE_DPAD_LEFT       , 13 },
+  { AKEYCODE_DPAD_RIGHT      , 14 },
+  { AKEYCODE_BUTTON_L2       , 16 },
+  { AKEYCODE_BUTTON_R2       , 17 },
+  { AKEYCODE_BUTTON_C        , 51 },
+  { AKEYCODE_BUTTON_Z        , 52 },
+};
+
+// missing in early NDKs, is present in r9b+
+extern float AMotionEvent_getAxisValue(const AInputEvent* motion_event, int32_t axis, size_t pointer_index);
+static typeof(AMotionEvent_getAxisValue) *p_AMotionEvent_getAxisValue;
+#define AMotionEvent_getAxisValue (*p_AMotionEvent_getAxisValue)
+
+/************************************************************************/
+/************************************************************************/
+static float AxisClampAsButton(const APP_InputDeviceAxis &axis, float value)
+{
+  // Clamp Axis so it acts like a D-Pad, return -1, 0 or +1
+  if (fabs(value) < axis.buttonclamp)
+    return 0.0;
+  else
+    return value < 0.0 ? -1.0:1.0;
+}
+
+static void LogAxisValues(int axis_id, const APP_InputDeviceAxis &axis)
+{
+  CLog::Log(LOGDEBUG, "LogAxisValues: "
+    "axis(%d) Enabled(%d) Max(%f) Min(%f) Range(%f) Flat(%f) Fuzz(%f)",
+    axis_id, axis.enabled, axis.max, axis.min, axis.range, axis.flat, axis.fuzz);
+}
+
+static void SetAxisFromValues(const float min, const float max,
+  const float flat, const float fuzz, const float range, APP_InputDeviceAxis &axis)
+{
+  axis.min  = min;
+  axis.max  = max;
+  axis.flat = flat;
+  axis.fuzz = fuzz;
+  axis.range= range;
+  // precalc some internals
+  axis.deadzone= axis.flat + axis.fuzz;
+  if (axis.deadzone < 0.1f)
+    axis.deadzone = 0.1f;
+  axis.buttonclamp = axis.range / 4.0f;
+}
+
+static void SetupAxis(const CJNIViewInputDevice &input_device, APP_InputDeviceAxis &axis, int axis_id, int source)
+{
+  CJNIViewInputDeviceMotionRange range = input_device.getMotionRange(axis_id, source);
+
+  SetAxisFromValues(range.getMin(), range.getMax(), range.getFlat(), range.getFuzz(), range.getRange(), axis);
+  axis.enabled = true;
+}
+
+static void SetupJoySticks(APP_InputDeviceAxes *axes, int device)
+{
+  axes->id = device;
+  memset(&axes->x_hat,  0x00, sizeof(APP_InputDeviceAxis));
+  memset(&axes->y_hat,  0x00, sizeof(APP_InputDeviceAxis));
+  memset(&axes->x_axis, 0x00, sizeof(APP_InputDeviceAxis));
+  memset(&axes->y_axis, 0x00, sizeof(APP_InputDeviceAxis));
+  memset(&axes->z_axis, 0x00, sizeof(APP_InputDeviceAxis));
+  memset(&axes->rz_axis,0x00, sizeof(APP_InputDeviceAxis));
+
+  CJNIViewInputDevice  input_device = CJNIViewInputDevice::getDevice(axes->id);
+  int device_sources = input_device.getSources();
+  std::string device_name = input_device.getName();
+
+  CLog::Log(LOGDEBUG, "SetupJoySticks:caching  id(%d), sources(%d), device(%s)",
+    axes->id, device_sources, device_name.c_str());
+
+  CJNIList<CJNIViewInputDeviceMotionRange> device_ranges = input_device.getMotionRanges();
+  for (int i = 0; i < device_ranges.size(); i++)
+  {
+    int axis = device_ranges.get(i).getAxis();
+    int source = device_ranges.get(i).getSource();
+#ifdef DEBUG_VERBOSE
+    CLog::Log(LOGDEBUG, "SetupJoySticks:range(%d), axis(%d), source(%d)", i, axis, source);
+#endif
+
+    // ignore anything we do not understand
+    if (source != AINPUT_SOURCE_JOYSTICK)
+      continue;
+
+    // match axis/source to our handlers
+    // anything that is not present, will be disabled
+    switch(axis)
+    {
+      // Left joystick
+      case AMOTION_EVENT_AXIS_X:
+        SetupAxis(input_device, axes->x_axis,  axis, source);
+        break;
+      break;
+      case AMOTION_EVENT_AXIS_Y:
+        SetupAxis(input_device, axes->y_axis,  axis, source);
+        break;
+
+      // Right joystick
+      case AMOTION_EVENT_AXIS_Z:
+        SetupAxis(input_device, axes->z_axis,  axis, source);
+        break;
+      case AMOTION_EVENT_AXIS_RZ:
+        SetupAxis(input_device, axes->rz_axis, axis, source);
+        break;
+
+      // D-Pad
+      case AMOTION_EVENT_AXIS_HAT_X:
+        SetupAxis(input_device, axes->x_hat,   axis, source);
+        break;
+      case AMOTION_EVENT_AXIS_HAT_Y:
+        SetupAxis(input_device, axes->y_hat,   axis, source);
+      break;
+    }
+  }
+
+  if (device_name.find("GameStick Controller") != std::string::npos)
+  {
+    // Right joystick seems to have a range of -0.5 to 0.5, fix the range
+    // Production GameStick Controllers should not have this problem
+    // and this quirk can vanish once verified.
+    SetAxisFromValues(-0.5f, 0.5f, 0.1f, 0.0f, 1.0f, axes->z_axis);
+    SetAxisFromValues(-0.5f, 0.5f, 0.1f, 0.0f, 1.0f, axes->rz_axis);
+  }
+
+#ifdef DEBUG_VERBOSE
+  LogAxisValues(AMOTION_EVENT_AXIS_X,     axes->x_axis);
+  LogAxisValues(AMOTION_EVENT_AXIS_Y,     axes->y_axis);
+  LogAxisValues(AMOTION_EVENT_AXIS_Z,     axes->z_axis);
+  LogAxisValues(AMOTION_EVENT_AXIS_RZ,    axes->rz_axis);
+  LogAxisValues(AMOTION_EVENT_AXIS_HAT_X, axes->x_hat);
+  LogAxisValues(AMOTION_EVENT_AXIS_HAT_Y, axes->y_hat);
+#endif
+}
+
+/************************************************************************/
+/************************************************************************/
+CAndroidJoyStick::CAndroidJoyStick()
+  : m_prev_device(0)
+  , m_prev_button(0)
+  , m_prev_holdtime(0)
+{
+  p_AMotionEvent_getAxisValue = (typeof(AMotionEvent_getAxisValue)*) dlsym(RTLD_DEFAULT, "AMotionEvent_getAxisValue");
+  CXBMCApp::android_printf("CAndroidJoystick: AMotionEvent_getAxisValue: %p", p_AMotionEvent_getAxisValue);
+}
+
+CAndroidJoyStick::~CAndroidJoyStick()
+{
+  while (!m_input_devices.empty())
+  {
+    APP_InputDeviceAxes *device_axes = m_input_devices.back();
+    delete device_axes;
+    m_input_devices.pop_back();
+  }
+}
+
+bool CAndroidJoyStick::onJoyStickKeyEvent(AInputEvent *event)
+{
+  if (event == NULL)
+    return false;
+
+  int32_t keycode = AKeyEvent_getKeyCode(event);
+  // watch this check, others might be different.
+  // AML IR Controller is       AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD
+  // Gamestick Controller    == AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_KEYBOARD
+  // NVidiaShield Controller == AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_KEYBOARD
+  // we want to reject AML IR Controller.
+  if (AInputEvent_getSource(event) == (AINPUT_SOURCE_GAMEPAD | AINPUT_SOURCE_KEYBOARD))
+  {
+    // GamePad events are AINPUT_EVENT_TYPE_KEY events,
+    // trap them here and revector valid ones as JoyButtons
+    // so we get keymap handling.
+    for (size_t i = 0; i < sizeof(ButtonMap) / sizeof(KeyMap); i++)
+    {
+      if (keycode == ButtonMap[i].nativeKey)
+      {
+        uint32_t holdtime = 0;
+        uint8_t  button = ButtonMap[i].xbmcID;
+        int32_t  action = AKeyEvent_getAction(event);
+        int32_t  device = AInputEvent_getDeviceId(event);
+
+        if ((action == AKEY_EVENT_ACTION_UP))
+        {
+          // ProcessJoystickEvent does not understand up, ignore it.
+          m_prev_holdtime = m_prev_device = m_prev_button = 0;
+          return false;
+        }
+        else
+        {
+          if (m_prev_holdtime && device == m_prev_device && button == m_prev_button)
+          {
+            holdtime = CTimeUtils::GetFrameTime() - m_prev_holdtime;
+          }
+          else
+          {
+            m_prev_holdtime = CTimeUtils::GetFrameTime();
+            m_prev_device = device;
+            m_prev_button = button;
+          }
+        }
+
+        XBMC_JoyButton(device, button, holdtime, action == AKEY_EVENT_ACTION_UP);
+        return true;
+      }
+    }
+  }
+
+  return false;
+}
+
+bool CAndroidJoyStick::onJoyStickMotionEvent(AInputEvent *event)
+{
+  if (event == NULL)
+    return false;
+
+  // match this device to a created device struct,
+  // create it if we do not find it.
+  APP_InputDeviceAxes *device_axes = NULL;
+  int32_t device = AInputEvent_getDeviceId(event);
+  // look for device name in our inputdevice cache.
+  for (size_t i = 0; i < m_input_devices.size(); i++)
+  {
+    if (m_input_devices[i]->id == device)
+      device_axes = m_input_devices[i];
+  }
+  if (!device_axes)
+  {
+    // as we see each axis, create a device axes and cache it.
+    device_axes = new APP_InputDeviceAxes;
+    SetupJoySticks(device_axes, device);
+    m_input_devices.push_back(device_axes);
+  }
+
+  // handle queued motion events, we
+  // ingnore history as it only relates to touch.
+  for (size_t p = 0; p < AMotionEvent_getPointerCount(event); p++)
+    ProcessMotionEvents(event, p, device, device_axes);
+
+  return true;
+}
+
+void CAndroidJoyStick::ProcessMotionEvents(AInputEvent *event,
+  size_t pointer_index, int32_t device, APP_InputDeviceAxes *axes)
+{
+  // Left joystick
+  if (axes->y_axis.enabled)
+    ProcessAxis(event, pointer_index, axes->y_axis, device, AXIS_LEFT_STICK_L_R, AMOTION_EVENT_AXIS_Y);
+  if (axes->x_axis.enabled)
+    ProcessAxis(event, pointer_index, axes->x_axis, device, AXIS_LEFT_STICK_U_D, AMOTION_EVENT_AXIS_X);
+
+  // Right joystick
+  if (axes->z_axis.enabled)
+    ProcessAxis(event, pointer_index, axes->z_axis, device, AXIS_RIGHT_STICK_L_R, AMOTION_EVENT_AXIS_Z);
+  if (axes->rz_axis.enabled)
+    ProcessAxis(event, pointer_index, axes->rz_axis,device, AXIS_RIGHT_STICK_U_D, AMOTION_EVENT_AXIS_RZ);
+
+  // Dpad
+  if (axes->y_hat.enabled)
+    ProcessHat(event, pointer_index,  axes->y_hat,  device, AMOTION_EVENT_AXIS_HAT_Y);
+  if (axes->x_hat.enabled)
+    ProcessHat(event, pointer_index,  axes->x_hat,  device, AMOTION_EVENT_AXIS_HAT_X);
+
+#ifdef DEBUG_VERBOSE
+  CLog::Log(LOGDEBUG, "joystick event. x(%f),  y(%f)", axes->x_axis.value, axes->y_axis.value);
+  CLog::Log(LOGDEBUG, "joystick event. z(%f), rz(%f)", axes->z_axis.value, axes->rz_axis.value);
+  CLog::Log(LOGDEBUG, "joystick event. xhat(%f), yhat(%f)", axes->x_hat.value, axes->y_hat.value);
+#endif
+}
+
+bool CAndroidJoyStick::ProcessHat(AInputEvent *event, size_t pointer_index,
+  APP_InputDeviceAxis &hat, int device, int android_axis)
+{
+  bool rtn = false;
+  // Dpad (quantized to -1.0, 0.0 and 1.0)
+  float value = AMotionEvent_getAxisValue(event, android_axis, pointer_index);
+  if (value != hat.value)
+  {
+    u_int8_t hatvalue = XBMC_HAT_CENTERED;
+    if (value != 0)
+      switch (android_axis)
+      {
+        case AMOTION_EVENT_AXIS_HAT_X:
+          if (value < 0)
+            hatvalue |= XBMC_HAT_LEFT;
+          else
+            hatvalue |= XBMC_HAT_RIGHT;
+          break;
+
+        case AMOTION_EVENT_AXIS_HAT_Y:
+          if (value < 0)
+            hatvalue |= XBMC_HAT_UP;
+          else
+            hatvalue |= XBMC_HAT_DOWN;
+          break;
+      }
+
+    XBMC_JoyHat(device, hatvalue);
+    rtn = true;
+  }
+  hat.value = value;
+
+  return rtn;
+}
+
+bool CAndroidJoyStick::ProcessAxis(AInputEvent *event, size_t pointer_index,
+  APP_InputDeviceAxis &axis, int device, int keymap_axis, int android_axis)
+{
+  bool rtn = false;
+
+  float value = AMotionEvent_getAxisValue(event, android_axis, pointer_index);
+  //CLog::Log(LOGDEBUG, "ProcessAxis: keymap_axis(%d), value(%f)", keymap_axis, value);
+
+  value = AxisClampAsButton(axis, value);
+  if (value != axis.value)
+  {
+    XBMC_JoyAxis(device, keymap_axis, value);
+    rtn = true;
+  }
+  axis.value = value;
+
+  return rtn;
+}
+
+void CAndroidJoyStick::XBMC_JoyAxis(uint8_t device, uint8_t axis, float value)
+{
+  XBMC_Event newEvent = {};
+
+  newEvent.type       = XBMC_JOYAXISMOTION;
+  newEvent.jaxis.type = XBMC_JOYAXISMOTION;
+  newEvent.jaxis.which  = device;
+  newEvent.jaxis.axis   = axis;
+  newEvent.jaxis.fvalue = value;
+
+#ifdef DEBUG_VERBOSE
+  CLog::Log(LOGDEBUG, "XBMC_Axis(%u, %u, %u, %f)", newEvent.type, device, axis, value);
+#endif
+  CWinEvents::MessagePush(&newEvent);
+}
+
+void CAndroidJoyStick::XBMC_JoyHat(uint8_t device, uint8_t value)
+{
+  XBMC_Event newEvent = {};
+
+  newEvent.type       = XBMC_JOYHATMOTION;
+  newEvent.jhat.type = XBMC_JOYHATMOTION;
+  newEvent.jhat.which  = device;
+  newEvent.jhat.hat   = 1;
+  newEvent.jhat.value = value;
+
+#ifdef DEBUG_VERBOSE
+  CLog::Log(LOGDEBUG, "XBMC_Hat(%u, %u, %u)", newEvent.type, device, value);
+#endif
+  CWinEvents::MessagePush(&newEvent);
+}
+
+void CAndroidJoyStick::XBMC_JoyButton(uint8_t device, uint8_t button, uint32_t holdtime, bool up)
+{
+  XBMC_Event newEvent = {};
+
+  unsigned char type = up ? XBMC_JOYBUTTONUP : XBMC_JOYBUTTONDOWN;
+  newEvent.type = type;
+  newEvent.jbutton.type = type;
+  newEvent.jbutton.which  = device;
+  newEvent.jbutton.button = button;
+  newEvent.jbutton.holdTime = holdtime;
+
+#ifdef DEBUG_VERBOSE
+  CXBMCApp::android_printf("CAndroidJoyStick::XBMC_JoyButton(%u, %u, %u, %d)",
+    newEvent.jbutton.type, newEvent.jbutton.which, newEvent.jbutton.button, newEvent.jbutton.holdTime);
+#endif
+
+  CWinEvents::MessagePush(&newEvent);
+}
diff --git a/xbmc/android/activity/AndroidJoyStick.h b/xbmc/android/activity/AndroidJoyStick.h
new file mode 100644 (file)
index 0000000..722c977
--- /dev/null
@@ -0,0 +1,75 @@
+#pragma once
+/*
+ *      Copyright (C) 2012-2013 Team XBMC
+ *      http://xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string>
+#include <vector>
+
+struct AInputEvent;
+
+typedef struct {
+  float     flat;
+  float     fuzz;
+  float     min;
+  float     max;
+  float     range;
+  float     value;
+  // internal helper values
+  bool      enabled;
+  float     deadzone;
+  float     buttonclamp;
+} APP_InputDeviceAxis;
+
+typedef struct {
+  int32_t id;
+  APP_InputDeviceAxis x_hat;
+  APP_InputDeviceAxis y_hat;
+  APP_InputDeviceAxis x_axis;
+  APP_InputDeviceAxis y_axis;
+  APP_InputDeviceAxis z_axis;
+  APP_InputDeviceAxis rz_axis;
+} APP_InputDeviceAxes;
+
+class CAndroidJoyStick
+{
+public:
+  CAndroidJoyStick();
+ ~CAndroidJoyStick();
+
+  bool onJoyStickKeyEvent(AInputEvent* event);
+  bool onJoyStickMotionEvent(AInputEvent* event);
+
+private:
+  void  ProcessMotionEvents(AInputEvent *event, size_t pointer_index,
+          int32_t device, APP_InputDeviceAxes *axes);
+  bool  ProcessHat( AInputEvent *event, size_t pointer_index,
+          APP_InputDeviceAxis &hat, int device, int android_axis);
+  bool  ProcessAxis(AInputEvent *event, size_t pointer_index,
+          APP_InputDeviceAxis &axis, int device, int keymap_axis, int android_axis);
+
+  void  XBMC_JoyAxis(uint8_t device, uint8_t axis, float value);
+  void  XBMC_JoyHat(uint8_t device, uint8_t value);
+  void  XBMC_JoyButton(uint8_t device, uint8_t button, uint32_t holdtime, bool up);
+
+  uint8_t               m_prev_device;
+  uint8_t               m_prev_button;
+  uint32_t              m_prev_holdtime;
+  std::vector<APP_InputDeviceAxes*> m_input_devices;
+};
index ad06f54..c460493 100644 (file)
  */
 
 #include "AndroidKey.h"
+#include "AndroidExtra.h"
 #include "XBMCApp.h"
 #include "guilib/Key.h"
 #include "windowing/WinEvents.h"
 
-#include "AndroidExtra.h"
+
+typedef struct {
+  int32_t nativeKey;
+  int16_t xbmcKey;
+} KeyMap;
 
 static KeyMap keyMap[] = {
   { AKEYCODE_UNKNOWN         , XBMCK_LAST },
@@ -149,16 +154,16 @@ static KeyMap keyMap[] = {
   { AKEYCODE_MEDIA_EJECT     , XBMCK_EJECT },
 };
 
-bool CAndroidKey::onKeyboardEvent(AInputEventevent)
+bool CAndroidKey::onKeyboardEvent(AInputEvent *event)
 {
-  CXBMCApp::android_printf("%s", __PRETTY_FUNCTION__);
   if (event == NULL)
     return false;
 
+  int32_t flags   = AKeyEvent_getFlags(event);
+  int32_t state   = AKeyEvent_getMetaState(event);
+  int32_t action  = AKeyEvent_getAction(event);
+  int32_t repeat  = AKeyEvent_getRepeatCount(event);
   int32_t keycode = AKeyEvent_getKeyCode(event);
-  int32_t flags = AKeyEvent_getFlags(event);
-  int32_t state = AKeyEvent_getMetaState(event);
-  int32_t repeatCount = AKeyEvent_getRepeatCount(event);
 
   // Check if we got some special key
   uint16_t sym = XBMCK_UNKNOWN;
@@ -170,7 +175,7 @@ bool CAndroidKey::onKeyboardEvent(AInputEvent* event)
       break;
     }
   }
-  
+
   // check if this is a key we don't want to handle
   if (sym == XBMCK_LAST || sym == XBMCK_UNKNOWN)
     return false;
@@ -188,40 +193,48 @@ bool CAndroidKey::onKeyboardEvent(AInputEvent* event)
   if (state & AMETA_SYM_ON)
     modifiers |= 0x000?;*/
 
-  switch (AKeyEvent_getAction(event))
+  switch (action)
   {
     case AKEY_EVENT_ACTION_DOWN:
-      CXBMCApp::android_printf("CXBMCApp: key down (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
-                      keycode, repeatCount, flags,
-                      (state & AMETA_ALT_ON) ? "yes" : "no",
-                      (state & AMETA_SHIFT_ON) ? "yes" : "no",
-                      (state & AMETA_SYM_ON) ? "yes" : "no");
+#if 1
+      CXBMCApp::android_printf("CAndroidKey: key down (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
+        keycode, repeat, flags,
+        (state & AMETA_ALT_ON) ? "yes" : "no",
+        (state & AMETA_SHIFT_ON) ? "yes" : "no",
+        (state & AMETA_SYM_ON) ? "yes" : "no");
+#endif
       XBMC_Key((uint8_t)keycode, sym, modifiers, false);
       return true;
 
     case AKEY_EVENT_ACTION_UP:
-      CXBMCApp::android_printf("CXBMCApp: key up (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
-                      keycode, repeatCount, flags,
-                      (state & AMETA_ALT_ON) ? "yes" : "no",
-                      (state & AMETA_SHIFT_ON) ? "yes" : "no",
-                     (state & AMETA_SYM_ON) ? "yes" : "no");
+#if 1
+      CXBMCApp::android_printf("CAndroidKey: key up (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
+        keycode, repeat, flags,
+        (state & AMETA_ALT_ON) ? "yes" : "no",
+        (state & AMETA_SHIFT_ON) ? "yes" : "no",
+        (state & AMETA_SYM_ON) ? "yes" : "no");
+#endif
       XBMC_Key((uint8_t)keycode, sym, modifiers, true);
       return true;
 
     case AKEY_EVENT_ACTION_MULTIPLE:
-      CXBMCApp::android_printf("CXBMCApp: key multiple (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
-                      keycode, repeatCount, flags,
-                      (state & AMETA_ALT_ON) ? "yes" : "no",
-                      (state & AMETA_SHIFT_ON) ? "yes" : "no",
-                      (state & AMETA_SYM_ON) ? "yes" : "no");
+#if 1
+      CXBMCApp::android_printf("CAndroidKey: key multiple (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
+        keycode, repeat, flags,
+        (state & AMETA_ALT_ON) ? "yes" : "no",
+        (state & AMETA_SHIFT_ON) ? "yes" : "no",
+        (state & AMETA_SYM_ON) ? "yes" : "no");
+#endif
       break;
 
     default:
-      CXBMCApp::android_printf("CXBMCApp: unknown key (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
-                      keycode, repeatCount, flags,
-                      (state & AMETA_ALT_ON) ? "yes" : "no",
-                      (state & AMETA_SHIFT_ON) ? "yes" : "no",
-                      (state & AMETA_SYM_ON) ? "yes" : "no");
+#if 1
+      CXBMCApp::android_printf("CAndroidKey: unknown key (code: %d; repeat: %d; flags: 0x%0X; alt: %s; shift: %s; sym: %s)",
+        keycode, repeat, flags,
+        (state & AMETA_ALT_ON) ? "yes" : "no",
+        (state & AMETA_SHIFT_ON) ? "yes" : "no",
+        (state & AMETA_SYM_ON) ? "yes" : "no");
+#endif
       break;
   }
 
@@ -241,6 +254,6 @@ void CAndroidKey::XBMC_Key(uint8_t code, uint16_t key, uint16_t modifiers, bool
   newEvent.key.keysym.unicode = key;
   newEvent.key.keysym.mod = (XBMCMod)modifiers;
 
-  CXBMCApp::android_printf("XBMC_Key(%u, %u, 0x%04X, %d)", code, key, modifiers, up);
+  //CXBMCApp::android_printf("XBMC_Key(%u, %u, 0x%04X, %d)", code, key, modifiers, up);
   CWinEvents::MessagePush(&newEvent);
 }
index 9bfff8a..bcb3ee2 100644 (file)
  *
  */
 
-#include <stdint.h>
 #include <android/input.h>
 
-typedef struct {
-  int32_t nativeKey;
-  int16_t xbmcKey;
-} KeyMap;
+#include <stdint.h>
+#include <string>
+#include <vector>
 
 class CAndroidKey
 {
 public:
-  CAndroidKey(){};
-  ~CAndroidKey(){};
+  CAndroidKey() {};
+ ~CAndroidKey() {};
+
+  bool onKeyboardEvent(AInputEvent *event);
   void XBMC_Key(uint8_t code, uint16_t key, uint16_t modifiers, bool up);
-  bool onKeyboardEvent(AInputEvent* event);
-};
\ No newline at end of file
+  void XBMC_JoyButton(uint8_t id, uint8_t button, bool up);
+};
index daec385..11fe606 100644 (file)
@@ -18,6 +18,7 @@
  *  <http://www.gnu.org/licenses/>.
  *
  */
+
 #include <android/input.h>
 
 class CAndroidMouse
index 335bf6e..31ff94d 100644 (file)
@@ -18,6 +18,7 @@
  *  <http://www.gnu.org/licenses/>.
  *
  */
+
 #include <android/input.h>
 #include <math.h>
 
index 1f98d90..155b82e 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "EventLoop.h"
 #include "XBMCApp.h"
+#include "AndroidExtra.h"
 
 CEventLoop::CEventLoop(android_app* application)
   : m_enabled(false),
@@ -133,24 +134,39 @@ void CEventLoop::processActivity(int32_t command)
 
 int32_t CEventLoop::processInput(AInputEvent* event)
 {
-  int32_t type = AInputEvent_getType(event);
-  switch (type)
+  int32_t rtn    = 0;
+  int32_t type   = AInputEvent_getType(event);
+  int32_t source = AInputEvent_getSource(event);
+
+  switch(type)
   {
+    case AINPUT_EVENT_TYPE_KEY:
+      if (source & AINPUT_SOURCE_GAMEPAD || source & AINPUT_SOURCE_JOYSTICK)
+      {
+        if (m_inputHandler->onJoyStickKeyEvent(event))
+          return true;
+      }
+      if (source & AINPUT_SOURCE_CLASS_BUTTON)
+        rtn = m_inputHandler->onKeyboardEvent(event);
+      break;
     case AINPUT_EVENT_TYPE_MOTION:
-      switch (AInputEvent_getSource(event))
+      switch(source)
       {
         case AINPUT_SOURCE_TOUCHSCREEN:
-          return m_inputHandler->onTouchEvent(event);
+          rtn = m_inputHandler->onTouchEvent(event);
+          break;
         case AINPUT_SOURCE_MOUSE:
-          return m_inputHandler->onMouseEvent(event);
+          rtn = m_inputHandler->onMouseEvent(event);
+          break;
+        case AINPUT_SOURCE_GAMEPAD:
+        case AINPUT_SOURCE_JOYSTICK:
+          rtn = m_inputHandler->onJoyStickMotionEvent(event);
+          break;
       }
       break;
-
-    case AINPUT_EVENT_TYPE_KEY:
-      return m_inputHandler->onKeyboardEvent(event);
   }
 
-  return 0;
+  return rtn;
 }
 
 void CEventLoop::activityCallback(android_app* application, int32_t command)
@@ -164,11 +180,11 @@ void CEventLoop::activityCallback(android_app* application, int32_t command)
 
 int32_t CEventLoop::inputCallback(android_app* application, AInputEvent* event)
 {
-  if (application == NULL || application->userData == NULL ||
-      event == NULL)
+  if (application == NULL || application->userData == NULL || event == NULL)
     return 0;
 
   CEventLoop& eventLoop = *((CEventLoop*)application->userData);
+
   return eventLoop.processInput(event);
 }
 
index e629194..f0f1627 100644 (file)
 #include "AndroidTouch.h"
 #include "AndroidKey.h"
 #include "AndroidMouse.h"
+#include "AndroidJoyStick.h"
 
-class IInputHandler : public CAndroidTouch, public CAndroidKey, public CAndroidMouse
+class IInputHandler
+: public CAndroidKey
+, public CAndroidMouse
+, public CAndroidTouch
+, public CAndroidJoyStick
 {
 public:
-  IInputHandler() : CAndroidTouch(), CAndroidKey(), CAndroidMouse() {}
+  IInputHandler()
+  : CAndroidKey()
+  , CAndroidMouse()
+  , CAndroidTouch()
+  , CAndroidJoyStick()
+  {}
 
   virtual void setDPI(uint32_t dpi) { CAndroidTouch::setDPI(dpi); }
 };
index 653a19f..c34a785 100644 (file)
@@ -13,6 +13,7 @@ SRCS      += AndroidFeatures.cpp
 SRCS      += AndroidKey.cpp
 SRCS      += AndroidTouch.cpp
 SRCS      += AndroidMouse.cpp
+SRCS      += AndroidJoyStick.cpp
 SRCS      += GraphicBuffer.cpp
 SRCS      += EventLoop.cpp
 SRCS      += XBMCApp.cpp
index af36d84..bd2e7dc 100644 (file)
  */
 
 #include "List.h"
-#include "jutils/jutils-details.hpp"
+#include "View.h"
 #include "ScanResult.h"
 #include "WifiConfiguration.h"
 #include "ApplicationInfo.h"
 
+#include "jutils/jutils-details.hpp"
+
 using namespace jni;
 
 template <typename T>
@@ -44,3 +46,4 @@ int CJNIList<T>::size()
 template class CJNIList<CJNIScanResult>;
 template class CJNIList<CJNIWifiConfiguration>;
 template class CJNIList<CJNIApplicationInfo>;
+template class CJNIList<CJNIViewInputDeviceMotionRange>;
index e45bb55..2667a97 100644 (file)
@@ -86,6 +86,18 @@ std::string CJNIViewInputDevice::getName() const
     "getName", "()Ljava/lang/String;"));
 }
 
+int CJNIViewInputDevice::getSources() const
+{
+  return call_method<int>(m_object,
+    "getSources", "()I");
+}
+
+const CJNIList<CJNIViewInputDeviceMotionRange> CJNIViewInputDevice::getMotionRanges() const
+{
+  return call_method<jhobject>(m_object,
+    "getMotionRanges", "()Ljava/util/List;");
+}
+
 const CJNIViewInputDeviceMotionRange CJNIViewInputDevice::getMotionRange(int axis) const
 {
   return call_method<jhobject>(m_object,
@@ -93,6 +105,13 @@ const CJNIViewInputDeviceMotionRange CJNIViewInputDevice::getMotionRange(int axi
     axis);
 }
 
+const CJNIViewInputDeviceMotionRange CJNIViewInputDevice::getMotionRange(int axis, int source) const
+{
+  return call_method<jhobject>(m_object,
+    "getMotionRange", "(II)Landroid/view/InputDevice$MotionRange;",
+    axis, source);
+}
+
 /************************************************************************/
 /************************************************************************/
 int CJNIView::SYSTEM_UI_FLAG_FULLSCREEN(0);
index 7adb129..1284024 100644 (file)
@@ -20,6 +20,7 @@
  */
 
 #include "JNIBase.h"
+#include "List.h"
 
 class CJNIViewInputDeviceMotionRange : public CJNIBase
 {
@@ -48,7 +49,10 @@ public:
   static const CJNIViewInputDevice getDevice(int id);
 
   std::string  getName() const;
+  int          getSources() const;
+  const CJNIList<CJNIViewInputDeviceMotionRange> getMotionRanges() const;
   const CJNIViewInputDeviceMotionRange getMotionRange(int axis) const;
+  const CJNIViewInputDeviceMotionRange getMotionRange(int axis, int source) const;
 
 private:
   CJNIViewInputDevice();
index 00545bb..479fa2b 100644 (file)
 #define XBMC_RELEASED  0
 #define XBMC_PRESSED   1
 
+/* Hat definitions */
+#define XBMC_HAT_CENTERED    0
+#define XBMC_HAT_UP          0x01
+#define XBMC_HAT_RIGHT       0x02
+#define XBMC_HAT_DOWN        0x04
+#define XBMC_HAT_LEFT        0x08
+#define XBMC_HAT_LEFTUP      XBMC_HAT_UP   | XBMC_HAT_LEFT
+#define XBMC_HAT_RIGHTUP     XBMC_HAT_UP   | XBMC_HAT_RIGHT
+#define XBMC_HAT_LEFTDOWN    XBMC_HAT_DOWN | XBMC_HAT_LEFT
+#define XBMC_HAT_RIGHTDOWN   XBMC_HAT_DOWN | XBMC_HAT_RIGHT
+
 /* Event enumerations */
 typedef enum {
        XBMC_NOEVENT = 0,        /* Unused (do not remove) */
@@ -102,6 +113,7 @@ typedef struct XBMC_JoyAxisEvent {
        unsigned char which;    /* The joystick device index */
        unsigned char axis;     /* The joystick axis index */
        int16_t value;  /* The axis value (range: -32768 to 32767) */
+       float   fvalue; /* The axis value (range: -1.0 to 1.0) */
 } XBMC_JoyAxisEvent;
 
 /* Joystick trackball motion event structure */
index 820995c..f1fbf11 100644 (file)
@@ -1,5 +1,5 @@
 /*
-*      Copyright (C) 2010-2013 Team XBMC
+ *      Copyright (C) 2010-2013 Team XBMC
  *      http://xbmc.org
  *
  *  This Program is free software; you can redistribute it and/or modify
  */
 
 #include "system.h"
-#include <list>
+
 #include "WinEventsAndroid.h"
+
 #include "Application.h"
-#include "threads/CriticalSection.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/XBMC_vkeys.h"
+#include "input/SDLJoystick.h"
+#include "utils/log.h"
+#include "windowing/WindowingFactory.h"
+
+#include "android/jni/View.h"
+
+#define DEBUG_MESSAGEPUMP 0
+
+#define ALMOST_ZERO 0.125f
+enum {
+  EVENT_STATE_TEST,
+  EVENT_STATE_HOLD,
+  EVENT_STATE_REPEAT
+};
+
+/************************************************************************/
+/************************************************************************/
+static bool different_event(XBMC_Event &curEvent, XBMC_Event &newEvent)
+{
+  // different type
+  if (curEvent.type != newEvent.type)
+    return true;
+
+  // Hat
+  if(newEvent.type == XBMC_JOYHATMOTION)
+    return (curEvent.jhat.value != newEvent.jhat.value);
+
+  // different axis
+  if (curEvent.jaxis.axis != newEvent.jaxis.axis)
+    return true;
 
-static CCriticalSection g_inputCond;
+  // different axis direction (handles -1 vs 1)
+  if (signbit(curEvent.jaxis.fvalue) != signbit(newEvent.jaxis.fvalue))
+    return true;
 
-static std::list<XBMC_Event> events;
+  // different axis value (handles 0 vs 1 or -1)
+  if ((fabs(curEvent.jaxis.fvalue) < ALMOST_ZERO) != (fabs(newEvent.jaxis.fvalue) < ALMOST_ZERO))
+    return true;
 
-void CWinEventsAndroid::DeInit()
+  return false;
+}
+
+/************************************************************************/
+/************************************************************************/
+CWinEventsAndroid::CWinEventsAndroid()
+: CThread("CWinEventsAndroid")
 {
+  CLog::Log(LOGDEBUG, "CWinEventsAndroid::CWinEventsAndroid");
+  Create();
 }
 
-void CWinEventsAndroid::Init()
+CWinEventsAndroid::~CWinEventsAndroid()
 {
+  m_bStop = true;
+  StopThread(true);
 }
 
 void CWinEventsAndroid::MessagePush(XBMC_Event *newEvent)
 {
-  CSingleLock lock(g_inputCond);
-  events.push_back(*newEvent);
+  CSingleLock lock(m_eventsCond);
+
+  m_events.push_back(*newEvent);
+  #if DEBUG_MESSAGEPUMP
+  CLog::Log(LOGDEBUG, "push     event, size(%d), fvalue(%f)",
+    m_events.size(), newEvent->jaxis.fvalue);
+  #endif
+}
+
+void CWinEventsAndroid::MessagePushRepeat(XBMC_Event *repeatEvent)
+{
+  CSingleLock lock(m_eventsCond);
+
+  std::list<XBMC_Event>::iterator itt;
+  for (itt = m_events.begin(); itt != m_events.end(); ++itt)
+  {
+    // we have events pending, if we we just
+    // repush, we might push the repeat event
+    // in back of a canceling non-active event.
+    // do not repush if pending are different event.
+    if (different_event(*itt, *repeatEvent))
+    {
+      #if DEBUG_MESSAGEPUMP
+      CLog::Log(LOGDEBUG, "repush    skip, size(%d), fvalue(%f)",
+        m_events.size(), repeatEvent->jaxis.fvalue);
+      #endif
+      return;
+    }
+  }
+  // is a repeat, push it
+  m_events.push_back(*repeatEvent);
+  #if DEBUG_MESSAGEPUMP
+  CLog::Log(LOGDEBUG, "repush   event, size(%d), fvalue(%f)",
+    m_events.size(), repeatEvent->jaxis.fvalue);
+  #endif
 }
 
 bool CWinEventsAndroid::MessagePump()
 {
   bool ret = false;
 
-  // Do not always loop, only pump the initial queued count events. else if ui keep pushing 
-  // events the loop won't finish then it will block xbmc main message loop. 
-  for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount) 
+  // Do not always loop, only pump the initial queued count events. else if ui keep pushing
+  // events the loop won't finish then it will block xbmc main message loop.
+  for (size_t pumpEventCount = GetQueueSize(); pumpEventCount > 0; --pumpEventCount)
   {
-  
-    // Pop up only one event per time since in App::OnEvent it may init modal dialog which init 
-    // deeper message loop and call the deeper MessagePump from there. 
-    XBMC_Event pumpEvent; 
-    { 
-      CSingleLock lock(g_inputCond); 
-      if (events.size() == 0) 
-        return ret; 
-      pumpEvent = events.front(); 
-      events.pop_front(); 
-    }  
-
-    ret |= g_application.OnEvent(pumpEvent);
+    // Pop up only one event per time since in App::OnEvent it may init modal dialog which init
+    // deeper message loop and call the deeper MessagePump from there.
+    XBMC_Event pumpEvent;
+    {
+      CSingleLock lock(m_eventsCond);
+      if (m_events.empty())
+        return ret;
+      pumpEvent = m_events.front();
+      m_events.pop_front();
+      #if DEBUG_MESSAGEPUMP
+      CLog::Log(LOGDEBUG, "  pop    event, size(%d), fvalue(%f)",
+        m_events.size(), pumpEvent.jaxis.fvalue);
+      #endif
+    }
+
+    if ((pumpEvent.type == XBMC_JOYBUTTONUP)   ||
+        (pumpEvent.type == XBMC_JOYBUTTONDOWN) ||
+        (pumpEvent.type == XBMC_JOYAXISMOTION) ||
+        (pumpEvent.type == XBMC_JOYHATMOTION))
+    {
+      int             item;
+      int             type;
+      uint32_t        holdTime;
+      APP_InputDevice input_device;
+      float           amount = 1.0f;
+      short           input_type;
+
+      type = pumpEvent.type;
+      switch (type)
+      {
+        case XBMC_JOYAXISMOTION:
+          // The typical joystick keymap xml has the following where 'id' is the axis
+          //  and 'limit' is which action to choose (ie. Up or Down).
+          //  <axis id="5" limit="-1">Up</axis>
+          //  <axis id="5" limit="+1">Down</axis>
+          // One would think that limits is in reference to fvalue but
+          // it is really in reference to id :) The sign of item passed
+          // into ProcessJoystickEvent indicates the action mapping.
+          item = pumpEvent.jaxis.axis;
+          if (fabs(pumpEvent.jaxis.fvalue) < ALMOST_ZERO)
+            amount = 0.0f;
+          else if (pumpEvent.jaxis.fvalue  < 0.0f)
+            item = -item;
+          holdTime = 0;
+          input_device.id = pumpEvent.jaxis.which;
+          input_type = JACTIVE_AXIS;
+          break;
+
+        case XBMC_JOYHATMOTION:
+          item = pumpEvent.jhat.hat | 0xFFF00000 | (pumpEvent.jhat.value<<16);
+          holdTime = 0;
+          input_device.id = pumpEvent.jhat.which;
+          input_type = JACTIVE_HAT;
+          break;
+
+        case XBMC_JOYBUTTONUP:
+        case XBMC_JOYBUTTONDOWN:
+          item = pumpEvent.jbutton.button;
+          holdTime = pumpEvent.jbutton.holdTime;
+          input_device.id = pumpEvent.jbutton.which;
+          input_type = JACTIVE_BUTTON;
+          break;
+      }
+
+      // look for device name in our inputdevice cache
+      // so we can lookup and match the right joystick.xxx.xml
+      for (size_t i = 0; i < m_input_devices.size(); i++)
+      {
+        if (m_input_devices[i].id == input_device.id)
+          input_device.name = m_input_devices[i].name;
+      }
+      if (input_device.name.empty())
+      {
+        // not in inputdevice cache, fetch and cache it.
+        CJNIViewInputDevice view_input_device = CJNIViewInputDevice::getDevice(input_device.id);
+        input_device.name = view_input_device.getName();
+        CLog::Log(LOGDEBUG, "CWinEventsAndroid::MessagePump:caching  id(%d), device(%s)",
+          input_device.id, input_device.name.c_str());
+        m_input_devices.push_back(input_device);
+      }
+
+      if (type == XBMC_JOYAXISMOTION || type == XBMC_JOYHATMOTION)
+      {
+        // Joystick autorepeat -> only handle axis
+        CSingleLock lock(m_lasteventCond);
+        m_lastevent.push(pumpEvent);
+      }
+
+      if (fabs(amount) >= ALMOST_ZERO)
+      {
+        ret |= g_application.ProcessJoystickEvent(input_device.name,
+          item, input_type, amount, holdTime);
+      }
+    }
+    else
+    {
+      ret |= g_application.OnEvent(pumpEvent);
+    }
+
+    if (pumpEvent.type == XBMC_MOUSEBUTTONUP)
+      g_windowManager.SendMessage(GUI_MSG_UNFOCUS_ALL, 0, 0, 0, 0);
   }
 
   return ret;
@@ -70,6 +237,111 @@ bool CWinEventsAndroid::MessagePump()
 
 size_t CWinEventsAndroid::GetQueueSize()
 {
-  CSingleLock lock(g_inputCond);
-  return events.size();
+  CSingleLock lock(m_eventsCond);
+  return m_events.size();
+}
+
+void CWinEventsAndroid::Process()
+{
+  uint32_t timeout = 10;
+  uint32_t holdTimeout = 500;
+  uint32_t repeatTimeout = 100;
+  uint32_t repeatDuration = 0;
+
+  XBMC_Event cur_event;
+  int state = EVENT_STATE_TEST;
+  while (!m_bStop)
+  {
+    // run a 10ms (timeout) wait cycle
+    Sleep(timeout);
+
+    CSingleLock lock(m_lasteventCond);
+
+    switch(state)
+    {
+      default:
+      case EVENT_STATE_TEST:
+        // check for axis action events
+        if (!m_lastevent.empty())
+        {
+          if ((m_lastevent.front().type == XBMC_JOYAXISMOTION && fabs(m_lastevent.front().jaxis.fvalue) >= ALMOST_ZERO)
+              || (m_lastevent.front().type == XBMC_JOYHATMOTION && m_lastevent.front().jhat.value > XBMC_HAT_CENTERED))
+          {
+            // new active event
+            cur_event = m_lastevent.front();
+            #if DEBUG_MESSAGEPUMP
+            CLog::Log(LOGDEBUG, "test   -> hold, size(%d), fvalue(%f)",
+              m_lastevent.size(), m_lastevent.front().jaxis.fvalue);
+            #endif
+            m_lastevent.pop();
+            repeatDuration = 0;
+            state = EVENT_STATE_HOLD;
+            break;
+          }
+          #if DEBUG_MESSAGEPUMP
+          CLog::Log(LOGDEBUG, "munch     test, size(%d), fvalue(%f)",
+            m_lastevent.size(), m_lastevent.front().jaxis.fvalue);
+          #endif
+          // non-active event, eat it
+          m_lastevent.pop();
+        }
+        break;
+
+      case EVENT_STATE_HOLD:
+        repeatDuration += timeout;
+        if (!m_lastevent.empty())
+        {
+          if (different_event(cur_event, m_lastevent.front()))
+          {
+            // different axis event, cycle back to test
+            state = EVENT_STATE_TEST;
+            break;
+          }
+          #if DEBUG_MESSAGEPUMP
+          CLog::Log(LOGDEBUG, "munch     hold, size(%d), fvalue(%f)",
+            m_lastevent.size(), m_lastevent.front().jaxis.fvalue);
+          #endif
+          // same axis event, eat it
+          m_lastevent.pop();
+        }
+        if (repeatDuration >= holdTimeout)
+        {
+          CLog::Log(LOGDEBUG, "hold  ->repeat, size(%d), repeatDuration(%d)", m_lastevent.size(), repeatDuration);
+          state = EVENT_STATE_REPEAT;
+        }
+        break;
+
+      case EVENT_STATE_REPEAT:
+        repeatDuration += timeout;
+        if (!m_lastevent.empty())
+        {
+          if (different_event(cur_event, m_lastevent.front()))
+          {
+            // different axis event, cycle back to test
+            state = EVENT_STATE_TEST;
+            #if DEBUG_MESSAGEPUMP
+            CLog::Log(LOGDEBUG, "repeat->  test, size(%d), fvalue(%f)",
+              m_lastevent.size(), m_lastevent.front().jaxis.fvalue);
+            #endif
+            break;
+          }
+          #if DEBUG_MESSAGEPUMP
+          CLog::Log(LOGDEBUG, "munch   repeat, size(%d), fvalue(%f)",
+            m_lastevent.size(), m_lastevent.front().jaxis.fvalue);
+          #endif
+          // same axis event, eat it
+          m_lastevent.pop();
+        }
+        if (repeatDuration >= holdTimeout)
+        {
+          // this is a repeat, push it
+          MessagePushRepeat(&cur_event);
+          // assuming holdTimeout > repeatTimeout,
+          // just subtract the repeatTimeout
+          // to get the next cycle time
+          repeatDuration -= repeatTimeout;
+        }
+        break;
+    }
+  }
 }
index a7a62f6..282c428 100644 (file)
@@ -1,5 +1,5 @@
 /*
-*      Copyright (C) 2010-2013 Team XBMC
+ *      Copyright (C) 2010-2013 Team XBMC
  *      http://xbmc.org
  *
  *  This Program is free software; you can redistribute it and/or modify
 #ifndef WINDOW_EVENTS_ANDROID_H
 #define WINDOW_EVENTS_ANDROID_H
 
+#include <list>
+#include <queue>
+#include <vector>
+#include <string>
+
+#include "threads/Event.h"
+#include "threads/Thread.h"
+#include "threads/CriticalSection.h"
 #include "windowing/WinEvents.h"
 
-class CWinEventsAndroid : public IWinEvents
+typedef struct {
+  int32_t id;
+  std::string name;
+} APP_InputDevice;
+
+class CWinEventsAndroid : public IWinEvents, public CThread
 {
 public:
-  static void Init();
-  static void DeInit();
-  void MessagePush(XBMC_Event *newEvent);
-  bool MessagePump();
+  CWinEventsAndroid();
+ ~CWinEventsAndroid();
+
+  void            MessagePush(XBMC_Event *newEvent);
+  void            MessagePushRepeat(XBMC_Event *repeatEvent);
+  bool            MessagePump();
   virtual size_t  GetQueueSize();
+
+private:
+  // for CThread
+  virtual void    Process();
+
+  CCriticalSection             m_eventsCond;
+  std::list<XBMC_Event>        m_events;
+
+  CCriticalSection             m_lasteventCond;
+  std::queue<XBMC_Event>       m_lastevent;
+
+  std::vector<APP_InputDevice> m_input_devices;
 };
 
 #endif // WINDOW_EVENTS_ANDROID_H
index f79757b..179e91a 100644 (file)
@@ -22,6 +22,7 @@
 #include <list>
 #include "WinEventsIOS.h"
 #include "input/XBMC_vkeys.h"
+#include "input/SDLJoystick.h"
 #include "Application.h"
 #include "windowing/WindowingFactory.h"
 #include "threads/CriticalSection.h"
@@ -64,13 +65,12 @@ bool CWinEventsIOS::MessagePump()
       // the jbutton.which will be the keyID to translate using joystick.AppleRemote.xml
       // jbutton.holdTime is the time the button is hold in ms (for repeated keypresses)
       std::string joystickName = "AppleRemote";
-      bool isAxis = false;
       float fAmount = 1.0;
       unsigned char wKeyID = pumpEvent.jbutton.which;
       unsigned int holdTime = pumpEvent.jbutton.holdTime;
 
       CLog::Log(LOGDEBUG,"CWinEventsIOS: Button press keyID = %i", wKeyID);
-      ret |= g_application.ProcessJoystickEvent(joystickName, wKeyID, isAxis, fAmount, holdTime);
+      ret |= g_application.ProcessJoystickEvent(joystickName, wKeyID, JACTIVE_BUTTON, fAmount, holdTime);
     }
     else
       ret |= g_application.OnEvent(pumpEvent);