BOOLEAN CheckIfOurDevice( HANDLE file, USAGE myUsagePage, USAGE myUsage) { PHIDP_PREPARSED_DATA Ppd = NULL; // The opaque parser info describing this device HIDD_ATTRIBUTES Attributes; // The Attributes of this hid device. HIDP_CAPS Caps; // The Capabilities of this hid device. BOOLEAN result = FALSE; if (!HidD_GetPreparsedData (file, &Ppd)) { printf("Error: HidD_GetPreparsedData failed \n"); goto cleanup; } if (!HidD_GetAttributes(file, &Attributes)) { printf("Error: HidD_GetAttributes failed \n"); goto cleanup; } if (Attributes.VendorID == VMULTI_VID && Attributes.ProductID == VMULTI_PID) { if (!HidP_GetCaps (Ppd, &Caps)) { printf("Error: HidP_GetCaps failed \n"); goto cleanup; } if ((Caps.UsagePage == myUsagePage) && (Caps.Usage == myUsage)) { printf("Success: Found my device.. \n"); result = TRUE; } } cleanup: if (Ppd != NULL) { HidD_FreePreparsedData (Ppd); } return result; }
void CUSBDevice::setDetail(TCHAR *path){ HANDLE temp; PHIDP_PREPARSED_DATA PreparsedData; delete[] m_pPath; m_pPath = new TCHAR[strlen(path) * sizeof(TCHAR) + 1]; strcpy(m_pPath, path); TRACE("received at addr %x %s\r\n", m_pPath, m_pPath); temp = CreateFile(m_pPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); HidD_GetPreparsedData(temp, &PreparsedData); HidP_GetCaps(PreparsedData, &m_Capabilities); }
bool HIDDeviceManager::initUsage(HANDLE hidDev, HIDDeviceDesc* desc) const { bool result = false; HIDP_CAPS caps; HIDP_PREPARSED_DATA* preparsedData = 0; if (!HidD_GetPreparsedData(hidDev, &preparsedData)) return false; if (HidP_GetCaps(preparsedData, &caps) == HIDP_STATUS_SUCCESS) { desc->Usage = caps.Usage; desc->UsagePage = caps.UsagePage; result = true; } HidD_FreePreparsedData(preparsedData); return result; }
bool Device::open(boost::asio::io_service &io_service, std::shared_ptr<ICallback> callback) { auto handle = CreateFile(m_path.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if(handle == INVALID_HANDLE_VALUE) { return false; } m_handle=std::shared_ptr<void>(handle, &::CloseHandle); // get hid caps { HIDP_PREPARSED_DATA* preparsedData=0; if (!HidD_GetPreparsedData(handle, &preparsedData)){ return false; } std::shared_ptr<HIDP_PREPARSED_DATA> parsed( preparsedData, HidD_FreePreparsedData); HIDP_CAPS caps; if (HidP_GetCaps(preparsedData, &caps) != HIDP_STATUS_SUCCESS) { return false; } std::cout << "Usage: " << caps.Usage << std::endl; std::cout << "UsagePage: " << caps.UsagePage << std::endl; std::cout << "InputReportByteLength: " << caps.InputReportByteLength << std::endl; std::cout << "OutputReportByteLength: " << caps.OutputReportByteLength << std::endl; std::cout << "FeatureReportByteLength: " << caps.FeatureReportByteLength << std::endl; } // setup stream m_stream=std::make_shared<boost::asio::windows::stream_handle>(io_service, handle); m_callback=callback; beginRead(); m_callback->onConnect(this); return true; }
BOOLEAN SwitchCSR(__in LPTSTR lpDongleName, __in HANDLE hHidDevice, __in BOOL toHID) { PHIDP_PREPARSED_DATA PreparsedData; if (!HidD_GetPreparsedData(hHidDevice, &PreparsedData)) { LbtReportFunctionError(TEXT("HidD_GetPreparsedData")); return FALSE; } HIDP_CAPS HidCaps; if (!HidP_GetCaps(PreparsedData, &HidCaps)) { LbtReportFunctionError(TEXT("HidP_GetCaps")); HidD_FreePreparsedData(PreparsedData); return FALSE; } if (HidCaps.UsagePage != 0xFF00 || HidCaps.Usage != 0x0001) { // HidD_FreePreparsedData(PreparsedData); // return FALSE; } if (HidCaps.FeatureReportByteLength != sizeof(ToHCICSRReports)) { // HidD_FreePreparsedData(PreparsedData); // return FALSE; } CHAR ReportBuffer[sizeof(ToHCICSRReports)]; RtlCopyMemory(ReportBuffer, ToHCICSRReports, sizeof(ToHCICSRReports)); if (!HidD_SetFeature(hHidDevice, ReportBuffer, HidCaps.FeatureReportByteLength)) { LbtReportFunctionError(TEXT("HidD_SetOutputReport")); HidD_FreePreparsedData(PreparsedData); return FALSE; } HidD_FreePreparsedData(PreparsedData); LbtReportDongleSwitch(lpDongleName, toHID); return TRUE; }
/** * constructor */ boca_hid_printer_t *boca_hid_new(const char *device_path) { boca_hid_printer_t *self = (boca_hid_printer_t *) calloc(1, sizeof(boca_hid_printer_t)); if (!self) return NULL; self->device_path = strdup(device_path); #ifdef __WIN32__ HANDLE handle; handle = CreateFile(device_path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { PHIDP_PREPARSED_DATA preparsed_data; if (HidD_GetPreparsedData(handle, &preparsed_data)) { HIDP_CAPS capabilities; NTSTATUS ret = HidP_GetCaps(preparsed_data, &capabilities); if (ret == HIDP_STATUS_SUCCESS) { self->input_length = capabilities.InputReportByteLength; self->output_length = capabilities.OutputReportByteLength; self->feature_length = capabilities.FeatureReportByteLength; zsys_info("hid printer: input length %d output length %d feature length %d", self->input_length, self->output_length, self->feature_length); } else { zsys_warning("hid printer: can not get hid printer capabilities."); } } else { zsys_warning("hid printer: can not get hid prepared data"); } CloseHandle(handle); } else { zsys_warning("hid printer: can not open hid device code %d", GetLastError()); } #endif self->device = hid_open_path(self->device_path); if (self->device == NULL) { zsys_error("hid printer: could not create device from path %s", device_path); boca_hid_destroy(&self); return NULL; } return self; }
int RawInputPad::Open() { PHIDP_PREPARSED_DATA pPreparsedData = NULL; //TODO Better place? LoadMappings(mapVector); memset(&this->ovl, 0, sizeof(OVERLAPPED)); memset(&this->ovlW, 0, sizeof(OVERLAPPED)); //this->padState.initStage = 0; this->doPassthrough = !!conf.DFPPass;//TODO per player this->usbHandle = INVALID_HANDLE_VALUE; std::wstring path; { CONFIGVARIANT var(N_JOYSTICK, CONFIG_TYPE_WCHAR); if (LoadSetting(mPort, APINAME, var)) path = var.wstrValue; else return 1; } this->usbHandle = CreateFileW(path.c_str(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if(this->usbHandle != INVALID_HANDLE_VALUE) { this->ovl.hEvent = CreateEvent(0, 0, 0, 0); this->ovlW.hEvent = CreateEvent(0, 0, 0, 0); HidD_GetAttributes(this->usbHandle, &(this->attr)); HidD_GetPreparsedData(this->usbHandle, &pPreparsedData); HidP_GetCaps(pPreparsedData, &(this->caps)); HidD_FreePreparsedData(pPreparsedData); return 0; } else fwprintf(stderr, L"Could not open device '%s'.\nPassthrough and FFB will not work.\n", path.c_str()); return 1; }
// 获取设备一些能力信息 USBHIDDLL_API void __stdcall USBHIDGetDeviceCapabilities(USBHANDLE handle, PHIDD_ATTRIBUTES attributes, PHIDP_CAPS caps) { if (handle != INVALID_HANDLE_VALUE) { HidD_GetAttributes(handle, attributes); //Get the Capabilities structure for the device. PHIDP_PREPARSED_DATA PreparsedData; /* API function: HidD_GetPreparsedData Returns: a pointer to a buffer containing the information about the device's capabilities. Requires: A handle returned by CreateFile. There's no need to access the buffer directly, but HidP_GetCaps and other API functions require a pointer to the buffer. */ HidD_GetPreparsedData (handle, &PreparsedData); //DisplayLastError("HidD_GetPreparsedData: "); /* API function: HidP_GetCaps Learn the device's capabilities. For standard devices such as joysticks, you can find out the specific capabilities of the device. For a custom device, the software will probably know what the device is capable of, and the call only verifies the information. Requires: the pointer to the buffer returned by HidD_GetPreparsedData. Returns: a Capabilities structure containing the information. */ HidP_GetCaps (PreparsedData, caps); //No need for PreparsedData any more, so free the memory it's using. HidD_FreePreparsedData(PreparsedData); //DisplayLastError("HidD_FreePreparsedData: ") ; } }
int open_path(const char * path, int print) { int device = async_open_path(path, print); if(device >= 0) { HIDD_ATTRIBUTES attributes = { .Size = sizeof(HIDD_ATTRIBUTES) }; if(HidD_GetAttributes(devices[device].handle, &attributes) == TRUE) { PHIDP_PREPARSED_DATA preparsedData; HIDP_CAPS hidCapabilities; if(HidD_GetPreparsedData(devices[device].handle, &preparsedData) == TRUE) { if(HidP_GetCaps(preparsedData, &hidCapabilities) == HIDP_STATUS_SUCCESS ) { devices[device].write.size = hidCapabilities.OutputReportByteLength; async_set_read_size(device, hidCapabilities.InputReportByteLength); devices[device].hidInfo.vendor_id = attributes.VendorID; devices[device].hidInfo.product_id = attributes.ProductID; devices[device].hidInfo.bcdDevice = attributes.VersionNumber; } else { ASYNC_PRINT_ERROR("HidP_GetCaps") async_close(device); device = -1; } HidD_FreePreparsedData(preparsedData); } else { ASYNC_PRINT_ERROR("HidD_GetPreparsedData") async_close(device); device = -1; } } else { ASYNC_PRINT_ERROR("HidD_GetAttributes") async_close(device); device = -1; } } return device; }
void parse_info_from_path(hid_event* event, char* device_path) { auto write_handle = open_device(device_path, TRUE); /* Check validity of write_handle. */ if (write_handle == INVALID_HANDLE_VALUE) { /* Unable to open the device. */ //register_error(dev, "CreateFile"); //goto cont_close; return; } //TODO: clean this shit up HIDD_ATTRIBUTES attrib; attrib.Size = sizeof(HIDD_ATTRIBUTES); HidD_GetAttributes(write_handle, &attrib); event->device_info->product_id = attrib.ProductID; event->device_info->vendor_id = attrib.VendorID; event->device_info->release_number = attrib.VersionNumber; PHIDP_PREPARSED_DATA pp_data = NULL; auto res = HidD_GetPreparsedData(write_handle, &pp_data); if (res) { HIDP_CAPS caps; auto nt_res = HidP_GetCaps(pp_data, &caps); if (nt_res == HIDP_STATUS_SUCCESS) { event->device_info->usage_page = caps.UsagePage; event->device_info->usage = caps.Usage; } HidD_FreePreparsedData(pp_data); } CloseHandle(write_handle); }
bool Controller::registerController(int pDeviceId) { //Required variables to iterate over the device interface DWORD result = 0; LPGUID hidguid = (LPGUID)malloc(sizeof(GUID)); HDEVINFO deviceset; PSP_DEVICE_INTERFACE_DATA deviceInterfaceData = nullptr; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceInfo = nullptr; DWORD buffersize = 0; //malloc check CHECK_NULL(hidguid, SETUP_ERROR); //get hid guid HidD_GetHidGuid(hidguid); //get device list deviceset = SetupDiGetClassDevs(hidguid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (deviceset == INVALID_HANDLE_VALUE) { goto SETUP_ERROR; } int deviceindex = 0; deviceInterfaceData = (PSP_DEVICE_INTERFACE_DATA)malloc(sizeof(SP_DEVICE_INTERFACE_DATA)); //malloc check CHECK_NULL(deviceInterfaceData, SETUP_ERROR); deviceInterfaceData->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); //iterate through devices while (SetupDiEnumDeviceInterfaces(deviceset, nullptr, hidguid, deviceindex, deviceInterfaceData)) { //this should fail originally in order to get buffer size if (deviceInterfaceInfo != nullptr) { free(deviceInterfaceInfo); deviceInterfaceInfo = 0; } if (SetupDiGetDeviceInterfaceDetail(deviceset, deviceInterfaceData, nullptr, 0, (PDWORD)&buffersize, nullptr) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { goto SETUP_ERROR; } deviceInterfaceInfo = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(buffersize); //malloc check CHECK_NULL(deviceInterfaceInfo, SETUP_ERROR); deviceInterfaceInfo->cbSize = sizeof(*deviceInterfaceInfo); //device info buffer allocated, get details if (!SetupDiGetDeviceInterfaceDetail(deviceset, deviceInterfaceData, deviceInterfaceInfo, buffersize, (PDWORD)&buffersize, nullptr)) { goto SETUP_ERROR; } //you must pass in the device id of the hid client to read from if (deviceindex == pDeviceId) { _controllerHandle = CreateFile( deviceInterfaceInfo->DevicePath, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr ); if (_controllerHandle == INVALID_HANDLE_VALUE) { Log(L"Error opening HID Device: Error No: %d", GetLastError()); goto HID_SETUP_FAILURE; } else { //get preparsed data if (HidD_GetPreparsedData(_controllerHandle, &_pPreparsedData) == FALSE) { goto HID_SETUP_FAILURE; } //get device capabilities _pCaps = (PHIDP_CAPS)(malloc(sizeof(HIDP_CAPS))); //malloc check CHECK_NULL(_pCaps, HID_SETUP_FAILURE); _succ = HidP_GetCaps(_pPreparsedData, _pCaps); CHECK_HID(_succ, HID_SETUP_FAILURE); //get button capabilities _pButtonCaps = (PHIDP_BUTTON_CAPS)malloc(sizeof(HIDP_BUTTON_CAPS)* _pCaps->NumberInputButtonCaps); //malloc check CHECK_NULL(_pButtonCaps, HID_SETUP_FAILURE); USHORT numInputButtonCaps = _pCaps->NumberInputButtonCaps; _succ = HidP_GetButtonCaps(HidP_Input, _pButtonCaps, &numInputButtonCaps, _pPreparsedData); CHECK_HID(_succ, HID_SETUP_FAILURE); //prep the button usage data structs _pButtonUsages = (PUSAGE)malloc(sizeof(USAGE)*(_pButtonCaps->Range.DataIndexMax - _pButtonCaps->Range.DataIndexMin + 1)); //malloc check CHECK_NULL(_pButtonUsages, HID_SETUP_FAILURE); ULONG numButtonUsages = _pButtonCaps->Range.UsageMax - _pButtonCaps->Range.UsageMin + 1; //get max data length _pInputReport = (PCHAR)malloc(_pCaps->InputReportByteLength); //malloc check CHECK_NULL(_pInputReport, HID_SETUP_FAILURE); DWORD readbytecount = 0; //get value caps _pValueCaps = (PHIDP_VALUE_CAPS)malloc(sizeof(HIDP_VALUE_CAPS)* _pCaps->NumberInputValueCaps); //malloc check CHECK_NULL(_pValueCaps, HID_SETUP_FAILURE); USHORT numInputValueCaps = _pCaps->NumberInputValueCaps; _succ = HidP_GetValueCaps(HidP_Input, _pValueCaps, &numInputValueCaps, _pPreparsedData); CHECK_HID(_succ, HID_SETUP_FAILURE); goto SETUP_DONE; HID_SETUP_FAILURE: clearHidStructures(); return false; } } deviceindex++; deviceInterfaceData->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); } goto SETUP_DONE; SETUP_ERROR: int err = GetLastError(); return false; SETUP_DONE: if (deviceset != INVALID_HANDLE_VALUE && deviceset != nullptr) { SetupDiDestroyDeviceInfoList(deviceset); } if (hidguid != nullptr) { free(hidguid); } if (deviceInterfaceData != nullptr) { free(deviceInterfaceData); } if (deviceInterfaceInfo != nullptr) { free(deviceInterfaceInfo); } return true; }
static void ParseRawInputHID(PRAWINPUT pRawInput) { PHIDP_PREPARSED_DATA pPreparsedData = NULL; HIDP_CAPS Caps; PHIDP_BUTTON_CAPS pButtonCaps = NULL; PHIDP_VALUE_CAPS pValueCaps = NULL; UINT bufferSize; ULONG usageLength, value; TCHAR name[1024] = {0}; UINT nameSize = 1024; RID_DEVICE_INFO devInfo = {0}; std::wstring devName; USHORT capsLength = 0; USAGE usage[MAX_BUTTONS] = {0}; Mappings *mapping = NULL; MapVector::iterator it; GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, name, &nameSize); devName = name; std::transform(devName.begin(), devName.end(), devName.begin(), ::toupper); for(it = mapVector.begin(); it != mapVector.end(); it++) { if((*it).hidPath == devName) { mapping = &(*it); break; } } if(mapping == NULL) return; CHECK( GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, NULL, &bufferSize) == 0 ); CHECK( pPreparsedData = (PHIDP_PREPARSED_DATA)malloc(bufferSize) ); CHECK( (int)GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, pPreparsedData, &bufferSize) >= 0 ); CHECK( HidP_GetCaps(pPreparsedData, &Caps) == HIDP_STATUS_SUCCESS ); //pSize = sizeof(devInfo); //GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICEINFO, &devInfo, &pSize); //Unset buttons/axes mapped to this device //ResetData(&mapping->data[0]); //ResetData(&mapping->data[1]); memset(&mapping->data[0], 0xFF, sizeof(wheel_data_t)); memset(&mapping->data[1], 0xFF, sizeof(wheel_data_t)); mapping->data[0].buttons = 0; mapping->data[1].buttons = 0; mapping->data[0].hatswitch = 0x8; mapping->data[1].hatswitch = 0x8; //Get pressed buttons CHECK( pButtonCaps = (PHIDP_BUTTON_CAPS)malloc(sizeof(HIDP_BUTTON_CAPS) * Caps.NumberInputButtonCaps) ); //If fails, maybe wheel only has axes capsLength = Caps.NumberInputButtonCaps; HidP_GetButtonCaps(HidP_Input, pButtonCaps, &capsLength, pPreparsedData); int numberOfButtons = pButtonCaps->Range.UsageMax - pButtonCaps->Range.UsageMin + 1; usageLength = numberOfButtons; NTSTATUS stat; if((stat = HidP_GetUsages( HidP_Input, pButtonCaps->UsagePage, 0, usage, &usageLength, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid)) == HIDP_STATUS_SUCCESS ) { for(uint32_t i = 0; i < usageLength; i++) { uint16_t btn = mapping->btnMap[usage[i] - pButtonCaps->Range.UsageMin]; for(int j=0; j<2; j++) { PS2WheelTypes wt = (PS2WheelTypes)conf.WheelType[j]; if(PLY_IS_MAPPED(j, btn)) { uint32_t wtbtn = (1 << convert_wt_btn(wt, PLY_GET_VALUE(j, btn))) & 0xFFF; //12bit mask mapping->data[j].buttons |= wtbtn; } } } } /// Get axes' values CHECK( pValueCaps = (PHIDP_VALUE_CAPS)malloc(sizeof(HIDP_VALUE_CAPS) * Caps.NumberInputValueCaps) ); capsLength = Caps.NumberInputValueCaps; if(HidP_GetValueCaps(HidP_Input, pValueCaps, &capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS ) { for(USHORT i = 0; i < capsLength; i++) { if(HidP_GetUsageValue( HidP_Input, pValueCaps[i].UsagePage, 0, pValueCaps[i].Range.UsageMin, &value, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid ) != HIDP_STATUS_SUCCESS ) { continue; // if here then maybe something is up with HIDP_CAPS.NumberInputValueCaps } //fprintf(stderr, "Min/max %d/%d\t", pValueCaps[i].LogicalMin, pValueCaps[i].LogicalMax); //TODO can be simpler? //Get mapped axis for physical axis uint16_t v = 0; switch(pValueCaps[i].Range.UsageMin) { // X-axis 0x30 case HID_USAGE_GENERIC_X: v = mapping->axisMap[0]; break; // Y-axis case HID_USAGE_GENERIC_Y: v = mapping->axisMap[1]; break; // Z-axis case HID_USAGE_GENERIC_Z: v = mapping->axisMap[2]; break; // Rotate-X case HID_USAGE_GENERIC_RX: v = mapping->axisMap[3]; break; // Rotate-Y case HID_USAGE_GENERIC_RY: v = mapping->axisMap[4]; break; // Rotate-Z 0x35 case HID_USAGE_GENERIC_RZ: v = mapping->axisMap[5]; break; case HID_USAGE_GENERIC_HATSWITCH: //fprintf(stderr, "Hat: %02X\n", value); v = mapping->axisMap[6]; break; } int type = 0; for(int j=0; j<2; j++) { if(!PLY_IS_MAPPED(j, v)) continue; type = conf.WheelType[j]; switch(PLY_GET_VALUE(j, v)) { case PAD_AXIS_X: // X-axis //fprintf(stderr, "X: %d\n", value); // Need for logical min too? //generic_data.axis_x = ((value - pValueCaps[i].LogicalMin) * 0x3FF) / (pValueCaps[i].LogicalMax - pValueCaps[i].LogicalMin); if(type == WT_DRIVING_FORCE_PRO) mapping->data[j].steering = (value * 0x3FFF) / pValueCaps[i].LogicalMax; else //XXX Limit value range to 0..1023 if using 'generic' wheel descriptor mapping->data[j].steering = (value * 0x3FF) / pValueCaps[i].LogicalMax; break; case PAD_AXIS_Y: // Y-axis if(!(devInfo.hid.dwVendorId == 0x046D && devInfo.hid.dwProductId == 0xCA03)) //XXX Limit value range to 0..255 mapping->data[j].clutch = (value * 0xFF) / pValueCaps[i].LogicalMax; break; case PAD_AXIS_Z: // Z-axis //fprintf(stderr, "Z: %d\n", value); //XXX Limit value range to 0..255 mapping->data[j].throttle = (value * 0xFF) / pValueCaps[i].LogicalMax; break; case PAD_AXIS_RZ: // Rotate-Z //fprintf(stderr, "Rz: %d\n", value); //XXX Limit value range to 0..255 mapping->data[j].brake = (value * 0xFF) / pValueCaps[i].LogicalMax; break; case PAD_AXIS_HAT: // TODO Hat Switch //fprintf(stderr, "Hat: %02X\n", value); //TODO 4 vs 8 direction hat switch if(pValueCaps[i].LogicalMax == 4 && value < 4) mapping->data[j].hatswitch = HATS_8TO4[value]; else mapping->data[j].hatswitch = value; break; } } } } Error: SAFE_FREE(pPreparsedData); SAFE_FREE(pButtonCaps); SAFE_FREE(pValueCaps); }
static void ParseRawInputHID(PRAWINPUT pRawInput, HIDState *hs) { PHIDP_PREPARSED_DATA pPreparsedData = NULL; HIDP_CAPS Caps; PHIDP_BUTTON_CAPS pButtonCaps = NULL; PHIDP_VALUE_CAPS pValueCaps = NULL; UINT bufferSize = 0; ULONG usageLength, value; TCHAR name[1024] = {0}; UINT nameSize = 1024; RID_DEVICE_INFO devInfo = {0}; std::wstring devName; USHORT capsLength = 0; USAGE usage[32] = {0}; int numberOfButtons; GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, name, &nameSize); devName = name; std::transform(devName.begin(), devName.end(), devName.begin(), ::toupper); CHECK( GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, NULL, &bufferSize) == 0 ); CHECK( pPreparsedData = (PHIDP_PREPARSED_DATA)malloc(bufferSize) ); CHECK( (int)GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, pPreparsedData, &bufferSize) >= 0 ); CHECK( HidP_GetCaps(pPreparsedData, &Caps) == HIDP_STATUS_SUCCESS ); //Get pressed buttons CHECK( pButtonCaps = (PHIDP_BUTTON_CAPS)malloc(sizeof(HIDP_BUTTON_CAPS) * Caps.NumberInputButtonCaps) ); //If fails, maybe wheel only has axes capsLength = Caps.NumberInputButtonCaps; HidP_GetButtonCaps(HidP_Input, pButtonCaps, &capsLength, pPreparsedData); numberOfButtons = pButtonCaps->Range.UsageMax - pButtonCaps->Range.UsageMin + 1; usageLength = countof(usage);//numberOfButtons; NTSTATUS stat; if((stat = HidP_GetUsages( HidP_Input, pButtonCaps->UsagePage, 0, usage, &usageLength, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid)) == HIDP_STATUS_SUCCESS ) { for(uint32_t i = 0; i < usageLength; i++) { uint16_t btn = usage[i] - pButtonCaps->Range.UsageMin; } } /// Get axes' values CHECK( pValueCaps = (PHIDP_VALUE_CAPS)malloc(sizeof(HIDP_VALUE_CAPS) * Caps.NumberInputValueCaps) ); capsLength = Caps.NumberInputValueCaps; if(HidP_GetValueCaps(HidP_Input, pValueCaps, &capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS ) { for(USHORT i = 0; i < capsLength; i++) { if(HidP_GetUsageValue( HidP_Input, pValueCaps[i].UsagePage, 0, pValueCaps[i].Range.UsageMin, &value, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid ) != HIDP_STATUS_SUCCESS ) { continue; // if here then maybe something is up with HIDP_CAPS.NumberInputValueCaps } switch(pValueCaps[i].Range.UsageMin) { case HID_USAGE_GENERIC_X: //0x30 case HID_USAGE_GENERIC_Y: case HID_USAGE_GENERIC_Z: case HID_USAGE_GENERIC_RX: case HID_USAGE_GENERIC_RY: case HID_USAGE_GENERIC_RZ: //0x35 //int axis = (value * 0x3FFF) / pValueCaps[i].LogicalMax; break; case HID_USAGE_GENERIC_HATSWITCH: //fprintf(stderr, "Hat: %02X\n", value); break; } } } Error: SAFE_FREE(pPreparsedData); SAFE_FREE(pButtonCaps); SAFE_FREE(pValueCaps); }
/********************************************************************* * * _AddConnection * * Function description * Checks the information about the queried device, if complying with * out vendor page information, add the device to the connection list. * * * Return value: * 1 - O.K., HID device added. * 0 - HID device not compatible. * */ static int _AddConnection(HANDLE hDevList, SP_DEVICE_INTERFACE_DATA * pDevData, CONN_INFO * pConnInfo) { SP_INTERFACE_DEVICE_DETAIL_DATA * pDevDetail; DWORD ReqLen; BOOL succ; HANDLE hDevice; int r; r = 0; ReqLen = 0; pDevDetail = NULL; // Get length of INTERFACE_DEVICE_DETAIL_DATA, allocate buffer // This function call returns the system error "buffer too small" (code 122). // We ignore this return code. SetupDiGetDeviceInterfaceDetail(hDevList, pDevData, NULL, ReqLen, &ReqLen, NULL); pDevDetail = (SP_INTERFACE_DEVICE_DETAIL_DATA*)malloc(ReqLen); if (pDevDetail == NULL ) { // Memory allocation failed return 0; } // Now get the INTERFACE_DEVICE_DETAIL_DATA struct ZeroMemory(pDevDetail,ReqLen); pDevDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); succ = SetupDiGetDeviceInterfaceDetail(hDevList, pDevData, pDevDetail, ReqLen, &ReqLen, NULL); if (succ == FALSE) { // // Could not retrieve information about the device // free(pDevDetail); return 0; } // // Open a device handle to the HID device and retriece HID information // hDevice = CreateFile(pDevDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDevice != INVALID_HANDLE_VALUE) { PHIDP_PREPARSED_DATA pPreparsedData; HIDP_CAPS Capabilities; // // Parse the information from HID device // HidD_GetPreparsedData(hDevice, &pPreparsedData); HidP_GetCaps(pPreparsedData, &Capabilities); // // Does the device match the Vendor specific page implementation? // if (Capabilities.UsagePage == _VendorPage) { HIDD_ATTRIBUTES Attributes; memset(&Attributes, 0, sizeof(Attributes)); Attributes.Size = sizeof(Attributes); HidD_GetAttributes(hDevice, &Attributes); strcpy(pConnInfo->acDevicePath, pDevDetail->DevicePath); pConnInfo->InputReportByteLength = Capabilities.InputReportByteLength; pConnInfo->OutputReportByteLength = Capabilities.OutputReportByteLength; pConnInfo->ProductId = Attributes.ProductID; pConnInfo->VendorId = Attributes.VendorID; r = 1; } CloseHandle(hDevice); } free(pDevDetail); return r; }
// rawhid_open - open 1 or more devices // // Inputs: // max = maximum number of devices to open // vid = Vendor ID, or -1 if any // pid = Product ID, or -1 if any // usage_page = top level usage page, or -1 if any // usage = top level usage number, or -1 if any // Output: // actual number of devices opened // int rawhid_open(int max, int vid, int pid, int usage_page, int usage) { GUID guid; HDEVINFO info; DWORD index=0, reqd_size; SP_DEVICE_INTERFACE_DATA iface; SP_DEVICE_INTERFACE_DETAIL_DATA *details; HIDD_ATTRIBUTES attrib; PHIDP_PREPARSED_DATA hid_data; HIDP_CAPS capabilities; HANDLE h; BOOL ret; hid_t *hid; int count=0; if (first_hid) free_all_hid(); if (max < 1) return 0; if (!rx_event) { rx_event = CreateEvent(NULL, TRUE, TRUE, NULL); tx_event = CreateEvent(NULL, TRUE, TRUE, NULL); InitializeCriticalSection(&rx_mutex); InitializeCriticalSection(&tx_mutex); } HidD_GetHidGuid(&guid); info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (info == INVALID_HANDLE_VALUE) return 0; for (index=0; 1 ;index++) { iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); ret = SetupDiEnumDeviceInterfaces(info, NULL, &guid, index, &iface); if (!ret) return count; SetupDiGetInterfaceDeviceDetail(info, &iface, NULL, 0, &reqd_size, NULL); details = (SP_DEVICE_INTERFACE_DETAIL_DATA *)malloc(reqd_size); if (details == NULL) continue; memset(details, 0, reqd_size); details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); ret = SetupDiGetDeviceInterfaceDetail(info, &iface, details, reqd_size, NULL, NULL); if (!ret) { free(details); continue; } h = CreateFile(details->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); free(details); if (h == INVALID_HANDLE_VALUE) continue; attrib.Size = sizeof(HIDD_ATTRIBUTES); ret = HidD_GetAttributes(h, &attrib); //printf("vid: %4x\n", attrib.VendorID); if (!ret || (vid > 0 && attrib.VendorID != vid) || (pid > 0 && attrib.ProductID != pid) || !HidD_GetPreparsedData(h, &hid_data)) { CloseHandle(h); continue; } if (!HidP_GetCaps(hid_data, &capabilities) || (usage_page > 0 && capabilities.UsagePage != usage_page) || (usage > 0 && capabilities.Usage != usage)) { HidD_FreePreparsedData(hid_data); CloseHandle(h); continue; } HidD_FreePreparsedData(hid_data); hid = (struct hid_struct *)malloc(sizeof(struct hid_struct)); if (!hid) { CloseHandle(h); continue; } hid->handle = h; hid->open = 1; add_hid(hid); count++; if (count >= max) return count; } return count; }
BOOLEAN OpenHidDevice ( IN PCHAR DevicePath, IN BOOL HasReadAccess, IN BOOL HasWriteAccess, IN BOOL IsOverlapped, IN BOOL IsExclusive, IN OUT PHID_DEVICE HidDevice ) /*++ RoutineDescription: Given the HardwareDeviceInfo, representing a handle to the plug and play information, and deviceInfoData, representing a specific hid device, open that device and fill in all the relivant information in the given HID_DEVICE structure. return if the open and initialization was successfull or not. --*/ { DWORD accessFlags = 0; DWORD sharingFlags = 0; BOOLEAN bSuccess; INT iDevicePathSize; HRESULT stringReturn; iDevicePathSize = strlen(DevicePath) + 1; HidDevice -> DevicePath = malloc(iDevicePathSize); if (NULL == HidDevice -> DevicePath) { return (FALSE); } stringReturn = StringCbCopy(HidDevice -> DevicePath, iDevicePathSize, DevicePath); if (HasReadAccess) { accessFlags |= GENERIC_READ; } if (HasWriteAccess) { accessFlags |= GENERIC_WRITE; } if (!IsExclusive) { sharingFlags = FILE_SHARE_READ | FILE_SHARE_WRITE; } // // The hid.dll api's do not pass the overlapped structure into deviceiocontrol // so to use them we must have a non overlapped device. If the request is for // an overlapped device we will close the device below and get a handle to an // overlapped device // HidDevice->HidDevice = CreateFile (DevicePath, accessFlags, sharingFlags, NULL, // no SECURITY_ATTRIBUTES structure OPEN_EXISTING, // No special create flags 0, // Open device as non-overlapped so we can get data NULL); // No template file if (INVALID_HANDLE_VALUE == HidDevice->HidDevice) { free(HidDevice -> DevicePath); HidDevice -> DevicePath = INVALID_HANDLE_VALUE ; return FALSE; } HidDevice -> OpenedForRead = HasReadAccess; HidDevice -> OpenedForWrite = HasWriteAccess; HidDevice -> OpenedOverlapped = IsOverlapped; HidDevice -> OpenedExclusive = IsExclusive; // // If the device was not opened as overlapped, then fill in the rest of the // HidDevice structure. However, if opened as overlapped, this handle cannot // be used in the calls to the HidD_ exported functions since each of these // functions does synchronous I/O. // if (!HidD_GetPreparsedData (HidDevice->HidDevice, &HidDevice->Ppd)) { free(HidDevice -> DevicePath); HidDevice -> DevicePath = NULL ; CloseHandle(HidDevice -> HidDevice); HidDevice -> HidDevice = INVALID_HANDLE_VALUE ; return FALSE; } if (!HidD_GetAttributes (HidDevice->HidDevice, &HidDevice->Attributes)) { free(HidDevice -> DevicePath); HidDevice -> DevicePath = NULL; CloseHandle(HidDevice -> HidDevice); HidDevice -> HidDevice = INVALID_HANDLE_VALUE; HidD_FreePreparsedData (HidDevice->Ppd); HidDevice->Ppd = NULL; return FALSE; } if (!HidP_GetCaps (HidDevice->Ppd, &HidDevice->Caps)) { free(HidDevice -> DevicePath); HidDevice -> DevicePath = NULL; CloseHandle(HidDevice -> HidDevice); HidDevice -> HidDevice = INVALID_HANDLE_VALUE; HidD_FreePreparsedData (HidDevice->Ppd); HidDevice->Ppd = NULL; return FALSE; } // // At this point the client has a choice. It may chose to look at the // Usage and Page of the top level collection found in the HIDP_CAPS // structure. In this way it could just use the usages it knows about. // If either HidP_GetUsages or HidP_GetUsageValue return an error then // that particular usage does not exist in the report. // This is most likely the preferred method as the application can only // use usages of which it already knows. // In this case the app need not even call GetButtonCaps or GetValueCaps. // // In this example, however, we will call FillDeviceInfo to look for all // of the usages in the device. // bSuccess = FillDeviceInfo(HidDevice); if (FALSE == bSuccess) { CloseHidDevice(HidDevice); return (FALSE); } if (IsOverlapped) { CloseHandle(HidDevice->HidDevice); HidDevice->HidDevice = CreateFile (DevicePath, accessFlags, sharingFlags, NULL, // no SECURITY_ATTRIBUTES structure OPEN_EXISTING, // No special create flags FILE_FLAG_OVERLAPPED, // Now we open the device as overlapped NULL); // No template file if (INVALID_HANDLE_VALUE == HidDevice->HidDevice) { CloseHidDevice(HidDevice); return FALSE; } } return (TRUE); }
HRESULT CMyDevice::GetHidCapabilities( void ) /*++ Routine Description: This routine gets the capabilities and attributes from the HID collection Arguments: None. Return Value: HRESULT --*/ { HID_COLLECTION_INFORMATION info; HRESULT hr = S_OK; ZeroMemory((BYTE *)&info, sizeof(info)); hr = CreateAndSendIoctlRequest(NULL, 0, (BYTE*)(&info), sizeof(info), IOCTL_HID_GET_COLLECTION_INFORMATION); if (SUCCEEDED(hr)) { m_Ppd = (PHIDP_PREPARSED_DATA)(new BYTE[info.DescriptorSize]); if (m_Ppd == NULL) { hr = E_OUTOFMEMORY; TraceEvents(TRACE_LEVEL_ERROR, TEST_TRACE_DEVICE, "%!FUNC! Out of memory" ); } } if (SUCCEEDED(hr)) { ZeroMemory((BYTE*)m_Ppd, info.DescriptorSize); hr = CreateAndSendIoctlRequest(NULL, 0, (BYTE*)m_Ppd, info.DescriptorSize, IOCTL_HID_GET_COLLECTION_DESCRIPTOR); } if (SUCCEEDED(hr)) { ZeroMemory(&m_Caps, sizeof (m_Caps)); hr = HidP_GetCaps (m_Ppd, &m_Caps); if (FAILED(hr)) { TraceEvents(TRACE_LEVEL_ERROR, TEST_TRACE_DEVICE, "%!FUNC! HidP_GetCaps failed %!HRESULT!", hr ); } } if (SUCCEEDED(hr)) { // // Validate the capabilities. // if (m_Caps.UsagePage != 0xff00 || m_Caps.FeatureReportByteLength != 2) { hr = E_UNEXPECTED; TraceEvents(TRACE_LEVEL_ERROR, TEST_TRACE_DEVICE, "%!FUNC! Capabilities don't match expected usage page 0x%x and feature report length %d %!HRESULT!", m_Caps.UsagePage, m_Caps.FeatureReportByteLength, hr ); } } return hr; }
// open - open 1 or more devices // // Inputs: // max = maximum number of devices to open // vid = Vendor ID, or -1 if any // pid = Product ID, or -1 if any // usage_page = top level usage page, or -1 if any // usage = top level usage number, or -1 if any // Output: // actual number of devices opened // int pjrc_rawhid::open(int max, int vid, int pid, int usage_page, int usage) { GUID guid; HDEVINFO info; DWORD index=0, reqd_size; SP_DEVICE_INTERFACE_DATA iface; SP_DEVICE_INTERFACE_DETAIL_DATA *details; HIDD_ATTRIBUTES attrib; PHIDP_PREPARSED_DATA hid_data; HIDP_CAPS capabilities; HANDLE h; BOOL ret; hid_t *hid; int count=0; if (first_hid) free_all_hid(); if (max < 1) return 0; if (!rx_event) { rx_event = CreateEvent(NULL, TRUE, TRUE, NULL); tx_event = CreateEvent(NULL, TRUE, TRUE, NULL); InitializeCriticalSection(&rx_mutex); InitializeCriticalSection(&tx_mutex); } HidD_GetHidGuid(&guid); info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (info == INVALID_HANDLE_VALUE) return 0; for (index=0; 1 ;index++) { iface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); ret = SetupDiEnumDeviceInterfaces(info, NULL, &guid, index, &iface); if (!ret) return count; SetupDiGetInterfaceDeviceDetail(info, &iface, NULL, 0, &reqd_size, NULL); details = (SP_DEVICE_INTERFACE_DETAIL_DATA *)malloc(reqd_size); if (details == NULL) continue; memset(details, 0, reqd_size); details->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); ret = SetupDiGetDeviceInterfaceDetail(info, &iface, details, reqd_size, NULL, NULL); if (!ret) { free(details); continue; } h = CreateFile(details->DevicePath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (h == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); // I get ERROR_ACCESS_DENIED with most/all my input devices (mice/trackballs/tablet). // Let's not log it :) if (err == ERROR_ACCESS_DENIED) { free(details); continue; } // qDebug wipes the GetLastError() it seems, so do that after print_win32_err(). print_win32_err(err); qDebug() << "Problem opening handle, path: " << QString().fromWCharArray(details->DevicePath); free(details); continue; } free(details); attrib.Size = sizeof(HIDD_ATTRIBUTES); ret = HidD_GetAttributes(h, &attrib); //printf("vid: %4x\n", attrib.VendorID); if (!ret || (vid > 0 && attrib.VendorID != vid) || (pid > 0 && attrib.ProductID != pid) || !HidD_GetPreparsedData(h, &hid_data)) { CloseHandle(h); continue; } if (!HidP_GetCaps(hid_data, &capabilities) || (usage_page > 0 && capabilities.UsagePage != usage_page) || (usage > 0 && capabilities.Usage != usage)) { HidD_FreePreparsedData(hid_data); CloseHandle(h); continue; } HidD_FreePreparsedData(hid_data); hid = (struct hid_struct *)malloc(sizeof(struct hid_struct)); if (!hid) { CloseHandle(h); continue; } // COMMTIMEOUTS CommTimeouts; // CommTimeouts.ReadIntervalTimeout = 100; // 100ms // CommTimeouts.ReadTotalTimeoutConstant = 5; // ms // CommTimeouts.ReadTotalTimeoutMultiplier = 1; // // CommTimeouts.WriteTotalTimeoutConstant = 5; // ms // CommTimeouts.WriteTotalTimeoutMultiplier = 1; // // if (!SetCommTimeouts(h, &CommTimeouts)) // { //// DWORD err = GetLastError(); // // } qDebug("Open: Handle address: %li for num: %i", (long int) h, count); hid->handle = h; add_hid(hid); count++; if (count >= max) return count; } return count; }
USBHIDDLL_API DWORD dwRead_FromHIDDevice(PHANDLE pReadHandle, BYTE byReadBuff[], DWORD len){ DWORD dwNumberOfBytesRead; DWORD dwResponseSW = 0; HIDP_CAPS Capabilities; PHIDP_PREPARSED_DATA HidParsedData; OVERLAPPED HidOverlapped; HANDLE hEvent; LPBYTE lpReadBuff = (LPBYTE)malloc(0x21); /* Create a new event for report capture */ hEvent = CreateEvent(NULL, TRUE, TRUE, ""); /* fill the HidOverlapped structure so that Windows knows which event to cause when the device sends an IN report */ HidOverlapped.hEvent = hEvent; HidOverlapped.Offset = 0; HidOverlapped.OffsetHigh = 0; BOOL bResult = false; // LPCOMMTIMEOUTS lpCommitTimes = (LPCOMMTIMEOUTS)malloc(sizeof(LPCOMMTIMEOUTS)); if(*pReadHandle != NULL){ HidD_GetPreparsedData(*pReadHandle, &HidParsedData); /* extract the capabilities info */ HidP_GetCaps( HidParsedData ,&Capabilities); /* Free the memory allocated when getting the preparsed data */ HidD_FreePreparsedData(HidParsedData); bResult = ReadFile(*pReadHandle, lpReadBuff, Capabilities.InputReportByteLength, &dwNumberOfBytesRead, (LPOVERLAPPED)&HidOverlapped); if ( bResult == 0 ) { // 读取错误信息 DWORD dwResult = GetLastError(); // I/O端口繁忙 if ( dwResult == ERROR_IO_PENDING ) { // 需挂起等待 dwResult = WaitForSingleObject ( HidOverlapped.hEvent, INFINITE ); // I/O端口正常 if ( dwResult == WAIT_OBJECT_0 ) { // 去读取数据的正常返回值 GetOverlappedResult ( *pReadHandle, &HidOverlapped, &dwNumberOfBytesRead, FALSE ); } else { // BOOL bGetTime = GetCommTimeouts(*pReadHandle, lpCommitTimes); free(lpReadBuff); CloseHandle(hEvent); return -1; // 等待超时 } } else { return -1; // 不明错误 } } } for(int i = 0; i < (len + 2); i ++){ byReadBuff[i] = *(lpReadBuff + i + 1); } dwResponseSW |= byReadBuff[len]; dwResponseSW = (dwResponseSW << 8) | (byReadBuff[len + 1]); free(lpReadBuff); CloseHandle(hEvent); return dwResponseSW; }
bool CLCDConnectionLogitech::HIDInit() { if (GetConnectionState() != CONNECTED || m_pConnectedDevice->GetIndex() != LGLCD_DEVICE_BW) //LGLCD_DEVICE_FAMILY_KEYBOARD_G15) return false; // Logitech G15 int VendorID = 0x046d; int ProductID = 0xc222; //Use a series of API calls to find a HID with a specified Vendor IF and Product ID. HIDD_ATTRIBUTES Attributes; SP_DEVICE_INTERFACE_DATA devInfoData; bool LastDevice = FALSE; int MemberIndex = 0; LONG Result; DWORD Length = 0; PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL; HANDLE hDevInfo = NULL; GUID HidGuid; ULONG Required = 0; bool MyDeviceDetected = false; /* API function: HidD_GetHidGuid Get the GUID for all system HIDs. Returns: the GUID in HidGuid. */ HidD_GetHidGuid(&HidGuid); /* API function: SetupDiGetClassDevs Returns: a handle to a device information set for all installed devices. Requires: the GUID returned by GetHidGuid. */ hDevInfo = SetupDiGetClassDevs (&HidGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); devInfoData.cbSize = sizeof(devInfoData); //Step through the available devices looking for the one we want. //Quit on detecting the desired device or checking all available devices without success. MemberIndex = 0; LastDevice = FALSE; do { /* API function: SetupDiEnumDeviceInterfaces On return, MyDeviceInterfaceData contains the handle to a SP_DEVICE_INTERFACE_DATA structure for a detected device. Requires: The DeviceInfoSet returned in SetupDiGetClassDevs. The HidGuid returned in GetHidGuid. An index to specify a device. */ Result = SetupDiEnumDeviceInterfaces (hDevInfo, 0, &HidGuid, MemberIndex, &devInfoData); if (Result != 0) { //A device has been detected, so get more information about it. /* API function: SetupDiGetDeviceInterfaceDetail Returns: an SP_DEVICE_INTERFACE_DETAIL_DATA structure containing information about a device. To retrieve the information, call this function twice. The first time returns the size of the structure in Length. The second time returns a pointer to the data in DeviceInfoSet. Requires: A DeviceInfoSet returned by SetupDiGetClassDevs The SP_DEVICE_INTERFACE_DATA structure returned by SetupDiEnumDeviceInterfaces. The final parameter is an optional pointer to an SP_DEV_INFO_DATA structure. This application doesn't retrieve or use the structure. If retrieving the structure, set MyDeviceInfoData.cbSize = length of MyDeviceInfoData. and pass the structure's address. */ //Get the Length value. //The call will return with a "buffer too small" error which can be ignored. Result = SetupDiGetDeviceInterfaceDetail (hDevInfo, &devInfoData, NULL, 0, &Length, NULL); //Allocate memory for the hDevInfo structure, using the returned Length. detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(Length); //Set cbSize in the detailData structure. detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); //Call the function again, this time passing it the returned buffer size. Result = SetupDiGetDeviceInterfaceDetail (hDevInfo, &devInfoData, detailData, Length, &Required, NULL); // Open a handle to the device. // To enable retrieving information about a system mouse or keyboard, // don't request Read or Write access for this handle. /* API function: CreateFile Returns: a handle that enables reading and writing to the device. Requires: The DevicePath in the detailData structure returned by SetupDiGetDeviceInterfaceDetail. */ m_hHIDDeviceHandle = CreateFile (detailData->DevicePath, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); /* API function: HidD_GetAttributes Requests information from the device. Requires: the handle returned by CreateFile. Returns: a HIDD_ATTRIBUTES structure containing the Vendor ID, Product ID, and Product Version Number. Use this information to decide if the detected device is the one we're looking for. */ //Set the Size to the number of bytes in the structure. Attributes.Size = sizeof(Attributes); Result = HidD_GetAttributes (m_hHIDDeviceHandle, &Attributes); //Is it the desired device? MyDeviceDetected = FALSE; if (Attributes.VendorID == VendorID) { if (Attributes.ProductID == ProductID) { //Both the Vendor ID and Product ID match. MyDeviceDetected = TRUE; } else CloseHandle(m_hHIDDeviceHandle); } else CloseHandle(m_hHIDDeviceHandle); //Free the memory used by the detailData structure (no longer needed). free(detailData); } else LastDevice = TRUE; MemberIndex = MemberIndex + 1; } //do while ((LastDevice == FALSE) && (MyDeviceDetected == FALSE)); if (MyDeviceDetected) { PHIDP_PREPARSED_DATA PreparsedData; HidD_GetPreparsedData (m_hHIDDeviceHandle, &PreparsedData); HidP_GetCaps (PreparsedData, &m_HIDCapabilities); HidD_FreePreparsedData(PreparsedData); } //Free the memory reserved for hDevInfo by SetupDiClassDevs. SetupDiDestroyDeviceInfoList(hDevInfo); return MyDeviceDetected; }
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) { BOOL res; struct hid_device_info *root = NULL; /* return object */ struct hid_device_info *cur_dev = NULL; /* Windows objects for interacting with the driver. */ GUID InterfaceClassGuid; HidD_GetHidGuid(&InterfaceClassGuid); SP_DEVINFO_DATA devinfo_data; SP_DEVICE_INTERFACE_DATA device_interface_data; SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; HDEVINFO device_info_set = INVALID_HANDLE_VALUE; int device_index = 0; int i; if (hid_init() < 0) return NULL; /* Initialize the Windows objects. */ memset(&devinfo_data, 0x0, sizeof(devinfo_data)); devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); /* Get information for all the devices belonging to the HID class. */ device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); /* Iterate over each device in the HID class, looking for the right one. */ for (;;) { HANDLE write_handle = INVALID_HANDLE_VALUE; DWORD required_size = 0; HIDD_ATTRIBUTES attrib; res = SetupDiEnumDeviceInterfaces(device_info_set, NULL, &InterfaceClassGuid, device_index, &device_interface_data); if (!res) { /* A return of FALSE from this function means that there are no more devices. */ break; } /* Call with 0-sized detail size, and let the function tell us how long the detail struct needs to be. The size is put in &required_size. */ res = SetupDiGetDeviceInterfaceDetailA(device_info_set, &device_interface_data, NULL, 0, &required_size, NULL); /* Allocate a long enough structure for device_interface_detail_data. */ device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); /* Get the detailed data for this device. The detail data gives us the device path for this device, which is then passed into CreateFile() to get a handle to the device. */ res = SetupDiGetDeviceInterfaceDetailA(device_info_set, &device_interface_data, device_interface_detail_data, required_size, NULL, NULL); if (!res) { /* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); Continue to the next device. */ goto cont; } /* Make sure this device is of Setup Class "HIDClass" and has a driver bound to it. */ for (i = 0; ; i++) { char driver_name[256]; /* Populate devinfo_data. This function will return failure when there are no more interfaces left. */ res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data); if (!res) goto cont; res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); if (!res) goto cont; if (strcmp(driver_name, "HIDClass") == 0) { /* See if there's a driver bound. */ res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data, SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL); if (res) break; } } //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); /* Open a handle to the device */ write_handle = open_device(device_interface_detail_data->DevicePath, TRUE); /* Check validity of write_handle. */ if (write_handle == INVALID_HANDLE_VALUE) { /* Unable to open the device. */ //register_error(dev, "CreateFile"); goto cont_close; } /* Get the Vendor ID and Product ID for this device. */ attrib.Size = sizeof(HIDD_ATTRIBUTES); HidD_GetAttributes(write_handle, &attrib); //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); /* Check the VID/PID to see if we should add this device to the enumeration list. */ if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) && (product_id == 0x0 || attrib.ProductID == product_id)) { #define WSTR_LEN 512 const char *str; struct hid_device_info *tmp; PHIDP_PREPARSED_DATA pp_data = NULL; HIDP_CAPS caps; BOOLEAN res; NTSTATUS nt_res; wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */ size_t len; /* VID/PID match. Create the record. */ tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); if (cur_dev) { cur_dev->next = tmp; } else { root = tmp; } cur_dev = tmp; /* Get the Usage Page and Usage for this device. */ res = HidD_GetPreparsedData(write_handle, &pp_data); if (res) { nt_res = HidP_GetCaps(pp_data, &caps); if (nt_res == HIDP_STATUS_SUCCESS) { cur_dev->usage_page = caps.UsagePage; cur_dev->usage = caps.Usage; } HidD_FreePreparsedData(pp_data); } /* Fill out the record */ cur_dev->next = NULL; str = device_interface_detail_data->DevicePath; if (str) { len = strlen(str); cur_dev->path = (char*) calloc(len+1, sizeof(char)); strncpy(cur_dev->path, str, len+1); cur_dev->path[len] = '\0'; } else cur_dev->path = NULL; /* Serial Number */ res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) { cur_dev->serial_number = _wcsdup(wstr); } /* Manufacturer String */ res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) { cur_dev->manufacturer_string = _wcsdup(wstr); } /* Product String */ res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); wstr[WSTR_LEN-1] = 0x0000; if (res) { cur_dev->product_string = _wcsdup(wstr); } /* VID/PID */ cur_dev->vendor_id = attrib.VendorID; cur_dev->product_id = attrib.ProductID; /* Release Number */ cur_dev->release_number = attrib.VersionNumber; /* Interface Number. It can sometimes be parsed out of the path on Windows if a device has multiple interfaces. See http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or search for "Hardware IDs for HID Devices" at MSDN. If it's not in the path, it's set to -1. */ cur_dev->interface_number = -1; if (cur_dev->path) { char *interface_component = strstr(cur_dev->path, "&mi_"); if (interface_component) { char *hex_str = interface_component + 4; char *endptr = NULL; cur_dev->interface_number = strtol(hex_str, &endptr, 16); if (endptr == hex_str) { /* The parsing failed. Set interface_number to -1. */ cur_dev->interface_number = -1; } } } } cont_close: CloseHandle(write_handle); cont: /* We no longer need the detail data. It can be freed */ free(device_interface_detail_data); device_index++; } /* Close the device information handle. */ SetupDiDestroyDeviceInfoList(device_info_set); return root; }
static void ParseRawInput(PRAWINPUT pRawInput) { //IwDebugTraceLinePrintf("%s(%d): Parsing raw input\n", __FUNCTION__, __LINE__); PHIDP_PREPARSED_DATA pPreparsedData; HIDP_CAPS Caps; PHIDP_BUTTON_CAPS pButtonCaps; PHIDP_VALUE_CAPS pValueCaps; USHORT capsLength; UINT bufferSize; HANDLE hHeap; USAGE usage[MAX_BUTTONS]; ULONG i, usageLength, value; pPreparsedData = NULL; pButtonCaps = NULL; pValueCaps = NULL; hHeap = GetProcessHeap(); // // Get the preparsed data block // CHECK( GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, NULL, &bufferSize) == 0 ); CHECK( pPreparsedData = (PHIDP_PREPARSED_DATA)HeapAlloc(hHeap, 0, bufferSize) ); CHECK( (int)GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_PREPARSEDDATA, pPreparsedData, &bufferSize) >= 0 ); // // Get the joystick's capabilities // // Button caps CHECK( HidP_GetCaps(pPreparsedData, &Caps) == HIDP_STATUS_SUCCESS ) CHECK( pButtonCaps = (PHIDP_BUTTON_CAPS)HeapAlloc(hHeap, 0, sizeof(HIDP_BUTTON_CAPS) * Caps.NumberInputButtonCaps) ); capsLength = Caps.NumberInputButtonCaps; CHECK( HidP_GetButtonCaps(HidP_Input, pButtonCaps, &capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS ) g_NumberOfButtons = pButtonCaps->Range.UsageMax - pButtonCaps->Range.UsageMin + 1; // Value caps CHECK( pValueCaps = (PHIDP_VALUE_CAPS)HeapAlloc(hHeap, 0, sizeof(HIDP_VALUE_CAPS) * Caps.NumberInputValueCaps) ); capsLength = Caps.NumberInputValueCaps; CHECK( HidP_GetValueCaps(HidP_Input, pValueCaps, &capsLength, pPreparsedData) == HIDP_STATUS_SUCCESS ) // // Get the pressed buttons // usageLength = g_NumberOfButtons; CHECK( HidP_GetUsages( HidP_Input, pButtonCaps->UsagePage, 0, usage, &usageLength, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid ) == HIDP_STATUS_SUCCESS ); ZeroMemory(bButtonStates, sizeof(bButtonStates)); for(i = 0; i < usageLength; i++) bButtonStates[usage[i] - pButtonCaps->Range.UsageMin] = TRUE; // // Get the state of discrete-valued-controls // //#define VALUES_DEBUG #ifdef VALUES_DEBUG char str[512] = ""; #endif for(i = 0; i < Caps.NumberInputValueCaps; i++) { CHECK( HidP_GetUsageValue( HidP_Input, pValueCaps[i].UsagePage, 0, pValueCaps[i].Range.UsageMin, &value, pPreparsedData, (PCHAR)pRawInput->data.hid.bRawData, pRawInput->data.hid.dwSizeHid ) == HIDP_STATUS_SUCCESS ); // NOTE: These values are only correct for the Xbox 360 controller // and for the PS3 Controller (via MotioninJoy '360 Emulation' mode) switch(pValueCaps[i].Range.UsageMin) { case 0x30: // X-axis (L-Stick Horizontal) lAxisX = (LONG)value; break; case 0x31: // Y-axis (L-Stick Vertical) lAxisY = (LONG)value; break; case 0x32: // Z-axis (L-Trigger: 32K-65K, R-Trigger: 0K-32K) lAxisZ = (LONG)value; break; case 0x33: // U-axis (R-Stick Horizontal) lAxisU = (LONG)value; break; case 0x34: // R-axis (R-Stick Vertical) lAxisR = (LONG)value; break; // POV Hat Switch (DPAD: // 1: Up // 2: UP/RIGHT // 3: RIGHT // 4: RIGHT/DOWN // 5: DOWN // 6: DOWN/LEFT // 7: LEFT // 8: LEFT/UP case 0x39: lDPad = (LONG)value; break; } #ifdef VALUES_DEBUG char s[128]; _snprintf(s, 128, "[Usage=0x%2X, Val=%4u] ", pValueCaps[i].Range.UsageMin, value); strcat(str, s); #endif } // // Clean up // Error: SAFE_FREE(pPreparsedData); SAFE_FREE(pButtonCaps); SAFE_FREE(pValueCaps); }
NTSTATUS Hid::GetCaps(void *PreparsedData, CAPS *caps) { return HidP_GetCaps(PreparsedData, caps); }
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, long* result) { MSG *msg = static_cast<MSG *>(message); if (msg->message == WM_INPUT && false) { UINT dwSize; GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); LPBYTE lpb = new BYTE[dwSize]; if (lpb == NULL) { return 0; } if (GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) OutputDebugString(TEXT("GetRawInputData does not return correct size !\n")); RAWINPUT* raw = (RAWINPUT*)lpb; if (raw->header.dwType == RIM_TYPEKEYBOARD) { MessageBox(NULL, L"Test", L"Test", MB_OK); } if (raw->header.dwType == RIM_TYPEHID) { UINT bufferSize; GetRawInputDeviceInfo(raw->header.hDevice, RIDI_PREPARSEDDATA, NULL, &bufferSize); LPBYTE deviceInfo = new BYTE[bufferSize]; //Add check for allocation //Get the HID preparsed data GetRawInputDeviceInfo(raw->header.hDevice, RIDI_PREPARSEDDATA, deviceInfo, &bufferSize); PHIDP_PREPARSED_DATA deviceInfoData = (PHIDP_PREPARSED_DATA)deviceInfo; HIDP_CAPS caps; HidP_GetCaps(deviceInfoData, &caps); LPBYTE ButtonCapsBuf = new BYTE[sizeof(HIDP_BUTTON_CAPS) * caps.NumberInputButtonCaps]; //Add check for allocation //Get the button capabilities from HID PHIDP_BUTTON_CAPS ButtonCaps = (PHIDP_BUTTON_CAPS)ButtonCapsBuf; USHORT capsLength = caps.NumberInputButtonCaps; HidP_GetButtonCaps(HidP_Input, ButtonCaps, &capsLength, deviceInfoData); LPBYTE ValCapsBuf = new BYTE[sizeof(HIDP_BUTTON_CAPS) * caps.NumberInputValueCaps]; //Find the current button usage data from HID device USAGE usage[128]; ULONG usageLength = ButtonCaps->Range.UsageMax - ButtonCaps->Range.UsageMin + 1; HidP_GetUsages(HidP_Input, ButtonCaps->UsagePage, 0, usage, &usageLength, deviceInfoData, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid); GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, NULL, &bufferSize); LPBYTE deviceNameInfoBuf = new BYTE[sizeof(wchar_t) * bufferSize]; //Add check for allocation //Get the HID Name GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, deviceNameInfoBuf, &bufferSize); wchar_t DeviceName[127]; HANDLE HIDHandle = CreateFile((LPCWSTR)deviceNameInfoBuf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); if (HIDHandle) { HidD_GetProductString(HIDHandle, DeviceName, sizeof(wchar_t) * 126); CloseHandle(HIDHandle); for (int i = 0; i < usageLength; i++) { MessageBox(NULL, boost::lexical_cast<std::wstring>(usage[i] - ButtonCaps->Range.UsageMin).c_str(), std::wstring(DeviceName).c_str(), MB_OK); } } //Clean up byte arrays; delete[] deviceNameInfoBuf; delete[] ValCapsBuf; delete[] ButtonCapsBuf; delete[] deviceInfo; } //Clean up byte arrays; delete[] lpb; return true; } return false; }
NTSTATUS FireflySetFeature( IN PDEVICE_CONTEXT DeviceContext, IN UCHAR PageId, IN USHORT FeatureId, IN BOOLEAN EnableFeature ) /*++ Routine Description: This routine sets the HID feature by sending HID ioctls to our device. These IOCTLs will be handled by HIDUSB and converted into USB requests and send to the device. Arguments: DeviceContext - Context for our device PageID - UsagePage of the light control feature. FeatureId - Usage ID of the feature. EnanbleFeature - True to turn the light on, Falst to turn if off. Return Value: NT Status code --*/ { WDF_MEMORY_DESCRIPTOR inputDescriptor, outputDescriptor; NTSTATUS status; HID_COLLECTION_INFORMATION collectionInformation = {0}; PHIDP_PREPARSED_DATA preparsedData; HIDP_CAPS caps; USAGE usage; ULONG usageLength; PCHAR report; WDFIOTARGET hidTarget; WDF_IO_TARGET_OPEN_PARAMS openParams; PAGED_CODE(); // // Preinit for error. // preparsedData = NULL; report = NULL; hidTarget = NULL; status = WdfIoTargetCreate(WdfObjectContextGetObject(DeviceContext), WDF_NO_OBJECT_ATTRIBUTES, &hidTarget); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: WdfIoTargetCreate failed 0x%x\n", status)); return status; } // // Open it up, write access only! // WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME( &openParams, &DeviceContext->PdoName, FILE_WRITE_ACCESS); // // We will let the framework to respond automatically to the pnp // state changes of the target by closing and opening the handle. // openParams.ShareAccess = FILE_SHARE_WRITE | FILE_SHARE_READ; status = WdfIoTargetOpen(hidTarget, &openParams); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: WdfIoTargetOpen failed 0x%x\n", status)); goto ExitAndFree; } WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&outputDescriptor, (PVOID) &collectionInformation, sizeof(HID_COLLECTION_INFORMATION)); // // Now get the collection information for this device // status = WdfIoTargetSendIoctlSynchronously(hidTarget, NULL, IOCTL_HID_GET_COLLECTION_INFORMATION, NULL, &outputDescriptor, NULL, NULL); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: WdfIoTargetSendIoctlSynchronously failed 0x%x\n", status)); goto ExitAndFree; } preparsedData = (PHIDP_PREPARSED_DATA) ExAllocatePoolWithTag( NonPagedPool, collectionInformation.DescriptorSize, 'ffly'); if (preparsedData == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto ExitAndFree; } WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&outputDescriptor, (PVOID) preparsedData, collectionInformation.DescriptorSize); status = WdfIoTargetSendIoctlSynchronously(hidTarget, NULL, IOCTL_HID_GET_COLLECTION_DESCRIPTOR, NULL, &outputDescriptor, NULL, NULL); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: WdfIoTargetSendIoctlSynchronously failed 0x%x\n", status)); goto ExitAndFree; } // // Now get the capabilities. // RtlZeroMemory(&caps, sizeof(HIDP_CAPS)); status = HidP_GetCaps(preparsedData, &caps); if (!NT_SUCCESS(status)) { goto ExitAndFree; } // // Create a report to send to the device. // report = (PCHAR) ExAllocatePoolWithTag( NonPagedPool, caps.FeatureReportByteLength, 'ffly'); if (report == NULL) { goto ExitAndFree; } // // Start with a zeroed report. If we are disabling the feature, this might // be all we need to do. // RtlZeroMemory(report, caps.FeatureReportByteLength); status = STATUS_SUCCESS; if (EnableFeature) { // // Edit the report to reflect the enabled feature // usage = FeatureId; usageLength = 1; status = HidP_SetUsages( HidP_Feature, PageId, 0, &usage, // pointer to the usage list &usageLength, // number of usages in the usage list preparsedData, report, caps.FeatureReportByteLength ); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: HidP_SetUsages failed 0x%x\n", status)); goto ExitAndFree; } } WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&inputDescriptor, report, caps.FeatureReportByteLength); status = WdfIoTargetSendIoctlSynchronously(hidTarget, NULL, IOCTL_HID_SET_FEATURE, &inputDescriptor, NULL, NULL, NULL); if (!NT_SUCCESS(status)) { KdPrint(("FireFly: WdfIoTargetSendIoctlSynchronously failed 0x%x\n", status)); goto ExitAndFree; } ExitAndFree: if (preparsedData != NULL) { ExFreePool(preparsedData); preparsedData = NULL; } if (report != NULL) { ExFreePool(report); report = NULL; } if (hidTarget != NULL) { WdfObjectDelete(hidTarget); } return status; }