Atlas - SDL_report_descriptor.c

Home / ext / SDL / src / joystick / hidapi Lines: 1 | Size: 17691 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21#include "SDL_internal.h" 22 23#include "SDL_report_descriptor.h" 24 25// This is a very simple (and non-compliant!) report descriptor parser 26// used to quickly parse Xbox Bluetooth reports 27 28typedef enum 29{ 30 DescriptorItemTypeMain = 0, 31 DescriptorItemTypeGlobal = 1, 32 DescriptorItemTypeLocal = 2, 33 DescriptorItemTypeReserved = 3, 34} ItemType; 35 36typedef enum 37{ 38 MainTagInput = 0x8, 39 MainTagOutput = 0x9, 40 MainTagFeature = 0xb, 41 MainTagCollection = 0xa, 42 MainTagEndCollection = 0xc, 43} MainTag; 44 45typedef enum 46{ 47 MainFlagConstant = 0x0001, 48 MainFlagVariable = 0x0002, 49 MainFlagRelative = 0x0004, 50 MainFlagWrap = 0x0008, 51 MainFlagNonLinear = 0x0010, 52 MainFlagNoPreferred = 0x0020, 53 MainFlagNullState = 0x0040, 54 MainFlagVolatile = 0x0080, 55 MainFlagBufferedBytes = 0x0100, 56} MainFlag; 57 58typedef enum 59{ 60 GlobalTagUsagePage = 0x0, 61 GlobalTagLogicalMinimum = 0x1, 62 GlobalTagLogicalMaximum = 0x2, 63 GlobalTagPhysicalMinimum = 0x3, 64 GlobalTagPhysicalMaximum = 0x4, 65 GlobalTagUnitExponent = 0x5, 66 GlobalTagUnit = 0x6, 67 GlobalTagReportSize = 0x7, 68 GlobalTagReportID = 0x8, 69 GlobalTagReportCount = 0x9, 70 GlobalTagPush = 0xa, 71 GlobalTagPop = 0xb, 72} GlobalTag; 73 74typedef enum 75{ 76 LocalTagUsage = 0x0, 77 LocalTagUsageMinimum = 0x1, 78 LocalTagUsageMaximum = 0x2, 79 LocalTagDesignatorIndex = 0x3, 80 LocalTagDesignatorMinimum = 0x4, 81 LocalTagDesignatorMaximum = 0x5, 82 LocalTagStringIndex = 0x7, 83 LocalTagStringMinimum = 0x8, 84 LocalTagStringMaximum = 0x9, 85 LocalTagDelimiter = 0xa, 86} LocalTag; 87 88typedef struct 89{ 90 Uint32 usage_page; 91 Uint32 report_size; 92 Uint32 report_count; 93 Uint32 report_id; 94} DescriptorGlobalState; 95 96typedef struct 97{ 98 Uint32 usage_minimum; 99 Uint32 usage_maximum; 100 int usage_maxcount; 101 int usage_count; 102 Uint32 *usages; 103} DescriptorLocalState; 104 105typedef struct 106{ 107 int collection_depth; 108 DescriptorGlobalState global; 109 DescriptorLocalState local; 110 int field_maxcount; 111 int field_count; 112 int field_offset; 113 DescriptorInputField *fields; 114} DescriptorContext; 115 116static void DebugDescriptor(DescriptorContext *ctx, const char *fmt, ...) 117{ 118#ifdef DEBUG_DESCRIPTOR 119 va_list ap; 120 va_start(ap, fmt); 121 char *message = NULL; 122 SDL_vasprintf(&message, fmt, ap); 123 va_end(ap); 124 if (ctx->collection_depth > 0) { 125 size_t len = 4 * ctx->collection_depth + SDL_strlen(message) + 1; 126 char *output = (char *)SDL_malloc(len); 127 if (output) { 128 SDL_memset(output, ' ', 4 * ctx->collection_depth); 129 output[4 * ctx->collection_depth] = '\0'; 130 SDL_strlcat(output, message, len); 131 SDL_free(message); 132 message = output; 133 } 134 } 135 SDL_Log("%s", message); 136 SDL_free(message); 137#endif // DEBUG_DESCRIPTOR 138} 139 140static void DebugMainTag(DescriptorContext *ctx, const char *tag, Uint32 flags) 141{ 142#ifdef DEBUG_DESCRIPTOR 143 char message[1024] = { 0 }; 144 145 SDL_strlcat(message, tag, sizeof(message)); 146 SDL_strlcat(message, "(", sizeof(message)); 147 if (flags & MainFlagConstant) { 148 SDL_strlcat(message, " Constant", sizeof(message)); 149 } else { 150 SDL_strlcat(message, " Data", sizeof(message)); 151 } 152 if (flags & MainFlagVariable) { 153 SDL_strlcat(message, " Variable", sizeof(message)); 154 } else { 155 SDL_strlcat(message, " Array", sizeof(message)); 156 } 157 if (flags & MainFlagRelative) { 158 SDL_strlcat(message, " Relative", sizeof(message)); 159 } else { 160 SDL_strlcat(message, " Absolute", sizeof(message)); 161 } 162 if (flags & MainFlagWrap) { 163 SDL_strlcat(message, " Wrap", sizeof(message)); 164 } else { 165 SDL_strlcat(message, " No Wrap", sizeof(message)); 166 } 167 if (flags & MainFlagNonLinear) { 168 SDL_strlcat(message, " Non Linear", sizeof(message)); 169 } else { 170 SDL_strlcat(message, " Linear", sizeof(message)); 171 } 172 if (flags & MainFlagNoPreferred) { 173 SDL_strlcat(message, " No Preferred", sizeof(message)); 174 } else { 175 SDL_strlcat(message, " Preferred State", sizeof(message)); 176 } 177 if (flags & MainFlagNullState) { 178 SDL_strlcat(message, " Null State", sizeof(message)); 179 } else { 180 SDL_strlcat(message, " No Null Position", sizeof(message)); 181 } 182 if (flags & MainFlagVolatile) { 183 SDL_strlcat(message, " Volatile", sizeof(message)); 184 } else { 185 SDL_strlcat(message, " Non Volatile", sizeof(message)); 186 } 187 if (flags & MainFlagBufferedBytes) { 188 SDL_strlcat(message, " Buffered Bytes", sizeof(message)); 189 } else { 190 SDL_strlcat(message, " Bit Field", sizeof(message)); 191 } 192 SDL_strlcat(message, " )", sizeof(message)); 193 194 DebugDescriptor(ctx, "%s", message); 195 196#endif // DEBUG_DESCRIPTOR 197} 198 199static Uint32 ReadValue(const Uint8 *data, int size) 200{ 201 Uint32 value = 0; 202 203 int shift = 0; 204 while (size--) { 205 value |= ((Uint32)(*data++)) << shift; 206 shift += 8; 207 } 208 return value; 209} 210 211static void ResetLocalState(DescriptorContext *ctx) 212{ 213 ctx->local.usage_minimum = 0; 214 ctx->local.usage_maximum = 0; 215 ctx->local.usage_count = 0; 216} 217 218static bool AddUsage(DescriptorContext *ctx, Uint32 usage) 219{ 220 if (ctx->local.usage_count == ctx->local.usage_maxcount) { 221 int usage_maxcount = ctx->local.usage_maxcount + 4; 222 Uint32 *usages = (Uint32 *)SDL_realloc(ctx->local.usages, usage_maxcount * sizeof(*usages)); 223 if (!usages) { 224 return false; 225 } 226 ctx->local.usages = usages; 227 ctx->local.usage_maxcount = usage_maxcount; 228 } 229 230 if (usage <= 0xFFFF) { 231 usage |= (ctx->global.usage_page << 16); 232 } 233 ctx->local.usages[ctx->local.usage_count++] = usage; 234 return true; 235} 236 237static bool AddInputField(DescriptorContext *ctx, Uint32 usage, int bit_size) 238{ 239 if (ctx->field_count == ctx->field_maxcount) { 240 int field_maxcount = ctx->field_maxcount + 4; 241 DescriptorInputField *fields = (DescriptorInputField *)SDL_realloc(ctx->fields, field_maxcount * sizeof(*fields)); 242 if (!fields) { 243 return false; 244 } 245 ctx->fields = fields; 246 ctx->field_maxcount = field_maxcount; 247 } 248 249 DescriptorInputField *field = &ctx->fields[ctx->field_count++]; 250 field->report_id = (Uint8)ctx->global.report_id; 251 field->usage = usage; 252 field->bit_offset = ctx->field_offset; 253 field->bit_size = bit_size; 254 255 DebugDescriptor(ctx, "Adding report %d field 0x%.8x size %d bits at bit offset %d", field->report_id, field->usage, field->bit_size, field->bit_offset); 256 return true; 257} 258 259static bool AddInputFields(DescriptorContext *ctx) 260{ 261 Uint32 usage = 0; 262 263 if (ctx->global.report_count == 0 || ctx->global.report_size == 0) { 264 return true; 265 } 266 267 if (ctx->local.usage_count == 0 && 268 ctx->local.usage_minimum > 0 && 269 ctx->local.usage_maximum >= ctx->local.usage_minimum) { 270 for (usage = ctx->local.usage_minimum; usage <= ctx->local.usage_maximum; ++usage) { 271 if (!AddUsage(ctx, usage)) { 272 return false; 273 } 274 } 275 } 276 277 int usage_index = 0; 278 for (Uint32 i = 0; i < ctx->global.report_count; ++i) { 279 if (usage_index < ctx->local.usage_count) { 280 usage = ctx->local.usages[usage_index]; 281 if (usage_index < (ctx->local.usage_count - 1)) { 282 ++usage_index; 283 } 284 } 285 286 int size = (int)ctx->global.report_size; 287 if (usage > 0) { 288 if (!AddInputField(ctx, usage, size)) { 289 return false; 290 } 291 } 292 ctx->field_offset += size; 293 } 294 return true; 295} 296 297static bool ParseMainItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) 298{ 299 Uint32 flags; 300 301 switch (tag) { 302 case MainTagInput: 303 flags = ReadValue(data, size); 304 DebugMainTag(ctx, "MainTagInput", flags); 305 AddInputFields(ctx); 306 break; 307 case MainTagOutput: 308 flags = ReadValue(data, size); 309 DebugMainTag(ctx, "MainTagOutput", flags); 310 break; 311 case MainTagFeature: 312 flags = ReadValue(data, size); 313 DebugMainTag(ctx, "MainTagFeature", flags); 314 break; 315 case MainTagCollection: 316 DebugDescriptor(ctx, "MainTagCollection"); 317 switch (*data) { 318 case 0x00: 319 DebugDescriptor(ctx, "Physical"); 320 break; 321 case 0x01: 322 DebugDescriptor(ctx, "Application"); 323 break; 324 case 0x02: 325 DebugDescriptor(ctx, "Logical"); 326 break; 327 case 0x03: 328 DebugDescriptor(ctx, "Report"); 329 break; 330 case 0x04: 331 DebugDescriptor(ctx, "Named Array"); 332 break; 333 case 0x05: 334 DebugDescriptor(ctx, "Usage Switch"); 335 break; 336 case 0x06: 337 DebugDescriptor(ctx, "Usage Modifier"); 338 break; 339 default: 340 break; 341 } 342 ++ctx->collection_depth; 343 break; 344 case MainTagEndCollection: 345 if (ctx->collection_depth > 0) { 346 --ctx->collection_depth; 347 } 348 DebugDescriptor(ctx, "MainTagEndCollection"); 349 break; 350 default: 351 DebugDescriptor(ctx, "Unknown main tag: %d", tag); 352 break; 353 } 354 355 ResetLocalState(ctx); 356 357 return true; 358} 359 360static bool ParseGlobalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) 361{ 362 Uint32 value; 363 364 switch (tag) { 365 case GlobalTagUsagePage: 366 ctx->global.usage_page = ReadValue(data, size); 367 DebugDescriptor(ctx, "GlobalTagUsagePage: 0x%.4x", ctx->global.usage_page); 368 break; 369 case GlobalTagLogicalMinimum: 370 value = ReadValue(data, size); 371 DebugDescriptor(ctx, "GlobalTagLogicalMinimum: %u", value); 372 break; 373 case GlobalTagLogicalMaximum: 374 value = ReadValue(data, size); 375 DebugDescriptor(ctx, "GlobalTagLogicalMaximum: %u", value); 376 break; 377 case GlobalTagPhysicalMinimum: 378 value = ReadValue(data, size); 379 DebugDescriptor(ctx, "GlobalTagPhysicalMinimum: %u", value); 380 break; 381 case GlobalTagPhysicalMaximum: 382 value = ReadValue(data, size); 383 DebugDescriptor(ctx, "GlobalTagPhysicalMaximum: %u", value); 384 break; 385 case GlobalTagUnitExponent: 386 DebugDescriptor(ctx, "GlobalTagUnitExponent"); 387 break; 388 case GlobalTagUnit: 389 DebugDescriptor(ctx, "GlobalTagUnit"); 390 break; 391 case GlobalTagReportSize: 392 ctx->global.report_size = ReadValue(data, size); 393 DebugDescriptor(ctx, "GlobalTagReportSize: %u", ctx->global.report_size); 394 break; 395 case GlobalTagReportID: 396 ctx->global.report_id = ReadValue(data, size); 397 ctx->field_offset = 0; 398 DebugDescriptor(ctx, "GlobalTagReportID: %u", ctx->global.report_id); 399 break; 400 case GlobalTagReportCount: 401 ctx->global.report_count = ReadValue(data, size); 402 DebugDescriptor(ctx, "GlobalTagReportCount: %u", ctx->global.report_count); 403 break; 404 case GlobalTagPush: 405 DebugDescriptor(ctx, "GlobalTagPush"); 406 break; 407 case GlobalTagPop: 408 DebugDescriptor(ctx, "GlobalTagPop"); 409 break; 410 default: 411 DebugDescriptor(ctx, "Unknown global tag"); 412 break; 413 } 414 return true; 415} 416 417static bool ParseLocalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) 418{ 419 Uint32 value; 420 421 switch (tag) { 422 case LocalTagUsage: 423 value = ReadValue(data, size); 424 AddUsage(ctx, value); 425 DebugDescriptor(ctx, "LocalTagUsage: 0x%.4x", value); 426 break; 427 case LocalTagUsageMinimum: 428 ctx->local.usage_minimum = ReadValue(data, size); 429 DebugDescriptor(ctx, "LocalTagUsageMinimum: 0x%.4x", ctx->local.usage_minimum); 430 break; 431 case LocalTagUsageMaximum: 432 ctx->local.usage_maximum = ReadValue(data, size); 433 DebugDescriptor(ctx, "LocalTagUsageMaximum: 0x%.4x", ctx->local.usage_maximum); 434 break; 435 case LocalTagDesignatorIndex: 436 DebugDescriptor(ctx, "LocalTagDesignatorIndex"); 437 break; 438 case LocalTagDesignatorMinimum: 439 DebugDescriptor(ctx, "LocalTagDesignatorMinimum"); 440 break; 441 case LocalTagDesignatorMaximum: 442 DebugDescriptor(ctx, "LocalTagDesignatorMaximum"); 443 break; 444 case LocalTagStringIndex: 445 DebugDescriptor(ctx, "LocalTagStringIndex"); 446 break; 447 case LocalTagStringMinimum: 448 DebugDescriptor(ctx, "LocalTagStringMinimum"); 449 break; 450 case LocalTagStringMaximum: 451 DebugDescriptor(ctx, "LocalTagStringMaximum"); 452 break; 453 case LocalTagDelimiter: 454 DebugDescriptor(ctx, "LocalTagDelimiter"); 455 break; 456 default: 457 DebugDescriptor(ctx, "Unknown local tag"); 458 break; 459 } 460 return true; 461} 462 463static bool ParseDescriptor(DescriptorContext *ctx, const Uint8 *descriptor, int descriptor_size) 464{ 465 SDL_zerop(ctx); 466 467 for (const Uint8 *here = descriptor; here < descriptor + descriptor_size; ) { 468 static const int sizes[4] = { 0, 1, 2, 4 }; 469 Uint8 data = *here++; 470 int size = sizes[(data & 0x3)]; 471 int type = ((data >> 2) & 0x3); 472 int tag = (data >> 4); 473 474 if ((here + size) > (descriptor + descriptor_size)) { 475 return SDL_SetError("Invalid descriptor"); 476 } 477 478#ifdef DEBUG_DESCRIPTOR 479 SDL_Log("Data: 0x%.2x, size: %d, type: %d, tag: %d", data, size, type, tag); 480#endif 481 switch (type) { 482 case DescriptorItemTypeMain: 483 if (!ParseMainItem(ctx, tag, size, here)) { 484 return false; 485 } 486 break; 487 case DescriptorItemTypeGlobal: 488 if (!ParseGlobalItem(ctx, tag, size, here)) { 489 return false; 490 } 491 break; 492 case DescriptorItemTypeLocal: 493 if (!ParseLocalItem(ctx, tag, size, here)) { 494 return false; 495 } 496 break; 497 case DescriptorItemTypeReserved: 498 // Long items are currently unsupported 499 return SDL_Unsupported(); 500 } 501 502 here += size; 503 } 504 return true; 505} 506 507static void CleanupContext(DescriptorContext *ctx) 508{ 509 SDL_free(ctx->local.usages); 510 SDL_free(ctx->fields); 511} 512 513SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size) 514{ 515 SDL_ReportDescriptor *result = NULL; 516 517 DescriptorContext ctx; 518 if (ParseDescriptor(&ctx, descriptor, descriptor_size)) { 519 result = (SDL_ReportDescriptor *)SDL_malloc(sizeof(*result)); 520 if (result) { 521 result->field_count = ctx.field_count; 522 result->fields = ctx.fields; 523 ctx.fields = NULL; 524 } 525 } 526 CleanupContext(&ctx); 527 528 return result; 529} 530 531bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage) 532{ 533 if (!descriptor) { 534 return false; 535 } 536 537 Uint32 full_usage = (((Uint32)usage_page << 16) | usage); 538 for (int i = 0; i < descriptor->field_count; ++i) { 539 if (descriptor->fields[i].usage == full_usage) { 540 return true; 541 } 542 } 543 return false; 544} 545 546void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor) 547{ 548 if (descriptor) { 549 SDL_free(descriptor->fields); 550 SDL_free(descriptor); 551 } 552} 553 554bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, Uint32 *value) 555{ 556 int offset = (bit_offset / 8); 557 if (offset >= size) { 558 *value = 0; 559 return SDL_SetError("Out of bounds reading report data"); 560 } 561 562 *value = ReadValue(data + offset, (bit_size + 7) / 8); 563 564 int shift = (bit_offset % 8); 565 if (shift > 0) { 566 *value >>= shift; 567 } 568 569 switch (bit_size) { 570 case 1: 571 *value &= 0x1; 572 break; 573 case 4: 574 *value &= 0xf; 575 break; 576 case 10: 577 *value &= 0x3ff; 578 break; 579 case 15: 580 *value &= 0x7fff; 581 break; 582 default: 583 SDL_assert((bit_size % 8) == 0); 584 break; 585 } 586 return true; 587} 588 589#ifdef TEST_MAIN 590 591#include <SDL3/SDL_main.h> 592 593int main(int argc, char *argv[]) 594{ 595 const char *file = argv[1]; 596 if (argc < 2) { 597 SDL_Log("Usage: %s file", argv[0]); 598 return 1; 599 } 600 601 size_t descriptor_size = 0; 602 Uint8 *descriptor = SDL_LoadFile(argv[1], &descriptor_size); 603 if (!descriptor) { 604 SDL_Log("Couldn't load %s: %s", argv[1], SDL_GetError()); 605 return 2; 606 } 607 608 DescriptorContext ctx; 609 if (!ParseDescriptor(&ctx, descriptor, descriptor_size)) { 610 SDL_Log("Couldn't parse %s: %s", argv[1], SDL_GetError()); 611 return 3; 612 } 613 return 0; 614} 615 616#endif // TEST_MAIN 617
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.