// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "IrrCompileConfig.h" #ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ #include "CIrrDeviceWin32.h" #include "IEventReceiver.h" #include "irrList.h" #include "os.h" #include "CTimer.h" #include "irrString.h" #include "COSOperator.h" #include "dimension2d.h" #include "IGUISpriteBank.h" #include #include #include #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) // This define will switch to use XInput 9.1, which does not // require an installer and works on most windows platforms. // See https://blogs.msdn.microsoft.com/chuckw/2012/04/25/xinput-and-windows-8/ #undef _WIN32_WINNT #define _WIN32_WINNT 0x0601 // Don't change the case of xinput.h, otherwise cygwin compilation fails. #include #ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ #define DIRECTINPUT_VERSION 0x0800 #include #include #include #ifdef _MSC_VER #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") #endif #else #ifdef _MSC_VER #pragma comment(lib, "winmm.lib") #endif #endif #endif #include #pragma comment(lib, "imm32.lib") namespace irr { namespace video { #ifdef _IRR_COMPILE_WITH_DIRECT3D_8_ IVideoDriver* createDirectX8Driver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, HWND window); #endif #ifdef _IRR_COMPILE_WITH_DIRECT3D_9_ IVideoDriver* createDirectX9Driver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, HWND window); #endif #ifdef _IRR_COMPILE_WITH_OPENGL_ IVideoDriver* createOpenGLDriver(const irr::SIrrlichtCreationParameters& params, io::IFileSystem* io, CIrrDeviceWin32* device); #endif } } // end namespace irr namespace irr { struct SJoystickWin32Control { CIrrDeviceWin32* Device; #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_) IDirectInput8* DirectInputDevice; #endif #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) struct JoystickInfo { u32 Index; #ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ core::stringc Name; GUID guid; LPDIRECTINPUTDEVICE8 lpdijoy; DIDEVCAPS devcaps; u8 axisValid[8]; #else JOYCAPS Caps; #endif /** For xbox-360 controller. */ bool m_use_xinput; }; core::array ActiveJoysticks; #endif SJoystickWin32Control(CIrrDeviceWin32* dev) : Device(dev) { #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_) DirectInputDevice=0; if (DI_OK != (DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&DirectInputDevice, NULL))) { os::Printer::log("Could not create DirectInput8 Object", ELL_WARNING); return; } #endif } ~SJoystickWin32Control() { #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_) for(u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) { LPDIRECTINPUTDEVICE8 dev = ActiveJoysticks[joystick].lpdijoy; if (dev) { dev->Unacquire(); } dev->Release(); } if (DirectInputDevice) DirectInputDevice->Release(); #endif } #if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_) && defined(_IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_) static BOOL CALLBACK EnumJoysticks(LPCDIDEVICEINSTANCE lpddi, LPVOID cp) { SJoystickWin32Control* p=(SJoystickWin32Control*)cp; p->directInputAddJoystick(lpddi); return DIENUM_CONTINUE; } bool IsXInputDevice(const GUID* pGuidProductFromDirectInput); void directInputAddJoystick(LPCDIDEVICEINSTANCE lpddi) { //Get the GUID of the joystuck const GUID guid = lpddi->guidInstance; JoystickInfo activeJoystick; activeJoystick.m_use_xinput = isXInputDevice(&lpddi->guidProduct); activeJoystick.Index=ActiveJoysticks.size(); activeJoystick.guid=guid; activeJoystick.Name=lpddi->tszProductName; if (FAILED(DirectInputDevice->CreateDevice(guid, &activeJoystick.lpdijoy, NULL))) { os::Printer::log("Could not create DirectInput device", ELL_WARNING); return; } activeJoystick.devcaps.dwSize=sizeof(activeJoystick.devcaps); if (FAILED(activeJoystick.lpdijoy->GetCapabilities(&activeJoystick.devcaps))) { os::Printer::log("Could not create DirectInput device", ELL_WARNING); return; } if (FAILED(activeJoystick.lpdijoy->SetCooperativeLevel(Device->HWnd, DISCL_BACKGROUND | DISCL_EXCLUSIVE))) { os::Printer::log("Could not set DirectInput device cooperative level", ELL_WARNING); return; } if (FAILED(activeJoystick.lpdijoy->SetDataFormat(&c_dfDIJoystick2))) { os::Printer::log("Could not set DirectInput device data format", ELL_WARNING); return; } if (FAILED(activeJoystick.lpdijoy->Acquire())) { os::Printer::log("Could not set DirectInput cooperative level", ELL_WARNING); return; } DIJOYSTATE2 info; if (FAILED(activeJoystick.lpdijoy->GetDeviceState(sizeof(info),&info))) { os::Printer::log("Could not read DirectInput device state", ELL_WARNING); return; } ZeroMemory(activeJoystick.axisValid,sizeof(activeJoystick.axisValid)); activeJoystick.axisValid[0]= (info.lX!=0) ? 1 : 0; activeJoystick.axisValid[1]= (info.lY!=0) ? 1 : 0; activeJoystick.axisValid[2]= (info.lZ!=0) ? 1 : 0; activeJoystick.axisValid[3]= (info.lRx!=0) ? 1 : 0; activeJoystick.axisValid[4]= (info.lRy!=0) ? 1 : 0; activeJoystick.axisValid[5]= (info.lRz!=0) ? 1 : 0; int caxis=0; for (u8 i=0; i<6; i++) { if (activeJoystick.axisValid[i]) caxis++; } for (u8 i=0; i<(activeJoystick.devcaps.dwAxes)-caxis; i++) { if (i+caxis < 8) activeJoystick.axisValid[i+caxis]=1; } // On Xbox 360 devices left and right trigger are reported as two // different axes (instead of 1 with DirectInput), so we need one // additional axis: if (activeJoystick.m_use_xinput && activeJoystick.devcaps.dwAxes == 5) activeJoystick.devcaps.dwAxes = 6; ActiveJoysticks.push_back(activeJoystick); } //----------------------------------------------------------------------------- /** Taken from: * https://msdn.microsoft.com/en-us/library/windows/desktop/dd940435(v=vs.85).aspx */ template void SAFE_RELEASE(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } /** Taken from: * https://msdn.microsoft.com/en-us/library/windows/desktop/ee417014(v=vs.85).aspx */ //----------------------------------------------------------------------------- // Enum each PNP device using WMI and check each device ID to see if it contains // "IG_" (ex. "VID_045E&PID_028E&IG_00"). If it does, then it's an XInput device // Unfortunately this information can not be found by just using DirectInput //----------------------------------------------------------------------------- bool isXInputDevice(const GUID* pGuidProductFromDirectInput) { IWbemLocator* pIWbemLocator = NULL; IEnumWbemClassObject* pEnumDevices = NULL; IWbemClassObject* pDevices[20] = { 0 }; IWbemServices* pIWbemServices = NULL; BSTR bstrNamespace = NULL; BSTR bstrDeviceID = NULL; BSTR bstrClassName = NULL; DWORD uReturned = 0; bool bIsXinputDevice = false; UINT iDevice = 0; VARIANT var; HRESULT hr; // CoInit if needed hr = CoInitialize(NULL); bool bCleanupCOM = SUCCEEDED(hr); // Create WMI hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator); if (FAILED(hr) || pIWbemLocator == NULL) goto LCleanup; bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (bstrNamespace == NULL) goto LCleanup; bstrClassName = SysAllocString(L"Win32_PNPEntity"); if (bstrClassName == NULL) goto LCleanup; bstrDeviceID = SysAllocString(L"DeviceID"); if (bstrDeviceID == NULL) goto LCleanup; // Connect to WMI hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices); if (FAILED(hr) || pIWbemServices == NULL) goto LCleanup; // 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 LCleanup; // Loop over all devices for (;; ) { // Get 20 at a time hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned); if (FAILED(hr)) goto LCleanup; if (uReturned == 0) break; for (iDevice = 0; iDeviceGet(bstrDeviceID, 0L, &var, NULL, NULL); if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) { // Check if the device ID contains "IG_". If it does, then it's an XInput device // This information can not be found from DirectInput if (wcsstr(var.bstrVal, L"IG_")) { // If it does, then get the 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 the VID/PID to the DInput device DWORD dwVidPid = MAKELONG(dwVid, dwPid); if (dwVidPid == pGuidProductFromDirectInput->Data1) { bIsXinputDevice = true; goto LCleanup; } } } SAFE_RELEASE(&pDevices[iDevice]); } } LCleanup: if (bstrNamespace) SysFreeString(bstrNamespace); if (bstrDeviceID) SysFreeString(bstrDeviceID); if (bstrClassName) SysFreeString(bstrClassName); for (iDevice = 0; iDevice<20; iDevice++) SAFE_RELEASE(&pDevices[iDevice]); SAFE_RELEASE(&pEnumDevices); SAFE_RELEASE(&pIWbemLocator); SAFE_RELEASE(&pIWbemServices); if (bCleanupCOM) CoUninitialize(); return bIsXinputDevice; } // ------------------------------------------------------------------------ #endif void pollJoysticks() { if (0 == ActiveJoysticks.size()) return; #if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ #ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ u32 joystick; DIJOYSTATE2 info; for(joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) { SEvent event; event.EventType = irr::EET_JOYSTICK_INPUT_EVENT; event.JoystickEvent.Joystick = (u8)joystick; if (ActiveJoysticks[joystick].m_use_xinput) { XINPUT_STATE state; memset(&state, 0, sizeof(state)); DWORD result = XInputGetState(ActiveJoysticks[joystick].Index, &state); // XInput reports the buttons in a different order. So to keep // old configs to work as expected, remap the buttons. int abxy = (state.Gamepad.wButtons & (XINPUT_GAMEPAD_A | XINPUT_GAMEPAD_B | XINPUT_GAMEPAD_X | XINPUT_GAMEPAD_Y ) ) >> 12; int shoulder = (state.Gamepad.wButtons & (XINPUT_GAMEPAD_LEFT_SHOULDER| XINPUT_GAMEPAD_RIGHT_SHOULDER) ) >> 4; int start = (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) << 3; int back = (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK ) << 1; event.JoystickEvent.ButtonStates = abxy | shoulder | start | back; int angle = 65535; if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) { if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) angle = 22500; else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 13500; else angle = 18000; } else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) { if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) angle = 31500; else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 4500; else angle = 0; } else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) angle = 9000; else if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) angle = 27000; event.JoystickEvent.POV = angle; // Map the axis as they were previously, so existing configs // still work as expected. The Y axis needs to be reversed: // -32768 --> 32767, ..., 32767 --> -32768 // Inverting the bits with ~ does that (-sThumbLY would map -32768 to -32768!!) event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] = state.Gamepad.sThumbLX; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = ~state.Gamepad.sThumbLY; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] = state.Gamepad.bLeftTrigger * 128; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = ~state.Gamepad.sThumbRY; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] = state.Gamepad.sThumbRX; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] = state.Gamepad.bRightTrigger * 128; (void)Device->postEventFromUser(event); continue; } // needs to be reset for each joystick // request ALL values and POV as continuous if possible const DIDEVCAPS & caps = ActiveJoysticks[joystick].devcaps; // if no POV is available don't ask for POV values if (!FAILED(ActiveJoysticks[joystick].lpdijoy->GetDeviceState(sizeof(info),&info))) { event.JoystickEvent.POV = (u16)info.rgdwPOV[0]; // set to undefined if no POV value was returned or the value // is out of range if ((caps.dwPOVs==0) || (event.JoystickEvent.POV > 35900)) event.JoystickEvent.POV = 65535; for(int axis = 0; axis < SEvent::SJoystickEvent::NUMBER_OF_AXES; ++axis) event.JoystickEvent.Axis[axis] = 0; u16 dxAxis=0; u16 irrAxis=0; while (dxAxis < 6 && irrAxis 0) axisFound=1; if (axisFound) { s32 val=axisValue - 32768; if (val <-32767) val=-32767; if (val > 32767) val=32767; event.JoystickEvent.Axis[irrAxis]=(s16)(val); irrAxis++; } dxAxis++; } u32 buttons=0; BYTE* bytebuttons=info.rgbButtons; for (u16 i=0; i<32; i++) { if (bytebuttons[i] >0) { buttons |= (1 << i); } } event.JoystickEvent.ButtonStates = buttons; (void)Device->postEventFromUser(event); } } #else u32 joystick; for(joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) { SEvent event; event.EventType = irr::EET_JOYSTICK_INPUT_EVENT; event.JoystickEvent.Joystick = (u8)joystick; for (int axis = 0; axis < SEvent::SJoystickEvent::NUMBER_OF_AXES; ++axis) event.JoystickEvent.Axis[axis] = 0; if (ActiveJoysticks[joystick].m_use_xinput) { XINPUT_STATE state; memset(&state, 0, sizeof(state)); DWORD result = XInputGetState(ActiveJoysticks[joystick].Index, &state); event.JoystickEvent.ButtonStates = state.Gamepad.wButtons; // Thumb values are in [-32768, 32767] event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] = state.Gamepad.sThumbRX; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] = state.Gamepad.sThumbRY; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = state.Gamepad.sThumbLX; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] = state.Gamepad.sThumbLY; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = state.Gamepad.bLeftTrigger*128; event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] = state.Gamepad.bRightTrigger*128; } else // old API { JOYINFOEX info; // needs to be reset for each joystick // request ALL values and POV as continuous if possible info.dwSize = sizeof(info); info.dwFlags = JOY_RETURNALL | JOY_RETURNPOVCTS; const JOYCAPS & caps = ActiveJoysticks[joystick].Caps; // if no POV is available don't ask for POV values if (!(caps.wCaps & JOYCAPS_HASPOV)) info.dwFlags &= ~(JOY_RETURNPOV | JOY_RETURNPOVCTS); if (joyGetPosEx(ActiveJoysticks[joystick].Index, &info) != JOYERR_NOERROR) continue; event.JoystickEvent.POV = (u16)info.dwPOV; // set to undefined if no POV value was returned or the value // is out of range if (!(info.dwFlags & JOY_RETURNPOV) || (event.JoystickEvent.POV > 35900)) event.JoystickEvent.POV = 65535; event.JoystickEvent.ButtonStates = info.dwButtons; switch(caps.wNumAxes) { default: case 6: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] = (s16)((65535 * (info.dwVpos - caps.wVmin)) / (caps.wVmax - caps.wVmin) - 32768); case 5: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] = (s16)((65535 * (info.dwUpos - caps.wUmin)) / (caps.wUmax - caps.wUmin) - 32768); case 4: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = (s16)((65535 * (info.dwRpos - caps.wRmin)) / (caps.wRmax - caps.wRmin) - 32768); case 3: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] = (s16)((65535 * (info.dwZpos - caps.wZmin)) / (caps.wZmax - caps.wZmin) - 32768); case 2: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = (s16)((65535 * (info.dwYpos - caps.wYmin)) / (caps.wYmax - caps.wYmin) - 32768); case 1: event.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] = (s16)((65535 * (info.dwXpos - caps.wXmin)) / (caps.wXmax - caps.wXmin) - 32768); } // switch } // if XInput ... else (void)Device->postEventFromUser(event); } // for all joysticks #endif #endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ } /** This function is ported from SDL and released under zlip: * Copyright (C) 1997-2014 Sam Lantinga */ void setJoystickName(int index, const JOYCAPS &caps, SJoystickInfo *joystick) { // As a default use the name given in the joystick structure // - though that is always the same name, independent of joystick :( joystick->Name = caps.szPname; joystick->HasGenericName = true; core::stringc key = core::stringc(REGSTR_PATH_JOYCONFIG)+"\\"+caps.szRegKey + "\\"+REGSTR_KEY_JOYCURR; HKEY hTopKey = HKEY_LOCAL_MACHINE; HKEY hKey; long regresult = RegOpenKeyExA(hTopKey, key.c_str(), 0, KEY_READ, &hKey); if (regresult != ERROR_SUCCESS) { hTopKey = HKEY_CURRENT_USER; regresult = RegOpenKeyExA(hTopKey, key.c_str(), 0, KEY_READ, &hKey); } if (regresult != ERROR_SUCCESS) return; /* find the registry key name for the joystick's properties */ char regname[256]; DWORD regsize = sizeof(regname); core::stringc regvalue = core::stringc("Joystick")+core::stringc(index+1) + REGSTR_VAL_JOYOEMNAME; regresult = RegQueryValueExA(hKey, regvalue.c_str(), 0, 0, (LPBYTE)regname, ®size); RegCloseKey(hKey); if (regresult != ERROR_SUCCESS) return; /* open that registry key */ core::stringc regkey = core::stringc(REGSTR_PATH_JOYOEM)+"\\"+regname; regresult = RegOpenKeyExA(hTopKey, regkey.c_str(), 0, KEY_READ, &hKey); if (regresult != ERROR_SUCCESS) return; /* find the size for the OEM name text */ regsize = sizeof(regvalue); regresult = RegQueryValueExA(hKey, REGSTR_VAL_JOYOEMNAME, 0, 0, NULL, ®size); if (regresult == ERROR_SUCCESS) { char *name; /* allocate enough memory for the OEM name text ... */ name = new char[regsize]; if (name) { /* ... and read it from the registry */ regresult = RegQueryValueExA(hKey, REGSTR_VAL_JOYOEMNAME, 0, 0, (LPBYTE)name, ®size ); joystick->Name = name; joystick->HasGenericName = false; } // if name } // if SUCCESS RegCloseKey(hKey); } bool activateJoysticks(core::array & joystickInfo) { #if defined _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ #ifdef _IRR_COMPILE_WITH_DIRECTINPUT_JOYSTICK_ if (!DirectInputDevice || (DirectInputDevice->EnumDevices(DI8DEVCLASS_GAMECTRL, SJoystickWin32Control::EnumJoysticks, this, DIEDFL_ATTACHEDONLY ))) { os::Printer::log("Could not enum DirectInput8 controllers", ELL_WARNING); return false; } for(u32 joystick = 0; joystick < ActiveJoysticks.size(); ++joystick) { JoystickInfo& activeJoystick = ActiveJoysticks[joystick]; SJoystickInfo info; info.Axes=activeJoystick.devcaps.dwAxes; info.Buttons=activeJoystick.devcaps.dwButtons; info.Name=activeJoystick.Name; info.PovHat = (activeJoystick.devcaps.dwPOVs != 0) ? SJoystickInfo::POV_HAT_PRESENT : SJoystickInfo::POV_HAT_ABSENT; joystickInfo.push_back(info); } return true; #else joystickInfo.clear(); ActiveJoysticks.clear(); // This number includes XInput and legacy devices const u32 numberOfJoysticks = ::joyGetNumDevs(); JOYINFOEX info; info.dwSize = sizeof(info); info.dwFlags = JOY_RETURNALL; JoystickInfo activeJoystick; SJoystickInfo returnInfo; joystickInfo.reallocate(numberOfJoysticks); ActiveJoysticks.reallocate(numberOfJoysticks); // First discover all Xbox 360 controllers, which need to use // XInput to get the state (otherwise the two triggers can not // be used at the same time). u32 joystick = 0; for (int i = 0; i < XUSER_MAX_COUNT; i++) { XINPUT_STATE state; memset(&state, 0, sizeof(state)); DWORD result = XInputGetState(i, &state); // Not connected or not an XInput device if (result != ERROR_SUCCESS) continue; activeJoystick.Index = i; activeJoystick.m_use_xinput = true; ActiveJoysticks.push_back(activeJoystick); // This information is returned to the calling program SJoystickInfo joy_info; // 2 sticks with 2 directions each plus two triggers joy_info.Axes = 6; joy_info.Buttons = 30; joy_info.Name = "XBOX 360 Gamepad"; joy_info.HasGenericName = false; joy_info.PovHat = SJoystickInfo::POV_HAT_PRESENT; joystickInfo.push_back(joy_info); } // for i < XUSER_MAX_COUNT for(; joystick < numberOfJoysticks; ++joystick) { if(JOYERR_NOERROR == joyGetPosEx(joystick, &info) && JOYERR_NOERROR == joyGetDevCaps(joystick, &activeJoystick.Caps, sizeof(activeJoystick.Caps))) { setJoystickName(joystick, activeJoystick.Caps, &returnInfo); core::stringc low_name = returnInfo.Name.make_lower(); // Ignore xbox controller, which are handled using XInput above if (low_name.find("xbox") != -1) continue; activeJoystick.Index = joystick; activeJoystick.m_use_xinput = false; ActiveJoysticks.push_back(activeJoystick); returnInfo.Joystick = (u8)joystick; returnInfo.Axes = activeJoystick.Caps.wNumAxes; returnInfo.Buttons = activeJoystick.Caps.wNumButtons; returnInfo.PovHat = ((activeJoystick.Caps.wCaps & JOYCAPS_HASPOV) == JOYCAPS_HASPOV) ? SJoystickInfo::POV_HAT_PRESENT : SJoystickInfo::POV_HAT_ABSENT; joystickInfo.push_back(returnInfo); } } for(joystick = 0; joystick < joystickInfo.size(); ++joystick) { char logString[256]; (void)sprintf(logString, "Found joystick %d, %d axes, %d buttons '%s'", joystick, joystickInfo[joystick].Axes, joystickInfo[joystick].Buttons, joystickInfo[joystick].Name.c_str()); os::Printer::log(logString, ELL_INFORMATION); } return true; #endif #else return false; #endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_ } }; } // end namespace irr // Get the codepage from the locale language id // Based on the table from http://www.science.co.il/Language/Locale-Codes.asp?s=decimal static unsigned int LocaleIdToCodepage(unsigned int lcid) { switch ( lcid ) { case 1098: // Telugu case 1095: // Gujarati case 1094: // Punjabi case 1103: // Sanskrit case 1111: // Konkani case 1114: // Syriac case 1099: // Kannada case 1102: // Marathi case 1125: // Divehi case 1067: // Armenian case 1081: // Hindi case 1079: // Georgian case 1097: // Tamil return 0; case 1054: // Thai return 874; case 1041: // Japanese return 932; case 2052: // Chinese (PRC) case 4100: // Chinese (Singapore) return 936; case 1042: // Korean return 949; case 5124: // Chinese (Macau S.A.R.) case 3076: // Chinese (Hong Kong S.A.R.) case 1028: // Chinese (Taiwan) return 950; case 1048: // Romanian case 1060: // Slovenian case 1038: // Hungarian case 1051: // Slovak case 1045: // Polish case 1052: // Albanian case 2074: // Serbian (Latin) case 1050: // Croatian case 1029: // Czech return 1250; case 1104: // Mongolian (Cyrillic) case 1071: // FYRO Macedonian case 2115: // Uzbek (Cyrillic) case 1058: // Ukrainian case 2092: // Azeri (Cyrillic) case 1092: // Tatar case 1087: // Kazakh case 1059: // Belarusian case 1088: // Kyrgyz (Cyrillic) case 1026: // Bulgarian case 3098: // Serbian (Cyrillic) case 1049: // Russian return 1251; case 8201: // English (Jamaica) case 3084: // French (Canada) case 1036: // French (France) case 5132: // French (Luxembourg) case 5129: // English (New Zealand) case 6153: // English (Ireland) case 1043: // Dutch (Netherlands) case 9225: // English (Caribbean) case 4108: // French (Switzerland) case 4105: // English (Canada) case 1110: // Galician case 10249: // English (Belize) case 3079: // German (Austria) case 6156: // French (Monaco) case 12297: // English (Zimbabwe) case 1069: // Basque case 2067: // Dutch (Belgium) case 2060: // French (Belgium) case 1035: // Finnish case 1080: // Faroese case 1031: // German (Germany) case 3081: // English (Australia) case 1033: // English (United States) case 2057: // English (United Kingdom) case 1027: // Catalan case 11273: // English (Trinidad) case 7177: // English (South Africa) case 1030: // Danish case 13321: // English (Philippines) case 15370: // Spanish (Paraguay) case 9226: // Spanish (Colombia) case 5130: // Spanish (Costa Rica) case 7178: // Spanish (Dominican Republic) case 12298: // Spanish (Ecuador) case 17418: // Spanish (El Salvador) case 4106: // Spanish (Guatemala) case 18442: // Spanish (Honduras) case 3082: // Spanish (International Sort) case 13322: // Spanish (Chile) case 19466: // Spanish (Nicaragua) case 2058: // Spanish (Mexico) case 10250: // Spanish (Peru) case 20490: // Spanish (Puerto Rico) case 1034: // Spanish (Traditional Sort) case 14346: // Spanish (Uruguay) case 8202: // Spanish (Venezuela) case 1089: // Swahili case 1053: // Swedish case 2077: // Swedish (Finland) case 5127: // German (Liechtenstein) case 1078: // Afrikaans case 6154: // Spanish (Panama) case 4103: // German (Luxembourg) case 16394: // Spanish (Bolivia) case 2055: // German (Switzerland) case 1039: // Icelandic case 1057: // Indonesian case 1040: // Italian (Italy) case 2064: // Italian (Switzerland) case 2068: // Norwegian (Nynorsk) case 11274: // Spanish (Argentina) case 1046: // Portuguese (Brazil) case 1044: // Norwegian (Bokmal) case 1086: // Malay (Malaysia) case 2110: // Malay (Brunei Darussalam) case 2070: // Portuguese (Portugal) return 1252; case 1032: // Greek return 1253; case 1091: // Uzbek (Latin) case 1068: // Azeri (Latin) case 1055: // Turkish return 1254; case 1037: // Hebrew return 1255; case 5121: // Arabic (Algeria) case 15361: // Arabic (Bahrain) case 9217: // Arabic (Yemen) case 3073: // Arabic (Egypt) case 2049: // Arabic (Iraq) case 11265: // Arabic (Jordan) case 13313: // Arabic (Kuwait) case 12289: // Arabic (Lebanon) case 4097: // Arabic (Libya) case 6145: // Arabic (Morocco) case 8193: // Arabic (Oman) case 16385: // Arabic (Qatar) case 1025: // Arabic (Saudi Arabia) case 10241: // Arabic (Syria) case 14337: // Arabic (U.A.E.) case 1065: // Farsi case 1056: // Urdu case 7169: // Arabic (Tunisia) return 1256; case 1061: // Estonian case 1062: // Latvian case 1063: // Lithuanian return 1257; case 1066: // Vietnamese return 1258; } return 65001; // utf-8 } namespace { struct SEnvMapper { HWND hWnd; irr::CIrrDeviceWin32* irrDev; }; irr::core::list EnvMap; HKL KEYBOARD_INPUT_HKL=0; unsigned int KEYBOARD_INPUT_CODEPAGE = 1252; } SEnvMapper* getEnvMapperFromHWnd(HWND hWnd) { irr::core::list::Iterator it = EnvMap.begin(); for (; it!= EnvMap.end(); ++it) if ((*it).hWnd == hWnd) return &(*it); return 0; } irr::CIrrDeviceWin32* getDeviceFromHWnd(HWND hWnd) { irr::core::list::Iterator it = EnvMap.begin(); for (; it!= EnvMap.end(); ++it) if ((*it).hWnd == hWnd) return (*it).irrDev; return 0; } namespace irr { void updateICPos(void* hWnd, s32 x, s32 y, s32 height) { COMPOSITIONFORM cf; HWND hwnd = (HWND)hWnd; HIMC hIMC = ImmGetContext(hwnd); if(hIMC) { cf.dwStyle = CFS_POINT; cf.ptCurrentPos.x = x; cf.ptCurrentPos.y = y - height; ImmSetCompositionWindow(hIMC, &cf); ImmReleaseContext(hwnd, hIMC); } } } // end of namespace irr LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x020A #endif #ifndef WHEEL_DELTA #define WHEEL_DELTA 120 #endif irr::CIrrDeviceWin32* dev = 0; irr::SEvent event; static irr::s32 ClickCount=0; if (GetCapture() != hWnd && ClickCount > 0) ClickCount = 0; struct messageMap { irr::s32 group; UINT winMessage; irr::s32 irrMessage; }; static messageMap mouseMap[] = { {0, WM_LBUTTONDOWN, irr::EMIE_LMOUSE_PRESSED_DOWN}, {1, WM_LBUTTONUP, irr::EMIE_LMOUSE_LEFT_UP}, {0, WM_RBUTTONDOWN, irr::EMIE_RMOUSE_PRESSED_DOWN}, {1, WM_RBUTTONUP, irr::EMIE_RMOUSE_LEFT_UP}, {0, WM_MBUTTONDOWN, irr::EMIE_MMOUSE_PRESSED_DOWN}, {1, WM_MBUTTONUP, irr::EMIE_MMOUSE_LEFT_UP}, {2, WM_MOUSEMOVE, irr::EMIE_MOUSE_MOVED}, {3, WM_MOUSEWHEEL, irr::EMIE_MOUSE_WHEEL}, {-1, 0, 0} }; // handle grouped events messageMap * m = mouseMap; while ( m->group >=0 && m->winMessage != message ) m += 1; if ( m->group >= 0 ) { if ( m->group == 0 ) // down { ClickCount++; SetCapture(hWnd); } else if ( m->group == 1 ) // up { ClickCount--; if (ClickCount<1) { ClickCount=0; ReleaseCapture(); } } event.EventType = irr::EET_MOUSE_INPUT_EVENT; event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT) m->irrMessage; event.MouseInput.X = (short)LOWORD(lParam); event.MouseInput.Y = (short)HIWORD(lParam); event.MouseInput.Shift = ((LOWORD(wParam) & MK_SHIFT) != 0); event.MouseInput.Control = ((LOWORD(wParam) & MK_CONTROL) != 0); // left and right mouse buttons event.MouseInput.ButtonStates = wParam & ( MK_LBUTTON | MK_RBUTTON); // middle and extra buttons if (wParam & MK_MBUTTON) event.MouseInput.ButtonStates |= irr::EMBSM_MIDDLE; #if(_WIN32_WINNT >= 0x0500) if (wParam & MK_XBUTTON1) event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA1; if (wParam & MK_XBUTTON2) event.MouseInput.ButtonStates |= irr::EMBSM_EXTRA2; #endif event.MouseInput.Wheel = 0.f; // wheel if ( m->group == 3 ) { POINT p; // fixed by jox p.x = 0; p.y = 0; ClientToScreen(hWnd, &p); event.MouseInput.X -= p.x; event.MouseInput.Y -= p.y; event.MouseInput.Wheel = ((irr::f32)((short)HIWORD(wParam))) / (irr::f32)WHEEL_DELTA; } dev = getDeviceFromHWnd(hWnd); if (dev) { dev->postEventFromUser(event); if ( event.MouseInput.Event >= irr::EMIE_LMOUSE_PRESSED_DOWN && event.MouseInput.Event <= irr::EMIE_MMOUSE_PRESSED_DOWN ) { irr::u32 clicks = dev->checkSuccessiveClicks(event.MouseInput.X, event.MouseInput.Y, event.MouseInput.Event); if ( clicks == 2 ) { event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT)(irr::EMIE_LMOUSE_DOUBLE_CLICK + event.MouseInput.Event-irr::EMIE_LMOUSE_PRESSED_DOWN); dev->postEventFromUser(event); } else if ( clicks == 3 ) { event.MouseInput.Event = (irr::EMOUSE_INPUT_EVENT)(irr::EMIE_LMOUSE_TRIPLE_CLICK + event.MouseInput.Event-irr::EMIE_LMOUSE_PRESSED_DOWN); dev->postEventFromUser(event); } } } if (m->irrMessage == irr::EMIE_LMOUSE_PRESSED_DOWN || m->irrMessage == irr::EMIE_LMOUSE_LEFT_UP) { event.EventType = irr::EET_IMPUT_METHOD_EVENT; event.InputMethodEvent.Event = irr::EIME_CHANGE_POS; event.InputMethodEvent.Handle = hWnd; if (dev) dev->postEventFromUser(event); } return 0; } switch (message) { case WM_PAINT: { PAINTSTRUCT ps; BeginPaint(hWnd, &ps); EndPaint(hWnd, &ps); } return 0; case WM_ERASEBKGND: return 0; case WM_IME_CHAR: { event.EventType = irr::EET_IMPUT_METHOD_EVENT; event.InputMethodEvent.Event = irr::EIME_CHAR_INPUT; event.InputMethodEvent.Handle = hWnd; event.KeyInput.Char = 0; event.KeyInput.Key = irr::IRR_KEY_OEM_CLEAR; event.KeyInput.Shift = 0; event.KeyInput.Control = 0; char bits[2] = { (char)((wParam & 0xff00) >> 8), (char)(wParam & 0xff) }; if (bits[0] == 0) event.InputMethodEvent.Char = (wchar_t)wParam; else MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, bits, 2, &event.InputMethodEvent.Char, 1); dev = getDeviceFromHWnd(hWnd); if (dev) dev->postEventFromUser(event); return 0; } case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_KEYDOWN: case WM_KEYUP: { BYTE allKeys[256]; event.EventType = irr::EET_KEY_INPUT_EVENT; event.KeyInput.Key = (irr::EKEY_CODE)wParam; event.KeyInput.PressedDown = (message==WM_KEYDOWN || message == WM_SYSKEYDOWN); const UINT MY_MAPVK_VSC_TO_VK_EX = 3; // MAPVK_VSC_TO_VK_EX should be in SDK according to MSDN, but isn't in mine. if ( event.KeyInput.Key == irr::IRR_KEY_SHIFT ) { // this will fail on systems before windows NT/2000/XP, not sure _what_ will return there instead. event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_MAPVK_VSC_TO_VK_EX ); } if ( event.KeyInput.Key == irr::IRR_KEY_CONTROL ) { event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_MAPVK_VSC_TO_VK_EX ); // some keyboards will just return LEFT for both - left and right keys. So also check extend bit. if (lParam & 0x1000000) event.KeyInput.Key = irr::IRR_KEY_RCONTROL; } if ( event.KeyInput.Key == irr::IRR_KEY_MENU ) { event.KeyInput.Key = (irr::EKEY_CODE)MapVirtualKey( ((lParam>>16) & 255), MY_MAPVK_VSC_TO_VK_EX ); if (lParam & 0x1000000) event.KeyInput.Key = irr::IRR_KEY_RMENU; } GetKeyboardState(allKeys); event.KeyInput.Shift = ((allKeys[VK_SHIFT] & 0x80)!=0); event.KeyInput.Control = ((allKeys[VK_CONTROL] & 0x80)!=0); // Handle unicode and deadkeys in a way that works since Windows 95 and nt4.0 // Using ToUnicode instead would be shorter, but would to my knowledge not run on 95 and 98. WORD keyChars[2]; UINT scanCode = HIWORD(lParam); int conversionResult = ToAsciiEx(wParam,scanCode,allKeys,keyChars,0,KEYBOARD_INPUT_HKL); if (conversionResult == 1) { WORD unicodeChar; MultiByteToWideChar( KEYBOARD_INPUT_CODEPAGE, MB_PRECOMPOSED, // default (LPCSTR)keyChars, sizeof(keyChars), (WCHAR*)&unicodeChar, 1 ); event.KeyInput.Char = unicodeChar; } else event.KeyInput.Char = 0; // allow composing characters like '@' with Alt Gr on non-US keyboards if ((allKeys[VK_MENU] & 0x80) != 0) event.KeyInput.Control = 0; dev = getDeviceFromHWnd(hWnd); if (dev) dev->postEventFromUser(event); event.EventType = irr::EET_IMPUT_METHOD_EVENT; event.InputMethodEvent.Event = irr::EIME_CHANGE_POS; event.InputMethodEvent.Handle = hWnd; if (dev) dev->postEventFromUser(event); if (message == WM_SYSKEYDOWN || message == WM_SYSKEYUP) return DefWindowProc(hWnd, message, wParam, lParam); else return 0; } case WM_SIZE: { // resize dev = getDeviceFromHWnd(hWnd); if (dev) dev->OnResized(); } return 0; case WM_DESTROY: PostQuitMessage(0); return 0; case WM_SYSCOMMAND: // prevent screensaver or monitor powersave mode from starting if ((wParam & 0xFFF0) == SC_SCREENSAVE || (wParam & 0xFFF0) == SC_MONITORPOWER || (wParam & 0xFFF0) == SC_KEYMENU ) return 0; break; case WM_ACTIVATE: // we need to take care for screen changes, e.g. Alt-Tab dev = getDeviceFromHWnd(hWnd); if (dev && dev->isFullscreen()) { if ((wParam&0xFF)==WA_INACTIVE) { // If losing focus we minimize the app to show other one ShowWindow(hWnd,SW_MINIMIZE); // and switch back to default resolution dev->switchToFullScreen(true); } else { // Otherwise we retore the fullscreen Irrlicht app SetForegroundWindow(hWnd); ShowWindow(hWnd, SW_RESTORE); // and set the fullscreen resolution again dev->switchToFullScreen(); } } event.EventType = irr::EET_IMPUT_METHOD_EVENT; event.InputMethodEvent.Event = irr::EIME_CHANGE_POS; event.InputMethodEvent.Handle = hWnd; if (dev) dev->postEventFromUser(event); break; case WM_USER: event.EventType = irr::EET_USER_EVENT; event.UserEvent.UserData1 = (irr::s32)wParam; event.UserEvent.UserData2 = (irr::s32)lParam; dev = getDeviceFromHWnd(hWnd); if (dev) dev->postEventFromUser(event); return 0; case WM_SETCURSOR: // because Windows forgot about that in the meantime dev = getDeviceFromHWnd(hWnd); if (dev) { dev->getCursorControl()->setActiveIcon( dev->getCursorControl()->getActiveIcon() ); dev->getCursorControl()->setVisible( dev->getCursorControl()->isVisible() ); } break; case WM_INPUTLANGCHANGE: // get the new codepage used for keyboard input KEYBOARD_INPUT_HKL = GetKeyboardLayout(0); KEYBOARD_INPUT_CODEPAGE = LocaleIdToCodepage( LOWORD(KEYBOARD_INPUT_HKL) ); return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } namespace irr { //! constructor CIrrDeviceWin32::CIrrDeviceWin32(const SIrrlichtCreationParameters& params) : CIrrDeviceStub(params), HWnd(0), ChangedToFullScreen(false), Resized(false), ExternalWindow(false), Win32CursorControl(0), JoyControl(0) { #ifdef _DEBUG setDebugName("CIrrDeviceWin32"); #endif // get windows version and create OS operator core::stringc winversion; getWindowsVersion(winversion); Operator = new COSOperator(winversion); os::Printer::log(winversion.c_str(), ELL_INFORMATION); // get handle to exe file HINSTANCE hInstance = GetModuleHandle(0); // Store original desktop mode. memset(&DesktopMode, 0, sizeof(DesktopMode)); DesktopMode.dmSize = sizeof(DesktopMode); EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DesktopMode); // create the window if we need to and we do not use the null device if (!CreationParams.WindowId && CreationParams.DriverType != video::EDT_NULL) { const fschar_t* ClassName = __TEXT("CIrrDeviceWin32"); // Register Class WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = NULL; wcex.hCursor = 0; // LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = 0; wcex.lpszClassName = ClassName; wcex.hIconSm = 0; // if there is an icon, load it wcex.hIcon = (HICON)LoadImage(hInstance, __TEXT("irrlicht.ico"), IMAGE_ICON, 0,0, LR_LOADFROMFILE); RegisterClassEx(&wcex); // calculate client size RECT clientSize; clientSize.top = 0; clientSize.left = 0; clientSize.right = CreationParams.WindowSize.Width; clientSize.bottom = CreationParams.WindowSize.Height; DWORD style = WS_POPUP; if (!CreationParams.Fullscreen) style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; AdjustWindowRect(&clientSize, style, FALSE); const s32 realWidth = clientSize.right - clientSize.left; const s32 realHeight = clientSize.bottom - clientSize.top; s32 windowLeft = (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2; s32 windowTop = (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2; if ( windowLeft < 0 ) windowLeft = 0; if ( windowTop < 0 ) windowTop = 0; // make sure window menus are in screen on creation if (CreationParams.Fullscreen) { windowLeft = 0; windowTop = 0; } // create window HWnd = CreateWindow( ClassName, __TEXT(""), style, windowLeft, windowTop, realWidth, realHeight, NULL, NULL, hInstance, NULL); CreationParams.WindowId = HWnd; // CreationParams.WindowSize.Width = realWidth; // CreationParams.WindowSize.Height = realHeight; ShowWindow(HWnd, SW_SHOWNORMAL); UpdateWindow(HWnd); // fix ugly ATI driver bugs. Thanks to ariaci MoveWindow(HWnd, windowLeft, windowTop, realWidth, realHeight, TRUE); // make sure everything gets updated to the real sizes Resized = true; } else if (CreationParams.WindowId) { // attach external window HWnd = static_cast(CreationParams.WindowId); RECT r; GetWindowRect(HWnd, &r); CreationParams.WindowSize.Width = r.right - r.left; CreationParams.WindowSize.Height = r.bottom - r.top; CreationParams.Fullscreen = false; ExternalWindow = true; } // create cursor control Win32CursorControl = new CCursorControl(this, CreationParams.WindowSize, HWnd, CreationParams.Fullscreen); CursorControl = Win32CursorControl; JoyControl = new SJoystickWin32Control(this); // initialize doubleclicks with system values MouseMultiClicks.DoubleClickTime = GetDoubleClickTime(); // create driver createDriver(); if (VideoDriver) createGUIAndScene(); // register environment SEnvMapper em; em.irrDev = this; em.hWnd = HWnd; EnvMap.push_back(em); // set this as active window if (!ExternalWindow) { SetActiveWindow(HWnd); SetForegroundWindow(HWnd); } // get the codepage used for keyboard input KEYBOARD_INPUT_HKL = GetKeyboardLayout(0); KEYBOARD_INPUT_CODEPAGE = LocaleIdToCodepage( LOWORD(KEYBOARD_INPUT_HKL) ); // inform driver about the window size etc. resizeIfNecessary(); // for reset the position of composition window updateICPos(HWnd, 0, 0, 0); } //! destructor CIrrDeviceWin32::~CIrrDeviceWin32() { delete JoyControl; // unregister environment irr::core::list::Iterator it = EnvMap.begin(); for (; it!= EnvMap.end(); ++it) { if ((*it).hWnd == HWnd) { EnvMap.erase(it); break; } } switchToFullScreen(true); } //! create the driver void CIrrDeviceWin32::createDriver() { switch(CreationParams.DriverType) { case video::EDT_DIRECT3D8: #ifdef _IRR_COMPILE_WITH_DIRECT3D_8_ VideoDriver = video::createDirectX8Driver(CreationParams, FileSystem, HWnd); if (!VideoDriver) { os::Printer::log("Could not create DIRECT3D8 Driver.", ELL_ERROR); } #else os::Printer::log("DIRECT3D8 Driver was not compiled into this dll. Try another one.", ELL_ERROR); #endif // _IRR_COMPILE_WITH_DIRECT3D_8_ break; case video::EDT_DIRECT3D9: #ifdef _IRR_COMPILE_WITH_DIRECT3D_9_ VideoDriver = video::createDirectX9Driver(CreationParams, FileSystem, HWnd); if (!VideoDriver) { os::Printer::log("Could not create DIRECT3D9 Driver.", ELL_ERROR); } #else os::Printer::log("DIRECT3D9 Driver was not compiled into this dll. Try another one.", ELL_ERROR); #endif // _IRR_COMPILE_WITH_DIRECT3D_9_ break; case video::EDT_OPENGL: #ifdef _IRR_COMPILE_WITH_OPENGL_ switchToFullScreen(); VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, this); if (!VideoDriver) { os::Printer::log("Could not create OpenGL driver.", ELL_ERROR); } #else os::Printer::log("OpenGL driver was not compiled in.", ELL_ERROR); #endif break; case video::EDT_SOFTWARE: #ifdef _IRR_COMPILE_WITH_SOFTWARE_ switchToFullScreen(); VideoDriver = video::createSoftwareDriver(CreationParams.WindowSize, CreationParams.Fullscreen, FileSystem, this); #else os::Printer::log("Software driver was not compiled in.", ELL_ERROR); #endif break; case video::EDT_BURNINGSVIDEO: #ifdef _IRR_COMPILE_WITH_BURNINGSVIDEO_ switchToFullScreen(); VideoDriver = video::createBurningVideoDriver(CreationParams, FileSystem, this); #else os::Printer::log("Burning's Video driver was not compiled in.", ELL_ERROR); #endif break; case video::EDT_NULL: // create null driver VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); break; default: os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR); break; } } //! runs the device. Returns false if device wants to be deleted bool CIrrDeviceWin32::run() { os::Timer::tick(); static_cast(CursorControl)->update(); handleSystemMessages(); if (!Close) resizeIfNecessary(); if(!Close && JoyControl) JoyControl->pollJoysticks(); _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; return !Close; } //! Pause the current process for the minimum time allowed only to allow other processes to execute void CIrrDeviceWin32::yield() { Sleep(1); } //! Pause execution and let other processes to run for a specified amount of time. void CIrrDeviceWin32::sleep(u32 timeMs, bool pauseTimer) { const bool wasStopped = Timer ? Timer->isStopped() : true; if (pauseTimer && !wasStopped) Timer->stop(); Sleep(timeMs); if (pauseTimer && !wasStopped) Timer->start(); } void CIrrDeviceWin32::resizeIfNecessary() { if (!Resized || !getVideoDriver()) return; RECT r; GetClientRect(HWnd, &r); char tmp[255]; if (r.right < 2 || r.bottom < 2) { sprintf(tmp, "Ignoring resize operation to (%ld %ld)", r.right, r.bottom); os::Printer::log(tmp); } else { sprintf(tmp, "Resizing window (%ld %ld)", r.right, r.bottom); os::Printer::log(tmp); getVideoDriver()->OnResize(irr::core::dimension2du((u32)r.right, (u32)r.bottom)); getWin32CursorControl()->OnResize(getVideoDriver()->getScreenSize()); } Resized = false; } //! sets the caption of the window void CIrrDeviceWin32::setWindowCaption(const wchar_t* text) { // We use SendMessage instead of SetText to ensure proper // function even in cases where the HWND was created in a different thread DWORD_PTR dwResult; SendMessageTimeoutW(HWnd, WM_SETTEXT, 0, reinterpret_cast(text), SMTO_ABORTIFHUNG, 2000, &dwResult); } //! presents a surface in the client area bool CIrrDeviceWin32::present(video::IImage* image, void* windowId, core::rect* src) { HWND hwnd = HWnd; if ( windowId ) hwnd = reinterpret_cast(windowId); HDC dc = GetDC(hwnd); if ( dc ) { RECT rect; GetClientRect(hwnd, &rect); const void* memory = (const void *)image->lock(); BITMAPV4HEADER bi; ZeroMemory (&bi, sizeof(bi)); bi.bV4Size = sizeof(BITMAPINFOHEADER); bi.bV4BitCount = (WORD)image->getBitsPerPixel(); bi.bV4Planes = 1; bi.bV4Width = image->getDimension().Width; bi.bV4Height = -((s32)image->getDimension().Height); bi.bV4V4Compression = BI_BITFIELDS; bi.bV4AlphaMask = image->getAlphaMask(); bi.bV4RedMask = image->getRedMask(); bi.bV4GreenMask = image->getGreenMask(); bi.bV4BlueMask = image->getBlueMask(); if ( src ) { StretchDIBits(dc, 0,0, rect.right, rect.bottom, src->UpperLeftCorner.X, src->UpperLeftCorner.Y, src->getWidth(), src->getHeight(), memory, (const BITMAPINFO*)(&bi), DIB_RGB_COLORS, SRCCOPY); } else { StretchDIBits(dc, 0,0, rect.right, rect.bottom, 0, 0, image->getDimension().Width, image->getDimension().Height, memory, (const BITMAPINFO*)(&bi), DIB_RGB_COLORS, SRCCOPY); } image->unlock(); ReleaseDC(hwnd, dc); } return true; } //! notifies the device that it should close itself void CIrrDeviceWin32::closeDevice() { MSG msg; PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE); PostQuitMessage(0); PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE); if (!ExternalWindow) { DestroyWindow(HWnd); const fschar_t* ClassName = __TEXT("CIrrDeviceWin32"); HINSTANCE hInstance = GetModuleHandle(0); UnregisterClass(ClassName, hInstance); } Close=true; } //! returns if window is active. if not, nothing needs to be drawn bool CIrrDeviceWin32::isWindowActive() const { _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; return (GetActiveWindow() == HWnd); } //! returns if window has focus bool CIrrDeviceWin32::isWindowFocused() const { bool ret = (GetFocus() == HWnd); _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; return ret; } //! returns if window is minimized bool CIrrDeviceWin32::isWindowMinimized() const { WINDOWPLACEMENT plc; plc.length=sizeof(WINDOWPLACEMENT); bool ret=false; if (GetWindowPlacement(HWnd,&plc)) ret=(plc.showCmd & SW_SHOWMINIMIZED)!=0; _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; return ret; } //! switches to fullscreen bool CIrrDeviceWin32::switchToFullScreen(bool reset) { if (!CreationParams.Fullscreen) return true; if (reset) { if (ChangedToFullScreen) { return (ChangeDisplaySettings(&DesktopMode,0)==DISP_CHANGE_SUCCESSFUL); } else return true; } // use default values from current setting DEVMODE dm; memset(&dm, 0, sizeof(dm)); dm.dmSize = sizeof(dm); EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm); dm.dmPelsWidth = CreationParams.WindowSize.Width; dm.dmPelsHeight = CreationParams.WindowSize.Height; dm.dmBitsPerPel = CreationParams.Bits; dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY; LONG res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); if (res != DISP_CHANGE_SUCCESSFUL) { // try again without forcing display frequency dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; res = ChangeDisplaySettings(&dm, CDS_FULLSCREEN); } bool ret = false; switch(res) { case DISP_CHANGE_SUCCESSFUL: ChangedToFullScreen = true; ret = true; break; case DISP_CHANGE_RESTART: os::Printer::log("Switch to fullscreen: The computer must be restarted in order for the graphics mode to work.", ELL_ERROR); break; case DISP_CHANGE_BADFLAGS: os::Printer::log("Switch to fullscreen: An invalid set of flags was passed in.", ELL_ERROR); break; case DISP_CHANGE_BADPARAM: os::Printer::log("Switch to fullscreen: An invalid parameter was passed in. This can include an invalid flag or combination of flags.", ELL_ERROR); break; case DISP_CHANGE_FAILED: os::Printer::log("Switch to fullscreen: The display driver failed the specified graphics mode.", ELL_ERROR); break; case DISP_CHANGE_BADMODE: os::Printer::log("Switch to fullscreen: The graphics mode is not supported.", ELL_ERROR); break; default: os::Printer::log("An unknown error occured while changing to fullscreen.", ELL_ERROR); break; } _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; return ret; } //! returns the win32 cursor control CIrrDeviceWin32::CCursorControl* CIrrDeviceWin32::getWin32CursorControl() { return Win32CursorControl; } //! \return Returns a pointer to a list with all video modes supported //! by the gfx adapter. video::IVideoModeList* CIrrDeviceWin32::getVideoModeList() { if (!VideoModeList.getVideoModeCount()) { // enumerate video modes. DWORD i=0; DEVMODE mode; memset(&mode, 0, sizeof(mode)); mode.dmSize = sizeof(mode); while (EnumDisplaySettings(NULL, i, &mode)) { VideoModeList.addMode(core::dimension2d(mode.dmPelsWidth, mode.dmPelsHeight), mode.dmBitsPerPel); ++i; } if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &mode)) VideoModeList.setDesktop(mode.dmBitsPerPel, core::dimension2d(mode.dmPelsWidth, mode.dmPelsHeight)); } return &VideoModeList; } typedef BOOL (WINAPI *PGPI)(DWORD, DWORD, DWORD, DWORD, PDWORD); // Needed for old windows apis // depending on the SDK version and compilers some defines might be available // or not #ifndef PRODUCT_ULTIMATE #define PRODUCT_ULTIMATE 0x00000001 #define PRODUCT_HOME_BASIC 0x00000002 #define PRODUCT_HOME_PREMIUM 0x00000003 #define PRODUCT_ENTERPRISE 0x00000004 #define PRODUCT_HOME_BASIC_N 0x00000005 #define PRODUCT_BUSINESS 0x00000006 #define PRODUCT_STARTER 0x0000000B #endif #ifndef PRODUCT_ULTIMATE_N #define PRODUCT_BUSINESS_N 0x00000010 #define PRODUCT_HOME_PREMIUM_N 0x0000001A #define PRODUCT_ENTERPRISE_N 0x0000001B #define PRODUCT_ULTIMATE_N 0x0000001C #endif #ifndef PRODUCT_STARTER_N #define PRODUCT_STARTER_N 0x0000002F #endif #ifndef PRODUCT_PROFESSIONAL #define PRODUCT_PROFESSIONAL 0x00000030 #define PRODUCT_PROFESSIONAL_N 0x00000031 #endif #ifndef PRODUCT_ULTIMATE_E #define PRODUCT_STARTER_E 0x00000042 #define PRODUCT_HOME_BASIC_E 0x00000043 #define PRODUCT_HOME_PREMIUM_E 0x00000044 #define PRODUCT_PROFESSIONAL_E 0x00000045 #define PRODUCT_ENTERPRISE_E 0x00000046 #define PRODUCT_ULTIMATE_E 0x00000047 #endif void CIrrDeviceWin32::getWindowsVersion(core::stringc& out) { OSVERSIONINFOEX osvi; PGPI pGPI; BOOL bOsVersionInfoEx; ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO*) &osvi); if (!bOsVersionInfoEx) { osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (! GetVersionEx((OSVERSIONINFO *) &osvi)) return; } switch (osvi.dwPlatformId) { case VER_PLATFORM_WIN32_NT: if (osvi.dwMajorVersion <= 4) out.append("Microsoft Windows NT "); else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0) out.append("Microsoft Windows 2000 "); else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1) out.append("Microsoft Windows XP "); else if (osvi.dwMajorVersion == 6 ) { if (osvi.dwMinorVersion == 0) { if (osvi.wProductType == VER_NT_WORKSTATION) out.append("Microsoft Windows Vista "); else out.append("Microsoft Windows Server 2008 "); } else if (osvi.dwMinorVersion == 1) { if (osvi.wProductType == VER_NT_WORKSTATION) out.append("Microsoft Windows 7 "); else out.append("Microsoft Windows Server 2008 R2 "); } else if (osvi.dwMinorVersion == 2) { if (osvi.wProductType == VER_NT_WORKSTATION) out.append("Microsoft Windows 8 "); else out.append("Microsoft Windows Server 2012 "); } else if (osvi.dwMinorVersion == 3) { if (osvi.wProductType == VER_NT_WORKSTATION) out.append("Microsoft Windows 8.1 "); else out.append("Microsoft Windows Server 2012 R2 "); } } if (bOsVersionInfoEx) { if (osvi.dwMajorVersion == 6) { DWORD dwType; pGPI = (PGPI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetProductInfo"); pGPI(osvi.dwMajorVersion, osvi.dwMinorVersion, 0, 0, &dwType); switch (dwType) { case PRODUCT_ULTIMATE: case PRODUCT_ULTIMATE_E: case PRODUCT_ULTIMATE_N: out.append("Ultimate Edition "); break; case PRODUCT_PROFESSIONAL: case PRODUCT_PROFESSIONAL_E: case PRODUCT_PROFESSIONAL_N: out.append("Professional Edition "); break; case PRODUCT_HOME_BASIC: case PRODUCT_HOME_BASIC_E: case PRODUCT_HOME_BASIC_N: out.append("Home Basic Edition "); break; case PRODUCT_HOME_PREMIUM: case PRODUCT_HOME_PREMIUM_E: case PRODUCT_HOME_PREMIUM_N: out.append("Home Premium Edition "); break; case PRODUCT_ENTERPRISE: case PRODUCT_ENTERPRISE_E: case PRODUCT_ENTERPRISE_N: out.append("Enterprise Edition "); break; case PRODUCT_BUSINESS: case PRODUCT_BUSINESS_N: out.append("Business Edition "); break; case PRODUCT_STARTER: case PRODUCT_STARTER_E: case PRODUCT_STARTER_N: out.append("Starter Edition "); break; } } #ifdef VER_SUITE_ENTERPRISE else if (osvi.wProductType == VER_NT_WORKSTATION) { #ifndef __BORLANDC__ if( osvi.wSuiteMask & VER_SUITE_PERSONAL ) out.append("Personal "); else out.append("Professional "); #endif } else if (osvi.wProductType == VER_NT_SERVER) { if( osvi.wSuiteMask & VER_SUITE_DATACENTER ) out.append("DataCenter Server "); else if( osvi.wSuiteMask & VER_SUITE_ENTERPRISE ) out.append("Advanced Server "); else out.append("Server "); } #endif } else { HKEY hKey; char szProductType[80]; DWORD dwBufLen; RegOpenKeyEx( HKEY_LOCAL_MACHINE, __TEXT("SYSTEM\\CurrentControlSet\\Control\\ProductOptions"), 0, KEY_QUERY_VALUE, &hKey ); RegQueryValueEx( hKey, __TEXT("ProductType"), NULL, NULL, (LPBYTE) szProductType, &dwBufLen); RegCloseKey( hKey ); if (_strcmpi( "WINNT", szProductType) == 0 ) out.append("Professional "); if (_strcmpi( "LANMANNT", szProductType) == 0) out.append("Server "); if (_strcmpi( "SERVERNT", szProductType) == 0) out.append("Advanced Server "); } // Display version, service pack (if any), and build number. char tmp[255]; if (osvi.dwMajorVersion <= 4 ) { sprintf(tmp, "version %ld.%ld %s (Build %ld)", osvi.dwMajorVersion, osvi.dwMinorVersion, irr::core::stringc(osvi.szCSDVersion).c_str(), osvi.dwBuildNumber & 0xFFFF); } else { sprintf(tmp, "%s (Build %ld)", irr::core::stringc(osvi.szCSDVersion).c_str(), osvi.dwBuildNumber & 0xFFFF); } out.append(tmp); break; case VER_PLATFORM_WIN32_WINDOWS: if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0) { out.append("Microsoft Windows 95 "); if ( osvi.szCSDVersion[1] == 'C' || osvi.szCSDVersion[1] == 'B' ) out.append("OSR2 " ); } if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 10) { out.append("Microsoft Windows 98 "); if ( osvi.szCSDVersion[1] == 'A' ) out.append( "SE " ); } if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 90) out.append("Microsoft Windows Me "); break; case VER_PLATFORM_WIN32s: out.append("Microsoft Win32s "); break; } } //! Notifies the device, that it has been resized void CIrrDeviceWin32::OnResized() { Resized = true; } //! Sets if the window should be resizable in windowed mode. void CIrrDeviceWin32::setResizable(bool resize) { if (ExternalWindow || !getVideoDriver() || CreationParams.Fullscreen) return; LONG_PTR style = WS_POPUP; if (!resize) style = WS_SYSMENU | WS_BORDER | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; else style = WS_THICKFRAME | WS_SYSMENU | WS_CAPTION | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; if (!SetWindowLongPtr(HWnd, GWL_STYLE, style)) os::Printer::log("Could not change window style."); RECT clientSize; clientSize.top = 0; clientSize.left = 0; clientSize.right = getVideoDriver()->getScreenSize().Width; clientSize.bottom = getVideoDriver()->getScreenSize().Height; AdjustWindowRect(&clientSize, style, FALSE); const s32 realWidth = clientSize.right - clientSize.left; const s32 realHeight = clientSize.bottom - clientSize.top; const s32 windowLeft = (GetSystemMetrics(SM_CXSCREEN) - realWidth) / 2; const s32 windowTop = (GetSystemMetrics(SM_CYSCREEN) - realHeight) / 2; SetWindowPos(HWnd, HWND_TOP, windowLeft, windowTop, realWidth, realHeight, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_SHOWWINDOW); static_cast(CursorControl)->updateBorderSize(CreationParams.Fullscreen, resize); } //! Minimizes the window. void CIrrDeviceWin32::minimizeWindow() { WINDOWPLACEMENT wndpl; wndpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(HWnd, &wndpl); wndpl.showCmd = SW_SHOWMINNOACTIVE; SetWindowPlacement(HWnd, &wndpl); } //! Maximizes the window. void CIrrDeviceWin32::maximizeWindow() { WINDOWPLACEMENT wndpl; wndpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(HWnd, &wndpl); wndpl.showCmd = SW_SHOWMAXIMIZED; SetWindowPlacement(HWnd, &wndpl); } //! Restores the window to its original size. void CIrrDeviceWin32::restoreWindow() { WINDOWPLACEMENT wndpl; wndpl.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(HWnd, &wndpl); wndpl.showCmd = SW_SHOWNORMAL; SetWindowPlacement(HWnd, &wndpl); } //! Move window to requested position bool CIrrDeviceWin32::moveWindow(int x, int y) { if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen) return false; bool success = SetWindowPos(HWnd, HWND_TOP, x, y, -1, -1, SWP_NOOWNERZORDER | SWP_NOSIZE); return success; } //! Get current window position. bool CIrrDeviceWin32::getWindowPosition(int* x, int* y) { if (CreationParams.DriverType == video::EDT_NULL || CreationParams.Fullscreen) return false; WINDOWPLACEMENT placement; placement.length = sizeof(WINDOWPLACEMENT); bool success = GetWindowPlacement(HWnd, &placement); if (!success) return false; *x = (int)placement.rcNormalPosition.left; *y = (int)placement.rcNormalPosition.top; return true; } bool CIrrDeviceWin32::activateJoysticks(core::array & joystickInfo) { if (JoyControl) return JoyControl->activateJoysticks(joystickInfo); else return false; } //! Set the current Gamma Value for the Display bool CIrrDeviceWin32::setGammaRamp( f32 red, f32 green, f32 blue, f32 brightness, f32 contrast ) { bool r; u16 ramp[3][256]; calculateGammaRamp( ramp[0], red, brightness, contrast ); calculateGammaRamp( ramp[1], green, brightness, contrast ); calculateGammaRamp( ramp[2], blue, brightness, contrast ); HDC dc = GetDC(0); r = SetDeviceGammaRamp ( dc, ramp ) == TRUE; ReleaseDC(HWnd, dc); return r; } //! Get the current Gamma Value for the Display bool CIrrDeviceWin32::getGammaRamp( f32 &red, f32 &green, f32 &blue, f32 &brightness, f32 &contrast ) { bool r; u16 ramp[3][256]; HDC dc = GetDC(0); r = GetDeviceGammaRamp ( dc, ramp ) == TRUE; ReleaseDC(HWnd, dc); if ( r ) { calculateGammaFromRamp(red, ramp[0]); calculateGammaFromRamp(green, ramp[1]); calculateGammaFromRamp(blue, ramp[2]); } brightness = 0.f; contrast = 0.f; return r; } //! Process system events void CIrrDeviceWin32::handleSystemMessages() { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // No message translation because we don't use WM_CHAR and it would conflict with our // deadkey handling. TranslateMessage(&msg); if (ExternalWindow && msg.hwnd == HWnd) WndProc(HWnd, msg.message, msg.wParam, msg.lParam); else DispatchMessage(&msg); if (msg.message == WM_QUIT) Close = true; } } //! Remove all messages pending in the system message loop void CIrrDeviceWin32::clearSystemMessages() { MSG msg; while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) {} while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) {} } // shows last error in a messagebox to help internal debugging. void CIrrDeviceWin32::ReportLastWinApiError() { // (based on code from ovidiucucu from http://www.codeguru.com/forum/showthread.php?t=318721) LPCTSTR pszCaption = __TEXT("Windows SDK Error Report"); DWORD dwError = GetLastError(); if(NOERROR == dwError) { MessageBox(NULL, __TEXT("No error"), pszCaption, MB_OK); } else { const DWORD dwFormatControl = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM; LPVOID pTextBuffer = NULL; DWORD dwCount = FormatMessage(dwFormatControl, NULL, dwError, 0, (LPTSTR) &pTextBuffer, 0, NULL); if(0 != dwCount) { MessageBox(NULL, (LPCTSTR)pTextBuffer, pszCaption, MB_OK|MB_ICONERROR); LocalFree(pTextBuffer); } else { MessageBox(NULL, __TEXT("Unknown error"), pszCaption, MB_OK|MB_ICONERROR); } } } // Convert an Irrlicht texture to a Windows cursor // Based on http://www.codeguru.com/cpp/w-p/win32/cursors/article.php/c4529/ HCURSOR CIrrDeviceWin32::TextureToCursor(HWND hwnd, irr::video::ITexture * tex, const core::rect& sourceRect, const core::position2d &hotspot) { // // create the bitmaps needed for cursors from the texture HDC dc = GetDC(hwnd); HDC andDc = CreateCompatibleDC(dc); HDC xorDc = CreateCompatibleDC(dc); HBITMAP andBitmap = CreateCompatibleBitmap(dc, sourceRect.getWidth(), sourceRect.getHeight()); HBITMAP xorBitmap = CreateCompatibleBitmap(dc, sourceRect.getWidth(), sourceRect.getHeight()); HBITMAP oldAndBitmap = (HBITMAP)SelectObject(andDc, andBitmap); HBITMAP oldXorBitmap = (HBITMAP)SelectObject(xorDc, xorBitmap); video::ECOLOR_FORMAT format = tex->getColorFormat(); u32 bytesPerPixel = video::IImage::getBitsPerPixelFromFormat(format) / 8; u32 bytesLeftGap = sourceRect.UpperLeftCorner.X * bytesPerPixel; u32 bytesRightGap = tex->getPitch() - sourceRect.LowerRightCorner.X * bytesPerPixel; const u8* data = (const u8*)tex->lock(video::ETLM_READ_ONLY, 0); data += sourceRect.UpperLeftCorner.Y*tex->getPitch(); for ( s32 y = 0; y < sourceRect.getHeight(); ++y ) { data += bytesLeftGap; for ( s32 x = 0; x < sourceRect.getWidth(); ++x ) { video::SColor pixelCol; pixelCol.setData((const void*)data, format); data += bytesPerPixel; if ( pixelCol.getAlpha() == 0 ) // transparent { SetPixel(andDc, x, y, RGB(255,255,255)); SetPixel(xorDc, x, y, RGB(0,0,0)); } else // color { SetPixel(andDc, x, y, RGB(0,0,0)); SetPixel(xorDc, x, y, RGB(pixelCol.getRed(), pixelCol.getGreen(), pixelCol.getBlue())); } } data += bytesRightGap; } tex->unlock(); SelectObject(andDc, oldAndBitmap); SelectObject(xorDc, oldXorBitmap); DeleteDC(xorDc); DeleteDC(andDc); ReleaseDC(hwnd, dc); // create the cursor ICONINFO iconinfo; iconinfo.fIcon = false; // type is cursor not icon iconinfo.xHotspot = hotspot.X; iconinfo.yHotspot = hotspot.Y; iconinfo.hbmMask = andBitmap; iconinfo.hbmColor = xorBitmap; HCURSOR cursor = CreateIconIndirect(&iconinfo); DeleteObject(andBitmap); DeleteObject(xorBitmap); return cursor; } CIrrDeviceWin32::CCursorControl::CCursorControl(CIrrDeviceWin32* device, const core::dimension2d& wsize, HWND hwnd, bool fullscreen) : Device(device), WindowSize(wsize), InvWindowSize(0.0f, 0.0f), HWnd(hwnd), BorderX(0), BorderY(0), UseReferenceRect(false), IsVisible(true) , ActiveIcon(gui::ECI_NORMAL), ActiveIconStartTime(0) { if (WindowSize.Width!=0) InvWindowSize.Width = 1.0f / WindowSize.Width; if (WindowSize.Height!=0) InvWindowSize.Height = 1.0f / WindowSize.Height; updateBorderSize(fullscreen, false); initCursors(); } CIrrDeviceWin32::CCursorControl::~CCursorControl() { for ( u32 i=0; i < Cursors.size(); ++i ) { for ( u32 f=0; f < Cursors[i].Frames.size(); ++f ) { DestroyCursor(Cursors[i].Frames[f].IconHW); } } } void CIrrDeviceWin32::CCursorControl::initCursors() { Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_ARROW)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_CROSS)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_HAND)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_HELP)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_IBEAM)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_NO)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_WAIT)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZEALL)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENESW)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENWSE)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZENS)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_SIZEWE)) ); Cursors.push_back( CursorW32(LoadCursor(NULL, IDC_UPARROW)) ); } void CIrrDeviceWin32::CCursorControl::update() { if ( !Cursors[ActiveIcon].Frames.empty() && Cursors[ActiveIcon].FrameTime ) { // update animated cursors. This could also be done by X11 in case someone wants to figure that out (this way was just easier to implement) u32 now = Device->getTimer()->getRealTime(); u32 frame = ((now - ActiveIconStartTime) / Cursors[ActiveIcon].FrameTime) % Cursors[ActiveIcon].Frames.size(); SetCursor( Cursors[ActiveIcon].Frames[frame].IconHW ); } } //! Sets the active cursor icon void CIrrDeviceWin32::CCursorControl::setActiveIcon(gui::ECURSOR_ICON iconId) { if ( iconId >= (s32)Cursors.size() ) return; ActiveIcon = iconId; ActiveIconStartTime = Device->getTimer()->getRealTime(); if ( Cursors[ActiveIcon].Frames.size() ) SetCursor( Cursors[ActiveIcon].Frames[0].IconHW ); } //! Add a custom sprite as cursor icon. gui::ECURSOR_ICON CIrrDeviceWin32::CCursorControl::addIcon(const gui::SCursorSprite& icon) { if ( icon.SpriteId >= 0 ) { CursorW32 cW32; cW32.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime; for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i ) { irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber; irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber; irr::core::rect rectIcon = icon.SpriteBank->getPositions()[rectId]; HCURSOR hc = Device->TextureToCursor(HWnd, icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot); cW32.Frames.push_back( CursorFrameW32(hc) ); } Cursors.push_back( cW32 ); return (gui::ECURSOR_ICON)(Cursors.size() - 1); } return gui::ECI_NORMAL; } //! replace the given cursor icon. void CIrrDeviceWin32::CCursorControl::changeIcon(gui::ECURSOR_ICON iconId, const gui::SCursorSprite& icon) { if ( iconId >= (s32)Cursors.size() ) return; for ( u32 i=0; i < Cursors[iconId].Frames.size(); ++i ) DestroyCursor(Cursors[iconId].Frames[i].IconHW); if ( icon.SpriteId >= 0 ) { CursorW32 cW32; cW32.FrameTime = icon.SpriteBank->getSprites()[icon.SpriteId].frameTime; for ( u32 i=0; i < icon.SpriteBank->getSprites()[icon.SpriteId].Frames.size(); ++i ) { irr::u32 texId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].textureNumber; irr::u32 rectId = icon.SpriteBank->getSprites()[icon.SpriteId].Frames[i].rectNumber; irr::core::rect rectIcon = icon.SpriteBank->getPositions()[rectId]; HCURSOR hc = Device->TextureToCursor(HWnd, icon.SpriteBank->getTexture(texId), rectIcon, icon.HotSpot); cW32.Frames.push_back( CursorFrameW32(hc) ); } Cursors[iconId] = cW32; } } //! Return a system-specific size which is supported for cursors. Larger icons will fail, smaller icons might work. core::dimension2di CIrrDeviceWin32::CCursorControl::getSupportedIconSize() const { core::dimension2di result; result.Width = GetSystemMetrics(SM_CXCURSOR); result.Height = GetSystemMetrics(SM_CYCURSOR); return result; } } // end namespace #endif // _IRR_COMPILE_WITH_WINDOWS_DEVICE_