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"
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 */
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)
52 CJoystick::CJoystick()
55 m_joystickEnabled = false;
61 m_HatState = SDL_HAT_CENTERED;
62 m_ActiveFlags = JACTIVE_NONE;
70 CJoystick::~CJoystick()
75 void CJoystick::ReleaseJoysticks()
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)
83 SAFE_RELEASE( (*it) );
86 m_JoystickNames.clear();
90 m_HatState = SDL_HAT_CENTERED;
91 m_ActiveFlags = JACTIVE_NONE;
95 // Release any DirectInput objects.
96 SAFE_RELEASE( m_pDI );
99 BOOL CALLBACK CJoystick::EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext )
102 CJoystick* p_this = (CJoystick*) pContext;
103 LPDIRECTINPUTDEVICE8 pJoystick = NULL;
105 // Obtain an interface to the enumerated joystick.
106 hr = p_this->m_pDI->CreateDevice( pdidInstance->guidInstance, &pJoystick, NULL );
107 if( SUCCEEDED( hr ) )
109 // Set the data format to "simple joystick" - a predefined data format
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 ) ) )
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 ) ) )
121 diDevCaps.dwSize = sizeof(DIDEVCAPS);
122 if (SUCCEEDED(hr = pJoystick->GetCapabilities(&diDevCaps)))
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);
131 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to GetCapabilities for: %s", pdidInstance->tszProductName);
134 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetCooperativeLevel on: %s", pdidInstance->tszProductName);
138 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetDataFormat on: %s", pdidInstance->tszProductName);
141 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to CreateDevice: %s", pdidInstance->tszProductName);
143 return DIENUM_CONTINUE;
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 )
155 LPDIRECTINPUTDEVICE8 pJoy = (LPDIRECTINPUTDEVICE8) pContext;
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 )
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;
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);
174 return DIENUM_CONTINUE;
177 void CJoystick::Initialize()
184 // clear old joystick names
187 if( FAILED( hr = DirectInput8Create( GetModuleHandle( NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8, ( VOID** )&m_pDI, NULL ) ) )
189 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to create DirectInput");
193 if( FAILED( hr = m_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY ) ) )
196 if(m_pJoysticks.size() == 0)
198 CLog::Log(LOGDEBUG, __FUNCTION__" : No Joystick found");
202 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
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");
214 // Set deadzone range
215 SetDeadzone(g_advancedSettings.m_controllerDeadzone);
218 void CJoystick::Reset(bool axis /*=true*/)
222 SetAxisActive(false);
223 for (int i = 0 ; i<MAX_AXES ; i++)
230 void CJoystick::Update()
239 int numj = m_pJoysticks.size();
243 // go through all joysticks
244 for (int j = 0; j<numj; j++)
246 LPDIRECTINPUTDEVICE8 pjoy = m_pJoysticks[j];
248 DIJOYSTATE2 js; // DInput joystick state
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;
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();
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
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()
275 // get button states first, they take priority over axis
276 for( int b = 0; b < 128; b++ )
278 if( js.rgbButtons[b] & 0x80 )
288 m_HatState = SDL_HAT_CENTERED;
289 for (int h = 0; h < numhat; h++)
291 if((LOWORD(js.rgdwPOV[h]) == 0xFFFF) != true)
296 if ( (js.rgdwPOV[0] > JOY_POVLEFT) || (js.rgdwPOV[0] < JOY_POVRIGHT) )
297 m_HatState |= SDL_HAT_UP;
299 if ( (js.rgdwPOV[0] > JOY_POVFORWARD) && (js.rgdwPOV[0] < JOY_POVBACKWARD) )
300 m_HatState |= SDL_HAT_RIGHT;
302 if ( (js.rgdwPOV[0] > JOY_POVRIGHT) && (js.rgdwPOV[0] < JOY_POVLEFT) )
303 m_HatState |= SDL_HAT_DOWN;
305 if ( js.rgdwPOV[0] > JOY_POVBACKWARD )
306 m_HatState |= SDL_HAT_LEFT;
316 m_Amount[4] = js.lRx;
317 m_Amount[5] = js.lRy;
318 m_Amount[6] = js.lRz;
320 m_AxisId = GetAxisWithMaxAmount();
332 CLog::Log(LOGDEBUG, "Joystick %d hat %d Centered", m_JoyId, abs(hatId));
341 CLog::Log(LOGDEBUG, "Joystick %d hat %u Down", m_JoyId, hatId);
343 m_pressTicksHat = XbmcThreads::SystemClockMillis();
352 CLog::Log(LOGDEBUG, "Joystick %d button %d Up", m_JoyId, m_ButtonId);
354 m_pressTicksButton = 0;
355 SetButtonActive(false);
360 if (buttonId!=m_ButtonId)
362 CLog::Log(LOGDEBUG, "Joystick %d button %d Down", m_JoyId, buttonId);
363 m_ButtonId = buttonId;
364 m_pressTicksButton = XbmcThreads::SystemClockMillis();
371 bool CJoystick::GetHat(int &id, int &position,bool consider_repeat)
373 if (!IsEnabled() || !IsHatActive())
378 position = m_HatState;
380 if (!consider_repeat)
383 uint32_t nowTicks = 0;
385 if ((m_HatId>=0) && m_pressTicksHat)
387 // return the id if it's the first press
388 if (m_lastPressTicks!=m_pressTicksHat)
390 m_lastPressTicks = m_pressTicksHat;
393 nowTicks = XbmcThreads::SystemClockMillis();
394 if ((nowTicks-m_pressTicksHat)<500) // 500ms delay before we repeat
396 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
399 m_lastTicks = nowTicks;
405 bool CJoystick::GetButton(int &id, bool consider_repeat)
407 if (!IsEnabled() || !IsButtonActive())
412 if (!consider_repeat)
418 uint32_t nowTicks = 0;
420 if ((m_ButtonId>=0) && m_pressTicksButton)
422 // return the id if it's the first press
423 if (m_lastPressTicks!=m_pressTicksButton)
425 m_lastPressTicks = m_pressTicksButton;
429 nowTicks = XbmcThreads::SystemClockMillis();
430 if ((nowTicks-m_pressTicksButton)<500) // 500ms delay before we repeat
434 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
438 m_lastTicks = nowTicks;
444 bool CJoystick::GetAxis (int &id)
446 if (!IsEnabled() || !IsAxisActive())
455 int CJoystick::GetAxisWithMaxAmount()
460 for (int i = 1 ; i<=m_NumAxes ; i++)
462 tempf = abs(m_Amount[i]);
463 if (tempf>m_DeadzoneRange && tempf>maxAmount)
469 SetAxisActive(0 != maxAmount);
473 float CJoystick::GetAmount(int axis)
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);
482 void CJoystick::SetEnabled(bool enabled /*=true*/)
484 if( enabled && !m_joystickEnabled )
486 m_joystickEnabled = true;
489 else if( !enabled && m_joystickEnabled )
492 m_joystickEnabled = false;
496 float CJoystick::SetDeadzone(float val)
500 m_DeadzoneRange = (int)(val*MAX_AXISAMOUNT);
504 bool CJoystick::Reinitialize()
510 void CJoystick::Acquire()
514 if(!m_pJoysticks.empty())
516 CLog::Log(LOGDEBUG, __FUNCTION__": Focus back, acquire Joysticks");
517 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)