droid: add controller/gamepad handling
authordavilla <davilla@4pi.com>
Thu, 10 Oct 2013 22:44:15 +0000 (18:44 -0400)
committerdavilla <davilla@4pi.com>
Fri, 1 Nov 2013 20:29:28 +0000 (16:29 -0400)
17 files changed:
system/keymaps/joystick.Gamestick.xml [new file with mode: 0644]
system/keymaps/joystick.NVidiaShield.xml [new file with mode: 0644]
system/keymaps/joystick.Ouya.xml [new file with mode: 0644]
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/windowing/XBMC_events.h
xbmc/windowing/android/WinEventsAndroid.cpp
xbmc/windowing/android/WinEventsAndroid.h

diff --git a/system/keymaps/joystick.Gamestick.xml b/system/keymaps/joystick.Gamestick.xml
new file mode 100644 (file)
index 0000000..8fd18a0
--- /dev/null
@@ -0,0 +1,127 @@
+<!-- This file contains the mappings for a Gamestick controller to actions within XBMC    -->
+<!-- The <global> section is a fall through - they will only be used if the button is not          -->
+<!-- used in the current window's section.  Note that there is only handling                       -->
+<!-- for a single action per button at this stage.                                                 -->
+
+<!-- The format of a mapping is:                                -->
+<!--    <device name="name">                                    -->
+<!--      <button id="x">action</button>                        -->
+<!--      <axis id="x" limit="y">action</axis>                  -->
+<!--    </device>                                               -->
+
+<!-- Note that the action can be a built-in function.           -->
+<!-- eg <button id="x">XBMC.ActivateWindow(Home)</button>       -->
+<!-- would automatically go to Home on the press of button 'x'. -->
+
+<!-- Joystick Name: Gamestick                  -->
+
+<!-- Button Mappings in Android:               -->
+<!-- see JoyButtonMap in AndroidKey.cpp        -->
+<!--                                           -->
+<!-- ID              Button                    -->
+<!--                                           -->
+<!-- 1               A                         -->
+<!-- 2               B                         -->
+<!-- 4               X                         -->
+<!-- 5               Y                         -->
+<!-- 7               Left Shoulder             -->
+<!-- 8               Right Shoulder            -->
+<!-- 11              Left Stick Button         -->
+<!-- 12              Right Stick Button        -->
+<!-- 13              Start                     -->
+
+<!-- Axis Mappings:                   -->
+<!--                                  -->
+<!-- ID              Button           -->
+<!--                                  -->
+<!-- 1               Left Stick L/R   -->
+<!-- 2               Left Stick U/D   -->
+<!-- 3               Right Stick L/R  -->
+<!-- 4               Right Stick U/D  -->
+<!-- 5               D-Pad U/D        -->
+<!-- 6               D-Pad L/R        -->
+
+<keymap>
+  <global>
+    <joystick name="GameStick Controller">
+      <altname>GameStick Controller 1</altname>
+      <altname>GameStick Controller 2</altname>
+      <!-- A selects. B goes back. X gets context menu. Y goes fullscreen and back. -->
+      <button id="1">Select</button>
+      <button id="2">Back</button>
+      <button id="4">ContextMenu</button>
+      <button id="5">FullScreen</button>
+      <!--Left Shoulder Queues videos. Right shoulder displays the current queue. -->
+      <button id="7">Queue</button>
+      <button id="8">Playlist</button>
+      <!--left/right stick buttons -->
+      <button id="11">Enter</button>
+      <button id="12">Enter</button>
+      <!-- Push up on the left stick for volueme up. Push down for volume down. -->
+      <axis id="1" limit="-1">Up</axis>
+      <axis id="1" limit="+1">Down</axis>
+      <axis id="2" limit="-1">Left</axis>
+      <axis id="2" limit="+1">Right</axis>
+      <!-- Push up on the right stick for volueme up. Push down for volume down. -->
+      <axis id="3" limit="-1">VolumeDown</axis>
+      <axis id="3" limit="+1">VolumeUp</axis>
+      <axis id="4" limit="-1">VolumeDown</axis>
+      <axis id="4" limit="+1">VolumeUp</axis>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">Up</axis>
+      <axis id="5" limit="+1">Down</axis>
+      <axis id="6" limit="-1">Left</axis>
+      <axis id="6" limit="+1">Right</axis>
+    </joystick>
+  </global>
+  <FullscreenVideo>
+    <joystick name="GameStick Controller">
+      <altname>GameStick Controller 1</altname>
+      <altname>GameStick Controller 2</altname>
+      <!--
+            A pauses and starts the video.
+            B stops the video.
+            X opens the onscreen display.
+            Y switches in and out of full screen
+            -->
+      <button id="1">Pause</button>
+      <button id="2">Stop</button>
+      <button id="4">OSD</button>
+      <!--
+            Left shoulder changes aspect ratio.
+            Right shoulder changes subtitles.
+            Right stick changes Audio Language.
+            Start button displays info.
+            -->
+      <button id="7">Info</button>
+      <button id="8">AudioNextLanguage</button>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">BigStepForward</axis>
+      <axis id="5" limit="+1">BigStepBack</axis>
+      <axis id="6" limit="-1">StepBack</axis>
+      <axis id="6" limit="+1">StepForward</axis>
+    </joystick>
+  </FullscreenVideo>
+  <FullscreenInfo>
+    <joystick name="GameStick Controller">
+      <altname>GameStick Controller 1</altname>
+      <altname>GameStick Controller 2</altname>
+      <button id="2">Close</button>
+      <button id="4">OSD</button>
+    </joystick>
+  </FullscreenInfo>
+  <VideoOSD>
+    <joystick name="GameStick Controller">
+      <altname>GameStick Controller 1</altname>
+      <altname>GameStick Controller 2</altname>
+      <button id="4">CodecInfo</button>
+    </joystick>
+  </VideoOSD>
+  <PlayerControls>
+    <joystick name="GameStick Controller">
+      <altname>GameStick Controller 1</altname>
+      <altname>GameStick Controller 2</altname>
+      <button id="4">Close</button>
+    </joystick>
+  </PlayerControls>
+</keymap>
diff --git a/system/keymaps/joystick.NVidiaShield.xml b/system/keymaps/joystick.NVidiaShield.xml
new file mode 100644 (file)
index 0000000..d633858
--- /dev/null
@@ -0,0 +1,122 @@
+<!-- This file contains the mappings for a NVIDIA Shiled Controller to actions within XBMC    -->
+<!-- The <global> section is a fall through - they will only be used if the button is not          -->
+<!-- used in the current window's section.  Note that there is only handling                       -->
+<!-- for a single action per button at this stage.                                                 -->
+
+<!-- The format of a mapping is:                                -->
+<!--    <device name="name">                                    -->
+<!--      <button id="x">action</button>                        -->
+<!--      <axis id="x" limit="y">action</axis>                  -->
+<!--    </device>                                               -->
+
+<!-- Note that the action can be a built-in function.           -->
+<!-- eg <button id="x">XBMC.ActivateWindow(Home)</button>       -->
+<!-- would automatically go to Home on the press of button 'x'. -->
+
+<!-- Joystick Name: NVIDIA Corporation NVIDIA Controller        -->
+
+<!-- Button Mappings in Android:               -->
+<!-- see JoyButtonMap in AndroidKey.cpp        -->
+<!--                                           -->
+<!-- ID              Button                    -->
+<!--                                           -->
+<!-- 1               A                         -->
+<!-- 2               B                         -->
+<!-- 4               X                         -->
+<!-- 5               Y                         -->
+<!-- 7               Left Shoulder             -->
+<!-- 8               Right Shoulder            -->
+<!-- 11              Left Stick Button         -->
+<!-- 12              Right Stick Button        -->
+<!-- 13              Start                     -->
+
+<!-- Axis Mappings:                   -->
+<!--                                  -->
+<!-- ID              Button           -->
+<!--                                  -->
+<!-- 1               Left Stick L/R   -->
+<!-- 2               Left Stick U/D   -->
+<!-- 3               Right Stick L/R  -->
+<!-- 4               Right Stick U/D  -->
+<!-- 5               D-Pad U/D        -->
+<!-- 6               D-Pad L/R        -->
+
+<keymap>
+  <global>
+    <joystick name="NVIDIA Corporation NVIDIA Controller">
+      <altname>NVIDIA Corporation NVIDIA Controller v01.01</altname>
+      <!-- A selects. B goes back. X gets context menu. Y goes fullscreen and back. -->
+      <button id="1">Select</button>
+      <button id="2">Back</button>
+      <button id="4">ContextMenu</button>
+      <button id="5">FullScreen</button>
+      <!--Left Shoulder Queues videos. Right shoulder displays the current queue. -->
+      <button id="7">Queue</button>
+      <button id="8">Playlist</button>
+      <!--left/right stick buttons -->
+      <button id="11">Enter</button>
+      <button id="12">Enter</button>
+      <!-- Push up on the left stick for volueme up. Push down for volume down. -->
+      <axis id="1" limit="-1">Up</axis>
+      <axis id="1" limit="+1">Down</axis>
+      <axis id="2" limit="-1">Left</axis>
+      <axis id="2" limit="+1">Right</axis>
+      <!-- Push up on the right stick for volueme up. Push down for volume down. -->
+      <axis id="3" limit="-1">VolumeDown</axis>
+      <axis id="3" limit="+1">VolumeUp</axis>
+      <axis id="4" limit="-1">VolumeDown</axis>
+      <axis id="4" limit="+1">VolumeUp</axis>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">Up</axis>
+      <axis id="5" limit="+1">Down</axis>
+      <axis id="6" limit="-1">Left</axis>
+      <axis id="6" limit="+1">Right</axis>
+    </joystick>
+  </global>
+  <FullscreenVideo>
+    <joystick name="NVIDIA Corporation NVIDIA Controller">
+      <altname>NVIDIA Corporation NVIDIA Controller v01.01</altname>
+      <!--
+            A pauses and starts the video.
+            B stops the video.
+            X opens the onscreen display.
+            Y switches in and out of full screen
+            -->
+      <button id="1">Pause</button>
+      <button id="2">Stop</button>
+      <button id="4">OSD</button>
+      <!--
+            Left shoulder changes aspect ratio.
+            Right shoulder changes subtitles.
+            Right stick changes Audio Language.
+            Start button displays info.
+            -->
+      <button id="7">Info</button>
+      <button id="8">AudioNextLanguage</button>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">BigStepForward</axis>
+      <axis id="5" limit="+1">BigStepBack</axis>
+      <axis id="6" limit="-1">StepBack</axis>
+      <axis id="6" limit="+1">StepForward</axis>
+    </joystick>
+  </FullscreenVideo>
+  <FullscreenInfo>
+    <joystick name="NVIDIA Corporation NVIDIA Controller">
+      <altname>NVIDIA Corporation NVIDIA Controller v01.01</altname>
+      <button id="2">Close</button>
+      <button id="4">OSD</button>
+    </joystick>
+  </FullscreenInfo>
+  <VideoOSD>
+    <joystick name="NVIDIA Corporation NVIDIA Controller">
+      <altname>NVIDIA Corporation NVIDIA Controller v01.01</altname>
+      <button id="4">CodecInfo</button>
+    </joystick>
+  </VideoOSD>
+  <PlayerControls>
+    <joystick name="NVIDIA Corporation NVIDIA Controller">
+      <altname>NVIDIA Corporation NVIDIA Controller v01.01</altname>
+      <button id="4">Close</button>
+    </joystick>
+  </PlayerControls>
+</keymap>
diff --git a/system/keymaps/joystick.Ouya.xml b/system/keymaps/joystick.Ouya.xml
new file mode 100644 (file)
index 0000000..94d7361
--- /dev/null
@@ -0,0 +1,127 @@
+<!-- This file contains the mappings for a OUYA Game Controller to actions within XBMC    -->
+<!-- The <global> section is a fall through - they will only be used if the button is not          -->
+<!-- used in the current window's section.  Note that there is only handling                       -->
+<!-- for a single action per button at this stage.                                                 -->
+
+<!-- The format of a mapping is:                                -->
+<!--    <device name="name">                                    -->
+<!--      <button id="x">action</button>                        -->
+<!--      <axis id="x" limit="y">action</axis>                  -->
+<!--    </device>                                               -->
+
+<!-- Note that the action can be a built-in function.           -->
+<!-- eg <button id="x">XBMC.ActivateWindow(Home)</button>       -->
+<!-- would automatically go to Home on the press of button 'x'. -->
+
+<!-- Joystick Name: OUYA Game Controller       -->
+
+<!-- Button Mappings in Android:               -->
+<!-- see JoyButtonMap in AndroidKey.cpp        -->
+<!--                                           -->
+<!-- ID              Button                    -->
+<!--                                           -->
+<!-- 1               A                         -->
+<!-- 2               B                         -->
+<!-- 4               X                         -->
+<!-- 5               Y                         -->
+<!-- 7               Left Shoulder             -->
+<!-- 8               Right Shoulder            -->
+<!-- 11              Left Stick Button         -->
+<!-- 12              Right Stick Button        -->
+<!-- 13              Start                     -->
+
+<!-- Axis Mappings:                   -->
+<!--                                  -->
+<!-- ID              Button           -->
+<!--                                  -->
+<!-- 1               Left Stick L/R   -->
+<!-- 2               Left Stick U/D   -->
+<!-- 3               Right Stick L/R  -->
+<!-- 4               Right Stick U/D  -->
+<!-- 5               D-Pad U/D        -->
+<!-- 6               D-Pad L/R        -->
+
+<keymap>
+  <global>
+    <joystick name="OUYA Game Controller">
+      <altname>OUYA Game Controller 1</altname>
+      <altname>OUYA Game Controller 2</altname>
+      <!-- A selects. B goes back. X gets context menu. Y goes fullscreen and back. -->
+      <button id="1">Select</button>
+      <button id="2">Back</button>
+      <button id="4">ContextMenu</button>
+      <button id="5">FullScreen</button>
+      <!--Left Shoulder Queues videos. Right shoulder displays the current queue. -->
+      <button id="7">Queue</button>
+      <button id="8">Playlist</button>
+      <!--left/right stick buttons -->
+      <button id="11">Enter</button>
+      <button id="12">Enter</button>
+      <!-- Push up on the left stick for volueme up. Push down for volume down. -->
+      <axis id="1" limit="-1">Up</axis>
+      <axis id="1" limit="+1">Down</axis>
+      <axis id="2" limit="-1">Left</axis>
+      <axis id="2" limit="+1">Right</axis>
+      <!-- Push up on the right stick for volueme up. Push down for volume down. -->
+      <axis id="3" limit="-1">VolumeDown</axis>
+      <axis id="3" limit="+1">VolumeUp</axis>
+      <axis id="4" limit="-1">VolumeDown</axis>
+      <axis id="4" limit="+1">VolumeUp</axis>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">Up</axis>
+      <axis id="5" limit="+1">Down</axis>
+      <axis id="6" limit="-1">Left</axis>
+      <axis id="6" limit="+1">Right</axis>
+    </joystick>
+  </global>
+  <FullscreenVideo>
+    <joystick name="OUYA Game Controller">
+      <altname>OUYA Game Controller 1</altname>
+      <altname>OUYA Game Controller 2</altname>
+      <!--
+            A pauses and starts the video.
+            B stops the video.
+            X opens the onscreen display.
+            Y switches in and out of full screen
+            -->
+      <button id="1">Pause</button>
+      <button id="2">Stop</button>
+      <button id="4">OSD</button>
+      <!--
+            Left shoulder changes aspect ratio.
+            Right shoulder changes subtitles.
+            Right stick changes Audio Language.
+            Start button displays info.
+            -->
+      <button id="7">Info</button>
+      <button id="8">AudioNextLanguage</button>
+      <!-- Analog DPad. -->
+      <axis id="5" limit="-1">BigStepForward</axis>
+      <axis id="5" limit="+1">BigStepBack</axis>
+      <axis id="6" limit="-1">StepBack</axis>
+      <axis id="6" limit="+1">StepForward</axis>
+    </joystick>
+  </FullscreenVideo>
+  <FullscreenInfo>
+    <joystick name="OUYA Game Controller">
+      <altname>OUYA Game Controller 1</altname>
+      <altname>OUYA Game Controller 2</altname>
+      <button id="2">Close</button>
+      <button id="4">OSD</button>
+    </joystick>
+  </FullscreenInfo>
+  <VideoOSD>
+    <joystick name="OUYA Game Controller">
+      <altname>OUYA Game Controller 1</altname>
+      <altname>OUYA Game Controller 2</altname>
+      <button id="4">CodecInfo</button>
+    </joystick>
+  </VideoOSD>
+  <PlayerControls>
+    <joystick name="OUYA Game Controller">
+      <altname>OUYA Game Controller 1</altname>
+      <altname>OUYA Game Controller 2</altname>
+      <button id="4">Close</button>
+    </joystick>
+  </PlayerControls>
+</keymap>
index 37eae3f..53b3dec 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;
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..d31a5d7
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ *      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>
+
+// mapping to axis IDs codes in keymaps.xmls
+enum {
+  AXIS_LEFT_STICK_L_R  = 1,
+  AXIS_LEFT_STICK_U_D  = 2,
+  AXIS_RIGHT_STICK_L_R = 3,
+  AXIS_RIGHT_STICK_U_D = 4,
+  AXIS_DPAD_U_D        = 5,
+  AXIS_DPAD_L_R        = 6,
+};
+
+// mapping to button codes in keymaps.xmls
+typedef struct {
+  int32_t nativeKey;
+  int16_t xbmcID;
+} KeyMap;
+
+static const KeyMap GamePadMap[] = {
+  { AKEYCODE_BUTTON_A        , 1 },
+  { AKEYCODE_BUTTON_B        , 2 },
+  { AKEYCODE_BUTTON_C        , 3 },
+  { AKEYCODE_BUTTON_X        , 4 },
+  { AKEYCODE_BUTTON_Y        , 5 },
+  { AKEYCODE_BUTTON_Z        , 6 },
+  { AKEYCODE_BUTTON_L1       , 7 },
+  { AKEYCODE_BUTTON_R1       , 8 },
+  { AKEYCODE_BUTTON_L2       , 9 },
+  { AKEYCODE_BUTTON_R2       , 10 },
+  { AKEYCODE_BUTTON_THUMBL   , 11 },
+  { AKEYCODE_BUTTON_THUMBR   , 12 },
+  { AKEYCODE_BUTTON_START    , 13 }
+};
+
+// missing in early NDKs, should be present in r9+
+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();
+    CLog::Log(LOGDEBUG, "SetupJoySticks:range(%d), axis(%d), source(%d)", i, axis, source);
+
+    // 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);
+  }
+
+#if 1
+  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(GamePadMap) / sizeof(KeyMap); i++)
+    {
+      if (keycode == GamePadMap[i].nativeKey)
+      {
+        uint32_t holdtime = 0;
+        uint8_t  button = GamePadMap[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, AXIS_DPAD_U_D, AMOTION_EVENT_AXIS_HAT_Y);
+  if (axes->x_hat.enabled)
+    ProcessHat(event, pointer_index,  axes->x_hat,  device, AXIS_DPAD_L_R, AMOTION_EVENT_AXIS_HAT_X);
+
+#if 0
+  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 keymap_axis, 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)
+  {
+    XBMC_JoyAxis(device, keymap_axis, value);
+    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;
+
+  //CLog::Log(LOGDEBUG, "XBMC_Axis(%u, %u, %u, %f)", type, device, axis, value);
+  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;
+
+  //CXBMCApp::android_printf("CAndroidJoyStick::XBMC_JoyButton(%u, %u, %u, %d)",
+  //  newEvent.jbutton.type, newEvent.jbutton.which, newEvent.jbutton.button, newEvent.jbutton.holdTime);
+
+  CWinEvents::MessagePush(&newEvent);
+}
diff --git a/xbmc/android/activity/AndroidJoyStick.h b/xbmc/android/activity/AndroidJoyStick.h
new file mode 100644 (file)
index 0000000..8a5b93e
--- /dev/null
@@ -0,0 +1,74 @@
+#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 keymap_axis, 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_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..4afb781 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");
       XBMC_Key((uint8_t)keycode, sym, modifiers, true);
+#endif
       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..4ed345d 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,41 @@ 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);
