2 * Copyright (C) 2012-2013 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, see
17 * <http://www.gnu.org/licenses/>.
21 #include "WINJoystick.h"
22 #include "input/ButtonTranslator.h"
23 #include "settings/AdvancedSettings.h"
24 #include "utils/log.h"
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 */
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)
51 CJoystick::CJoystick()
53 CSingleLock lock(m_critSection);
55 m_joystickEnabled = false;
61 m_HatState = SDL_HAT_CENTERED;
62 m_ActiveFlags = JACTIVE_NONE;
70 CJoystick::~CJoystick()
75 void CJoystick::ReleaseJoysticks()
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)
84 SAFE_RELEASE( (*it) );
87 m_JoystickNames.clear();
91 m_HatState = SDL_HAT_CENTERED;
92 m_ActiveFlags = JACTIVE_NONE;
96 // Release any DirectInput objects.
97 SAFE_RELEASE( m_pDI );
100 BOOL CALLBACK CJoystick::EnumJoysticksCallback( const DIDEVICEINSTANCE* pdidInstance, VOID* pContext )
103 CJoystick* p_this = (CJoystick*) pContext;
104 LPDIRECTINPUTDEVICE8 pJoystick = NULL;
106 // Obtain an interface to the enumerated joystick.
107 hr = p_this->m_pDI->CreateDevice( pdidInstance->guidInstance, &pJoystick, NULL );
108 if( SUCCEEDED( hr ) )
110 // Set the data format to "simple joystick" - a predefined data format
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 ) ) )
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 ) ) )
122 diDevCaps.dwSize = sizeof(DIDEVCAPS);
123 if (SUCCEEDED(hr = pJoystick->GetCapabilities(&diDevCaps)))
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);
132 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to GetCapabilities for: %s", pdidInstance->tszProductName);
135 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetCooperativeLevel on: %s", pdidInstance->tszProductName);
139 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to SetDataFormat on: %s", pdidInstance->tszProductName);
142 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to CreateDevice: %s", pdidInstance->tszProductName);
144 return DIENUM_CONTINUE;
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 )
156 LPDIRECTINPUTDEVICE8 pJoy = (LPDIRECTINPUTDEVICE8) pContext;
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 )
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;
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);
175 return DIENUM_CONTINUE;
178 void CJoystick::Initialize()
185 // clear old joystick names
187 CSingleLock lock(m_critSection);
189 if( FAILED( hr = DirectInput8Create( GetModuleHandle( NULL ), DIRECTINPUT_VERSION, IID_IDirectInput8, ( VOID** )&m_pDI, NULL ) ) )
191 CLog::Log(LOGDEBUG, __FUNCTION__" : Failed to create DirectInput");
195 if( FAILED( hr = m_pDI->EnumDevices( DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, this, DIEDFL_ATTACHEDONLY ) ) )
198 if(m_pJoysticks.size() == 0)
200 CLog::Log(LOGDEBUG, __FUNCTION__" : No Joystick found");
204 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)
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");
216 // Set deadzone range
217 SetDeadzone(g_advancedSettings.m_controllerDeadzone);
220 void CJoystick::Reset(bool axis /*=true*/)
224 SetAxisActive(false);
225 for (int i = 0 ; i<MAX_AXES ; i++)
232 void CJoystick::Update()
241 int numj = m_pJoysticks.size();
245 // go through all joysticks
246 for (int j = 0; j<numj; j++)
248 LPDIRECTINPUTDEVICE8 pjoy = m_pJoysticks[j];
250 DIJOYSTATE2 js; // DInput joystick state
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;
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();
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
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()
277 // get button states first, they take priority over axis
278 for( int b = 0; b < 128; b++ )
280 if( js.rgbButtons[b] & 0x80 )
290 m_HatState = SDL_HAT_CENTERED;
291 for (int h = 0; h < numhat; h++)
293 if((LOWORD(js.rgdwPOV[h]) == 0xFFFF) != true)
298 if ( (js.rgdwPOV[0] > JOY_POVLEFT) || (js.rgdwPOV[0] < JOY_POVRIGHT) )
299 m_HatState |= SDL_HAT_UP;
301 if ( (js.rgdwPOV[0] > JOY_POVFORWARD) && (js.rgdwPOV[0] < JOY_POVBACKWARD) )
302 m_HatState |= SDL_HAT_RIGHT;
304 if ( (js.rgdwPOV[0] > JOY_POVRIGHT) && (js.rgdwPOV[0] < JOY_POVLEFT) )
305 m_HatState |= SDL_HAT_DOWN;
307 if ( js.rgdwPOV[0] > JOY_POVBACKWARD )
308 m_HatState |= SDL_HAT_LEFT;
318 m_Amount[4] = js.lRx;
319 m_Amount[5] = js.lRy;
320 m_Amount[6] = js.lRz;
322 m_AxisId = GetAxisWithMaxAmount();
334 CLog::Log(LOGDEBUG, "Joystick %d hat %d Centered", m_JoyId, abs(hatId));
343 CLog::Log(LOGDEBUG, "Joystick %d hat %u Down", m_JoyId, hatId);
345 m_pressTicksHat = XbmcThreads::SystemClockMillis();
354 CLog::Log(LOGDEBUG, "Joystick %d button %d Up", m_JoyId, m_ButtonId);
356 m_pressTicksButton = 0;
357 SetButtonActive(false);
362 if (buttonId!=m_ButtonId)
364 CLog::Log(LOGDEBUG, "Joystick %d button %d Down", m_JoyId, buttonId);
365 m_ButtonId = buttonId;
366 m_pressTicksButton = XbmcThreads::SystemClockMillis();
373 bool CJoystick::GetHat(int &id, int &position,bool consider_repeat)
375 if (!IsEnabled() || !IsHatActive())
380 position = m_HatState;
382 if (!consider_repeat)
385 uint32_t nowTicks = 0;
387 if ((m_HatId>=0) && m_pressTicksHat)
389 // return the id if it's the first press
390 if (m_lastPressTicks!=m_pressTicksHat)
392 m_lastPressTicks = m_pressTicksHat;
395 nowTicks = XbmcThreads::SystemClockMillis();
396 if ((nowTicks-m_pressTicksHat)<500) // 500ms delay before we repeat
398 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
401 m_lastTicks = nowTicks;
407 bool CJoystick::GetButton(int &id, bool consider_repeat)
409 if (!IsEnabled() || !IsButtonActive())
414 if (!consider_repeat)
420 uint32_t nowTicks = 0;
422 if ((m_ButtonId>=0) && m_pressTicksButton)
424 // return the id if it's the first press
425 if (m_lastPressTicks!=m_pressTicksButton)
427 m_lastPressTicks = m_pressTicksButton;
431 nowTicks = XbmcThreads::SystemClockMillis();
432 if ((nowTicks-m_pressTicksButton)<500) // 500ms delay before we repeat
436 if ((nowTicks-m_lastTicks)<100) // 100ms delay before successive repeats
440 m_lastTicks = nowTicks;
446 bool CJoystick::GetAxis (int &id)
448 if (!IsEnabled() || !IsAxisActive())
457 int CJoystick::GetAxisWithMaxAmount()
462 for (int i = 1 ; i<=m_NumAxes ; i++)
464 tempf = abs(m_Amount[i]);
465 if (tempf>m_DeadzoneRange && tempf>maxAmount)
471 SetAxisActive(0 != maxAmount);
475 float CJoystick::GetAmount(int axis)
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);
484 void CJoystick::SetEnabled(bool enabled /*=true*/)
486 if( enabled && !m_joystickEnabled )
488 m_joystickEnabled = true;
491 else if( !enabled && m_joystickEnabled )
494 m_joystickEnabled = false;
498 float CJoystick::SetDeadzone(float val)
502 m_DeadzoneRange = (int)(val*MAX_AXISAMOUNT);
506 bool CJoystick::Reinitialize()
512 void CJoystick::Acquire()
516 if(!m_pJoysticks.empty())
518 CLog::Log(LOGDEBUG, __FUNCTION__": Focus back, acquire Joysticks");
519 for(std::vector<LPDIRECTINPUTDEVICE8>::iterator it = m_pJoysticks.begin(); it != m_pJoysticks.end(); ++it)