#pragma region CPL License
/*
Nuclex Native Framework
Copyright (C) 2002-2013 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#pragma endregion // CPL License
// If the library is compiled as a DLL, this ensures symbols are exported
#define NUCLEX_INPUT_SOURCE 1
#include "WindowsInputManager.Impl.h"
#include "XInputDeviceEnumerator.h"
#include "../Devices/XInput/XInputGamePad.h"
#include "../Devices/Windows/DirectInputGamePad.h"
namespace {
// ------------------------------------------------------------------------------------------- //
/// Determines if the specified handle is valid
/// Handle the will be checked
/// True if the handle is a valid handle, otherwise false
bool isValidHandle(HMODULE handle) {
return (handle != nullptr) && (handle != INVALID_HANDLE_VALUE);
}
// ------------------------------------------------------------------------------------------- //
/// Adds informations about a DirectInput device to a vector
/// Device informations that will be added to the vector
/// Vector the device informations will be added to
/// DIENUM_CONTINUE, always
BOOL CALLBACK addDeviceInstance(const DIDEVICEINSTANCE *deviceInstance, void *userdata) {
typedef std::vector DeviceInstanceVector;
DeviceInstanceVector *deviceInstances = reinterpret_cast(userdata);
deviceInstances->push_back(*deviceInstance);
return DIENUM_CONTINUE;
}
// ------------------------------------------------------------------------------------------- //
} // anonymous namespace
namespace Nuclex { namespace Input {
// ------------------------------------------------------------------------------------------- //
WindowsInputManager::Impl::Impl(HWND windowHandle, HINSTANCE instanceHandle) :
windowHandle(windowHandle),
instanceHandle(instanceHandle),
Mouse(windowHandle) {
this->mouseEventTrackData.cbSize = sizeof(this->mouseEventTrackData);
this->mouseEventTrackData.dwFlags = TME_LEAVE;
this->mouseEventTrackData.dwHoverTime = 0;
this->mouseEventTrackData.hwndTrack = windowHandle;
tryInitializeDirectInput();
subclassWindow();
for(std::size_t playerIndex = 0; playerIndex < 4; ++playerIndex) {
this->GamePads.push_back(new Devices::XInputGamePad(playerIndex));
}
for(std::size_t playerIndex = 0; playerIndex < 4; ++playerIndex) {
this->VirtualGamePads.push_back(new Devices::VirtualGamePad());
}
std::vector deviceInstances = EnumerateDirectInputGamePads();
for(std::size_t index = 0; index < 4; ++index) {
if(index >= deviceInstances.size()) {
break;
}
this->VirtualGamePads[index]->SetForwardingGamePad(
new Devices::DirectInputGamePad(this->directInput, deviceInstances[index])
);
}
}
// ------------------------------------------------------------------------------------------- //
WindowsInputManager::Impl::~Impl() {
for(std::size_t playerIndex = 0; playerIndex < 4; ++playerIndex) {
Devices::GamePad *gamePad = this->VirtualGamePads[playerIndex]->GetForwardingGamePad();
delete this->VirtualGamePads[playerIndex];
delete gamePad;
}
for(std::size_t playerIndex = 0; playerIndex < 4; ++playerIndex) {
delete this->GamePads[playerIndex];
}
unsubclassWindow();
}
// ------------------------------------------------------------------------------------------- //
void WindowsInputManager::Impl::tryInitializeDirectInput() {
// Try to load dinput8.dll. If it isn't on the system (or cannot be loaded for
// some reason), we'll get a null handle. This allows a clean fallback instead
// of an ugly error message to the user.
HMODULE dinputDllHandle = ::LoadLibrary(L"dinput8.dll");
// If we could load dinput8.dll, we can attempt to initialize DirectInput
if(isValidHandle(dinputDllHandle)) {
::FreeLibrary(dinputDllHandle);
// Ugly COM stuff, creates the DirectInput COM object and returns it under
// the interface we requested.
HRESULT result = ::DirectInput8Create(
this->instanceHandle,
DIRECTINPUT_VERSION,
IID_IDirectInput8,
reinterpret_cast(&directInput),
nullptr
);
if(FAILED(result)) {
directInput = nullptr;
}
}
}
// ------------------------------------------------------------------------------------------- //
std::vector WindowsInputManager::Impl::EnumerateDirectInputGamePads() const {
std::vector deviceInstances;
// Let DirectInput fill our vector through the callback
HRESULT result = this->directInput->EnumDevices(
DI8DEVCLASS_GAMECTRL,
addDeviceInstance,
&deviceInstances,
DIEDFL_ALLDEVICES
);
if(FAILED(result)) {
throw std::runtime_error("Error enumerating game controllers via DirectInput");
}
#if TESTING_ONLY_DONT_FILTER_XINPUT_DEVICES
return deviceInstances; // Only for testing, do not ship with this statement enabled!
#endif
// Remove all devices which are also XINPUT devices so we don't have them under both APIs
std::set vidPids = XInputDeviceEnumerator::GetXInputVidPidPairs();
for(std::size_t index = 0; index < deviceInstances.size();) {
DWORD deviceVidPid = deviceInstances[index].guidProduct.Data1;
if(vidPids.find(deviceVidPid) == vidPids.end()) {
++index;
} else {
deviceInstances.erase(deviceInstances.begin() + index);
}
}
return deviceInstances;
}
// ------------------------------------------------------------------------------------------- //
void WindowsInputManager::Impl::subclassWindow() {
BOOL result = ::SetPropA(
this->windowHandle, "Nuclex::Input::WindowsInputManager::Impl", this
);
if(result == 0) {
throw std::runtime_error("Could not assign property to window");
}
this->oldWindowProc = reinterpret_cast(
::SetWindowLongPtrA(
this->windowHandle, GWLP_WNDPROC,
reinterpret_cast(&WindowsInputManager::Impl::windowProcedure)
)
);
}
// ------------------------------------------------------------------------------------------- //
void WindowsInputManager::Impl::unsubclassWindow() {
::SetWindowLongPtrA(
this->windowHandle, GWLP_WNDPROC, reinterpret_cast(this->oldWindowProc)
);
}
// ------------------------------------------------------------------------------------------- //
LRESULT CALLBACK WindowsInputManager::Impl::windowProcedure(
HWND windowHandle, UINT message, WPARAM parameter1, LPARAM parameter2
) {
WindowsInputManager::Impl *self = reinterpret_cast(
::GetPropA(windowHandle, "Nuclex::Input::WindowsInputManager::Impl")
);
if(self == nullptr) {
return ::DefWindowProcA(windowHandle, message, parameter1, parameter2);
} else {
if(!self->Keyboard.ProcessWindowMessage(message, parameter1, parameter2)) {
if(!self->Mouse.ProcessWindowMessage(message, parameter1, parameter2)) {
return ::CallWindowProcA(
self->oldWindowProc, windowHandle, message, parameter1, parameter2
);
}
}
return 0;
}
}
// ------------------------------------------------------------------------------------------- //
}} // namespace Nuclex::Input