#include "ui/events/devices/x11/touch_factory_x11.h"
#include <stddef.h>
#include <algorithm>
#include <string_view>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/system/sys_info.h"
#include "build/build_config.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/devices/x11/device_data_manager_x11.h"
#include "ui/events/devices/x11/device_list_cache_x11.h"
#include "ui/events/devices/x11/xinput_util.h"
#include "ui/events/event_switches.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/future.h"
namespace ui {
namespace {
void AddPointerDevicesFromString(
const std::string& pointer_devices,
EventPointerType type,
std::vector<std::pair<int, EventPointerType>>* devices) {
for (std::string_view dev : base::SplitStringPiece(
pointer_devices, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
int devid;
if (base::StringToInt(dev, &devid))
devices->push_back({devid, type});
else
DLOG(WARNING) << "Invalid device id: " << dev;
}
}
}
TouchFactory::TouchFactory()
: pointer_device_lookup_(),
touch_device_list_(),
id_generator_(0),
touch_screens_enabled_(true) {
DeviceDataManagerX11::CreateInstance();
if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
return;
UpdateDeviceList(x11::Connection::Get());
}
TouchFactory::~TouchFactory() = default;
TouchFactory* TouchFactory::GetInstance() {
return base::Singleton<TouchFactory>::get();
}
void TouchFactory::SetTouchDeviceListFromCommandLine() {
std::vector<std::pair<int, EventPointerType>> devices;
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
AddPointerDevicesFromString(
command_line->GetSwitchValueASCII(switches::kTouchDevices),
EventPointerType::kTouch, &devices);
AddPointerDevicesFromString(
command_line->GetSwitchValueASCII(switches::kPenDevices),
EventPointerType::kPen, &devices);
if (!devices.empty())
ui::TouchFactory::GetInstance()->SetTouchDeviceList(devices);
}
void TouchFactory::UpdateDeviceList(x11::Connection* connection) {
touch_device_lookup_.clear();
touch_device_list_.clear();
touchscreen_ids_.clear();
if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
return;
pointer_device_lookup_.clear();
const XIDeviceList& xi_dev_list =
DeviceListCacheX11::GetInstance()->GetXI2DeviceList(connection);
for (const auto& devinfo : xi_dev_list) {
if (devinfo.type == x11::Input::DeviceType::FloatingSlave ||
devinfo.type == x11::Input::DeviceType::MasterPointer ||
devinfo.type == x11::Input::DeviceType::SlavePointer) {
for (const auto& xiclassinfo : devinfo.classes) {
if (!xiclassinfo.touch.has_value())
continue;
auto& tci = *xiclassinfo.touch;
if (tci.mode != x11::Input::TouchMode::Direct)
continue;
auto master_id = devinfo.type == x11::Input::DeviceType::SlavePointer
? devinfo.attachment
: devinfo.deviceid;
touch_device_lookup_.insert(master_id);
touch_device_list_[master_id] = {true, EventPointerType::kTouch};
if (devinfo.type != x11::Input::DeviceType::MasterPointer)
CacheTouchscreenIds(devinfo.deviceid);
if (devinfo.type == x11::Input::DeviceType::MasterPointer) {
device_master_id_list_[devinfo.deviceid] = master_id;
touch_device_lookup_.insert(devinfo.deviceid);
touch_device_list_[devinfo.deviceid] = {false,
EventPointerType::kTouch};
}
}
if (devinfo.type != x11::Input::DeviceType::SlavePointer) {
pointer_device_lookup_.insert(devinfo.deviceid);
}
} else if (devinfo.type == x11::Input::DeviceType::MasterKeyboard) {
virtual_core_keyboard_device_ = devinfo.deviceid;
}
}
}
bool TouchFactory::ShouldProcessDeviceEvent(
const x11::Input::DeviceEvent& xiev) {
const bool is_touch_disabled = !touch_screens_enabled_;
if (xiev.opcode == x11::Input::DeviceEvent::TouchBegin ||
xiev.opcode == x11::Input::DeviceEvent::TouchUpdate ||
xiev.opcode == x11::Input::DeviceEvent::TouchEnd) {
bool is_from_master_or_float = touch_device_list_[xiev.deviceid].is_master;
bool is_from_slave_device =
!is_from_master_or_float && xiev.sourceid == xiev.deviceid;
return !is_touch_disabled && IsTouchDevice(xiev.deviceid) &&
!is_from_slave_device;
}
if (xiev.opcode == x11::Input::DeviceEvent::KeyPress ||
xiev.opcode == x11::Input::DeviceEvent::KeyRelease) {
return !virtual_core_keyboard_device_.has_value() ||
*virtual_core_keyboard_device_ == xiev.deviceid;
}
return ShouldProcessEventForDevice(xiev.deviceid);
}
bool TouchFactory::ShouldProcessCrossingEvent(
const x11::Input::CrossingEvent& xiev) {
if (xiev.opcode != x11::Input::CrossingEvent::Enter &&
xiev.opcode != x11::Input::CrossingEvent::Leave) {
return true;
}
return ShouldProcessEventForDevice(xiev.deviceid);
}
void TouchFactory::SetupXI2ForXWindow(x11::Window window) {
auto* connection = x11::Connection::Get();
x11::Input::EventMask mask{x11::Input::DeviceId::AllMaster};
mask.mask.push_back({});
auto* mask_data = mask.mask.data();
SetXinputMask(mask_data, x11::Input::CrossingEvent::Enter);
SetXinputMask(mask_data, x11::Input::CrossingEvent::Leave);
SetXinputMask(mask_data, x11::Input::CrossingEvent::FocusIn);
SetXinputMask(mask_data, x11::Input::CrossingEvent::FocusOut);
SetXinputMask(mask_data, x11::Input::DeviceEvent::ButtonPress);
SetXinputMask(mask_data, x11::Input::DeviceEvent::ButtonRelease);
SetXinputMask(mask_data, x11::Input::DeviceEvent::Motion);
SetXinputMask(mask_data, x11::Input::DeviceEvent::KeyPress);
SetXinputMask(mask_data, x11::Input::DeviceEvent::KeyRelease);
x11::Input::EventMask touch_mask{x11::Input::DeviceId::All};
touch_mask.mask.push_back({});
auto* touch_mask_data = touch_mask.mask.data();
SetXinputMask(touch_mask_data, x11::Input::DeviceEvent::TouchBegin);
SetXinputMask(touch_mask_data, x11::Input::DeviceEvent::TouchUpdate);
SetXinputMask(touch_mask_data, x11::Input::DeviceEvent::TouchEnd);
connection->xinput().XISelectEvents({window, {mask, touch_mask}});
connection->Flush();
}
void TouchFactory::SetTouchDeviceList(
const std::vector<std::pair<int, EventPointerType>>& devices) {
touch_device_lookup_.clear();
touch_device_list_.clear();
for (auto& device : devices) {
int device_int = device.first;
auto device_id = static_cast<x11::Input::DeviceId>(device_int);
EventPointerType type = device.second;
touch_device_lookup_.insert(device_id);
touch_device_list_[device_id] = {false, type};
if (device_master_id_list_.find(device_id) !=
device_master_id_list_.end()) {
touch_device_lookup_.insert(device_master_id_list_[device_id]);
touch_device_list_[device_master_id_list_[device_id]] = {false, type};
}
}
}
bool TouchFactory::IsTouchDevice(x11::Input::DeviceId deviceid) const {
return touch_device_lookup_.contains(deviceid);
}
bool TouchFactory::IsMultiTouchDevice(x11::Input::DeviceId deviceid) const {
return touch_device_lookup_.contains(deviceid) &&
touch_device_list_.at(deviceid).is_master;
}
EventPointerType TouchFactory::GetTouchDevicePointerType(
x11::Input::DeviceId deviceid) const {
DCHECK(IsTouchDevice(deviceid));
return touch_device_list_.find(deviceid)->second.pointer_type;
}
bool TouchFactory::QuerySlotForTrackingID(uint32_t tracking_id, int* slot) {
if (!id_generator_.HasGeneratedIDFor(tracking_id))
return false;
*slot = GetSlotForTrackingID(tracking_id);
return true;
}
int TouchFactory::GetSlotForTrackingID(uint32_t tracking_id) {
return id_generator_.GetGeneratedID(tracking_id);
}
void TouchFactory::ReleaseSlot(int slot) {
id_generator_.ReleaseID(slot);
}
bool TouchFactory::IsTouchDevicePresent() {
return touch_screens_enabled_ && !touch_device_lookup_.empty();
}
void TouchFactory::ResetForTest() {
pointer_device_lookup_.clear();
touch_device_lookup_.clear();
touch_device_list_.clear();
touchscreen_ids_.clear();
id_generator_.ResetForTest();
SetTouchscreensEnabled(true);
}
void TouchFactory::SetTouchDeviceForTest(const std::vector<int>& devices) {
touch_device_lookup_.clear();
touch_device_list_.clear();
for (int device_id : devices) {
auto device = static_cast<x11::Input::DeviceId>(device_id);
touch_device_lookup_.insert(device);
touch_device_list_[device] = {true, EventPointerType::kTouch};
}
SetTouchscreensEnabled(true);
}
void TouchFactory::SetPointerDeviceForTest(const std::vector<int>& devices) {
pointer_device_lookup_.clear();
for (int device : devices)
pointer_device_lookup_.insert(static_cast<x11::Input::DeviceId>(device));
}
void TouchFactory::SetTouchscreensEnabled(bool enabled) {
touch_screens_enabled_ = enabled;
DeviceDataManager::GetInstance()->SetTouchscreensEnabled(enabled);
}
void TouchFactory::CacheTouchscreenIds(x11::Input::DeviceId device_id) {
if (!DeviceDataManager::HasInstance())
return;
std::vector<TouchscreenDevice> touchscreens =
DeviceDataManager::GetInstance()->GetTouchscreenDevices();
const auto it = std::ranges::find(touchscreens, static_cast<int>(device_id),
&TouchscreenDevice::id);
if (it != touchscreens.end() && it->vendor_id && it->product_id)
touchscreen_ids_.emplace(it->vendor_id, it->product_id);
}
bool TouchFactory::ShouldProcessEventForDevice(
x11::Input::DeviceId device_id) const {
if (!pointer_device_lookup_.contains(device_id)) {
return false;
}
return IsTouchDevice(device_id) ? touch_screens_enabled_ : true;
}
}