| // Copyright (c) 2012 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 "ui/events/devices/x11/touch_factory_x11.h" |
| |
| #include <X11/Xatom.h> |
| #include <X11/cursorfont.h> |
| #include <X11/extensions/XInput.h> |
| #include <X11/extensions/XInput2.h> |
| #include <X11/extensions/XIproto.h> |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/logging.h" |
| #include "base/memory/singleton.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/sys_info.h" |
| #include "ui/events/devices/x11/device_data_manager_x11.h" |
| #include "ui/events/devices/x11/device_list_cache_x11.h" |
| #include "ui/events/event_switches.h" |
| #include "ui/gfx/x/x11_types.h" |
| |
| namespace ui { |
| |
| TouchFactory::TouchFactory() |
| : pointer_device_lookup_(), |
| touch_events_disabled_(false), |
| touch_device_list_(), |
| virtual_core_keyboard_device_(-1), |
| id_generator_(0) { |
| if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available()) |
| return; |
| |
| XDisplay* display = gfx::GetXDisplay(); |
| UpdateDeviceList(display); |
| |
| base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); |
| touch_events_disabled_ = cmdline->HasSwitch(switches::kTouchEvents) && |
| cmdline->GetSwitchValueASCII(switches::kTouchEvents) == |
| switches::kTouchEventsDisabled; |
| } |
| |
| TouchFactory::~TouchFactory() { |
| } |
| |
| // static |
| TouchFactory* TouchFactory::GetInstance() { |
| return Singleton<TouchFactory>::get(); |
| } |
| |
| // static |
| void TouchFactory::SetTouchDeviceListFromCommandLine() { |
| // Get a list of pointer-devices that should be treated as touch-devices. |
| // This is primarily used for testing/debugging touch-event processing when a |
| // touch-device isn't available. |
| std::string touch_devices = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| switches::kTouchDevices); |
| |
| if (!touch_devices.empty()) { |
| std::vector<std::string> devs = base::SplitString( |
| touch_devices, ",", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| std::vector<int> device_ids; |
| int devid; |
| for (std::vector<std::string>::iterator iter = devs.begin(); |
| iter != devs.end(); ++iter) { |
| if (base::StringToInt(*iter, reinterpret_cast<int*>(&devid))) |
| device_ids.push_back(devid); |
| else |
| DLOG(WARNING) << "Invalid touch-device id: " << *iter; |
| } |
| ui::TouchFactory::GetInstance()->SetTouchDeviceList(device_ids); |
| } |
| } |
| |
| void TouchFactory::UpdateDeviceList(Display* display) { |
| // Detect touch devices. |
| touch_device_lookup_.reset(); |
| touch_device_list_.clear(); |
| touchscreen_ids_.clear(); |
| |
| if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available()) |
| return; |
| |
| // Instead of asking X for the list of devices all the time, let's maintain a |
| // list of pointer devices we care about. |
| // It should not be necessary to select for slave devices. XInput2 provides |
| // enough information to the event callback to decide which slave device |
| // triggered the event, thus decide whether the 'pointer event' is a |
| // 'mouse event' or a 'touch event'. |
| // However, on some desktops, some events from a master pointer are |
| // not delivered to the client. So we select for slave devices instead. |
| // If the touch device has 'GrabDevice' set and 'SendCoreEvents' unset (which |
| // is possible), then the device is detected as a floating device, and a |
| // floating device is not connected to a master device. So it is necessary to |
| // also select on the floating devices. |
| pointer_device_lookup_.reset(); |
| const XIDeviceList& xi_dev_list = |
| DeviceListCacheX11::GetInstance()->GetXI2DeviceList(display); |
| for (int i = 0; i < xi_dev_list.count; i++) { |
| const XIDeviceInfo& devinfo = xi_dev_list[i]; |
| if (devinfo.use == XIFloatingSlave || devinfo.use == XIMasterPointer) { |
| for (int k = 0; k < devinfo.num_classes; ++k) { |
| XIAnyClassInfo* xiclassinfo = devinfo.classes[k]; |
| if (xiclassinfo->type == XITouchClass) { |
| XITouchClassInfo* tci = |
| reinterpret_cast<XITouchClassInfo*>(xiclassinfo); |
| // Only care direct touch device (such as touch screen) right now |
| if (tci->mode == XIDirectTouch) { |
| touch_device_lookup_[devinfo.deviceid] = true; |
| touch_device_list_[devinfo.deviceid] = true; |
| } |
| } |
| } |
| pointer_device_lookup_[devinfo.deviceid] = true; |
| } else if (devinfo.use == XIMasterKeyboard) { |
| virtual_core_keyboard_device_ = devinfo.deviceid; |
| } |
| |
| if (devinfo.use == XIFloatingSlave || devinfo.use == XISlavePointer) { |
| for (int k = 0; k < devinfo.num_classes; ++k) { |
| XIAnyClassInfo* xiclassinfo = devinfo.classes[k]; |
| if (xiclassinfo->type == XITouchClass) { |
| XITouchClassInfo* tci = |
| reinterpret_cast<XITouchClassInfo*>(xiclassinfo); |
| // Only care direct touch device (such as touch screen) right now |
| if (tci->mode == XIDirectTouch) |
| CacheTouchscreenIds(devinfo.deviceid); |
| if (devinfo.use == XISlavePointer) { |
| device_master_id_list_[devinfo.deviceid] = devinfo.attachment; |
| // If the slave device is direct touch device, we also set its |
| // master device to be touch device. |
| touch_device_lookup_[devinfo.attachment] = true; |
| touch_device_list_[devinfo.attachment] = true; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| bool TouchFactory::ShouldProcessXI2Event(XEvent* xev) { |
| DCHECK_EQ(GenericEvent, xev->type); |
| XIEvent* event = static_cast<XIEvent*>(xev->xcookie.data); |
| XIDeviceEvent* xiev = reinterpret_cast<XIDeviceEvent*>(event); |
| |
| if (event->evtype == XI_TouchBegin || |
| event->evtype == XI_TouchUpdate || |
| event->evtype == XI_TouchEnd) { |
| // Since SetupXI2ForXWindow() selects events from all devices, for a |
| // touchscreen attached to a master pointer device, X11 sends two |
| // events for each touch: one from the slave (deviceid == the id of |
| // the touchscreen device), and one from the master (deviceid == the |
| // id of the master pointer device). Instead of processing both |
| // events, discard the event that comes from the slave, and only |
| // allow processing the event coming from the master. |
| // For a 'floating' touchscreen device, X11 sends only one event for |
| // each touch, with both deviceid and sourceid set to the id of the |
| // touchscreen device. |
| bool is_from_master_or_float = touch_device_list_[xiev->deviceid]; |
| bool is_from_slave_device = !is_from_master_or_float |
| && xiev->sourceid == xiev->deviceid; |
| return !touch_events_disabled_ && |
| IsTouchDevice(xiev->deviceid) && |
| !is_from_slave_device; |
| } |
| |
| // Make sure only key-events from the virtual core keyboard are processed. |
| if (event->evtype == XI_KeyPress || event->evtype == XI_KeyRelease) { |
| return (virtual_core_keyboard_device_ < 0) || |
| (virtual_core_keyboard_device_ == xiev->deviceid); |
| } |
| |
| if (event->evtype != XI_ButtonPress && |
| event->evtype != XI_ButtonRelease && |
| event->evtype != XI_Motion) |
| return true; |
| |
| if (!pointer_device_lookup_[xiev->deviceid]) |
| return false; |
| |
| return IsTouchDevice(xiev->deviceid) ? !touch_events_disabled_ : true; |
| } |
| |
| void TouchFactory::SetupXI2ForXWindow(Window window) { |
| // Setup mask for mouse events. It is possible that a device is loaded/plugged |
| // in after we have setup XInput2 on a window. In such cases, we need to |
| // either resetup XInput2 for the window, so that we get events from the new |
| // device, or we need to listen to events from all devices, and then filter |
| // the events from uninteresting devices. We do the latter because that's |
| // simpler. |
| |
| XDisplay* display = gfx::GetXDisplay(); |
| |
| unsigned char mask[XIMaskLen(XI_LASTEVENT)]; |
| memset(mask, 0, sizeof(mask)); |
| |
| XISetMask(mask, XI_TouchBegin); |
| XISetMask(mask, XI_TouchUpdate); |
| XISetMask(mask, XI_TouchEnd); |
| |
| XISetMask(mask, XI_ButtonPress); |
| XISetMask(mask, XI_ButtonRelease); |
| XISetMask(mask, XI_Motion); |
| #if defined(OS_CHROMEOS) |
| // XGrabKey() must be replaced with XI2 keyboard grab if XI2 key events are |
| // enabled on desktop Linux. |
| if (base::SysInfo::IsRunningOnChromeOS()) { |
| XISetMask(mask, XI_KeyPress); |
| XISetMask(mask, XI_KeyRelease); |
| } |
| #endif |
| |
| XIEventMask evmask; |
| evmask.deviceid = XIAllDevices; |
| evmask.mask_len = sizeof(mask); |
| evmask.mask = mask; |
| XISelectEvents(display, window, &evmask, 1); |
| XFlush(display); |
| } |
| |
| void TouchFactory::SetTouchDeviceList(const std::vector<int>& devices) { |
| touch_device_lookup_.reset(); |
| touch_device_list_.clear(); |
| for (int deviceid : devices) { |
| DCHECK(IsValidDevice(deviceid)); |
| touch_device_lookup_[deviceid] = true; |
| touch_device_list_[deviceid] = false; |
| if (device_master_id_list_.find(deviceid) != device_master_id_list_.end()) { |
| // When we set the device through the "--touch-devices" flag to slave |
| // touch device, we also set its master device to be touch device. |
| touch_device_lookup_[device_master_id_list_[deviceid]] = true; |
| touch_device_list_[device_master_id_list_[deviceid]] = false; |
| } |
| } |
| } |
| |
| bool TouchFactory::IsValidDevice(int deviceid) const { |
| return (deviceid >= 0) && |
| (static_cast<size_t>(deviceid) < touch_device_lookup_.size()); |
| } |
| |
| bool TouchFactory::IsTouchDevice(int deviceid) const { |
| return IsValidDevice(deviceid) ? touch_device_lookup_[deviceid] : false; |
| } |
| |
| bool TouchFactory::IsMultiTouchDevice(int deviceid) const { |
| return (IsValidDevice(deviceid) && touch_device_lookup_[deviceid]) |
| ? touch_device_list_.find(deviceid)->second |
| : false; |
| } |
| |
| bool TouchFactory::QuerySlotForTrackingID(uint32 tracking_id, int* slot) { |
| if (!id_generator_.HasGeneratedIDFor(tracking_id)) |
| return false; |
| *slot = static_cast<int>(id_generator_.GetGeneratedID(tracking_id)); |
| return true; |
| } |
| |
| int TouchFactory::GetSlotForTrackingID(uint32 tracking_id) { |
| return id_generator_.GetGeneratedID(tracking_id); |
| } |
| |
| void TouchFactory::ReleaseSlotForTrackingID(uint32 tracking_id) { |
| id_generator_.ReleaseNumber(tracking_id); |
| } |
| |
| bool TouchFactory::IsTouchDevicePresent() { |
| return !touch_events_disabled_ && touch_device_lookup_.any(); |
| } |
| |
| void TouchFactory::ResetForTest() { |
| pointer_device_lookup_.reset(); |
| touch_device_lookup_.reset(); |
| touch_events_disabled_ = false; |
| touch_device_list_.clear(); |
| touchscreen_ids_.clear(); |
| id_generator_.ResetForTest(); |
| } |
| |
| void TouchFactory::SetTouchDeviceForTest( |
| const std::vector<int>& devices) { |
| touch_device_lookup_.reset(); |
| touch_device_list_.clear(); |
| for (std::vector<int>::const_iterator iter = devices.begin(); |
| iter != devices.end(); ++iter) { |
| DCHECK(IsValidDevice(*iter)); |
| touch_device_lookup_[*iter] = true; |
| touch_device_list_[*iter] = true; |
| } |
| touch_events_disabled_ = false; |
| } |
| |
| void TouchFactory::SetPointerDeviceForTest( |
| const std::vector<int>& devices) { |
| pointer_device_lookup_.reset(); |
| for (std::vector<int>::const_iterator iter = devices.begin(); |
| iter != devices.end(); ++iter) { |
| pointer_device_lookup_[*iter] = true; |
| } |
| } |
| |
| void TouchFactory::CacheTouchscreenIds(int device_id) { |
| if (!DeviceDataManager::HasInstance()) |
| return; |
| std::vector<TouchscreenDevice> touchscreens = |
| DeviceDataManager::GetInstance()->touchscreen_devices(); |
| const auto it = |
| std::find_if(touchscreens.begin(), touchscreens.end(), |
| [device_id](const TouchscreenDevice& touchscreen) { |
| return touchscreen.id == device_id; |
| }); |
| // Internal displays will have a vid and pid of 0. Ignore them. |
| if (it != touchscreens.end() && it->vendor_id && it->product_id) |
| touchscreen_ids_.insert(std::make_pair(it->vendor_id, it->product_id)); |
| } |
| |
| } // namespace ui |