#include "DirectInputSystem.h"
#include "Supermodel.h"

#include <wbemidl.h>
#include <oleauto.h>

#include <SDL.h>
#include <SDL_syswm.h>

// TODO - need to double check these all correct and see if can fill in any missing codes (although most just don't exist)
DIKeyMapStruct CDirectInputSystem::s_keyMap[] = 
{
	// General keys
	{ "BACKSPACE",			DIK_BACK },
	{ "TAB",				DIK_TAB },
	//{ "CLEAR",			?? },
	{ "RETURN",				DIK_RETURN },
	{ "PAUSE",				DIK_PAUSE },
	{ "ESCAPE",				DIK_ESCAPE },
	{ "SPACE",				DIK_SPACE },
	//{ "EXCLAIM",			?? },
	//{ "DBLQUOTE",			?? },
	//{ "HASH",				?? }, 
	//{ "DOLLAR",			?? },
	//{ "AMPERSAND",		?? },
	{ "QUOTE",				DIK_APOSTROPHE },
	{ "LEFTPAREN",			DIK_LBRACKET },
	{ "RIGHTPAREN",			DIK_RBRACKET },
	//{ "ASTERISK",			?? },
	//{ "PLUS",				?? },
	{ "COMMA",				DIK_COMMA },
	{ "MINUS",				DIK_MINUS },
	{ "PERIOD",				DIK_PERIOD },
	{ "SLASH",				DIK_SLASH },
	{ "0",					DIK_0 },
	{ "1",					DIK_1 },
	{ "2",					DIK_2 },
	{ "3",					DIK_3 },
	{ "4",					DIK_4 },
	{ "5",					DIK_5 },
	{ "6",					DIK_6 },
	{ "7",					DIK_7 },
	{ "8",					DIK_8 },
	{ "9",					DIK_9 },
	//{ "COLON",			?? },
	{ "SEMICOLON",			DIK_SEMICOLON },
	{ "LESS",				DIK_OEM_102 },
	{ "EQUALS",				DIK_EQUALS },
	//{ "GREATER",			?? },
	//{ "QUESTION",			?? },
	//{ "AT",				?? },
	//{ "LEFTBRACKET",		?? },
	//{ "BACKSLASH",		?? },
	//{ "RIGHTBRACKET",		?? },
	//{ "CARET",			?? },
	//{ "UNDERSCORE",		?? },
	{ "BACKQUOTE",			DIK_GRAVE },
	{ "A",					DIK_A },
	{ "B",					DIK_B },
	{ "C",					DIK_C },
	{ "D",					DIK_D },
	{ "E",					DIK_E },
	{ "F",					DIK_F },
	{ "G",					DIK_G },
	{ "H",					DIK_H },
	{ "I",					DIK_I },
	{ "J",					DIK_J },
	{ "K",					DIK_K },
	{ "L",					DIK_L },
	{ "M",					DIK_M },
	{ "N",					DIK_N },
	{ "O",					DIK_O },
	{ "P",					DIK_P },
	{ "Q",					DIK_Q },
	{ "R",					DIK_R },
	{ "S",					DIK_S },
	{ "T",					DIK_T },
	{ "U",					DIK_U },
	{ "V",					DIK_V },
	{ "W",					DIK_W },
	{ "X",					DIK_X },
	{ "Y",					DIK_Y },
	{ "Z",					DIK_Z },
	{ "DEL",				DIK_DELETE },
	
	// Keypad
	{ "KEYPAD0",			DIK_NUMPAD0 },
	{ "KEYPAD1",			DIK_NUMPAD1 },
	{ "KEYPAD2",			DIK_NUMPAD2 },
	{ "KEYPAD3",			DIK_NUMPAD3 },
	{ "KEYPAD4",			DIK_NUMPAD4 },
	{ "KEYPAD5",			DIK_NUMPAD5 },
	{ "KEYPAD6",			DIK_NUMPAD6 },
	{ "KEYPAD7",			DIK_NUMPAD7 },
	{ "KEYPAD8",			DIK_NUMPAD8 },
	{ "KEYPAD9",			DIK_NUMPAD9 },
	{ "KEYPADPERIOD",		DIK_DECIMAL },
	{ "KEYPADDIVIDE",		DIK_DIVIDE },
	{ "KEYPADMULTIPLY",		DIK_MULTIPLY },
	{ "KEYPADMINUS",		DIK_SUBTRACT },
	{ "KEYPADPLUS",			DIK_ADD },
	{ "KEYPADENTER",		DIK_NUMPADENTER },
	{ "KEYPADEQUALS",		DIK_NUMPADEQUALS },
	
	// Arrows + Home/End Pad
	{ "UP",					DIK_UP },
	{ "DOWN",				DIK_DOWN },
	{ "RIGHT",				DIK_RIGHT },
	{ "LEFT",				DIK_LEFT },
	{ "INSERT",				DIK_INSERT },
	{ "HOME",				DIK_HOME },
	{ "END",				DIK_END },
	{ "PGUP",				DIK_PRIOR },
	{ "PGDN",				DIK_NEXT },

	// Function Key
	{ "F1",					DIK_F1 },
	{ "F2",					DIK_F2 },
	{ "F3",					DIK_F3 },
	{ "F4",					DIK_F4 },
	{ "F5",					DIK_F5 },
	{ "F6",					DIK_F6 },
	{ "F7",					DIK_F7 },
	{ "F8",					DIK_F8 },
	{ "F9",					DIK_F9 },
	{ "F10",				DIK_F10 },
	{ "F11",				DIK_F11 },
	{ "F12",				DIK_F12 },
	{ "F13",				DIK_F13 },
	{ "F14",				DIK_F14 },
	{ "F15",				DIK_F15 },
    
	// Modifier Keys  
	{ "NUMLOCK",			DIK_NUMLOCK },
	{ "CAPSLOCK",			DIK_CAPITAL },
	{ "SCROLLLOCK",			DIK_SCROLL },
	{ "RIGHTSHIFT",			DIK_RSHIFT },
	{ "LEFTSHIFT",			DIK_LSHIFT },
	{ "RIGHTCTRL",			DIK_RCONTROL },
	{ "LEFTCTRL",			DIK_LCONTROL },
	{ "RIGHTALT",			DIK_RMENU },
	{ "LEFTALT",			DIK_LMENU },
	//{ "RIGHTMETA",		?? },
	//{ "LEFTMETA",			?? },
	{ "RIGHTWINDOWS",		DIK_RWIN },
	{ "LEFTWINDOWS",		DIK_LWIN },
	//{ "ALTGR",			?? },
	//{ "COMPOSE",			?? },
    
	// Other
	//{ "HELP",				?? },
	{ "PRINT",				DIK_SYSRQ },
	//{ "SYSREQ",			?? },
	//{ "BREAK",			?? },								
	//{ "MENU",				?? },
	//{ "POWER",			?? },
	//{ "EURO",				?? },
	//{ "UNDO",				?? },
};

bool IsXInputDevice(const GUID &devProdGUID)
{
	// Following code taken from MSDN
	IWbemLocator* pIWbemLocator  = NULL;
    IEnumWbemClassObject* pEnumDevices = NULL;
    IWbemClassObject* pDevices[20] = {0};
    IWbemServices* pIWbemServices = NULL;
    BSTR bstrNamespace = NULL;
    BSTR bstrDeviceID = NULL;
    BSTR bstrClassName = NULL;
    
    // Create WMI
    bool isXInpDev = false;
    HRESULT hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator);
    if (FAILED(hr) || pIWbemLocator == NULL)
        goto exit;

    if ((bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2")) == NULL) goto exit;        
    if ((bstrClassName = SysAllocString(L"Win32_PNPEntity")) == NULL)    goto exit;        
    if ((bstrDeviceID  = SysAllocString(L"DeviceID")) == NULL)           goto exit;        
    
    // Connect to WMI 
    hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);
    if (FAILED(hr) || pIWbemServices == NULL)
        goto exit;

    // Switch security level to IMPERSONATE 
    CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
    
	hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices); 
    if (FAILED(hr) || pEnumDevices == NULL)
        goto exit;

    // Loop over all devices
    for (;;)
    {
        // Get 20 at a time
		DWORD uReturned;
        hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
        if (FAILED(hr) || uReturned == 0)
            goto exit;

        for (unsigned devNum = 0; devNum < uReturned; devNum++)
        {
            // For each device, get its device ID
			VARIANT var;
            hr = pDevices[devNum]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
            if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL)
            {
                // Check if the device ID contains "IG_", which means it's an XInput device (this can't be determined via DirectInput on its own)
                if (wcsstr(var.bstrVal, L"IG_"))
                {
                    // If so, then get VID/PID from var.bstrVal
                    DWORD dwPid = 0, dwVid = 0;
                    WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
                    if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1)
                        dwVid = 0;
                    WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
                    if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
                        dwPid = 0;

                    // Compare VID/PID to values held in DirectInput device's product GUID
                    DWORD dwVidPid = MAKELONG(dwVid, dwPid);
                    if (dwVidPid == devProdGUID.Data1)
                    {
                        isXInpDev = true;
                        goto exit;
                    }
                }
            }   
			if (pDevices[devNum] != NULL)
			{
				pDevices[devNum]->Release();	
				pDevices[devNum] = NULL;
			}
        }
    }

