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