// HEADER FILES ------------------------------------------------------------ #define WIN32_LEAN_AND_MEAN #define DIRECTINPUT_VERSION 0x800 #define _WIN32_WINNT 0x0501 #include #include #ifndef __GNUC__ #include #endif #include #include #define USE_WINDOWS_DWORD #include "i_input.h" #include "i_system.h" #include "d_event.h" #include "d_gui.h" #include "c_cvars.h" #include "c_dispatch.h" #include "doomdef.h" #include "doomstat.h" #include "win32iface.h" #include "templates.h" #include "gameconfigfile.h" #include "cmdlib.h" #include "v_text.h" #include "m_argv.h" #include "rawinput.h" // WBEMIDL BITS -- because w32api doesn't have this, either ----------------- #ifdef __GNUC__ struct IWbemClassObject : public IUnknown { public: virtual HRESULT __stdcall GetQualifierSet() = 0; virtual HRESULT __stdcall Get(LPCWSTR wszName, long lFlags, VARIANT *pVal, long *pType, long *plFlavor) = 0; virtual HRESULT __stdcall Put() = 0; virtual HRESULT __stdcall Delete() = 0; virtual HRESULT __stdcall GetNames() = 0; virtual HRESULT __stdcall BeginEnumeration() = 0; virtual HRESULT __stdcall Next() = 0; virtual HRESULT __stdcall EndEnumeration() = 0; virtual HRESULT __stdcall GetPropertyQualifierSet() = 0; virtual HRESULT __stdcall Clone() = 0; virtual HRESULT __stdcall GetObjectText() = 0; virtual HRESULT __stdcall SpawnDerivedClass() = 0; virtual HRESULT __stdcall SpawnInstance() = 0; virtual HRESULT __stdcall CompareTo() = 0; virtual HRESULT __stdcall GetPropertyOrigin() = 0; virtual HRESULT __stdcall InheritsFrom() = 0; virtual HRESULT __stdcall GetMethod() = 0; virtual HRESULT __stdcall PutMethod() = 0; virtual HRESULT __stdcall DeleteMethod() = 0; virtual HRESULT __stdcall BeginMethodEnumeration() = 0; virtual HRESULT __stdcall NextMethod() = 0; virtual HRESULT __stdcall EndMethodEnumeration() = 0; virtual HRESULT __stdcall GetMethodQualifierSet() = 0; virtual HRESULT __stdcall GetMethodOrigin() = 0; }; struct IEnumWbemClassObject : public IUnknown { public: virtual HRESULT __stdcall Reset() = 0; virtual HRESULT __stdcall Next(long lTimeout, ULONG uCount, IWbemClassObject **apObjects, ULONG *puReturned) = 0; virtual HRESULT __stdcall NextAsync() = 0; virtual HRESULT __stdcall Clone() = 0; virtual HRESULT __stdcall Skip(long lTimeout, ULONG nCount) = 0; }; struct IWbemServices : public IUnknown { public: virtual HRESULT __stdcall OpenNamespace() = 0; virtual HRESULT __stdcall CancelAsyncCall() = 0; virtual HRESULT __stdcall QueryObjectSink() = 0; virtual HRESULT __stdcall GetObject() = 0; virtual HRESULT __stdcall GetObjectAsync() = 0; virtual HRESULT __stdcall PutClass() = 0; virtual HRESULT __stdcall PutClassAsync() = 0; virtual HRESULT __stdcall DeleteClass() = 0; virtual HRESULT __stdcall DeleteClassAsync() = 0; virtual HRESULT __stdcall CreateClassEnum() = 0; virtual HRESULT __stdcall CreateClassEnumAsync() = 0; virtual HRESULT __stdcall PutInstance() = 0; virtual HRESULT __stdcall PutInstanceAsync() = 0; virtual HRESULT __stdcall DeleteInstance() = 0; virtual HRESULT __stdcall DeleteInstanceAsync() = 0; virtual HRESULT __stdcall CreateInstanceEnum( const BSTR strFilter, long lFlags, void *pCtx, IEnumWbemClassObject **ppEnum) = 0; virtual HRESULT __stdcall CreateInstanceEnumAsync() = 0; virtual HRESULT __stdcall ExecQuery() = 0; virtual HRESULT __stdcall ExecQueryAsync() = 0; virtual HRESULT __stdcall ExecNotificationQuery() = 0; virtual HRESULT __stdcall ExecNotificationQueryAsync() = 0; virtual HRESULT __stdcall ExecMethod() = 0; virtual HRESULT __stdcall ExecMethodAsync() = 0; }; struct IWbemLocator : public IUnknown { public: virtual HRESULT __stdcall ConnectServer( const BSTR strNetworkResource, const BSTR strUser, const BSTR strPassword, const BSTR strLocale, long lSecurityFlags, const BSTR strAuthority, void *pCtx, IWbemServices **ppNamespace) = 0; }; #endif // MACROS ------------------------------------------------------------------ #define DEFAULT_DEADZONE 0.25f // TYPES ------------------------------------------------------------------- class FDInputJoystick : public FInputDevice, IJoystickConfig { public: FDInputJoystick(const GUID *instance, FString &name); ~FDInputJoystick(); bool GetDevice(); void ProcessInput(); void AddAxes(float axes[NUM_JOYAXIS]); // IJoystickConfig interface FString GetName(); float GetSensitivity(); virtual void SetSensitivity(float scale); int GetNumAxes(); float GetAxisDeadZone(int axis); EJoyAxis GetAxisMap(int axis); const char *GetAxisName(int axis); float GetAxisScale(int axis); void SetAxisDeadZone(int axis, float deadzone); void SetAxisMap(int axis, EJoyAxis gameaxis); void SetAxisScale(int axis, float scale); bool IsSensitivityDefault(); bool IsAxisDeadZoneDefault(int axis); bool IsAxisMapDefault(int axis); bool IsAxisScaleDefault(int axis); void SetDefaultConfig(); FString GetIdentifier(); protected: struct AxisInfo { FString Name; GUID Guid; DWORD Type; DWORD Ofs; LONG Min, Max; float Value; float DeadZone, DefaultDeadZone; float Multiplier, DefaultMultiplier; EJoyAxis GameAxis, DefaultGameAxis; BYTE ButtonValue; }; struct ButtonInfo { FString Name; GUID Guid; DWORD Type; DWORD Ofs; BYTE Value; }; LPDIRECTINPUTDEVICE8 Device; GUID Instance; FString Name; bool Marked; float Multiplier; int Warmup; TArray Axes; TArray Buttons; TArray POVs; DIOBJECTDATAFORMAT *Objects; DIDATAFORMAT DataFormat; static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef); void OrderAxes(); bool ReorderAxisPair(const GUID &x, const GUID &y, int pos); HRESULT SetDataFormat(); bool SetConfigSection(bool create); friend class FDInputJoystickManager; }; class FDInputJoystickManager : public FJoystickCollection { public: FDInputJoystickManager(); ~FDInputJoystickManager(); bool GetDevice(); void ProcessInput(); void AddAxes(float axes[NUM_JOYAXIS]); void GetDevices(TArray &sticks); IJoystickConfig *Rescan(); protected: struct Enumerator { GUID Instance; FString Name; }; struct EnumData { TArray *All; bool GenericDevices; }; TArray Devices; FDInputJoystick *EnumDevices(); static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); static int STACK_ARGS NameSort(const void *a, const void *b); static bool IsXInputDevice(const GUID *guid); static bool IsXInputDeviceFast(const GUID *guid); static bool IsXInputDeviceSlow(const GUID *guid); }; // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- // PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- // PRIVATE FUNCTION PROTOTYPES --------------------------------------------- static void MapAxis(FIntCVar &var, int num); // EXTERNAL DATA DECLARATIONS ---------------------------------------------- extern LPDIRECTINPUT8 g_pdi; extern HWND Window; // PUBLIC DATA DEFINITIONS ------------------------------------------------- CUSTOM_CVAR(Bool, joy_dinput, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_NOINITCALL) { I_StartupDirectInputJoystick(); event_t ev = { EV_DeviceChange }; D_PostEvent(&ev); } // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const BYTE POVButtons[9] = { 0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09, 0x00 }; //("dc12a687-737f-11cf-884d-00aa004b2e24") static const IID IID_IWbemLocator = { 0xdc12a687, 0x737f, 0x11cf, { 0x88, 0x4d, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } }; //("4590f811-1d3a-11d0-891f-00aa004b2e24") static const CLSID CLSID_WbemLocator = { 0x4590f811, 0x1d3a, 0x11d0, { 0x89, 0x1f, 0x00, 0xaa, 0x00, 0x4b, 0x2e, 0x24 } }; // CODE -------------------------------------------------------------------- //=========================================================================== // // FDInputJoystick - Constructor // //=========================================================================== FDInputJoystick::FDInputJoystick(const GUID *instance, FString &name) { Device = NULL; DataFormat.rgodf = NULL; Instance = *instance; Name = name; Marked = false; } //=========================================================================== // // FDInputJoystick - Destructor // //=========================================================================== FDInputJoystick::~FDInputJoystick() { unsigned int i; if (Device != NULL) { M_SaveJoystickConfig(this); Device->Release(); Device = NULL; } if (DataFormat.rgodf != NULL) { delete[] DataFormat.rgodf; } // Send key ups before destroying this. if (Axes.Size() == 1) { Joy_GenerateButtonEvents(Axes[0].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS); } else { Joy_GenerateButtonEvents(Axes[1].ButtonValue, 0, 4, KEY_JOYAXIS1PLUS); for (i = 2; i < Axes.Size(); ++i) { Joy_GenerateButtonEvents(Axes[i].ButtonValue, 0, 2, KEY_JOYAXIS1PLUS + i*2); } } for (i = 0; i < Buttons.Size(); ++i) { if (Buttons[i].Value) { event_t ev = { EV_KeyUp }; ev.data1 = KEY_FIRSTJOYBUTTON + i; } } for (i = 0; i < POVs.Size(); ++i) { Joy_GenerateButtonEvents(POVs[i].Value, 0, 4, KEY_JOYPOV1_UP + i*4); } } //=========================================================================== // // FDInputJoystick :: GetDevice // //=========================================================================== bool FDInputJoystick::GetDevice() { HRESULT hr; if (g_pdi == NULL) { return false; } hr = g_pdi->CreateDevice(Instance, &Device, NULL); if (FAILED(hr) || Device == NULL) { return false; } hr = Device->EnumObjects(EnumObjectsCallback, this, DIDFT_ABSAXIS | DIDFT_BUTTON | DIDFT_POV); OrderAxes(); hr = SetDataFormat(); if (FAILED(hr)) { Printf(TEXTCOLOR_ORANGE "Setting data format for %s failed.\n", Name.GetChars()); return false; } hr = Device->SetCooperativeLevel(Window, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if (FAILED(hr)) { Printf(TEXTCOLOR_ORANGE "Setting cooperative level for %s failed.\n", Name.GetChars()); return false; } Device->Acquire(); M_LoadJoystickConfig(this); Warmup = 4; return true; } //=========================================================================== // // FDInputJoystick :: ProcessInput // // Send button events and record axes for later. // //=========================================================================== void FDInputJoystick::ProcessInput() { HRESULT hr; BYTE *state; unsigned i; event_t ev; if (Device == NULL) { return; } hr = Device->Poll(); if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) { hr = Device->Acquire(); } if (FAILED(hr)) { return; } state = (BYTE *)alloca(DataFormat.dwDataSize); hr = Device->GetDeviceState(DataFormat.dwDataSize, state); if (FAILED(hr)) return; // Some controllers send false values when they are first // initialized, so give some time for them to get past that // before we pay any attention to their state. if (Warmup > 0) { Warmup--; return; } // Convert axis values to floating point and save them for a later call // to AddAxes(). Axes that are past their dead zone will also be translated // into button presses. for (i = 0; i < Axes.Size(); ++i) { AxisInfo *info = &Axes[i]; LONG value = *(LONG *)(state + info->Ofs); double axisval; BYTE buttonstate = 0; // Scale to [-1.0, 1.0] axisval = (value - info->Min) * 2.0 / (info->Max - info->Min) - 1.0; // Cancel out dead zone axisval = Joy_RemoveDeadZone(axisval, info->DeadZone, &buttonstate); info->Value = float(axisval); if (i < NUM_JOYAXISBUTTONS && (i > 2 || Axes.Size() == 1)) { Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 2, KEY_JOYAXIS1PLUS + i*2); } else if (i == 1) { // Since we sorted the axes, we know that the first two are definitely X and Y. // They are probably a single stick, so use angular position to determine buttons. buttonstate = Joy_XYAxesToButtons(axisval, Axes[0].Value); Joy_GenerateButtonEvents(info->ButtonValue, buttonstate, 4, KEY_JOYAXIS1PLUS); } info->ButtonValue = buttonstate; } // Compare button states and generate events for buttons that have changed. memset(&ev, 0, sizeof(ev)); for (i = 0; i < Buttons.Size(); ++i) { ButtonInfo *info = &Buttons[i]; BYTE newstate = *(BYTE *)(state + info->Ofs) & 0x80; if (newstate != info->Value) { info->Value = newstate; ev.data1 = KEY_FIRSTJOYBUTTON + i; ev.type = (newstate != 0) ? EV_KeyDown : EV_KeyUp; D_PostEvent(&ev); } } // POV hats are treated as a set of four buttons, because if it's a // D-pad, that's exactly what it is. for (i = 0; i < POVs.Size(); ++i) { ButtonInfo *info = &POVs[i]; DWORD povangle = *(DWORD *)(state + info->Ofs); int pov; // Smoosh POV angles down into octants. 8 is centered. pov = (LOWORD(povangle) == 0xFFFF) ? 8 : ((povangle + 2250) % 36000) / 4500; // Convert octant to one or two buttons needed to represent it. pov = POVButtons[pov]; // Send events for POV "buttons" that have changed. Joy_GenerateButtonEvents(info->Value, pov, 4, KEY_JOYPOV1_UP + i*4); info->Value = pov; } } //=========================================================================== // // FDInputJoystick :: AddAxes // // Add the values of each axis to the game axes. // //=========================================================================== void FDInputJoystick::AddAxes(float axes[NUM_JOYAXIS]) { for (unsigned i = 0; i < Axes.Size(); ++i) { // Add to the game axis. axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * Axes[i].Multiplier); } } //=========================================================================== // // FDInputJoystick :: EnumObjectsCallback STATIC // // Finds all axes, buttons, and hats on the controller. // //=========================================================================== BOOL CALLBACK FDInputJoystick::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef) { FDInputJoystick *joy = (FDInputJoystick *)pvRef; if (lpddoi->guidType == GUID_Button) { ButtonInfo info; info.Name = lpddoi->tszName; info.Guid = lpddoi->guidType; info.Type = lpddoi->dwType; info.Ofs = 0; info.Value = 0; // We don't have the key labels necessary to support more than 128 // joystick buttons. This is what DIJOYSTATE2 offers, so we // probably don't need to worry about any devices with more than // that. if (joy->Buttons.Size() < 128) { joy->Buttons.Push(info); } } else if (lpddoi->guidType == GUID_POV) { ButtonInfo info; info.Name = lpddoi->tszName; info.Guid = lpddoi->guidType; info.Type = lpddoi->dwType; info.Ofs = 0; info.Value = 0; // We don't have the key labels necessary to support more than 4 // hats. I don't know any devices with more than 1, and the // standard DirectInput DIJOYSTATE does not support more than 4 // hats either, so this is probably a non-issue. if (joy->POVs.Size() < 4) { joy->POVs.Push(info); } } else if (lpddoi->guidType == GUID_XAxis || lpddoi->guidType == GUID_YAxis || lpddoi->guidType == GUID_ZAxis || lpddoi->guidType == GUID_RxAxis || lpddoi->guidType == GUID_RyAxis || lpddoi->guidType == GUID_RzAxis || lpddoi->guidType == GUID_Slider) { DIPROPRANGE diprg; AxisInfo info; diprg.diph.dwSize = sizeof(DIPROPRANGE); diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); diprg.diph.dwObj = lpddoi->dwType; diprg.diph.dwHow = DIPH_BYID; diprg.lMin = 0; diprg.lMax = 0; joy->Device->GetProperty(DIPROP_RANGE, &diprg.diph); info.Name = lpddoi->tszName; info.Guid = lpddoi->guidType; info.Type = lpddoi->dwType; info.Ofs = 0; info.Min = diprg.lMin; info.Max = diprg.lMax; info.GameAxis = JOYAXIS_None; info.Value = 0; info.ButtonValue = 0; joy->Axes.Push(info); } return DIENUM_CONTINUE; } //=========================================================================== // // FDInputJoystick :: OrderAxes // // Try to put the axes in some sort of sane order. X and Y axes are pretty // much standard. Unfortunately, the rest are entirely up to the // manufacturers to decide how they want to assign them. // //=========================================================================== void FDInputJoystick::OrderAxes() { // Make X,Y the first pair. if (!ReorderAxisPair(GUID_XAxis, GUID_YAxis, 0)) { return; } // The second pair is either Rx,Ry or Rz,Z, depending on what we have if (!ReorderAxisPair(GUID_RxAxis, GUID_RyAxis, 2)) { ReorderAxisPair(GUID_RzAxis, GUID_ZAxis, 2); } } bool FDInputJoystick::ReorderAxisPair(const GUID &xid, const GUID &yid, int pos) { unsigned i; int x, y; // Find each axis. x = -1; y = -1; for (i = 0; i < Axes.Size(); ++i) { if (x < 0 && Axes[i].Guid == xid) { x = i; } else if (y < 0 && Axes[i].Guid == yid) { y = i; } } // If we don't have both X and Y axes, do nothing. if (x < 0 || y < 0) { return false; } if (x == pos + 1 && y == pos) { // Xbox 360 Controllers return them in this order. swapvalues(Axes[pos], Axes[pos + 1]); } else if (x != pos || y != pos + 1) { AxisInfo xinfo = Axes[x], yinfo = Axes[y]; Axes.Delete(x); if (x < y) { y--; } Axes.Delete(y); Axes.Insert(pos, xinfo); Axes.Insert(pos + 1, yinfo); } return true; } //=========================================================================== // // FDInputJoystick :: SetDataFormat // // Using the objects we previously enumerated, construct a data format // structure for DirectInput to use for this device. We could use the // predefined c_dfDIJoystick2, except that we would have no way of knowing // which axis mapped to a certain point in the structure if there is more // than one of a particular type of axis. The dwOfs member of // DIDEVICEOBJECTINSTANCE is practically useless, because it describes the // offset of the object in the device's native data format, and not // the offset in something we can actually use. // //=========================================================================== HRESULT FDInputJoystick::SetDataFormat() { DIOBJECTDATAFORMAT *objects; DWORD numobjs; DWORD nextofs; unsigned i; objects = new DIOBJECTDATAFORMAT[Axes.Size() + POVs.Size() + Buttons.Size()]; numobjs = nextofs = 0; // Add all axes for (i = 0; i < Axes.Size(); ++i) { objects[i].pguid = &Axes[i].Guid; objects[i].dwOfs = Axes[i].Ofs = nextofs; objects[i].dwType = Axes[i].Type; objects[i].dwFlags = 0; nextofs += sizeof(LONG); } numobjs = i; // Add all POVs for (i = 0; i < POVs.Size(); ++i) { objects[numobjs + i].pguid = &POVs[i].Guid; objects[numobjs + i].dwOfs = POVs[i].Ofs = nextofs; objects[numobjs + i].dwType = POVs[i].Type; objects[numobjs + i].dwFlags = 0; nextofs += sizeof(DWORD); } numobjs += i; // Add all buttons for (i = 0; i < Buttons.Size(); ++i) { objects[numobjs + i].pguid = &Buttons[i].Guid; objects[numobjs + i].dwOfs = Buttons[i].Ofs = nextofs; objects[numobjs + i].dwType = Buttons[i].Type; objects[numobjs + i].dwFlags = 0; nextofs += sizeof(BYTE); } numobjs += i; // Set format DataFormat.dwSize = sizeof(DIDATAFORMAT); DataFormat.dwObjSize = sizeof(DIOBJECTDATAFORMAT); DataFormat.dwFlags = DIDF_ABSAXIS; DataFormat.dwDataSize = (nextofs + 3) & ~3; // Round to the nearest multiple of 4. DataFormat.dwNumObjs = numobjs; DataFormat.rgodf = objects; return Device->SetDataFormat(&DataFormat); } //=========================================================================== // // FDInputJoystick :: GetIdentifier // //=========================================================================== FString FDInputJoystick::GetIdentifier() { char id[48]; id[0] = 'D'; id[1] = 'I'; id[2] = ':'; FormatGUID(id + 3, countof(id) - 3, Instance); return id; } //=========================================================================== // // FDInputJoystick :: SetDefaultConfig // // Try for a reasonable default axis configuration. // //=========================================================================== void FDInputJoystick::SetDefaultConfig() { unsigned i; Multiplier = 1; for (i = 0; i < Axes.Size(); ++i) { Axes[i].DeadZone = DEFAULT_DEADZONE; Axes[i].Multiplier = 1; Axes[i].GameAxis = JOYAXIS_None; } // Triggers on a 360 controller have a much smaller deadzone. if (Axes.Size() == 5 && Axes[4].Guid == GUID_ZAxis) { Axes[4].DeadZone = 30 / 256.f; } // Two axes? Horizontal is yaw and vertical is forward. if (Axes.Size() == 2) { Axes[0].GameAxis = JOYAXIS_Yaw; Axes[1].GameAxis = JOYAXIS_Forward; } // Three axes? First two are movement, third is yaw. else if (Axes.Size() >= 3) { Axes[0].GameAxis = JOYAXIS_Side; Axes[1].GameAxis = JOYAXIS_Forward; Axes[2].GameAxis = JOYAXIS_Yaw; // Four axes? First two are movement, last two are looking around. if (Axes.Size() >= 4) { Axes[3].GameAxis = JOYAXIS_Pitch; Axes[3].Multiplier = 0.75f; // Five axes? Use the fifth one for moving up and down. if (Axes.Size() >= 5) { Axes[4].GameAxis = JOYAXIS_Up; } } } // If there is only one axis, then we make no assumptions about how // the user might want to use it. // Preserve defaults for config saving. for (i = 0; i < Axes.Size(); ++i) { Axes[i].DefaultDeadZone = Axes[i].DeadZone; Axes[i].DefaultMultiplier = Axes[i].Multiplier; Axes[i].DefaultGameAxis = Axes[i].GameAxis; } } //=========================================================================== // // FDInputJoystick :: GetName // //=========================================================================== FString FDInputJoystick::GetName() { return Name; } //=========================================================================== // // FDInputJoystick :: GetSensitivity // //=========================================================================== float FDInputJoystick::GetSensitivity() { return Multiplier; } //=========================================================================== // // FDInputJoystick :: SetSensitivity // //=========================================================================== void FDInputJoystick::SetSensitivity(float scale) { Multiplier = scale; } //=========================================================================== // // FDInputJoystick :: IsSensitivityDefault // //=========================================================================== bool FDInputJoystick::IsSensitivityDefault() { return Multiplier == 1; } //=========================================================================== // // FDInputJoystick :: GetNumAxes // //=========================================================================== int FDInputJoystick::GetNumAxes() { return (int)Axes.Size(); } //=========================================================================== // // FDInputJoystick :: GetAxisDeadZone // //=========================================================================== float FDInputJoystick::GetAxisDeadZone(int axis) { if (unsigned(axis) >= Axes.Size()) { return 0; } return Axes[axis].DeadZone; } //=========================================================================== // // FDInputJoystick :: GetAxisMap // //=========================================================================== EJoyAxis FDInputJoystick::GetAxisMap(int axis) { if (unsigned(axis) >= Axes.Size()) { return JOYAXIS_None; } return Axes[axis].GameAxis; } //=========================================================================== // // FDInputJoystick :: GetAxisName // //=========================================================================== const char *FDInputJoystick::GetAxisName(int axis) { if (unsigned(axis) < Axes.Size()) { return Axes[axis].Name; } return "Invalid"; } //=========================================================================== // // FDInputJoystick :: GetAxisScale // //=========================================================================== float FDInputJoystick::GetAxisScale(int axis) { if (unsigned(axis) >= Axes.Size()) { return 0; } return Axes[axis].Multiplier; } //=========================================================================== // // FDInputJoystick :: SetAxisDeadZone // //=========================================================================== void FDInputJoystick::SetAxisDeadZone(int axis, float deadzone) { if (unsigned(axis) < Axes.Size()) { Axes[axis].DeadZone = clamp(deadzone, 0.f, 1.f); } } //=========================================================================== // // FDInputJoystick :: SetAxisMap // //=========================================================================== void FDInputJoystick::SetAxisMap(int axis, EJoyAxis gameaxis) { if (unsigned(axis) < Axes.Size()) { Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None; } } //=========================================================================== // // FDInputJoystick :: SetAxisScale // //=========================================================================== void FDInputJoystick::SetAxisScale(int axis, float scale) { if (unsigned(axis) < Axes.Size()) { Axes[axis].Multiplier = scale; } } //=========================================================================== // // FDInputJoystick :: IsAxisDeadZoneDefault // //=========================================================================== bool FDInputJoystick::IsAxisDeadZoneDefault(int axis) { if (unsigned(axis) < Axes.Size()) { return Axes[axis].DeadZone == Axes[axis].DefaultDeadZone; } return true; } //=========================================================================== // // FDInputJoystick :: IsAxisScaleDefault // //=========================================================================== bool FDInputJoystick::IsAxisScaleDefault(int axis) { if (unsigned(axis) < Axes.Size()) { return Axes[axis].Multiplier == Axes[axis].DefaultMultiplier; } return true; } //=========================================================================== // // FDInputJoystick :: IsAxisMapDefault // //=========================================================================== bool FDInputJoystick::IsAxisMapDefault(int axis) { if (unsigned(axis) < Axes.Size()) { return Axes[axis].GameAxis == Axes[axis].DefaultGameAxis; } return true; } //=========================================================================== // // FDInputJoystickManager - Constructor // //=========================================================================== FDInputJoystickManager::FDInputJoystickManager() { } //=========================================================================== // // FDInputJoystickManager - Destructor // //=========================================================================== FDInputJoystickManager::~FDInputJoystickManager() { for (unsigned i = 0; i < Devices.Size(); ++i) { delete Devices[i]; } } //=========================================================================== // // FDInputJoystickManager :: GetDevice // //=========================================================================== bool FDInputJoystickManager::GetDevice() { if (g_pdi == NULL) { return false; } EnumDevices(); return true; } //=========================================================================== // // FDInputJoystickManager :: ProcessInput // // Process input for every attached device. // //=========================================================================== void FDInputJoystickManager::ProcessInput() { for (unsigned i = 0; i < Devices.Size(); ++i) { if (Devices[i] != NULL) { Devices[i]->ProcessInput(); } } } //=========================================================================== // // FDInputJoystickManager :: AddAxes // // Adds the state of all attached device axes to the passed array. // //=========================================================================== void FDInputJoystickManager :: AddAxes(float axes[NUM_JOYAXIS]) { for (unsigned i = 0; i < Devices.Size(); ++i) { Devices[i]->AddAxes(axes); } } //=========================================================================== // // FDInputJoystickManager :: GetDevices // // Adds the IJoystick interfaces for each device we created to the sticks // array. // //=========================================================================== void FDInputJoystickManager::GetDevices(TArray &sticks) { for (unsigned i = 0; i < Devices.Size(); ++i) { sticks.Push(Devices[i]); } } //=========================================================================== // // FDInputJoystickManager :: EnumCallback STATIC // // Adds each DirectInput game controller to a TArray. // //=========================================================================== BOOL CALLBACK FDInputJoystickManager::EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) { EnumData *data = (EnumData *)pvRef; // Special check for the Microsoft SideWinder Strategic Commander, // which for some odd reason reports itself as a generic device and // not a game controller. if (data->GenericDevices && lpddi->guidProduct.Data1 != MAKELONG(0x45e, 0x0033)) { return DIENUM_CONTINUE; } // Do not add PS2 adapters if Raw PS2 Input was initialized. // Do not add XInput devices if XInput was initialized. if ((JoyDevices[INPUT_RawPS2] == NULL || !I_IsPS2Adapter(lpddi->guidProduct.Data1)) && (JoyDevices[INPUT_XInput] == NULL || !IsXInputDevice(&lpddi->guidProduct))) { Enumerator thisone; thisone.Instance = lpddi->guidInstance; thisone.Name = lpddi->tszInstanceName; data->All->Push(thisone); } return DIENUM_CONTINUE; } //=========================================================================== // // FDInputJoystickManager :: IsXInputDevice STATIC // // Does the DirectInput product GUID correspond to an XInput controller? // If the product's device ID contains contains "IG_" // (ex. "VID_045E&PID_028E&IG_00"), then it is an XInput device. // Unfortunately this information can not be found by just using DirectInput. // //=========================================================================== bool FDInputJoystickManager::IsXInputDevice(const GUID *guid) { if (MyGetRawInputDeviceList == NULL || MyGetRawInputDeviceInfoA == NULL) { return IsXInputDeviceSlow(guid); } else { return IsXInputDeviceFast(guid); } } //=========================================================================== // // FDInputJoystickManager :: IsXInputDeviceSlow STATIC // // Pretty much copied straight from the article "XInput and DirectInput". // // Enum each PNP device using WMI and check each device ID. This is // Microsoft's reference implementation, but it is damn slow. After // a hardware change, connecting to the WMI server can take nearly three // seconds, and creating the instance enumerator consistantly takes longer // than 0.25 seconds. // // The XInput DLL can apparently be hacked fairly simply to work with // Windows 2000, and since Raw Input wasn't introduced until XP, I think // that's reason enough to keep this version around, despite it being // so horrendously slow. // //=========================================================================== bool FDInputJoystickManager::IsXInputDeviceSlow(const GUID *guid) { IWbemLocator *wbemlocator = NULL; IEnumWbemClassObject *enumdevices = NULL; IWbemClassObject *devices[20] = { 0 }; IWbemServices *wbemservices = NULL; BSTR namespce = NULL; BSTR deviceid = NULL; BSTR classname = NULL; DWORD returned = 0; bool isxinput = false; UINT device = 0; VARIANT var; HRESULT hr; // Create WMI hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&wbemlocator); if (FAILED(hr) || wbemlocator == NULL) goto cleanup; if (NULL == (namespce = SysAllocString(OLESTR("\\\\.\\root\\cimv2")))) goto cleanup; if (NULL == (classname = SysAllocString(OLESTR("Win32_PNPEntity")))) goto cleanup; if (NULL == (deviceid = SysAllocString(OLESTR("DeviceID")))) goto cleanup; // Connect to WMI hr = wbemlocator->ConnectServer(namespce, NULL, NULL, 0, 0, NULL, NULL, &wbemservices); if (FAILED(hr) || wbemservices == NULL) goto cleanup; // Switch security level to IMPERSONATE. CoSetProxyBlanket(wbemservices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE); hr = wbemservices->CreateInstanceEnum(classname, 0, NULL, &enumdevices); if (FAILED(hr) || enumdevices == NULL) goto cleanup; // Loop over all devices for (;;) { // Get 20 at a time. hr = enumdevices->Next(10000, countof(devices), devices, &returned); if (FAILED(hr)) goto cleanup; if (returned == 0) break; for (device = 0; device < returned; device++) { // For each device, get its device ID. hr = devices[device]->Get(deviceid, 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 cannot be found from DirectInput. if (wcsstr(var.bstrVal, L"IG_")) { // If it does, then get the VID/PID from var.bstrVal. DWORD pid = 0, vid = 0; WCHAR *strvid = wcsstr(var.bstrVal, L"VID_"); if (strvid && swscanf(strvid, L"VID_%4X", &vid) != 1) vid = 0; WCHAR *strpid = wcsstr(var.bstrVal, L"PID_"); if (strpid && swscanf(strpid, L"PID_%4X", &pid) != 1) pid = 0; // Compare the VID/PID to the DInput device. DWORD vidpid = MAKELONG(vid, pid); if (vidpid == guid->Data1) { isxinput = true; goto cleanup; } } } SAFE_RELEASE(devices[device]); } } cleanup: if (namespce) SysFreeString(namespce); if (deviceid) SysFreeString(deviceid); if (classname) SysFreeString(classname); for (device = 0; device < countof(devices); ++device) SAFE_RELEASE(devices[device]); SAFE_RELEASE(enumdevices); SAFE_RELEASE(wbemlocator); SAFE_RELEASE(wbemservices); return isxinput; } //=========================================================================== // // FDInputJoystickManager :: IsXInputDeviceFast STATIC // // The theory of operation is the same as for IsXInputDeviceSlow, except that // we use the Raw Input device list to find the device ID instead of WMI. // This generally takes ~0.02 ms on my machine, though it occasionally spikes // at over 1 ms. Either way, it is a huge order of magnitude faster than WMI. // (around 10,000 times faster, in fact!) // //=========================================================================== bool FDInputJoystickManager::IsXInputDeviceFast(const GUID *guid) { UINT nDevices, numDevices; RAWINPUTDEVICELIST *devices; UINT i; bool isxinput = false; if (MyGetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { return false; } if ((devices = (RAWINPUTDEVICELIST *)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL) { return false; } if ((numDevices = MyGetRawInputDeviceList(devices, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT)-1) { free(devices); return false; } for (i = 0; i < numDevices; ++i) { // I am making the assumption here that all possible XInput devices // will report themselves as generic HID devices and not as keyboards // or mice. if (devices[i].dwType == RIM_TYPEHID) { RID_DEVICE_INFO rdi; UINT cbSize; cbSize = rdi.cbSize = sizeof(rdi); if ((INT)MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) >= 0) { if(MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)guid->Data1) { char name[256]; UINT namelen = countof(name); UINT reslen; reslen = MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, name, &namelen); if (reslen != (UINT)-1) { isxinput = (strstr(name, "IG_") != NULL); break; } } } } } free(devices); return isxinput; } //=========================================================================== // // FDInputJoystickManager :: NameSort STATIC // //=========================================================================== int FDInputJoystickManager::NameSort(const void *a, const void *b) { const Enumerator *ea = (const Enumerator *)a; const Enumerator *eb = (const Enumerator *)b; int lex = ea->Name.Compare(eb->Name); if (lex == 0) { return memcmp(&ea->Instance, &eb->Instance, sizeof(GUID)); } return lex; } //=========================================================================== // // FDInputJoystickManager :: EnumDevices // // Find out what DirectInput game controllers are on the system and create // FDInputJoystick objects for them. May return a pointer to the first new // device found. // //=========================================================================== FDInputJoystick *FDInputJoystickManager::EnumDevices() { FDInputJoystick *newone = NULL; TArray controllers; EnumData data; unsigned i, j, k; // Find all game controllers data.All = &controllers; data.GenericDevices = false; g_pdi->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &data, DIEDFL_ALLDEVICES); // Again, just for the Strategic Commander data.GenericDevices = true; g_pdi->EnumDevices(DI8DEVCLASS_DEVICE, EnumCallback, &data, DIEDFL_ALLDEVICES); // Sort by name so that devices with duplicate names can have numbers appended. qsort(&controllers[0], controllers.Size(), sizeof(Enumerator), NameSort); for (i = 1; i < controllers.Size(); ++i) { // Does this device have the same name as the previous one? If so, how // many more have the same name? for (j = i; j < controllers.Size(); ++j) { if (controllers[j-1].Name.Compare(controllers[j].Name) != 0) break; } // j is one past the last duplicate name. if (j > i) { // Append numbers. for (k = i - 1; k < j; ++k) { controllers[k].Name.AppendFormat(" #%d", k - i + 2); } } } // Compare the new list of devices with the one we previously instantiated. // Every device we currently hold is marked 0. Then scan through the new // list and try to find it there, if it's found, it is marked 1. At the end // of this, devices marked 1 existed before and are left alone. Devices // marked 0 are no longer present and should be destroyed. If a device is // present in the new list that we have not yet instantiated, we // instantiate it now. for (j = 0; j < Devices.Size(); ++j) { Devices[j]->Marked = false; } for (i = 0; i < controllers.Size(); ++i) { GUID *lookfor = &controllers[i].Instance; for (j = 0; j < Devices.Size(); ++j) { if (Devices[j]->Instance == *lookfor) { Devices[j]->Marked = true; break; } } if (j == Devices.Size()) { // Not found. Add it. FDInputJoystick *device = new FDInputJoystick(lookfor, controllers[i].Name); if (!device->GetDevice()) { delete device; } else { device->Marked = true; Devices.Push(device); if (newone == NULL) { newone = device; } } } } // Remove detached devices and avoid holes in the list. for (i = j = 0; j < Devices.Size(); ++j) { if (!Devices[j]->Marked) { delete Devices[j]; } else { if (i != j) { Devices[i] = Devices[j]; } ++i; } } Devices.Resize(i); return newone; } //=========================================================================== // // FDInputJoystickManager :: Rescan // // Used by the joy_ps2raw and joy_xinput callbacks to rescan the // DirectInput devices, since their presence or absence can affect the // results of the scan. // //=========================================================================== IJoystickConfig *FDInputJoystickManager::Rescan() { return EnumDevices(); } //=========================================================================== // // I_StartupDirectInputJoystick // //=========================================================================== void I_StartupDirectInputJoystick() { if (!joy_dinput || !use_joystick || Args->CheckParm("-nojoy")) { if (JoyDevices[INPUT_DIJoy] != NULL) { delete JoyDevices[INPUT_DIJoy]; JoyDevices[INPUT_DIJoy] = NULL; UpdateJoystickMenu(NULL); } } else { if (JoyDevices[INPUT_DIJoy] == NULL) { FJoystickCollection *joys = new FDInputJoystickManager; if (joys->GetDevice()) { JoyDevices[INPUT_DIJoy] = joys; } } } }