2 * Copyright (C) 2012 Team XBMC
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)
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.
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
22 #include "WINJoystick.h"
23 #include "input/ButtonTranslator.h"
24 #include "settings/AdvancedSettings.h"
25 #include "utils/log.h"
34 CJoystick g_Joystick; // global
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 */
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)
54 CJoystick::CJoystick()
62 m_HatState = SDL_HAT_CENTERED;
63 m_ActiveFlags = JACTIVE_NONE;
64 for (int i = 0 ; i<MAX_AXES ; i++)
73 CJoystick::~CJoystick()
78 void CJoystick::ReleaseJoysticks()
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)
86 SAFE_RELEASE( (*it) );
89 m_JoystickNames.clear();
91 // Release any DirectInput objects.
92 SAFE_RELEASE( m_pDI );
95 BOOL CALLBACK CJoystick::EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext )
98 CJoystick* p_this = (CJoystick*) pContext;
99 LPDIRECTINPUTDEVICE8 pJoystick = NULL;
101 // Obtain an interface to the enumerated joystick.
102 hr = p_this->m_pDI->CreateDevice( pdidInstance->guidInstance, &pJoystick, NULL );
103 if( SUCCEEDED( hr ) )
105 // Set the data format to "simple joystick" - a predefined data format
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 ) ) )
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 ) ) )
117 diDevCaps.dwSize = sizeof(DIDEVCAPS);
118 if (SUCCEEDED(hr = pJoystick->GetCapabilities(&diDevCaps)))
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);
127 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to GetCapabilities for: %s", pdidInstance->tszProductName);
130 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetCooperativeLevel on: %s", pdidInstance->tszProductName);
134 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetDataFormat on: %s", pdidInstance->tszProductName);
137 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to CreateDevice: %s", pdidInstance->tszProductName);
139 return DIENUM_CONTINUE;
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 )
151 LPDIRECTINPUTDEVICE8 pJoy = (LPDIRECTINPUTDEVICE8) pContext;
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 )
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;
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);
170 return DIENUM_CONTINUE;
173 void CJoystick::Initialize()
177 // clear old joystick names
180 if( FAILED( hr = DirectInput8Create( GetModuleHandle( NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8, ( VOID** )&m_pDI, NULL ) ) )
182 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to create DirectInput");
186 if( FAILED( hr = m_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY ) ) )
189 if(m_pJoysticks.size() == 0)
191 CLog::Log(LOGDEBUG, __FUNCTION__" : No Joystick found");
195 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
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");
207 // Set deadzone range
208 SetDeadzone(g_advancedSettings.m_controllerDeadzone);
211 void CJoystick::Reset(bool axis)
215 SetAxisActive(false);
216 for (int i = 0 ; i<MAX_AXES ; i++)
223 void CJoystick::Update()
229 int numj = m_pJoysticks.size();
233 // go through all joysticks
234 for (int j = 0; j<numj; j++)
236 LPDIRECTINPUTDEVICE8 pjoy = m_pJoysticks[j];
238 DIJOYSTATE2 js; // DInput joystick state
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;
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();
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
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()
265 // get button states first, they take priority over axis
266 for( int b = 0; b < 128; b++ )
268 if( js.rgbButtons[b] & 0x80 )
278 m_HatState = SDL_HAT_CENTERED;
279 for (int h = 0; h < numhat; h++)
281 if((LOWORD(js.rgdwPOV[h]) == 0xFFFF) != true)
286 if ( (js.rgdwPOV[0] > JOY_POVLEFT) || (js.rgdwPOV[0] < JOY_POVRIGHT) )
287 m_HatState |= SDL_HAT_UP;
289 if ( (js.rgdwPOV[0] > JOY_POVFORWARD) && (js.rgdwPOV[0] < JOY_POVBACKWARD) )
290 m_HatState |= SDL_HAT_RIGHT;
292 if ( (js.rgdwPOV[0] > JOY_POVRIGHT) && (js.rgdwPOV[0] < JOY_POVLEFT) )
293 m_HatState |= SDL_HAT_DOWN;
295 if ( js.rgdwPOV[0] > JOY_POVBACKWARD )
296 m_HatState |= SDL_HAT_LEFT;
306 m_Amount[4] = js.lRx;
307 m_Amount[5] = js.lRy;
308 m_Amount[6] = js.lRz;
310 m_AxisId = GetAxisWithMaxAmount();
322 CLog::Log(LOGDEBUG, "Joystick %d hat %d Centered", m_JoyId, abs(hatId));
331 CLog::Log(LOGDEBUG, "Joystick %d hat %u Down", m_JoyId, hatId);
333 m_pressTicksHat = XbmcThreads::SystemClockMillis();
342 CLog::Log(LOGDEBUG, "Joystick %d button %d Up", m_JoyId, m_ButtonId);
344 m_pressTicksButton = 0;
345 SetButtonActive(false);
350 if (buttonId!=m_ButtonId)
352 CLog::Log(LOGDEBUG, "Joystick %d button %d Down", m_JoyId, buttonId);
353 m_ButtonId = buttonId;
354 m_pressTicksButton = XbmcThreads::SystemClockMillis();
361 bool CJoystick::GetHat(int &id, int &position,bool consider_repeat)
365 position = m_HatState;
367 if (!consider_repeat)
370 uint32_t nowTicks = 0;
372 if ((m_HatId>=0) && m_pressTicksHat)
374 // return the id if it's the first press
375 if (m_lastPressTicks!=m_pressTicksHat)
377 m_lastPressTicks = m_pressTicksHat;
380 nowTicks = XbmcThreads::SystemClockMillis();
381 if ((nowTicks-m_pressTicksHat)<500) // 500ms delay before we repeat
383 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
386 m_lastTicks = nowTicks;
392 bool CJoystick::GetButton(int &id, bool consider_repeat)
394 if (!IsButtonActive())
396 if (!consider_repeat)
402 uint32_t nowTicks = 0;
404 if ((m_ButtonId>=0) && m_pressTicksButton)
406 // return the id if it's the first press
407 if (m_lastPressTicks!=m_pressTicksButton)
409 m_lastPressTicks = m_pressTicksButton;
413 nowTicks = XbmcThreads::SystemClockMillis();
414 if ((nowTicks-m_pressTicksButton)<500) // 500ms delay before we repeat
418 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
422 m_lastTicks = nowTicks;
428 int CJoystick::GetAxisWithMaxAmount()
433 for (int i = 1 ; i<=m_NumAxes ; i++)
435 tempf = abs(m_Amount[i]);
436 if (tempf>m_DeadzoneRange && tempf>maxAmount)
442 SetAxisActive(0 != maxAmount);
446 float CJoystick::GetAmount(int axis)
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);
455 float CJoystick::SetDeadzone(float val)
459 m_DeadzoneRange = (int)(val*MAX_AXISAMOUNT);
463 bool CJoystick::Reinitialize()
469 void CJoystick::Acquire()
471 if(!m_pJoysticks.empty())
473 CLog::Log(LOGDEBUG, __FUNCTION__": Focus back, acquire Joysticks");
474 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)