[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / input / windows / WINJoystick.cpp
1 /*
2 *      Copyright (C) 2012-2013 Team XBMC
3 *      http://www.xbmc.org
4 *
5 *  This Program is free software; you can redistribute it and/or modify
6 *  it under the terms of the GNU General Public License as published by
7 *  the Free Software Foundation; either version 2, or (at your option)
8 *  any later version.
9 *
10 *  This Program is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 *  GNU General Public License for more details.
14 *
15 *  You should have received a copy of the GNU General Public License
16 *  along with XBMC; see the file COPYING.  If not, see
17 *  <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #include "WINJoystick.h"
22 #include "input/ButtonTranslator.h"
23 #include "settings/AdvancedSettings.h"
24 #include "utils/log.h"
25
26 #include <math.h>
27
28 #include <dinput.h>
29 #include <dinputd.h>
30
31 using namespace std;
32
33 extern HWND g_hWnd;
34
35 #define MAX_AXISAMOUNT  32768
36 #define AXIS_MIN       -32768  /* minimum value for axis coordinate */
37 #define AXIS_MAX        32767  /* maximum value for axis coordinate */
38
39 #if !defined(HAS_SDL)
40 #define SDL_HAT_CENTERED    0x00
41 #define SDL_HAT_UP          0x01
42 #define SDL_HAT_RIGHT       0x02
43 #define SDL_HAT_DOWN        0x04
44 #define SDL_HAT_LEFT        0x08
45 #define SDL_HAT_RIGHTUP     (SDL_HAT_RIGHT|SDL_HAT_UP)
46 #define SDL_HAT_RIGHTDOWN   (SDL_HAT_RIGHT|SDL_HAT_DOWN)
47 #define SDL_HAT_LEFTUP      (SDL_HAT_LEFT|SDL_HAT_UP)
48 #define SDL_HAT_LEFTDOWN    (SDL_HAT_LEFT|SDL_HAT_DOWN)
49 #endif
50
51 CJoystick::CJoystick()
52 {
53   CSingleLock lock(m_critSection);
54   Reset(true);
55   m_joystickEnabled = false;
56   m_NumAxes = 0;
57   m_AxisId = 0;
58   m_JoyId = 0;
59   m_ButtonId = 0;
60   m_HatId = 0;
61   m_HatState = SDL_HAT_CENTERED;
62   m_ActiveFlags = JACTIVE_NONE;
63   SetDeadzone(0);
64
65   m_pDI = NULL;
66   m_lastPressTicks = 0;
67   m_lastTicks = 0;
68 }
69
70 CJoystick::~CJoystick()
71 {
72   ReleaseJoysticks();
73 }
74
75 void CJoystick::ReleaseJoysticks()
76 {
77   CSingleLock lock(m_critSection);
78   // Unacquire the device one last time just in case
79   // the app tried to exit while the device is still acquired.
80   for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
81   {
82     if( (*it) )
83       (*it)->Unacquire();
84     SAFE_RELEASE( (*it) );
85   }
86   m_pJoysticks.clear();
87   m_JoystickNames.clear();
88   m_devCaps.clear();
89   m_HatId = 0;
90   m_ButtonId = 0;
91   m_HatState = SDL_HAT_CENTERED;
92   m_ActiveFlags = JACTIVE_NONE;
93   Reset(true);
94   m_lastPressTicks = 0;
95   m_lastTicks = 0;
96   // Release any DirectInput objects.
97   SAFE_RELEASE( m_pDI );
98 }
99
100 BOOL CALLBACK CJoystick::EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext )
101 {
102   HRESULT hr;
103   CJoystick* p_this = (CJoystick*) pContext;
104   LPDIRECTINPUTDEVICE8    pJoystick = NULL;
105
106   // Obtain an interface to the enumerated joystick.
107   hr = p_this->m_pDI->CreateDevice( pdidInstance->guidInstance, &pJoystick, NULL );
108   if( SUCCEEDED( hr ) )
109   {
110     // Set the data format to "simple joystick" - a predefined data format
111     //
112     // A data format specifies which controls on a device we are interested in,
113     // and how they should be reported. This tells DInput that we will be
114     // passing a DIJOYSTATE2 structure to IDirectInputDevice::GetDeviceState().
115     if( SUCCEEDED( hr = pJoystick->SetDataFormat( &c_dfDIJoystick2 ) ) )
116     {
117       // Set the cooperative level to let DInput know how this device should
118       // interact with the system and with other DInput applications.
119       if( SUCCEEDED( hr = pJoystick->SetCooperativeLevel( g_hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND ) ) )
120       {
121         DIDEVCAPS diDevCaps;
122         diDevCaps.dwSize = sizeof(DIDEVCAPS);
123         if (SUCCEEDED(hr = pJoystick->GetCapabilities(&diDevCaps)))
124         {
125           CLog::Log(LOGNOTICE, __FUNCTION__" : Enabled Joystick: %s", pdidInstance->tszProductName);
126           CLog::Log(LOGNOTICE, __FUNCTION__" : Total Axis: %d Total Hats: %d Total Buttons: %d", diDevCaps.dwAxes, diDevCaps.dwPOVs, diDevCaps.dwButtons);
127           p_this->m_pJoysticks.push_back(pJoystick);
128           p_this->m_JoystickNames.push_back(pdidInstance->tszProductName);
129           p_this->m_devCaps.push_back(diDevCaps);
130         }
131         else
132           CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to GetCapabilities for: %s", pdidInstance->tszProductName);
133       }
134       else
135         CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetCooperativeLevel on: %s", pdidInstance->tszProductName);
136
137     }
138     else
139       CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetDataFormat on: %s", pdidInstance->tszProductName);
140   }
141   else
142     CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to CreateDevice: %s", pdidInstance->tszProductName);
143
144   return DIENUM_CONTINUE;
145 }
146
147 //-----------------------------------------------------------------------------
148 // Name: EnumObjectsCallback()
149 // Desc: Callback function for enumerating objects (axes, buttons, POVs) on a
150 //       joystick. This function enables user interface elements for objects
151 //       that are found to exist, and scales axes min/max values.
152 //-----------------------------------------------------------------------------
153 BOOL CALLBACK CJoystick::EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )
154 {
155
156   LPDIRECTINPUTDEVICE8 pJoy = (LPDIRECTINPUTDEVICE8) pContext;
157
158   // For axes that are returned, set the DIPROP_RANGE property for the
159   // enumerated axis in order to scale min/max values.
160   if( pdidoi->dwType & DIDFT_AXIS )
161   {
162       DIPROPRANGE diprg;
163       diprg.diph.dwSize = sizeof( DIPROPRANGE );
164       diprg.diph.dwHeaderSize = sizeof( DIPROPHEADER );
165       diprg.diph.dwHow = DIPH_BYID;
166       diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis
167       diprg.lMin = AXIS_MIN;
168       diprg.lMax = AXIS_MAX;
169
170       // Set the range for the axis
171       if( FAILED( pJoy->SetProperty( DIPROP_RANGE, &diprg.diph ) ) )
172         CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to set property on %s", pdidoi->tszName);
173   }
174
175   return DIENUM_CONTINUE;
176 }
177
178 void CJoystick::Initialize()
179 {
180   if (!IsEnabled())
181     return;
182
183   HRESULT hr;
184
185   // clear old joystick names
186   ReleaseJoysticks();
187   CSingleLock lock(m_critSection);
188
189   if( FAILED( hr = DirectInput8Create( GetModuleHandle( NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8, ( VOID** )&m_pDI, NULL ) ) )
190   {
191     CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to create DirectInput");
192     return;
193   }
194
195   if( FAILED( hr = m_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY ) ) )
196     return;
197
198   if(m_pJoysticks.size() == 0)
199   {
200     CLog::Log(LOGDEBUG, __FUNCTION__" : No Joystick found");
201     return;
202   }
203
204   for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
205   {
206     LPDIRECTINPUTDEVICE8 pJoy = (*it);
207     // Enumerate the joystick objects. The callback function enabled user
208     // interface elements for objects that are found, and sets the min/max
209     // values property for discovered axes.
210     if( FAILED( hr = pJoy->EnumObjects( EnumObjectsCallback, pJoy, DIDFT_ALL ) ) )
211       CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to enumerate objects");
212   }
213
214   m_JoyId = -1;
215
216   // Set deadzone range
217   SetDeadzone(g_advancedSettings.m_controllerDeadzone);
218 }
219
220 void CJoystick::Reset(bool axis /*=true*/)
221 {
222   if (axis)
223   {
224     SetAxisActive(false);
225     for (int i = 0 ; i<MAX_AXES ; i++)
226     {
227       ResetAxis(i);
228     }
229   }
230 }
231
232 void CJoystick::Update()
233 {
234   if (!IsEnabled())
235     return;
236
237   int buttonId    = -1;
238   int axisId      = -1;
239   int hatId       = -1;
240   int numhat      = -1;
241   int numj        = m_pJoysticks.size();
242   if (numj <= 0)
243     return;
244
245   // go through all joysticks
246   for (int j = 0; j<numj; j++)
247   {
248     LPDIRECTINPUTDEVICE8 pjoy = m_pJoysticks[j];
249     HRESULT hr;
250     DIJOYSTATE2 js;           // DInput joystick state
251
252     m_NumAxes = (m_devCaps[j].dwAxes > MAX_AXES) ? MAX_AXES : m_devCaps[j].dwAxes;
253     numhat    = (m_devCaps[j].dwPOVs > 4) ? 4 : m_devCaps[j].dwPOVs;
254
255     hr = pjoy->Poll();
256     if( FAILED( hr ) )
257     {
258       int i=0;
259       // DInput is telling us that the input stream has been
260       // interrupted. We aren't tracking any state between polls, so
261       // we don't have any special reset that needs to be done. We
262       // just re-acquire and try again.
263       hr = pjoy->Acquire();
264       while( (hr == DIERR_INPUTLOST) && (i++ < 10)  )
265           hr = pjoy->Acquire();
266
267       // hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
268       // may occur when the app is minimized or in the process of
269       // switching, so just try again later
270       return;
271     }
272
273     // Get the input's device state
274     if( FAILED( hr = pjoy->GetDeviceState( sizeof( DIJOYSTATE2 ), &js ) ) )
275       return; // The device should have been acquired during the Poll()
276
277     // get button states first, they take priority over axis
278     for( int b = 0; b < 128; b++ )
279     {
280       if( js.rgbButtons[b] & 0x80 )
281       {
282         m_JoyId = j;
283         buttonId = b+1;
284         j = numj-1;
285         break;
286       }
287     }
288
289     // get hat position
290     m_HatState = SDL_HAT_CENTERED;
291     for (int h = 0; h < numhat; h++)
292     {
293       if((LOWORD(js.rgdwPOV[h]) == 0xFFFF) != true)
294       {
295         m_JoyId = j;
296         hatId = h + 1;
297         j = numj-1;
298         if ( (js.rgdwPOV[0] > JOY_POVLEFT) || (js.rgdwPOV[0] < JOY_POVRIGHT) )
299           m_HatState |= SDL_HAT_UP;
300
301         if ( (js.rgdwPOV[0] > JOY_POVFORWARD) && (js.rgdwPOV[0] < JOY_POVBACKWARD) )
302           m_HatState |= SDL_HAT_RIGHT;
303
304         if ( (js.rgdwPOV[0] > JOY_POVRIGHT) && (js.rgdwPOV[0] < JOY_POVLEFT) )
305           m_HatState |= SDL_HAT_DOWN;
306
307         if ( js.rgdwPOV[0] > JOY_POVBACKWARD )
308           m_HatState |= SDL_HAT_LEFT;
309         break;
310       }
311     }
312
313     // get axis states
314     m_Amount[0] = 0;
315     m_Amount[1] = js.lX;
316     m_Amount[2] = js.lY;
317     m_Amount[3] = js.lZ;
318     m_Amount[4] = js.lRx;
319     m_Amount[5] = js.lRy;
320     m_Amount[6] = js.lRz;
321
322     m_AxisId = GetAxisWithMaxAmount();
323     if (m_AxisId)
324     {
325       m_JoyId = j;
326       j = numj-1;
327       break;
328     }
329   }
330
331   if(hatId==-1)
332   {
333     if(m_HatId!=0)
334       CLog::Log(LOGDEBUG, "Joystick %d hat %d Centered", m_JoyId, abs(hatId));
335     m_pressTicksHat = 0;
336     SetHatActive(false);
337     m_HatId = 0;
338   }
339   else
340   {
341     if(hatId!=m_HatId)
342     {
343       CLog::Log(LOGDEBUG, "Joystick %d hat %u Down", m_JoyId, hatId);
344       m_HatId = hatId;
345       m_pressTicksHat = XbmcThreads::SystemClockMillis();
346     }
347     SetHatActive();
348   }
349
350   if (buttonId==-1)
351   {
352     if (m_ButtonId!=0)
353     {
354       CLog::Log(LOGDEBUG, "Joystick %d button %d Up", m_JoyId, m_ButtonId);
355     }
356     m_pressTicksButton = 0;
357     SetButtonActive(false);
358     m_ButtonId = 0;
359   }
360   else
361   {
362     if (buttonId!=m_ButtonId)
363     {
364       CLog::Log(LOGDEBUG, "Joystick %d button %d Down", m_JoyId, buttonId);
365       m_ButtonId = buttonId;
366       m_pressTicksButton = XbmcThreads::SystemClockMillis();
367     }
368     SetButtonActive();
369   }
370
371 }
372
373 bool CJoystick::GetHat(int &id, int &position,bool consider_repeat)
374 {
375   if (!IsEnabled() || !IsHatActive())
376   {
377     id = position = 0;
378     return false;
379   }
380   position = m_HatState;
381   id = m_HatId;
382   if (!consider_repeat)
383     return true;
384
385   uint32_t nowTicks = 0;
386
387   if ((m_HatId>=0) && m_pressTicksHat)
388   {
389     // return the id if it's the first press
390     if (m_lastPressTicks!=m_pressTicksHat)
391     {
392       m_lastPressTicks = m_pressTicksHat;
393       return true;
394     }
395     nowTicks = XbmcThreads::SystemClockMillis();
396     if ((nowTicks-m_pressTicksHat)<500) // 500ms delay before we repeat
397       return false;
398     if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
399       return false;
400
401     m_lastTicks = nowTicks;
402   }
403
404   return true;
405 }
406
407 bool CJoystick::GetButton(int &id, bool consider_repeat)
408 {
409   if (!IsEnabled() || !IsButtonActive())
410   {
411     id = 0;
412     return false;
413   }
414   if (!consider_repeat)
415   {
416     id = m_ButtonId;
417     return true;
418   }
419
420   uint32_t nowTicks = 0;
421
422   if ((m_ButtonId>=0) && m_pressTicksButton)
423   {
424     // return the id if it's the first press
425     if (m_lastPressTicks!=m_pressTicksButton)
426     {
427       m_lastPressTicks = m_pressTicksButton;
428       id = m_ButtonId;
429       return true;
430     }
431     nowTicks = XbmcThreads::SystemClockMillis();
432     if ((nowTicks-m_pressTicksButton)<500) // 500ms delay before we repeat
433     {
434       return false;
435     }
436     if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
437     {
438       return false;
439     }
440     m_lastTicks = nowTicks;
441   }
442   id = m_ButtonId;
443   return true;
444 }
445
446 bool CJoystick::GetAxis (int &id)
447
448   if (!IsEnabled() || !IsAxisActive()) 
449   {
450     id = 0;
451     return false; 
452   }
453   id = m_AxisId; 
454   return true; 
455 }
456
457 int CJoystick::GetAxisWithMaxAmount()
458 {
459   int maxAmount = 0;
460   int axis = 0;
461   int tempf;
462   for (int i = 1 ; i<=m_NumAxes ; i++)
463   {
464     tempf = abs(m_Amount[i]);
465     if (tempf>m_DeadzoneRange && tempf>maxAmount)
466     {
467       maxAmount = tempf;
468       axis = i;
469     }
470   }
471   SetAxisActive(0 != maxAmount);
472   return axis;
473 }
474
475 float CJoystick::GetAmount(int axis)
476 {
477   if (m_Amount[axis] > m_DeadzoneRange)
478     return (float)(m_Amount[axis]-m_DeadzoneRange)/(float)(MAX_AXISAMOUNT-m_DeadzoneRange);
479   if (m_Amount[axis] < -m_DeadzoneRange)
480     return (float)(m_Amount[axis]+m_DeadzoneRange)/(float)(MAX_AXISAMOUNT-m_DeadzoneRange);
481   return 0;
482 }
483
484 void CJoystick::SetEnabled(bool enabled /*=true*/)
485 {
486   if( enabled && !m_joystickEnabled )
487   {
488     m_joystickEnabled = true;
489     Initialize();
490   }
491   else if( !enabled && m_joystickEnabled )
492   {
493     ReleaseJoysticks();
494     m_joystickEnabled = false;
495   }
496 }
497
498 float CJoystick::SetDeadzone(float val)
499 {
500   if (val<0) val=0;
501   if (val>1) val=1;
502   m_DeadzoneRange = (int)(val*MAX_AXISAMOUNT);
503   return val;
504 }
505
506 bool CJoystick::Reinitialize()
507 {
508   Initialize();
509   return true;
510 }
511
512 void CJoystick::Acquire()
513 {
514   if (!IsEnabled())
515     return;
516   if(!m_pJoysticks.empty())
517   {
518     CLog::Log(LOGDEBUG, __FUNCTION__": Focus back, acquire Joysticks");
519     for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
520     {
521       if( (*it) )
522         (*it)->Acquire();
523     }
524   }
525 }