#include "ui/display/manager/touch_device_manager.h"
#include <algorithm>
#include <set>
#include <string>
#include <tuple>
#include "base/containers/contains.h"
#include "base/hash/hash.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/display/util/display_util.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/touchscreen_device.h"
namespace display {
namespace {
using ManagedDisplayInfoList = std::vector<ManagedDisplayInfo*>;
using DeviceList = std::vector<ui::TouchscreenDevice>;
constexpr char kFallbackTouchDeviceName[] = "fallback_touch_device_name";
constexpr char kFallbackTouchDevicePhys[] = "fallback_touch_device_phys";
bool IsDeviceConnectedViaUsb(const base::FilePath& path) {
for (const auto& component : path.GetComponents()) {
if (base::StartsWith(component, "usb",
base::CompareCase::INSENSITIVE_ASCII)) {
return true;
}
if (component.starts_with("evdi")) {
return true;
}
}
return false;
}
int GetUsbAssociationScore(const ManagedDisplayInfo* display,
const ui::TouchscreenDevice& device) {
if (!IsDeviceConnectedViaUsb(display->sys_path()) ||
!IsDeviceConnectedViaUsb(device.sys_path))
return 0;
std::vector<base::FilePath::StringType> display_components =
display->sys_path().GetComponents();
std::vector<base::FilePath::StringType> device_components =
device.sys_path.GetComponents();
std::size_t largest_idx = 0;
while (largest_idx < display_components.size() &&
largest_idx < device_components.size() &&
display_components[largest_idx] == device_components[largest_idx]) {
++largest_idx;
}
return largest_idx;
}
DeviceList::const_iterator GuessBestUsbDevice(const ManagedDisplayInfo* display,
const DeviceList& devices) {
int best_score = 0;
DeviceList::const_iterator best_device_it = devices.end();
for (auto it = devices.begin(); it != devices.end(); it++) {
int score = GetUsbAssociationScore(display, *it);
if (score > best_score) {
best_score = score;
best_device_it = it;
}
}
return best_device_it;
}
bool IsInternalDisplay(const ManagedDisplayInfo* display) {
return IsInternalDisplayId(display->id());
}
bool IsInternalDevice(const ui::TouchscreenDevice& device) {
return device.type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL;
}
ManagedDisplayInfo* GetInternalDisplay(ManagedDisplayInfoList* displays) {
auto it = std::ranges::find_if(*displays, &IsInternalDisplay);
return it == displays->end() ? nullptr : *it;
}
void ClearCalibrationDataInMap(TouchDeviceManager::AssociationInfoMap& info_map,
int64_t display_id) {
if (info_map.find(display_id) == info_map.end())
return;
info_map[display_id].calibration_data = TouchCalibrationData();
}
ManagedDisplayInfo* GetBestMatchForDevice(
const TouchDeviceManager::TouchAssociationMap& touch_associations,
const TouchDeviceIdentifier& identifier,
ManagedDisplayInfoList* displays) {
ManagedDisplayInfo* display_info = nullptr;
base::Time most_recent_timestamp;
if (!base::Contains(touch_associations, identifier))
return display_info;
const TouchDeviceManager::AssociationInfoMap& info_map =
touch_associations.at(identifier);
for (auto* display : *displays) {
if (IsInternalDisplayId(display->id()))
continue;
if (!base::Contains(info_map, display->id()))
continue;
const TouchDeviceManager::TouchAssociationInfo& info =
info_map.at(display->id());
if (info.timestamp > most_recent_timestamp) {
display_info = display;
most_recent_timestamp = info.timestamp;
}
}
return display_info;
}
std::set<TouchDeviceIdentifier, TouchDeviceIdentifier::WeakComp>
GetCollisionSet(const DeviceList& devices) {
std::set<TouchDeviceIdentifier, TouchDeviceIdentifier::WeakComp>
collision_set;
std::set<TouchDeviceIdentifier, TouchDeviceIdentifier::WeakComp> ids;
for (const ui::TouchscreenDevice& device : devices) {
TouchDeviceIdentifier id = TouchDeviceIdentifier::FromDevice(device);
if (ids.find(id) != ids.end())
collision_set.insert(id);
else
ids.insert(id);
}
return collision_set;
}
}
const TouchDeviceIdentifier&
TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier() {
static const TouchDeviceIdentifier kFallTouchDeviceIdentifier(
GenerateIdentifier(kFallbackTouchDeviceName, 0, 0),
base::PersistentHash(kFallbackTouchDevicePhys));
return kFallTouchDeviceIdentifier;
}
uint32_t TouchDeviceIdentifier::GenerateIdentifier(std::string name,
uint16_t vendor_id,
uint16_t product_id) {
std::string hash_str = name + "-" + base::NumberToString(vendor_id) + "-" +
base::NumberToString(product_id);
return base::PersistentHash(hash_str);
}
TouchDeviceIdentifier TouchDeviceIdentifier::FromDevice(
const ui::TouchscreenDevice& touch_device) {
if (!touch_device.id)
return GetFallbackTouchDeviceIdentifier();
return TouchDeviceIdentifier(
GenerateIdentifier(touch_device.name, touch_device.vendor_id,
touch_device.product_id),
base::PersistentHash(touch_device.phys));
}
TouchDeviceIdentifier::TouchDeviceIdentifier(uint32_t identifier)
: id_(identifier),
secondary_id_(base::PersistentHash(kFallbackTouchDevicePhys)) {}
TouchDeviceIdentifier::TouchDeviceIdentifier(uint32_t identifier,
uint32_t secondary_id)
: id_(identifier), secondary_id_(secondary_id) {}
TouchDeviceIdentifier::TouchDeviceIdentifier(const TouchDeviceIdentifier& other)
: id_(other.id_), secondary_id_(other.secondary_id_) {}
TouchDeviceIdentifier& TouchDeviceIdentifier::operator=(
TouchDeviceIdentifier other) {
id_ = other.id_;
secondary_id_ = other.secondary_id_;
return *this;
}
std::string TouchDeviceIdentifier::ToString() const {
return base::NumberToString(id_);
}
std::string TouchDeviceIdentifier::SecondaryIdToString() const {
return base::NumberToString(secondary_id_);
}
bool TouchCalibrationData::CalibrationPointPairCompare(
const CalibrationPointPair& pair_1,
const CalibrationPointPair& pair_2) {
return pair_1.first < pair_2.first;
}
TouchCalibrationData::TouchCalibrationData() = default;
TouchCalibrationData::TouchCalibrationData(
const TouchCalibrationData::CalibrationPointPairQuad& point_pairs,
const gfx::Size& bounds)
: point_pairs(point_pairs), bounds(bounds) {}
TouchCalibrationData::TouchCalibrationData(
const TouchCalibrationData& calibration_data) = default;
TouchCalibrationData& TouchCalibrationData::operator=(
const TouchCalibrationData& calibration_data) = default;
bool TouchCalibrationData::operator==(const TouchCalibrationData& other) const {
if (bounds != other.bounds)
return false;
CalibrationPointPairQuad quad_1 = point_pairs;
CalibrationPointPairQuad quad_2 = other.point_pairs;
std::sort(quad_1.begin(), quad_1.end(), CalibrationPointPairCompare);
std::sort(quad_2.begin(), quad_2.end(), CalibrationPointPairCompare);
return quad_1 == quad_2;
}
bool TouchCalibrationData::IsEmpty() const {
return bounds.IsEmpty();
}
TouchDeviceManager::TouchDeviceManager() = default;
TouchDeviceManager::~TouchDeviceManager() = default;
void TouchDeviceManager::AssociateTouchscreens(
std::vector<ManagedDisplayInfo>* all_displays,
const std::vector<ui::TouchscreenDevice>& all_devices) {
active_touch_associations_.clear();
ManagedDisplayInfoList displays;
for (ManagedDisplayInfo& display : *all_displays) {
display.set_touch_support(Display::TouchSupport::UNAVAILABLE);
displays.push_back(&display);
}
DeviceList devices;
for (const ui::TouchscreenDevice& device : all_devices)
devices.push_back(device);
if (VLOG_IS_ON(2)) {
for (const ManagedDisplayInfo* display : displays) {
VLOG(2) << "Received display " << display->name()
<< " (size: " << display->GetNativeModeSize().ToString() << ", "
<< "sys_path: " << display->sys_path().LossyDisplayName() << ")";
}
for (const ui::TouchscreenDevice& device : devices) {
VLOG(2) << "Received device " << device.name
<< " (size: " << device.size.ToString()
<< ", sys_path: " << device.sys_path.LossyDisplayName() << ")";
}
}
AssociateInternalDevices(&displays, &devices);
AssociateDevicesWithCollision(&displays, &devices);
AssociateFromHistoricalData(&displays, &devices);
AssociateUsbDevices(&displays, &devices);
AssociateSameSizeDevices(&displays, &devices);
AssociateToSingleDisplay(&displays, &devices);
AssociateAnyRemainingDevices(&displays, &devices);
for (const ui::TouchscreenDevice& device : devices)
LOG(WARNING) << "Unmatched device " << device.name;
}
void TouchDeviceManager::AssociateInternalDevices(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
VLOG(2) << "Trying to match internal devices (" << displays->size()
<< " displays and " << devices->size() << " devices to match)";
ManagedDisplayInfo* internal_display = GetInternalDisplay(displays);
bool matched = false;
for (auto device_it = devices->begin(); device_it != devices->end();) {
const ui::TouchscreenDevice& internal_device = *device_it;
if (!IsInternalDevice(internal_device)) {
++device_it;
continue;
}
if (internal_display) {
VLOG(2) << "=> Matched device " << internal_device.name << " to display "
<< internal_display->name();
Associate(internal_display, internal_device);
matched = true;
} else {
VLOG(2) << "=> Removing internal device " << internal_device.name;
}
device_it = devices->erase(device_it);
}
if (!matched && internal_display) {
VLOG(2) << "=> No device found to match with internal display "
<< internal_display->name();
}
}
void TouchDeviceManager::AssociateDevicesWithCollision(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
if (!devices->size() || !displays->size())
return;
std::set<TouchDeviceIdentifier, TouchDeviceIdentifier::WeakComp>
collision_set = GetCollisionSet(*devices);
if (collision_set.empty())
return;
VLOG(2) << "Trying to match " << devices->size() << " devices "
<< "and " << displays->size() << " displays where there is/are "
<< collision_set.size() << " collisions with the touch device ids";
for (auto device_it = devices->begin(); device_it != devices->end();) {
const auto identifier = TouchDeviceIdentifier::FromDevice(*device_it);
if (!base::Contains(collision_set, identifier) ||
!base::Contains(port_associations_, identifier)) {
device_it++;
continue;
}
int64_t display_id = port_associations_.at(identifier);
ManagedDisplayInfoList::iterator display_it =
std::ranges::find(*displays, display_id, &ManagedDisplayInfo::id);
if (display_it != displays->end()) {
VLOG(2) << "=> Matched device " << (*device_it).name << " to display "
<< (*display_it)->name();
Associate(*display_it, *device_it);
device_it = devices->erase(device_it);
} else {
device_it++;
}
}
}
void TouchDeviceManager::AssociateFromHistoricalData(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
if (!devices->size() || !displays->size())
return;
VLOG(2) << "Trying to match " << devices->size() << " devices "
<< "and " << displays->size() << " displays based on historical "
<< "preferences.";
for (auto device_it = devices->begin(); device_it != devices->end();) {
auto* matched_display_info = GetBestMatchForDevice(
touch_associations_, TouchDeviceIdentifier::FromDevice(*device_it),
displays);
if (matched_display_info) {
VLOG(2) << "=> Matched device " << (*device_it).name << " to display "
<< matched_display_info->name();
Associate(matched_display_info, *device_it);
device_it = devices->erase(device_it);
} else {
device_it++;
}
}
}
void TouchDeviceManager::AssociateUsbDevices(ManagedDisplayInfoList* displays,
DeviceList* devices) {
VLOG(2) << "Trying to match usb devices (" << displays->size()
<< " displays and " << devices->size() << " devices to match)";
for (auto display_it = displays->begin(); display_it != displays->end();
display_it++) {
ManagedDisplayInfo* display = *display_it;
auto device_it = GuessBestUsbDevice(display, *devices);
if (device_it != devices->end()) {
const ui::TouchscreenDevice& device = *device_it;
VLOG(2) << "=> Matched device " << device.name << " to display "
<< display->name()
<< " (score=" << GetUsbAssociationScore(display, device) << ")";
Associate(display, device);
devices->erase(device_it);
}
}
}
void TouchDeviceManager::AssociateSameSizeDevices(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
VLOG(2) << "Trying to match same-size devices (" << displays->size()
<< " displays and " << devices->size() << " devices to match)";
for (auto display_it = displays->begin(); display_it != displays->end();
display_it++) {
ManagedDisplayInfo* display = *display_it;
if (IsInternalDisplay(display))
continue;
const gfx::Size native_size = display->GetNativeModeSize();
DeviceList::iterator device_it = std::ranges::find_if(
*devices, [&native_size](const ui::TouchscreenDevice& device) {
return std::abs(native_size.width() - device.size.width()) <= 1 &&
std::abs(native_size.height() - device.size.height()) <= 1;
});
if (device_it != devices->end()) {
const ui::TouchscreenDevice& device = *device_it;
VLOG(2) << "=> Matched device " << device.name << " to display "
<< display->name() << " (display_size: " << native_size.ToString()
<< ", device_size: " << device.size.ToString() << ")";
Associate(display, device);
device_it = devices->erase(device_it);
}
}
}
void TouchDeviceManager::AssociateToSingleDisplay(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
VLOG(2) << "Trying to match to single display (" << displays->size()
<< " displays and " << devices->size() << " devices to match)";
std::size_t num_displays_excluding_internal = displays->size();
ManagedDisplayInfo* internal_display = GetInternalDisplay(displays);
if (internal_display)
num_displays_excluding_internal--;
if (num_displays_excluding_internal != 1 || devices->empty())
return;
ManagedDisplayInfo* display = *displays->begin();
if (display == internal_display)
display = (*displays)[1];
for (const ui::TouchscreenDevice& device : *devices) {
VLOG(2) << "=> Matched device " << device.name << " to display "
<< display->name();
Associate(display, device);
}
devices->clear();
}
void TouchDeviceManager::AssociateAnyRemainingDevices(
ManagedDisplayInfoList* displays,
DeviceList* devices) {
if (!displays->size() || !devices->size())
return;
VLOG(2) << "Trying to match remaining " << devices->size()
<< " devices to a display.";
ManagedDisplayInfo* display = GetInternalDisplay(displays);
if (!display) {
display = *(displays->begin());
VLOG(2) << "Could not find any internal display. Matching all devices to a "
<< "random non internal display.";
}
VLOG(2) << "Matching " << devices->size() << " touch devices to "
<< display->name() << "[" << display->id() << "]";
for (auto device_it = devices->begin(); device_it != devices->end();) {
if ((*device_it).type == ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
device_it++;
continue;
}
VLOG(2) << "=> Matched " << (*device_it).name << " to " << display->name();
Associate(display, *device_it);
device_it = devices->erase(device_it);
}
}
void TouchDeviceManager::Associate(ManagedDisplayInfo* display,
const ui::TouchscreenDevice& device) {
display->set_touch_support(Display::TouchSupport::AVAILABLE);
active_touch_associations_[TouchDeviceIdentifier::FromDevice(device)] =
display->id();
}
void TouchDeviceManager::AddTouchCalibrationData(
const ui::TouchscreenDevice& device,
int64_t display_id,
const TouchCalibrationData& data) {
AddTouchCalibrationDataImpl(device, display_id, &data);
}
void TouchDeviceManager::AddTouchAssociation(
const ui::TouchscreenDevice& device,
int64_t display_id) {
AddTouchCalibrationDataImpl(device, display_id, nullptr);
}
void TouchDeviceManager::AddTouchCalibrationDataImpl(
const ui::TouchscreenDevice& device,
int64_t display_id,
const TouchCalibrationData* data) {
const TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(device);
active_touch_associations_[identifier] = display_id;
auto& association_info_map = touch_associations_[identifier];
auto it = association_info_map.find(display_id);
if (it != association_info_map.end()) {
if (data) {
it->second.calibration_data = *data;
it->second.timestamp = base::Time::Now();
}
} else {
TouchAssociationInfo info;
info.timestamp = base::Time::Now();
if (data) {
info.calibration_data = *data;
}
association_info_map.emplace(display_id, info);
}
port_associations_[identifier] = display_id;
}
void TouchDeviceManager::ClearTouchCalibrationData(
const ui::TouchscreenDevice& device,
int64_t display_id) {
const TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(device);
if (base::Contains(touch_associations_, identifier)) {
ClearCalibrationDataInMap(touch_associations_.at(identifier), display_id);
}
}
void TouchDeviceManager::ClearAllTouchCalibrationData(int64_t display_id) {
for (auto it : touch_associations_) {
ClearCalibrationDataInMap(it.second, display_id);
}
}
TouchCalibrationData TouchDeviceManager::GetCalibrationData(
const ui::TouchscreenDevice& touchscreen,
int64_t display_id) const {
TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(touchscreen);
if (display_id == kInvalidDisplayId) {
if (!base::Contains(active_touch_associations_, identifier))
return TouchCalibrationData();
display_id = active_touch_associations_.at(identifier);
}
if (base::Contains(touch_associations_, identifier)) {
const AssociationInfoMap& info_map = touch_associations_.at(identifier);
if (info_map.find(display_id) != info_map.end())
return info_map.at(display_id).calibration_data;
}
TouchDeviceIdentifier fallback_identifier(
TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier());
if (base::Contains(touch_associations_, fallback_identifier)) {
const AssociationInfoMap& info_map =
touch_associations_.at(fallback_identifier);
if (info_map.find(display_id) != info_map.end())
return info_map.at(display_id).calibration_data;
}
return TouchCalibrationData();
}
bool TouchDeviceManager::DisplayHasTouchDevice(
int64_t display_id,
const ui::TouchscreenDevice& device) const {
const TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(device);
return base::Contains(active_touch_associations_, identifier) &&
active_touch_associations_.at(identifier) == display_id;
}
int64_t TouchDeviceManager::GetAssociatedDisplay(
const ui::TouchscreenDevice& device) const {
const TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(device);
if (base::Contains(active_touch_associations_, identifier))
return active_touch_associations_.at(identifier);
return kInvalidDisplayId;
}
std::vector<ui::TouchscreenDevice>
TouchDeviceManager::GetAssociatedTouchDevicesForDisplay(
int64_t display_id) const {
std::vector<ui::TouchscreenDevice> result;
for (const auto& device :
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
const TouchDeviceIdentifier identifier =
TouchDeviceIdentifier::FromDevice(device);
const auto it = active_touch_associations_.find(identifier);
if (it != active_touch_associations_.end() && it->second == display_id)
result.push_back(device);
}
return result;
}
void TouchDeviceManager::RegisterTouchAssociations(
const TouchAssociationMap& touch_associations,
const PortAssociationMap& port_associations) {
touch_associations_ = touch_associations;
port_associations_ = port_associations;
}
std::ostream& operator<<(std::ostream& os,
const TouchDeviceIdentifier& identifier) {
return os << identifier.ToString() << " [" << identifier.SecondaryIdToString()
<< "]";
}
bool HasExternalTouchscreenDevice() {
for (const auto& device :
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
if (device.type == ui::InputDeviceType::INPUT_DEVICE_USB ||
device.type == ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH) {
return true;
}
}
return false;
}
}