exit:
    if (bstrNamespace)
        SysFreeString(bstrNamespace);
    if (bstrDeviceID)
        SysFreeString(bstrDeviceID);
    if (bstrClassName)
        SysFreeString(bstrClassName);
    for (unsigned devNum = 0; devNum < 20; devNum++)
	{
		if (pDevices[devNum] != NULL)
			pDevices[devNum]->Release();
	}
	if (pEnumDevices != NULL)
		pEnumDevices->Release();
	if (pIWbemLocator != NULL)
		pIWbemLocator->Release();
	if (pIWbemServices != NULL)
		pIWbemServices->Release();
    return isXInpDev;
}

BOOL CALLBACK DI8EnumDevicesCallback(LPCDIDEVICEINSTANCE instance, LPVOID context)
{
	// Keep track of all joystick device GUIDs
	DIEnumDevsContext *diContext = (DIEnumDevsContext*)context;
	DIJoyInfo info;
	memset(&info, 0, sizeof(DIJoyInfo));
	info.guid = instance->guidInstance;
	// If XInput is enabled, see if device is an XInput device
	info.isXInput = diContext->useXInput && IsXInputDevice(instance->guidProduct);
	diContext->infos->push_back(info);
	return DIENUM_CONTINUE;
}

BOOL CALLBACK DI8EnumAxesCallback(LPCDIDEVICEOBJECTINSTANCE instance, LPVOID context)
{
	// Workout which axis is currently being enumerated
	int objNum = DIDFT_GETINSTANCE(instance->dwType);
	DIOBJECTDATAFORMAT fmt = c_dfDIJoystick2.rgodf[objNum];
	int axisNum;
	switch (fmt.dwOfs)
	{
		case DIJOFS_X:  axisNum = AXIS_X; break;
		case DIJOFS_Y:  axisNum = AXIS_Y; break;
		case DIJOFS_Z:  axisNum = AXIS_Z; break;
		case DIJOFS_RX: axisNum = AXIS_RX; break;
		case DIJOFS_RY: axisNum = AXIS_RY; break;
		case DIJOFS_RZ: axisNum = AXIS_RZ; break;
		default:        return DIENUM_CONTINUE;
	}
	
	// Record fact that axis is present and also whether it has force feedback available
	JoyDetails *joyDetails = (JoyDetails*)context;
	joyDetails->hasAxis[axisNum] = true; 
	joyDetails->axisHasFF[axisNum] = !!(instance->dwFlags & DIDOI_FFACTUATOR);
	return DIENUM_CONTINUE;
}

BOOL CALLBACK DI8EnumEffectsCallback(LPCDIEFFECTINFO effectInfo, LPVOID context)
{
	JoyDetails *joyDetails = (JoyDetails*)context;
	// Check joystick has at least one of required types of effects
	if (!!(effectInfo->dwEffType & (DIEFT_CONSTANTFORCE | DIEFT_PERIODIC | DIEFT_CONDITION)))
		joyDetails->hasFFeedback = true;
	return DIENUM_CONTINUE;
}

const char *CDirectInputSystem::ConstructName(bool useRawInput, bool useXInput, bool enableFFeedback)
{
	if (useRawInput)
		return (useXInput ? "RawInput/XInput" : "RawInput/DirectInput");
	else
		return (useXInput ? "Xinput" : "DirectInput");
}

CDirectInputSystem::CDirectInputSystem(bool useRawInput, bool useXInput, bool enableFFeedback) : 
	CInputSystem(ConstructName(useRawInput, useXInput, enableFFeedback)),
	m_useRawInput(useRawInput), m_useXInput(useXInput), m_enableFFeedback(enableFFeedback),
	m_initializedCOM(false), m_activated(false), m_hwnd(NULL), m_screenW(0), m_screenH(0), 
	m_getRIDevListPtr(NULL), m_getRIDevInfoPtr(NULL), m_regRIDevsPtr(NULL), m_getRIDataPtr(NULL),
	m_xiGetCapabilitiesPtr(NULL), m_xiGetStatePtr(NULL), m_xiSetStatePtr(NULL), m_di8(NULL), m_di8Keyboard(NULL), m_di8Mouse(NULL)
{
	// Reset initial states
	memset(&m_combRawMseState, 0, sizeof(RawMseState));
	memset(&m_diKeyState, 0, sizeof(LPDIRECTINPUTDEVICE8));
	memset(&m_diMseState, 0, sizeof(LPDIRECTINPUTDEVICE8));
}

CDirectInputSystem::~CDirectInputSystem()
{
	CloseKeyboardsAndMice();
	CloseJoysticks();

	if (m_di8)
	{
		m_di8->Release();
		m_di8 = NULL;
		if (m_initializedCOM)
			CoUninitialize();
	}
}

bool CDirectInputSystem::GetRegString(HKEY regKey, const char *regPath, string &str)
{
	// Query to get the length
	DWORD dataLen;
	LONG result = RegQueryValueEx(regKey, regPath, NULL, NULL, NULL, &dataLen);
	if (result != ERROR_SUCCESS)
		return false;
	
	// Retrieve the actual data
	char data[MAX_PATH];
	dataLen = min<DWORD>(MAX_PATH - 1, dataLen);
	result = RegQueryValueEx(regKey, regPath, NULL, NULL, (LPBYTE)data, &dataLen);
	if (result != ERROR_SUCCESS)
		return false;
	data[MAX_PATH - 1] = '\0';
	str.assign(data);
	return true;
}

bool CDirectInputSystem::GetRegDeviceName(const char *rawDevName, char *name)
{
	// Check raw device string is in form that can be handled and remove initial 4-char sequence
	// For XP this is: \??\TypeID#HardwareID#InstanceID#{DevicesClasses-id}
	// For Vista/Win7 64bit this is: \\?\TypeID#HardwareID#InstanceID#{DevicesClasses-id}
	string devNameStr(rawDevName);
	if (devNameStr.find("\\??\\") != string::npos || devNameStr.find("\\\\?\\") != string::npos)
		devNameStr.erase(0, 4);
	else 
		return false;

	// Append raw device string to base registry path and convert all #'s to \ in the process
	string regPath = "SYSTEM\\CurrentControlSet\\Enum\\" + devNameStr;
	for (size_t i = 0; i < regPath.size(); i++)
	{
		if (regPath[i] == '#')
			regPath[i] = '\\';
	}

	// Remove part after last \ in path
	size_t last = regPath.rfind('\\');
	if (last != string::npos)
		regPath = regPath.erase(last);

	// Try and open registry key with this path
	HKEY regKey;
	LONG result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regPath.c_str(), 0, KEY_READ, &regKey);
	if (result != ERROR_SUCCESS)
		return false;

	string parentIdStr;

	// Fetch device description from registry, if it exists, and use that for name
	string regStr;
	if (GetRegString(regKey, "DeviceDesc", regStr))
		goto Found;

	// If above failed, then try looking at USB key for HID devices
	RegCloseKey(regKey);

	// Check it is HID device
	if (devNameStr.find("HID") == string::npos)
		return false;

	// Get parent id, from after last \ in name
	last = regPath.rfind('\\');
	if (last == regPath.size() - 1 || last == string::npos)
		return false;
	parentIdStr = regPath.substr(last + 1);

	// Open USB base key
	result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Enum\\USB", 0, KEY_READ, &regKey);
	if (result != ERROR_SUCCESS)
		return false;

	// Loop through all USB devices
	for (int usbIndex = 0; result == ERROR_SUCCESS; usbIndex++)
	{
		// Get sub-key name
		char keyName[MAX_PATH];
		DWORD nameLen = MAX_PATH - 1;
		result = RegEnumKeyEx(regKey, usbIndex, keyName, &nameLen, NULL, NULL, NULL, NULL);
		if (result == ERROR_SUCCESS)
		{
			// Open sub-key
			HKEY subRegKey;
			LONG subResult = RegOpenKeyEx(regKey, keyName, 0, KEY_READ, &subRegKey);
			if (subResult != ERROR_SUCCESS)
				continue;

			// Loop through all sub-keys
			for (int subIndex = 0; subResult == ERROR_SUCCESS; subIndex++)
			{
				//  the next enumerated subkey and scan it
				nameLen = MAX_PATH - 1;
				subResult = RegEnumKeyEx(subRegKey, subIndex, keyName, &nameLen, NULL, NULL, NULL, NULL);
				if (subResult == ERROR_SUCCESS)
				{
					// Open final key
					HKEY finalRegKey;
					LONG finalResult = RegOpenKeyEx(subRegKey, keyName, 0, KEY_READ, &finalRegKey);
					if (finalResult != ERROR_SUCCESS)
						continue;

					// Get parent id prefix and see if it matches
					string finalParentIdStr;
					if (GetRegString(finalRegKey, "ParentIdPrefix", finalParentIdStr) && parentIdStr.compare(0, finalParentIdStr.size(), finalParentIdStr) == 0)
					{
						// Get device description, if it exists, and use that for name
						if (GetRegString(finalRegKey, "DeviceDesc", regStr))
						{
							RegCloseKey(finalRegKey);
							RegCloseKey(subRegKey);
							goto Found;
						}
					}

					// Close final key
					RegCloseKey(finalRegKey);
				}
			}

			// Close sub-key
			RegCloseKey(subRegKey);
		}
	}

	RegCloseKey(regKey);
	return false;

