diff --git a/dll/win32/hid/CMakeLists.txt b/dll/win32/hid/CMakeLists.txt index e045cfe4460..9e966ec9a96 100644 --- a/dll/win32/hid/CMakeLists.txt +++ b/dll/win32/hid/CMakeLists.txt @@ -1,11 +1,11 @@ +spec2def(hid.dll hid.spec ADD_IMPORTLIB) + list(APPEND SOURCE hid.c stubs.c precomp.h) -spec2def(hid.dll hid.spec) - add_library(hid SHARED ${SOURCE} hid.rc diff --git a/modules/rostests/winetests/CMakeLists.txt b/modules/rostests/winetests/CMakeLists.txt index fff038fc272..959db2b9271 100644 --- a/modules/rostests/winetests/CMakeLists.txt +++ b/modules/rostests/winetests/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(faultrep) add_subdirectory(fusion) add_subdirectory(gdi32) add_subdirectory(gdiplus) +add_subdirectory(hid) add_subdirectory(hlink) add_subdirectory(hnetcfg) add_subdirectory(imagehlp) diff --git a/modules/rostests/winetests/hid/CMakeLists.txt b/modules/rostests/winetests/hid/CMakeLists.txt new file mode 100644 index 00000000000..6a20859f09b --- /dev/null +++ b/modules/rostests/winetests/hid/CMakeLists.txt @@ -0,0 +1,5 @@ + +add_executable(hid_winetest device.c testlist.c) +set_module_type(hid_winetest win32cui) +add_importlibs(hid_winetest hid setupapi msvcrt kernel32) +add_rostests_file(TARGET hid_winetest) diff --git a/modules/rostests/winetests/hid/device.c b/modules/rostests/winetests/hid/device.c new file mode 100644 index 00000000000..ce9b3a5d129 --- /dev/null +++ b/modules/rostests/winetests/hid/device.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2017 Aric Stewart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windows.h" +#include "setupapi.h" +#include "hidusage.h" +#include "ddk/hidsdi.h" + +#include "wine/test.h" + +#define READ_MAX_TIME 5000 + +typedef void (device_test)(HANDLE device); + +static void test_device_info(HANDLE device) +{ + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS Caps; + NTSTATUS status; + BOOL rc; + WCHAR device_name[128]; + + rc = HidD_GetPreparsedData(device, &ppd); + ok(rc, "Failed to get preparsed data(0x%x)\n", GetLastError()); + status = HidP_GetCaps(ppd, &Caps); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get Caps(0x%x)\n", status); + rc = HidD_GetProductString(device, device_name, sizeof(device_name)); + ok(rc, "Failed to get product string(0x%x)\n", GetLastError()); + trace("Found device %s (%02x, %02x)\n", wine_dbgstr_w(device_name), Caps.UsagePage, Caps.Usage); + rc = HidD_FreePreparsedData(ppd); + ok(rc, "Failed to free preparsed data(0x%x)\n", GetLastError()); +} + +static void run_for_each_device(device_test *test) +{ + GUID hid_guid; + HDEVINFO info_set; + DWORD index = 0; + SP_DEVICE_INTERFACE_DATA interface_data; + DWORD detail_size = MAX_PATH * sizeof(WCHAR); + SP_DEVICE_INTERFACE_DETAIL_DATA_W *data; + + HidD_GetHidGuid(&hid_guid); + + ZeroMemory(&interface_data, sizeof(interface_data)); + interface_data.cbSize = sizeof(interface_data); + + data = HeapAlloc(GetProcessHeap(), 0, sizeof(*data) + detail_size); + data->cbSize = sizeof(*data); + + info_set = SetupDiGetClassDevsW(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE); + while (SetupDiEnumDeviceInterfaces(info_set, NULL, &hid_guid, index, &interface_data)) + { + index ++; + + if (SetupDiGetDeviceInterfaceDetailW(info_set, &interface_data, data, sizeof(*data) + detail_size, NULL, NULL)) + { + HANDLE file = CreateFileW(data->DevicePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); + if (file == INVALID_HANDLE_VALUE) + { + trace("Failed to access device %s, likely not plugged in or access is denied.\n", wine_dbgstr_w(data->DevicePath)); + continue; + } + + test(file); + + CloseHandle(file); + } + } + HeapFree(GetProcessHeap(), 0, data); + SetupDiDestroyDeviceInfoList(info_set); +} + +static HANDLE get_device(USHORT page, USHORT usages[], UINT usage_count, DWORD access) +{ + GUID hid_guid; + HDEVINFO info_set; + DWORD index = 0; + SP_DEVICE_INTERFACE_DATA interface_data; + DWORD detail_size = MAX_PATH * sizeof(WCHAR); + SP_DEVICE_INTERFACE_DETAIL_DATA_W *data; + NTSTATUS status; + BOOL rc; + + HidD_GetHidGuid(&hid_guid); + + ZeroMemory(&interface_data, sizeof(interface_data)); + interface_data.cbSize = sizeof(interface_data); + + data = HeapAlloc(GetProcessHeap(), 0 , sizeof(*data) + detail_size); + data->cbSize = sizeof(*data); + + info_set = SetupDiGetClassDevsW(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE); + while (SetupDiEnumDeviceInterfaces(info_set, NULL, &hid_guid, index, &interface_data)) + { + index ++; + + if (SetupDiGetDeviceInterfaceDetailW(info_set, &interface_data, data, sizeof(*data) + detail_size, NULL, NULL)) + { + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS Caps; + HANDLE file = CreateFileW(data->DevicePath, access, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); + if (file == INVALID_HANDLE_VALUE) + { + trace("Failed to access device %s, likely not plugged in or access is denied.\n", wine_dbgstr_w(data->DevicePath)); + continue; + } + rc = HidD_GetPreparsedData(file, &ppd); + ok(rc, "Failed to get preparsed data(0x%x)\n", GetLastError()); + status = HidP_GetCaps(ppd, &Caps); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get Caps(0x%x)\n", status); + rc = HidD_FreePreparsedData(ppd); + ok(rc, "Failed to free preparsed data(0x%x)\n", GetLastError()); + if (!page || page == Caps.UsagePage) + { + int j; + if (!usage_count) + { + HeapFree(GetProcessHeap(), 0, data); + SetupDiDestroyDeviceInfoList(info_set); + return file; + } + for (j = 0; j < usage_count; j++) + if (!usages[j] || usages[j] == Caps.Usage) + { + HeapFree(GetProcessHeap(), 0, data); + SetupDiDestroyDeviceInfoList(info_set); + return file; + } + } + CloseHandle(file); + } + } + HeapFree(GetProcessHeap(), 0, data); + SetupDiDestroyDeviceInfoList(info_set); + return NULL; +} + +static void process_data(HIDP_CAPS Caps, PHIDP_PREPARSED_DATA ppd, CHAR *data, DWORD data_length) +{ + INT i; + NTSTATUS status; + + if (Caps.NumberInputButtonCaps) + { + USAGE button_pages[100]; + + for (i = 1; i < 0xff; i++) + { + ULONG usage_length = 100; + status = HidP_GetUsages(HidP_Input, i, 0, button_pages, &usage_length, ppd, data, data_length); + ok (status == HIDP_STATUS_SUCCESS || usage_length == 0, + "HidP_GetUsages failed (%x) but usage length still %i\n", status, usage_length); + if (usage_length) + { + CHAR report[50]; + int count; + int j; + + count = usage_length; + j = 0; + report[0] = 0; + trace("\tButtons [0x%x: %i buttons]:\n", i, usage_length); + for (count = 0; count < usage_length; count += 15) + { + for (j=count; j < count+15 && j < usage_length; j++) + { + CHAR btn[7]; + sprintf(btn, "%i ", button_pages[j]); + strcat(report, btn); + } + trace("\t\t%s\n", report); + } + } + } + } + + if (Caps.NumberInputValueCaps) + { + ULONG value; + USHORT length; + HIDP_VALUE_CAPS *values = NULL; + + values = HeapAlloc(GetProcessHeap(), 0, sizeof(HIDP_VALUE_CAPS) * Caps.NumberInputValueCaps); + length = Caps.NumberInputValueCaps; + status = HidP_GetValueCaps(HidP_Input, values, &length, ppd); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get value caps (%x)\n",status); + + trace("\tValues:\n"); + for (i = 0; i < length; i++) + { + status = HidP_GetUsageValue(HidP_Input, values[i].UsagePage, 0, + values[i].Range.UsageMin, &value, ppd, data, data_length); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get value [%i,%i] (%x)\n", + values[i].UsagePage, values[i].Range.UsageMin, status); + trace("[%02x, %02x]: %u\n",values[i].UsagePage, values[i].Range.UsageMin, value); + } + + HeapFree(GetProcessHeap(), 0, values); + } +} + +static void test_read_device(void) +{ + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS Caps; + OVERLAPPED overlapped; + WCHAR device_name[128]; + CHAR *data = NULL; + DWORD read; + BOOL rc; + NTSTATUS status; + DWORD timeout, tick, spent, max_time; + char *report; + + USAGE device_usages[] = {HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_GAMEPAD}; + HANDLE device = get_device(HID_USAGE_PAGE_GENERIC, device_usages, 2, GENERIC_READ); + + if (!device) + device = get_device(0x0, NULL, 0x0, GENERIC_READ); + + if (!device) + { + trace("No device found for reading\n"); + return; + } + rc = HidD_GetProductString(device, device_name, sizeof(device_name)); + ok(rc, "Failed to get product string(0x%x)\n", GetLastError()); + trace("Read tests on device :%s\n",wine_dbgstr_w(device_name)); + + rc = HidD_GetPreparsedData(device, &ppd); + ok(rc, "Failed to get preparsed data(0x%x)\n", GetLastError()); + status = HidP_GetCaps(ppd, &Caps); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get Caps(0x%x)\n", status); + data = HeapAlloc(GetProcessHeap(), 0, Caps.InputReportByteLength); + + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL); + if (winetest_interactive) + { + max_time = READ_MAX_TIME; + timeout = 1000; + } + else + max_time = timeout = 100; + if (winetest_interactive) + trace("Test your device for the next %i seconds\n", max_time/1000); + report = HeapAlloc(GetProcessHeap(), 0, 3 * Caps.InputReportByteLength); + tick = GetTickCount(); + spent = 0; + do + { + ReadFile(device, data, Caps.InputReportByteLength, NULL, &overlapped); + if (WaitForSingleObject(overlapped.hEvent, timeout) != WAIT_OBJECT_0) + { + ResetEvent(overlapped.hEvent); + spent = GetTickCount() - tick; + trace("REMAINING: %d ms\n", max_time - spent); + continue; + } + ResetEvent(overlapped.hEvent); + spent = GetTickCount() - tick; + GetOverlappedResult(device, &overlapped, &read, FALSE); + if (read) + { + int i; + + report[0] = 0; + for (i = 0; i < read && i < Caps.InputReportByteLength; i++) + { + char bytestr[5]; + sprintf(bytestr, "%x ", (BYTE)data[i]); + strcat(report, bytestr); + } + trace("Input report (%i): %s\n", read, report); + + process_data(Caps, ppd, data, read); + } + trace("REMAINING: %d ms\n", max_time - spent); + } while(spent < max_time); + + CloseHandle(overlapped.hEvent); + rc = HidD_FreePreparsedData(ppd); + ok(rc, "Failed to free preparsed data(0x%x)\n", GetLastError()); + CloseHandle(device); + HeapFree(GetProcessHeap(), 0, data); + HeapFree(GetProcessHeap(), 0, report); +} + +static void test_get_input_report(void) +{ + PHIDP_PREPARSED_DATA ppd; + HIDP_CAPS Caps; + WCHAR device_name[128]; + CHAR *data = NULL; + DWORD tick, spent, max_time; + char *report; + BOOL rc; + NTSTATUS status; + + USAGE device_usages[] = {HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_GENERIC_GAMEPAD}; + HANDLE device = get_device(HID_USAGE_PAGE_GENERIC, device_usages, 2, GENERIC_READ); + + if (!device) + device = get_device(0x0, NULL, 0x0, GENERIC_READ); + + if (!device) + { + trace("No device found for testing\n"); + return; + } + rc = HidD_GetProductString(device, device_name, sizeof(device_name)); + ok(rc, "Failed to get product string(0x%x)\n", GetLastError()); + trace("HidD_GetInputRpeort tests on device :%s\n",wine_dbgstr_w(device_name)); + + rc = HidD_GetPreparsedData(device, &ppd); + ok(rc, "Failed to get preparsed data(0x%x)\n", GetLastError()); + status = HidP_GetCaps(ppd, &Caps); + ok(status == HIDP_STATUS_SUCCESS, "Failed to get Caps(0x%x)\n", status); + data = HeapAlloc(GetProcessHeap(), 0, Caps.InputReportByteLength); + + if (winetest_interactive) + max_time = READ_MAX_TIME; + else + max_time = 100; + if (winetest_interactive) + trace("Test your device for the next %i seconds\n", max_time/1000); + report = HeapAlloc(GetProcessHeap(), 0, 3 * Caps.InputReportByteLength); + tick = GetTickCount(); + spent = 0; + do + { + int i; + + data[0] = 0; /* Just testing report ID 0 for now, That will catch most devices */ + rc = HidD_GetInputReport(device, data, Caps.InputReportByteLength); + spent = GetTickCount() - tick; + + if (rc) + { + ok(data[0] == 0, "Report ID (0) is not the first byte of the data\n"); + report[0] = 0; + for (i = 0; i < Caps.InputReportByteLength; i++) + { + char bytestr[5]; + sprintf(bytestr, "%x ", (BYTE)data[i]); + strcat(report, bytestr); + } + trace("Input report (%i): %s\n", Caps.InputReportByteLength, report); + + process_data(Caps, ppd, data, Caps.InputReportByteLength); + } + else + trace("Failed to get Input Report, (%x)\n", rc); + trace("REMAINING: %d ms\n", max_time - spent); + Sleep(500); + } while(spent < max_time); + + rc = HidD_FreePreparsedData(ppd); + ok(rc, "Failed to free preparsed data(0x%x)\n", GetLastError()); + CloseHandle(device); + HeapFree(GetProcessHeap(), 0, data); + HeapFree(GetProcessHeap(), 0, report); +} + +START_TEST(device) +{ + run_for_each_device(test_device_info); + test_read_device(); + test_get_input_report(); +} diff --git a/modules/rostests/winetests/hid/testlist.c b/modules/rostests/winetests/hid/testlist.c new file mode 100644 index 00000000000..12e1f21f980 --- /dev/null +++ b/modules/rostests/winetests/hid/testlist.c @@ -0,0 +1,12 @@ +/* Automatically generated file; DO NOT EDIT!! */ + +#define STANDALONE +#include + +extern void func_device(void); + +const struct test winetest_testlist[] = +{ + { "device", func_device }, + { 0, 0 } +};