| /* |
| * 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 <stdio.h> |
| #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(); |
| } |