// Copyright (c) 2016 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/events_x_utils.h" #include #include #include #include #include #include #include #include #include "base/logging.h" #include "base/macros.h" #include "base/memory/singleton.h" #include "build/build_config.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/touch_factory_x11.h" #include "ui/events/keycodes/keyboard_code_conversion_x.h" #include "ui/gfx/display.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/screen.h" #include "ui/gfx/x/x11_atom_cache.h" #include "ui/gfx/x/x11_types.h" namespace { // Scroll amount for each wheelscroll event. 53 is also the value used for GTK+. const int kWheelScrollAmount = 53; const int kMinWheelButton = 4; const int kMaxWheelButton = 7; // A class to track current modifier state on master device. Only track ctrl, // alt, shift and caps lock keys currently. The tracked state can then be used // by floating device. class XModifierStateWatcher { public: static XModifierStateWatcher* GetInstance() { return base::Singleton::get(); } int StateFromKeyboardCode(ui::KeyboardCode keyboard_code) { switch (keyboard_code) { case ui::VKEY_CONTROL: return ControlMask; case ui::VKEY_SHIFT: return ShiftMask; case ui::VKEY_MENU: return Mod1Mask; case ui::VKEY_CAPITAL: return LockMask; default: return 0; } } void UpdateStateFromXEvent(const XEvent& xev) { ui::KeyboardCode keyboard_code = ui::KeyboardCodeFromXKeyEvent(&xev); unsigned int mask = StateFromKeyboardCode(keyboard_code); // Floating device can't access the modifer state from master device. // We need to track the states of modifier keys in a singleton for // floating devices such as touch screen. Issue 106426 is one example // of why we need the modifier states for floating device. switch (xev.type) { case KeyPress: state_ = xev.xkey.state | mask; break; case KeyRelease: state_ = xev.xkey.state & ~mask; break; case GenericEvent: { XIDeviceEvent* xievent = static_cast(xev.xcookie.data); switch (xievent->evtype) { case XI_KeyPress: state_ = xievent->mods.effective |= mask; break; case XI_KeyRelease: state_ = xievent->mods.effective &= ~mask; break; default: NOTREACHED(); break; } break; } default: NOTREACHED(); break; } } // Returns the current modifer state in master device. It only contains the // state of ctrl, shift, alt and caps lock keys. unsigned int state() { return state_; } private: friend struct base::DefaultSingletonTraits; XModifierStateWatcher() : state_(0) {} unsigned int state_; DISALLOW_COPY_AND_ASSIGN(XModifierStateWatcher); }; // Detects if a touch event is a driver-generated 'special event'. // A 'special event' is a touch event with maximum radius and pressure at // location (0, 0). // This needs to be done in a cleaner way: http://crbug.com/169256 bool TouchEventIsGeneratedHack(const XEvent& xev) { XIDeviceEvent* event = static_cast(xev.xcookie.data); CHECK(event->evtype == XI_TouchBegin || event->evtype == XI_TouchUpdate || event->evtype == XI_TouchEnd); // Force is normalized to [0, 1]. if (ui::GetTouchForceFromXEvent(xev) < 1.0f) return false; if (ui::EventLocationFromXEvent(xev) != gfx::Point()) return false; // Radius is in pixels, and the valuator is the diameter in pixels. double radius = ui::GetTouchRadiusXFromXEvent(xev), min, max; unsigned int deviceid = static_cast(xev.xcookie.data)->sourceid; if (!ui::DeviceDataManagerX11::GetInstance()->GetDataRange( deviceid, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, &min, &max)) { return false; } return radius * 2 == max; } int GetEventFlagsFromXState(unsigned int state) { int flags = 0; if (state & ShiftMask) flags |= ui::EF_SHIFT_DOWN; if (state & LockMask) flags |= ui::EF_CAPS_LOCK_ON; if (state & ControlMask) flags |= ui::EF_CONTROL_DOWN; if (state & Mod1Mask) flags |= ui::EF_ALT_DOWN; if (state & Mod2Mask) flags |= ui::EF_NUM_LOCK_ON; if (state & Mod3Mask) flags |= ui::EF_MOD3_DOWN; if (state & Mod4Mask) flags |= ui::EF_COMMAND_DOWN; if (state & Mod5Mask) flags |= ui::EF_ALTGR_DOWN; if (state & Button1Mask) flags |= ui::EF_LEFT_MOUSE_BUTTON; if (state & Button2Mask) flags |= ui::EF_MIDDLE_MOUSE_BUTTON; if (state & Button3Mask) flags |= ui::EF_RIGHT_MOUSE_BUTTON; // There are no masks for EF_BACK_MOUSE_BUTTON and // EF_FORWARD_MOUSE_BUTTON. return flags; } int GetEventFlagsFromXKeyEvent(const XEvent& xev) { DCHECK(xev.type == KeyPress || xev.type == KeyRelease); #if defined(OS_CHROMEOS) const int ime_fabricated_flag = 0; #else // XIM fabricates key events for the character compositions by XK_Multi_key. // For example, when a user hits XK_Multi_key, XK_apostrophe, and XK_e in // order to input "é", then XIM generates a key event with keycode=0 and // state=0 for the composition, and the sequence of X11 key events will be // XK_Multi_key, XK_apostrophe, **NoSymbol**, and XK_e. If the user used // shift key and/or caps lock key, state can be ShiftMask, LockMask or both. // // We have to send these fabricated key events to XIM so it can correctly // handle the character compositions. const unsigned int shift_lock_mask = ShiftMask | LockMask; const bool fabricated_by_xim = xev.xkey.keycode == 0 && (xev.xkey.state & ~shift_lock_mask) == 0; const int ime_fabricated_flag = fabricated_by_xim ? ui::EF_IME_FABRICATED_KEY : 0; #endif return GetEventFlagsFromXState(xev.xkey.state) | (xev.xkey.send_event ? ui::EF_FINAL : 0) | ime_fabricated_flag; } int GetEventFlagsFromXGenericEvent(const XEvent& xev) { DCHECK(xev.type == GenericEvent); XIDeviceEvent* xievent = static_cast(xev.xcookie.data); DCHECK((xievent->evtype == XI_KeyPress) || (xievent->evtype == XI_KeyRelease)); return GetEventFlagsFromXState(xievent->mods.effective) | (xev.xkey.send_event ? ui::EF_FINAL : 0); } // Get the event flag for the button in XButtonEvent. During a ButtonPress // event, |state| in XButtonEvent does not include the button that has just been // pressed. Instead |state| contains flags for the buttons (if any) that had // already been pressed before the current button, and |button| stores the most // current pressed button. So, if you press down left mouse button, and while // pressing it down, press down the right mouse button, then for the latter // event, |state| would have Button1Mask set but not Button3Mask, and |button| // would be 3. int GetEventFlagsForButton(int button) { switch (button) { case 1: return ui::EF_LEFT_MOUSE_BUTTON; case 2: return ui::EF_MIDDLE_MOUSE_BUTTON; case 3: return ui::EF_RIGHT_MOUSE_BUTTON; case 8: return ui::EF_BACK_MOUSE_BUTTON; case 9: return ui::EF_FORWARD_MOUSE_BUTTON; default: return 0; } } int GetButtonMaskForX2Event(XIDeviceEvent* xievent) { int buttonflags = 0; for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) { if (XIMaskIsSet(xievent->buttons.mask, i)) { int button = (xievent->sourceid == xievent->deviceid) ? ui::DeviceDataManagerX11::GetInstance()->GetMappedButton(i) : i; buttonflags |= GetEventFlagsForButton(button); } } return buttonflags; } ui::EventType GetTouchEventType(const XEvent& xev) { XIDeviceEvent* event = static_cast(xev.xcookie.data); switch (event->evtype) { case XI_TouchBegin: return TouchEventIsGeneratedHack(xev) ? ui::ET_UNKNOWN : ui::ET_TOUCH_PRESSED; case XI_TouchUpdate: return TouchEventIsGeneratedHack(xev) ? ui::ET_UNKNOWN : ui::ET_TOUCH_MOVED; case XI_TouchEnd: return TouchEventIsGeneratedHack(xev) ? ui::ET_TOUCH_CANCELLED : ui::ET_TOUCH_RELEASED; } DCHECK(ui::TouchFactory::GetInstance()->IsTouchDevice(event->sourceid)); switch (event->evtype) { case XI_ButtonPress: return ui::ET_TOUCH_PRESSED; case XI_ButtonRelease: return ui::ET_TOUCH_RELEASED; case XI_Motion: // Should not convert any emulated Motion event from touch device to // touch event. if (!(event->flags & XIPointerEmulated) && GetButtonMaskForX2Event(event)) return ui::ET_TOUCH_MOVED; return ui::ET_UNKNOWN; default: NOTREACHED(); } return ui::ET_UNKNOWN; } double GetTouchParamFromXEvent(const XEvent& xev, ui::DeviceDataManagerX11::DataType val, double default_value) { ui::DeviceDataManagerX11::GetInstance()->GetEventData(xev, val, &default_value); return default_value; } void ScaleTouchRadius(const XEvent& xev, double* radius) { DCHECK_EQ(GenericEvent, xev.type); XIDeviceEvent* xiev = static_cast(xev.xcookie.data); ui::DeviceDataManagerX11::GetInstance()->ApplyTouchRadiusScale(xiev->sourceid, radius); } bool GetGestureTimes(const XEvent& xev, double* start_time, double* end_time) { if (!ui::DeviceDataManagerX11::GetInstance()->HasGestureTimes(xev)) return false; double start_time_, end_time_; if (!start_time) start_time = &start_time_; if (!end_time) end_time = &end_time_; ui::DeviceDataManagerX11::GetInstance()->GetGestureTimes(xev, start_time, end_time); return true; } int64_t g_last_seen_timestamp_ms_ = 0; // accumulated rollover time. int64_t g_rollover_ms_ = 0; // Takes a 32-bit timestamp in milliseconds (e.g., an Xlib Time) and returns // a time delta that is immune to timer rollover. This function is not thread // safe as we do not use a lock. base::TimeDelta TimeDeltaFromXEventTime(uint32_t timestamp) { int64_t timestamp_64 = static_cast(timestamp); // Register a rollover if the distance between last timestamp and current one // is larger than half the width. This avoids false rollovers even in a case // where X server delivers reasonably close events out-of-order. if (g_last_seen_timestamp_ms_ - timestamp_64 > (UINT32_MAX >> 1)) g_rollover_ms_ += static_cast(UINT32_MAX) + 1; // ~49.7 days. g_last_seen_timestamp_ms_ = timestamp_64; return base::TimeDelta::FromMilliseconds(g_rollover_ms_ + timestamp_64); } } // namespace namespace ui { EventType EventTypeFromXEvent(const XEvent& xev) { // Allow the DeviceDataManager to block the event. If blocked return // ET_UNKNOWN as the type so this event will not be further processed. // NOTE: During some events unittests there is no device data manager. if (DeviceDataManager::HasInstance() && static_cast(DeviceDataManager::GetInstance()) ->IsEventBlocked(xev)) { return ET_UNKNOWN; } switch (xev.type) { case KeyPress: return ET_KEY_PRESSED; case KeyRelease: return ET_KEY_RELEASED; case ButtonPress: if (static_cast(xev.xbutton.button) >= kMinWheelButton && static_cast(xev.xbutton.button) <= kMaxWheelButton) return ET_MOUSEWHEEL; return ET_MOUSE_PRESSED; case ButtonRelease: // Drop wheel events; we should've already scrolled on the press. if (static_cast(xev.xbutton.button) >= kMinWheelButton && static_cast(xev.xbutton.button) <= kMaxWheelButton) return ET_UNKNOWN; return ET_MOUSE_RELEASED; case MotionNotify: if (xev.xmotion.state & (Button1Mask | Button2Mask | Button3Mask)) return ET_MOUSE_DRAGGED; return ET_MOUSE_MOVED; case EnterNotify: // The standard on Windows is to send a MouseMove event when the mouse // first enters a window instead of sending a special mouse enter event. // To be consistent we follow the same style. return ET_MOUSE_MOVED; case LeaveNotify: return ET_MOUSE_EXITED; case GenericEvent: { TouchFactory* factory = TouchFactory::GetInstance(); if (!factory->ShouldProcessXI2Event(const_cast(&xev))) return ET_UNKNOWN; XIDeviceEvent* xievent = static_cast(xev.xcookie.data); // This check works only for master and floating slave devices. That is // why it is necessary to check for the XI_Touch* events in the following // switch statement to account for attached-slave touchscreens. if (factory->IsTouchDevice(xievent->sourceid)) return GetTouchEventType(xev); switch (xievent->evtype) { case XI_TouchBegin: return ui::ET_TOUCH_PRESSED; case XI_TouchUpdate: return ui::ET_TOUCH_MOVED; case XI_TouchEnd: return ui::ET_TOUCH_RELEASED; case XI_ButtonPress: { int button = EventButtonFromXEvent(xev); if (button >= kMinWheelButton && button <= kMaxWheelButton) return ET_MOUSEWHEEL; return ET_MOUSE_PRESSED; } case XI_ButtonRelease: { int button = EventButtonFromXEvent(xev); // Drop wheel events; we should've already scrolled on the press. if (button >= kMinWheelButton && button <= kMaxWheelButton) return ET_UNKNOWN; return ET_MOUSE_RELEASED; } case XI_Motion: { bool is_cancel; DeviceDataManagerX11* devices = DeviceDataManagerX11::GetInstance(); if (GetFlingDataFromXEvent(xev, NULL, NULL, NULL, NULL, &is_cancel)) return is_cancel ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START; if (devices->IsScrollEvent(xev)) { return devices->IsTouchpadXInputEvent(xev) ? ET_SCROLL : ET_MOUSEWHEEL; } if (devices->GetScrollClassEventDetail(xev) != SCROLL_TYPE_NO_SCROLL) return ET_MOUSEWHEEL; if (devices->IsCMTMetricsEvent(xev)) return ET_UMA_DATA; if (GetButtonMaskForX2Event(xievent)) return ET_MOUSE_DRAGGED; if (DeviceDataManagerX11::GetInstance()->HasEventData( xievent, DeviceDataManagerX11::DT_CMT_SCROLL_X) || DeviceDataManagerX11::GetInstance()->HasEventData( xievent, DeviceDataManagerX11::DT_CMT_SCROLL_Y)) { // Don't produce mouse move events for mousewheel scrolls. return ET_UNKNOWN; } return ET_MOUSE_MOVED; } case XI_KeyPress: return ET_KEY_PRESSED; case XI_KeyRelease: return ET_KEY_RELEASED; } } default: break; } return ET_UNKNOWN; } int EventFlagsFromXEvent(const XEvent& xev) { switch (xev.type) { case KeyPress: case KeyRelease: { XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(xev); return GetEventFlagsFromXKeyEvent(xev); } case ButtonPress: case ButtonRelease: { int flags = GetEventFlagsFromXState(xev.xbutton.state); const EventType type = EventTypeFromXEvent(xev); if (type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) flags |= GetEventFlagsForButton(xev.xbutton.button); return flags; } case EnterNotify: case LeaveNotify: return GetEventFlagsFromXState(xev.xcrossing.state); case MotionNotify: return GetEventFlagsFromXState(xev.xmotion.state); case GenericEvent: { XIDeviceEvent* xievent = static_cast(xev.xcookie.data); switch (xievent->evtype) { case XI_TouchBegin: case XI_TouchUpdate: case XI_TouchEnd: return GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective) | GetEventFlagsFromXState( XModifierStateWatcher::GetInstance()->state()); break; case XI_ButtonPress: case XI_ButtonRelease: { const bool touch = TouchFactory::GetInstance()->IsTouchDevice(xievent->sourceid); int flags = GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective); if (touch) { flags |= GetEventFlagsFromXState( XModifierStateWatcher::GetInstance()->state()); } const EventType type = EventTypeFromXEvent(xev); int button = EventButtonFromXEvent(xev); if ((type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) && !touch) flags |= GetEventFlagsForButton(button); return flags; } case XI_Motion: return GetButtonMaskForX2Event(xievent) | GetEventFlagsFromXState(xievent->mods.effective); case XI_KeyPress: case XI_KeyRelease: { XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(xev); return GetEventFlagsFromXGenericEvent(xev); } } } } return 0; } base::TimeDelta EventTimeFromXEvent(const XEvent& xev) { switch (xev.type) { case KeyPress: case KeyRelease: return TimeDeltaFromXEventTime(xev.xkey.time); case ButtonPress: case ButtonRelease: return TimeDeltaFromXEventTime(xev.xbutton.time); break; case MotionNotify: return TimeDeltaFromXEventTime(xev.xmotion.time); break; case EnterNotify: case LeaveNotify: return TimeDeltaFromXEventTime(xev.xcrossing.time); break; case GenericEvent: { double start, end; double touch_timestamp; if (GetGestureTimes(xev, &start, &end)) { // If the driver supports gesture times, use them. return base::TimeDelta::FromMicroseconds(end * 1000000); } else if (DeviceDataManagerX11::GetInstance()->GetEventData( xev, DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP, &touch_timestamp)) { return base::TimeDelta::FromMicroseconds(touch_timestamp * 1000000); } else { XIDeviceEvent* xide = static_cast(xev.xcookie.data); return TimeDeltaFromXEventTime(xide->time); } break; } } NOTREACHED(); return base::TimeDelta(); } gfx::Point EventLocationFromXEvent(const XEvent& xev) { switch (xev.type) { case EnterNotify: case LeaveNotify: return gfx::Point(xev.xcrossing.x, xev.xcrossing.y); case ButtonPress: case ButtonRelease: return gfx::Point(xev.xbutton.x, xev.xbutton.y); case MotionNotify: return gfx::Point(xev.xmotion.x, xev.xmotion.y); case GenericEvent: { XIDeviceEvent* xievent = static_cast(xev.xcookie.data); float x = xievent->event_x; float y = xievent->event_y; #if defined(OS_CHROMEOS) switch (xievent->evtype) { case XI_TouchBegin: case XI_TouchUpdate: case XI_TouchEnd: ui::DeviceDataManagerX11::GetInstance()->ApplyTouchTransformer( xievent->deviceid, &x, &y); break; default: break; } #endif // defined(OS_CHROMEOS) return gfx::Point(static_cast(x), static_cast(y)); } } return gfx::Point(); } gfx::Point EventSystemLocationFromXEvent(const XEvent& xev) { switch (xev.type) { case EnterNotify: case LeaveNotify: { return gfx::Point(xev.xcrossing.x_root, xev.xcrossing.y_root); } case ButtonPress: case ButtonRelease: { return gfx::Point(xev.xbutton.x_root, xev.xbutton.y_root); } case MotionNotify: { return gfx::Point(xev.xmotion.x_root, xev.xmotion.y_root); } case GenericEvent: { XIDeviceEvent* xievent = static_cast(xev.xcookie.data); return gfx::Point(xievent->root_x, xievent->root_y); } } return gfx::Point(); } int EventButtonFromXEvent(const XEvent& xev) { CHECK_EQ(GenericEvent, xev.type); XIDeviceEvent* xievent = static_cast(xev.xcookie.data); int button = xievent->detail; return (xievent->sourceid == xievent->deviceid) ? DeviceDataManagerX11::GetInstance()->GetMappedButton(button) : button; } int GetChangedMouseButtonFlagsFromXEvent(const XEvent& xev) { switch (xev.type) { case ButtonPress: case ButtonRelease: return GetEventFlagsForButton(xev.xbutton.button); case GenericEvent: { XIDeviceEvent* xievent = static_cast(xev.xcookie.data); switch (xievent->evtype) { case XI_ButtonPress: case XI_ButtonRelease: return GetEventFlagsForButton(EventButtonFromXEvent(xev)); default: break; } } default: break; } return 0; } gfx::Vector2d GetMouseWheelOffsetFromXEvent(const XEvent& xev) { float x_offset, y_offset; if (GetScrollOffsetsFromXEvent(xev, &x_offset, &y_offset, NULL, NULL, NULL)) { return gfx::Vector2d(static_cast(x_offset), static_cast(y_offset)); } int button = xev.type == GenericEvent ? EventButtonFromXEvent(xev) : xev.xbutton.button; switch (button) { case 4: return gfx::Vector2d(0, kWheelScrollAmount); case 5: return gfx::Vector2d(0, -kWheelScrollAmount); case 6: return gfx::Vector2d(kWheelScrollAmount, 0); case 7: return gfx::Vector2d(-kWheelScrollAmount, 0); default: return gfx::Vector2d(); } } void ClearTouchIdIfReleasedFromXEvent(const XEvent& xev) { ui::EventType type = ui::EventTypeFromXEvent(xev); if (type == ui::ET_TOUCH_CANCELLED || type == ui::ET_TOUCH_RELEASED) { ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance(); double tracking_id; if (manager->GetEventData(xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) { factory->ReleaseSlotForTrackingID(tracking_id); } } } int GetTouchIdFromXEvent(const XEvent& xev) { double slot = 0; ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance(); double tracking_id; if (!manager->GetEventData( xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) { LOG(ERROR) << "Could not get the tracking ID for the event. Using 0."; } else { ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); slot = factory->GetSlotForTrackingID(tracking_id); } return slot; } float GetTouchRadiusXFromXEvent(const XEvent& xev) { double radius = GetTouchParamFromXEvent( xev, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, 0.0) / 2.0; ScaleTouchRadius(xev, &radius); return radius; } float GetTouchRadiusYFromXEvent(const XEvent& xev) { double radius = GetTouchParamFromXEvent( xev, ui::DeviceDataManagerX11::DT_TOUCH_MINOR, 0.0) / 2.0; ScaleTouchRadius(xev, &radius); return radius; } float GetTouchAngleFromXEvent(const XEvent& xev) { return GetTouchParamFromXEvent( xev, ui::DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.0) / 2.0; } float GetTouchForceFromXEvent(const XEvent& xev) { double force = 0.0; force = GetTouchParamFromXEvent( xev, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, 0.0); unsigned int deviceid = static_cast(xev.xcookie.data)->sourceid; // Force is normalized to fall into [0, 1] if (!ui::DeviceDataManagerX11::GetInstance()->NormalizeData( deviceid, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, &force)) force = 0.0; return force; } bool GetScrollOffsetsFromXEvent(const XEvent& xev, float* x_offset, float* y_offset, float* x_offset_ordinal, float* y_offset_ordinal, int* finger_count) { if (DeviceDataManagerX11::GetInstance()->IsScrollEvent(xev)) { // Temp values to prevent passing NULLs to DeviceDataManager. float x_offset_, y_offset_; float x_offset_ordinal_, y_offset_ordinal_; int finger_count_; if (!x_offset) x_offset = &x_offset_; if (!y_offset) y_offset = &y_offset_; if (!x_offset_ordinal) x_offset_ordinal = &x_offset_ordinal_; if (!y_offset_ordinal) y_offset_ordinal = &y_offset_ordinal_; if (!finger_count) finger_count = &finger_count_; DeviceDataManagerX11::GetInstance()->GetScrollOffsets( xev, x_offset, y_offset, x_offset_ordinal, y_offset_ordinal, finger_count); return true; } if (DeviceDataManagerX11::GetInstance()->GetScrollClassDeviceDetail(xev) != SCROLL_TYPE_NO_SCROLL) { double x_scroll_offset, y_scroll_offset; DeviceDataManagerX11::GetInstance()->GetScrollClassOffsets( xev, &x_scroll_offset, &y_scroll_offset); *x_offset = x_scroll_offset * kWheelScrollAmount; *y_offset = y_scroll_offset * kWheelScrollAmount; return true; } return false; } bool GetFlingDataFromXEvent(const XEvent& xev, float* vx, float* vy, float* vx_ordinal, float* vy_ordinal, bool* is_cancel) { if (!DeviceDataManagerX11::GetInstance()->IsFlingEvent(xev)) return false; float vx_, vy_; float vx_ordinal_, vy_ordinal_; bool is_cancel_; if (!vx) vx = &vx_; if (!vy) vy = &vy_; if (!vx_ordinal) vx_ordinal = &vx_ordinal_; if (!vy_ordinal) vy_ordinal = &vy_ordinal_; if (!is_cancel) is_cancel = &is_cancel_; DeviceDataManagerX11::GetInstance()->GetFlingData(xev, vx, vy, vx_ordinal, vy_ordinal, is_cancel); return true; } void ResetTimestampRolloverCountersForTesting() { g_last_seen_timestamp_ms_ = 0; g_rollover_ms_ = 0; } } // namespace ui