+  int32_t repeat = AKeyEvent_getRepeatCount(event);
+  int32_t keycod = AKeyEvent_getKeyCode(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 +182,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 00545bb..a1327ee 100644 (file)
@@ -102,6 +102,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..dca857c 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 "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 axis
+  if (curEvent.jaxis.axis != newEvent.jaxis.axis)
+    return true;
+
+  // different axis direction (handles -1 vs 1)
+  if (signbit(curEvent.jaxis.fvalue) != signbit(newEvent.jaxis.fvalue))
+    return true;
 
-static CCriticalSection g_inputCond;
+  // different axis value (handles 0 vs 1 or -1)
+  if ((fabs(curEvent.jaxis.fvalue) < ALMOST_ZERO) != (fabs(newEvent.jaxis.fvalue) < ALMOST_ZERO))
+    return true;
 
-static std::list<XBMC_Event> events;
+  return false;
+}
 
-void CWinEventsAndroid::DeInit()
+/************************************************************************/
+/************************************************************************/
+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))
+    {
+      int             item;
+      int             type;
+      uint32_t        holdTime;
+      APP_InputDevice input_device;
+      float           amount = 1.0f;
+
+      type = pumpEvent.type;
+      if (type == 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;
+      }
+      else
+      {
+        item = pumpEvent.jbutton.button;
+        holdTime = pumpEvent.jbutton.holdTime;
+        input_device.id = pumpEvent.jbutton.which;
+      }
+
+      // 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)
+      {
+        // 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, type == XBMC_JOYAXISMOTION, 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 +214,110 @@ 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 (fabs(m_lastevent.front().jaxis.fvalue) >= ALMOST_ZERO)
+          {
+            // 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