blob: 342dba4a8b49beeac1e5e3d38e102c8381fc626a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/public/cpp/hid/hid_collection.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "services/device/public/cpp/hid/hid_item_state_table.h"
namespace device {
HidCollection::HidCollection(HidCollection* parent,
uint32_t usage_page,
uint32_t usage,
uint32_t type)
: parent_(parent), usage_(usage, usage_page), collection_type_(type) {}
HidCollection::~HidCollection() = default;
// static
std::vector<std::unique_ptr<HidCollection>> HidCollection::BuildCollections(
const std::vector<std::unique_ptr<HidReportDescriptorItem>>& items) {
std::vector<std::unique_ptr<HidCollection>> collections;
// This HID report descriptor parser implements a state machine described
// in the HID specification. See section 6.2.2 Report Descriptor.
HidItemStateTable state;
for (const auto& current_item : items) {
switch (current_item->tag()) {
case HidReportDescriptorItem::kTagCollection:
// Add a new collection. Collections at the top-most level describe
// separate components of the device and are often treated as separate
// devices. Nested components represent logical collections of fields
// within a report.
AddCollection(*current_item, collections, state);
state.local.Reset();
break;
case HidReportDescriptorItem::kTagEndCollection:
// Mark the end of the current collection. Subsequent items describe
// reports associated with the parent collection.
if (state.collection)
state.collection = state.collection->parent_;
state.local.Reset();
break;
case HidReportDescriptorItem::kTagInput:
case HidReportDescriptorItem::kTagOutput:
case HidReportDescriptorItem::kTagFeature:
// Add a report item to an input, output, or feature report within the
// current collection. The properties of the report item are determined
// by the current descriptor item and the current item state table.
// Changes to input, output, and feature reports are propagated to all
// ancestor collections.
if (state.collection) {
auto* collection = state.collection;
while (collection) {
collection->AddReportItem(current_item->tag(),
current_item->GetShortData(), state);
collection = collection->parent_;
}
}
state.local.Reset();
break;
case HidReportDescriptorItem::kTagPush:
// Push a copy of the current global state onto the stack. If there is
// no global state, the push has no effect and is ignored.
if (!state.global_stack.empty())
state.global_stack.push_back(state.global_stack.back());
break;
case HidReportDescriptorItem::kTagPop:
// Pop the top item of the global state stack, returning to the
// previously pushed state. If there is no such item, the pop has no
// effect and is ignored.
if (!state.global_stack.empty())
state.global_stack.pop_back();
break;
case HidReportDescriptorItem::kTagReportId:
// Update the current report ID. The report ID is global, but is not
// affected by push and pop. Changes to the report ID are propagated to
// all ancestor collections.
if (state.collection) {
state.report_id = current_item->GetShortData();
auto* collection = state.collection;
while (collection) {
collection->report_ids_.push_back(state.report_id);
collection = collection->parent_;
}
}
break;
case HidReportDescriptorItem::kTagUsagePage:
case HidReportDescriptorItem::kTagLogicalMinimum:
case HidReportDescriptorItem::kTagLogicalMaximum:
case HidReportDescriptorItem::kTagPhysicalMinimum:
case HidReportDescriptorItem::kTagPhysicalMaximum:
case HidReportDescriptorItem::kTagUnitExponent:
case HidReportDescriptorItem::kTagUnit:
case HidReportDescriptorItem::kTagReportSize:
case HidReportDescriptorItem::kTagReportCount:
case HidReportDescriptorItem::kTagUsage:
case HidReportDescriptorItem::kTagUsageMinimum:
case HidReportDescriptorItem::kTagUsageMaximum:
case HidReportDescriptorItem::kTagDesignatorIndex:
case HidReportDescriptorItem::kTagDesignatorMinimum:
case HidReportDescriptorItem::kTagDesignatorMaximum:
case HidReportDescriptorItem::kTagStringIndex:
case HidReportDescriptorItem::kTagStringMinimum:
case HidReportDescriptorItem::kTagStringMaximum:
case HidReportDescriptorItem::kTagDelimiter:
// Update the value associated with a local or global item in the item
// state table.
state.SetItemValue(current_item->tag(), current_item->GetShortData());
break;
default:
break;
}
}
return collections;
}
// static
void HidCollection::AddCollection(
const HidReportDescriptorItem& item,
std::vector<std::unique_ptr<HidCollection>>& collections,
HidItemStateTable& state) {
// Extract |usage| and |usage_page| from the current state. The usage page may
// be set either by a global usage page, or in the high-order bytes of a local
// usage value. When both are provided, the local usage value takes
// precedence.
uint32_t usage = state.local.usages.empty() ? 0 : state.local.usages.front();
uint32_t usage_page = (usage >> 16) & 0xffff;
if (usage_page == 0 && !state.global_stack.empty())
usage_page = state.global_stack.back().usage_page;
// Create the new collection. If it is a child of another collection, append
// it to that collection's list of children. Otherwise, append it to the list
// of top-level collections in |collections|.
uint32_t collection_type = item.GetShortData();
auto collection = std::make_unique<HidCollection>(
state.collection, usage_page, usage, collection_type);
if (state.collection) {
state.collection->children_.push_back(std::move(collection));
state.collection = state.collection->children_.back().get();
} else {
collections.push_back(std::move(collection));
state.collection = collections.back().get();
}
}
void HidCollection::AddChildForTesting(
std::unique_ptr<HidCollection> collection) {
children_.push_back(std::move(collection));
}
void HidCollection::AddReportItem(HidReportDescriptorItem::Tag tag,
uint32_t report_info,
const HidItemStateTable& state) {
// Get the correct report map for the current report item (input, output,
// or feature). The new item will be appended to a report in this report map.
std::unordered_map<uint8_t, HidReport>* reports = nullptr;
if (tag == HidReportDescriptorItem::kTagInput)
reports = &input_reports_;
else if (tag == HidReportDescriptorItem::kTagOutput)
reports = &output_reports_;
else if (tag == HidReportDescriptorItem::kTagFeature)
reports = &feature_reports_;
else
return;
// Fetch the report with the |report_id| matching this item, or insert a new
// report into the map if it does not yet exist.
HidReport* report = nullptr;
auto find_it = reports->find(state.report_id);
if (find_it == reports->end()) {
auto emplace_result = reports->emplace(state.report_id, HidReport());
report = &emplace_result.first->second;
} else {
report = &find_it->second;
}
// Create the report item and append it to the report.
report->push_back(HidReportItem::Create(tag, report_info, state));
}
mojom::HidCollectionInfoPtr HidCollection::GetDetails(
size_t* max_input_report_bits,
size_t* max_output_report_bits,
size_t* max_feature_report_bits) {
DCHECK(max_input_report_bits);
DCHECK(max_output_report_bits);
DCHECK(max_feature_report_bits);
struct {
const std::unordered_map<uint8_t, HidReport>& reports;
size_t& max_report_bits;
} report_lists[]{
{input_reports_, *max_input_report_bits},
{output_reports_, *max_output_report_bits},
{feature_reports_, *max_feature_report_bits},
};
auto collection_info = mojom::HidCollectionInfo::New();
collection_info->usage =
mojom::HidUsageAndPage::New(usage_.usage, usage_.usage_page);
collection_info->report_ids.insert(collection_info->report_ids.end(),
report_ids_.begin(), report_ids_.end());
for (const auto& entry : report_lists) {
entry.max_report_bits = 0;
for (const auto& report : entry.reports) {
size_t report_bits = 0;
for (const auto& item : report.second)
report_bits += item->GetReportSize() * item->GetReportCount();
entry.max_report_bits = std::max(entry.max_report_bits, report_bits);
}
}
return collection_info;
}
} // namespace device