Skip to content

Commit

Permalink
Get joypad's vendor ID, product ID and name on Windows for XInput dev…
Browse files Browse the repository at this point in the history
…ices.
  • Loading branch information
MJacred committed Nov 5, 2024
1 parent b00e1cb commit 195dddc
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 18 deletions.
10 changes: 7 additions & 3 deletions doc/classes/Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@
<return type="String" />
<param index="0" name="device" type="int" />
<description>
Returns an SDL2-compatible device GUID on platforms that use gamepad remapping, e.g. [code]030000004c050000c405000000010000[/code]. Returns [code]"Default Gamepad"[/code] otherwise. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names and mappings based on this GUID.
Returns an SDL2-compatible device GUID on platforms that use gamepad remapping, e.g. [code]030000004c050000c405000000010000[/code]. Returns an empty String otherwise. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names and mappings based on this GUID. Although Godot overwrites [url=https://github.com/godotengine/godot/blob/master/core/input/godotcontrollerdb.txt]some of the GUIDs[/url]. DirectX joypads on Windows, for example, all return the GUID [code]__XINPUT_DEVICE__[/code].
</description>
</method>
<method name="get_joy_info" qualifiers="const">
Expand All @@ -127,7 +127,10 @@
<description>
Returns a dictionary with extra platform-specific information about the device, e.g. the raw gamepad name from the OS or the Steam Input index.
On Windows the dictionary contains the following fields:
[code]xinput_index[/code]: The index of the controller in the XInput system.
[code]xinput_index[/code]: The index of the controller in the XInput system. Is undefined for DirectInput devices.
[code]xinput_name[/code]: The USB product name of the device. Is undefined for DirectInput devices.
[code]vendor_id[/code]: The USB vendor ID of the device.
[code]product_id[/code]: The USB product ID of the device.
On Linux:
[code]raw_name[/code]: The name of the controller as it came from the OS, before getting renamed by the godot controller database.
[code]vendor_id[/code]: The USB vendor ID of the device.
Expand All @@ -140,7 +143,8 @@
<return type="String" />
<param index="0" name="device" type="int" />
<description>
Returns the name of the joypad at the specified device index, e.g. [code]PS4 Controller[/code]. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names.
Returns the name of the joypad at the specified device index, e.g. [code]PS4 Controller[/code]. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names. Although Godot overwrites [url=https://github.com/godotengine/godot/blob/master/core/input/godotcontrollerdb.txt]some of the names[/url], especially on Windows for XInput devices.
On Windows, for XInput devices, use [method get_joy_info] to retrieve the joy name.
</description>
</method>
<method name="get_joy_vibration_duration">
Expand Down
76 changes: 64 additions & 12 deletions platform/windows/joypad_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,17 @@ DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
return ERROR_DEVICE_NOT_CONNECTED;
}

MMRESULT WINAPI _winmm_get_joycaps(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc) {
return MMSYSERR_NODRIVER;
}

JoypadWindows::JoypadWindows() {
}

JoypadWindows::JoypadWindows(HWND *hwnd) {
input = Input::get_singleton();
hWnd = hwnd;
joypad_count = 0;
d_joypad_count = 0;
dinput = nullptr;
xinput_dll = nullptr;
xinput_get_state = nullptr;
Expand All @@ -79,14 +83,15 @@ JoypadWindows::JoypadWindows(HWND *hwnd) {
}

JoypadWindows::~JoypadWindows() {
close_joypad();
close_d_joypad();
if (dinput) {
dinput->Release();
}
unload_winmm();
unload_xinput();
}

bool JoypadWindows::have_device(const GUID &p_guid) {
bool JoypadWindows::have_d_device(const GUID &p_guid) {
for (int i = 0; i < JOYPADS_MAX; i++) {
if (d_joypads[i].guid == p_guid) {
d_joypads[i].confirmed = true;
Expand Down Expand Up @@ -164,7 +169,7 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
HRESULT hr;
int num = input->get_unused_joy_id();

if (have_device(instance->guidInstance) || num == -1) {
if (have_d_device(instance->guidInstance) || num == -1) {
return false;
}

Expand Down Expand Up @@ -193,6 +198,10 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
WORD version = 0;
sprintf_s(uid, "%04x%04x%04x%04x%04x%04x%04x%04x", type, 0, vendor, 0, product, 0, version, 0);

Dictionary joypad_info;
joypad_info["vendor_id"] = itos(vendor);
joypad_info["product_id"] = itos(product);

id_to_change = num;
slider_count = 0;

Expand All @@ -202,12 +211,12 @@ bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
joy->joy_axis.sort();

joy->guid = instance->guidInstance;
input->joy_connection_changed(num, true, instance->tszProductName, uid);
input->joy_connection_changed(num, true, instance->tszProductName, uid, joypad_info);
joy->attached = true;
joy->id = num;
attached_joypads[num] = true;
joy->confirmed = true;
joypad_count++;
d_joypad_count++;
return true;
}

Expand Down Expand Up @@ -284,10 +293,10 @@ BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_ins
return DIENUM_CONTINUE;
}

void JoypadWindows::close_joypad(int id) {
void JoypadWindows::close_d_joypad(int id) {
if (id == -1) {
for (int i = 0; i < JOYPADS_MAX; i++) {
close_joypad(i);
close_d_joypad(i);
}
return;
}
Expand All @@ -302,12 +311,25 @@ void JoypadWindows::close_joypad(int id) {
attached_joypads[d_joypads[id].id] = false;
d_joypads[id].guid.Data1 = d_joypads[id].guid.Data2 = d_joypads[id].guid.Data3 = 0;
input->joy_connection_changed(d_joypads[id].id, false, "");
joypad_count--;
d_joypad_count--;
}

// FIXME: copied from godot_windows.cpp
char *wc_to_utf8(const wchar_t *wc) {
int ulen = WideCharToMultiByte(CP_UTF8, 0, wc, -1, nullptr, 0, nullptr, nullptr);
char *ubuf = new char[ulen + 1];
WideCharToMultiByte(CP_UTF8, 0, wc, -1, ubuf, ulen, nullptr, nullptr);
ubuf[ulen] = 0;
return ubuf;
}

void JoypadWindows::probe_joypads() {
ERR_FAIL_NULL_MSG(dinput, "DirectInput not initialized. Rebooting your PC may solve this issue.");

// Handle connect, disconnect & re-connect for DirectX joypads.
DWORD dwResult;
JOYCAPS jc;
MMRESULT jcResult;
for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE));

Expand All @@ -322,7 +344,17 @@ void JoypadWindows::probe_joypads() {
x_joypads[i].vibrating = false;
attached_joypads[id] = true;
Dictionary joypad_info;

joypad_info["xinput_index"] = (int)i;

memset(&jc, 0, sizeof(jc));
jcResult = winmm_get_joycaps((unsigned int)id, &jc, sizeof(JOYCAPS));
if (jcResult == JOYERR_NOERROR) {
joypad_info["vendor_id"] = itos(jc.wMid);
joypad_info["product_id"] = itos(jc.wPid);
joypad_info["xinput_name"] = wc_to_utf8(jc.szPname);

Check failure on line 355 in platform/windows/joypad_windows.cpp

View workflow job for this annotation

GitHub Actions / 🏁 Windows / Editor (target=editor, tests=yes)

'char *wc_to_utf8(const wchar_t *)': cannot convert argument 1 from 'CHAR [32]' to 'const wchar_t *'
}

input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__", joypad_info);
}
} else if (x_joypads[i].attached) {
Expand All @@ -332,22 +364,24 @@ void JoypadWindows::probe_joypads() {
}
}

for (int i = 0; i < joypad_count; i++) {
// Handle connect, disconnect & re-connect for DirectInput joypads.
for (int i = 0; i < d_joypad_count; i++) {
d_joypads[i].confirmed = false;
}

dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY);

for (int i = 0; i < joypad_count; i++) {
for (int i = 0; i < d_joypad_count; i++) {
if (!d_joypads[i].confirmed) {
close_joypad(i);
close_d_joypad(i);
}
}
}

void JoypadWindows::process_joypads() {
HRESULT hr;

// Handle DirectX joypads.
for (int i = 0; i < XUSER_MAX_COUNT; i++) {
xinput_gamepad &joy = x_joypads[i];
if (!joy.attached) {
Expand Down Expand Up @@ -388,6 +422,7 @@ void JoypadWindows::process_joypads() {
}
}

// Handle DirectIndput joypads.
for (int i = 0; i < JOYPADS_MAX; i++) {
dinput_gamepad *joy = &d_joypads[i];

Expand Down Expand Up @@ -535,7 +570,9 @@ void JoypadWindows::joypad_vibration_stop_xinput(int p_device, uint64_t p_timest
void JoypadWindows::load_xinput() {
xinput_get_state = &_xinput_get_state;
xinput_set_state = &_xinput_set_state;
winmm_get_joycaps = &_winmm_get_joycaps;
bool legacy_xinput = false;

xinput_dll = LoadLibrary("XInput1_4.dll");
if (!xinput_dll) {
xinput_dll = LoadLibrary("XInput1_3.dll");
Expand All @@ -560,10 +597,25 @@ void JoypadWindows::load_xinput() {
}
xinput_get_state = func;
xinput_set_state = set_func;

winmm_dll = LoadLibrary("Winmm.dll");
if (winmm_dll) {
winmm_get_joycaps = (joyGetDevCaps_t)GetProcAddress((HMODULE)winmm_dll, "joyGetDevCaps");
if (!winmm_get_joycaps) {
unload_winmm();
return;
}
}
}

void JoypadWindows::unload_xinput() {
if (xinput_dll) {
FreeLibrary((HMODULE)xinput_dll);
}
}

void JoypadWindows::unload_winmm() {
if (winmm_dll) {
FreeLibrary((HMODULE)winmm_dll);
}
}
11 changes: 8 additions & 3 deletions platform/windows/joypad_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,17 @@ class JoypadWindows {
typedef DWORD(WINAPI *XInputGetState_t)(DWORD dwUserIndex, XINPUT_STATE *pState);
typedef DWORD(WINAPI *XInputSetState_t)(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration);

typedef MMRESULT(WINAPI *joyGetDevCaps_t)(UINT uJoyID, LPJOYCAPS pjc, UINT cbjc);

HWND *hWnd = nullptr;
HANDLE xinput_dll;
HANDLE winmm_dll;
LPDIRECTINPUT8 dinput;
Input *input = nullptr;

int id_to_change;
int slider_count;
int joypad_count;
int d_joypad_count;
bool attached_joypads[JOYPADS_MAX];
dinput_gamepad d_joypads[JOYPADS_MAX];
xinput_gamepad x_joypads[XUSER_MAX_COUNT];
Expand All @@ -123,13 +126,14 @@ class JoypadWindows {
static BOOL CALLBACK objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context);

void setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id);
void close_joypad(int id = -1);
void close_d_joypad(int id = -1);
void load_xinput();
void unload_xinput();
void unload_winmm();

void post_hat(int p_device, DWORD p_dpad);

bool have_device(const GUID &p_guid);
bool have_d_device(const GUID &p_guid);
bool is_xinput_device(const GUID *p_guid);
bool setup_dinput_joypad(const DIDEVICEINSTANCE *instance);
void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
Expand All @@ -138,6 +142,7 @@ class JoypadWindows {
float axis_correct(int p_val, bool p_xinput = false, bool p_trigger = false, bool p_negate = false) const;
XInputGetState_t xinput_get_state;
XInputSetState_t xinput_set_state;
joyGetDevCaps_t winmm_get_joycaps; // Only for reading joypad info on XInput joypads.
};

#endif // JOYPAD_WINDOWS_H

0 comments on commit 195dddc

Please sign in to comment.