Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get joypad's vendor ID, product ID and name on Windows for XInput devices. #98861

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
67 changes: 55 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,16 @@ 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--;
}

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 +335,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"] = String(jc.szPname);
}

input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__", joypad_info);
}
} else if (x_joypads[i].attached) {
Expand All @@ -332,22 +355,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 +413,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 +561,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 +588,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
Loading