Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions Client/core/CJoystickManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ extern IDirectInput8* g_pDirectInput8;

#define VALID_INDEX_FOR(array, index) (index >= 0 && index < NUMELMS(array))

// How long to wait before retrying a failed joystick detection, and how long a device can go without
// responding before we consider it unplugged
constexpr uint JOYSTICK_RETRY_DELAY_MS = 3000;

SString GUIDToString(const GUID& g)
{
return SString("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4],
Expand Down Expand Up @@ -138,6 +142,7 @@ class CJoystickManager : public CJoystickManagerInterface

// Status
virtual bool IsJoypadConnected();
virtual void OnPossibleDeviceChange();

// Settings
virtual string GetControllerName();
Expand Down Expand Up @@ -182,6 +187,9 @@ class CJoystickManager : public CJoystickManagerInterface
bool m_bXInputDeviceAttached;
uint m_uiXInputReattachDelay;
CElapsedTime m_XInputReattachTimer;
uint m_uiDirectInputReattachDelay;
CElapsedTime m_DirectInputReattachTimer;
CElapsedTime m_PollFailTimer;
bool m_bAutoDeadZoneEnabled;
int m_iAutoDeadZoneCounter;

Expand Down Expand Up @@ -519,6 +527,11 @@ void CJoystickManager::InitDirectInput()
{
WriteDebugEvent("InitDirectInput - SetCooperativeLevel failed");
}

// The device isn't Acquired yet, so its first Poll() is expected to fail. Start the fail timer
// from here, otherwise it would count the time since CJoystickManager was created and could
// drop the device as unresponsive before it was ever given a chance to be polled.
m_PollFailTimer.Reset();
}

///////////////////////////////////////////////////////////////
Expand All @@ -540,6 +553,19 @@ void CJoystickManager::DoPulse()
InitDirectInput();
m_bDoneInit = true;
}
else if (!m_bUseXInput && !m_DevInfo.pDevice)
{
// Not using XInput yet and no DirectInput joystick either, so keep checking both in case
// an XInput pad (e.g. an Xbox controller) got connected after startup
if (IsXInputDeviceAttached())
m_bUseXInput = true;
else if (m_DirectInputReattachTimer.Get() >= m_uiDirectInputReattachDelay)
{
InitDirectInput();
m_DirectInputReattachTimer.Reset();
m_uiDirectInputReattachDelay = JOYSTICK_RETRY_DELAY_MS;
}
}

// Stop if no joystick
if (!IsJoypadConnected())
Expand Down Expand Up @@ -846,9 +872,20 @@ bool CJoystickManager::ReadInputSubsystem(DIJOYSTATE2& js)
// Try to poll
if (FAILED(m_DevInfo.pDevice->Poll()))
{
m_DevInfo.pDevice->Acquire();
if (m_PollFailTimer.Get() > JOYSTICK_RETRY_DELAY_MS)
{
// Been failing to respond for a while, most likely unplugged, so forget it and look for a replacement
m_DevInfo.pDevice->Release();
m_DevInfo.pDevice = nullptr;
m_DevInfo.bDoneEnumAxes = false;
memset(m_DevInfo.axis, 0, sizeof(m_DevInfo.axis));
m_DevInfo.iAxisCount = 0;
}
else
m_DevInfo.pDevice->Acquire();
return false;
}
m_PollFailTimer.Reset();

if (FAILED(m_DevInfo.pDevice->GetDeviceState(sizeof(DIJOYSTATE2), &js)))
return false;
Expand Down Expand Up @@ -961,7 +998,7 @@ bool CJoystickManager::IsXInputDeviceAttached()
if (m_XInputReattachTimer.Get() < m_uiXInputReattachDelay)
return false;
m_XInputReattachTimer.Reset();
m_uiXInputReattachDelay = 3000;
m_uiXInputReattachDelay = JOYSTICK_RETRY_DELAY_MS;

