From 417779440d9e9aaf9ad36b257b8bdfdb94d13223 Mon Sep 17 00:00:00 2001 From: Randy Heit Date: Sat, 11 Jul 2009 01:40:01 +0000 Subject: [PATCH] - Added joystick config loading and saving support; moved generic joystick interfaces to m_joy.h. - Added Raw Input PS2 adapter support. SVN r1713 (trunk) --- docs/rh-log.txt | 9 +- src/CMakeLists.txt | 2 + src/doomdef.h | 12 - src/g_game.cpp | 1 + src/m_joy.cpp | 144 +++++ src/m_joy.h | 53 ++ src/m_options.cpp | 1 + src/sdl/i_input.cpp | 6 + src/sdl/i_input.h | 2 - src/win32/i_dijoy.cpp | 220 ++++--- src/win32/i_input.cpp | 10 +- src/win32/i_input.h | 26 +- src/win32/i_rawps2.cpp | 1317 ++++++++++++++++++++++++++++++++++++++++ src/win32/i_xinput.cpp | 74 ++- zdoom.vcproj | 12 + 15 files changed, 1733 insertions(+), 156 deletions(-) create mode 100644 src/m_joy.cpp create mode 100644 src/m_joy.h create mode 100644 src/win32/i_rawps2.cpp diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 0857687ea..00a92bfdd 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,11 @@ -July 6, 2009 (Changes by Graf Zahl) +July 10, 2009 +- Added joystick config loading and saving support; moved generic joystick + interfaces to m_joy.h. + +July 9, 2009 +- Added Raw Input PS2 adapter support. + +July 6, 2009 (Changes by Graf Zahl) - Added Gez's A_WeaponReady enhancement submission. July 5, 2009 (Changes by Graf Zahl) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ffb66eed..a55d0470c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -412,6 +412,7 @@ if( WIN32 ) win32/i_keyboard.cpp win32/i_mouse.cpp win32/i_dijoy.cpp + win32/i_rawps2.cpp win32/i_xinput.cpp win32/i_main.cpp win32/i_movie.cpp @@ -527,6 +528,7 @@ add_executable( zdoom WIN32 m_argv.cpp m_bbox.cpp m_cheat.cpp + m_joy.cpp m_menu.cpp m_misc.cpp m_options.cpp diff --git a/src/doomdef.h b/src/doomdef.h index 03921f09b..bec8985d4 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -217,18 +217,6 @@ enum ESkillLevels #define NUM_KEYS 0x1C4 -enum EJoyAxis -{ - JOYAXIS_None = -1, - JOYAXIS_Yaw, - JOYAXIS_Pitch, - JOYAXIS_Forward, - JOYAXIS_Side, - JOYAXIS_Up, -// JOYAXIS_Roll, // Ha ha. No roll for you. - NUM_JOYAXIS, -}; - // [RH] dmflags bits (based on Q2's) enum { diff --git a/src/g_game.cpp b/src/g_game.cpp index 038e2bded..6f0636ddb 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -75,6 +75,7 @@ #include "d_net.h" #include "d_event.h" #include "p_acs.h" +#include "m_joy.h" #include diff --git a/src/m_joy.cpp b/src/m_joy.cpp new file mode 100644 index 000000000..8da19e068 --- /dev/null +++ b/src/m_joy.cpp @@ -0,0 +1,144 @@ +// HEADER FILES ------------------------------------------------------------ + +#include "m_joy.h" +#include "gameconfigfile.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// M_SetJoystickConfigSection +// +// Sets up the config for reading or writing this controller's axis config. +// +//========================================================================== + +static bool M_SetJoystickConfigSection(IJoystickConfig *joy, bool create) +{ + FString id = "Joy:"; + id += joy->GetIdentifier(); + return GameConfig->SetSection(id, create); +} + +//========================================================================== +// +// M_LoadJoystickConfig +// +//========================================================================== + +bool M_LoadJoystickConfig(IJoystickConfig *joy) +{ + char key[32]; + const char *value; + int axislen; + int numaxes; + + joy->SetDefaultConfig(); + if (!M_SetJoystickConfigSection(joy, false)) + { + return false; + } + value = GameConfig->GetValueForKey("Sensitivity"); + if (value != NULL) + { + joy->SetSensitivity((float)atof(value)); + } + numaxes = joy->GetNumAxes(); + for (int i = 0; i < numaxes; ++i) + { + axislen = mysnprintf(key, countof(key), "Axis%u", i); + + mysnprintf(key + axislen, countof(key) - axislen, "deadzone"); + value = GameConfig->GetValueForKey(key); + if (value != NULL) + { + joy->SetAxisDeadZone(i, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "scale"); + value = GameConfig->GetValueForKey(key); + if (value != NULL) + { + joy->SetAxisScale(i, (float)atof(value)); + } + + mysnprintf(key + axislen, countof(key) - axislen, "map"); + value = GameConfig->GetValueForKey(key); + if (value != NULL) + { + EJoyAxis gameaxis = (EJoyAxis)atoi(value); + if (gameaxis < JOYAXIS_None || gameaxis >= NUM_JOYAXIS) + { + gameaxis = JOYAXIS_None; + } + joy->SetAxisMap(i, gameaxis); + } + } + return true; +} + +//========================================================================== +// +// M_SaveJoystickConfig +// +// Only saves settings that are not at their defaults. +// +//========================================================================== + +void M_SaveJoystickConfig(IJoystickConfig *joy) +{ + char key[32], value[32]; + int axislen, numaxes; + + if (M_SetJoystickConfigSection(joy, true)) + { + GameConfig->ClearCurrentSection(); + if (!joy->IsSensitivityDefault()) + { + mysnprintf(value, countof(value), "%g", joy->GetSensitivity()); + GameConfig->SetValueForKey("Sensitivity", value); + } + numaxes = joy->GetNumAxes(); + for (int i = 0; i < numaxes; ++i) + { + axislen = mysnprintf(key, countof(key), "Axis%u", i); + + if (!joy->IsAxisDeadZoneDefault(i)) + { + mysnprintf(key + axislen, countof(key) - axislen, "deadzone"); + mysnprintf(value, countof(value), "%g", joy->GetAxisDeadZone(i)); + GameConfig->SetValueForKey(key, value); + } + if (!joy->IsAxisScaleDefault(i)) + { + mysnprintf(key + axislen, countof(key) - axislen, "scale"); + mysnprintf(value, countof(value), "%g", joy->GetAxisScale(i)); + GameConfig->SetValueForKey(key, value); + } + if (!joy->IsAxisMapDefault(i)) + { + mysnprintf(key + axislen, countof(key) - axislen, "map"); + mysnprintf(value, countof(value), "%d", joy->GetAxisMap(i)); + GameConfig->SetValueForKey(key, value); + } + } + // If the joystick is entirely at its defaults, delete this section + // so that we don't write out a lone section header. + if (GameConfig->SectionIsEmpty()) + { + GameConfig->DeleteCurrentSection(); + } + } +} diff --git a/src/m_joy.h b/src/m_joy.h new file mode 100644 index 000000000..ce773b323 --- /dev/null +++ b/src/m_joy.h @@ -0,0 +1,53 @@ +#ifndef M_JOY_H +#define M_JOY_H + +#include "doomtype.h" +#include "tarray.h" + +enum EJoyAxis +{ + JOYAXIS_None = -1, + JOYAXIS_Yaw, + JOYAXIS_Pitch, + JOYAXIS_Forward, + JOYAXIS_Side, + JOYAXIS_Up, +// JOYAXIS_Roll, // Ha ha. No roll for you. + NUM_JOYAXIS, +}; + +// Generic configuration interface for a controller. +struct NOVTABLE IJoystickConfig +{ + virtual FString GetName() = 0; + virtual float GetSensitivity() = 0; + virtual void SetSensitivity(float scale) = 0; + + virtual int GetNumAxes() = 0; + virtual float GetAxisDeadZone(int axis) = 0; + virtual EJoyAxis GetAxisMap(int axis) = 0; + virtual const char *GetAxisName(int axis) = 0; + virtual float GetAxisScale(int axis) = 0; + + virtual void SetAxisDeadZone(int axis, float zone) = 0; + virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0; + virtual void SetAxisScale(int axis, float scale) = 0; + + // Used by the saver to not save properties that are at their defaults. + virtual bool IsSensitivityDefault() = 0; + virtual bool IsAxisDeadZoneDefault(int axis) = 0; + virtual bool IsAxisMapDefault(int axis) = 0; + virtual bool IsAxisScaleDefault(int axis) = 0; + + virtual void SetDefaultConfig() = 0; + virtual FString GetIdentifier() = 0; +}; + +bool M_LoadJoystickConfig(IJoystickConfig *joy); +void M_SaveJoystickConfig(IJoystickConfig *joy); + +// These ought to be provided by a system-specific i_input.cpp. +void I_GetAxes(float axes[NUM_JOYAXIS]); +void I_GetJoysticks(TArray &sticks); + +#endif diff --git a/src/m_options.cpp b/src/m_options.cpp index 63f8edf3e..b0e0ff015 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -53,6 +53,7 @@ #include "i_music.h" #include "i_input.h" +#include "m_joy.h" #include "v_video.h" #include "v_text.h" diff --git a/src/sdl/i_input.cpp b/src/sdl/i_input.cpp index 78754d19e..7867dd2eb 100644 --- a/src/sdl/i_input.cpp +++ b/src/sdl/i_input.cpp @@ -17,6 +17,7 @@ #include "dikeys.h" #include "templates.h" #include "s_sound.h" +#include "m_joy.h" static void I_CheckGUICapture (); static void I_CheckNativeMouse (); @@ -461,6 +462,11 @@ void I_StartFrame () } } +void I_GetJoysticks(TArray &sticks) +{ + sticks.Clear(); +} + void I_GetAxes(float axes[NUM_JOYAXIS]) { for (int i = 0; i < NUM_JOYAXIS; ++i) diff --git a/src/sdl/i_input.h b/src/sdl/i_input.h index cfbf476d9..01d3673c6 100644 --- a/src/sdl/i_input.h +++ b/src/sdl/i_input.h @@ -4,7 +4,5 @@ void I_PutInClipboard (const char *str); FString I_GetFromClipboard (bool use_primary_selection); -void I_GetAxes(float axes[NUM_JOYAXIS]); - #endif diff --git a/src/win32/i_dijoy.cpp b/src/win32/i_dijoy.cpp index 0de745fbf..dc2b205f5 100644 --- a/src/win32/i_dijoy.cpp +++ b/src/win32/i_dijoy.cpp @@ -131,10 +131,6 @@ public: bool GetDevice(); void ProcessInput(); void AddAxes(float axes[NUM_JOYAXIS]); - void SaveConfig(); - bool LoadConfig(); - FString GetIdentifier(); - void SetDefaultConfig(); // IJoystickConfig interface FString GetName(); @@ -151,6 +147,14 @@ public: 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 { @@ -160,9 +164,9 @@ protected: DWORD Ofs; LONG Min, Max; float Value; - float DeadZone; - float Multiplier; - EJoyAxis GameAxis; + float DeadZone, DefaultDeadZone; + float Multiplier, DefaultMultiplier; + EJoyAxis GameAxis, DefaultGameAxis; BYTE ButtonValue; }; struct ButtonInfo @@ -220,7 +224,6 @@ protected: static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); static int STACK_ARGS NameSort(const void *a, const void *b); - static int STACK_ARGS GUIDSort(const void *a, const void *b); static bool IsXInputDevice(const GUID *guid); }; @@ -282,7 +285,12 @@ FDInputJoystick::FDInputJoystick(const GUID *instance, FString &name) FDInputJoystick::~FDInputJoystick() { - SAFE_RELEASE(Device); + if (Device != NULL) + { + M_SaveJoystickConfig(this); + Device->Release(); + Device = NULL; + } if (DataFormat.rgodf != NULL) { delete[] DataFormat.rgodf; @@ -323,7 +331,7 @@ bool FDInputJoystick::GetDevice() return false; } Device->Acquire(); - LoadConfig(); + M_LoadJoystickConfig(this); Warmup = 4; return true; } @@ -680,107 +688,6 @@ FString FDInputJoystick::GetIdentifier() return id; } -//=========================================================================== -// -// FDInputJoystick :: SetConfigSection -// -// Sets up the config for reading or writing this controller's axis config. -// -//=========================================================================== - -bool FDInputJoystick::SetConfigSection(bool create) -{ - FString id = GetIdentifier(); - id += ".Axes"; - return GameConfig->SetSection(id, create); - DIDEVICEINSTANCE inst = { sizeof(DIDEVICEINSTANCE), }; -} - -//=========================================================================== -// -// FDInputJoystick :: LoadConfig -// -//=========================================================================== - -bool FDInputJoystick::LoadConfig() -{ - char key[32]; - const char *value; - int axislen; - - SetDefaultConfig(); - if (!SetConfigSection(false)) - { - return false; - } - value = GameConfig->GetValueForKey("Multiplier"); - if (value != NULL) - { - Multiplier = (float)atof(value); - } - for (unsigned i = 0; i < Axes.Size(); ++i) - { - axislen = mysnprintf(key, countof(key), "Axis%u", i); - - mysnprintf(key + axislen, countof(key) - axislen, "deadzone"); - value = GameConfig->GetValueForKey(key); - if (value != NULL) - { - Axes[i].DeadZone = (float)atof(value); - } - mysnprintf(key + axislen, countof(key) - axislen, "multiplier"); - value = GameConfig->GetValueForKey(key); - if (value != NULL) - { - Axes[i].Multiplier = (float)atof(value); - } - mysnprintf(key + axislen, countof(key) - axislen, "gameaxis"); - value = GameConfig->GetValueForKey(key); - if (value != NULL) - { - Axes[i].GameAxis = (EJoyAxis)atoi(value); - if (Axes[i].GameAxis < JOYAXIS_None || Axes[i].GameAxis >= NUM_JOYAXIS) - { - Axes[i].GameAxis = JOYAXIS_None; - } - } - } - return true; -} - -//=========================================================================== -// -// FDInputJoystick :: SaveConfig -// -//=========================================================================== - -void FDInputJoystick::SaveConfig() -{ - char key[32], value[32]; - int axislen; - - if (SetConfigSection(true)) - { - GameConfig->ClearCurrentSection(); - mysnprintf(value, countof(value), "%g", Multiplier); - GameConfig->SetValueForKey("Multiplier", value); - for (unsigned i = 0; i < Axes.Size(); ++i) - { - axislen = mysnprintf(key, countof(key), "Axis%u", i); - - mysnprintf(key + axislen, countof(key) - axislen, "deadzone"); - mysnprintf(value, countof(value), "%g", Axes[i].DeadZone); - GameConfig->SetValueForKey(key, value); - mysnprintf(key + axislen, countof(key) - axislen, "multiplier"); - mysnprintf(value, countof(value), "%g", Axes[i].Multiplier); - GameConfig->SetValueForKey(key, value); - mysnprintf(key + axislen, countof(key) - axislen, "gameaxis"); - mysnprintf(value, countof(value), "%d", Axes[i].GameAxis); - GameConfig->SetValueForKey(key, value); - } - } -} - //=========================================================================== // // FDInputJoystick :: SetDefaultConfig @@ -791,8 +698,10 @@ void FDInputJoystick::SaveConfig() void FDInputJoystick::SetDefaultConfig() { + unsigned i; + Multiplier = 1; - for (unsigned i = 0; i < Axes.Size(); ++i) + for (i = 0; i < Axes.Size(); ++i) { Axes[i].DeadZone = DEFAULT_DEADZONE; Axes[i].Multiplier = 1; @@ -828,6 +737,14 @@ void FDInputJoystick::SetDefaultConfig() } // 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; + } } //=========================================================================== @@ -863,6 +780,17 @@ void FDInputJoystick::SetSensitivity(float scale) Multiplier = scale; } +//=========================================================================== +// +// FDInputJoystick :: IsSensitivityDefault +// +//=========================================================================== + +bool FDInputJoystick::IsSensitivityDefault() +{ + return Multiplier == 1; +} + //=========================================================================== // // FDInputJoystick :: GetNumAxes @@ -976,6 +904,51 @@ void FDInputJoystick::SetAxisScale(int axis, float 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 @@ -1109,8 +1082,10 @@ bool FDInputJoystickManager::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, BOOL CALLBACK FDInputJoystickManager::EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef) { + // Do not add PS2 adapters if Raw PS2 Input was initialized. // Do not add XInput devices if XInput was initialized. - if (JoyDevices[INPUT_XInput] == NULL || !IsXInputDevice(&lpddi->guidProduct)) + if ((JoyDevices[INPUT_RawPS2] == NULL || !I_IsPS2Adapter(lpddi->guidProduct.Data1)) && + (JoyDevices[INPUT_XInput] == NULL || !IsXInputDevice(&lpddi->guidProduct))) { TArray *all = (TArray *)pvRef; Enumerator thisone; @@ -1415,3 +1390,22 @@ void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, in } } } + +void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys) +{ + int changed = oldbuttons ^ newbuttons; + if (changed != 0) + { + event_t ev = { 0 }; + int mask = 1; + for (int j = 0; j < numbuttons; mask <<= 1, ++j) + { + if (changed & mask) + { + ev.data1 = keys[j]; + ev.type = (newbuttons & mask) ? EV_KeyDown : EV_KeyUp; + D_PostEvent(&ev); + } + } + } +} \ No newline at end of file diff --git a/src/win32/i_input.cpp b/src/win32/i_input.cpp index fa47c484f..7968ad4e3 100644 --- a/src/win32/i_input.cpp +++ b/src/win32/i_input.cpp @@ -336,9 +336,12 @@ LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) int code = GET_RAWINPUT_CODE_WPARAM(wParam); if (Keyboard == NULL || !Keyboard->ProcessRawInput((RAWINPUT *)buffer, code)) { - if (Mouse != NULL) + if (Mouse == NULL || !Mouse->ProcessRawInput((RAWINPUT *)buffer, code)) { - Mouse->ProcessRawInput((RAWINPUT *)buffer, code); + if (JoyDevices[INPUT_RawPS2] != NULL) + { + JoyDevices[INPUT_RawPS2]->ProcessRawInput((RAWINPUT *)buffer, code); + } } } } @@ -625,6 +628,9 @@ bool I_InitInput (void *hwnd) Printf ("I_StartupXInput\n"); I_StartupXInput(); + Printf ("I_StartupRawPS2\n"); + I_StartupRawPS2(); + Printf ("I_StartupDirectInputJoystick\n"); I_StartupDirectInputJoystick(); diff --git a/src/win32/i_input.h b/src/win32/i_input.h index cfbddc1ee..c1e35c2ac 100644 --- a/src/win32/i_input.h +++ b/src/win32/i_input.h @@ -44,26 +44,9 @@ FString I_GetFromClipboard (bool windows_has_no_selection_clipboard); void I_GetEvent(); -struct NOVTABLE IJoystickConfig -{ - virtual FString GetName() = 0; - virtual float GetSensitivity() = 0; - virtual void SetSensitivity(float scale) = 0; - - virtual int GetNumAxes() = 0; - virtual float GetAxisDeadZone(int axis) = 0; - virtual EJoyAxis GetAxisMap(int axis) = 0; - virtual const char *GetAxisName(int axis) = 0; - virtual float GetAxisScale(int axis) = 0; - - virtual void SetAxisDeadZone(int axis, float zone) = 0; - virtual void SetAxisMap(int axis, EJoyAxis gameaxis) = 0; - virtual void SetAxisScale(int axis, float scale) = 0; -}; -void I_GetAxes(float axes[NUM_JOYAXIS]); -void I_GetJoysticks(TArray &sticks); - #ifdef USE_WINDOWS_DWORD +#include "m_joy.h" + // Don't make these definitions available to the main body of the source code. struct tagRAWINPUT; @@ -138,7 +121,7 @@ enum { INPUT_DIJoy, INPUT_XInput, - INPUT_PS2EMS, + INPUT_RawPS2, NUM_JOYDEVICES }; @@ -149,8 +132,11 @@ void I_CheckNativeMouse(bool prefer_native); void I_StartupKeyboard(); void I_StartupXInput(); void I_StartupDirectInputJoystick(); +void I_StartupRawPS2(); +bool I_IsPS2Adapter(DWORD vidpid); void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, int base); +void Joy_GenerateButtonEvents(int oldbuttons, int newbuttons, int numbuttons, const int *keys); double Joy_RemoveDeadZone(double axisval, double deadzone, BYTE *buttons); // USB HID usage page numbers diff --git a/src/win32/i_rawps2.cpp b/src/win32/i_rawps2.cpp new file mode 100644 index 000000000..375f4803e --- /dev/null +++ b/src/win32/i_rawps2.cpp @@ -0,0 +1,1317 @@ +// HEADER FILES ------------------------------------------------------------ + +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0501 +#include +#include +#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 "m_menu.h" +#include "templates.h" +#include "gameconfigfile.h" +#include "cmdlib.h" +#include "v_text.h" +#include "m_argv.h" +#include "rawinput.h" + +// MACROS ------------------------------------------------------------------ + +#define DEFAULT_DEADZONE 0.25f +#define STATUS_SWITCH_TIME 3 + +#define VID_PLAY_COM 0x0b43 +#define PID_EMS_USB2_PS2_CONTROLLER_ADAPTER 0x0003 + +#define VID_GREENASIA 0x0e8f +#define PID_DRAGON_PS2_CONTROLLER_ADAPTER 0x0003 +#define PID_PANTHERLORD_PS2_CONTROLLER_ADAPTER 0x0029 + +#define VID_PERSONAL_COMMUNICATION_SYSTEMS 0x0810 +#define PID_TWIN_USB_VIBRATION_GAMEPAD 0x0001 + +#define STATUS_DISCONNECTED 0xFF +#define STATUS_DIGITAL 0x41 +#define STATUS_ANALOG 0x73 + +// TYPES ------------------------------------------------------------------- + +enum EAdapterType +{ + ADAPTER_EMSUSB2, + ADAPTER_DragonPlus, + ADAPTER_PantherLord, + ADAPTER_TwinUSB, + ADAPTER_Unknown +}; + +struct FAdapterHandle +{ + HANDLE Handle; + EAdapterType Type; + int ControllerNumber; + FString DeviceID; +}; + +class FRawPS2Controller : public IJoystickConfig +{ +public: + FRawPS2Controller(HANDLE handle, EAdapterType type, int sequence, int controller, FString devid); + ~FRawPS2Controller(); + + bool ProcessInput(RAWHID *raw, int code); + void AddAxes(float axes[NUM_JOYAXIS]); + bool IsConnected() { return Connected; } + + // 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 + { + float Value; + float DeadZone; + float Multiplier; + EJoyAxis GameAxis; + BYTE ButtonValue; + }; + struct DefaultAxisConfig + { + EJoyAxis GameAxis; + float Multiplier; + }; + enum + { + AXIS_ThumbLX, + AXIS_ThumbLY, + AXIS_ThumbRX, + AXIS_ThumbRY, + NUM_AXES + }; + + HANDLE Handle; + FString DeviceID; + int ControllerNumber; + int Sequence; + DWORD DisconnectCount; + EAdapterType Type; + float Multiplier; + AxisInfo Axes[NUM_AXES]; + static DefaultAxisConfig DefaultAxes[NUM_AXES]; + WORD LastButtons; + bool Connected; + bool Marked; + bool Active; + + void Attached(); + void Detached(); + void NeutralInput(); + + static void ProcessThumbstick(int value, AxisInfo *axis, int base); + + friend class FRawPS2Manager; +}; + +class FRawPS2Manager : public FJoystickCollection +{ +public: + FRawPS2Manager(); + ~FRawPS2Manager(); + + bool GetDevice(); + bool ProcessRawInput(RAWINPUT *raw, int code); + bool WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result); + void AddAxes(float axes[NUM_JOYAXIS]); + void GetDevices(TArray &sticks); + +protected: + HMODULE XInputDLL; + TArray Devices; + bool Registered; + + void DoRegister(); + FRawPS2Controller *EnumDevices(); + static int STACK_ARGS DeviceSort(const void *a, const void *b); +}; + +// Each entry is an offset to the corresponding data field in the +// adapter's data packet. Some of these fields are optional and are +// assigned negative values if the adapter does not include them. +struct PS2Descriptor +{ + const char *AdapterName; + BYTE PacketSize; + SBYTE ControllerNumber; + SBYTE ControllerStatus; + BYTE LeftX; + BYTE LeftY; + BYTE RightX; + BYTE RightY; + SBYTE DPadHat; + BYTE DPadButtonsNibble:1; + SBYTE DPadButtons:7; // up, right, down, left + BYTE ButtonSet1:7; // triangle, circle, cross, square + BYTE ButtonSet1Nibble:1; + BYTE ButtonSet2:7; // L2, R2, L1, R1 + BYTE ButtonSet2Nibble:1; + BYTE ButtonSet3:7; // select, start, lthumb, rthumb + BYTE ButtonSet3Nibble:1; +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +extern HWND Window; + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +static const int ButtonKeys[16] = +{ + KEY_PAD_DPAD_UP, + KEY_PAD_DPAD_RIGHT, + KEY_PAD_DPAD_DOWN, + KEY_PAD_DPAD_LEFT, + + KEY_PAD_Y, // triangle + KEY_PAD_B, // circle + KEY_PAD_A, // cross + KEY_PAD_X, // square + + KEY_PAD_LTRIGGER, // L2 + KEY_PAD_RTRIGGER, // R2 + KEY_PAD_LSHOULDER, // L1 + KEY_PAD_RSHOULDER, // R1 + + KEY_PAD_BACK, // select + KEY_PAD_START, + KEY_PAD_LTHUMB, + KEY_PAD_RTHUMB +}; + +static const BYTE HatButtons[16] = +{ + 1, 1+2, 2, 2+4, 4, 4+8, 8, 8+1, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const PS2Descriptor Descriptors[ADAPTER_Unknown] = +{ + { // ADAPTER_EMS_USB2 + "EMS USB2 Adapter", + 8, // packet size + 0, // controller number + 7, // controller status + 3, // left x + 4, // left y + 5, // right x + 6, // right y + -1, // hat + 1, // d-pad buttons nibble + 2, // d-pad buttons + 1, // buttons 1 + 0, // buttons 1 nibble + 1, // buttons 2 + 1, // buttons 2 nibble + 2, // buttons 3 + 0, // buttons 3 nibble + }, + { // ADAPTER_DragonPlus + "Dragon+ Adapter", + 9, // packet size + -1, // controller number + -1, // controller status + 3, // left x + 4, // left y + 2, // right x + 1, // right y + 6, // hat + 0, // d-pad buttons nibble + -1, // d-pad buttons + 6, // buttons 1 + 1, // buttons 1 nibble + 7, // buttons 2 + 0, // buttons 2 nibble + 7, // buttons 3 + 1, // buttons 3 nibble + }, + { // ADAPTER_PantherLord + // Windows indentifies it as a ga451-USB device, but I call it a PantherLord + // because there's a box in the middle of the cable with a PantherLord sticker + // on it. + "PantherLord Adapter", + 9, // packet size + -1, // controller number + -1, // controller status + 4, // left x + 5, // left y + 3, // right x + 2, // right y + -1, // hat ... This device has both a hat and d-pad buttons. + 0, // d-pad buttons nibble Since buttons are better, we just read those. + 8, // d-pad buttons + 6, // buttons 1 + 1, // buttons 1 nibble + 7, // buttons 2 + 0, // buttons 2 nibble + 7, // buttons 3 + 1, // buttons 3 nibble + }, + { // ADAPTER_TwinUSB + "Twin USB Gamepad", + 8, // packet size + 0, // controller number + -1, // controller status + 3, // left x + 4, // left y + 2, // right x + 1, // right y + 5, // hat + 0, // d-pad buttons nibble + -1, // d-pad buttons + 5, // buttons 1 + 1, // buttons 1 nibble + 6, // buttons 2 + 0, // buttons 2 nibble + 6, // buttons 3 + 1, // buttons 3 nibble + } +}; + +static const char *AxisNames[] = +{ + "Left Thumb X Axis", + "Left Thumb Y Axis", + "Right Thumb X Axis", + "Right Thumb Y Axis", +}; + +FRawPS2Controller::DefaultAxisConfig FRawPS2Controller::DefaultAxes[NUM_AXES] = +{ + // Game axis, multiplier + { JOYAXIS_Side, 1 }, // ThumbLX + { JOYAXIS_Forward, 1 }, // ThumbLY + { JOYAXIS_Yaw, 1 }, // ThumbRX + { JOYAXIS_Pitch, 0.75 }, // ThumbRY +}; + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// FRawPS2Controller - Constructor +// +// handle: The Raw Input handle for this device +// type: The adapter type +// sequence: The seqeunce number, for attaching numbers to names +// controller: The controller to check, for multi-controller adapters +// +//========================================================================== + +FRawPS2Controller::FRawPS2Controller(HANDLE handle, EAdapterType type, int sequence, int controller, FString devid) +{ + Handle = handle; + Type = type; + ControllerNumber = controller; + Sequence = sequence; + DeviceID = devid; + + // The EMS USB2 controller provides attachment status. The others do not. + Connected = (Descriptors[type].ControllerStatus < 0); + if (Connected) + { + Attached(); + } + + M_LoadJoystickConfig(this); +} + +//========================================================================== +// +// FRawPS2Controller - Destructor +// +//========================================================================== + +FRawPS2Controller::~FRawPS2Controller() +{ + M_SaveJoystickConfig(this); +} + +//========================================================================== +// +// FRawPS2Controller :: ProcessInput +// +//========================================================================== + +bool FRawPS2Controller::ProcessInput(RAWHID *raw, int code) +{ + // w32api has an incompatible definition of bRawData +#if __GNUC__ + BYTE *rawdata = &raw->bRawData; +#else + BYTE *rawdata = raw->bRawData; +#endif + const PS2Descriptor *desc = &Descriptors[Type]; + bool digital; + + // Ensure packet size is what we expect. + if (raw->dwSizeHid != desc->PacketSize) + { + return false; + } + +#if 0 + // If this is a multi-controller device, check that this packet + // is for the controller we were created for. We probably don't + // need to do this, because the multi-controller adapters + // send data for each controller to seperate device instances. + if (desc->ControllerNumber >= 0 && + raw->bRawData[desc->ControllerNumber] != ControllerNumber) + { + return false; + } +#endif + + // Check for disconnected controller + if (desc->ControllerStatus >= 0) + { + if (rawdata[desc->ControllerStatus] == STATUS_DISCONNECTED) + { + // When you press the Analog button on a controller, the EMS + // USB2 will briefly report the controller as disconnected. + if (++DisconnectCount < STATUS_SWITCH_TIME) + { + NeutralInput(); + return true; + } + if (Connected) + { + Detached(); + } + return true; + } + if (!Connected) + { + Attached(); + } + } + DisconnectCount = 0; + + if (code == RIM_INPUTSINK) + { + NeutralInput(); + return true; + } + + // Check for digital controller + digital = false; + if (desc->ControllerStatus >= 0) + { + // The EMS USB2 is nice enough to actually tell us what type of + // controller is attached. + digital = (rawdata[desc->ControllerStatus] == STATUS_DIGITAL); + } + else + { + // The other adapters don't bother to tell us, but we can still + // make an educated guess. In analog mode, the axes center at 0x80. + // In digital mode, they center at 0x7F, and the right stick is + // fixed at center because it gets translated to presses of the + // four face buttons instead. + digital = (rawdata[desc->RightX] == 0x7F && rawdata[desc->RightY] == 0x7F); + } + + // Convert axes to floating point and cancel out deadzones. + ProcessThumbstick(rawdata[desc->LeftX], &Axes[AXIS_ThumbLX], KEY_PAD_LTHUMB_RIGHT); + ProcessThumbstick(rawdata[desc->LeftY], &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_DOWN); + + // If we know we are digital, ignore the right stick. + if (digital) + { + ProcessThumbstick(0x80, &Axes[AXIS_ThumbRX], KEY_PAD_RTHUMB_RIGHT); + ProcessThumbstick(0x80, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_DOWN); + } + else + { + ProcessThumbstick(rawdata[desc->RightX], &Axes[AXIS_ThumbRX], KEY_PAD_RTHUMB_RIGHT); + ProcessThumbstick(rawdata[desc->RightY], &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_DOWN); + } + + // Generate events for buttons that have changed. + WORD buttons = 0; + + // If we know we are digital, ignore the D-Pad. + if (!digital) + { + if (desc->DPadButtons >= 0) + { + buttons = rawdata[desc->DPadButtons] >> (4 * desc->DPadButtonsNibble); + } + else if (desc->DPadHat >= 0) + { + buttons = HatButtons[rawdata[desc->DPadHat] & 15]; + } + } + buttons |= ((rawdata[desc->ButtonSet1] >> (4 * desc->ButtonSet1Nibble)) & 15) << 4; + buttons |= ((rawdata[desc->ButtonSet2] >> (4 * desc->ButtonSet2Nibble)) & 15) << 8; + buttons |= ((rawdata[desc->ButtonSet3] >> (4 * desc->ButtonSet3Nibble)) & 15) << 12; + Joy_GenerateButtonEvents(LastButtons, buttons, 16, ButtonKeys); + + LastButtons = buttons; + return true; +} + +//========================================================================== +// +// FRawPS2Controller :: ProcessThumbstick STATIC +// +// Converts one axis of a thumb stick to floating point, cancels out the +// deadzone, and generates button up/down events for that axis. +// +//========================================================================== + +void FRawPS2Controller::ProcessThumbstick(int value, AxisInfo *axis, int base) +{ + BYTE buttonstate; + double axisval; + + axisval = value * (2.0 / 255) - 1.0; + axisval = Joy_RemoveDeadZone(axisval, axis->DeadZone, &buttonstate); + Joy_GenerateButtonEvents(axis->ButtonValue, buttonstate, 2, base); + axis->ButtonValue = buttonstate; + axis->Value = float(axisval); +} + +//========================================================================== +// +// FRawPS2Controller :: Attached +// +// This controller was just attached. Set all buttons and axes to 0. +// +//========================================================================== + +void FRawPS2Controller::Attached() +{ + int i; + + Connected = true; + DisconnectCount = 0; + LastButtons = 0; + for (i = 0; i < NUM_AXES; ++i) + { + Axes[i].Value = 0; + Axes[i].ButtonValue = 0; + } + UpdateJoystickMenu(this); +} + +//========================================================================== +// +// FRawPS2Controller :: Detached +// +// This controller was just detached. Send button ups for buttons that +// were pressed the last time we got input from it. +// +//========================================================================== + +void FRawPS2Controller::Detached() +{ + Connected = false; + NeutralInput(); + UpdateJoystickMenu(NULL); +} + +//========================================================================== +// +// FRawPS2Controller :: NeutralInput +// +// Sets the controller's state to a neutral one. Either because the +// controller is disconnected or because we are in the background. +// +//========================================================================== + +void FRawPS2Controller::NeutralInput() +{ + int i; + + for (i = 0; i < NUM_AXES; ++i) + { + ProcessThumbstick(0x80, &Axes[i], KEY_PAD_LTHUMB_RIGHT + i*2); + } + Joy_GenerateButtonEvents(LastButtons, 0, 16, ButtonKeys); + LastButtons = 0; +} + +//========================================================================== +// +// FRawPS2Controller :: AddAxes +// +// Add the values of each axis to the game axes. +// +//========================================================================== + +void FRawPS2Controller::AddAxes(float axes[NUM_JOYAXIS]) +{ + float mul = Multiplier; + int i; + + if (Button_Speed.bDown) + { + mul *= 0.5f; + } + + // Add to game axes. + for (i = 0; i < NUM_AXES; ++i) + { + axes[Axes[i].GameAxis] -= float(Axes[i].Value * mul * Axes[i].Multiplier); + } +} + +//========================================================================== +// +// FRawPS2Controller :: SetDefaultConfig +// +//========================================================================== + +void FRawPS2Controller::SetDefaultConfig() +{ + Multiplier = 1; + for (int i = 0; i < NUM_AXES; ++i) + { + Axes[i].DeadZone = DEFAULT_DEADZONE; + Axes[i].GameAxis = DefaultAxes[i].GameAxis; + Axes[i].Multiplier = DefaultAxes[i].Multiplier; + } +} + +//========================================================================== +// +// FRawPS2Controller :: GetIdentifier +// +//========================================================================== + +FString FRawPS2Controller::GetIdentifier() +{ + FString id = "PS2:"; + id += DeviceID; + return id; +} + +//========================================================================== +// +// FRawPS2Controller :: GetName +// +//========================================================================== + +FString FRawPS2Controller::GetName() +{ + FString res = Descriptors[Type].AdapterName; + if (Sequence != 0) + { + res.AppendFormat(" #%d", Sequence); + } + return res; +} + +//========================================================================== +// +// FRawPS2Controller :: GetSensitivity +// +//========================================================================== + +float FRawPS2Controller::GetSensitivity() +{ + return Multiplier; +} + +//========================================================================== +// +// FRawPS2Controller :: SetSensitivity +// +//========================================================================== + +void FRawPS2Controller::SetSensitivity(float scale) +{ + Multiplier = scale; +} + +//========================================================================== +// +// FRawPS2Controller :: IsSensitivityDefault +// +//========================================================================== + +bool FRawPS2Controller::IsSensitivityDefault() +{ + return Multiplier == 1; +} + +//========================================================================== +// +// FRawPS2Controller :: GetNumAxes +// +//========================================================================== + +int FRawPS2Controller::GetNumAxes() +{ + return NUM_AXES; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisDeadZone +// +//========================================================================== + +float FRawPS2Controller::GetAxisDeadZone(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DeadZone; + } + return 0; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisMap +// +//========================================================================== + +EJoyAxis FRawPS2Controller::GetAxisMap(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].GameAxis; + } + return JOYAXIS_None; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisName +// +//========================================================================== + +const char *FRawPS2Controller::GetAxisName(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return AxisNames[axis]; + } + return "Invalid"; +} + +//========================================================================== +// +// FRawPS2Controller :: GetAxisScale +// +//========================================================================== + +float FRawPS2Controller::GetAxisScale(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].Multiplier; + } + return 0; +} + +//========================================================================== +// +// FRawPS2Controller :: SetAxisDeadZone +// +//========================================================================== + +void FRawPS2Controller::SetAxisDeadZone(int axis, float deadzone) +{ + if (unsigned(axis) < NUM_AXES) + { + Axes[axis].DeadZone = clamp(deadzone, 0.f, 1.f); + } +} + +//========================================================================== +// +// FRawPS2Controller :: SetAxisMap +// +//========================================================================== + +void FRawPS2Controller::SetAxisMap(int axis, EJoyAxis gameaxis) +{ + if (unsigned(axis) < NUM_AXES) + { + Axes[axis].GameAxis = (unsigned(gameaxis) < NUM_JOYAXIS) ? gameaxis : JOYAXIS_None; + } +} + +//========================================================================== +// +// FRawPS2Controller :: SetAxisScale +// +//========================================================================== + +void FRawPS2Controller::SetAxisScale(int axis, float scale) +{ + if (unsigned(axis) < NUM_AXES) + { + Axes[axis].Multiplier = scale; + } +} + +//=========================================================================== +// +// FRawPS2Controller :: IsAxisDeadZoneDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsAxisDeadZoneDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DeadZone == DEFAULT_DEADZONE; + } + return true; +} + +//=========================================================================== +// +// FRawPS2Controller :: IsAxisScaleDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsAxisScaleDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].Multiplier == DefaultAxes[axis].Multiplier; + } + return true; +} + +//=========================================================================== +// +// FRawPS2Controller :: IsAxisMapDefault +// +//=========================================================================== + +bool FRawPS2Controller::IsAxisMapDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; + } + return true; +} + +//========================================================================== +// +// FRawPS2Manager - Constructor +// +//========================================================================== + +FRawPS2Manager::FRawPS2Manager() +{ + Registered = false; +} + +//========================================================================== +// +// FRawPS2Manager - Destructor +// +//========================================================================== + +FRawPS2Manager::~FRawPS2Manager() +{ + for (unsigned i = 0; i < Devices.Size(); ++i) + { + if (Devices[i] != NULL) + { + delete Devices[i]; + } + } +} + +//========================================================================== +// +// FRawPS2Manager :: GetDevice +// +//========================================================================== + +bool FRawPS2Manager::GetDevice() +{ + RAWINPUTDEVICE rid; + + if (MyRegisterRawInputDevices == NULL || + MyGetRawInputDeviceList == NULL || + MyGetRawInputDeviceInfoA == NULL) + { + return false; + } + rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE; + rid.usUsage = HID_GDP_JOYSTICK; + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = Window; + if (!MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) + { + return false; + } + rid.dwFlags = RIDEV_REMOVE; + rid.hwndTarget = NULL; // Must be NULL for RIDEV_REMOVE. + MyRegisterRawInputDevices(&rid, 1, sizeof(rid)); + EnumDevices(); + return true; +} + +//=========================================================================== +// +// FRawPS2Manager :: AddAxes +// +// Adds the state of all attached device axes to the passed array. +// +//=========================================================================== + +void FRawPS2Manager::AddAxes(float axes[NUM_JOYAXIS]) +{ + for (unsigned i = 0; i < Devices.Size(); ++i) + { + if (Devices[i]->IsConnected()) + { + Devices[i]->AddAxes(axes); + } + } +} + +//=========================================================================== +// +// FRawPS2Manager :: GetJoysticks +// +// Adds the IJoystick interfaces for each device we created to the sticks +// array, if they are detected as connected. +// +//=========================================================================== + +void FRawPS2Manager::GetDevices(TArray &sticks) +{ + for (unsigned i = 0; i < Devices.Size(); ++i) + { + if (Devices[i]->IsConnected()) + { + sticks.Push(Devices[i]); + } + } +} + +//=========================================================================== +// +// FRawPS2Manager :: WndProcHook +// +// Listen for device change broadcasts and rescan the attached devices +// when they are received. +// +//=========================================================================== + +bool FRawPS2Manager::WndProcHook(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result) +{ + if (message != WM_DEVICECHANGE) + { + return false; + } +#ifdef _DEBUG + char out[64]; + mysnprintf(out, countof(out), "WM_DEVICECHANGE wParam=%d\n", wParam); + OutputDebugString(out); +#endif + if ((wParam != DBT_DEVNODES_CHANGED && + wParam != DBT_DEVICEARRIVAL && + wParam != DBT_CONFIGCHANGED)) + { + return false; + } + UpdateJoystickMenu(EnumDevices()); + // Return false so that other devices can handle this too if they want. + return false; +} + +//=========================================================================== +// +// FRawPS2Manager :: ProcessRawInput +// +//=========================================================================== + +bool FRawPS2Manager::ProcessRawInput(RAWINPUT *raw, int code) +{ + if (raw->header.dwType != RIM_TYPEHID) + { + return false; + } + for (unsigned i = 0; i < Devices.Size(); ++i) + { + if (Devices[i]->Handle == raw->header.hDevice) + { + if (Devices[i]->ProcessInput(&raw->data.hid, code)) + { + return true; + } + } + } + return false; +} + +//=========================================================================== +// +// FRawPS2Manager :: EnumDevices +// +// Find out what PS2 adaptors we understand are on the system and create +// FRawPS2Controller objects for them. May return a pointer to the first new +// device found. +// +//=========================================================================== + +FRawPS2Controller *FRawPS2Manager::EnumDevices() +{ + UINT nDevices, numDevices; + RAWINPUTDEVICELIST *devices; + UINT i, j; + + if (MyGetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) + { + return NULL; + } + if ((devices = (RAWINPUTDEVICELIST *)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL) + { + return NULL; + } + if ((numDevices = MyGetRawInputDeviceList(devices, &nDevices, sizeof(RAWINPUTDEVICELIST))) == (UINT)-1) + { + free(devices); + return NULL; + } + + TArray adapters; + + for (i = 0; i < numDevices; ++i) + { + if (devices[i].dwType == RIM_TYPEHID) + { + RID_DEVICE_INFO rdi; + UINT cbSize; + + cbSize = rdi.cbSize = sizeof(rdi); + if (MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) >= 0) + { + // All the PS2 adapters report themselves as joysticks. + // (By comparison, the 360 controller reports itself as a gamepad.) + if (rdi.hid.usUsagePage == HID_GENERIC_DESKTOP_PAGE && + rdi.hid.usUsage == HID_GDP_JOYSTICK) + { + EAdapterType type = ADAPTER_Unknown; + + // Check vendor and product IDs to see if we know what this is. + if (rdi.hid.dwVendorId == VID_PLAY_COM) + { + if (rdi.hid.dwProductId == PID_EMS_USB2_PS2_CONTROLLER_ADAPTER) + { + type = ADAPTER_EMSUSB2; + } + } + else if (rdi.hid.dwVendorId == VID_GREENASIA) + { + if (rdi.hid.dwProductId == PID_DRAGON_PS2_CONTROLLER_ADAPTER) + { + type = ADAPTER_DragonPlus; + } + else if (rdi.hid.dwProductId == PID_PANTHERLORD_PS2_CONTROLLER_ADAPTER) + { + type = ADAPTER_PantherLord; + } + } + else if (rdi.hid.dwVendorId == VID_PERSONAL_COMMUNICATION_SYSTEMS) + { + if (rdi.hid.dwProductId == PID_TWIN_USB_VIBRATION_GAMEPAD) + { + type = ADAPTER_TwinUSB; + } + } + if (type != ADAPTER_Unknown) + { + // Get the device name. Part of this is a path under HKLM\CurrentControlSet\Enum + // with \ characters replaced by # characters. It is not a human-friendly name. + // The layout for the name string is: + // ## + // The Device ID has multiple #-seperated parts and uniquely identifies + // this device and which USB port it is connected to. + char name[256]; + UINT namelen = countof(name); + char *devid, *devidend; + + if (MyGetRawInputDeviceInfoA(devices[i].hDevice, RIDI_DEVICENAME, name, &namelen) == (UINT)-1) + { // Can't get name. Skip it, since there's stuff in there we need for config. + continue; + } + + devid = strchr(name, '#'); + if (devid == NULL) + { // Should not happen + continue; + } + devidend = strrchr(++devid, '#'); + if (devidend != NULL) + { + *devidend = '\0'; + } + + FAdapterHandle handle = { devices[i].hDevice, type, 0, devid }; + + // Adapters that support more than one controller have a seperate device + // entry for each controller. We can examine the name to determine which + // controller this device is for. + if (Descriptors[type].ControllerNumber >= 0) + { + char *col = strstr(devid, "&Col"); + if (col != NULL) + { + // I have no idea if this number is base 16 or base 10. Every + // other number in the name is base 16, so I assume this one is + // too, but since I don't have anything that goes higher than 02, + // I can't be sure. + handle.ControllerNumber = strtoul(col + 4, NULL, 16); + } + } + adapters.Push(handle); + } + } + } + } + } + free(devices); + + // Sort the found devices so that we have a consistant ordering. + qsort(&adapters[0], adapters.Size(), sizeof(FAdapterHandle), DeviceSort); + + // 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. + FRawPS2Controller *newone = NULL; + EAdapterType lasttype = ADAPTER_Unknown; + int sequence = 0; // Resets to 0 or 1 each time the adapter type changes + + for (j = 0; j < Devices.Size(); ++j) + { + Devices[j]->Marked = false; + } + for (i = 0; i < adapters.Size(); ++i) + { + FAdapterHandle *adapter = &adapters[i]; + + if (adapter->Type != lasttype) + { + lasttype = adapter->Type; + // Peak ahead. If the next adapter has the same type, use 1. + // Otherwise, use 0. (0 means to not append a number to + // the device name.) + if (i == adapters.Size() - 1 || adapter->Type != adapters[i+1].Type) + { + sequence = 0; + } + else + { + sequence = 1; + } + } + + for (j = 0; j < Devices.Size(); ++j) + { + if (Devices[j]->Handle == adapter->Handle) + { + Devices[j]->Marked = true; + break; + } + } + if (j == Devices.Size()) + { // Not found. Add it. + FRawPS2Controller *device = new FRawPS2Controller(adapter->Handle, adapter->Type, sequence++, + adapter->ControllerNumber, adapter->DeviceID); + 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); + DoRegister(); + return newone; +} + +//=========================================================================== +// +// FRawPS2Manager :: DeviceSort STATIC +// +// Sorts first by device type, then by ID, then by controller number. +// +//=========================================================================== + +int FRawPS2Manager::DeviceSort(const void *a, const void *b) +{ + const FAdapterHandle *ha = (const FAdapterHandle *)a; + const FAdapterHandle *hb = (const FAdapterHandle *)b; + int lex = ha->Type - hb->Type; + if (lex == 0) + { + // Skip device part of the ID and sort the connection part + const char *ca = strchr(ha->DeviceID, '#'); + const char *cb = strchr(hb->DeviceID, '#'); + const char *ea, *eb; + // The last bit looks like a controller number. Strip it out to be safe + // if this is a multi-controller adapter. + if (ha->ControllerNumber != 0) + { + ea = strrchr(ca, '&'); + eb = strrchr(cb, '&'); + } + else + { + ea = strlen(ca) + ca; + eb = strlen(cb) + cb; + } + for (; ca < ea && cb < eb && lex == 0; ++ca, ++cb) + { + lex = *ca - *cb; + } + if (lex == 0) + { + lex = ha->ControllerNumber - hb->ControllerNumber; + } + } + return lex; +} + +//=========================================================================== +// +// FRawPS2Manager :: DoRegister +// +// Ensure that we are only listening for input if devices we care about +// are attached to the system. +// +//=========================================================================== + +void FRawPS2Manager::DoRegister() +{ + RAWINPUTDEVICE rid; + rid.usUsagePage = HID_GENERIC_DESKTOP_PAGE; + rid.usUsage = HID_GDP_JOYSTICK; + if (Devices.Size() == 0) + { + if (Registered) + { + rid.dwFlags = RIDEV_REMOVE; + rid.hwndTarget = NULL; + if (MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) + { + Registered = false; + } + } + } + else + { + if (!Registered) + { + rid.dwFlags = RIDEV_INPUTSINK; + rid.hwndTarget = Window; + if (MyRegisterRawInputDevices(&rid, 1, sizeof(rid))) + { + Registered = true; + } + } + } +} + +//=========================================================================== +// +// I_StartupRawPS2 +// +//=========================================================================== + +void I_StartupRawPS2() +{ + FJoystickCollection *joys = new FRawPS2Manager; + if (joys->GetDevice()) + { + JoyDevices[INPUT_RawPS2] = joys; + } +} + +//=========================================================================== +// +// I_IsPS2Adapter +// +// The Data1 part of a DirectInput product GUID contains the device's vendor +// and product IDs. Returns true if we know what this device is. +// +//=========================================================================== + +bool I_IsPS2Adapter(DWORD vidpid) +{ + return (vidpid == MAKELONG(VID_PLAY_COM, PID_EMS_USB2_PS2_CONTROLLER_ADAPTER) || + vidpid == MAKELONG(VID_GREENASIA, PID_DRAGON_PS2_CONTROLLER_ADAPTER) || + vidpid == MAKELONG(VID_GREENASIA, PID_PANTHERLORD_PS2_CONTROLLER_ADAPTER) || + vidpid == MAKELONG(VID_PERSONAL_COMMUNICATION_SYSTEMS, PID_TWIN_USB_VIBRATION_GAMEPAD)); +} diff --git a/src/win32/i_xinput.cpp b/src/win32/i_xinput.cpp index d10f9f843..18d1f6736 100644 --- a/src/win32/i_xinput.cpp +++ b/src/win32/i_xinput.cpp @@ -1,3 +1,5 @@ +#ifndef NO_XINPUT + // HEADER FILES ------------------------------------------------------------ #define WIN32_LEAN_AND_MEAN @@ -26,8 +28,6 @@ // MACROS ------------------------------------------------------------------ -#ifndef NO_XINPUT - // TYPES ------------------------------------------------------------------- typedef DWORD (WINAPI *XInputGetStateType)(DWORD index, XINPUT_STATE *state); @@ -43,8 +43,6 @@ public: void ProcessInput(); void AddAxes(float axes[NUM_JOYAXIS]); - FString GetIdentifier(); - void SetDefaultConfig(); bool IsConnected() { return Connected; } // IJoystickConfig interface @@ -62,6 +60,14 @@ public: 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 { @@ -170,7 +176,7 @@ FXInputController::FXInputController(int index) { Index = index; Connected = false; - SetDefaultConfig(); + M_LoadJoystickConfig(this); } //========================================================================== @@ -181,6 +187,7 @@ FXInputController::FXInputController(int index) FXInputController::~FXInputController() { + M_SaveJoystickConfig(this); } //========================================================================== @@ -419,6 +426,17 @@ void FXInputController::SetSensitivity(float scale) Multiplier = scale; } +//========================================================================== +// +// FXInputController :: IsSensitivityDefault +// +//========================================================================== + +bool FXInputController::IsSensitivityDefault() +{ + return Multiplier == 1; +} + //========================================================================== // // FXInputController :: GetNumAxes @@ -532,6 +550,51 @@ void FXInputController::SetAxisScale(int axis, float scale) } } +//=========================================================================== +// +// FXInputController :: IsAxisDeadZoneDefault +// +//=========================================================================== + +bool FXInputController::IsAxisDeadZoneDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].DeadZone == DefaultAxes[axis].DeadZone; + } + return true; +} + +//=========================================================================== +// +// FXInputController :: IsAxisScaleDefault +// +//=========================================================================== + +bool FXInputController::IsAxisScaleDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].Multiplier == DefaultAxes[axis].Multiplier; + } + return true; +} + +//=========================================================================== +// +// FXInputController :: IsAxisMapDefault +// +//=========================================================================== + +bool FXInputController::IsAxisMapDefault(int axis) +{ + if (unsigned(axis) < NUM_AXES) + { + return Axes[axis].GameAxis == DefaultAxes[axis].GameAxis; + } + return true; +} + //========================================================================== // // FXInputManager - Constructor @@ -694,4 +757,3 @@ void I_StartupXInput() } #endif - diff --git a/zdoom.vcproj b/zdoom.vcproj index 6cd5ef76e..2b0c1b899 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -684,6 +684,10 @@ RelativePath=".\src\m_cheat.cpp" > + + @@ -1373,6 +1377,10 @@ RelativePath=".\src\m_fixed.h" > + + @@ -1996,6 +2004,10 @@ RelativePath=".\src\win32\i_movie.cpp" > + +