Found:
	// If found device description, name will be from final colon
	last = regStr.rfind(';');
	if (last == regStr.size() - 1 || last == string::npos)
		last = 0;
	else 
		last++;
	strncpy(name, regStr.c_str() + last, MAX_NAME_LENGTH - 1);
	name[MAX_NAME_LENGTH - 1] = '\0';

	RegCloseKey(regKey);
	return true;
}

void CDirectInputSystem::OpenKeyboardsAndMice()
{
	if (m_useRawInput)
	{
		// If RawInput enabled, get list of available devices
		UINT nDevices;
		if (m_getRIDevListPtr(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) == 0 && nDevices > 0)
		{
			PRAWINPUTDEVICELIST pDeviceList = new RAWINPUTDEVICELIST[nDevices];
			if (pDeviceList != NULL && m_getRIDevListPtr(pDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST)) != (UINT)-1)
			{
				// Loop through devices backwards (since new devices are usually added at beginning)
				for (int devNum = nDevices - 1; devNum >= 0; devNum--)
				{
					RAWINPUTDEVICELIST device = pDeviceList[devNum];
					
					// Get device name
					UINT nLength;
					if (m_getRIDevInfoPtr(device.hDevice, RIDI_DEVICENAME, NULL, &nLength) != 0)
						continue;
					nLength = min<int>(MAX_NAME_LENGTH, nLength);
					char name[MAX_NAME_LENGTH];
					if (m_getRIDevInfoPtr(device.hDevice, RIDI_DEVICENAME, name, &nLength) == -1)
						continue;

					// Ignore any RDP devices
					if (strstr(name, "Root#RDP_") != NULL)
						continue;

					// Store details and device handles for attached keyboards and mice
					if (device.dwType == RIM_TYPEKEYBOARD)
					{
						m_rawKeyboards.push_back(device.hDevice);

						KeyDetails keyDetails;
						if (!GetRegDeviceName(name, keyDetails.name))
							strcpy(keyDetails.name, "Unknown Keyboard");
						m_keyDetails.push_back(keyDetails);

						BOOL *pKeyState = new BOOL[255];
						memset(pKeyState, 0, sizeof(BOOL) * 255);
						m_rawKeyStates.push_back(pKeyState);
					}
					else if (device.dwType == RIM_TYPEMOUSE)
					{
						m_rawMice.push_back(device.hDevice);

						MouseDetails mseDetails;
						if (!GetRegDeviceName(name, mseDetails.name))
							strcpy(mseDetails.name, "Unknown Mouse");
						// TODO mseDetails.isAbsolute = ???
						m_mseDetails.push_back(mseDetails);

						RawMseState mseState;
						memset(&mseState, 0, sizeof(RawMseState));
						m_rawMseStates.push_back(mseState);
					}
				}

				DebugLog("RawInput - found %d keyboards and %d mice", m_rawKeyboards.size(), m_rawMice.size());

				// Check some devices were actually found
				m_useRawInput = m_rawKeyboards.size() > 0 && m_rawMice.size() > 0;
			}
			else
			{
				ErrorLog("Unable to query RawInput API for attached devices (error %d) - switching to DirectInput.\n", GetLastError());

				m_useRawInput = false;
			}

			if (pDeviceList != NULL)
				delete[] pDeviceList;
		}
		else
		{
			ErrorLog("Unable to query RawInput API for attached devices (error %d) - switching to DirectInput.\n", GetLastError());

			m_useRawInput = false;
		}

		if (m_useRawInput)
			return;
	}

	// If get here then either RawInput disabled or getting its devices failed so default to DirectInput.
	// Open DirectInput system keyboard and set its data format
	HRESULT hr;
	if (FAILED(hr = m_di8->CreateDevice(GUID_SysKeyboard, &m_di8Keyboard, NULL)))
	{
		ErrorLog("Unable to create DirectInput keyboard device (error %d) - key input will be unavailable.\n", hr);

		m_di8Keyboard = NULL;	
	}
	else if (FAILED(hr = m_di8Keyboard->SetDataFormat(&c_dfDIKeyboard)))
	{
		ErrorLog("Unable to set data format for DirectInput keyboard (error %d) - key input will be unavailable.\n", hr);

		m_di8Keyboard->Release();
		m_di8Keyboard = NULL;
	}
	
	// Open DirectInput system mouse and set its data format
	if (FAILED(hr = m_di8->CreateDevice(GUID_SysMouse, &m_di8Mouse, NULL)))
	{
		ErrorLog("Unable to create DirectInput mouse device (error %d) - mouse input will be unavailable.\n", hr);

		m_di8Mouse = NULL;	
		return;
	}
	if (FAILED(hr = m_di8Mouse->SetDataFormat(&c_dfDIMouse2)))
	{
		ErrorLog("Unable to set data format for DirectInput mouse (error %d) - mouse input will be unavailable.\n", hr);

		m_di8Mouse->Release();
		m_di8Mouse = NULL;
		return;
	}

	// Set mouse axis mode to relative
	DIPROPDWORD dipdw;
	dipdw.diph.dwSize = sizeof(DIPROPDWORD); 
	dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
	dipdw.diph.dwHow = DIPH_DEVICE;
	dipdw.diph.dwObj = 0;
	dipdw.dwData = DIPROPAXISMODE_REL;
	if (FAILED(hr = m_di8Mouse->SetProperty(DIPROP_AXISMODE, &dipdw.diph)))
	{
		ErrorLog("Unable to set axis mode for DirectInput mouse (error %d) - mouse input will be unavailable.\n", hr);

		m_di8Mouse->Release();
		m_di8Mouse = NULL;
	}
}

void CDirectInputSystem::ActivateKeyboardsAndMice()
{
	// Sync up all mice with current cursor position
	ResetMice();

	if (m_useRawInput)
	{
		// Register for RawInput
		RAWINPUTDEVICE rid[2];
		
		// Register for keyboard input
		rid[0].usUsagePage = 0x01;
		rid[0].usUsage = 0x06;
		rid[0].dwFlags = (m_grabMouse ? RIDEV_CAPTUREMOUSE : RIDEV_INPUTSINK) | RIDEV_NOLEGACY;
		rid[0].hwndTarget = m_hwnd;

		// Register for mouse input
		rid[1].usUsagePage = 0x01;
		rid[1].usUsage = 0x02;
		rid[1].dwFlags = (m_grabMouse ? RIDEV_CAPTUREMOUSE : RIDEV_INPUTSINK) | RIDEV_NOLEGACY;
		rid[1].hwndTarget = m_hwnd;

		if (!m_regRIDevsPtr(rid, 2, sizeof(RAWINPUTDEVICE)))
			ErrorLog("Unable to register for keyboard and mouse input with RawInput API (error %d) - keyboard and mouse input will be unavailable.\n", GetLastError());
		return;
	}
	
	// Set DirectInput cooperative level of keyboard and mouse
	if (m_di8Keyboard != NULL)
		m_di8Keyboard->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
	if (m_di8Mouse != NULL)
		m_di8Mouse->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
}

void CDirectInputSystem::PollKeyboardsAndMice()
{
	if (m_useRawInput)
	{
		// For RawInput, only thing to do is update wheelDir from wheelData for each mouse state.  Everything else is updated via WM events.
		for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
		{
			if (it->wheelDelta != 0)
			{
				it->wheelDir = (it->wheelDelta > 0 ? 1 : -1);
				it->wheelDelta = 0;
			}
			else
				it->wheelDir = 0;
		}
		if (m_combRawMseState.wheelDelta != 0)
		{
			m_combRawMseState.wheelDir = (m_combRawMseState.wheelDelta > 0 ? 1 : -1);
			m_combRawMseState.wheelDelta = 0;
		}
		else
			m_combRawMseState.wheelDir = 0;
		return;
	}

	// Get current keyboard state from DirectInput
	HRESULT hr;
	if (m_di8Keyboard != NULL)
	{
		if (FAILED(hr = m_di8Keyboard->Poll()))
		{
			hr = m_di8Keyboard->Acquire();
			while (hr == DIERR_INPUTLOST)
				hr = m_di8Keyboard->Acquire();

			if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
				return;
		}

		// Keep track of keyboard state
		m_di8Keyboard->GetDeviceState(sizeof(m_diKeyState), m_diKeyState);
	}

	// Get current mouse state from DirectInput
	if (m_di8Mouse != NULL)
	{
		if (FAILED(hr = m_di8Mouse->Poll()))
		{
			hr = m_di8Mouse->Acquire();
			while (hr == DIERR_INPUTLOST)
				hr = m_di8Mouse->Acquire();

			if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
				return;
		}

		// Keep track of mouse absolute axis values, clamping them at display edges, aswell as wheel direction and buttons
		DIMOUSESTATE2 mseState;
		m_di8Mouse->GetDeviceState(sizeof(mseState), &mseState);
		m_diMseState.x = CInputSource::Clamp(m_diMseState.x + mseState.lX, m_dispX, m_dispX + m_dispW);
		m_diMseState.y = CInputSource::Clamp(m_diMseState.y + mseState.lY, m_dispY, m_dispY + m_dispH);
		if (mseState.lZ != 0)
		{
			// Z-axis is clamped to range -100 to 100 (DirectInput returns +120 & -120 for wheel delta which are scaled to +5 & -5)
			LONG wheelDelta = 5 * mseState.lZ / 120;
			m_diMseState.z = CInputSource::Clamp(m_diMseState.z + wheelDelta, -100, 100);
			m_diMseState.wheelDir = (wheelDelta > 0 ? 1 : -1);
		}
		else
			m_diMseState.wheelDir = 0;
		memcpy(&m_diMseState.buttons, mseState.rgbButtons, sizeof(m_diMseState.buttons));
	}
}

