mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-12 15:44:10 +00:00
a90bde274e
- Joystick devices now send key up events for any buttons that are held down when they are destroyed. - Changed the joystick enable-y menu items to be nonrepeatable so that you can't create several device scans per second. Changing them with a controller is also disabled so that you can't, for example, toggle XInput support using an XInput controller and have things go haywire when the game receives an infinite number of key down events when the controller is reinitialized with the other input system. - Changed menu input to use a consolidated set of buttons, so most menus can be navigated with a controller as well as a keyboard. - Changed the way that X/Y analog axes are converted to digital axes. Previously, this was done by checking if each axis was outside its deadzone. Now they are checked together based on their angle, so straight up/down/ left/right are much easier to achieve. SVN r1739 (trunk)
1315 lines
34 KiB
C++
1315 lines
34 KiB
C++
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#define _WIN32_WINNT 0x0501
|
|
#include <windows.h>
|
|
#include <malloc.h>
|
|
#include <limits.h>
|
|
|
|
#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 value1, AxisInfo *axis1, int value2, AxisInfo *axis2, int base);
|
|
|
|
friend class FRawPS2Manager;
|
|
};
|
|
|
|
class FRawPS2Manager : public FJoystickCollection
|
|
{
|
|
public:
|
|
FRawPS2Manager();
|
|
~FRawPS2Manager();
|
|
|
|
bool GetDevice();
|
|
bool ProcessRawInput(RAWINPUT *raw, int code);
|
|
void AddAxes(float axes[NUM_JOYAXIS]);
|
|
void GetDevices(TArray<IJoystickConfig *> &sticks);
|
|
IJoystickConfig *Rescan();
|
|
|
|
protected:
|
|
TArray<FRawPS2Controller *> 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 -------------------------------------------------
|
|
|
|
CUSTOM_CVAR(Bool, joy_ps2raw, true, CVAR_GLOBALCONFIG|CVAR_ARCHIVE|CVAR_NOINITCALL)
|
|
{
|
|
I_StartupRawPS2();
|
|
event_t ev = { EV_DeviceChange };
|
|
D_PostEvent(&ev);
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
// Make sure to send any key ups before destroying this.
|
|
NeutralInput();
|
|
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],
|
|
rawdata[desc->LeftY], &Axes[AXIS_ThumbLY], KEY_PAD_LTHUMB_RIGHT);
|
|
|
|
// If we know we are digital, ignore the right stick.
|
|
if (digital)
|
|
{
|
|
ProcessThumbstick(0x80, &Axes[AXIS_ThumbRX],
|
|
0x80, &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
|
|
}
|
|
else
|
|
{
|
|
ProcessThumbstick(rawdata[desc->RightX], &Axes[AXIS_ThumbRX],
|
|
rawdata[desc->RightY], &Axes[AXIS_ThumbRY], KEY_PAD_RTHUMB_RIGHT);
|
|
}
|
|
|
|
// 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 both axie of a thumb stick to floating point, cancels out the
|
|
// deadzone, and generates button up/down events for them.
|
|
//
|
|
//==========================================================================
|
|
|
|
void FRawPS2Controller::ProcessThumbstick(int value1, AxisInfo *axis1, int value2, AxisInfo *axis2, int base)
|
|
{
|
|
BYTE buttonstate;
|
|
double axisval1, axisval2;
|
|
|
|
axisval1 = value1 * (2.0 / 255) - 1.0;
|
|
axisval2 = value2 * (2.0 / 255) - 1.0;
|
|
axisval1 = Joy_RemoveDeadZone(axisval1, axis1->DeadZone, NULL);
|
|
axisval2 = Joy_RemoveDeadZone(axisval2, axis2->DeadZone, NULL);
|
|
axis1->Value = float(axisval1);
|
|
axis2->Value = float(axisval2);
|
|
|
|
// We store all four buttons in the first axis and ignore the second.
|
|
buttonstate = Joy_XYAxesToButtons(axisval1, axisval2);
|
|
Joy_GenerateButtonEvents(axis1->ButtonValue, buttonstate, 4, base);
|
|
axis1->ButtonValue = buttonstate;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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()
|
|
{
|
|
for (int i = 0; i < NUM_AXES; i += 2)
|
|
{
|
|
ProcessThumbstick(0x80, &Axes[i], 0x80, &Axes[i+1], 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])
|
|
{
|
|
// Add to game axes.
|
|
for (int i = 0; i < NUM_AXES; ++i)
|
|
{
|
|
axes[Axes[i].GameAxis] -= float(Axes[i].Value * Multiplier * 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<IJoystickConfig *> &sticks)
|
|
{
|
|
for (unsigned i = 0; i < Devices.Size(); ++i)
|
|
{
|
|
if (Devices[i]->IsConnected())
|
|
{
|
|
sticks.Push(Devices[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// 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 :: Rescan
|
|
//
|
|
//===========================================================================
|
|
|
|
IJoystickConfig *FRawPS2Manager::Rescan()
|
|
{
|
|
return EnumDevices();
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// 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<FAdapterHandle> 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:
|
|
// <Enumerator>#<Device ID>#<Device Interface Class GUID>
|
|
// 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()
|
|
{
|
|
if (!joy_ps2raw || !use_joystick || Args->CheckParm("-nojoy"))
|
|
{
|
|
if (JoyDevices[INPUT_RawPS2] != NULL)
|
|
{
|
|
delete JoyDevices[INPUT_RawPS2];
|
|
JoyDevices[INPUT_RawPS2] = NULL;
|
|
UpdateJoystickMenu(NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (JoyDevices[INPUT_RawPS2] == NULL)
|
|
{
|
|
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));
|
|
}
|