Parse a HID report descriptor into a tablet layout.
Free of device state and of HIDAPI, so captured descriptor bytes can be parsed and asserted against with no hardware present.
254{
255
256 TabletLayout layout;
257
258 GlobalState global;
259 std::vector<GlobalState> global_stack;
260
261 std::vector<LocalUsage> local_usages;
262 uint32_t usage_min = 0;
263 uint32_t usage_max = 0;
264 bool has_usage_range = false;
265
266 std::unordered_map<uint8_t, uint32_t> bit_cursor;
267 bool digitizer_seen = false;
268
269 const auto clear_locals = [&]() {
270 local_usages.clear();
271 usage_min = 0;
272 usage_max = 0;
273 has_usage_range = false;
274 };
275
276 size_t i = 0;
277 while (i < descriptor.size()) {
278 const uint8_t prefix = descriptor[i];
279
280 if (prefix == 0xFE) {
281 if (i + 1 >= descriptor.size())
282 break;
283 i += 2U + descriptor[i + 1];
284 continue;
285 }
286
287 size_t size = prefix & 0x03U;
290 const uint8_t
type = (prefix >> 2U) & 0x03U;
291 const uint8_t tag = (prefix >> 4U) & 0x0FU;
292
293 ++i;
294 if (i +
size > descriptor.size())
295 break;
296
297 const uint8_t*
payload = descriptor.data() + i;
299
301 switch (tag) {
302 case 0x0:
303 global.usage_page =
static_cast<uint16_t
>(item_unsigned(
payload,
size));
304 if (global.usage_page == k_page_digitizer)
305 digitizer_seen = true;
306 break;
307
308 case 0x1:
310 break;
311
312 case 0x2: {
313 const int32_t as_signed = item_signed(
payload,
size);
314 global.logical_max = (global.logical_min >= 0 && as_signed < 0)
315 ?
static_cast<int32_t
>(item_unsigned(
payload,
size))
316 : as_signed;
317 break;
318 }
319
320 case 0x7:
322 break;
323
324 case 0x8:
325 global.report_id =
static_cast<uint8_t
>(item_unsigned(
payload,
size));
326 layout.uses_report_ids = true;
327 break;
328
329 case 0x9:
330 global.report_count = item_unsigned(
payload,
size);
331 break;
332
333 case 0xA:
334 global_stack.push_back(global);
335 break;
336
337 case 0xB:
338 if (!global_stack.empty()) {
339 global = global_stack.back();
340 global_stack.pop_back();
341 }
342 break;
343
344 default:
345 break;
346 }
347 continue;
348 }
349
351 switch (tag) {
352 case 0x0:
354 const uint32_t full = item_unsigned(
payload,
size);
355 const auto page =
static_cast<uint16_t
>(full >> 16U);
356 local_usages.push_back({ .page =
page, .usage =
static_cast<uint16_t
>(full & 0xFFFFU) });
357 if (
page == k_page_digitizer)
358 digitizer_seen = true;
359 } else {
360 local_usages.push_back({ .page = global.usage_page,
361 .usage =
static_cast<uint16_t
>(item_unsigned(
payload,
size)) });
362 }
363 break;
364
365 case 0x1:
367 has_usage_range = true;
368 break;
369
370 case 0x2:
372 has_usage_range = true;
373 break;
374
375 default:
376 break;
377 }
378 continue;
379 }
380
382 continue;
383 }
384
385 if (tag != 0x8) {
386 clear_locals();
387 continue;
388 }
389
390 const uint32_t flags = item_unsigned(
payload,
size);
391 const bool is_constant = (flags & 0x01U) != 0U;
392
393 if (global.report_size == 0 || global.report_count == 0) {
394 clear_locals();
395 continue;
396 }
397
398 uint32_t& cursor = bit_cursor[global.report_id];
399
401 LocalUsage
current { .page = global.usage_page, .usage = 0 };
402
403 if (!is_constant) {
404 if (
index < local_usages.size()) {
406 } else if (!local_usages.empty()) {
408 } else if (has_usage_range) {
409 current.usage =
static_cast<uint16_t
>(
410 std::min(usage_min +
index, usage_max));
411 }
412 }
413
414 const size_t slot = is_constant
415 ? k_slot_none
417
418 if (slot != k_slot_none) {
419 TabletField field;
420 field.report_id = global.report_id;
421 field.usage_page =
current.page;
423 field.bit_offset = cursor;
424 field.bit_size = global.report_size;
425 field.logical_min = global.logical_min;
426 field.logical_max = global.logical_max;
427 field.slot = slot;
428 layout.fields.push_back(field);
429
431 layout.axes |= axis_for_slot(slot);
432 if (slot == k_pseudo_invert)
433 layout.has_invert = true;
434 if (slot == k_pseudo_serial)
435 layout.has_serial = true;
436 }
437
438 cursor += global.report_size;
439 }
440
441 clear_locals();
442 }
443
444 if (!digitizer_seen)
445 layout.fields.clear();
446
447 return layout;
448}
vk::PhysicalDeviceType type
static constexpr size_t SLOT_COUNT