| /* |
| * HID descriptor parsing |
| * |
| * Copyright (C) 2015 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 "config.h" |
| |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #define NONAMELESSUNION |
| #include "hid.h" |
| |
| #include "wine/debug.h" |
| #include "wine/list.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(hid); |
| |
| #define USAGE_MAX 10 |
| |
| /* Flags that are defined in the document |
| "Device Class Definition for Human Interface Devices" */ |
| enum { |
| INPUT_DATA_CONST = 0x01, /* Data (0) | Constant (1) */ |
| INPUT_ARRAY_VAR = 0x02, /* Array (0) | Variable (1) */ |
| INPUT_ABS_REL = 0x04, /* Absolute (0) | Relative (1) */ |
| INPUT_WRAP = 0x08, /* No Wrap (0) | Wrap (1) */ |
| INPUT_LINEAR = 0x10, /* Linear (0) | Non Linear (1) */ |
| INPUT_PREFSTATE = 0x20, /* Preferred State (0) | No Preferred (1) */ |
| INPUT_NULL = 0x40, /* No Null position (0) | Null state(1) */ |
| INPUT_VOLATILE = 0x80, /* Non Volatile (0) | Volatile (1) */ |
| INPUT_BITFIELD = 0x100 /* Bit Field (0) | Buffered Bytes (1) */ |
| }; |
| |
| enum { |
| TAG_TYPE_MAIN = 0x0, |
| TAG_TYPE_GLOBAL, |
| TAG_TYPE_LOCAL, |
| TAG_TYPE_RESERVED, |
| }; |
| |
| enum { |
| TAG_MAIN_INPUT = 0x08, |
| TAG_MAIN_OUTPUT = 0x09, |
| TAG_MAIN_FEATURE = 0x0B, |
| TAG_MAIN_COLLECTION = 0x0A, |
| TAG_MAIN_END_COLLECTION = 0x0C |
| }; |
| |
| enum { |
| TAG_GLOBAL_USAGE_PAGE = 0x0, |
| TAG_GLOBAL_LOGICAL_MINIMUM, |
| TAG_GLOBAL_LOGICAL_MAXIMUM, |
| TAG_GLOBAL_PHYSICAL_MINIMUM, |
| TAG_GLOBAL_PHYSICAL_MAXIMUM, |
| TAG_GLOBAL_UNIT_EXPONENT, |
| TAG_GLOBAL_UNIT, |
| TAG_GLOBAL_REPORT_SIZE, |
| TAG_GLOBAL_REPORT_ID, |
| TAG_GLOBAL_REPORT_COUNT, |
| TAG_GLOBAL_PUSH, |
| TAG_GLOBAL_POP |
| }; |
| |
| enum { |
| TAG_LOCAL_USAGE = 0x0, |
| TAG_LOCAL_USAGE_MINIMUM, |
| TAG_LOCAL_USAGE_MAXIMUM, |
| TAG_LOCAL_DESIGNATOR_INDEX, |
| TAG_LOCAL_DESIGNATOR_MINIMUM, |
| TAG_LOCAL_DESIGNATOR_MAXIMUM, |
| TAG_LOCAL_STRING_INDEX, |
| TAG_LOCAL_STRING_MINIMUM, |
| TAG_LOCAL_STRING_MAXIMUM, |
| TAG_LOCAL_DELIMITER |
| }; |
| |
| |
| static const char* const feature_string[] = |
| { "Input", "Output", "Feature" }; |
| |
| struct caps { |
| USAGE UsagePage; |
| LONG LogicalMin; |
| LONG LogicalMax; |
| LONG PhysicalMin; |
| LONG PhysicalMax; |
| ULONG UnitsExp; |
| ULONG Units; |
| USHORT BitSize; |
| UCHAR ReportID; |
| USHORT ReportCount; |
| |
| BOOLEAN IsRange; |
| BOOLEAN IsStringRange; |
| BOOLEAN IsDesignatorRange; |
| unsigned int usage_count; |
| union { |
| struct { |
| USAGE UsageMin; |
| USAGE UsageMax; |
| USHORT StringMin; |
| USHORT StringMax; |
| USHORT DesignatorMin; |
| USHORT DesignatorMax; |
| } Range; |
| struct { |
| USAGE Usage[USAGE_MAX]; |
| USAGE Reserved1; |
| USHORT StringIndex; |
| USHORT Reserved2; |
| USHORT DesignatorIndex; |
| USHORT Reserved3; |
| } NotRange; |
| } DUMMYUNIONNAME; |
| |
| int Delim; |
| }; |
| |
| struct feature { |
| struct list entry; |
| struct list col_entry; |
| struct caps caps; |
| |
| HIDP_REPORT_TYPE type; |
| BOOLEAN isData; |
| BOOLEAN isArray; |
| BOOLEAN IsAbsolute; |
| BOOLEAN Wrap; |
| BOOLEAN Linear; |
| BOOLEAN prefState; |
| BOOLEAN HasNull; |
| BOOLEAN Volatile; |
| BOOLEAN BitField; |
| |
| unsigned int index; |
| struct collection *collection; |
| }; |
| |
| static const char* const collection_string[] = { |
| "Physical", |
| "Application", |
| "Logical", |
| "Report", |
| "Named Array", |
| "Usage Switch", |
| "Usage Modifier", |
| }; |
| |
| struct collection { |
| struct list entry; |
| struct caps caps; |
| unsigned int index; |
| unsigned int type; |
| struct collection *parent; |
| struct list features; |
| struct list collections; |
| }; |
| |
| struct caps_stack { |
| struct list entry; |
| struct caps caps; |
| }; |
| |
| static const char* debugstr_usages(struct caps *caps) |
| { |
| if (!caps->IsRange) |
| { |
| char out[12 * USAGE_MAX]; |
| unsigned int i; |
| if (caps->usage_count == 0) |
| return "[ none ]"; |
| out[0] = 0; |
| for (i = 0; i < caps->usage_count; i++) |
| sprintf(out + strlen(out), "0x%x ", caps->u.NotRange.Usage[i]); |
| return wine_dbg_sprintf("[ %s] ", out); |
| } |
| else |
| return wine_dbg_sprintf("[0x%x - 0x%x]", caps->u.Range.UsageMin, caps->u.Range.UsageMax); |
| } |
| |
| static const char* debugstr_stringindex(struct caps *caps) |
| { |
| if (!caps->IsStringRange) |
| return wine_dbg_sprintf("%i", caps->u.NotRange.StringIndex); |
| else |
| return wine_dbg_sprintf("[%i - %i]", caps->u.Range.StringMin, caps->u.Range.StringMax); |
| } |
| |
| static const char* debugstr_designatorindex(struct caps *caps) |
| { |
| if (!caps->IsDesignatorRange) |
| return wine_dbg_sprintf("%i", caps->u.NotRange.DesignatorIndex); |
| else |
| return wine_dbg_sprintf("[%i - %i]", caps->u.Range.DesignatorMin, caps->u.Range.DesignatorMax); |
| } |
| |
| static void debugstr_caps(const char* type, struct caps *caps) |
| { |
| if (!caps) |
| return; |
| TRACE("(%s Caps: UsagePage 0x%x; LogicalMin %i; LogicalMax %i; PhysicalMin %i; PhysicalMax %i; UnitsExp %i; Units %i; BitSize %i; ReportID %i; ReportCount %i; Usage %s; StringIndex %s; DesignatorIndex %s; Delim %i;)\n", |
| type, |
| caps->UsagePage, |
| caps->LogicalMin, |
| caps->LogicalMax, |
| caps->PhysicalMin, |
| caps->PhysicalMax, |
| caps->UnitsExp, |
| caps->Units, |
| caps->BitSize, |
| caps->ReportID, |
| caps->ReportCount, |
| debugstr_usages(caps), |
| debugstr_stringindex(caps), |
| debugstr_designatorindex(caps), |
| caps->Delim); |
| } |
| |
| static void debug_feature(struct feature *feature) |
| { |
| if (!feature) |
| return; |
| TRACE("[Feature type %s [%i]; %s; %s; %s; %s; %s; %s; %s; %s; %s]\n", |
| feature_string[feature->type], |
| feature->index, |
| (feature->isData)?"Data":"Const", |
| (feature->isArray)?"Array":"Var", |
| (feature->IsAbsolute)?"Abs":"Rel", |
| (feature->Wrap)?"Wrap":"NoWrap", |
| (feature->Linear)?"Linear":"NonLinear", |
| (feature->prefState)?"PrefStat":"NoPrefState", |
| (feature->HasNull)?"HasNull":"NoNull", |
| (feature->Volatile)?"Volatile":"NonVolatile", |
| (feature->BitField)?"BitField":"Buffered"); |
| |
| debugstr_caps("Feature", &feature->caps); |
| } |
| |
| static void debug_collection(struct collection *collection) |
| { |
| struct feature *fentry; |
| struct collection *centry; |
| if (TRACE_ON(hid)) |
| { |
| TRACE("START Collection %i <<< %s, parent: %p, %i features, %i collections\n", collection->index, collection_string[collection->type], collection->parent, list_count(&collection->features), list_count(&collection->collections)); |
| debugstr_caps("Collection", &collection->caps); |
| LIST_FOR_EACH_ENTRY(fentry, &collection->features, struct feature, col_entry) |
| debug_feature(fentry); |
| LIST_FOR_EACH_ENTRY(centry, &collection->collections, struct collection, entry) |
| debug_collection(centry); |
| TRACE(">>> END Collection %i\n", collection->index); |
| } |
| } |
| |
| static void debug_print_button_cap(const CHAR * type, WINE_HID_ELEMENT *wine_element) |
| { |
| if (!wine_element->caps.button.IsRange) |
| TRACE("%s Button: 0x%x/0x%04x: ReportId %i, startBit %i/1\n" , type, |
| wine_element->caps.button.UsagePage, |
| wine_element->caps.button.u.NotRange.Usage, |
| wine_element->caps.value.ReportID, |
| wine_element->valueStartBit); |
| else |
| TRACE("%s Button: 0x%x/[0x%04x-0x%04x]: ReportId %i, startBit %i/%i\n" ,type, |
| wine_element->caps.button.UsagePage, |
| wine_element->caps.button.u.Range.UsageMin, |
| wine_element->caps.button.u.Range.UsageMax, |
| wine_element->caps.value.ReportID, |
| wine_element->valueStartBit, |
| wine_element->bitCount); |
| } |
| |
| static void debug_print_value_cap(const CHAR * type, WINE_HID_ELEMENT *wine_element) |
| { |
| TRACE("%s Value: 0x%x/0x%x: ReportId %i, IsAbsolute %i, HasNull %i, " |
| "Bit Size %i, ReportCount %i, UnitsExp %i, Units %i, " |
| "LogicalMin %i, Logical Max %i, PhysicalMin %i, " |
| "PhysicalMax %i -- StartBit %i/%i\n", type, |
| wine_element->caps.value.UsagePage, |
| wine_element->caps.value.u.NotRange.Usage, |
| wine_element->caps.value.ReportID, |
| wine_element->caps.value.IsAbsolute, |
| wine_element->caps.value.HasNull, |
| wine_element->caps.value.BitSize, |
| wine_element->caps.value.ReportCount, |
| wine_element->caps.value.UnitsExp, |
| wine_element->caps.value.Units, |
| wine_element->caps.value.LogicalMin, |
| wine_element->caps.value.LogicalMax, |
| wine_element->caps.value.PhysicalMin, |
| wine_element->caps.value.PhysicalMax, |
| wine_element->valueStartBit, |
| wine_element->bitCount); |
| } |
| |
| static void debug_print_element(const CHAR* type, WINE_HID_ELEMENT *wine_element) |
| { |
| if (wine_element->ElementType == ButtonElement) |
| debug_print_button_cap(type, wine_element); |
| else if (wine_element->ElementType == ValueElement) |
| debug_print_value_cap(type, wine_element); |
| else |
| TRACE("%s: UNKNOWN\n", type); |
| } |
| |
| static void debug_print_report(const char* type, WINE_HID_REPORT *report) |
| { |
| unsigned int i; |
| TRACE("START Report %i <<< %s report : dwSize: %i elementCount: %i\n", |
| report->reportID, |
| type, |
| report->dwSize, |
| report->elementCount); |
| for (i = 0; i < report->elementCount; i++) |
| debug_print_element(type, &report->Elements[i]); |
| TRACE(">>> END Report %i\n",report->reportID); |
| } |
| |
| static void debug_print_preparsed(WINE_HIDP_PREPARSED_DATA *data) |
| { |
| unsigned int i; |
| WINE_HID_REPORT *r; |
| if (TRACE_ON(hid)) |
| { |
| TRACE("START PREPARSED Data <<< dwSize: %i Usage: %i, UsagePage: %i, InputReportByteLength: %i, tOutputReportByteLength: %i, FeatureReportByteLength: %i, NumberLinkCollectionNodes: %i, NumberInputButtonCaps: %i, NumberInputValueCaps: %i,NumberInputDataIndices: %i, NumberOutputButtonCaps: %i, NumberOutputValueCaps: %i, NumberOutputDataIndices: %i, NumberFeatureButtonCaps: %i, NumberFeatureValueCaps: %i, NumberFeatureDataIndices: %i, dwInputReportCount: %i, dwOutputReportCount: %i, dwFeatureReportCount: %i, dwOutputReportOffset: %i, dwFeatureReportOffset: %i\n", |
| data->dwSize, |
| data->caps.Usage, |
| data->caps.UsagePage, |
| data->caps.InputReportByteLength, |
| data->caps.OutputReportByteLength, |
| data->caps.FeatureReportByteLength, |
| data->caps.NumberLinkCollectionNodes, |
| data->caps.NumberInputButtonCaps, |
| data->caps.NumberInputValueCaps, |
| data->caps.NumberInputDataIndices, |
| data->caps.NumberOutputButtonCaps, |
| data->caps.NumberOutputValueCaps, |
| data->caps.NumberOutputDataIndices, |
| data->caps.NumberFeatureButtonCaps, |
| data->caps.NumberFeatureValueCaps, |
| data->caps.NumberFeatureDataIndices, |
| data->dwInputReportCount, |
| data->dwOutputReportCount, |
| data->dwFeatureReportCount, |
| data->dwOutputReportOffset, |
| data->dwFeatureReportOffset); |
| |
| r = HID_INPUT_REPORTS(data); |
| for (i = 0; i < data->dwInputReportCount; i++) |
| { |
| debug_print_report("INPUT", r); |
| r = HID_NEXT_REPORT(data, r); |
| } |
| r = HID_OUTPUT_REPORTS(data); |
| for (i = 0; i < data->dwOutputReportCount; i++) |
| { |
| debug_print_report("OUTPUT", r); |
| r = HID_NEXT_REPORT(data, r); |
| } |
| r = HID_FEATURE_REPORTS(data); |
| for (i = 0; i < data->dwFeatureReportCount; i++) |
| { |
| debug_print_report("FEATURE", r); |
| r = HID_NEXT_REPORT(data, r); |
| } |
| TRACE(">>> END Preparsed Data\n"); |
| } |
| } |
| |
| static int getValue(int bsize, int source, BOOL allow_negative) |
| { |
| int mask = 0xff; |
| int negative = 0x80; |
| int outofrange = 0x100; |
| int value; |
| unsigned int i; |
| |
| if (bsize == 4) |
| return source; |
| |
| for (i = 1; i < bsize; i++) |
| { |
| mask = (mask<<8) + 0xff; |
| negative = (negative<<8); |
| outofrange = (outofrange<<8); |
| } |
| value = (source&mask); |
| if (allow_negative && value&negative) |
| value = -1 * (outofrange - value); |
| return value; |
| } |
| |
| static void parse_io_feature(unsigned int bSize, int itemVal, int bTag, |
| unsigned int *feature_index, |
| struct feature *feature) |
| { |
| if (bSize == 0) |
| { |
| return; |
| } |
| else |
| { |
| feature->isData = ((itemVal & INPUT_DATA_CONST) == 0); |
| feature->isArray = ((itemVal & INPUT_ARRAY_VAR) == 0); |
| feature->IsAbsolute = ((itemVal & INPUT_ABS_REL) == 0); |
| feature->Wrap = ((itemVal & INPUT_WRAP) != 0); |
| feature->Linear = ((itemVal & INPUT_LINEAR) == 0); |
| feature->prefState = ((itemVal & INPUT_PREFSTATE) == 0); |
| feature->HasNull = ((itemVal & INPUT_NULL) != 0); |
| |
| if (bTag != TAG_MAIN_INPUT) |
| { |
| feature->Volatile = ((itemVal & INPUT_VOLATILE) != 0); |
| } |
| if (bSize > 1) |
| { |
| feature->BitField = ((itemVal & INPUT_BITFIELD) == 0); |
| } |
| feature->index = *feature_index; |
| *feature_index = *feature_index + 1; |
| } |
| } |
| |
| static void parse_collection(unsigned int bSize, int itemVal, |
| struct collection *collection) |
| { |
| if (bSize <= 0) |
| return; |
| else |
| { |
| collection->type = itemVal; |
| |
| if (itemVal >= 0x07 && itemVal <= 0x7F) { |
| ERR(" (Reserved 0x%x )\n", itemVal); |
| } |
| else if (itemVal >= 0x80 && itemVal <= 0xFF) { |
| ERR(" (Vendor Defined 0x%x )\n", itemVal); |
| } |
| } |
| } |
| |
| static void new_caps(struct caps *caps) |
| { |
| caps->IsRange = 0; |
| caps->IsStringRange = 0; |
| caps->IsDesignatorRange = 0; |
| caps->usage_count = 0; |
| } |
| |
| static int parse_descriptor(BYTE *descriptor, unsigned int index, unsigned int length, |
| unsigned int *feature_index, unsigned int *collection_index, |
| struct collection *collection, struct caps *caps, |
| struct list *features, struct list *stack) |
| { |
| unsigned int i; |
| for (i = index; i < length;) |
| { |
| BYTE b0 = descriptor[i++]; |
| int bSize = b0 & 0x03; |
| int bType = (b0 >> 2) & 0x03; |
| int bTag = (b0 >> 4) & 0x0F; |
| |
| bSize = (bSize == 3) ? 4 : bSize; |
| if (bType == TAG_TYPE_RESERVED && bTag == 0x0F && bSize == 2 && |
| i + 2 < length) |
| { |
| /* Long data items: Should be unused */ |
| ERR("Long Data Item, should be unused\n"); |
| } |
| else |
| { |
| int bSizeActual = 0; |
| int itemVal = 0; |
| unsigned int j; |
| |
| for (j = 0; j < bSize; j++) |
| { |
| if (i + j < length) |
| { |
| itemVal += descriptor[i + j] << (8 * j); |
| bSizeActual++; |
| } |
| } |
| TRACE(" 0x%x[%i], type %i , tag %i, size %i, val %i\n",b0,i-1,bType, bTag, bSize, itemVal ); |
| |
| if (bType == TAG_TYPE_MAIN) |
| { |
| struct feature *feature; |
| switch(bTag) |
| { |
| case TAG_MAIN_INPUT: |
| feature = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*feature)); |
| list_add_tail(&collection->features, &feature->col_entry); |
| list_add_tail(features, &feature->entry); |
| feature->type = HidP_Input; |
| parse_io_feature(bSize, itemVal, bTag, feature_index, feature); |
| feature->caps = *caps; |
| feature->collection = collection; |
| new_caps(caps); |
| break; |
| case TAG_MAIN_OUTPUT: |
| feature = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*feature)); |
| list_add_tail(&collection->features, &feature->col_entry); |
| list_add_tail(features, &feature->entry); |
| feature->type = HidP_Output; |
| parse_io_feature(bSize, itemVal, bTag, feature_index, feature); |
| feature->caps = *caps; |
| feature->collection = collection; |
| new_caps(caps); |
| break; |
| case TAG_MAIN_FEATURE: |
| feature = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*feature)); |
| list_add_tail(&collection->features, &feature->col_entry); |
| list_add_tail(features, &feature->entry); |
| feature->type = HidP_Feature; |
| parse_io_feature(bSize, itemVal, bTag, feature_index, feature); |
| feature->caps = *caps; |
| feature->collection = collection; |
| new_caps(caps); |
| break; |
| case TAG_MAIN_COLLECTION: |
| { |
| struct collection *subcollection = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct collection)); |
| list_add_tail(&collection->collections, &subcollection->entry); |
| subcollection->parent = collection; |
| /* Only set our collection once... |
| We do not properly handle composite devices yet. */ |
| if (*collection_index == 0) |
| collection->caps = *caps; |
| subcollection->caps = *caps; |
| subcollection->index = *collection_index; |
| *collection_index = *collection_index + 1; |
| list_init(&subcollection->features); |
| list_init(&subcollection->collections); |
| new_caps(caps); |
| |
| parse_collection(bSize, itemVal, subcollection); |
| |
| i = parse_descriptor(descriptor, i+1, length, feature_index, collection_index, subcollection, caps, features, stack); |
| continue; |
| } |
| case TAG_MAIN_END_COLLECTION: |
| return i; |
| default: |
| ERR("Unknown (bTag: 0x%x, bType: 0x%x)\n", bTag, bType); |
| } |
| } |
| else if (bType == TAG_TYPE_GLOBAL) |
| { |
| switch(bTag) |
| { |
| case TAG_GLOBAL_USAGE_PAGE: |
| caps->UsagePage = getValue(bSize, itemVal, FALSE); |
| break; |
| case TAG_GLOBAL_LOGICAL_MINIMUM: |
| caps->LogicalMin = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_LOGICAL_MAXIMUM: |
| caps->LogicalMax = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_PHYSICAL_MINIMUM: |
| caps->PhysicalMin = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_PHYSICAL_MAXIMUM: |
| caps->PhysicalMax = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_UNIT_EXPONENT: |
| caps->UnitsExp = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_UNIT: |
| caps->Units = getValue(bSize, itemVal, TRUE); |
| break; |
| case TAG_GLOBAL_REPORT_SIZE: |
| caps->BitSize = getValue(bSize, itemVal, FALSE); |
| break; |
| case TAG_GLOBAL_REPORT_ID: |
| caps->ReportID = getValue(bSize, itemVal, FALSE); |
| break; |
| case TAG_GLOBAL_REPORT_COUNT: |
| caps->ReportCount = getValue(bSize, itemVal, FALSE); |
| break; |
| case TAG_GLOBAL_PUSH: |
| { |
| struct caps_stack *saved = HeapAlloc(GetProcessHeap(), 0, sizeof(*saved)); |
| saved->caps = *caps; |
| TRACE("Push\n"); |
| list_add_tail(stack, &saved->entry); |
| break; |
| } |
| case TAG_GLOBAL_POP: |
| { |
| struct list *tail; |
| struct caps_stack *saved; |
| TRACE("Pop\n"); |
| tail = list_tail(stack); |
| if (tail) |
| { |
| saved = LIST_ENTRY(tail, struct caps_stack, entry); |
| *caps = saved->caps; |
| list_remove(tail); |
| HeapFree(GetProcessHeap(), 0, saved); |
| } |
| else |
| ERR("Pop but no stack!\n"); |
| break; |
| } |
| default: |
| ERR("Unknown (bTag: 0x%x, bType: 0x%x)\n", bTag, bType); |
| } |
| } |
| else if (bType == TAG_TYPE_LOCAL) |
| { |
| switch(bTag) |
| { |
| case TAG_LOCAL_USAGE: |
| if (caps->usage_count >= USAGE_MAX) |
| FIXME("More than %i individual usages defined\n",USAGE_MAX); |
| else |
| { |
| caps->u.NotRange.Usage[caps->usage_count++] = getValue(bSize, itemVal, FALSE); |
| caps->IsRange = FALSE; |
| } |
| break; |
| case TAG_LOCAL_USAGE_MINIMUM: |
| caps->usage_count = 1; |
| caps->u.Range.UsageMin = getValue(bSize, itemVal, FALSE); |
| caps->IsRange = TRUE; |
| break; |
| case TAG_LOCAL_USAGE_MAXIMUM: |
| caps->usage_count = 1; |
| caps->u.Range.UsageMax = getValue(bSize, itemVal, FALSE); |
| caps->IsRange = TRUE; |
| break; |
| case TAG_LOCAL_DESIGNATOR_INDEX: |
| caps->u.NotRange.DesignatorIndex = getValue(bSize, itemVal, FALSE); |
| caps->IsDesignatorRange = FALSE; |
| break; |
| case TAG_LOCAL_DESIGNATOR_MINIMUM: |
| caps->u.Range.DesignatorMin = getValue(bSize, itemVal, FALSE); |
| caps->IsDesignatorRange = TRUE; |
| break; |
| case TAG_LOCAL_DESIGNATOR_MAXIMUM: |
| caps->u.Range.DesignatorMax = getValue(bSize, itemVal, FALSE); |
| caps->IsDesignatorRange = TRUE; |
| break; |
| case TAG_LOCAL_STRING_INDEX: |
| caps->u.NotRange.StringIndex = getValue(bSize, itemVal, FALSE); |
| caps->IsStringRange = FALSE; |
| break; |
| case TAG_LOCAL_STRING_MINIMUM: |
| caps->u.Range.StringMin = getValue(bSize, itemVal, FALSE); |
| caps->IsStringRange = TRUE; |
| break; |
| case TAG_LOCAL_STRING_MAXIMUM: |
| caps->u.Range.StringMax = getValue(bSize, itemVal, FALSE); |
| caps->IsStringRange = TRUE; |
| break; |
| case TAG_LOCAL_DELIMITER: |
| caps->Delim = getValue(bSize, itemVal, FALSE); |
| break; |
| default: |
| ERR("Unknown (bTag: 0x%x, bType: 0x%x)\n", bTag, bType); |
| } |
| } |
| else |
| ERR("Unknown (bTag: 0x%x, bType: 0x%x)\n", bTag, bType); |
| |
| i += bSize; |
| } |
| } |
| return i; |
| } |
| |
| static inline void new_report(WINE_HID_REPORT *wine_report, struct feature* feature) |
| { |
| wine_report->reportID = feature->caps.ReportID; |
| wine_report->dwSize = sizeof(*wine_report) - sizeof(WINE_HID_ELEMENT); |
| wine_report->elementCount = 0; |
| } |
| |
| static void build_elements(WINE_HID_REPORT *wine_report, struct feature* feature, DWORD *bitOffset, unsigned int *data_index) |
| { |
| unsigned int i; |
| |
| if (!feature->isData) |
| { |
| *bitOffset = *bitOffset + (feature->caps.BitSize * feature->caps.ReportCount); |
| return; |
| } |
| |
| for (i = 0; i < feature->caps.usage_count; i++) |
| { |
| WINE_HID_ELEMENT *wine_element = &wine_report->Elements[wine_report->elementCount]; |
| |
| wine_element->valueStartBit = *bitOffset; |
| if (feature->caps.UsagePage == HID_USAGE_PAGE_BUTTON) |
| { |
| wine_element->ElementType = ButtonElement; |
| wine_element->caps.button.UsagePage = feature->caps.UsagePage; |
| wine_element->caps.button.ReportID = feature->caps.ReportID; |
| wine_element->caps.button.BitField = feature->BitField; |
| wine_element->caps.button.IsRange = feature->caps.IsRange; |
| wine_element->caps.button.IsStringRange = feature->caps.IsStringRange; |
| wine_element->caps.button.IsDesignatorRange = feature->caps.IsDesignatorRange; |
| wine_element->caps.button.IsAbsolute = feature->IsAbsolute; |
| if (wine_element->caps.button.IsRange) |
| { |
| wine_element->bitCount = (feature->caps.u.Range.UsageMax - feature->caps.u.Range.UsageMin) + 1; |
| *bitOffset = *bitOffset + wine_element->bitCount; |
| wine_element->caps.button.u.Range.UsageMin = feature->caps.u.Range.UsageMin; |
| wine_element->caps.button.u.Range.UsageMax = feature->caps.u.Range.UsageMax; |
| wine_element->caps.button.u.Range.StringMin = feature->caps.u.Range.StringMin; |
| wine_element->caps.button.u.Range.StringMax = feature->caps.u.Range.StringMax; |
| wine_element->caps.button.u.Range.DesignatorMin = feature->caps.u.Range.DesignatorMin; |
| wine_element->caps.button.u.Range.DesignatorMax = feature->caps.u.Range.DesignatorMax; |
| wine_element->caps.button.u.Range.DataIndexMin = *data_index; |
| wine_element->caps.button.u.Range.DataIndexMax = *data_index + wine_element->bitCount - 1; |
| *data_index = *data_index + wine_element->bitCount; |
| } |
| else |
| { |
| *bitOffset = *bitOffset + 1; |
| wine_element->bitCount = 1; |
| wine_element->caps.button.u.NotRange.Usage = feature->caps.u.NotRange.Usage[i]; |
| wine_element->caps.button.u.NotRange.StringIndex = feature->caps.u.NotRange.StringIndex; |
| wine_element->caps.button.u.NotRange.DesignatorIndex = feature->caps.u.NotRange.DesignatorIndex; |
| wine_element->caps.button.u.NotRange.DataIndex = *data_index; |
| *data_index = *data_index + 1; |
| } |
| } |
| else |
| { |
| wine_element->ElementType = ValueElement; |
| wine_element->caps.value.UsagePage = feature->caps.UsagePage; |
| wine_element->caps.value.ReportID = feature->caps.ReportID; |
| wine_element->caps.value.BitField = feature->BitField; |
| wine_element->caps.value.IsRange = feature->caps.IsRange; |
| wine_element->caps.value.IsStringRange = feature->caps.IsStringRange; |
| wine_element->caps.value.IsDesignatorRange = feature->caps.IsDesignatorRange; |
| wine_element->caps.value.IsAbsolute = feature->IsAbsolute; |
| wine_element->caps.value.HasNull = feature->HasNull; |
| wine_element->caps.value.BitSize = feature->caps.BitSize; |
| if (feature->caps.usage_count > 1) |
| { |
| if (feature->caps.ReportCount > feature->caps.usage_count) |
| wine_element->caps.value.ReportCount = feature->caps.ReportCount / feature->caps.usage_count; |
| else |
| wine_element->caps.value.ReportCount = 1; |
| } |
| else |
| wine_element->caps.value.ReportCount = feature->caps.ReportCount; |
| wine_element->bitCount = (feature->caps.BitSize * wine_element->caps.value.ReportCount); |
| *bitOffset = *bitOffset + wine_element->bitCount; |
| wine_element->caps.value.UnitsExp = feature->caps.UnitsExp; |
| wine_element->caps.value.Units = feature->caps.Units; |
| wine_element->caps.value.LogicalMin = feature->caps.LogicalMin; |
| wine_element->caps.value.LogicalMax = feature->caps.LogicalMax; |
| wine_element->caps.value.PhysicalMin = feature->caps.PhysicalMin; |
| wine_element->caps.value.PhysicalMax = feature->caps.PhysicalMax; |
| if (wine_element->caps.value.IsRange) |
| { |
| wine_element->caps.value.u.Range.UsageMin = feature->caps.u.Range.UsageMin; |
| wine_element->caps.value.u.Range.UsageMax = feature->caps.u.Range.UsageMax; |
| wine_element->caps.value.u.Range.StringMin = feature->caps.u.Range.StringMin; |
| wine_element->caps.value.u.Range.StringMax = feature->caps.u.Range.StringMax; |
| wine_element->caps.value.u.Range.DesignatorMin = feature->caps.u.Range.DesignatorMin; |
| wine_element->caps.value.u.Range.DesignatorMax = feature->caps.u.Range.DesignatorMax; |
| wine_element->caps.value.u.Range.DataIndexMin = *data_index; |
| wine_element->caps.value.u.Range.DataIndexMax = *data_index + |
| (wine_element->caps.value.u.Range.UsageMax - |
| wine_element->caps.value.u.Range.UsageMin); |
| *data_index = *data_index + |
| (wine_element->caps.value.u.Range.UsageMax - |
| wine_element->caps.value.u.Range.UsageMin) + 1; |
| } |
| else |
| { |
| wine_element->caps.value.u.NotRange.Usage = feature->caps.u.NotRange.Usage[i]; |
| wine_element->caps.value.u.NotRange.StringIndex = feature->caps.u.NotRange.StringIndex; |
| wine_element->caps.value.u.NotRange.DesignatorIndex = feature->caps.u.NotRange.DesignatorIndex; |
| wine_element->caps.value.u.NotRange.DataIndex = *data_index; |
| *data_index = *data_index + 1; |
| } |
| } |
| |
| wine_report->elementCount++; |
| } |
| } |
| |
| static void count_elements(struct feature* feature, USHORT *buttons, USHORT *values) |
| { |
| if (feature->caps.UsagePage == HID_USAGE_PAGE_BUTTON) |
| { |
| if (feature->caps.IsRange) |
| *buttons = *buttons + 1; |
| else |
| *buttons = *buttons + feature->caps.usage_count; |
| } |
| else |
| { |
| if (feature->caps.IsRange) |
| *values = *values + 1; |
| else |
| *values = *values + feature->caps.usage_count; |
| } |
| } |
| |
| static WINE_HIDP_PREPARSED_DATA* build_PreparseData( |
| struct feature **features, int feature_count, |
| struct feature **input_features, int i_count, |
| struct feature **output_features, int o_count, |
| struct feature **feature_features, int f_count, |
| struct collection *base_collection) |
| { |
| WINE_HIDP_PREPARSED_DATA *data; |
| WINE_HID_REPORT *wine_report = NULL; |
| DWORD bitOffset, bitLength; |
| unsigned int report_count = 1; |
| unsigned int i; |
| unsigned int element_count; |
| unsigned int size = 0; |
| unsigned int data_index; |
| |
| if (features[0]->caps.ReportID != 0) |
| { |
| unsigned int *report_ids; |
| unsigned int cnt = max(i_count, o_count); |
| cnt = max(cnt, f_count); |
| report_ids = HeapAlloc(GetProcessHeap(), 0 , sizeof(*report_ids) * cnt); |
| |
| if (i_count) |
| { |
| report_ids[0] = input_features[0]->caps.ReportID; |
| for (i = 1; i < i_count; i++) |
| { |
| unsigned int j; |
| unsigned int found = FALSE; |
| for (j = 0; !found && j < i_count; j++) |
| { |
| if (report_ids[j] == input_features[i]->caps.ReportID) |
| found = TRUE; |
| } |
| if (!found) |
| { |
| report_ids[report_count] = input_features[i]->caps.ReportID; |
| report_count++; |
| } |
| } |
| } |
| if (o_count) |
| { |
| report_count++; |
| report_ids[0] = output_features[0]->caps.ReportID; |
| for (i = 1; i < o_count; i++) |
| { |
| unsigned int j; |
| unsigned int found = FALSE; |
| for (j = 0; !found && j < o_count; j++) |
| { |
| if (report_ids[j] == output_features[i]->caps.ReportID) |
| found = TRUE; |
| } |
| if (!found) |
| { |
| report_ids[report_count] = output_features[i]->caps.ReportID; |
| report_count++; |
| } |
| } |
| } |
| if (f_count) |
| { |
| report_count++; |
| report_ids[0] = feature_features[0]->caps.ReportID; |
| for (i = 1; i < f_count; i++) |
| { |
| unsigned int j; |
| unsigned int found = FALSE; |
| for (j = 0; !found && j < f_count; j++) |
| { |
| if (report_ids[j] == feature_features[i]->caps.ReportID) |
| found = TRUE; |
| } |
| if (!found) |
| { |
| report_ids[report_count] = feature_features[i]->caps.ReportID; |
| report_count++; |
| } |
| } |
| } |
| HeapFree(GetProcessHeap(), 0, report_ids); |
| } |
| else |
| { |
| if (o_count) report_count++; |
| if (f_count) report_count++; |
| } |
| |
| element_count = 0; |
| for (i = 0; i < feature_count; i++) |
| element_count += features[i]->caps.usage_count; |
| |
| size = sizeof(WINE_HIDP_PREPARSED_DATA) + |
| (element_count * sizeof(WINE_HID_ELEMENT)) + |
| (report_count * sizeof(WINE_HID_REPORT)); |
| |
| TRACE("%i reports %i elements -> size %i\n",report_count, element_count, size); |
| |
| data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); |
| data->magic = HID_MAGIC; |
| data->dwSize = size; |
| data->caps.Usage = base_collection->caps.u.NotRange.Usage[0]; |
| data->caps.UsagePage = base_collection->caps.UsagePage; |
| |
| wine_report = data->InputReports; |
| if (i_count) |
| { |
| data_index = 0; |
| bitLength = 0; |
| new_report(wine_report, input_features[0]); |
| data->dwInputReportCount++; |
| |
| /* Room for the reportID */ |
| bitOffset = 8; |
| |
| for (i = 0; i < i_count; i++) |
| { |
| if (input_features[i]->caps.ReportID != wine_report->reportID) |
| { |
| wine_report->dwSize += (sizeof(WINE_HID_ELEMENT) * wine_report->elementCount); |
| wine_report = (WINE_HID_REPORT*)(((BYTE*)wine_report)+wine_report->dwSize); |
| new_report(wine_report, input_features[i]); |
| data->dwInputReportCount++; |
| bitLength = max(bitOffset, bitLength); |
| bitOffset = 8; |
| } |
| build_elements(wine_report, input_features[i], &bitOffset, &data_index); |
| count_elements(input_features[i], &data->caps.NumberInputButtonCaps, |
| &data->caps.NumberInputValueCaps); |
| } |
| wine_report->dwSize += (sizeof(WINE_HID_ELEMENT) * wine_report->elementCount); |
| bitLength = max(bitOffset, bitLength); |
| data->caps.InputReportByteLength = ((bitLength + 7) & ~7)/8; |
| data->caps.NumberInputDataIndices = data_index; |
| } |
| |
| if (o_count) |
| { |
| data_index = 0; |
| bitLength = 0; |
| wine_report = (WINE_HID_REPORT*)(((BYTE*)wine_report)+wine_report->dwSize); |
| data->dwOutputReportOffset = (BYTE*)wine_report - (BYTE*)data->InputReports; |
| new_report(wine_report, output_features[0]); |
| data->dwOutputReportCount++; |
| bitOffset = 8; |
| |
| for (i = 0; i < o_count; i++) |
| { |
| if (output_features[i]->caps.ReportID != wine_report->reportID) |
| { |
| wine_report->dwSize += (sizeof(WINE_HID_ELEMENT) * wine_report->elementCount); |
| wine_report = (WINE_HID_REPORT*)(((BYTE*)wine_report)+wine_report->dwSize); |
| new_report(wine_report, output_features[i]); |
| data->dwOutputReportCount++; |
| bitLength = max(bitOffset, bitLength); |
| bitOffset = 8; |
| } |
| build_elements(wine_report, output_features[i], &bitOffset, &data_index); |
| count_elements(output_features[i], &data->caps.NumberOutputButtonCaps, |
| &data->caps.NumberOutputValueCaps); |
| } |
| wine_report->dwSize += (sizeof(WINE_HID_ELEMENT) * wine_report->elementCount); |
| bitLength = max(bitOffset, bitLength); |
| data->caps.OutputReportByteLength = ((bitLength + 7) & ~7)/8; |
| data->caps.NumberOutputDataIndices = data_index; |
| } |
| |
| if (f_count) |
| { |
| data_index = 0; |
| bitLength = 0; |
| wine_report = (WINE_HID_REPORT*)(((BYTE*)wine_report)+wine_report->dwSize); |
| data->dwFeatureReportOffset = (BYTE*)wine_report - (BYTE*)data->InputReports; |
| new_report(wine_report, feature_features[0]); |
| data->dwFeatureReportCount++; |
| bitOffset = 8; |
| |
| for (i = 0; i < f_count; i++) |
| { |
| if (feature_features[i]->caps.ReportID != wine_report->reportID) |
| { |
| wine_report->dwSize += (sizeof(WINE_HID_ELEMENT) * wine_report->elementCount); |
| wine_report = (WINE_HID_REPORT*)((BYTE*)wine_report+wine_report->dwSize); |
| new_report(wine_report, feature_features[i]); |
| data->dwFeatureReportCount++; |
| bitLength = max(bitOffset, bitLength); |
| bitOffset = 8; |
| } |
| build_elements(wine_report, feature_features[i], &bitOffset, &data_index); |
| count_elements(feature_features[i], &data->caps.NumberFeatureButtonCaps, |
| &data->caps.NumberFeatureValueCaps); |
| } |
| bitLength = max(bitOffset, bitLength); |
| data->caps.FeatureReportByteLength = ((bitLength + 7) & ~7)/8; |
| data->caps.NumberFeatureDataIndices = data_index; |
| } |
| |
| return data; |
| } |
| |
| static void free_collection(struct collection *collection) |
| { |
| struct feature *fentry, *fnext; |
| struct collection *centry, *cnext; |
| LIST_FOR_EACH_ENTRY_SAFE(centry, cnext, &collection->collections, struct collection, entry) |
| { |
| list_remove(¢ry->entry); |
| free_collection(centry); |
| } |
| LIST_FOR_EACH_ENTRY_SAFE(fentry, fnext, &collection->features, struct feature, col_entry) |
| { |
| list_remove(&fentry->col_entry); |
| HeapFree(GetProcessHeap(), 0, fentry); |
| } |
| HeapFree(GetProcessHeap(), 0, collection); |
| } |
| |
| static int compare_reports(const void *a, const void* b) |
| { |
| struct feature *f1 = *(struct feature **)a; |
| struct feature *f2 = *(struct feature **)b; |
| int c = (f1->caps.ReportID - f2->caps.ReportID); |
| if (c) return c; |
| return (f1->index - f2->index); |
| } |
| |
| WINE_HIDP_PREPARSED_DATA* ParseDescriptor(BYTE *descriptor, unsigned int length) |
| { |
| WINE_HIDP_PREPARSED_DATA *data = NULL; |
| struct collection *base; |
| struct caps caps; |
| |
| struct list features; |
| struct list caps_stack; |
| |
| unsigned int feature_count = 0; |
| unsigned int cidx; |
| |
| if (TRACE_ON(hid)) |
| { |
| TRACE("Descriptor[%i]: ", length); |
| for (cidx = 0; cidx < length; cidx++) |
| { |
| TRACE("%x ",descriptor[cidx]); |
| if ((cidx+1) % 80 == 0) |
| TRACE("\n"); |
| } |
| TRACE("\n"); |
| } |
| |
| list_init(&features); |
| list_init(&caps_stack); |
| |
| base = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*base)); |
| base->index = 1; |
| list_init(&base->features); |
| list_init(&base->collections); |
| memset(&caps, 0, sizeof(caps)); |
| |
| cidx = 0; |
| parse_descriptor(descriptor, 0, length, &feature_count, &cidx, base, &caps, &features, &caps_stack); |
| |
| debug_collection(base); |
| |
| if (!list_empty(&caps_stack)) |
| { |
| struct caps_stack *entry, *cursor; |
| ERR("%i unpopped device caps on the stack\n", list_count(&caps_stack)); |
| LIST_FOR_EACH_ENTRY_SAFE(entry, cursor, &caps_stack, struct caps_stack, entry) |
| { |
| list_remove(&entry->entry); |
| HeapFree(GetProcessHeap(), 0, entry); |
| } |
| } |
| |
| cidx = 2; |
| if (feature_count) |
| { |
| struct feature *entry; |
| struct feature** sorted_features; |
| struct feature** input_features; |
| struct feature** output_features; |
| struct feature** feature_features; |
| unsigned int i_count, o_count, f_count; |
| unsigned int i; |
| |
| i_count = o_count = f_count = 0; |
| |
| sorted_features = HeapAlloc(GetProcessHeap(), 0, sizeof(*sorted_features) * feature_count); |
| input_features = HeapAlloc(GetProcessHeap(), 0, sizeof(*input_features) * feature_count); |
| output_features = HeapAlloc(GetProcessHeap(), 0, sizeof(*output_features) * feature_count); |
| feature_features = HeapAlloc(GetProcessHeap(), 0, sizeof(*feature_features) * feature_count); |
| |
| i = 0; |
| LIST_FOR_EACH_ENTRY(entry, &features, struct feature, entry) |
| sorted_features[i++] = entry; |
| |
| /* Sort features base on report if there are multiple reports */ |
| if (sorted_features[0]->caps.ReportID != 0) |
| qsort(sorted_features, feature_count, sizeof(struct feature*), compare_reports); |
| |
| for (i = 0; i < feature_count; i++) |
| { |
| switch (sorted_features[i]->type) |
| { |
| case HidP_Input: |
| input_features[i_count] = sorted_features[i]; |
| i_count++; |
| break; |
| case HidP_Output: |
| output_features[o_count] = sorted_features[i]; |
| o_count++; |
| break; |
| case HidP_Feature: |
| feature_features[f_count] = sorted_features[i]; |
| f_count++; |
| break; |
| default: |
| ERR("Unknown type %i\n",sorted_features[i]->type); |
| } |
| } |
| |
| if (TRACE_ON(hid)) |
| { |
| TRACE("DUMP FEATURES:\n"); |
| TRACE("----INPUT----\n"); |
| for (cidx = 0; cidx < i_count; cidx++) |
| debug_feature(input_features[cidx]); |
| TRACE("----OUTPUT----\n"); |
| for (cidx = 0; cidx < o_count; cidx++) |
| debug_feature(output_features[cidx]); |
| TRACE("----FEATURE----\n"); |
| for (cidx = 0; cidx < f_count; cidx++) |
| debug_feature(feature_features[cidx]); |
| } |
| |
| data = build_PreparseData(sorted_features, feature_count, input_features, i_count, output_features, o_count, feature_features, f_count, base); |
| |
| debug_print_preparsed(data); |
| |
| HeapFree(GetProcessHeap(), 0, sorted_features); |
| HeapFree(GetProcessHeap(), 0, input_features); |
| HeapFree(GetProcessHeap(), 0, output_features); |
| HeapFree(GetProcessHeap(), 0, feature_features); |
| } |
| |
| free_collection(base); |
| /* We do not have to free the list as free_collection does all the work */ |
| |
| return data; |
| } |