// 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/base/touch/touch_factory.h" #include #include #include #include #include "base/basictypes.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/string_split.h" #include "ui/base/ui_base_switches.h" #include "ui/base/x/x11_util.h" namespace { // The X cursor is hidden if it is idle for kCursorIdleSeconds seconds. int kCursorIdleSeconds = 5; } // namespace namespace ui { TouchFactory::TouchFactory() : is_cursor_visible_(true), touch_events_allowed_(false), cursor_timer_(), pointer_device_lookup_(), touch_device_available_(false), touch_present_called_(false), touch_device_list_(), #if defined(USE_XI2_MT) min_available_slot_(0), #endif slots_used_() { #if defined(USE_AURA) if (!base::MessagePumpForUI::HasXInput2()) return; #endif char nodata[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; XColor black; black.red = black.green = black.blue = 0; Display* display = ui::GetXDisplay(); Pixmap blank = XCreateBitmapFromData(display, ui::GetX11RootWindow(), nodata, 8, 8); invisible_cursor_ = XCreatePixmapCursor(display, blank, blank, &black, &black, 0, 0); arrow_cursor_ = XCreateFontCursor(display, XC_arrow); SetCursorVisible(false, false); UpdateDeviceList(display); // Make sure the list of devices is kept up-to-date by listening for // XI_HierarchyChanged event on the root window. unsigned char mask[XIMaskLen(XI_LASTEVENT)]; memset(mask, 0, sizeof(mask)); XISetMask(mask, XI_HierarchyChanged); XIEventMask evmask; evmask.deviceid = XIAllDevices; evmask.mask_len = sizeof(mask); evmask.mask = mask; XISelectEvents(display, ui::GetX11RootWindow(), &evmask, 1); CommandLine* cmdline = CommandLine::ForCurrentProcess(); if (cmdline->HasSwitch(switches::kTouchOptimizedUI) || cmdline->HasSwitch(switches::kEnableTouchEvents)) { touch_events_allowed_ = true; } } TouchFactory::~TouchFactory() { #if defined(USE_AURA) if (!base::MessagePumpForUI::HasXInput2()) return; #endif // The XDisplay may be lost by the time we get destroyed. if (ui::XDisplayExists()) { SetCursorVisible(true, false); Display* display = ui::GetXDisplay(); XFreeCursor(display, invisible_cursor_); XFreeCursor(display, arrow_cursor_); } } // static TouchFactory* TouchFactory::GetInstance() { return Singleton::get(); } // static void TouchFactory::SetTouchDeviceListFromCommandLine() { #if defined(TOOLKIT_VIEWS) // 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); } #endif } void TouchFactory::UpdateDeviceList(Display* display) { // Detect touch devices. // 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. int count = 0; bool last_touch_device_available = touch_device_available_; touch_device_available_ = false; touch_device_lookup_.reset(); touch_device_list_.clear(); #if !defined(USE_XI2_MT) XDeviceInfo* devlist = XListInputDevices(display, &count); for (int i = 0; i < count; i++) { if (devlist[i].type) { XScopedString devtype(XGetAtomName(display, devlist[i].type)); if (devtype.string() && !strcmp(devtype.string(), XI_TOUCHSCREEN)) { touch_device_lookup_[devlist[i].id] = true; touch_device_list_[devlist[i].id] = true; touch_device_available_ = true; } } } if (devlist) XFreeDeviceList(devlist); #endif // 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(); XIDeviceInfo* devices = XIQueryDevice(display, XIAllDevices, &count); for (int i = 0; i < count; i++) { XIDeviceInfo* devinfo = devices + i; #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; } } } #endif if (devinfo->use == XIFloatingSlave || devinfo->use == XISlavePointer) pointer_device_lookup_[devinfo->deviceid] = true; } if (devices) XIFreeDeviceInfo(devices); if ((last_touch_device_available != touch_device_available_) && touch_events_allowed_ && touch_present_called_) { // Touch_device_available_ has changed after it's been queried. // TODO(rbyers): Should dispatch an event to indicate that the availability // of touch devices has changed. crbug.com/124399. LOG(WARNING) << "Touch screen " << (touch_device_available_ ? "added" : "removed") << " after startup, which is not yet fully supported."; } } 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_allowed_ && IsTouchDevice(xiev->deviceid); } #endif 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_allowed_ : 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. Display* display = ui::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); 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; } #if defined(USE_XI2_MT) int TouchFactory::GetSlotForTrackingID(uint32 tracking_id) { TrackingIdMap::iterator itr = tracking_id_map_.find(tracking_id); if (itr != tracking_id_map_.end()) return itr->second; int slot = min_available_slot_; if (slot == kMaxTouchPoints) { LOG(ERROR) << "Could not find available slot for touch point"; return 0; } SetSlotUsed(slot, true); tracking_id_map_.insert(std::make_pair(tracking_id, slot)); // Updates the minium available slot ID while (++min_available_slot_ < kMaxTouchPoints && IsSlotUsed(min_available_slot_)) continue; return slot; } void TouchFactory::ReleaseSlotForTrackingID(uint32 tracking_id) { TrackingIdMap::iterator itr = tracking_id_map_.find(tracking_id); if (itr != tracking_id_map_.end()) { int slot = itr->second; SetSlotUsed(slot, false); tracking_id_map_.erase(itr); if (slot < min_available_slot_) min_available_slot_ = slot; } else { NOTREACHED() << "Cannot find slot mapping to tracking ID " << tracking_id; } } #endif bool TouchFactory::IsSlotUsed(int slot) const { CHECK_LT(slot, kMaxTouchPoints); return slots_used_[slot]; } void TouchFactory::SetSlotUsed(int slot, bool used) { CHECK_LT(slot, kMaxTouchPoints); slots_used_[slot] = used; } bool TouchFactory::GrabTouchDevices(Display* display, ::Window window) { #if defined(USE_AURA) if (!base::MessagePumpForUI::HasXInput2() || touch_device_list_.empty()) return true; #endif unsigned char mask[XIMaskLen(XI_LASTEVENT)]; bool success = true; 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); XIEventMask evmask; evmask.mask_len = sizeof(mask); evmask.mask = mask; for (std::map::const_iterator iter = touch_device_list_.begin(); iter != touch_device_list_.end(); ++iter) { evmask.deviceid = iter->first; Status status = XIGrabDevice(display, iter->first, window, CurrentTime, None, GrabModeAsync, GrabModeAsync, False, &evmask); success = success && status == GrabSuccess; } return success; } bool TouchFactory::UngrabTouchDevices(Display* display) { #if defined(USE_AURA) if (!base::MessagePumpForUI::HasXInput2()) return true; #endif bool success = true; for (std::map::const_iterator iter = touch_device_list_.begin(); iter != touch_device_list_.end(); ++iter) { Status status = XIUngrabDevice(display, iter->first, CurrentTime); success = success && status == GrabSuccess; } return success; } void TouchFactory::SetCursorVisible(bool show, bool start_timer) { // This function may get called after the display is terminated. if (!ui::XDisplayExists()) return; #if defined(USE_AURA) if (!base::MessagePumpForUI::HasXInput2()) return; #endif // The cursor is going to be shown. Reset the timer for hiding it. if (show && start_timer) { cursor_timer_.Stop(); cursor_timer_.Start( FROM_HERE, base::TimeDelta::FromSeconds(kCursorIdleSeconds), this, &TouchFactory::HideCursorForInactivity); } else { cursor_timer_.Stop(); } if (show == is_cursor_visible_) return; is_cursor_visible_ = show; Display* display = ui::GetXDisplay(); Window window = DefaultRootWindow(display); // Hide the cursor only if there's a chance that the user will be using touch // (i.e. if a touch device is available). if (is_cursor_visible_) XDefineCursor(display, window, arrow_cursor_); else if (touch_device_available_) XDefineCursor(display, window, invisible_cursor_); } bool TouchFactory::IsTouchDevicePresent() { touch_present_called_ = true; return (touch_device_available_ && touch_events_allowed_); } } // namespace ui