[go: nahoru, domu]

blob: 4bc3fa2c111161128cc390efa25e6f77d0b43d29 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/system/input_device_settings/input_device_notifier.h"
#include "ash/bluetooth_devices_observer.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/system/input_device_settings/input_device_settings_utils.h"
#include "base/containers/cxx20_erase_vector.h"
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_map.h"
#include "base/ranges/algorithm.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/bluetooth_device.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/devices/touchpad_device.h"
namespace ash {
namespace {
using DeviceId = InputDeviceSettingsController::DeviceId;
DeviceId ExtractDeviceIdFromInputDevice(const ui::InputDevice& device) {
return device.id;
}
template <class DeviceMojomPtr>
bool IsDeviceASuspectedImposter(BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
return device.suspected_imposter;
}
template <>
bool IsDeviceASuspectedImposter<mojom::KeyboardPtr>(
BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
// If the device is a keyboard that is known to pretend to have mouse-like
// functionality, do not use the `suspected_imposter` field.
if (IsKeyboardPretendingToBeMouse(device)) {
return false;
}
// If the device is bluetooth, check the bluetooth device to see if it is a
// keyboard or keyboard/mouse combo.
if (device.type == ui::INPUT_DEVICE_BLUETOOTH) {
const auto* bluetooth_device =
bluetooth_observer->GetConnectedBluetoothDevice(device);
if (!bluetooth_device) {
return false;
}
if (bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD ||
bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO) {
return false;
}
return true;
}
return device.suspected_imposter;
}
template <>
bool IsDeviceASuspectedImposter<mojom::MousePtr>(
BluetoothDevicesObserver* bluetooth_observer,
const ui::InputDevice& device) {
// If the device is a keyboard that is known to pretend to have mouse-like
// functionality, the device should always be considered an imposter.
if (IsKeyboardPretendingToBeMouse(device)) {
return true;
}
// If the device is bluetooth, check the bluetooth device to see if it is a
// mouse or mouse/keyboard combo.
if (device.type == ui::INPUT_DEVICE_BLUETOOTH) {
const auto* bluetooth_device =
bluetooth_observer->GetConnectedBluetoothDevice(device);
if (!bluetooth_device) {
return false;
}
if (bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::MOUSE ||
bluetooth_device->GetDeviceType() ==
device::BluetoothDeviceType::KEYBOARD_MOUSE_COMBO) {
return false;
}
return true;
}
return device.suspected_imposter;
}
template <typename T>
DeviceId ExtractDeviceIdFromDeviceMapPair(
const std::pair<DeviceId, T>& id_t_pair) {
return id_t_pair.first;
}
// Figures out which devices from `connected_devices` have been added/removed
// and stores them in the passed in vectors. `devices_to_add` and
// `devices_to_remove` will be cleared before being filled with the result.
template <class DeviceMojomPtr, typename InputDeviceType>
void GetAddedAndRemovedDevices(
BluetoothDevicesObserver* bluetooth_observer,
std::vector<InputDeviceType> updated_device_list,
const base::flat_map<DeviceId, DeviceMojomPtr>& connected_devices,
std::vector<InputDeviceType>* devices_to_add,
std::vector<DeviceId>* devices_to_remove) {
// Output parameter vectors must be empty to start.
devices_to_add->clear();
devices_to_remove->clear();
// Sort input device list by id as `base::ranges::set_difference` requires
// input lists are sorted.
// Remove any devices marked as imposters as well.
base::ranges::sort(updated_device_list, base::ranges::less(),
ExtractDeviceIdFromInputDevice);
base::EraseIf(updated_device_list, [&](const ui::InputDevice& device) {
return IsDeviceASuspectedImposter<DeviceMojomPtr>(bluetooth_observer,
device);
});
// Generate a vector with only the device ids from the connected_devices
// map. Guaranteed to be sorted as flat_map is always in sorted order by
// key.
std::vector<DeviceId> connected_devices_ids;
connected_devices_ids.reserve(connected_devices.size());
base::ranges::transform(connected_devices,
std::back_inserter(connected_devices_ids),
ExtractDeviceIdFromDeviceMapPair<DeviceMojomPtr>);
DCHECK(base::ranges::is_sorted(connected_devices_ids));
// Compares the `id` field of `updated_device_list` to the ids in
// `connected_devices_ids`. Devices that are in `updated_device_list` but not
// in `connected_devices_ids` are inserted into `devices_to_add`.
// `updated_device_list` and `connected_device_ids` must be sorted.
base::ranges::set_difference(updated_device_list, connected_devices_ids,
std::back_inserter(*devices_to_add),
/*Comp=*/base::ranges::less(),
/*Proj1=*/ExtractDeviceIdFromInputDevice);
// Compares the `connected_devices_ids` to the id field of
// `updated_device_list`. Ids that are in `connected_devices_ids` but not in
// `updated_device_list` are inserted into `devices_to_remove`.
// `updated_device_list` and `connected_device_ids` must be sorted.
base::ranges::set_difference(connected_devices_ids, updated_device_list,
std::back_inserter(*devices_to_remove),
/*Comp=*/base::ranges::less(),
/*Proj1=*/base::identity(),
/*Proj2=*/ExtractDeviceIdFromInputDevice);
}
} // namespace
template <typename MojomDevicePtr, typename InputDeviceType>
InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::InputDeviceNotifier(
base::flat_map<DeviceId, MojomDevicePtr>* connected_devices,
InputDeviceListsUpdatedCallback callback)
: connected_devices_(connected_devices),
device_lists_updated_callback_(callback) {
DCHECK(connected_devices_);
ui::DeviceDataManager::GetInstance()->AddObserver(this);
bluetooth_devices_observer_ =
std::make_unique<BluetoothDevicesObserver>(base::BindRepeating(
&InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnBluetoothAdapterOrDeviceChanged,
base::Unretained(this)));
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::~InputDeviceNotifier() {
ui::DeviceDataManager::GetInstance()->RemoveObserver(this);
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::RefreshDevices() {
std::vector<InputDeviceType> devices_to_add;
std::vector<DeviceId> device_ids_to_remove;
GetAddedAndRemovedDevices(bluetooth_devices_observer_.get(),
GetUpdatedDeviceList(), *connected_devices_,
&devices_to_add, &device_ids_to_remove);
device_lists_updated_callback_.Run(std::move(devices_to_add),
std::move(device_ids_to_remove));
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr,
InputDeviceType>::OnDeviceListsComplete() {
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnInputDeviceConfigurationChanged(uint8_t input_device_type) {
RefreshDevices();
}
template <typename MojomDevicePtr, typename InputDeviceType>
void InputDeviceNotifier<MojomDevicePtr, InputDeviceType>::
OnBluetoothAdapterOrDeviceChanged(device::BluetoothDevice* device) {
RefreshDevices();
}
// Template specialization for retrieving the updated device lists for each
// device type.
template <>
std::vector<ui::KeyboardDevice>
InputDeviceNotifier<mojom::KeyboardPtr,
ui::KeyboardDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetKeyboardDevices();
}
template <>
std::vector<ui::TouchpadDevice>
InputDeviceNotifier<mojom::TouchpadPtr,
ui::TouchpadDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetTouchpadDevices();
}
template <>
std::vector<ui::InputDevice>
InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>::GetUpdatedDeviceList() {
auto mice = ui::DeviceDataManager::GetInstance()->GetMouseDevices();
base::EraseIf(mice, [](const auto& mouse) {
// Some I2C touchpads falsely claim to be mice, see b/205272718
// By filtering out internal mice, i2c touchpads are prevented from being in
// the "mouse" category in settings.
return mouse.type == ui::INPUT_DEVICE_INTERNAL;
});
return mice;
}
template <>
std::vector<ui::InputDevice>
InputDeviceNotifier<mojom::PointingStickPtr,
ui::InputDevice>::GetUpdatedDeviceList() {
return ui::DeviceDataManager::GetInstance()->GetPointingStickDevices();
}
// Explicit instantiations for each device type.
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::KeyboardPtr, ui::KeyboardDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::TouchpadPtr, ui::TouchpadDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>;
template class EXPORT_TEMPLATE_DECLARE(ASH_EXPORT)
InputDeviceNotifier<mojom::PointingStickPtr, ui::InputDevice>;
} // namespace ash