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