diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 6fe5b7a80229..cfd67d54913b 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -118,7 +118,7 @@
- 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].
@@ -126,8 +126,11 @@
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.
+ On Windows, the dictionary contains the following fields:
+ [code]xinput_index[/code]: The index of the controller in the XInput system. Undefined for DirectInput devices.
+ [code]xinput_name[/code]: The USB product name of the device. 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.
@@ -140,7 +143,8 @@
- 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.
diff --git a/platform/windows/joypad_windows.cpp b/platform/windows/joypad_windows.cpp
index 0f55cda5d744..a5b4d4ee4476 100644
--- a/platform/windows/joypad_windows.cpp
+++ b/platform/windows/joypad_windows.cpp
@@ -46,17 +46,23 @@ DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)
return ERROR_DEVICE_NOT_CONNECTED;
}
+MMRESULT WINAPI _winmm_get_joycaps(UINT uJoyID, LPJOYCAPSW pjc, UINT cbjc) {
+ return MMSYSERR_NODRIVER;
+}
+
JoypadWindows::JoypadWindows() {
}
JoypadWindows::JoypadWindows(HWND *hwnd) {
input = Input::get_singleton();
hWnd = hwnd;
- joypad_count = 0;
+ x_joypad_probe_count = 0;
+ d_joypad_count = 0;
dinput = nullptr;
xinput_dll = nullptr;
xinput_get_state = nullptr;
xinput_set_state = nullptr;
+ winmm_get_joycaps = nullptr;
load_xinput();
@@ -79,14 +85,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::is_d_joypad_known(const GUID &p_guid) {
for (int i = 0; i < JOYPADS_MAX; i++) {
if (d_joypads[i].guid == p_guid) {
d_joypads[i].confirmed = true;
@@ -97,7 +104,7 @@ bool JoypadWindows::have_device(const GUID &p_guid) {
}
// adapted from SDL2, works a lot better than the MSDN version
-bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
+bool JoypadWindows::is_xinput_joypad(const GUID *p_guid) {
static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x28DE, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
@@ -159,12 +166,55 @@ bool JoypadWindows::is_xinput_device(const GUID *p_guid) {
return false;
}
+void JoypadWindows::probe_xinput_joypad(const String &name) {
+ if (x_joypad_probe_count >= XUSER_MAX_COUNT) {
+ return;
+ }
+ int i = x_joypad_probe_count;
+ x_joypad_probe_count++;
+
+ ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE));
+
+ DWORD dwResult = xinput_get_state(i, &x_joypads[i].state);
+ if (dwResult == ERROR_SUCCESS) {
+ int id = input->get_unused_joy_id();
+ if (id != -1 && !x_joypads[i].attached) {
+ x_joypads[i].attached = true;
+ x_joypads[i].id = id;
+ x_joypads[i].ff_timestamp = 0;
+ x_joypads[i].ff_end_timestamp = 0;
+ x_joypads[i].vibrating = false;
+ attached_joypads[id] = true;
+ Dictionary joypad_info;
+
+ joypad_info["xinput_index"] = (int)i;
+
+ JOYCAPSW jc;
+ memset(&jc, 0, sizeof(JOYCAPSW));
+ MMRESULT jcResult = winmm_get_joycaps((UINT)id, &jc, sizeof(JOYCAPSW));
+ if (jcResult == JOYERR_NOERROR) {
+ joypad_info["vendor_id"] = itos(jc.wMid);
+ joypad_info["product_id"] = itos(jc.wPid);
+ if (!name.is_empty()) {
+ joypad_info["xinput_name"] = name.trim_prefix("Controller (").trim_suffix(")");
+ }
+ }
+
+ input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__", joypad_info);
+ }
+ } else if (x_joypads[i].attached) {
+ x_joypads[i].attached = false;
+ attached_joypads[x_joypads[i].id] = false;
+ input->joy_connection_changed(x_joypads[i].id, false, "");
+ }
+}
+
bool JoypadWindows::setup_dinput_joypad(const DIDEVICEINSTANCE *instance) {
ERR_FAIL_NULL_V_MSG(dinput, false, "DirectInput not initialized. Rebooting your PC may solve this issue.");
HRESULT hr;
int num = input->get_unused_joy_id();
- if (have_device(instance->guidInstance) || num == -1) {
+ if (is_d_joypad_known(instance->guidInstance) || num == -1) {
return false;
}
@@ -193,6 +243,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;
@@ -202,16 +256,17 @@ 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);
+ const String &name = String(instance->tszProductName).trim_prefix("Controller (").trim_suffix(")");
+ input->joy_connection_changed(num, true, name, uid, joypad_info);
joy->attached = true;
joy->id = num;
attached_joypads[num] = true;
joy->confirmed = true;
- joypad_count++;
+ d_joypad_count++;
return true;
}
-void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) {
+void JoypadWindows::setup_d_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) {
if (ob->dwType & DIDFT_AXIS) {
HRESULT res;
DIPROPRANGE prop_range;
@@ -270,7 +325,8 @@ void JoypadWindows::setup_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_
BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context) {
JoypadWindows *self = static_cast(p_context);
- if (self->is_xinput_device(&p_instance->guidProduct)) {
+ if (self->is_xinput_joypad(&p_instance->guidProduct)) {
+ self->probe_xinput_joypad(p_instance->tszProductName);
return DIENUM_CONTINUE;
}
self->setup_dinput_joypad(p_instance);
@@ -279,15 +335,15 @@ BOOL CALLBACK JoypadWindows::enumCallback(const DIDEVICEINSTANCE *p_instance, vo
BOOL CALLBACK JoypadWindows::objectsCallback(const DIDEVICEOBJECTINSTANCE *p_instance, void *p_context) {
JoypadWindows *self = static_cast(p_context);
- self->setup_joypad_object(p_instance, self->id_to_change);
+ self->setup_d_joypad_object(p_instance, self->id_to_change);
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;
}
@@ -302,45 +358,29 @@ 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.");
- DWORD dwResult;
- for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
- ZeroMemory(&x_joypads[i].state, sizeof(XINPUT_STATE));
-
- dwResult = xinput_get_state(i, &x_joypads[i].state);
- if (dwResult == ERROR_SUCCESS) {
- int id = input->get_unused_joy_id();
- if (id != -1 && !x_joypads[i].attached) {
- x_joypads[i].attached = true;
- x_joypads[i].id = id;
- x_joypads[i].ff_timestamp = 0;
- x_joypads[i].ff_end_timestamp = 0;
- x_joypads[i].vibrating = false;
- attached_joypads[id] = true;
- Dictionary joypad_info;
- joypad_info["xinput_index"] = (int)i;
- input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__", joypad_info);
- }
- } else if (x_joypads[i].attached) {
- x_joypads[i].attached = false;
- attached_joypads[x_joypads[i].id] = false;
- input->joy_connection_changed(x_joypads[i].id, false, "");
- }
- }
- for (int i = 0; i < joypad_count; i++) {
- d_joypads[i].confirmed = false;
+ for (int i = 0; i < d_joypad_count; i++) {
+ d_joypads[i].confirmed = false; // Flag DirectInput devices for re-checking their availability.
}
+ x_joypad_probe_count = 0;
+ // Probe _all attached_ joypad devices.
dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY);
- for (int i = 0; i < joypad_count; i++) {
+ for (int i = x_joypad_probe_count; i < XUSER_MAX_COUNT; i++) {
+ // Handle disconnect of XInput devices.
+ // And act as a fallback, just in case DirectInput could not find the device.
+ probe_xinput_joypad();
+ }
+
+ for (int i = 0; i < d_joypad_count; i++) {
if (!d_joypads[i].confirmed) {
- close_joypad(i);
+ close_d_joypad(i); // Any DirectInput device not found during probing is considered as disconnected.
}
}
}
@@ -348,6 +388,7 @@ void JoypadWindows::probe_joypads() {
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) {
@@ -388,6 +429,7 @@ void JoypadWindows::process_joypads() {
}
}
+ // Handle DirectIndput joypads.
for (int i = 0; i < JOYPADS_MAX; i++) {
dinput_gamepad *joy = &d_joypads[i];
@@ -535,7 +577,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");
@@ -560,6 +604,16 @@ void JoypadWindows::load_xinput() {
}
xinput_get_state = func;
xinput_set_state = set_func;
+
+ winmm_dll = LoadLibrary("Winmm.dll");
+ if (winmm_dll) {
+ joyGetDevCaps_t caps_func = (joyGetDevCaps_t)GetProcAddress((HMODULE)winmm_dll, "joyGetDevCapsW");
+ if (caps_func) {
+ winmm_get_joycaps = caps_func;
+ } else {
+ unload_winmm();
+ }
+ }
}
void JoypadWindows::unload_xinput() {
@@ -567,3 +621,9 @@ void JoypadWindows::unload_xinput() {
FreeLibrary((HMODULE)xinput_dll);
}
}
+
+void JoypadWindows::unload_winmm() {
+ if (winmm_dll) {
+ FreeLibrary((HMODULE)winmm_dll);
+ }
+}
diff --git a/platform/windows/joypad_windows.h b/platform/windows/joypad_windows.h
index 87c7af76577d..0027645d2827 100644
--- a/platform/windows/joypad_windows.h
+++ b/platform/windows/joypad_windows.h
@@ -37,6 +37,8 @@
#include
#include
+#include
+
#ifndef SAFE_RELEASE // when Windows Media Device M? is not present
#define SAFE_RELEASE(x) \
if (x != nullptr) { \
@@ -107,14 +109,18 @@ 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, LPJOYCAPSW 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 x_joypad_probe_count; // XInput equivalent to dinput_gamepad.confirmed.
+ int d_joypad_count;
bool attached_joypads[JOYPADS_MAX];
dinput_gamepad d_joypads[JOYPADS_MAX];
xinput_gamepad x_joypads[XUSER_MAX_COUNT];
@@ -122,22 +128,25 @@ class JoypadWindows {
static BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE *p_instance, void *p_context);
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 setup_d_joypad_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id);
+ 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 is_xinput_device(const GUID *p_guid);
+ bool is_d_joypad_known(const GUID &p_guid);
+ bool is_xinput_joypad(const GUID *p_guid);
bool setup_dinput_joypad(const DIDEVICEINSTANCE *instance);
+ void probe_xinput_joypad(const String &name = ""); // Handles connect, disconnect & re-connect for DirectX joypads.
void joypad_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp);
void joypad_vibration_stop_xinput(int p_device, uint64_t p_timestamp);
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 info on XInput joypads.
};
#endif // JOYPAD_WINDOWS_H