// 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/x/touch_factory_x11.h" #include #include #include #include #include #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/event_switches.h" #include "ui/events/x/device_data_manager_x11.h" #include "ui/events/x/device_list_cache_x.h" #include "ui/gfx/x/x11_types.h" namespace ui { TouchFactory::TouchFactory() : pointer_device_lookup_(), touch_device_available_(false), touch_events_disabled_(false), touch_device_list_(), max_touch_points_(-1), virtual_core_keyboard_device_(-1), id_generator_(0) { if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available()) return; XDisplay* display = gfx::GetXDisplay(); UpdateDeviceList(display); CommandLine* cmdline = CommandLine::ForCurrentProcess(); touch_events_disabled_ = cmdline->HasSwitch(switches::kTouchEvents) && cmdline->GetSwitchValueASCII(switches::kTouchEvents) == switches::kTouchEventsDisabled; } TouchFactory::~TouchFactory() { } // static TouchFactory* TouchFactory::GetInstance() { return Singleton::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 = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kTouchDevices); if (!touch_devices.empty()) { std::vector devs; std::vector device_ids; unsigned int devid; base::SplitString(touch_devices, ',', &devs); for (std::vector::iterator iter = devs.begin(); iter != devs.end(); ++iter) { if (base::StringToInt(*iter, reinterpret_cast(&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_available_ = false; touch_device_lookup_.reset(); touch_device_list_.clear(); touchscreen_ids_.clear(); max_touch_points_ = -1; #if !defined(USE_XI2_MT) // NOTE: The new API for retrieving the list of devices (XIQueryDevice) does // not provide enough information to detect a touch device. As a result, the // old version of query function (XListInputDevices) is used instead. // If XInput2 is not supported, this will return null (with count of -1) so // we assume there cannot be any touch devices. // With XI2.1 or older, we allow only single touch devices. XDeviceList dev_list = DeviceListCacheX::GetInstance()->GetXDeviceList(display); Atom xi_touchscreen = XInternAtom(display, XI_TOUCHSCREEN, false); for (int i = 0; i < dev_list.count; i++) { if (dev_list[i].type == xi_touchscreen) { touch_device_lookup_[dev_list[i].id] = true; touch_device_list_[dev_list[i].id] = false; touch_device_available_ = true; } } #endif 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(); XIDeviceList xi_dev_list = DeviceListCacheX::GetInstance()->GetXI2DeviceList(display); for (int i = 0; i < xi_dev_list.count; i++) { XIDeviceInfo* devinfo = xi_dev_list.devices + i; if (devinfo->use == XIFloatingSlave || devinfo->use == XIMasterPointer) { #if defined(USE_XI2_MT) for (int k = 0; k < devinfo->num_classes; ++k) { XIAnyClassInfo* xiclassinfo = devinfo->classes[k]; if (xiclassinfo->type == XITouchClass) { XITouchClassInfo* tci = reinterpret_cast(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; touch_device_available_ = true; if (tci->num_touches > 0 && tci->num_touches > max_touch_points_) max_touch_points_ = tci->num_touches; } } } #endif pointer_device_lookup_[devinfo->deviceid] = true; } else if (devinfo->use == XIMasterKeyboard) { virtual_core_keyboard_device_ = devinfo->deviceid; } #if defined(USE_XI2_MT) 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(xiclassinfo); // Only care direct touch device (such as touch screen) right now if (tci->mode == XIDirectTouch) CacheTouchscreenIds(display, devinfo->deviceid); } } } #endif } } bool TouchFactory::ShouldProcessXI2Event(XEvent* xev) { DCHECK_EQ(GenericEvent, xev->type); XIEvent* event = static_cast(xev->xcookie.data); XIDeviceEvent* xiev = reinterpret_cast(event); #if defined(USE_XI2_MT) if (event->evtype == XI_TouchBegin || event->evtype == XI_TouchUpdate || event->evtype == XI_TouchEnd) { return !touch_events_disabled_ && IsTouchDevice(xiev->deviceid); } #endif // 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)); #if defined(USE_XI2_MT) XISetMask(mask, XI_TouchBegin); XISetMask(mask, XI_TouchUpdate); XISetMask(mask, XI_TouchEnd); #endif XISetMask(mask, XI_ButtonPress); XISetMask(mask, XI_ButtonRelease); XISetMask(mask, XI_Motion); #if defined(OS_CHROMEOS) 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& devices) { touch_device_lookup_.reset(); touch_device_list_.clear(); for (std::vector::const_iterator iter = devices.begin(); iter != devices.end(); ++iter) { DCHECK(*iter < touch_device_lookup_.size()); touch_device_lookup_[*iter] = true; touch_device_list_[*iter] = false; } } bool TouchFactory::IsTouchDevice(unsigned deviceid) const { return deviceid < touch_device_lookup_.size() ? touch_device_lookup_[deviceid] : false; } bool TouchFactory::IsMultiTouchDevice(unsigned int deviceid) const { return (deviceid < touch_device_lookup_.size() && 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(id_generator_.GetGeneratedID(tracking_id)); return true; } int TouchFactory::GetSlotForTrackingID(uint32 tracking_id) { return id_generator_.GetGeneratedID(tracking_id); } void TouchFactory::AcquireSlotForTrackingID(uint32 tracking_id) { tracking_id_refcounts_[tracking_id]++; } void TouchFactory::ReleaseSlotForTrackingID(uint32 tracking_id) { tracking_id_refcounts_[tracking_id]--; if (tracking_id_refcounts_[tracking_id] == 0) id_generator_.ReleaseNumber(tracking_id); } bool TouchFactory::IsTouchDevicePresent() { return !touch_events_disabled_ && touch_device_available_; } int TouchFactory::GetMaxTouchPoints() const { return max_touch_points_; } void TouchFactory::ResetForTest() { pointer_device_lookup_.reset(); touch_device_lookup_.reset(); touch_device_available_ = false; touch_events_disabled_ = false; touch_device_list_.clear(); touchscreen_ids_.clear(); tracking_id_refcounts_.clear(); max_touch_points_ = -1; id_generator_.ResetForTest(); } void TouchFactory::SetTouchDeviceForTest( const std::vector& devices) { touch_device_lookup_.reset(); touch_device_list_.clear(); for (std::vector::const_iterator iter = devices.begin(); iter != devices.end(); ++iter) { DCHECK(*iter < touch_device_lookup_.size()); touch_device_lookup_[*iter] = true; touch_device_list_[*iter] = true; } touch_device_available_ = true; touch_events_disabled_ = false; } void TouchFactory::SetPointerDeviceForTest( const std::vector& devices) { pointer_device_lookup_.reset(); for (std::vector::const_iterator iter = devices.begin(); iter != devices.end(); ++iter) { pointer_device_lookup_[*iter] = true; } } void TouchFactory::CacheTouchscreenIds(Display* display, int device_id) { XDevice* device = XOpenDevice(display, device_id); if (!device) return; Atom actual_type_return; int actual_format_return; unsigned long nitems_return; unsigned long bytes_after_return; unsigned char *prop_return; const char kDeviceProductIdString[] = "Device Product ID"; Atom device_product_id_atom = XInternAtom(display, kDeviceProductIdString, false); if (device_product_id_atom != None && XGetDeviceProperty(display, device, device_product_id_atom, 0, 2, False, XA_INTEGER, &actual_type_return, &actual_format_return, &nitems_return, &bytes_after_return, &prop_return) == Success) { if (actual_type_return == XA_INTEGER && actual_format_return == 32 && nitems_return == 2) { // An actual_format_return of 32 implies that the returned data is an // array of longs. See the description of |prop_return| in `man // XGetDeviceProperty` for details. long* ptr = reinterpret_cast(prop_return); // Internal displays will have a vid and pid of 0. Ignore them. // ptr[0] is the vid, and ptr[1] is the pid. if (ptr[0] || ptr[1]) touchscreen_ids_.insert(std::make_pair(ptr[0], ptr[1])); } XFree(prop_return); } XCloseDevice(display, device); } } // namespace ui