void CDirectInputSystem::CloseKeyboardsAndMice()
{
	if (m_useRawInput)
	{
		if (m_activated)
		{
			// If RawInput was registered, then unregister now
			RAWINPUTDEVICE rid[2];
			
			// Unregister from keyboard input
			rid[0].usUsagePage = 0x01;
			rid[0].usUsage = 0x06;
			rid[0].dwFlags = RIDEV_REMOVE;
			rid[0].hwndTarget = m_hwnd;

			// Unregister from mouse input
			rid[1].usUsagePage = 0x01;
			rid[1].usUsage = 0x02;
			rid[1].dwFlags = RIDEV_REMOVE;
			rid[1].hwndTarget = m_hwnd;

			m_regRIDevsPtr(rid, 2, sizeof(RAWINPUTDEVICE));
		}

		// Delete storage for keyboards
		for (vector<BOOL*>::iterator it = m_rawKeyStates.begin(); it != m_rawKeyStates.end(); it++)
			delete[] *it;
		m_keyDetails.clear();
		m_rawKeyboards.clear();
		m_rawKeyStates.clear();

		// Delete storage for mice
		m_mseDetails.clear();
		m_rawMice.clear();
		m_rawMseStates.clear();
	}

	// If DirectInput keyboard and mouse were created, then release them too
	if (m_di8Keyboard != NULL)
	{
		m_di8Keyboard->Unacquire();
		m_di8Keyboard->Release();
		m_di8Keyboard = NULL;
	}
	if (m_di8Mouse != NULL)
	{
		m_di8Mouse->Unacquire();
		m_di8Mouse->Release();
		m_di8Mouse = NULL;
	}
}

void CDirectInputSystem::ResetMice()
{
	// Get current mouse cursor position in window
	POINT p;
	if (!GetCursorPos(&p) || !ScreenToClient(m_hwnd, &p))
		return;

	// Set all mice coords to current cursor position
	if (m_useRawInput)
	{
		m_combRawMseState.x = p.x;
		m_combRawMseState.y = p.y;
		m_combRawMseState.z = 0;
		for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
		{
			it->x = p.x;
			it->y = p.y;
			it->z = 0;
		}
	}

	m_diMseState.x = p.x;
	m_diMseState.y = p.y;
	m_diMseState.z = 0;
}

void CDirectInputSystem::ProcessRawInput(HRAWINPUT hInput)
{
	// RawInput data event
	BYTE buffer[4096];
	LPBYTE pBuf = buffer;

	// Get size of data structure to receive
	UINT dwSize;
	if (m_getRIDataPtr(hInput, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) != 0)
		return;
	if (dwSize > sizeof(buffer))
	{
		pBuf = new BYTE[dwSize];
		if (pBuf == NULL)
			return;
	}

	// Get data
	if (m_getRIDataPtr(hInput, RID_INPUT, pBuf, &dwSize, sizeof(RAWINPUTHEADER)) == dwSize)
	{
		RAWINPUT *pData = (RAWINPUT*)pBuf;
		if (pData->header.dwType == RIM_TYPEKEYBOARD)
		{
			// Keyboard event, so identify which keyboard produced event
			BOOL *pKeyState = NULL;
			size_t kbdNum;
			for (kbdNum = 0; kbdNum < m_rawKeyboards.size(); kbdNum++)
			{
				if (m_rawKeyboards[kbdNum] == pData->header.hDevice)
				{
					pKeyState = m_rawKeyStates[kbdNum];
					break;
				}
			}
			
			// Check is a valid keyboard
			if (pKeyState != NULL)
			{
				// Get scancode of key and whether key was pressed or released
				BOOL isRight = pData->data.keyboard.Flags & RI_KEY_E0;
				UINT8 scanCode = (pData->data.keyboard.MakeCode & 0x7f) | (isRight ? 0x80 : 0x00);
				BOOL pressed = !(pData->data.keyboard.Flags & RI_KEY_BREAK);

				// Store current state for key 
				if (scanCode != 0xAA)
					pKeyState[scanCode] = pressed;
			}
		}
		else if (pData->header.dwType == RIM_TYPEMOUSE)
		{
			// Mouse event, so identify which mouse produced event
			RawMseState *pMseState = NULL;
			size_t mseNum;
			for (mseNum = 0; mseNum < m_rawMice.size(); mseNum++)
			{
				if (m_rawMice[mseNum] == pData->header.hDevice)
				{
					pMseState = &m_rawMseStates[mseNum];
					break;
				}
			}

			// Check is a valid mouse
			if (pMseState != NULL)
			{
				// Get X- & Y-axis data
				LONG lx = pData->data.mouse.lLastX;
				LONG ly = pData->data.mouse.lLastY;
				if (pData->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE)
				{
					// If data is absolute, then scale source values (which range 0 to 65535) to screen coordinates and convert
					// to be relative to game window origin
					POINT p;
					p.x = CInputSource::Scale(lx, 0, 0xFFFF, 0, m_screenW);
					p.y = CInputSource::Scale(ly, 0, 0xFFFF, 0, m_screenH);
					if (ScreenToClient(m_hwnd, &p))
					{
						pMseState->x = p.x;
						pMseState->y = p.y;
					}

					// Also update combined state
					m_combRawMseState.x = pMseState->x;
					m_combRawMseState.y = pMseState->y;
				}
				else
				{
					// If data is relative, then keep track of absolute position, clamping it at display edges
					pMseState->x = CInputSource::Clamp(pMseState->x + lx, m_dispX, m_dispX + m_dispW);
					pMseState->y = CInputSource::Clamp(pMseState->y + ly, m_dispY, m_dispY + m_dispH);

					// Also update combined state
					m_combRawMseState.x = CInputSource::Clamp(m_combRawMseState.x + lx, m_dispX, m_dispX + m_dispW);
					m_combRawMseState.y = CInputSource::Clamp(m_combRawMseState.y + ly, m_dispY, m_dispY + m_dispH);
				}

				// Get button flags and wheel delta (RawInput returns +120 & -120 for the latter which are scaled to +5 & -5)
				USHORT butFlags = pData->data.mouse.usButtonFlags;
				LONG wheelDelta = 5 * (SHORT)pData->data.mouse.usButtonData / 120;
				
				// Update Z-axis (wheel) value
				if (butFlags & RI_MOUSE_WHEEL)
				{
					// Z-axis is clamped to range -100 to 100
					pMseState->z = CInputSource::Clamp(pMseState->z + wheelDelta, -100, 100);
					pMseState->wheelDelta += wheelDelta;
				}

				// Keep track of buttons pressed/released
				if      (butFlags & RI_MOUSE_LEFT_BUTTON_DOWN)   pMseState->buttons |= 1;
				else if (butFlags & RI_MOUSE_LEFT_BUTTON_UP)     pMseState->buttons &= ~1;
				if      (butFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN) pMseState->buttons |= 2;
				else if (butFlags & RI_MOUSE_MIDDLE_BUTTON_UP)   pMseState->buttons &= ~2;
				if      (butFlags & RI_MOUSE_RIGHT_BUTTON_DOWN)  pMseState->buttons |= 4;
				else if (butFlags & RI_MOUSE_RIGHT_BUTTON_UP)    pMseState->buttons &= ~4;
				if      (butFlags & RI_MOUSE_BUTTON_4_DOWN)      pMseState->buttons |= 8;
				else if (butFlags & RI_MOUSE_BUTTON_4_UP)        pMseState->buttons &= ~8;
				if      (butFlags & RI_MOUSE_BUTTON_5_DOWN)      pMseState->buttons |= 16;
				else if (butFlags & RI_MOUSE_BUTTON_5_UP)        pMseState->buttons &= ~16;

				// Also update combined state for wheel axis and buttons
				if (butFlags & RI_MOUSE_WHEEL)
				{
					// Z-axis is clamped to range -100 to 100
					m_combRawMseState.z = CInputSource::Clamp(m_combRawMseState.z + wheelDelta, -100, 100);
					m_combRawMseState.wheelDelta += wheelDelta;
				}

				m_combRawMseState.buttons = 0;
				for (vector<RawMseState>::iterator it = m_rawMseStates.begin(); it != m_rawMseStates.end(); it++)
					m_combRawMseState.buttons |= it->buttons;
			}
		}
	}

	if (pBuf != buffer)
		delete[] pBuf;
}

