SDL: hidapi/windows: fixed PS4 controllers over Bluetooth on Windows 7

From 9c2ec04733c9c9c5ae413c1e2ab390358a102f43 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 25 May 2023 15:51:48 -0700
Subject: [PATCH] hidapi/windows: fixed PS4 controllers over Bluetooth on
 Windows 7

Signed-off-by: Sam Lantinga <slouken@libsdl.org>
---
 src/hidapi/windows/hid.c           | 41 ++++++++++++++++++++++++++++++
 src/hidapi/windows/hidapi_hidsdi.h |  1 +
 2 files changed, 42 insertions(+)

diff --git a/src/hidapi/windows/hid.c b/src/hidapi/windows/hid.c
index 093e23d35a1c..ed25280b58ab 100644
--- a/src/hidapi/windows/hid.c
+++ b/src/hidapi/windows/hid.c
@@ -97,6 +97,7 @@ static HidD_GetPreparsedData_ HidD_GetPreparsedData;
 static HidD_FreePreparsedData_ HidD_FreePreparsedData;
 static HidP_GetCaps_ HidP_GetCaps;
 static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
+static HidD_SetOutputReport_ HidD_SetOutputReport;
 
 static CM_Locate_DevNodeW_ CM_Locate_DevNodeW = NULL;
 static CM_Get_Parent_ CM_Get_Parent = NULL;
@@ -151,6 +152,7 @@ static int lookup_functions()
 	RESOLVE(hid_lib_handle, HidD_FreePreparsedData);
 	RESOLVE(hid_lib_handle, HidP_GetCaps);
 	RESOLVE(hid_lib_handle, HidD_SetNumInputBuffers);
+	RESOLVE(hid_lib_handle, HidD_SetOutputReport);
 
 	RESOLVE(cfgmgr32_lib_handle, CM_Locate_DevNodeW);
 	RESOLVE(cfgmgr32_lib_handle, CM_Get_Parent);
@@ -188,8 +190,28 @@ struct hid_device_ {
 		OVERLAPPED ol;
 		OVERLAPPED write_ol;
 		struct hid_device_info* device_info;
+		BOOL use_hid_write_output_report;
 };
 
+static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor)
+{
+	OSVERSIONINFOEXW osvi;
+	DWORDLONG const dwlConditionMask = VerSetConditionMask(
+		VerSetConditionMask(
+			VerSetConditionMask(
+				0, VER_MAJORVERSION, VER_GREATER_EQUAL ),
+			VER_MINORVERSION, VER_GREATER_EQUAL ),
+		VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL );
+
+	memset(&osvi, 0, sizeof(osvi));
+	osvi.dwOSVersionInfoSize = sizeof( osvi );
+	osvi.dwMajorVersion = wMajorVersion;
+	osvi.dwMinorVersion = wMinorVersion;
+	osvi.wServicePackMajor = wServicePackMajor;
+
+	return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
+}
+
 static hid_device *new_hid_device()
 {
 	hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
@@ -1020,6 +1042,11 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
 	dev->read_buf = (char*) malloc(dev->input_report_length);
 	dev->device_info = hid_internal_get_device_info(interface_path, dev->device_handle);
 
+	/* On Windows 7, we need to use hid_write_output_report() over Bluetooth */
+	if (dev->output_report_length > 512) {
+		dev->use_hid_write_output_report = !IsWindowsVersionOrGreater( HIBYTE( _WIN32_WINNT_WIN8 ), LOBYTE( _WIN32_WINNT_WIN8 ), 0 );
+	}
+
 end_of_function:
 	free(interface_path);
 	CloseHandle(device_handle);
@@ -1031,6 +1058,16 @@ HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
 	return dev;
 }
 
+static int hid_write_output_report(hid_device *dev, const unsigned char *data, size_t length)
+{
+	BOOL res;
+	res = HidD_SetOutputReport(dev->device_handle, (void *)data, (ULONG)length);
+	if (res)
+		return (int)length;
+	else
+		return -1;
+}
+
 int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
 {
 	DWORD bytes_written = 0;
@@ -1047,6 +1084,10 @@ int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *
 
 	register_string_error(dev, NULL);
 
+	if (dev->use_hid_write_output_report) {
+		return hid_write_output_report(dev, data, length);
+	}
+
 	/* Make sure the right number of bytes are passed to WriteFile. Windows
 	   expects the number of bytes which are in the _longest_ report (plus
 	   one for the report number) bytes even if the data is a report
diff --git a/src/hidapi/windows/hidapi_hidsdi.h b/src/hidapi/windows/hidapi_hidsdi.h
index 4da6b91d8f39..95e2fcb3b80c 100644
--- a/src/hidapi/windows/hidapi_hidsdi.h
+++ b/src/hidapi/windows/hidapi_hidsdi.h
@@ -52,6 +52,7 @@ typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_
 typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
 typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
 typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
+typedef BOOLEAN (__stdcall *HidD_SetOutputReport_)(HANDLE HidDeviceObject, PVOID ReportBuffer, ULONG ReportBufferLength);
 
 #endif