XINPUT_CAPABILITIES Capabilities;
DWORD dwStatus = XInputGetCapabilities(0, XINPUT_FLAG_GAMEPAD, &Capabilities);
Expand Down Expand Up @@ -1176,6 +1213,20 @@ bool CJoystickManager::IsJoypadConnected()
return m_DevInfo.pDevice != NULL;
}

///////////////////////////////////////////////////////////////
//
// CJoystickManager::OnPossibleDeviceChange
//
// Called when Windows tells us a HID device was plugged in or removed, so we
// don't have to wait for the next scheduled retry to notice.
//
///////////////////////////////////////////////////////////////
void CJoystickManager::OnPossibleDeviceChange()
{
m_uiDirectInputReattachDelay = 0;
m_uiXInputReattachDelay = 0;
}

///////////////////////////////////////////////////////////////
//
// CJoystickManager Settings
Expand Down
1 change: 1 addition & 0 deletions Client/core/CJoystickManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class CJoystickManagerInterface

// Status
virtual bool IsJoypadConnected() = 0;
virtual void OnPossibleDeviceChange() = 0;

// Settings
virtual std::string GetControllerName() = 0;
Expand Down
23 changes: 23 additions & 0 deletions Client/core/CMessageLoopHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@

#include "StdInc.h"
#include <game/CGame.h>
#include <dbt.h>

extern CCore* g_pCore;

// GUID_DEVINTERFACE_HID, used to get notified when a HID device (e.g. a joystick) is plugged in or removed
DEFINE_GUID(GUID_DevInterfaceHID, 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);

template <>
CMessageLoopHook* CSingleton<CMessageLoopHook>::m_pSingleton = NULL;

Expand All @@ -31,6 +35,7 @@ CMessageLoopHook::CMessageLoopHook()
m_HookedWindowHandle = NULL;
m_bRefreshMsgQueueEnabled = true;
m_MovementDummyWindow = NULL;
m_hDeviceNotify = nullptr;
}

CMessageLoopHook::~CMessageLoopHook()
Expand Down Expand Up @@ -68,6 +73,14 @@ void CMessageLoopHook::ApplyHook(HWND hFocusWindow)
wcDummy.lpszClassName = "MovementDummy";
wcDummy.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wcDummy);

// Get notified of HID devices (e.g. joysticks) being plugged in or removed, so we can
// check for one straight away instead of waiting for the next scheduled retry
DEV_BROADCAST_DEVICEINTERFACE notificationFilter = {};
notificationFilter.dbcc_size = sizeof(notificationFilter);
notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
notificationFilter.dbcc_classguid = GUID_DevInterfaceHID;
m_hDeviceNotify = RegisterDeviceNotification(hFocusWindow, &notificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
}
}

Expand All @@ -81,6 +94,12 @@ void CMessageLoopHook::RemoveHook()
// Reset the window handle and procedure variables.
m_HookedWindowProc = NULL;
m_HookedWindowHandle = NULL;

if (m_hDeviceNotify)
{
UnregisterDeviceNotification(m_hDeviceNotify);
m_hDeviceNotify = nullptr;
}
}
}

Expand Down Expand Up @@ -245,6 +264,10 @@ LRESULT CALLBACK CMessageLoopHook::ProcessMessage(HWND hwnd, UINT uMsg, WPARAM w
}
}

// A HID device (e.g. joystick) was plugged in or removed
if (uMsg == WM_DEVICECHANGE && (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE))
GetJoystickManager()->OnPossibleDeviceChange();

// Make sure our pointers are valid.
if (pThis != NULL && hwnd == pThis->GetHookedWindowHandle() && g_pCore->AreModulesLoaded())
{
Expand Down
1 change: 1 addition & 0 deletions Client/core/CMessageLoopHook.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CMessageLoopHook : public CSingleton<CMessageLoopHook>
bool m_bRefreshMsgQueueEnabled;
POINT m_MoveOffset;
HWND m_MovementDummyWindow;
HDEVNOTIFY m_hDeviceNotify;

static WPARAM m_LastVirtualKeyCode;
static UCHAR m_LastScanCode;
Expand Down
Loading