void CDirectInputSystem::OpenJoysticks()
{
	// Get the info about all attached joystick devices
	DIEnumDevsContext diContext;
	diContext.infos = &m_diJoyInfos;
	diContext.useXInput = m_useXInput;
	HRESULT hr;
	if (FAILED(hr = m_di8->EnumDevices(DI8DEVCLASS_GAMECTRL, DI8EnumDevicesCallback, &diContext, DIEDFL_ATTACHEDONLY)))
		return;

	// Loop through those found
	int joyNum = 0;
	int xNum = 0;
	for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
	{
		joyNum++;

		JoyDetails joyDetails;
		memset(&joyDetails, 0, sizeof(joyDetails));

		// See if can use XInput for device
		if (it->isXInput)
		{
			// If so, set joystick details (currently XBox controller is only gamepad handled by XInput and so its capabilities are fixed)
			sprintf(joyDetails.name, "Xbox 360 Controller %d (via XInput)", (xNum + 1));
			joyDetails.numAxes = 6;  // Left & right triggers are mapped to axes in addition to the two analog sticks, giving a total of 6 axes
			joyDetails.numPOVs = 1;  // Digital D-pad
			joyDetails.numButtons = 10;
			joyDetails.hasFFeedback = m_enableFFeedback;
			joyDetails.hasAxis[AXIS_X] = true;
			joyDetails.hasAxis[AXIS_Y] = true;
			joyDetails.hasAxis[AXIS_Z] = true;
			joyDetails.hasAxis[AXIS_RX] = true;
			joyDetails.hasAxis[AXIS_RY] = true;
			joyDetails.hasAxis[AXIS_RZ] = true;
			joyDetails.axisHasFF[AXIS_X] = true;  // Force feedback simulated on left and right sticks
			joyDetails.axisHasFF[AXIS_Y] = true;
			joyDetails.axisHasFF[AXIS_Z] = false;
			joyDetails.axisHasFF[AXIS_RX] = true;
			joyDetails.axisHasFF[AXIS_RY] = true;
			joyDetails.axisHasFF[AXIS_RZ] = false;
			
			// Keep track of XInput device number
			it->xInputNum = xNum++;		
		}
		else 
		{
			// Otherwise, open joystick with DirectInput for given GUID and set its data format
			LPDIRECTINPUTDEVICE8 joystick;
			if (FAILED(hr = m_di8->CreateDevice(it->guid, &joystick, NULL)))
			{
				ErrorLog("Unable to create DirectInput joystick device %d (error %d) - skipping joystick.\n", joyNum, hr);
				continue;
			}
			if (FAILED(hr = joystick->SetDataFormat(&c_dfDIJoystick2)))
			{
				ErrorLog("Unable to set data format for DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
				
				joystick->Release();
				continue;
			}

			// Get joystick's capabilities
			DIDEVCAPS devCaps;
			devCaps.dwSize = sizeof(DIDEVCAPS);
			if (FAILED(hr = joystick->GetCapabilities(&devCaps)))
			{
				ErrorLog("Unable to query capabilities of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);
				
				joystick->Release();
				continue;
			}

			// Gather joystick details (name, num POVs & buttons, which axes are available and whether force feedback is available)
			DIPROPSTRING didps;
			didps.diph.dwSize = sizeof(DIPROPSTRING); 
			didps.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
			didps.diph.dwHow = DIPH_DEVICE;
			didps.diph.dwObj = 0;
			if (FAILED(hr = joystick->GetProperty(DIPROP_INSTANCENAME, &didps.diph)))
			{
				ErrorLog("Unable to get name of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

				joystick->Release();
				continue;
			}
			// DInput returns name as Unicode, convert to ASCII
			int len = min<int>(MAX_NAME_LENGTH, (int)wcslen(didps.wsz) + 1);
			WideCharToMultiByte(CP_ACP, 0, didps.wsz, len, joyDetails.name, len, NULL, NULL);
			joyDetails.name[MAX_NAME_LENGTH - 1] = '\0';
			joyDetails.numPOVs = devCaps.dwPOVs;
			joyDetails.numButtons = devCaps.dwButtons;
			
			// Enumerate axes
			if (FAILED(hr = joystick->EnumObjects(DI8EnumAxesCallback, &joyDetails, DIDFT_AXIS)))
			{
				ErrorLog("Unable to enumerate axes of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

				joystick->Release();
				continue;
			}
			joyDetails.numAxes = 0;
			for (int axisNum = 0; axisNum < NUM_JOY_AXES; axisNum++)
				joyDetails.numAxes += joyDetails.hasAxis[axisNum];
			
			// See if force feedback enabled and is available for joystick 
			if (m_enableFFeedback && (devCaps.dwFlags & DIDC_FORCEFEEDBACK))
			{
				// If so, see what types of effects are available and for which axes
				if (FAILED(hr = joystick->EnumEffects(DI8EnumEffectsCallback, &joyDetails, DIEFT_ALL)))
					ErrorLog("Unable to enumerate effects of DirectInput joystick %d (error %d) - force feedback will be unavailable for joystick.\n", joyNum, hr);
			}

			// Configure axes, if any
			if (joyDetails.numAxes > 0)
			{
				// Set axis range to be from -32768 to 32767
				DIPROPRANGE didpr;
				didpr.diph.dwSize = sizeof(DIPROPRANGE); 
				didpr.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
				didpr.diph.dwHow = DIPH_DEVICE;
				didpr.diph.dwObj = 0;
				didpr.lMin = -32768;
				didpr.lMax = 32767;
				if (FAILED(hr = joystick->SetProperty(DIPROP_RANGE, &didpr.diph)))
				{
					ErrorLog("Unable to set axis range of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

					joystick->Release();
					continue;
				}

				// Set axis mode to absolute
				DIPROPDWORD dipdw;
				dipdw.diph.dwSize = sizeof(DIPROPDWORD); 
				dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
				dipdw.diph.dwHow = DIPH_DEVICE;
				dipdw.diph.dwObj = 0;
				dipdw.dwData = DIPROPAXISMODE_ABS;
				if (FAILED(hr = joystick->SetProperty(DIPROP_AXISMODE, &dipdw.diph)))
				{
					ErrorLog("Unable to set axis mode of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

					joystick->Release();
					continue;
				}

				// Turn off deadzone as handled by this class
				dipdw.dwData = 0;
				if (FAILED(hr = joystick->SetProperty(DIPROP_DEADZONE, &dipdw.diph)))
				{
					ErrorLog("Unable to set deadzone of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

					joystick->Release();
					continue;
				}

				// Turn off saturation as handle by this class
				dipdw.dwData = 10000;
				if (FAILED(hr = joystick->SetProperty(DIPROP_SATURATION, &dipdw.diph)))
				{
					ErrorLog("Unable to set saturation of DirectInput joystick %d (error %d) - skipping joystick.\n", joyNum, hr);

					joystick->Release();
					continue;
				}
			
				// If joystick has force feedback capabilities then disable auto-center
				if (joyDetails.hasFFeedback)
				{
					dipdw.dwData = FALSE;

					if (FAILED(hr = joystick->SetProperty(DIPROP_AUTOCENTER, &dipdw.diph)))
					{
						ErrorLog("Unable to unset auto-center of DirectInput joystick %d (error %d) - force feedback will be unavailable for joystick.\n", joyNum, hr);

						joyDetails.hasFFeedback = false;
					}
				}
			}
		
			// Keep track of DirectInput device number
			it->dInputNum = m_di8Joysticks.size();

			m_di8Joysticks.push_back(joystick);
		}

		// Create initial blank joystick state
		DIJOYSTATE2 joyState;
		memset(&joyState, 0, sizeof(DIJOYSTATE2));
		for (int povNum = 0; povNum < 4; povNum++)
			joyState.rgdwPOV[povNum] = -1;
		
		m_joyDetails.push_back(joyDetails);
		m_diJoyStates.push_back(joyState);
	}
}

void CDirectInputSystem::ActivateJoysticks()
{
	// Set DirectInput cooperative level of joysticks
	unsigned joyNum = 0;
	for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
	{
		if (!it->isXInput)
		{	
			LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[it->dInputNum];
			if (m_joyDetails[joyNum].hasFFeedback)
				joystick->SetCooperativeLevel(m_hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
			else
				joystick->SetCooperativeLevel(m_hwnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND);
		}
		joyNum++;
	}
}

void CDirectInputSystem::PollJoysticks()
{
	// Get current joystick states from XInput and DirectInput
	int i = 0;
	for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
	{
		LPDIJOYSTATE2 pJoyState = &m_diJoyStates[i++];

		HRESULT hr;
		if (it->isXInput)
		{
			// Use XInput to query joystick
			XINPUT_STATE xState;
			memset(&xState, 0, sizeof(XINPUT_STATE));
			if (FAILED(hr = m_xiGetStatePtr(it->xInputNum, &xState)))
			{
				memset(pJoyState, 0, sizeof(DIJOYSTATE2));
				continue;
			}

			// Map XInput state onto joystick's DirectInput state object
			XINPUT_GAMEPAD gamepad = xState.Gamepad;
			pJoyState->lX = (LONG)gamepad.sThumbLX, 
			pJoyState->lY = (LONG)-gamepad.sThumbLY;
			pJoyState->lZ = (LONG)CInputSource::Scale(gamepad.bLeftTrigger, 0, 255, 0, 32767);
			pJoyState->lRx = (LONG)gamepad.sThumbRX;
			pJoyState->lRy = (LONG)-gamepad.sThumbRY;
			pJoyState->lRz = (LONG)CInputSource::Scale(gamepad.bRightTrigger, 0, 255, 0, 32767);
			WORD buttons = gamepad.wButtons;
			BOOL dUp = buttons & XINPUT_GAMEPAD_DPAD_UP;
			BOOL dDown = buttons & XINPUT_GAMEPAD_DPAD_DOWN;
			BOOL dLeft = buttons & XINPUT_GAMEPAD_DPAD_LEFT;
			BOOL dRight = buttons & XINPUT_GAMEPAD_DPAD_RIGHT;
			if (dUp)
			{
				if      (dLeft)  pJoyState->rgdwPOV[0] = 31500;
				else if (dRight) pJoyState->rgdwPOV[0] = 4500;
				else             pJoyState->rgdwPOV[0] = 0;
			}
			else if (dDown)
			{
				if      (dLeft)  pJoyState->rgdwPOV[0] = 22500;
				else if (dRight) pJoyState->rgdwPOV[0] = 13500;
				else             pJoyState->rgdwPOV[0] = 18000;
			}
			else if (dLeft)  pJoyState->rgdwPOV[0] = 27000;
			else if (dRight) pJoyState->rgdwPOV[0] = 9000;
			else             pJoyState->rgdwPOV[0] = -1;
			pJoyState->rgbButtons[0] = !!(buttons & XINPUT_GAMEPAD_A);
			pJoyState->rgbButtons[1] = !!(buttons & XINPUT_GAMEPAD_B);
			pJoyState->rgbButtons[2] = !!(buttons & XINPUT_GAMEPAD_X);
			pJoyState->rgbButtons[3] = !!(buttons & XINPUT_GAMEPAD_Y);
			pJoyState->rgbButtons[4] = !!(buttons & XINPUT_GAMEPAD_LEFT_SHOULDER);
			pJoyState->rgbButtons[5] = !!(buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
			pJoyState->rgbButtons[6] = !!(buttons & XINPUT_GAMEPAD_BACK);
			pJoyState->rgbButtons[7] = !!(buttons & XINPUT_GAMEPAD_START);
			pJoyState->rgbButtons[8] = !!(buttons & XINPUT_GAMEPAD_LEFT_THUMB);
			pJoyState->rgbButtons[9] = !!(buttons & XINPUT_GAMEPAD_RIGHT_THUMB);
		}
		else
		{
			// Use DirectInput to query joystick
			LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[it->dInputNum];
			if (FAILED(hr = joystick->Poll()))
			{
				hr = joystick->Acquire();
				while (hr == DIERR_INPUTLOST)
					hr = joystick->Acquire();

				if (hr == DIERR_OTHERAPPHASPRIO || hr == DIERR_INVALIDPARAM || hr == DIERR_NOTINITIALIZED)
				{
					memset(pJoyState, 0, sizeof(DIJOYSTATE2));
					continue;
				}
			}
		
			// Update joystick's DirectInput state
			joystick->GetDeviceState(sizeof(DIJOYSTATE2), pJoyState);
		}
	}
}

void CDirectInputSystem::CloseJoysticks()
{
	// Release any DirectInput force feedback effects that were created
	for (vector<DIJoyInfo>::iterator it = m_diJoyInfos.begin(); it != m_diJoyInfos.end(); it++)
	{
		for (unsigned axisNum = 0; axisNum < NUM_JOY_AXES; axisNum++)
		{
			for (unsigned effNum = 0; effNum < NUM_FF_EFFECTS; effNum++)
			{
				if (it->dInputEffects[axisNum][effNum] != NULL)
				{
					it->dInputEffects[axisNum][effNum]->Release();
					it->dInputEffects[axisNum][effNum] = NULL;
				}
			}
		}
	}

	// Release each DirectInput joystick
	for (vector<LPDIRECTINPUTDEVICE8>::iterator it = m_di8Joysticks.begin(); it != m_di8Joysticks.end(); it++)
	{
		(*it)->Unacquire();
		(*it)->Release();
	}

	m_joyDetails.clear();
	m_diJoyInfos.clear();
	m_diJoyStates.clear();
	m_di8Joysticks.clear();
}

HRESULT CDirectInputSystem::CreateJoystickEffect(LPDIRECTINPUTDEVICE8 joystick, int axisNum, ForceFeedbackCmd ffCmd, LPDIRECTINPUTEFFECT *pEffect)
{
	// Map axis number to DI object offset
	DWORD axisOfs;
	switch (axisNum)
	{
		case AXIS_X:  axisOfs = DIJOFS_X; break;
		case AXIS_Y:  axisOfs = DIJOFS_Y; break;
		case AXIS_Z:  axisOfs = DIJOFS_Z; break;
		case AXIS_RX: axisOfs = DIJOFS_RX; break;
		case AXIS_RY: axisOfs = DIJOFS_RY; break;
		case AXIS_RZ: axisOfs = DIJOFS_RZ; break;
		default:      return E_FAIL;
	}

	DWORD rgdwAxes[1] = { axisOfs };
	LONG rglDirection[1] = { 0 };
	DICONSTANTFORCE dicf;
	DICONDITION dic;
	DIPERIODIC dip;
	DIENVELOPE die;
	GUID guid;

	// Set common effects parameters
	DIEFFECT eff;
	memset(&eff, 0, sizeof(eff));
	eff.dwSize = sizeof(DIEFFECT);
	eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
	eff.dwTriggerButton = DIEB_NOTRIGGER;
	eff.dwTriggerRepeatInterval = 0;
	eff.dwGain = DI_FFNOMINALMAX;
	eff.cAxes = 1;
	eff.rgdwAxes = rgdwAxes;
	eff.rglDirection = rglDirection;
	eff.dwDuration = INFINITE;
	eff.dwStartDelay = 0;
	
	// Set specific effects parameters
	switch (ffCmd.id)
	{
		case FFStop:
			return E_FAIL;
			
		case FFSelfCenter:
			guid = GUID_Spring;

			dic.lOffset = 0;
			dic.lPositiveCoefficient = 0;
			dic.lNegativeCoefficient = 0;
			dic.dwPositiveSaturation = DI_FFNOMINALMAX;
			dic.dwNegativeSaturation = DI_FFNOMINALMAX;
			dic.lDeadBand = (LONG)0.05 * DI_FFNOMINALMAX;
			
			eff.lpEnvelope = NULL;
			eff.cbTypeSpecificParams = sizeof(DICONDITION);
			eff.lpvTypeSpecificParams = &dic;
			break;

		case FFConstantForce:
			guid = GUID_ConstantForce;

			dicf.lMagnitude = 0;
			
			eff.lpEnvelope = NULL;
			eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
			eff.lpvTypeSpecificParams = &dicf;
			break;

		case FFVibrate:
			guid = GUID_Sine;

			dip.dwMagnitude = 0;
			dip.lOffset  = 0;
            dip.dwPhase  = 0;
            dip.dwPeriod = (DWORD)0.1 * DI_SECONDS; // 1/10th second
				
			die.dwSize = sizeof(die);
			die.dwAttackLevel = 0;
			die.dwAttackTime = (DWORD)0.5 * DI_SECONDS;
			die.dwFadeLevel = 0;
			die.dwFadeTime = (DWORD)0.5 * DI_SECONDS;

			eff.lpEnvelope = &die;
			eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
			eff.lpvTypeSpecificParams = &dip;
			break;
	}

	joystick->Acquire();

	HRESULT hr;
	if (FAILED(hr = joystick->CreateEffect(guid, &eff, pEffect, NULL)))
		return hr;
	if (*pEffect == NULL)
		return E_FAIL;
	(*pEffect)->Start(1, 0);
	return S_OK;
}

bool CDirectInputSystem::InitializeSystem()
{
	if (m_useRawInput)
	{
		// Dynamically load RawInput API
		HMODULE user32 = LoadLibrary(TEXT("user32.dll"));
		if (user32 != NULL)
		{
			m_getRIDevListPtr = (GetRawInputDeviceListPtr)GetProcAddress(user32, "GetRawInputDeviceList");
			m_getRIDevInfoPtr = (GetRawInputDeviceInfoPtr)GetProcAddress(user32, "GetRawInputDeviceInfoA");
			m_regRIDevsPtr = (RegisterRawInputDevicesPtr)GetProcAddress(user32, "RegisterRawInputDevices");
			m_getRIDataPtr = (GetRawInputDataPtr)GetProcAddress(user32, "GetRawInputData");
			m_useRawInput = m_getRIDevListPtr != NULL && m_getRIDevInfoPtr != NULL && m_regRIDevsPtr != NULL && m_getRIDataPtr != NULL;
		}
		else
			m_useRawInput = false;

		if (m_useRawInput)
		{
			// Get screen resolution (needed for absolute mouse devices)
			DEVMODEA settings;
			if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &settings))
			{
				ErrorLog("Unable to read current display settings\n");
				return false;
			}
			m_screenW = settings.dmPelsWidth;
			m_screenH = settings.dmPelsHeight;
		}
		else
			ErrorLog("Unable to initialize RawInput API (library hooks are not available) - switching to DirectInput.\n");
	}

	if (m_useXInput)
	{
		// Dynamically load XInput API
		HMODULE xInput = LoadLibrary(TEXT(XINPUT_DLL_A));
		if (xInput != NULL)
		{
			m_xiGetCapabilitiesPtr = (XInputGetCapabilitiesPtr)GetProcAddress(xInput, "XInputGetCapabilities");
			m_xiGetStatePtr = (XInputGetStatePtr)GetProcAddress(xInput, "XInputGetState");
			m_xiSetStatePtr = (XInputSetStatePtr)GetProcAddress(xInput, "XInputSetState");
			m_useXInput = m_xiGetCapabilitiesPtr != NULL && m_xiGetStatePtr != NULL && m_xiSetStatePtr != NULL;
		}
		else
			m_useXInput = false;

		if (!m_useXInput)
			ErrorLog("Unable to initialize XInput API (library hooks are not available) - switching to DirectInput.\n");
	}

	// Dynamically create DirectInput8 via COM, rather than statically linking to dinput8.dll
	// TODO - if fails, try older versions of DirectInput
	HRESULT hr;
	if (SUCCEEDED(hr = CoInitialize(NULL)))
		m_initializedCOM = true;
	else
	{
		// CoInitialize fails if called from managed context (ie .NET debugger) so check for this and ignore this error
		if (hr != RPC_E_CHANGED_MODE)
		{
			ErrorLog("Unable to initialize COM (error %d).\n", hr);

			return false;
		}
	}
	if (FAILED(hr = CoCreateInstance(CLSID_DirectInput8, NULL, CLSCTX_INPROC_SERVER, IID_IDirectInput8A, (LPVOID*)&m_di8)))
	{
		ErrorLog("Unable to initialize DirectInput API (error %d) - is DirectX 8 or later installed?\n", hr);
		
		if (m_initializedCOM)
			CoUninitialize();
		return false;
	}
	if (FAILED(hr = m_di8->Initialize(GetModuleHandle(NULL), DIRECTINPUT_VERSION)))
	{
		ErrorLog("Unable to initialize DirectInput API (error %d) - is DirectX 8 or later installed?\n", hr);

		m_di8->Release();
		m_di8 = NULL;
		if (m_initializedCOM)
			CoUninitialize();
		return false;
	}

	// Open all devices
	OpenKeyboardsAndMice();
	OpenJoysticks();

	return true;
}

int CDirectInputSystem::GetKeyIndex(const char *keyName)
{
	for (int i = 0; i < NUM_DI_KEYS; i++)
	{
		if (stricmp(keyName, s_keyMap[i].keyName) == 0)
			return i;
	}
	return -1;
}

const char *CDirectInputSystem::GetKeyName(int keyIndex)
{
	if (keyIndex < 0 || keyIndex >= NUM_DI_KEYS)
		return NULL;
	return s_keyMap[keyIndex].keyName;
}

bool CDirectInputSystem::IsKeyPressed(int kbdNum, int keyIndex)
{
	// Get DI key code (scancode) for given key index
	int diKey = s_keyMap[keyIndex].diKey;
	
	if (m_useRawInput)
	{
		// For RawInput, check if key is currently pressed for given keyboard number
		BOOL *keyState = m_rawKeyStates[kbdNum];
		return !!keyState[diKey];
	}

	// For DirectInput, just check common keyboard state
	return !!(m_diKeyState[diKey] & 0x80);
}

int CDirectInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
{
	if (m_useRawInput)
	{
		// For RawInput, get combined or individual mouse state and return value for given axis
		// The cursor is always hidden when using RawInput, so it does not matter if these values don't match with the cursor (with multiple
		// mice the cursor is irrelevant anyway)
		RawMseState *pMseState = (mseNum == ANY_MOUSE ? &m_combRawMseState : &m_rawMseStates[mseNum]);
		switch (axisNum)
		{
			case AXIS_X: return pMseState->x;
			case AXIS_Y: return pMseState->y;
			case AXIS_Z: return pMseState->z;
			default:     return 0;
		}
	}
	
	// For DirectInput, for X- and Y-axes just use cursor position within window if available (so that mouse movements sync with the cursor)
	if (axisNum == AXIS_X || axisNum == AXIS_Y)
	{
		POINT p;
		if (GetCursorPos(&p) && ScreenToClient(m_hwnd, &p))
			return (axisNum == AXIS_X ? p.x : p.y);
	}

	// Otherwise, return the raw DirectInput axis values
	switch (axisNum)
	{
		case AXIS_X: return m_diMseState.x;
		case AXIS_Y: return m_diMseState.y;
		case AXIS_Z: return m_diMseState.z;
		default:     return 0;
	}
}

int CDirectInputSystem::GetMouseWheelDir(int mseNum)
{
	if (m_useRawInput)
	{
		// For RawInput, return the wheel value for combined or individual mouse state
		return (mseNum == ANY_MOUSE ? m_combRawMseState.wheelDir : m_rawMseStates[mseNum].wheelDir);
	}

	// For DirectInput just return the common wheel value
	return m_diMseState.wheelDir;
}

bool CDirectInputSystem::IsMouseButPressed(int mseNum, int butNum)
{
	if (m_useRawInput)
	{
		// For RawInput, return the button state for combined or individual mouse state
		return !!((mseNum == ANY_MOUSE ? m_combRawMseState.buttons : m_rawMseStates[mseNum].buttons) & (1<<butNum));
	}
	
	// For DirectInput just return the common button state (taking care with the middle and right mouse buttons
	// which DirectInput numbers 2 and 1 respectively, rather than 1 and 2)
	if      (butNum == 1) butNum = 2;
	else if (butNum == 2) butNum = 1;
	return (butNum < 5 ? !!(m_diMseState.buttons[butNum] & 0x80) : false);
}

int CDirectInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
{
	// Return raw value for given joystick number and axis (values range from -32768 to 32767)
	switch (axisNum)
	{
		case AXIS_X:  return (int)m_diJoyStates[joyNum].lX;
		case AXIS_Y:  return (int)m_diJoyStates[joyNum].lY;
		case AXIS_Z:  return (int)m_diJoyStates[joyNum].lZ;
		case AXIS_RX: return (int)m_diJoyStates[joyNum].lRx;
		case AXIS_RY: return (int)m_diJoyStates[joyNum].lRy;
		case AXIS_RZ: return (int)m_diJoyStates[joyNum].lRz;
		default:      return 0;
	}
}

bool CDirectInputSystem::IsJoyPOVInDir(int joyNum, int povNum, int povDir)
{
	// Check if POV-hat value for given joystick number and POV is pointing in required direction
	int povVal = m_diJoyStates[joyNum].rgdwPOV[povNum] / 100;   // DirectInput value is angle of POV-hat in 100ths of a degree
	switch (povDir)
	{
		case POV_UP:    return povVal == 315 || povVal == 0 || povVal == 45;
		case POV_DOWN:  return povVal == 135 || povVal == 180 || povVal == 225;
		case POV_RIGHT: return povVal == 45 || povVal == 90 || povVal == 135;
		case POV_LEFT:  return povVal == 225 || povVal == 270 || povVal == 315;
		default:        return false;
	}
}

bool CDirectInputSystem::IsJoyButPressed(int joyNum, int butNum)
{
	// Get joystick state for given joystick and return current button value for given button number
	return !!m_diJoyStates[joyNum].rgbButtons[butNum];
}

bool CDirectInputSystem::ProcessForceFeedbackCmd(int joyNum, int axisNum, ForceFeedbackCmd ffCmd)
{
	DIJoyInfo *pInfo = &m_diJoyInfos[joyNum];

	HRESULT hr;
	if (pInfo->isXInput)
	{	
		XINPUT_VIBRATION vibration;
		switch (ffCmd.id)
		{
			case FFStop:
				if (axisNum == AXIS_X || axisNum == AXIS_Y)
					vibration.wLeftMotorSpeed = 0;
				else if (axisNum == AXIS_RX || axisNum == AXIS_RY)
					vibration.wRightMotorSpeed = 0;
				else
					return false;
				return SUCCEEDED(hr = m_xiSetStatePtr(pInfo->xInputNum, &vibration));

			case FFConstantForce:
				if (axisNum == AXIS_X || axisNum == AXIS_Y)
					vibration.wLeftMotorSpeed = ffCmd.data; // TODO - scale data to 0-65535
				else if (axisNum == AXIS_RX || axisNum == AXIS_RY) 
					vibration.wRightMotorSpeed = ffCmd.data; // TODO - scale data to 0-65535
				else
					return false;
				return SUCCEEDED(hr = m_xiSetStatePtr(pInfo->xInputNum, &vibration));

			default:
				// TODO - other force feedback types
				return false;
		}
	}
	else
	{
		LPDIRECTINPUTDEVICE8 joystick = m_di8Joysticks[pInfo->dInputNum];
		
		// See if command is to stop all force feedback
		if (ffCmd.id == -1)
			return SUCCEEDED(hr = joystick->SendForceFeedbackCommand(DISFFC_STOPALL));

		// Create effect for given axis if has not already been created
		int effNum = (int)ffCmd.id;
		LPDIRECTINPUTEFFECT *pEffect = &pInfo->dInputEffects[axisNum][effNum];
		if ((*pEffect) == NULL)
		{
			if (FAILED(hr = CreateJoystickEffect(joystick, axisNum, ffCmd, pEffect)))
				return false;
			
		}
		
		LONG rglDirection[1] = { 0 };
		DICONSTANTFORCE dicf;
		DICONDITION dic;
		DIPERIODIC dip;
		DIENVELOPE die;
				
		// Set common parameters
		DIEFFECT eff;
		memset(&eff, 0, sizeof(eff));
		eff.dwSize = sizeof(DIEFFECT);
		eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
		eff.cAxes = 1;
		eff.rglDirection = rglDirection;
		eff.dwStartDelay = 0;
				
		switch (ffCmd.id)	
		{
			case FFSelfCenter:
				dic.lPositiveCoefficient = ffCmd.data;
				dic.lNegativeCoefficient = ffCmd.data;
				dic.dwPositiveSaturation = DI_FFNOMINALMAX;
				dic.dwNegativeSaturation = DI_FFNOMINALMAX;
				dic.lDeadBand = (LONG)0.05 * DI_FFNOMINALMAX;

				eff.lpEnvelope = NULL;
				eff.cbTypeSpecificParams = sizeof(DICONDITION);
				eff.lpvTypeSpecificParams = &dic;
				break;

			case FFConstantForce:
				dicf.lMagnitude = ffCmd.data; // TODO - scale & cap at +/- DI_FFNOMINALMAX
				
				eff.lpEnvelope = NULL;
				eff.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
				eff.lpvTypeSpecificParams = &dicf;
				break;

			case FFVibrate:
				dip.dwMagnitude = ffCmd.data;
				dip.lOffset = 0;
                dip.dwPhase = 0;
                dip.dwPeriod = (DWORD)0.1 * DI_SECONDS; // 1/10th second

				die.dwSize = sizeof(die);
				die.dwAttackLevel = 0;
				die.dwAttackTime = (DWORD)0.5 * DI_SECONDS;
				die.dwFadeLevel = 0;
				die.dwFadeTime = (DWORD)0.5 * DI_SECONDS;

				eff.lpEnvelope = &die;
				eff.cbTypeSpecificParams = sizeof(DIPERIODIC);
				eff.lpvTypeSpecificParams = &dip;
				break;

			default:
				return false;
		}

		// Now set the new parameters and start the effect immediately.
		return SUCCEEDED(hr = (*pEffect)->SetParameters(&eff, DIEP_DIRECTION | DIEP_TYPESPECIFICPARAMS | DIEP_START));
	}
}

void CDirectInputSystem::Wait(int ms)
{
	Sleep(ms);
}

bool CDirectInputSystem::ConfigMouseCentered()
{
	// When checking if mouse centered, use system cursor rather than raw values (otherwise user's mouse movements won't match up
	// with onscreen cursor during configuration)
	POINT p;
	if (!GetCursorPos(&p) || !ScreenToClient(m_hwnd, &p))
		return false;
	
	// See if mouse in center of display
	unsigned lx = m_dispX + m_dispW / 4;
	unsigned ly = m_dispY + m_dispH / 4;
	if (p.x < (LONG)lx || p.x > (LONG)(lx + m_dispW / 2) || p.y < (LONG)ly || p.y > (LONG)(ly + m_dispH / 2))
		return false;

	// Once mouse has been centered, sync up mice raw values with current cursor position so that movements are detected correctly
	ResetMice();
	return true;
}

CInputSource *CDirectInputSystem::CreateAnyMouseSource(EMousePart msePart)
{
	// If using RawInput, create a mouse source that uses the combined mouse state m_combRawState, rather than combining all the individual mouse
	// sources in the default manner
	if (m_useRawInput)
		return CreateMouseSource(ANY_MOUSE, msePart);

	return CInputSystem::CreateAnyMouseSource(msePart);
}

int CDirectInputSystem::GetNumKeyboards()
{
	// If RawInput enabled, then return number of keyboards found.  Otherwise, return ANY_KEYBOARD as DirectInput cannot handle multiple keyboards
	return (m_useRawInput ? m_rawKeyboards.size() : ANY_KEYBOARD);
}
	
int CDirectInputSystem::GetNumMice()
{
	// If RawInput enabled, then return number of mice found.  Otherwise, return ANY_MOUSE as DirectInput cannot handle multiple keyboards
	return (m_useRawInput ? m_rawMice.size() : ANY_MOUSE);
}
	
int CDirectInputSystem::GetNumJoysticks()
{
	// Return number of joysticks found
	return m_diJoyInfos.size();
}

const KeyDetails *CDirectInputSystem::GetKeyDetails(int kbdNum)
{
	// If RawInput enabled, then return details for given keyboard.  Otherwise, return NULL as DirectInput cannot handle multiple keyboards
	return (m_useRawInput ? &m_keyDetails[kbdNum] : NULL);
}

const MouseDetails *CDirectInputSystem::GetMouseDetails(int mseNum)
{
	// If RawInput enabled, then return details of given mouse.  Otherwise, return NULL as DirectInput cannot handle multiple keyboards
	return (m_useRawInput ? &m_mseDetails[mseNum] : NULL);
}

const JoyDetails *CDirectInputSystem::GetJoyDetails(int joyNum)
{
	return &m_joyDetails[joyNum];
}

bool CDirectInputSystem::Poll()
{
	// See if keyboard, mice and joysticks have been activated yet
	if (!m_activated)
	{
		// If not, then get Window handle of SDL window
		SDL_SysWMinfo info;
        SDL_VERSION(&info.version);
        if (SDL_GetWMInfo(&info)) 
			m_hwnd = info.window;
		
		// Tell SDL to pass on all Windows events
		// Removed - see below
		//SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);

		// Activate the devices now that a Window handle is available
		ActivateKeyboardsAndMice();
		ActivateJoysticks();

		m_activated = true;
	}

	// Wait or poll for event from SDL
	// Removed - see below
	/*
	SDL_Event e;
	while (SDL_PollEvent(&e))
	{
		if (e.type == SDL_QUIT)
			return false; 	
		else if (e.type == SDL_SYSWMEVENT)
		{
			SDL_SysWMmsg *wmMsg = e.syswm.msg;
			ProcessWMEvent(wmMsg->hwnd, wmMsg->msg, wmMsg->wParam, wmMsg->lParam);
		}
	}*/

	// Wait or poll for event on Windows message queue (done this way instead of using SDL_PollEvent as above because
	// for some reason this causes RawInput HRAWINPUT handles to arrive stale.  Not sure what SDL_PollEvent is doing to cause this
	// but the following code can replace it without any problems as it is effectively what SDL_PollEvent does anyway)
	MSG msg;
	while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) 
	{
		int ret = GetMessage(&msg, NULL, 0, 0);
		if (ret == 0)
			return false;
		else if (ret > 0)
		{
			TranslateMessage(&msg);

			// Handle RawInput messages here
			if (m_useRawInput && msg.message == WM_INPUT)
				ProcessRawInput((HRAWINPUT)msg.lParam);

			// Propagate all messages to default (SDL) handlers
			DispatchMessage(&msg);
		}
	}

	// Poll keyboards, mice and joysticks
	PollKeyboardsAndMice();
	PollJoysticks();

	return true;
}

void CDirectInputSystem::SetMouseVisibility(bool visible)
{
	if (m_useRawInput)
		ShowCursor(!m_grabMouse && visible ? TRUE : FALSE);
	else
		ShowCursor(visible ? TRUE : FALSE);
}

void CDirectInputSystem::GrabMouse()
{
	CInputSystem::GrabMouse();

	SetMouseVisibility(false);

	// When grabbing mouse, make sure devices get re-activated
	ActivateKeyboardsAndMice();
	ActivateJoysticks();
}

void CDirectInputSystem::UngrabMouse()
{
	CInputSystem::UngrabMouse();

	// When ungrabbing mouse place, make sure devices get re-activated
	ActivateKeyboardsAndMice();
	ActivateJoysticks();
}