diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/ui/events | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) | |
download | qtwebengine-chromium-ab0a50979b9eb4dfa3320eff7e187e41efedf7a9.tar.gz |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/ui/events')
184 files changed, 18804 insertions, 2334 deletions
diff --git a/chromium/ui/events/BUILD.gn b/chromium/ui/events/BUILD.gn new file mode 100644 index 00000000000..26c4b1ed633 --- /dev/null +++ b/chromium/ui/events/BUILD.gn @@ -0,0 +1,311 @@ +# Copyright 2014 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. + +import("//build/config/ui.gni") + +static_library("dom4_keycode_converter") { + sources = [ + "keycodes/dom4/keycode_converter.cc", + "keycodes/dom4/keycode_converter.h", + "keycodes/dom4/keycode_converter_data.h", + ] + + deps = [ "//base" ] +} + +component("events_base") { + sources = [ + "event_constants.h", + "event_switches.cc", + "event_switches.h", + "events_base_export.h", + "gesture_event_details.cc", + "gesture_event_details.h", + "gestures/gesture_configuration.cc", + "gestures/gesture_configuration.h", + "keycodes/keyboard_code_conversion.cc", + "keycodes/keyboard_code_conversion.h", + "keycodes/keyboard_code_conversion_android.cc", + "keycodes/keyboard_code_conversion_android.h", + "keycodes/keyboard_code_conversion_mac.h", + "keycodes/keyboard_code_conversion_mac.mm", + "keycodes/keyboard_code_conversion_win.cc", + "keycodes/keyboard_code_conversion_win.h", + "keycodes/keyboard_codes.h", + "latency_info.cc", + "latency_info.h", + ] + + defines = [ "EVENTS_BASE_IMPLEMENTATION" ] + + deps = [ + ":dom4_keycode_converter", + "//base", + "//base/third_party/dynamic_annotations", + "//skia", + "//ui/events/platform", + "//ui/gfx", + "//ui/gfx/geometry", + ] + + if (use_x11) { + configs += [ "//build/config/linux:x11" ] + + sources += [ + "keycodes/keyboard_code_conversion_x.cc", + "keycodes/keyboard_code_conversion_x.h", + "x/device_data_manager.cc", + "x/device_data_manager.h", + "x/device_list_cache_x.cc", + "x/device_list_cache_x.h", + "x/touch_factory_x11.cc", + "x/touch_factory_x11.h", + ] + } +} + +component("events") { + deps = [ + ":dom4_keycode_converter", + ":events_base", + ":gesture_detection", + "//skia", + "//ui/gfx", + "//ui/gfx/geometry", + ] + + defines = [ "EVENTS_IMPLEMENTATION" ] + + sources = [ + "cocoa/cocoa_event_utils.h", + "cocoa/cocoa_event_utils.mm", + "event.cc", + "event.h", + "event_dispatcher.cc", + "event_dispatcher.h", + "event_handler.cc", + "event_handler.h", + "event_processor.cc", + "event_processor.h", + "event_rewriter.h", + "event_source.cc", + "event_source.h", + "event_target.cc", + "event_target.h", + "event_target_iterator.h", + "event_targeter.cc", + "event_targeter.h", + "event_utils.cc", + "event_utils.h", + "events_export.h", + "events_stub.cc", + "gestures/gesture_point.cc", + "gestures/gesture_point.h", + "gestures/gesture_recognizer.h", + "gestures/gesture_recognizer_impl.cc", + "gestures/gesture_recognizer_impl.h", + "gestures/gesture_recognizer_impl_mac.cc", + "gestures/gesture_sequence.cc", + "gestures/gesture_sequence.h", + "gestures/gesture_types.h", + "gestures/unified_gesture_detector_enabled.cc", + "gestures/unified_gesture_detector_enabled.h", + "gestures/velocity_calculator.cc", + "gestures/velocity_calculator.h", + "platform/x11/x11_event_source.cc", + "platform/x11/x11_event_source.h", + "win/events_win.cc", + "x/events_x.cc", + ] + + if (use_x11) { + configs += [ + "//build/config/linux:glib", + "//build/config/linux:x11", + ] + } else { + sources -= [ + "platform/x11/x11_event_source.cc", + "platform/x11/x11_event_source.h", + "x/events_x.cc", + ] + } + + if (!is_chromeos && is_linux) { + sources += [ + "linux/text_edit_command_auralinux.cc", + "linux/text_edit_command_auralinux.h", + "linux/text_edit_key_bindings_delegate_auralinux.cc", + "linux/text_edit_key_bindings_delegate_auralinux.h", + ] + } + + if (use_ozone) { + sources += [ + "ozone/device/udev/device_manager_udev.cc", + "ozone/device/udev/device_manager_udev.h", + "ozone/evdev/event_converter_evdev.cc", + "ozone/evdev/event_converter_evdev.h", + "ozone/evdev/event_device_info.cc", + "ozone/evdev/event_device_info.h", + "ozone/evdev/event_factory_evdev.cc", + "ozone/evdev/event_factory_evdev.h", + "ozone/evdev/event_modifiers_evdev.cc", + "ozone/evdev/event_modifiers_evdev.h", + "ozone/evdev/key_event_converter_evdev.cc", + "ozone/evdev/key_event_converter_evdev.h", + "ozone/evdev/touch_event_converter_evdev.cc", + "ozone/evdev/touch_event_converter_evdev.h", + "ozone/event_factory_ozone.cc", + "ozone/event_factory_ozone.h", + "ozone/events_ozone.cc", + ] + } + + if (use_aura) { + sources += [ + "gestures/gesture_provider_aura.cc", + "gestures/gesture_provider_aura.h", + "gestures/motion_event_aura.cc", + "gestures/motion_event_aura.h", + ] + } + + if (is_win || use_x11 || use_ozone) { + sources -= [ "events_stub.cc" ] + } +} + +component("gesture_detection") { + sources = [ + "gesture_detection/bitset_32.h", + "gesture_detection/filtered_gesture_provider.cc", + "gesture_detection/filtered_gesture_provider.h", + "gesture_detection/gesture_detection_export.h", + "gesture_detection/gesture_detector.cc", + "gesture_detection/gesture_detector.h", + "gesture_detection/gesture_event_data.cc", + "gesture_detection/gesture_event_data.h", + "gesture_detection/gesture_event_data_packet.cc", + "gesture_detection/gesture_event_data_packet.h", + "gesture_detection/gesture_config_helper.h", + "gesture_detection/gesture_provider.cc", + "gesture_detection/gesture_provider.h", + "gesture_detection/motion_event.h", + "gesture_detection/scale_gesture_detector.cc", + "gesture_detection/scale_gesture_detector.h", + "gesture_detection/snap_scroll_controller.cc", + "gesture_detection/snap_scroll_controller.h", + "gesture_detection/touch_disposition_gesture_filter.cc", + "gesture_detection/touch_disposition_gesture_filter.h", + "gesture_detection/velocity_tracker_state.cc", + "gesture_detection/velocity_tracker_state.h", + "gesture_detection/velocity_tracker.cc", + "gesture_detection/velocity_tracker.h", + ] + + deps = [ + ":events_base", + "//base", + "//ui/gfx", + "//ui/gfx/geometry", + ] + + defines = [ "GESTURE_DETECTION_IMPLEMENTATION" ] + + if (is_android) { + sources += [ "gesture_detection/gesture_config_helper_android.cc" ] + } else if (use_aura) { + sources += [ "gesture_detection/gesture_config_helper_aura.cc" ] + } else { + sources += [ "gesture_detection/gesture_config_helper.cc" ] + } +} + +source_set("events_test_support") { + sources = [ + "test/cocoa_test_event_utils.h", + "test/cocoa_test_event_utils.mm", + "test/events_test_utils.cc", + "test/events_test_utils.h", + "test/events_test_utils_x11.cc", + "test/events_test_utils_x11.h", + "test/platform_event_waiter.cc", + "test/platform_event_waiter.h", + "test/test_event_handler.cc", + "test/test_event_handler.h", + "test/test_event_processor.cc", + "test/test_event_processor.h", + "test/test_event_target.cc", + "test/test_event_target.h", + ] + + deps = [ + "//skia", + ":events_base", + ":events", + ] + + if (is_ios) { + sources -= [ + "test/cocoa_test_event_utils.h", + "test/cocoa_test_event_utils.mm", + ] + } + + if (use_x11) { + configs += [ "//build/config/linux:x11" ] + } else { + sources -= [ + "test/events_test_utils_x11.cc", + "test/events_test_utils_x11.h", + ] + } +} + +test("events_unittests") { + sources = [ + "cocoa/events_mac_unittest.mm", + "event_dispatcher_unittest.cc", + "event_processor_unittest.cc", + "event_rewriter_unittest.cc", + "event_unittest.cc", + "gestures/velocity_calculator_unittest.cc", + "gesture_detection/bitset_32_unittest.cc", + "gesture_detection/gesture_provider_unittest.cc", + "gesture_detection/mock_motion_event.h", + "gesture_detection/mock_motion_event.cc", + "gesture_detection/velocity_tracker_unittest.cc", + "gesture_detection/touch_disposition_gesture_filter_unittest.cc", + "keycodes/dom4/keycode_converter_unittest.cc", + "latency_info_unittest.cc", + "platform/platform_event_source_unittest.cc", + "x/events_x_unittest.cc", + ] + + if (!use_x11) { + sources -= [ + "x/events_x_unittest.cc", + ] + } + + if (use_ozone) { + sources += [ + "ozone/evdev/key_event_converter_evdev_unittest.cc", + "ozone/evdev/touch_event_converter_evdev_unittest.cc", + ] + } + + deps = [ + ":events", + ":events_base", + ":events_test_support", + ":gesture_detection", + "//base", + "//base/test:run_all_unittests", + "//skia", + "//testing/gtest", + "//ui/gfx:gfx_test_support", + ] +} diff --git a/chromium/ui/events/PRESUBMIT.py b/chromium/ui/events/PRESUBMIT.py new file mode 100644 index 00000000000..dcdf2d5fbfd --- /dev/null +++ b/chromium/ui/events/PRESUBMIT.py @@ -0,0 +1,26 @@ +# Copyright 2014 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. + +"""Chromium presubmit script for src/ui/events + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details on the presubmit API built into gcl. +""" + +def GetPreferredTryMasters(project, change): + tests = set(['ash_unittests', + 'aura_unittests', + 'events_unittests', + 'keyboard_unittests', + 'views_unittests']) + + return { + 'tryserver.chromium': { + 'linux_chromium_rel': tests, + 'linux_chromium_chromeos_rel': tests, + 'linux_chromeos_asan': tests, + 'win_chromium_compile_dbg': tests, + 'win_chromium_rel': tests, + } + } diff --git a/chromium/ui/events/cocoa/cocoa_event_utils.h b/chromium/ui/events/cocoa/cocoa_event_utils.h new file mode 100644 index 00000000000..a5984357537 --- /dev/null +++ b/chromium/ui/events/cocoa/cocoa_event_utils.h @@ -0,0 +1,26 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_ +#define UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_ + +#import <Cocoa/Cocoa.h> + +#include "ui/events/events_export.h" + +namespace ui { + +// Converts the Cocoa |modifiers| bitsum into a ui::EventFlags bitsum. +EVENTS_EXPORT int EventFlagsFromModifiers(NSUInteger modifiers); + +// Retrieves a bitsum of ui::EventFlags represented by |event|, +// but instead use the modifier flags given by |modifiers|, +// which is the same format as |-NSEvent modifierFlags|. This allows +// substitution of the modifiers without having to create a new event from +// scratch. +EVENTS_EXPORT int EventFlagsFromNSEventWithModifiers(NSEvent* event, + NSUInteger modifiers); +} // namespace ui + +#endif // UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_ diff --git a/chromium/ui/events/cocoa/cocoa_event_utils.mm b/chromium/ui/events/cocoa/cocoa_event_utils.mm new file mode 100644 index 00000000000..ade083c0eb2 --- /dev/null +++ b/chromium/ui/events/cocoa/cocoa_event_utils.mm @@ -0,0 +1,55 @@ +// Copyright 2014 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. + +#import "ui/events/cocoa/cocoa_event_utils.h" + +#include "ui/events/event_constants.h" +#include "ui/events/event_utils.h" + +namespace { + +bool IsLeftButtonEvent(NSEvent* event) { + NSEventType type = [event type]; + return type == NSLeftMouseDown || type == NSLeftMouseDragged || + type == NSLeftMouseUp; +} + +bool IsRightButtonEvent(NSEvent* event) { + NSEventType type = [event type]; + return type == NSRightMouseDown || type == NSRightMouseDragged || + type == NSRightMouseUp; +} + +bool IsMiddleButtonEvent(NSEvent* event) { + if ([event buttonNumber] != 2) + return false; + + NSEventType type = [event type]; + return type == NSOtherMouseDown || type == NSOtherMouseDragged || + type == NSOtherMouseUp; +} + +} // namespace + +namespace ui { + +int EventFlagsFromModifiers(NSUInteger modifiers) { + int flags = 0; + flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0; + flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0; + flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0; + flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0; + flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0; + return flags; +} + +int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) { + int flags = EventFlagsFromModifiers(modifiers); + flags |= IsLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0; + flags |= IsRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0; + flags |= IsMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0; + return flags; +} + +} // namespace ui diff --git a/chromium/ui/events/cocoa/events_mac.mm b/chromium/ui/events/cocoa/events_mac.mm index ffc34d7f2a7..7675eb6af05 100644 --- a/chromium/ui/events/cocoa/events_mac.mm +++ b/chromium/ui/events/cocoa/events_mac.mm @@ -1,59 +1,56 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2014 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 <Cocoa/Cocoa.h> +#include "ui/events/event_utils.h" -#include "ui/events/event_constants.h" +#include <Cocoa/Cocoa.h> -#include "base/event_types.h" #include "base/logging.h" #include "base/time/time.h" +#include "build/build_config.h" +#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/events/event_utils.h" #import "ui/events/keycodes/keyboard_code_conversion_mac.h" #include "ui/gfx/point.h" +#include "ui/gfx/vector2d.h" namespace ui { +void UpdateDeviceList() { + NOTIMPLEMENTED(); +} + EventType EventTypeFromNative(const base::NativeEvent& native_event) { - NSEventType native_type = [native_event type]; - switch (native_type) { + NSEventType type = [native_event type]; + switch (type) { + case NSKeyDown: + return ET_KEY_PRESSED; + case NSKeyUp: + return ET_KEY_RELEASED; case NSLeftMouseDown: case NSRightMouseDown: case NSOtherMouseDown: return ET_MOUSE_PRESSED; - case NSLeftMouseUp: case NSRightMouseUp: case NSOtherMouseUp: return ET_MOUSE_RELEASED; - - case NSMouseMoved: - return ET_MOUSE_MOVED; - case NSLeftMouseDragged: case NSRightMouseDragged: case NSOtherMouseDragged: return ET_MOUSE_DRAGGED; - + case NSMouseMoved: + return ET_MOUSE_MOVED; + case NSScrollWheel: + return ET_MOUSEWHEEL; case NSMouseEntered: return ET_MOUSE_ENTERED; - case NSMouseExited: return ET_MOUSE_EXITED; - - case NSKeyDown: - return ET_KEY_PRESSED; - - case NSKeyUp: - return ET_KEY_RELEASED; - + case NSEventTypeSwipe: + return ET_SCROLL_FLING_START; case NSFlagsChanged: - return ET_KEY_PRESSED; - - case NSScrollWheel: - return ET_MOUSEWHEEL; - case NSAppKitDefined: case NSSystemDefined: case NSApplicationDefined: @@ -61,156 +58,176 @@ EventType EventTypeFromNative(const base::NativeEvent& native_event) { case NSCursorUpdate: case NSTabletPoint: case NSTabletProximity: + case NSEventTypeGesture: + case NSEventTypeMagnify: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: + NOTIMPLEMENTED() << type; + break; default: - return ET_UNKNOWN; + NOTIMPLEMENTED() << type; + break; } + return ET_UNKNOWN; } -int EventFlagsFromNative(const base::NativeEvent& native_event) { - int event_flags = 0; - NSUInteger modifiers = [native_event modifierFlags]; - - if (modifiers & NSAlphaShiftKeyMask) - event_flags = event_flags | EF_CAPS_LOCK_DOWN; - - if (modifiers & NSShiftKeyMask) - event_flags = event_flags | EF_SHIFT_DOWN; - - if (modifiers & NSControlKeyMask) - event_flags = event_flags | EF_CONTROL_DOWN; - - if (modifiers & NSAlternateKeyMask) - event_flags = event_flags | EF_ALT_DOWN; - - if (modifiers & NSCommandKeyMask) - event_flags = event_flags | EF_COMMAND_DOWN; - - NSEventType type = [native_event type]; - - if (type == NSLeftMouseDown || - type == NSLeftMouseUp || - type == NSLeftMouseDragged) { - event_flags = event_flags | EF_LEFT_MOUSE_BUTTON; - } - - if (type == NSRightMouseDown || - type == NSRightMouseUp || - type == NSRightMouseDragged) { - event_flags = event_flags | EF_RIGHT_MOUSE_BUTTON; - } - - if (type == NSOtherMouseDown || - type == NSOtherMouseUp || - type == NSOtherMouseDragged) { - event_flags = event_flags | EF_MIDDLE_MOUSE_BUTTON; - } - - return event_flags; +int EventFlagsFromNative(const base::NativeEvent& event) { + NSUInteger modifiers = [event modifierFlags]; + return EventFlagsFromNSEventWithModifiers(event, modifiers); } base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) { - return base::TimeDelta::FromMicroseconds( - [native_event timestamp] * 1000000.0f); + NSTimeInterval since_system_startup = [native_event timestamp]; + // Truncate to extract seconds before doing floating point arithmetic. + int64_t seconds = since_system_startup; + since_system_startup -= seconds; + int64_t microseconds = since_system_startup * 1000000; + return base::TimeDelta::FromSeconds(seconds) + + base::TimeDelta::FromMicroseconds(microseconds); } gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) { NSWindow* window = [native_event window]; + if (!window) { + NOTIMPLEMENTED(); // Point will be in screen coordinates. + return gfx::Point(); + } NSPoint location = [native_event locationInWindow]; - - // Convert |location| to be relative to coordinate system of |contentView|. - // Note: this assumes that ui::Event coordinates are rooted in the top-level - // view (with flipped coordinates). A more general (but costly) approach - // would be to hit-test the view of the event and use the found view's - // coordinate system. Currently there is no need for this generality, and - // speed is preferred. Flipped views are not suppported. - DCHECK([[window contentView] isFlipped] == NO); - location = [[window contentView] convertPoint:location fromView:nil]; - location.y = [[window contentView] bounds].size.height - location.y; - - return gfx::Point(NSPointToCGPoint(location)); + NSRect content_rect = [window contentRectForFrameRect:[window frame]]; + return gfx::Point(location.x, NSHeight(content_rect) - location.y); } gfx::Point EventSystemLocationFromNative( const base::NativeEvent& native_event) { - // TODO(port): Needs to always return screen position here. Returning normal - // origin for now since that's obviously wrong. - return gfx::Point(0, 0); + NOTIMPLEMENTED(); + return gfx::Point(); } -KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) { - return ui::KeyboardCodeFromNSEvent(native_event); +int EventButtonFromNative(const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); + return 0; +} + +int GetChangedMouseButtonFlagsFromNative( + const base::NativeEvent& native_event) { + NSEventType type = [native_event type]; + switch (type) { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSLeftMouseDragged: + return EF_LEFT_MOUSE_BUTTON; + case NSRightMouseDown: + case NSRightMouseUp: + case NSRightMouseDragged: + return EF_RIGHT_MOUSE_BUTTON; + case NSOtherMouseDown: + case NSOtherMouseUp: + case NSOtherMouseDragged: + return EF_MIDDLE_MOUSE_BUTTON; + } + return 0; } -std::string CodeFromNative(const base::NativeEvent& native_event) { - return ui::CodeFromNSEvent(native_event); +gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& event) { + // Empirically, a value of 0.1 is typical for one mousewheel click. Positive + // values when scrolling up or to the left. Scrolling quickly results in a + // higher delta per click, up to about 15.0. (Quartz documentation suggests + // +/-10). + // Multiply by 1000 to vaguely approximate WHEEL_DELTA on Windows (120). + const CGFloat kWheelDeltaMultiplier = 1000; + return gfx::Vector2d(kWheelDeltaMultiplier * [event deltaX], + kWheelDeltaMultiplier * [event deltaY]); } -bool IsMouseEvent(const base::NativeEvent& native_event) { - EventType type = EventTypeFromNative(native_event); - return type == ET_MOUSE_PRESSED || - type == ET_MOUSE_DRAGGED || - type == ET_MOUSE_RELEASED || - type == ET_MOUSE_MOVED || - type == ET_MOUSE_ENTERED || - type == ET_MOUSE_EXITED; +base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { + return [event copy]; } -gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { - // TODO(dhollowa): Come back to this once comparisons can be made with other - // platforms. - return gfx::Vector2d([native_event deltaX], [native_event deltaY]); +void ReleaseCopiedNativeEvent(const base::NativeEvent& event) { + [event release]; } -void ClearTouchIdIfReleased(const base::NativeEvent& xev) { - // Touch is currently unsupported. +void ClearTouchIdIfReleased(const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); } int GetTouchId(const base::NativeEvent& native_event) { - // Touch is currently unsupported. + NOTIMPLEMENTED(); return 0; } float GetTouchRadiusX(const base::NativeEvent& native_event) { - // Touch is currently unsupported. - return 1.0; + NOTIMPLEMENTED(); + return 0.f; } float GetTouchRadiusY(const base::NativeEvent& native_event) { - // Touch is currently unsupported. - return 1.0; + NOTIMPLEMENTED(); + return 0.f; } float GetTouchAngle(const base::NativeEvent& native_event) { - // Touch is currently unsupported. - return 0.0; + NOTIMPLEMENTED(); + return 0.f; } float GetTouchForce(const base::NativeEvent& native_event) { - // Touch is currently unsupported. - return 0.0; + NOTIMPLEMENTED(); + return 0.f; } bool GetScrollOffsets(const base::NativeEvent& native_event, float* x_offset, float* y_offset, + float* x_offset_ordinal, + float* y_offset_ordinal, int* finger_count) { + NOTIMPLEMENTED(); return false; } -bool IsNoopEvent(const base::NativeEvent& event) { - return ([event type] == NSApplicationDefined && [event subtype] == 0); +bool GetFlingData(const base::NativeEvent& native_event, + float* vx, + float* vy, + float* vx_ordinal, + float* vy_ordinal, + bool* is_cancel) { + NOTIMPLEMENTED(); + return false; +} + +bool GetGestureTimes(const base::NativeEvent& native_event, + double* start_time, + double* end_time) { + NOTIMPLEMENTED(); + return false; +} + +void SetNaturalScroll(bool enabled) { + NOTIMPLEMENTED(); +} + +bool IsNaturalScrollEnabled() { + NOTIMPLEMENTED(); + return false; +} + +bool IsTouchpadEvent(const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); + return false; +} + +KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) { + return KeyboardCodeFromNSEvent(native_event); +} + +const char* CodeFromNative(const base::NativeEvent& native_event) { + return CodeFromNSEvent(native_event); } -base::NativeEvent CreateNoopEvent() { - return [NSEvent otherEventWithType:NSApplicationDefined - location:NSZeroPoint - modifierFlags:0 - timestamp:[NSDate timeIntervalSinceReferenceDate] - windowNumber:0 - context:nil - subtype:0 - data1:0 - data2:0]; +uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) { + return native_event.keyCode; } } // namespace ui diff --git a/chromium/ui/events/cocoa/events_mac_unittest.mm b/chromium/ui/events/cocoa/events_mac_unittest.mm new file mode 100644 index 00000000000..77f85495f48 --- /dev/null +++ b/chromium/ui/events/cocoa/events_mac_unittest.mm @@ -0,0 +1,315 @@ +// Copyright 2014 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/event_utils.h" + +#import <Cocoa/Cocoa.h> + +#include "base/mac/scoped_cftyperef.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_constants.h" +#import "ui/events/test/cocoa_test_event_utils.h" +#include "ui/gfx/point.h" +#import "ui/gfx/test/ui_cocoa_test_helper.h" + +namespace { + +NSWindow* g_test_window = nil; + +} // namespace + +// Mac APIs for creating test events are frustrating. Quartz APIs have, e.g., +// CGEventCreateMouseEvent() which can't set a window or modifier flags. +// Cocoa APIs have +[NSEvent mouseEventWithType:..] which can't set +// buttonNumber or scroll deltas. To work around this, these tests use some +// Objective C magic to donate member functions to NSEvent temporarily. +@interface MiddleMouseButtonNumberDonor : NSObject +@end + +@interface TestWindowDonor : NSObject +@end + +@implementation MiddleMouseButtonNumberDonor +- (NSUInteger)buttonNumber { return 2; } +@end + +@implementation TestWindowDonor +- (NSWindow*)window { return g_test_window; } +@end + +namespace ui { + +namespace { + +class EventsMacTest : public CocoaTest { + public: + EventsMacTest() {} + + gfx::Point Flip(gfx::Point window_location) { + NSRect window_frame = [test_window() frame]; + CGFloat content_height = + NSHeight([test_window() contentRectForFrameRect:window_frame]); + window_location.set_y(content_height - window_location.y()); + return window_location; + } + + void SwizzleMiddleMouseButton() { + DCHECK(!swizzler_); + swizzler_.reset(new ScopedClassSwizzler( + [NSEvent class], + [MiddleMouseButtonNumberDonor class], + @selector(buttonNumber))); + } + + void SwizzleTestWindow() { + DCHECK(!g_test_window); + DCHECK(!swizzler_); + g_test_window = test_window(); + swizzler_.reset(new ScopedClassSwizzler( + [NSEvent class], + [TestWindowDonor class], + @selector(window))); + } + + void ClearSwizzle() { + swizzler_.reset(); + g_test_window = nil; + } + + NSEvent* TestMouseEvent(NSEventType type, + const gfx::Point &window_location, + NSInteger modifier_flags) { + NSPoint point = NSPointFromCGPoint(Flip(window_location).ToCGPoint()); + return [NSEvent mouseEventWithType:type + location:point + modifierFlags:modifier_flags + timestamp:0 + windowNumber:[test_window() windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:1.0]; + } + + NSEvent* TestScrollEvent(const gfx::Point& window_location, + int32_t delta_x, + int32_t delta_y) { + SwizzleTestWindow(); + base::ScopedCFTypeRef<CGEventRef> scroll( + CGEventCreateScrollWheelEvent(NULL, + kCGScrollEventUnitLine, + 2, + delta_y, + delta_x)); + // CGEvents are always in global display coordinates. These are like screen + // coordinates, but flipped. But first the point needs to be converted out + // of window coordinates (which also requires flipping). + NSPoint window_point = + NSPointFromCGPoint(Flip(window_location).ToCGPoint()); + NSPoint screen_point = [test_window() convertBaseToScreen:window_point]; + CGFloat primary_screen_height = + NSHeight([[[NSScreen screens] objectAtIndex:0] frame]); + screen_point.y = primary_screen_height - screen_point.y; + CGEventSetLocation(scroll, NSPointToCGPoint(screen_point)); + return [NSEvent eventWithCGEvent:scroll]; + } + + private: + scoped_ptr<ScopedClassSwizzler> swizzler_; + + DISALLOW_COPY_AND_ASSIGN(EventsMacTest); +}; + +} // namespace + +TEST_F(EventsMacTest, EventFlagsFromNative) { + // Left click. + NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left)); + + // Right click. + NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp, + 0); + EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right)); + + // Middle click. + NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp, + 0); + EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle)); + + // Caps + Left + NSEvent* caps = cocoa_test_event_utils::MouseEventWithType( + NSLeftMouseUp, NSAlphaShiftKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CAPS_LOCK_DOWN, + EventFlagsFromNative(caps)); + + // Shift + Left + NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, + NSShiftKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift)); + + // Ctrl + Left + NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, + NSControlKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CONTROL_DOWN, EventFlagsFromNative(ctrl)); + + // Alt + Left + NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, + NSAlternateKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt)); + + // Cmd + Left + NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, + NSCommandKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN, EventFlagsFromNative(cmd)); + + // Shift + Ctrl + Left + NSEvent* shiftctrl = cocoa_test_event_utils::MouseEventWithType( + NSLeftMouseUp, NSShiftKeyMask | NSControlKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN | EF_CONTROL_DOWN, + EventFlagsFromNative(shiftctrl)); + + // Cmd + Alt + Right + NSEvent* cmdalt = cocoa_test_event_utils::MouseEventWithType( + NSLeftMouseUp, NSCommandKeyMask | NSAlternateKeyMask); + EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN | EF_ALT_DOWN, + EventFlagsFromNative(cmdalt)); +} + +// Tests mouse button presses and mouse wheel events. +TEST_F(EventsMacTest, ButtonEvents) { + gfx::Point location(5, 10); + gfx::Vector2d offset; + + NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); + EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + + SwizzleMiddleMouseButton(); + event = TestMouseEvent(NSOtherMouseDown, location, NSShiftKeyMask); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); + EXPECT_EQ(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_SHIFT_DOWN, + ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + ClearSwizzle(); + + event = TestMouseEvent(NSRightMouseUp, location, 0); + EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(event)); + EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + + // Scroll up. + event = TestScrollEvent(location, 0, 1); + EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); + EXPECT_EQ(0, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location.ToString(), ui::EventLocationFromNative(event).ToString()); + offset = ui::GetMouseWheelOffset(event); + EXPECT_GT(offset.y(), 0); + EXPECT_EQ(0, offset.x()); + ClearSwizzle(); + + // Scroll down. + event = TestScrollEvent(location, 0, -1); + EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); + EXPECT_EQ(0, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + offset = ui::GetMouseWheelOffset(event); + EXPECT_LT(offset.y(), 0); + EXPECT_EQ(0, offset.x()); + ClearSwizzle(); + + // Scroll left. + event = TestScrollEvent(location, 1, 0); + EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); + EXPECT_EQ(0, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + offset = ui::GetMouseWheelOffset(event); + EXPECT_EQ(0, offset.y()); + EXPECT_GT(offset.x(), 0); + ClearSwizzle(); + + // Scroll right. + event = TestScrollEvent(location, -1, 0); + EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); + EXPECT_EQ(0, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + offset = ui::GetMouseWheelOffset(event); + EXPECT_EQ(0, offset.y()); + EXPECT_LT(offset.x(), 0); + ClearSwizzle(); +} + +// Test correct location when the window has a native titlebar. +TEST_F(EventsMacTest, NativeTitlebarEventLocation) { + gfx::Point location(5, 10); + NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask | + NSMiniaturizableWindowMask | NSResizableWindowMask; + + // First check that the window provided by ui::CocoaTest is how we think. + DCHECK_EQ(NSBorderlessWindowMask, [test_window() styleMask]); + [test_window() setStyleMask:style_mask]; + DCHECK_EQ(style_mask, [test_window() styleMask]); + + // EventLocationFromNative should behave the same as the ButtonEvents test. + NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0); + EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); + EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); + EXPECT_EQ(location, ui::EventLocationFromNative(event)); + + // And be explicit, to ensure the test doesn't depend on some property of the + // test harness. The change to the frame rect could be OS-specfic, so set it + // to a known value. + const CGFloat kTestHeight = 400; + NSRect content_rect = NSMakeRect(0, 0, 600, kTestHeight); + NSRect frame_rect = [test_window() frameRectForContentRect:content_rect]; + [test_window() setFrame:frame_rect display:YES]; + event = [NSEvent mouseEventWithType:NSLeftMouseDown + location:NSMakePoint(0, 0) // Bottom-left corner. + modifierFlags:0 + timestamp:0 + windowNumber:[test_window() windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:1.0]; + // Bottom-left corner should be flipped. + EXPECT_EQ(gfx::Point(0, kTestHeight), ui::EventLocationFromNative(event)); + + // Removing the border, and sending the same event should move it down in the + // toolkit-views coordinate system. + int height_change = NSHeight(frame_rect) - kTestHeight; + EXPECT_GT(height_change, 0); + [test_window() setStyleMask:NSBorderlessWindowMask]; + [test_window() setFrame:frame_rect display:YES]; + EXPECT_EQ(gfx::Point(0, kTestHeight + height_change), + ui::EventLocationFromNative(event)); +} + +// Testing for ui::EventTypeFromNative() not covered by ButtonEvents. +TEST_F(EventsMacTest, EventTypeFromNative) { + NSEvent* event = cocoa_test_event_utils::KeyEventWithType(NSKeyDown, 0); + EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(event)); + + event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0); + EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event)); + + event = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseDragged, 0); + EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); + event = cocoa_test_event_utils::MouseEventWithType(NSRightMouseDragged, 0); + EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); + event = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseDragged, 0); + EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); + + event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0); + EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event)); + + event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseEntered); + EXPECT_EQ(ui::ET_MOUSE_ENTERED, ui::EventTypeFromNative(event)); + event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseExited); + EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(event)); +} + +} // namespace ui diff --git a/chromium/ui/events/event.cc b/chromium/ui/events/event.cc index 246e39721f2..ed7ff3adaea 100644 --- a/chromium/ui/events/event.cc +++ b/chromium/ui/events/event.cc @@ -5,6 +5,7 @@ #include "ui/events/event.h" #if defined(USE_X11) +#include <X11/extensions/XInput2.h> #include <X11/Xlib.h> #endif @@ -28,24 +29,6 @@ namespace { -base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { -#if defined(USE_X11) - if (!event || event->type == GenericEvent) - return NULL; - XEvent* copy = new XEvent; - *copy = *event; - return copy; -#elif defined(OS_WIN) - return event; -#elif defined(USE_OZONE) - return NULL; -#else - NOTREACHED() << - "Don't know how to copy base::NativeEvent for this platform"; - return NULL; -#endif -} - std::string EventTypeName(ui::EventType type) { #define RETURN_IF_TYPE(t) if (type == ui::t) return #t #define CASE_TYPE(t) case ui::t: return #t @@ -64,7 +47,6 @@ std::string EventTypeName(ui::EventType type) { CASE_TYPE(ET_TOUCH_RELEASED); CASE_TYPE(ET_TOUCH_PRESSED); CASE_TYPE(ET_TOUCH_MOVED); - CASE_TYPE(ET_TOUCH_STATIONARY); CASE_TYPE(ET_TOUCH_CANCELLED); CASE_TYPE(ET_DROP_TARGET_EVENT); CASE_TYPE(ET_TRANSLATED_KEY_PRESS); @@ -73,6 +55,7 @@ std::string EventTypeName(ui::EventType type) { CASE_TYPE(ET_GESTURE_SCROLL_END); CASE_TYPE(ET_GESTURE_SCROLL_UPDATE); CASE_TYPE(ET_GESTURE_SHOW_PRESS); + CASE_TYPE(ET_GESTURE_WIN8_EDGE_SWIPE); CASE_TYPE(ET_GESTURE_TAP); CASE_TYPE(ET_GESTURE_TAP_DOWN); CASE_TYPE(ET_GESTURE_TAP_CANCEL); @@ -84,7 +67,9 @@ std::string EventTypeName(ui::EventType type) { CASE_TYPE(ET_GESTURE_PINCH_UPDATE); CASE_TYPE(ET_GESTURE_LONG_PRESS); CASE_TYPE(ET_GESTURE_LONG_TAP); - CASE_TYPE(ET_GESTURE_MULTIFINGER_SWIPE); + CASE_TYPE(ET_GESTURE_SWIPE); + CASE_TYPE(ET_GESTURE_TAP_UNCONFIRMED); + CASE_TYPE(ET_GESTURE_DOUBLE_TAP); CASE_TYPE(ET_SCROLL); CASE_TYPE(ET_SCROLL_FLING_START); CASE_TYPE(ET_SCROLL_FLING_CANCEL); @@ -101,10 +86,22 @@ std::string EventTypeName(ui::EventType type) { bool IsX11SendEventTrue(const base::NativeEvent& event) { #if defined(USE_X11) - if (event && event->xany.send_event) - return true; + return event && event->xany.send_event; +#else + return false; #endif +} + +bool X11EventHasNonStandardState(const base::NativeEvent& event) { +#if defined(USE_X11) + const unsigned int kAllStateMask = + Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask | + Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask | ShiftMask | + LockMask | ControlMask | AnyModifier; + return event && (event->xkey.state & ~kAllStateMask) != 0; +#else return false; +#endif } } // namespace @@ -115,10 +112,8 @@ namespace ui { // Event Event::~Event() { -#if defined(USE_X11) if (delete_native_event_) - delete native_event_; -#endif + ReleaseCopiedNativeEvent(native_event_); } bool Event::HasNativeEvent() const { @@ -147,9 +142,7 @@ Event::Event(EventType type, base::TimeDelta time_stamp, int flags) : type_(type), time_stamp_(time_stamp), flags_(flags), -#if defined(USE_X11) - native_event_(NULL), -#endif + native_event_(base::NativeEvent()), delete_native_event_(false), cancelable_(true), target_(NULL), @@ -157,7 +150,6 @@ Event::Event(EventType type, base::TimeDelta time_stamp, int flags) result_(ER_UNHANDLED) { if (type_ < ET_LAST) name_ = EventTypeName(type_); - Init(); } Event::Event(const base::NativeEvent& native_event, @@ -166,6 +158,7 @@ Event::Event(const base::NativeEvent& native_event, : type_(type), time_stamp_(EventTimeFromNative(native_event)), flags_(flags), + native_event_(native_event), delete_native_event_(false), cancelable_(true), target_(NULL), @@ -175,18 +168,17 @@ Event::Event(const base::NativeEvent& native_event, if (type_ < ET_LAST) name_ = EventTypeName(type_); UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser", - delta.InMicroseconds(), 0, 1000000, 100); + delta.InMicroseconds(), 1, 1000000, 100); std::string name_for_event = base::StringPrintf("Event.Latency.Browser.%s", name_.c_str()); base::HistogramBase* counter_for_type = base::Histogram::FactoryGet( name_for_event, - 0, + 1, 1000000, 100, base::HistogramBase::kUmaTargetedHistogramFlag); counter_for_type->Add(delta.InMicroseconds()); - InitWithNativeEvent(native_event); } Event::Event(const Event& copy) @@ -194,18 +186,14 @@ Event::Event(const Event& copy) time_stamp_(copy.time_stamp_), latency_(copy.latency_), flags_(copy.flags_), - native_event_(::CopyNativeEvent(copy.native_event_)), - delete_native_event_(false), + native_event_(CopyNativeEvent(copy.native_event_)), + delete_native_event_(true), cancelable_(true), target_(NULL), phase_(EP_PREDISPATCH), result_(ER_UNHANDLED) { if (type_ < ET_LAST) name_ = EventTypeName(type_); -#if defined(USE_X11) - if (native_event_) - delete_native_event_ = true; -#endif } void Event::SetType(EventType type) { @@ -216,14 +204,6 @@ void Event::SetType(EventType type) { name_ = EventTypeName(type_); } -void Event::Init() { - std::memset(&native_event_, 0, sizeof(native_event_)); -} - -void Event::InitWithNativeEvent(const base::NativeEvent& native_event) { - native_event_ = native_event; -} - //////////////////////////////////////////////////////////////////////////////// // CancelModeEvent @@ -250,8 +230,8 @@ LocatedEvent::LocatedEvent(const base::NativeEvent& native_event) } LocatedEvent::LocatedEvent(EventType type, - const gfx::Point& location, - const gfx::Point& root_location, + const gfx::PointF& location, + const gfx::PointF& root_location, base::TimeDelta time_stamp, int flags) : Event(type, time_stamp, flags), @@ -264,7 +244,8 @@ void LocatedEvent::UpdateForRootTransform( // Transform has to be done at root level. gfx::Point3F p(location_); reversed_root_transform.TransformPoint(&p); - root_location_ = location_ = gfx::ToFlooredPoint(p.AsPointF()); + location_ = p.AsPointF(); + root_location_ = location_; } //////////////////////////////////////////////////////////////////////////////// @@ -279,11 +260,12 @@ MouseEvent::MouseEvent(const base::NativeEvent& native_event) } MouseEvent::MouseEvent(EventType type, - const gfx::Point& location, - const gfx::Point& root_location, - int flags) + const gfx::PointF& location, + const gfx::PointF& root_location, + int flags, + int changed_button_flags) : LocatedEvent(type, location, root_location, EventTimeForNow(), flags), - changed_button_flags_(0) { + changed_button_flags_(changed_button_flags) { if (this->type() == ET_MOUSE_MOVED && IsAnyButton()) SetType(ET_MOUSE_DRAGGED); } @@ -311,10 +293,10 @@ bool MouseEvent::IsRepeatedClickEvent( if (time_difference.InMilliseconds() > kDoubleClickTimeMS) return false; - if (abs(event2.x() - event1.x()) > kDoubleClickWidth / 2) + if (std::abs(event2.x() - event1.x()) > kDoubleClickWidth / 2) return false; - if (abs(event2.y() - event1.y()) > kDoubleClickHeight / 2) + if (std::abs(event2.y() - event1.y()) > kDoubleClickHeight / 2) return false; return true; @@ -324,23 +306,46 @@ bool MouseEvent::IsRepeatedClickEvent( int MouseEvent::GetRepeatCount(const MouseEvent& event) { int click_count = 1; if (last_click_event_) { - if (event.type() == ui::ET_MOUSE_RELEASED) - return last_click_event_->GetClickCount(); - if (IsX11SendEventTrue(event.native_event())) + if (event.type() == ui::ET_MOUSE_RELEASED) { + if (event.changed_button_flags() == + last_click_event_->changed_button_flags()) { + last_click_complete_ = true; + return last_click_event_->GetClickCount(); + } else { + // If last_click_event_ has changed since this button was pressed + // return a click count of 1. + return click_count; + } + } + if (event.time_stamp() != last_click_event_->time_stamp()) + last_click_complete_ = true; + if (!last_click_complete_ || + IsX11SendEventTrue(event.native_event())) { click_count = last_click_event_->GetClickCount(); - else if (IsRepeatedClickEvent(*last_click_event_, event)) + } else if (IsRepeatedClickEvent(*last_click_event_, event)) { click_count = last_click_event_->GetClickCount() + 1; + } delete last_click_event_; } last_click_event_ = new MouseEvent(event); + last_click_complete_ = false; if (click_count > 3) click_count = 3; last_click_event_->SetClickCount(click_count); return click_count; } +void MouseEvent::ResetLastClickForTest() { + if (last_click_event_) { + delete last_click_event_; + last_click_event_ = NULL; + last_click_complete_ = false; + } +} + // static MouseEvent* MouseEvent::last_click_event_ = NULL; +bool MouseEvent::last_click_complete_ = false; int MouseEvent::GetClickCount() const { if (type() != ET_MOUSE_PRESSED && type() != ET_MOUSE_RELEASED) @@ -436,19 +441,25 @@ TouchEvent::TouchEvent(const base::NativeEvent& native_event) radius_x_(GetTouchRadiusX(native_event)), radius_y_(GetTouchRadiusY(native_event)), rotation_angle_(GetTouchAngle(native_event)), - force_(GetTouchForce(native_event)) { + force_(GetTouchForce(native_event)), + source_device_id_(-1) { latency()->AddLatencyNumberWithTimestamp( INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 0, base::TimeTicks::FromInternalValue(time_stamp().ToInternalValue()), - 1, - true); + 1); + +#if defined(USE_X11) + XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(native_event->xcookie.data); + source_device_id_ = xiev->deviceid; +#endif + latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); } TouchEvent::TouchEvent(EventType type, - const gfx::Point& location, + const gfx::PointF& location, int touch_id, base::TimeDelta time_stamp) : LocatedEvent(type, location, location, time_stamp, 0), @@ -456,12 +467,13 @@ TouchEvent::TouchEvent(EventType type, radius_x_(0.0f), radius_y_(0.0f), rotation_angle_(0.0f), - force_(0.0f) { + force_(0.0f), + source_device_id_(-1) { latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); } TouchEvent::TouchEvent(EventType type, - const gfx::Point& location, + const gfx::PointF& location, int flags, int touch_id, base::TimeDelta time_stamp, @@ -474,7 +486,8 @@ TouchEvent::TouchEvent(EventType type, radius_x_(radius_x), radius_y_(radius_y), rotation_angle_(angle), - force_(force) { + force_(force), + source_device_id_(-1) { latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0); } @@ -486,11 +499,6 @@ TouchEvent::~TouchEvent() { ClearTouchIdIfReleased(native_event()); } -void TouchEvent::Relocate(const gfx::Point& origin) { - location_ -= origin.OffsetFromOrigin(); - root_location_ -= origin.OffsetFromOrigin(); -} - void TouchEvent::UpdateForRootTransform( const gfx::Transform& inverted_root_transform) { LocatedEvent::UpdateForRootTransform(inverted_root_transform); @@ -506,6 +514,42 @@ void TouchEvent::UpdateForRootTransform( //////////////////////////////////////////////////////////////////////////////// // KeyEvent +// static +KeyEvent* KeyEvent::last_key_event_ = NULL; + +// static +bool KeyEvent::IsRepeated(const KeyEvent& event) { + // A safe guard in case if there were continous key pressed events that are + // not auto repeat. + const int kMaxAutoRepeatTimeMs = 2000; + // Ignore key events that have non standard state masks as it may be + // reposted by an IME. IBUS-GTK uses this field to detect the + // re-posted event for example. crbug.com/385873. + if (X11EventHasNonStandardState(event.native_event())) + return false; + if (event.is_char()) + return false; + if (event.type() == ui::ET_KEY_RELEASED) { + delete last_key_event_; + last_key_event_ = NULL; + return false; + } + CHECK_EQ(ui::ET_KEY_PRESSED, event.type()); + if (!last_key_event_) { + last_key_event_ = new KeyEvent(event); + return false; + } + if (event.key_code() == last_key_event_->key_code() && + event.flags() == last_key_event_->flags() && + (event.time_stamp() - last_key_event_->time_stamp()).InMilliseconds() < + kMaxAutoRepeatTimeMs) { + return true; + } + delete last_key_event_; + last_key_event_ = new KeyEvent(event); + return false; +} + KeyEvent::KeyEvent(const base::NativeEvent& native_event, bool is_char) : Event(native_event, EventTypeFromNative(native_event), @@ -513,7 +557,11 @@ KeyEvent::KeyEvent(const base::NativeEvent& native_event, bool is_char) key_code_(KeyboardCodeFromNative(native_event)), code_(CodeFromNative(native_event)), is_char_(is_char), + platform_keycode_(PlatformKeycodeFromNative(native_event)), character_(0) { + if (IsRepeated(*this)) + set_flags(flags() | ui::EF_IS_REPEAT); + #if defined(USE_X11) NormalizeFlags(); #endif @@ -526,6 +574,7 @@ KeyEvent::KeyEvent(EventType type, : Event(type, EventTimeForNow(), flags), key_code_(key_code), is_char_(is_char), + platform_keycode_(0), character_(GetCharacterFromKeyCode(key_code, flags)) { } @@ -538,6 +587,7 @@ KeyEvent::KeyEvent(EventType type, key_code_(key_code), code_(code), is_char_(is_char), + platform_keycode_(0), character_(GetCharacterFromKeyCode(key_code, flags)) { } @@ -555,10 +605,13 @@ uint16 KeyEvent::GetCharacter() const { DCHECK(native_event()->type == KeyPress || native_event()->type == KeyRelease); - uint16 ch = 0; - if (!IsControlDown()) - ch = GetCharacterFromXEvent(native_event()); - return ch ? ch : GetCharacterFromKeyCode(key_code_, flags()); + // When a control key is held, prefer ASCII characters to non ASCII + // characters in order to use it for shortcut keys. GetCharacterFromKeyCode + // returns 'a' for VKEY_A even if the key is actually bound to 'à' in X11. + // GetCharacterFromXEvent returns 'à' in that case. + return IsControlDown() ? + GetCharacterFromKeyCode(key_code_, flags()) : + GetCharacterFromXEvent(native_event()); #else if (native_event()) { DCHECK(EventTypeFromNative(native_event()) == ET_KEY_PRESSED || @@ -570,6 +623,7 @@ uint16 KeyEvent::GetCharacter() const { } bool KeyEvent::IsUnicodeKeyCode() const { +#if defined(OS_WIN) if (!IsAltDown()) return false; const int key = key_code(); @@ -583,6 +637,9 @@ bool KeyEvent::IsUnicodeKeyCode() const { key == VKEY_NEXT || key == VKEY_LEFT || key == VKEY_CLEAR || key == VKEY_RIGHT || key == VKEY_HOME || key == VKEY_UP || key == VKEY_PRIOR)); +#else + return false; +#endif } void KeyEvent::NormalizeFlags() { @@ -609,28 +666,33 @@ void KeyEvent::NormalizeFlags() { set_flags(flags() & ~mask); } -//////////////////////////////////////////////////////////////////////////////// -// TranslatedKeyEvent - -TranslatedKeyEvent::TranslatedKeyEvent(const base::NativeEvent& native_event, - bool is_char) - : KeyEvent(native_event, is_char) { - SetType(type() == ET_KEY_PRESSED ? - ET_TRANSLATED_KEY_PRESS : ET_TRANSLATED_KEY_RELEASE); -} - -TranslatedKeyEvent::TranslatedKeyEvent(bool is_press, - KeyboardCode key_code, - int flags) - : KeyEvent((is_press ? ET_TRANSLATED_KEY_PRESS : ET_TRANSLATED_KEY_RELEASE), - key_code, - flags, - false) { +bool KeyEvent::IsTranslated() const { + switch (type()) { + case ET_KEY_PRESSED: + case ET_KEY_RELEASED: + return false; + case ET_TRANSLATED_KEY_PRESS: + case ET_TRANSLATED_KEY_RELEASE: + return true; + default: + NOTREACHED(); + return false; + } } -void TranslatedKeyEvent::ConvertToKeyEvent() { - SetType(type() == ET_TRANSLATED_KEY_PRESS ? - ET_KEY_PRESSED : ET_KEY_RELEASED); +void KeyEvent::SetTranslated(bool translated) { + switch (type()) { + case ET_KEY_PRESSED: + case ET_TRANSLATED_KEY_PRESS: + SetType(translated ? ET_TRANSLATED_KEY_PRESS : ET_KEY_PRESSED); + break; + case ET_KEY_RELEASED: + case ET_TRANSLATED_KEY_RELEASE: + SetType(translated ? ET_TRANSLATED_KEY_RELEASE : ET_KEY_RELEASED); + break; + default: + NOTREACHED(); + } } //////////////////////////////////////////////////////////////////////////////// @@ -656,7 +718,7 @@ ScrollEvent::ScrollEvent(const base::NativeEvent& native_event) } ScrollEvent::ScrollEvent(EventType type, - const gfx::Point& location, + const gfx::PointF& location, base::TimeDelta time_stamp, int flags, float x_offset, @@ -664,7 +726,7 @@ ScrollEvent::ScrollEvent(EventType type, float x_offset_ordinal, float y_offset_ordinal, int finger_count) - : MouseEvent(type, location, location, flags), + : MouseEvent(type, location, location, flags, 0), x_offset_(x_offset), y_offset_(y_offset), x_offset_ordinal_(x_offset_ordinal), @@ -685,15 +747,15 @@ void ScrollEvent::Scale(const float factor) { // GestureEvent GestureEvent::GestureEvent(EventType type, - int x, - int y, + float x, + float y, int flags, base::TimeDelta time_stamp, const GestureEventDetails& details, unsigned int touch_ids_bitfield) : LocatedEvent(type, - gfx::Point(x, y), - gfx::Point(x, y), + gfx::PointF(x, y), + gfx::PointF(x, y), time_stamp, flags | EF_FROM_TOUCH), details_(details), diff --git a/chromium/ui/events/event.h b/chromium/ui/events/event.h index b8a341a2234..9c0c580707b 100644 --- a/chromium/ui/events/event.h +++ b/chromium/ui/events/event.h @@ -8,13 +8,16 @@ #include "base/basictypes.h" #include "base/compiler_specific.h" #include "base/event_types.h" +#include "base/gtest_prod_util.h" #include "base/logging.h" #include "base/time/time.h" #include "ui/events/event_constants.h" +#include "ui/events/gesture_event_details.h" #include "ui/events/gestures/gesture_types.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/latency_info.h" #include "ui/gfx/point.h" +#include "ui/gfx/point_conversions.h" namespace gfx { class Transform; @@ -79,6 +82,7 @@ class EVENTS_EXPORT Event { bool IsCapsLockDown() const { return (flags_ & EF_CAPS_LOCK_DOWN) != 0; } bool IsAltDown() const { return (flags_ & EF_ALT_DOWN) != 0; } bool IsAltGrDown() const { return (flags_ & EF_ALTGR_DOWN) != 0; } + bool IsRepeat() const { return (flags_ & EF_IS_REPEAT) != 0; } bool IsKeyEvent() const { return type_ == ET_KEY_PRESSED || @@ -102,7 +106,6 @@ class EVENTS_EXPORT Event { return type_ == ET_TOUCH_RELEASED || type_ == ET_TOUCH_PRESSED || type_ == ET_TOUCH_MOVED || - type_ == ET_TOUCH_STATIONARY || type_ == ET_TOUCH_CANCELLED; } @@ -122,8 +125,9 @@ class EVENTS_EXPORT Event { case ET_GESTURE_PINCH_UPDATE: case ET_GESTURE_LONG_PRESS: case ET_GESTURE_LONG_TAP: - case ET_GESTURE_MULTIFINGER_SWIPE: + case ET_GESTURE_SWIPE: case ET_GESTURE_SHOW_PRESS: + case ET_GESTURE_WIN8_EDGE_SWIPE: // When adding a gesture event which is paired with an event which // occurs earlier, add the event to |IsEndingEvent|. return true; @@ -216,10 +220,6 @@ class EVENTS_EXPORT Event { private: friend class EventTestApi; - // Safely initializes the native event members of this class. - void Init(); - void InitWithNativeEvent(const base::NativeEvent& native_event); - EventType type_; std::string name_; base::TimeDelta time_stamp_; @@ -243,14 +243,22 @@ class EVENTS_EXPORT LocatedEvent : public Event { public: virtual ~LocatedEvent(); - int x() const { return location_.x(); } - int y() const { return location_.y(); } - void set_location(const gfx::Point& location) { location_ = location; } - gfx::Point location() const { return location_; } - void set_root_location(const gfx::Point& root_location) { + float x() const { return location_.x(); } + float y() const { return location_.y(); } + void set_location(const gfx::PointF& location) { location_ = location; } + // TODO(tdresser): Always return floating point location. See + // crbug.com/337824. + gfx::Point location() const { return gfx::ToFlooredPoint(location_); } + const gfx::PointF& location_f() const { return location_; } + void set_root_location(const gfx::PointF& root_location) { root_location_ = root_location; } - gfx::Point root_location() const { return root_location_; } + gfx::Point root_location() const { + return gfx::ToFlooredPoint(root_location_); + } + const gfx::PointF& root_location_f() const { + return root_location_; + } // Transform the locations using |inverted_root_transform|. // This is applied to both |location_| and |root_location_|. @@ -258,8 +266,14 @@ class EVENTS_EXPORT LocatedEvent : public Event { const gfx::Transform& inverted_root_transform); template <class T> void ConvertLocationToTarget(T* source, T* target) { - if (target && target != source) - T::ConvertPointToTarget(source, target, &location_); + if (!target || target == source) + return; + // TODO(tdresser): Rewrite ConvertPointToTarget to use PointF. See + // crbug.com/337824. + gfx::Point offset = gfx::ToFlooredPoint(location_); + T::ConvertPointToTarget(source, target, &offset); + gfx::Vector2d diff = gfx::ToFlooredPoint(location_) - offset; + location_= location_ - diff; } protected: @@ -279,16 +293,16 @@ class EVENTS_EXPORT LocatedEvent : public Event { // Used for synthetic events in testing. LocatedEvent(EventType type, - const gfx::Point& location, - const gfx::Point& root_location, + const gfx::PointF& location, + const gfx::PointF& root_location, base::TimeDelta time_stamp, int flags); - gfx::Point location_; + gfx::PointF location_; // |location_| multiplied by an optional transformation matrix for // rotations, animations and skews. - gfx::Point root_location_; + gfx::PointF root_location_; }; class EVENTS_EXPORT MouseEvent : public LocatedEvent { @@ -319,9 +333,10 @@ class EVENTS_EXPORT MouseEvent : public LocatedEvent { // Used for synthetic events in testing and by the gesture recognizer. MouseEvent(EventType type, - const gfx::Point& location, - const gfx::Point& root_location, - int flags); + const gfx::PointF& location, + const gfx::PointF& root_location, + int flags, + int changed_button_flags); // Conveniences to quickly test what button is down bool IsOnlyLeftMouseButton() const { @@ -376,14 +391,25 @@ class EVENTS_EXPORT MouseEvent : public LocatedEvent { int changed_button_flags() const { return changed_button_flags_; } private: + FRIEND_TEST_ALL_PREFIXES(EventTest, DoubleClickRequiresRelease); + FRIEND_TEST_ALL_PREFIXES(EventTest, SingleClickRightLeft); + // Returns the repeat count based on the previous mouse click, if it is // recent enough and within a small enough distance. static int GetRepeatCount(const MouseEvent& click_event); + // Resets the last_click_event_ for unit tests. + static void ResetLastClickForTest(); + // See description above getter for details. int changed_button_flags_; static MouseEvent* last_click_event_; + + // We can create a MouseEvent for a native event more than once. We set this + // to true when the next event either has a different timestamp or we see a + // release signalling that the press (click) event was completed. + static bool last_click_complete_; }; class ScrollEvent; @@ -401,11 +427,9 @@ class EVENTS_EXPORT MouseWheelEvent : public MouseEvent { template <class T> MouseWheelEvent(const MouseWheelEvent& model, T* source, - T* target, - EventType type, - int flags) - : MouseEvent(model, source, target, type, flags), - offset_(model.x_offset(), model.y_offset()){ + T* target) + : MouseEvent(model, source, target, model.type(), model.flags()), + offset_(model.x_offset(), model.y_offset()) { } // The amount to scroll. This is in multiples of kWheelDelta. @@ -436,16 +460,17 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent { radius_x_(model.radius_x_), radius_y_(model.radius_y_), rotation_angle_(model.rotation_angle_), - force_(model.force_) { + force_(model.force_), + source_device_id_(model.source_device_id_) { } TouchEvent(EventType type, - const gfx::Point& root_location, + const gfx::PointF& location, int touch_id, base::TimeDelta time_stamp); TouchEvent(EventType type, - const gfx::Point& location, + const gfx::PointF& location, int flags, int touch_id, base::TimeDelta timestamp, @@ -461,15 +486,14 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent { float radius_y() const { return radius_y_; } float rotation_angle() const { return rotation_angle_; } float force() const { return force_; } - - // Relocate the touch-point to a new |origin|. - // This is useful when touch event is in X Root Window coordinates, - // and it needs to be mapped into Aura Root Window coordinates. - void Relocate(const gfx::Point& origin); + int source_device_id() const { return source_device_id_; } // Used for unit tests. void set_radius_x(const float r) { radius_x_ = r; } void set_radius_y(const float r) { radius_y_ = r; } + void set_source_device_id(int source_device_id) { + source_device_id_ = source_device_id; + } // Overridden from LocatedEvent. virtual void UpdateForRootTransform( @@ -503,6 +527,9 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent { // Force (pressure) of the touch. Normalized to be [0, 1]. Default to be 0.0. float force_; + + // The device id of the screen the event came from. Default to be -1. + int source_device_id_; }; class EVENTS_EXPORT KeyEvent : public Event { @@ -526,6 +553,8 @@ class EVENTS_EXPORT KeyEvent : public Event { // BMP characters. uint16 GetCharacter() const; + // Gets the platform key code. For XKB, this is the xksym value. + uint32 platform_keycode() const { return platform_keycode_; } KeyboardCode key_code() const { return key_code_; } bool is_char() const { return is_char_; } @@ -545,6 +574,16 @@ class EVENTS_EXPORT KeyEvent : public Event { // in http://crbug.com/127142#c8, the normalization is necessary. void NormalizeFlags(); + // Returns true if the key event has already been processed by an input method + // and there is no need to pass the key event to the input method again. + bool IsTranslated() const; + // Marks this key event as translated or not translated. + void SetTranslated(bool translated); + + protected: + // This allows a subclass TranslatedKeyEvent to be a non character event. + void set_is_char(bool is_char) { is_char_ = is_char; } + private: KeyboardCode key_code_; @@ -559,28 +598,21 @@ class EVENTS_EXPORT KeyEvent : public Event { // share the same type: ET_KEY_PRESSED. bool is_char_; - uint16 character_; -}; - -// A key event which is translated by an input method (IME). -// For example, if an IME receives a KeyEvent(VKEY_SPACE), and it does not -// consume the key, the IME usually generates and dispatches a -// TranslatedKeyEvent(VKEY_SPACE) event. If the IME receives a KeyEvent and -// it does consume the event, it might dispatch a -// TranslatedKeyEvent(VKEY_PROCESSKEY) event as defined in the DOM spec. -class EVENTS_EXPORT TranslatedKeyEvent : public KeyEvent { - public: - TranslatedKeyEvent(const base::NativeEvent& native_event, bool is_char); + // The platform related keycode value. For XKB, it's keysym value. + // For now, this is used for CharacterComposer in ChromeOS. + uint32 platform_keycode_; - // Used for synthetic events such as a VKEY_PROCESSKEY key event. - TranslatedKeyEvent(bool is_press, KeyboardCode key_code, int flags); + // String of 'key' defined in DOM KeyboardEvent (e.g. 'a', 'â') + // http://www.w3.org/TR/uievents/#keyboard-key-codes. + // + // This value represents the text that the key event will insert to input + // field. For key with modifier key, it may have specifial text. + // e.g. CTRL+A has '\x01'. + uint16 character_; - // Changes the type() of the object from ET_TRANSLATED_KEY_* to ET_KEY_* so - // that RenderWidgetHostViewAura and NativeWidgetAura could handle the event. - void ConvertToKeyEvent(); + static bool IsRepeated(const KeyEvent& event); - private: - DISALLOW_COPY_AND_ASSIGN(TranslatedKeyEvent); + static KeyEvent* last_key_event_; }; class EVENTS_EXPORT ScrollEvent : public MouseEvent { @@ -600,7 +632,7 @@ class EVENTS_EXPORT ScrollEvent : public MouseEvent { // Used for tests. ScrollEvent(EventType type, - const gfx::Point& location, + const gfx::PointF& location, base::TimeDelta time_stamp, int flags, float x_offset, @@ -634,8 +666,8 @@ class EVENTS_EXPORT ScrollEvent : public MouseEvent { class EVENTS_EXPORT GestureEvent : public LocatedEvent { public: GestureEvent(EventType type, - int x, - int y, + float x, + float y, int flags, base::TimeDelta time_stamp, const GestureEventDetails& details, diff --git a/chromium/ui/events/event_constants.h b/chromium/ui/events/event_constants.h index 2a545b740ae..d2ff97c7aec 100644 --- a/chromium/ui/events/event_constants.h +++ b/chromium/ui/events/event_constants.h @@ -23,7 +23,6 @@ enum EventType { ET_TOUCH_RELEASED, ET_TOUCH_PRESSED, ET_TOUCH_MOVED, - ET_TOUCH_STATIONARY, ET_TOUCH_CANCELLED, ET_DROP_TARGET_EVENT, ET_TRANSLATED_KEY_PRESS, @@ -31,29 +30,37 @@ enum EventType { // GestureEvent types ET_GESTURE_SCROLL_BEGIN, + ET_GESTURE_TYPE_START = ET_GESTURE_SCROLL_BEGIN, ET_GESTURE_SCROLL_END, ET_GESTURE_SCROLL_UPDATE, ET_GESTURE_TAP, ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, - ET_GESTURE_BEGIN, // Sent before any other gesture types. - ET_GESTURE_END, // Sent after any other gestures. + ET_GESTURE_TAP_UNCONFIRMED, // User tapped, but the tap delay hasn't expired. + ET_GESTURE_DOUBLE_TAP, + ET_GESTURE_BEGIN, // The first event sent when each finger is pressed. + ET_GESTURE_END, // Sent for each released finger. ET_GESTURE_TWO_FINGER_TAP, ET_GESTURE_PINCH_BEGIN, ET_GESTURE_PINCH_END, ET_GESTURE_PINCH_UPDATE, ET_GESTURE_LONG_PRESS, ET_GESTURE_LONG_TAP, - // A SWIPE gesture can happen at the end of a TAP_UP gesture if the - // finger(s) were moving quickly before they are released. - ET_GESTURE_MULTIFINGER_SWIPE, + // A SWIPE gesture can happen at the end of a touch sequence involving one or + // more fingers if the finger velocity was high enough when the first finger + // was released. + ET_GESTURE_SWIPE, ET_GESTURE_SHOW_PRESS, + // Sent by Win8+ metro when the user swipes from the bottom or top. + ET_GESTURE_WIN8_EDGE_SWIPE, + // Scroll support. // TODO[davemoore] we need to unify these events w/ touch and gestures. ET_SCROLL, ET_SCROLL_FLING_START, ET_SCROLL_FLING_CANCEL, + ET_GESTURE_TYPE_END = ET_SCROLL_FLING_CANCEL, // Sent by the system to indicate any modal type operations, such as drag and // drop or menus, should stop. @@ -78,19 +85,34 @@ enum EventFlags { EF_LEFT_MOUSE_BUTTON = 1 << 4, EF_MIDDLE_MOUSE_BUTTON = 1 << 5, EF_RIGHT_MOUSE_BUTTON = 1 << 6, - EF_COMMAND_DOWN = 1 << 7, // Only useful on OSX + EF_COMMAND_DOWN = 1 << 7, // GUI Key (e.g. Command on OS X keyboards, + // Search on Chromebook keyboards, + // Windows on MS-oriented keyboards) EF_EXTENDED = 1 << 8, // Windows extended key (see WM_KEYDOWN doc) EF_IS_SYNTHESIZED = 1 << 9, EF_ALTGR_DOWN = 1 << 10, + EF_MOD3_DOWN = 1 << 11, +}; + +// Flags specific to key events +enum KeyEventFlags { + EF_NUMPAD_KEY = 1 << 16, // Key originates from number pad (Xkb only) + EF_IME_FABRICATED_KEY = 1 << 17, // Key event fabricated by the underlying + // IME without a user action. + // (Linux X11 only) + EF_IS_REPEAT = 1 << 18, + EF_FUNCTION_KEY = 1 << 19, // Key originates from function key row }; // Flags specific to mouse events enum MouseEventFlags { - EF_IS_DOUBLE_CLICK = 1 << 16, - EF_IS_TRIPLE_CLICK = 1 << 17, - EF_IS_NON_CLIENT = 1 << 18, - EF_FROM_TOUCH = 1 << 19, // Indicates this mouse event is generated - // from an unconsumed touch/gesture event. + EF_IS_DOUBLE_CLICK = 1 << 16, + EF_IS_TRIPLE_CLICK = 1 << 17, + EF_IS_NON_CLIENT = 1 << 18, + EF_FROM_TOUCH = 1 << 19, // Indicates this mouse event is generated + // from an unconsumed touch/gesture event. + EF_TOUCH_ACCESSIBILITY = 1 << 20, // Indicates this event was generated from + // touch accessibility mode. }; // Result of dispatching an event. diff --git a/chromium/ui/events/event_dispatcher.cc b/chromium/ui/events/event_dispatcher.cc index a1fbafa79c0..ca24cde3ff0 100644 --- a/chromium/ui/events/event_dispatcher.cc +++ b/chromium/ui/events/event_dispatcher.cc @@ -51,11 +51,18 @@ EventDispatchDetails EventDispatcherDelegate::DispatchEvent(EventTarget* target, dispatch_helper.set_result(ER_UNHANDLED); EventDispatchDetails details = PreDispatchEvent(target, event); - if (!event->handled() && !details.dispatcher_destroyed) + if (!event->handled() && + !details.dispatcher_destroyed && + !details.target_destroyed) { details = DispatchEventToTarget(target, event); - if (!details.dispatcher_destroyed) - details = PostDispatchEvent(target, *event); + } + bool target_destroyed_during_dispatch = details.target_destroyed; + if (!details.dispatcher_destroyed) { + details = PostDispatchEvent(target_destroyed_during_dispatch ? + NULL : target, *event); + } + details.target_destroyed |= target_destroyed_during_dispatch; return details; } @@ -81,7 +88,11 @@ EventDispatchDetails EventDispatcherDelegate::DispatchEventToTarget( else if (old_dispatcher) old_dispatcher->OnDispatcherDelegateDestroyed(); - return dispatcher.details(); + EventDispatchDetails details; + details.dispatcher_destroyed = dispatcher.delegate_destroyed(); + details.target_destroyed = + (!details.dispatcher_destroyed && !CanDispatchToTarget(target)); + return details; } //////////////////////////////////////////////////////////////////////////////// @@ -128,14 +139,9 @@ void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) { return; } - if (!delegate_) + if (!delegate_ || !delegate_->CanDispatchToTarget(target)) return; - if (!delegate_->CanDispatchToTarget(target)) { - details_.target_destroyed = true; - return; - } - handler_list_.clear(); target->GetPostTargetHandlers(&handler_list_); dispatch_helper.set_phase(EP_POSTTARGET); @@ -143,7 +149,6 @@ void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) { } void EventDispatcher::OnDispatcherDelegateDestroyed() { - details_.dispatcher_destroyed = true; delegate_ = NULL; } @@ -175,7 +180,6 @@ void EventDispatcher::DispatchEventToEventHandlers(EventHandlerList* list, void EventDispatcher::DispatchEvent(EventHandler* handler, Event* event) { // If the target has been invalidated or deleted, don't dispatch the event. if (!delegate_->CanDispatchToTarget(event->target())) { - details_.target_destroyed = true; if (event->cancelable()) event->StopPropagation(); return; diff --git a/chromium/ui/events/event_dispatcher.h b/chromium/ui/events/event_dispatcher.h index 9f3a450e76e..a8c985e6813 100644 --- a/chromium/ui/events/event_dispatcher.h +++ b/chromium/ui/events/event_dispatcher.h @@ -54,6 +54,7 @@ class EVENTS_EXPORT EventDispatcherDelegate { Event* event) WARN_UNUSED_RESULT; // This is called right after the event dispatch is completed. + // |target| is NULL if the target was deleted during dispatch. virtual EventDispatchDetails PostDispatchEvent( EventTarget* target, const Event& event) WARN_UNUSED_RESULT; @@ -80,7 +81,6 @@ class EVENTS_EXPORT EventDispatcher { Event* current_event() { return current_event_; } bool delegate_destroyed() const { return !delegate_; } - const EventDispatchDetails& details() const { return details_; } void OnHandlerDestroyed(EventHandler* handler); void OnDispatcherDelegateDestroyed(); @@ -99,8 +99,6 @@ class EVENTS_EXPORT EventDispatcher { EventHandlerList handler_list_; - EventDispatchDetails details_; - DISALLOW_COPY_AND_ASSIGN(EventDispatcher); }; diff --git a/chromium/ui/events/event_dispatcher_unittest.cc b/chromium/ui/events/event_dispatcher_unittest.cc index 30bbd0dd997..248784a8870 100644 --- a/chromium/ui/events/event_dispatcher_unittest.cc +++ b/chromium/ui/events/event_dispatcher_unittest.cc @@ -209,10 +209,8 @@ class TestEventDispatcher : public EventDispatcherDelegate { virtual ~TestEventDispatcher() {} - void ProcessEvent(EventTarget* target, Event* event) { - EventDispatchDetails details = DispatchEvent(target, event); - if (details.dispatcher_destroyed) - return; + EventDispatchDetails ProcessEvent(EventTarget* target, Event* event) { + return DispatchEvent(target, event); } private: @@ -258,7 +256,7 @@ TEST(EventDispatcherTest, EventDispatchOrder) { h8.set_expect_post_target(true); MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), - gfx::Point(3, 4), 0); + gfx::Point(3, 4), 0, 0); Event::DispatcherApi event_mod(&mouse); dispatcher.ProcessEvent(&child, &mouse); EXPECT_FALSE(mouse.stopped_propagation()); @@ -332,7 +330,7 @@ TEST(EventDispatcherTest, EventDispatchPhase) { handler.set_expect_post_target(true); MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), - gfx::Point(3, 4), 0); + gfx::Point(3, 4), 0, 0); Event::DispatcherApi event_mod(&mouse); dispatcher.ProcessEvent(&target, &mouse); EXPECT_EQ(ER_UNHANDLED, mouse.result()); @@ -364,9 +362,9 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) { h2.set_expect_pre_target(false); MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), - gfx::Point(3, 4), 0); - Event::DispatcherApi event_mod(&mouse); - dispatcher->ProcessEvent(&target, &mouse); + gfx::Point(3, 4), 0, 0); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_EQ(ER_CONSUMED, mouse.result()); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); @@ -391,8 +389,8 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) { h2.set_expect_pre_target(false); NonCancelableEvent event; - Event::DispatcherApi event_mod(&event); - dispatcher->ProcessEvent(&target, &event); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); EXPECT_EQ(5, target.handler_list()[1]); @@ -416,9 +414,9 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) { h2.set_expect_post_target(false); MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), - gfx::Point(3, 4), 0); - Event::DispatcherApi event_mod(&mouse); - dispatcher->ProcessEvent(&target, &mouse); + gfx::Point(3, 4), 0, 0); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_EQ(ER_CONSUMED, mouse.result()); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); @@ -443,8 +441,8 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) { h2.set_expect_post_target(false); NonCancelableEvent event; - Event::DispatcherApi event_mod(&event); - dispatcher->ProcessEvent(&target, &event); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); EXPECT_EQ(5, target.handler_list()[1]); @@ -469,8 +467,11 @@ TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) { // |h3| should not receive events as the target will be invalidated. h3.set_expect_pre_target(false); - MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0); - dispatcher.ProcessEvent(&target, &mouse); + MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0, + 0); + EventDispatchDetails details = dispatcher.ProcessEvent(&target, &mouse); + EXPECT_FALSE(details.dispatcher_destroyed); + EXPECT_TRUE(details.target_destroyed); EXPECT_FALSE(target.valid()); EXPECT_TRUE(mouse.stopped_propagation()); EXPECT_EQ(2U, target.handler_list().size()); @@ -480,9 +481,10 @@ TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) { // Test for non-cancelable event. target.Reset(); NonCancelableEvent event; - dispatcher.ProcessEvent(&target, &event); + details = dispatcher.ProcessEvent(&target, &event); + EXPECT_FALSE(details.dispatcher_destroyed); + EXPECT_TRUE(details.target_destroyed); EXPECT_FALSE(target.valid()); - EXPECT_TRUE(mouse.stopped_propagation()); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); EXPECT_EQ(2, target.handler_list()[1]); @@ -508,8 +510,11 @@ TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) { // destroyed it. h3->set_expect_pre_target(false); - MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0); - dispatcher.ProcessEvent(&target, &mouse); + MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0, + 0); + EventDispatchDetails details = dispatcher.ProcessEvent(&target, &mouse); + EXPECT_FALSE(details.dispatcher_destroyed); + EXPECT_FALSE(details.target_destroyed); EXPECT_FALSE(mouse.stopped_propagation()); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); @@ -533,7 +538,9 @@ TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) { h3->set_expect_pre_target(false); NonCancelableEvent event; - dispatcher.ProcessEvent(&target, &event); + EventDispatchDetails details = dispatcher.ProcessEvent(&target, &event); + EXPECT_FALSE(details.dispatcher_destroyed); + EXPECT_FALSE(details.target_destroyed); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); EXPECT_EQ(2, target.handler_list()[1]); @@ -561,8 +568,10 @@ TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) { // it. h3->set_expect_pre_target(false); - MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0); - dispatcher->ProcessEvent(&target, &mouse); + MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0, + 0); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_TRUE(mouse.stopped_propagation()); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); @@ -589,7 +598,8 @@ TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) { h3->set_expect_pre_target(false); NonCancelableEvent event; - dispatcher->ProcessEvent(&target, &event); + EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event); + EXPECT_TRUE(details.dispatcher_destroyed); EXPECT_EQ(2U, target.handler_list().size()); EXPECT_EQ(1, target.handler_list()[0]); EXPECT_EQ(2, target.handler_list()[1]); diff --git a/chromium/ui/events/event_processor.cc b/chromium/ui/events/event_processor.cc index 1a50d2b7411..3077cc85b16 100644 --- a/chromium/ui/events/event_processor.cc +++ b/chromium/ui/events/event_processor.cc @@ -14,12 +14,22 @@ EventDispatchDetails EventProcessor::OnEventFromSource(Event* event) { CHECK(root); EventTargeter* targeter = root->GetEventTargeter(); CHECK(targeter); + PrepareEventForDispatch(event); EventTarget* target = targeter->FindTargetForEvent(root, event); - if (!target) - return EventDispatchDetails(); - return DispatchEvent(target, event); + while (target) { + EventDispatchDetails details = DispatchEvent(target, event); + if (details.dispatcher_destroyed || + details.target_destroyed || + event->handled()) { + return details; + } + + target = targeter->FindNextBestTarget(target, event); + } + + return EventDispatchDetails(); } void EventProcessor::PrepareEventForDispatch(Event* event) { diff --git a/chromium/ui/events/event_processor.h b/chromium/ui/events/event_processor.h index 34c973054d5..4ce1ef0522e 100644 --- a/chromium/ui/events/event_processor.h +++ b/chromium/ui/events/event_processor.h @@ -20,7 +20,9 @@ class EVENTS_EXPORT EventProcessor : public EventDispatcherDelegate { virtual EventTarget* GetRootTarget() = 0; // Dispatches an event received from the EventSource to the tree of - // EventTargets (whose root is returned by GetRootTarget()). + // EventTargets (whose root is returned by GetRootTarget()). The co-ordinate + // space of the source must be the same as the root target, except that the + // target may have a high-dpi scale applied. virtual EventDispatchDetails OnEventFromSource(Event* event) WARN_UNUSED_RESULT; diff --git a/chromium/ui/events/event_processor_unittest.cc b/chromium/ui/events/event_processor_unittest.cc index 841cdb75038..048a4cdeae9 100644 --- a/chromium/ui/events/event_processor_unittest.cc +++ b/chromium/ui/events/event_processor_unittest.cc @@ -2,12 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <vector> + #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/event.h" #include "ui/events/event_targeter.h" +#include "ui/events/test/events_test_utils.h" +#include "ui/events/test/test_event_handler.h" #include "ui/events/test/test_event_processor.h" #include "ui/events/test/test_event_target.h" +typedef std::vector<std::string> HandlerSequenceRecorder; + namespace ui { namespace test { @@ -41,7 +47,7 @@ TEST_F(EventProcessorTest, Basic) { root()->AddChild(child.Pass()); MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), - EF_NONE); + EF_NONE, EF_NONE); DispatchEvent(&mouse); EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED)); EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); @@ -130,7 +136,8 @@ TEST_F(EventProcessorTest, Bounds) { // Dispatch a mouse event that falls on the parent, but not on the child. When // the default event-targeter used, the event will still reach |grandchild|, // because the default targeter does not look at the bounds. - MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1), EF_NONE); + MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1), EF_NONE, + EF_NONE); DispatchEvent(&mouse); EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED)); @@ -141,9 +148,11 @@ TEST_F(EventProcessorTest, Bounds) { // Now install a targeter on the parent that looks at the bounds and makes // sure the event reaches the target only if the location of the event within // the bounds of the target. + MouseEvent mouse2(ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1), EF_NONE, + EF_NONE); parent_r->SetEventTargeter(scoped_ptr<EventTargeter>( new BoundsEventTargeter<BoundsTestTarget>())); - DispatchEvent(&mouse); + DispatchEvent(&mouse2); EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); EXPECT_TRUE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED)); EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED)); @@ -151,7 +160,7 @@ TEST_F(EventProcessorTest, Bounds) { parent_r->ResetReceivedEvents(); MouseEvent second(ET_MOUSE_MOVED, gfx::Point(12, 12), gfx::Point(12, 12), - EF_NONE); + EF_NONE, EF_NONE); DispatchEvent(&second); EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED)); @@ -159,5 +168,207 @@ TEST_F(EventProcessorTest, Bounds) { EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED)); } +class IgnoreEventTargeter : public EventTargeter { + public: + IgnoreEventTargeter() {} + virtual ~IgnoreEventTargeter() {} + + private: + // EventTargeter: + virtual bool SubtreeShouldBeExploredForEvent( + EventTarget* target, const LocatedEvent& event) OVERRIDE { + return false; + } +}; + +// Verifies that the EventTargeter installed on an EventTarget can dictate +// whether the target itself can process an event. +TEST_F(EventProcessorTest, TargeterChecksOwningEventTarget) { + scoped_ptr<TestEventTarget> child(new TestEventTarget()); + root()->AddChild(child.Pass()); + + MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), + EF_NONE, EF_NONE); + DispatchEvent(&mouse); + EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED)); + EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); + root()->child_at(0)->ResetReceivedEvents(); + + // Install an event handler on |child| which always prevents the target from + // receiving event. + root()->child_at(0)->SetEventTargeter( + scoped_ptr<EventTargeter>(new IgnoreEventTargeter())); + MouseEvent mouse2(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), + EF_NONE, EF_NONE); + DispatchEvent(&mouse2); + EXPECT_FALSE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED)); + EXPECT_TRUE(root()->DidReceiveEvent(ET_MOUSE_MOVED)); +} + +// An EventTargeter which is used to allow a bubbling behaviour in event +// dispatch: if an event is not handled after being dispatched to its +// initial target, the event is dispatched to the next-best target as +// specified by FindNextBestTarget(). +class BubblingEventTargeter : public EventTargeter { + public: + explicit BubblingEventTargeter(TestEventTarget* initial_target) + : initial_target_(initial_target) {} + virtual ~BubblingEventTargeter() {} + + private: + // EventTargeter: + virtual EventTarget* FindTargetForEvent(EventTarget* root, + Event* event) OVERRIDE { + return initial_target_; + } + + virtual EventTarget* FindNextBestTarget(EventTarget* previous_target, + Event* event) OVERRIDE { + return previous_target->GetParentTarget(); + } + + TestEventTarget* initial_target_; + + DISALLOW_COPY_AND_ASSIGN(BubblingEventTargeter); +}; + +// Tests that unhandled events are correctly dispatched to the next-best +// target as decided by the BubblingEventTargeter. +TEST_F(EventProcessorTest, DispatchToNextBestTarget) { + scoped_ptr<TestEventTarget> child(new TestEventTarget()); + scoped_ptr<TestEventTarget> grandchild(new TestEventTarget()); + + root()->SetEventTargeter( + scoped_ptr<EventTargeter>(new BubblingEventTargeter(grandchild.get()))); + child->AddChild(grandchild.Pass()); + root()->AddChild(child.Pass()); + + ASSERT_EQ(1u, root()->child_count()); + ASSERT_EQ(1u, root()->child_at(0)->child_count()); + ASSERT_EQ(0u, root()->child_at(0)->child_at(0)->child_count()); + + TestEventTarget* child_r = root()->child_at(0); + TestEventTarget* grandchild_r = child_r->child_at(0); + + // When the root has a BubblingEventTargeter installed, events targeted + // at the grandchild target should be dispatched to all three targets. + KeyEvent key_event(ET_KEY_PRESSED, VKEY_ESCAPE, 0, false); + DispatchEvent(&key_event); + EXPECT_TRUE(root()->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_TRUE(child_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED)); + root()->ResetReceivedEvents(); + child_r->ResetReceivedEvents(); + grandchild_r->ResetReceivedEvents(); + + // Add a pre-target handler on the child of the root that will mark the event + // as handled. No targets in the hierarchy should receive the event. + TestEventHandler handler; + child_r->AddPreTargetHandler(&handler); + key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, 0, false); + DispatchEvent(&key_event); + EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_FALSE(child_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_FALSE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_EQ(1, handler.num_key_events()); + handler.Reset(); + + // Add a post-target handler on the child of the root that will mark the event + // as handled. Only the grandchild (the initial target) should receive the + // event. + child_r->RemovePreTargetHandler(&handler); + child_r->AddPostTargetHandler(&handler); + key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, 0, false); + DispatchEvent(&key_event); + EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_FALSE(child_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_EQ(1, handler.num_key_events()); + handler.Reset(); + grandchild_r->ResetReceivedEvents(); + child_r->RemovePostTargetHandler(&handler); + + // Mark the event as handled when it reaches the EP_TARGET phase of + // dispatch at the child of the root. The child and grandchild + // targets should both receive the event, but the root should not. + child_r->set_mark_events_as_handled(true); + key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, 0, false); + DispatchEvent(&key_event); + EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_TRUE(child_r->DidReceiveEvent(ET_KEY_PRESSED)); + EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED)); + root()->ResetReceivedEvents(); + child_r->ResetReceivedEvents(); + grandchild_r->ResetReceivedEvents(); + child_r->set_mark_events_as_handled(false); +} + +// Tests that unhandled events are seen by the correct sequence of +// targets, pre-target handlers, and post-target handlers when +// a BubblingEventTargeter is installed on the root target. +TEST_F(EventProcessorTest, HandlerSequence) { + scoped_ptr<TestEventTarget> child(new TestEventTarget()); + scoped_ptr<TestEventTarget> grandchild(new TestEventTarget()); + + root()->SetEventTargeter( + scoped_ptr<EventTargeter>(new BubblingEventTargeter(grandchild.get()))); + child->AddChild(grandchild.Pass()); + root()->AddChild(child.Pass()); + + ASSERT_EQ(1u, root()->child_count()); + ASSERT_EQ(1u, root()->child_at(0)->child_count()); + ASSERT_EQ(0u, root()->child_at(0)->child_at(0)->child_count()); + + TestEventTarget* child_r = root()->child_at(0); + TestEventTarget* grandchild_r = child_r->child_at(0); + + HandlerSequenceRecorder recorder; + root()->set_target_name("R"); + root()->set_recorder(&recorder); + child_r->set_target_name("C"); + child_r->set_recorder(&recorder); + grandchild_r->set_target_name("G"); + grandchild_r->set_recorder(&recorder); + + TestEventHandler pre_root; + pre_root.set_handler_name("PreR"); + pre_root.set_recorder(&recorder); + root()->AddPreTargetHandler(&pre_root); + + TestEventHandler pre_child; + pre_child.set_handler_name("PreC"); + pre_child.set_recorder(&recorder); + child_r->AddPreTargetHandler(&pre_child); + + TestEventHandler pre_grandchild; + pre_grandchild.set_handler_name("PreG"); + pre_grandchild.set_recorder(&recorder); + grandchild_r->AddPreTargetHandler(&pre_grandchild); + + TestEventHandler post_root; + post_root.set_handler_name("PostR"); + post_root.set_recorder(&recorder); + root()->AddPostTargetHandler(&post_root); + + TestEventHandler post_child; + post_child.set_handler_name("PostC"); + post_child.set_recorder(&recorder); + child_r->AddPostTargetHandler(&post_child); + + TestEventHandler post_grandchild; + post_grandchild.set_handler_name("PostG"); + post_grandchild.set_recorder(&recorder); + grandchild_r->AddPostTargetHandler(&post_grandchild); + + MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), + EF_NONE, EF_NONE); + DispatchEvent(&mouse); + + std::string expected[] = { "PreR", "PreC", "PreG", "G", "PostG", "PostC", + "PostR", "PreR", "PreC", "C", "PostC", "PostR", "PreR", "R", "PostR" }; + EXPECT_EQ(std::vector<std::string>( + expected, expected + arraysize(expected)), recorder); +} + } // namespace test } // namespace ui diff --git a/chromium/ui/events/event_rewriter.h b/chromium/ui/events/event_rewriter.h new file mode 100644 index 00000000000..f948725709a --- /dev/null +++ b/chromium/ui/events/event_rewriter.h @@ -0,0 +1,68 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_EVENT_REWRITER_H_ +#define UI_EVENTS_EVENT_REWRITER_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/events_export.h" + +namespace ui { + +class Event; + +// Return status of EventRewriter operations; see that class below. +enum EventRewriteStatus { + // Nothing was done; no rewritten event returned. Pass the original + // event to later rewriters, or send it to the EventProcessor if this + // was the final rewriter. + EVENT_REWRITE_CONTINUE, + + // The event has been rewritten. Send the rewritten event to the + // EventProcessor instead of the original event (without sending + // either to any later rewriters). + EVENT_REWRITE_REWRITTEN, + + // The event should be discarded, neither passing it to any later + // rewriters nor sending it to the EventProcessor. + EVENT_REWRITE_DISCARD, + + // The event has been rewritten. As for EVENT_REWRITE_REWRITTEN, + // send the rewritten event to the EventProcessor instead of the + // original event (without sending either to any later rewriters). + // In addition the rewriter has one or more additional new events + // to be retrieved using |NextDispatchEvent()| and sent to the + // EventProcessor. + EVENT_REWRITE_DISPATCH_ANOTHER, +}; + +// EventRewriter provides a mechanism for Events to be rewritten +// before being dispatched from EventSource to EventProcessor. +class EVENTS_EXPORT EventRewriter { + public: + virtual ~EventRewriter() {} + + // Potentially rewrites (replaces) an event, or requests it be discarded. + // or discards an event. If the rewriter wants to rewrite an event, and + // dispatch another event once the rewritten event is dispatched, it should + // return EVENT_REWRITE_DISPATCH_ANOTHER, and return the next event to + // dispatch from |NextDispatchEvent()|. + virtual EventRewriteStatus RewriteEvent( + const Event& event, + scoped_ptr<Event>* rewritten_event) = 0; + + // Supplies an additional event to be dispatched. It is only valid to + // call this after the immediately previous call to |RewriteEvent()| + // or |NextDispatchEvent()| has returned EVENT_REWRITE_DISPATCH_ANOTHER. + // Should only return either EVENT_REWRITE_REWRITTEN or + // EVENT_REWRITE_DISPATCH_ANOTHER; otherwise the previous call should not + // have returned EVENT_REWRITE_DISPATCH_ANOTHER. + virtual EventRewriteStatus NextDispatchEvent( + const Event& last_event, + scoped_ptr<Event>* new_event) = 0; +}; + +} // namespace ui + +#endif // UI_EVENTS_EVENT_REWRITER_H_ diff --git a/chromium/ui/events/event_rewriter_unittest.cc b/chromium/ui/events/event_rewriter_unittest.cc new file mode 100644 index 00000000000..11a05c9c157 --- /dev/null +++ b/chromium/ui/events/event_rewriter_unittest.cc @@ -0,0 +1,231 @@ +// Copyright 2014 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/event_rewriter.h" + +#include <list> +#include <map> +#include <set> +#include <utility> + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/test/test_event_processor.h" + +namespace ui { + +namespace { + +// To test the handling of |EventRewriter|s through |EventSource|, +// we rewrite and test event types. +class TestEvent : public Event { + public: + explicit TestEvent(EventType type) + : Event(type, base::TimeDelta(), 0), unique_id_(next_unique_id_++) {} + virtual ~TestEvent() {} + int unique_id() const { return unique_id_; } + + private: + static int next_unique_id_; + int unique_id_; +}; + +int TestEvent::next_unique_id_ = 0; + +// TestEventRewriteProcessor is set up with a sequence of event types, +// and fails if the events received via OnEventFromSource() do not match +// this sequence. These expected event types are consumed on receipt. +class TestEventRewriteProcessor : public test::TestEventProcessor { + public: + TestEventRewriteProcessor() {} + virtual ~TestEventRewriteProcessor() { CheckAllReceived(); } + + void AddExpectedEvent(EventType type) { expected_events_.push_back(type); } + // Test that all expected events have been received. + void CheckAllReceived() { EXPECT_TRUE(expected_events_.empty()); } + + // EventProcessor: + virtual EventDispatchDetails OnEventFromSource(Event* event) OVERRIDE { + EXPECT_FALSE(expected_events_.empty()); + EXPECT_EQ(expected_events_.front(), event->type()); + expected_events_.pop_front(); + return EventDispatchDetails(); + } + + private: + std::list<EventType> expected_events_; + DISALLOW_COPY_AND_ASSIGN(TestEventRewriteProcessor); +}; + +// Trivial EventSource that does nothing but send events. +class TestEventRewriteSource : public EventSource { + public: + explicit TestEventRewriteSource(EventProcessor* processor) + : processor_(processor) {} + virtual EventProcessor* GetEventProcessor() OVERRIDE { return processor_; } + void Send(EventType type) { + scoped_ptr<Event> event(new TestEvent(type)); + (void)SendEventToProcessor(event.get()); + } + + private: + EventProcessor* processor_; +}; + +// This EventRewriter always returns the same status, and if rewriting, the +// same event type; it is used to test simple rewriting, and rewriter addition, +// removal, and sequencing. Consequently EVENT_REWRITE_DISPATCH_ANOTHER is not +// supported here (calls to NextDispatchEvent() would continue indefinitely). +class TestConstantEventRewriter : public EventRewriter { + public: + TestConstantEventRewriter(EventRewriteStatus status, EventType type) + : status_(status), type_(type) { + CHECK_NE(EVENT_REWRITE_DISPATCH_ANOTHER, status); + } + + virtual EventRewriteStatus RewriteEvent(const Event& event, + scoped_ptr<Event>* rewritten_event) + OVERRIDE { + if (status_ == EVENT_REWRITE_REWRITTEN) + rewritten_event->reset(new TestEvent(type_)); + return status_; + } + virtual EventRewriteStatus NextDispatchEvent(const Event& last_event, + scoped_ptr<Event>* new_event) + OVERRIDE { + NOTREACHED(); + return status_; + } + + private: + EventRewriteStatus status_; + EventType type_; +}; + +// This EventRewriter runs a simple state machine; it is used to test +// EVENT_REWRITE_DISPATCH_ANOTHER. +class TestStateMachineEventRewriter : public EventRewriter { + public: + TestStateMachineEventRewriter() : last_rewritten_event_(0), state_(0) {} + void AddRule(int from_state, EventType from_type, + int to_state, EventType to_type, EventRewriteStatus to_status) { + RewriteResult r = {to_state, to_type, to_status}; + rules_.insert(std::pair<RewriteCase, RewriteResult>( + RewriteCase(from_state, from_type), r)); + } + virtual EventRewriteStatus RewriteEvent(const Event& event, + scoped_ptr<Event>* rewritten_event) + OVERRIDE { + RewriteRules::iterator find = + rules_.find(RewriteCase(state_, event.type())); + if (find == rules_.end()) + return EVENT_REWRITE_CONTINUE; + if ((find->second.status == EVENT_REWRITE_REWRITTEN) || + (find->second.status == EVENT_REWRITE_DISPATCH_ANOTHER)) { + last_rewritten_event_ = new TestEvent(find->second.type); + rewritten_event->reset(last_rewritten_event_); + } else { + last_rewritten_event_ = 0; + } + state_ = find->second.state; + return find->second.status; + } + virtual EventRewriteStatus NextDispatchEvent(const Event& last_event, + scoped_ptr<Event>* new_event) + OVERRIDE { + EXPECT_TRUE(last_rewritten_event_); + const TestEvent* arg_last = static_cast<const TestEvent*>(&last_event); + EXPECT_EQ(last_rewritten_event_->unique_id(), arg_last->unique_id()); + const TestEvent* arg_new = static_cast<const TestEvent*>(new_event->get()); + EXPECT_FALSE(arg_new && arg_last->unique_id() == arg_new->unique_id()); + return RewriteEvent(last_event, new_event); + } + + private: + typedef std::pair<int, EventType> RewriteCase; + struct RewriteResult { + int state; + EventType type; + EventRewriteStatus status; + }; + typedef std::map<RewriteCase, RewriteResult> RewriteRules; + RewriteRules rules_; + TestEvent* last_rewritten_event_; + int state_; +}; + +} // namespace + +TEST(EventRewriterTest, EventRewriting) { + // TestEventRewriter r0 always rewrites events to ET_CANCEL_MODE; + // it is placed at the beginning of the chain and later removed, + // to verify that rewriter removal works. + TestConstantEventRewriter r0(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE); + + // TestEventRewriter r1 always returns EVENT_REWRITE_CONTINUE; + // it is placed at the beginning of the chain to verify that a + // later rewriter sees the events. + TestConstantEventRewriter r1(EVENT_REWRITE_CONTINUE, ET_UNKNOWN); + + // TestEventRewriter r2 has a state machine, primarily to test + // |EVENT_REWRITE_DISPATCH_ANOTHER|. + TestStateMachineEventRewriter r2; + + // TestEventRewriter r3 always rewrites events to ET_CANCEL_MODE; + // it is placed at the end of the chain to verify that previously + // rewritten events are not passed further down the chain. + TestConstantEventRewriter r3(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE); + + TestEventRewriteProcessor p; + TestEventRewriteSource s(&p); + s.AddEventRewriter(&r0); + s.AddEventRewriter(&r1); + s.AddEventRewriter(&r2); + + // These events should be rewritten by r0 to ET_CANCEL_MODE. + p.AddExpectedEvent(ET_CANCEL_MODE); + s.Send(ET_MOUSE_DRAGGED); + p.AddExpectedEvent(ET_CANCEL_MODE); + s.Send(ET_MOUSE_PRESSED); + p.CheckAllReceived(); + + // Remove r0, and verify that it's gone and that events make it through. + s.AddEventRewriter(&r3); + s.RemoveEventRewriter(&r0); + r2.AddRule(0, ET_SCROLL_FLING_START, + 0, ET_SCROLL_FLING_CANCEL, EVENT_REWRITE_REWRITTEN); + p.AddExpectedEvent(ET_SCROLL_FLING_CANCEL); + s.Send(ET_SCROLL_FLING_START); + p.CheckAllReceived(); + s.RemoveEventRewriter(&r3); + + // Verify EVENT_REWRITE_DISPATCH_ANOTHER using a state machine + // (that happens to be analogous to sticky keys). + r2.AddRule(0, ET_KEY_PRESSED, + 1, ET_KEY_PRESSED, EVENT_REWRITE_CONTINUE); + r2.AddRule(1, ET_MOUSE_PRESSED, + 0, ET_MOUSE_PRESSED, EVENT_REWRITE_CONTINUE); + r2.AddRule(1, ET_KEY_RELEASED, + 2, ET_KEY_RELEASED, EVENT_REWRITE_DISCARD); + r2.AddRule(2, ET_MOUSE_RELEASED, + 3, ET_MOUSE_RELEASED, EVENT_REWRITE_DISPATCH_ANOTHER); + r2.AddRule(3, ET_MOUSE_RELEASED, + 0, ET_KEY_RELEASED, EVENT_REWRITE_REWRITTEN); + p.AddExpectedEvent(ET_KEY_PRESSED); + s.Send(ET_KEY_PRESSED); + s.Send(ET_KEY_RELEASED); + p.AddExpectedEvent(ET_MOUSE_PRESSED); + s.Send(ET_MOUSE_PRESSED); + + // Removing rewriters r1 and r3 shouldn't affect r2. + s.RemoveEventRewriter(&r1); + s.RemoveEventRewriter(&r3); + + // Continue with the state-based rewriting. + p.AddExpectedEvent(ET_MOUSE_RELEASED); + p.AddExpectedEvent(ET_KEY_RELEASED); + s.Send(ET_MOUSE_RELEASED); + p.CheckAllReceived(); +} + +} // namespace ui diff --git a/chromium/ui/events/event_source.cc b/chromium/ui/events/event_source.cc index 4946ea0dfd1..0f3dfb80a0a 100644 --- a/chromium/ui/events/event_source.cc +++ b/chromium/ui/events/event_source.cc @@ -4,16 +4,73 @@ #include "ui/events/event_source.h" +#include <algorithm> + #include "ui/events/event_processor.h" +#include "ui/events/event_rewriter.h" namespace ui { -void EventSource::SendEventToProcessor(Event* event) { +EventSource::EventSource() {} + +EventSource::~EventSource() {} + +void EventSource::AddEventRewriter(EventRewriter* rewriter) { + DCHECK(rewriter); + DCHECK(rewriter_list_.end() == + std::find(rewriter_list_.begin(), rewriter_list_.end(), rewriter)); + rewriter_list_.push_back(rewriter); +} + +void EventSource::RemoveEventRewriter(EventRewriter* rewriter) { + EventRewriterList::iterator find = + std::find(rewriter_list_.begin(), rewriter_list_.end(), rewriter); + if (find != rewriter_list_.end()) + rewriter_list_.erase(find); +} + +EventDispatchDetails EventSource::SendEventToProcessor(Event* event) { + scoped_ptr<Event> rewritten_event; + EventRewriteStatus status = EVENT_REWRITE_CONTINUE; + EventRewriterList::const_iterator it = rewriter_list_.begin(), + end = rewriter_list_.end(); + for (; it != end; ++it) { + status = (*it)->RewriteEvent(*event, &rewritten_event); + if (status == EVENT_REWRITE_DISCARD) { + CHECK(!rewritten_event); + return EventDispatchDetails(); + } + if (status == EVENT_REWRITE_CONTINUE) { + CHECK(!rewritten_event); + continue; + } + break; + } + CHECK((it == end && !rewritten_event) || rewritten_event); + EventDispatchDetails details = + DeliverEventToProcessor(rewritten_event ? rewritten_event.get() : event); + if (details.dispatcher_destroyed) + return details; + + while (status == EVENT_REWRITE_DISPATCH_ANOTHER) { + scoped_ptr<Event> new_event; + status = (*it)->NextDispatchEvent(*rewritten_event, &new_event); + if (status == EVENT_REWRITE_DISCARD) + return EventDispatchDetails(); + CHECK_NE(EVENT_REWRITE_CONTINUE, status); + CHECK(new_event); + details = DeliverEventToProcessor(new_event.get()); + if (details.dispatcher_destroyed) + return details; + rewritten_event.reset(new_event.release()); + } + return EventDispatchDetails(); +} + +EventDispatchDetails EventSource::DeliverEventToProcessor(Event* event) { EventProcessor* processor = GetEventProcessor(); CHECK(processor); - EventDispatchDetails details = processor->OnEventFromSource(event); - if (details.dispatcher_destroyed) - return; + return processor->OnEventFromSource(event); } } // namespace ui diff --git a/chromium/ui/events/event_source.h b/chromium/ui/events/event_source.h index f8da9e6635e..ca8b00f514b 100644 --- a/chromium/ui/events/event_source.h +++ b/chromium/ui/events/event_source.h @@ -5,23 +5,43 @@ #ifndef UI_EVENTS_EVENT_SOURCE_H_ #define UI_EVENTS_EVENT_SOURCE_H_ +#include <vector> + +#include "ui/events/event_dispatcher.h" #include "ui/events/events_export.h" namespace ui { class Event; class EventProcessor; +class EventRewriter; // EventSource receives events from the native platform (e.g. X11, win32 etc.) // and sends the events to an EventProcessor. class EVENTS_EXPORT EventSource { public: - virtual ~EventSource() {} + EventSource(); + virtual ~EventSource(); virtual EventProcessor* GetEventProcessor() = 0; + // Adds a rewriter to modify events before they are sent to the + // EventProcessor. The rewriter must be explicitly removed from the + // EventSource before the rewriter is destroyed. The EventSource + // does not take ownership of the rewriter. + void AddEventRewriter(EventRewriter* rewriter); + void RemoveEventRewriter(EventRewriter* rewriter); + protected: - void SendEventToProcessor(Event* event); + EventDispatchDetails SendEventToProcessor(Event* event); + + private: + friend class EventSourceTestApi; + + typedef std::vector<EventRewriter*> EventRewriterList; + EventDispatchDetails DeliverEventToProcessor(Event* event); + EventRewriterList rewriter_list_; + DISALLOW_COPY_AND_ASSIGN(EventSource); }; } // namespace ui diff --git a/chromium/ui/events/event_switches.cc b/chromium/ui/events/event_switches.cc index 195e2bbdb4f..d5109700a9d 100644 --- a/chromium/ui/events/event_switches.cc +++ b/chromium/ui/events/event_switches.cc @@ -20,6 +20,18 @@ const char kTouchEventsEnabled[] = "enabled"; // disabled: touch events are disabled. const char kTouchEventsDisabled[] = "disabled"; +// Enable the unified gesture detector, instead of the aura gesture detector. +const char kUnifiedGestureDetector[] = "unified-gesture-detector"; + +// The values the kUnifiedGestureDetector switch may have, as in +// --unified-gesture-detector=disabled. +// auto: Same as disabled. +const char kUnifiedGestureDetectorAuto[] = "auto"; +// enabled: Use the unified gesture detector. +const char kUnifiedGestureDetectorEnabled[] = "enabled"; +// disabled: Use the aura gesture detector. +const char kUnifiedGestureDetectorDisabled[] = "disabled"; + #if defined(OS_LINUX) // Tells chrome to interpret events from these devices as touch events. Only // available with XInput 2 (i.e. X server 1.8 or above). The id's of the @@ -27,4 +39,9 @@ const char kTouchEventsDisabled[] = "disabled"; const char kTouchDevices[] = "touch-devices"; #endif +#if defined(USE_XI2_MT) || defined(USE_OZONE) +// The calibration factors given as "<left>,<right>,<top>,<bottom>". +const char kTouchCalibration[] = "touch-calibration"; +#endif + } // namespace switches diff --git a/chromium/ui/events/event_switches.h b/chromium/ui/events/event_switches.h index d4343046e50..6697d3a7516 100644 --- a/chromium/ui/events/event_switches.h +++ b/chromium/ui/events/event_switches.h @@ -15,11 +15,19 @@ EVENTS_BASE_EXPORT extern const char kTouchEvents[]; EVENTS_BASE_EXPORT extern const char kTouchEventsAuto[]; EVENTS_BASE_EXPORT extern const char kTouchEventsEnabled[]; EVENTS_BASE_EXPORT extern const char kTouchEventsDisabled[]; +EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetector[]; +EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorAuto[]; +EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorEnabled[]; +EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorDisabled[]; #if defined(OS_LINUX) EVENTS_BASE_EXPORT extern const char kTouchDevices[]; #endif +#if defined(USE_XI2_MT) || defined(USE_OZONE) +EVENTS_BASE_EXPORT extern const char kTouchCalibration[]; +#endif + } // namespace switches #endif // UI_EVENTS_EVENTS_SWITCHES_H_ diff --git a/chromium/ui/events/event_target.h b/chromium/ui/events/event_target.h index fd03667da95..3ffec550048 100644 --- a/chromium/ui/events/event_target.h +++ b/chromium/ui/events/event_target.h @@ -77,11 +77,13 @@ class EVENTS_EXPORT EventTarget : public EventHandler { // Returns true if the event pre target list is empty. bool IsPreTargetListEmpty() const; - protected: void set_target_handler(EventHandler* handler) { target_handler_ = handler; } + protected: + EventHandler* target_handler() { return target_handler_; } + // Overridden from EventHandler: virtual void OnEvent(Event* event) OVERRIDE; virtual void OnKeyEvent(KeyEvent* event) OVERRIDE; diff --git a/chromium/ui/events/event_targeter.cc b/chromium/ui/events/event_targeter.cc index 41d44426596..e76b5892197 100644 --- a/chromium/ui/events/event_targeter.cc +++ b/chromium/ui/events/event_targeter.cc @@ -32,16 +32,18 @@ EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root, EventTarget* target = root; EventTarget* child = NULL; while ((child = iter->GetNextTarget())) { - if (!SubtreeShouldBeExploredForEvent(child, *event)) + EventTargeter* targeter = child->GetEventTargeter(); + if (!targeter) + targeter = this; + if (!targeter->SubtreeShouldBeExploredForEvent(child, *event)) continue; target->ConvertEventToTarget(child, event); - EventTargeter* targeter = child->GetEventTargeter(); + target = child; EventTarget* child_target = targeter ? targeter->FindTargetForLocatedEvent(child, event) : FindTargetForLocatedEvent(child, event); if (child_target) return child_target; - target = child; } target->ConvertEventToTarget(root, event); } @@ -50,6 +52,22 @@ EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root, bool EventTargeter::SubtreeShouldBeExploredForEvent(EventTarget* target, const LocatedEvent& event) { + return SubtreeCanAcceptEvent(target, event) && + EventLocationInsideBounds(target, event); +} + +EventTarget* EventTargeter::FindNextBestTarget(EventTarget* previous_target, + Event* event) { + return NULL; +} + +bool EventTargeter::SubtreeCanAcceptEvent(EventTarget* target, + const LocatedEvent& event) const { + return true; +} + +bool EventTargeter::EventLocationInsideBounds(EventTarget* target, + const LocatedEvent& event) const { return true; } diff --git a/chromium/ui/events/event_targeter.h b/chromium/ui/events/event_targeter.h index a28662b5e83..dead49f1367 100644 --- a/chromium/ui/events/event_targeter.h +++ b/chromium/ui/events/event_targeter.h @@ -26,18 +26,43 @@ class EVENTS_EXPORT EventTargeter { // Same as FindTargetForEvent(), but used for positional events. The location // etc. of |event| are in |root|'s coordinate system. When finding the target - // for the event, the targeter can mutate the |event| (e.g. chnage the - // coordinate to be in the returned target's coordinate sustem) so that it can + // for the event, the targeter can mutate the |event| (e.g. change the + // coordinate to be in the returned target's coordinate system) so that it can // be dispatched to the target without any farther modification. virtual EventTarget* FindTargetForLocatedEvent(EventTarget* root, LocatedEvent* event); - protected: - // Returns true of |target| or one of its descendants can be a target of - // |event|. Note that the location etc. of |event| is in |target|'s parent's - // coordinate system. + // Returns true if |target| or one of its descendants can be a target of + // |event|. This requires that |target| and its descendants are not + // prohibited from accepting the event, and that the event is within an + // actionable region of the target's bounds. Note that the location etc. of + // |event| is in |target|'s parent's coordinate system. + // TODO(tdanderson|sadrul): This function should be made non-virtual and + // non-public. virtual bool SubtreeShouldBeExploredForEvent(EventTarget* target, const LocatedEvent& event); + + // Returns the next best target for |event| as compared to |previous_target|. + // Also mutates |event| so that it can be dispatched to the returned target + // (e.g., by changing |event|'s location to be in the returned target's + // coordinate space). + virtual EventTarget* FindNextBestTarget(EventTarget* previous_target, + Event* event); + + protected: + // Returns false if neither |target| nor any of its descendants are allowed + // to accept |event| for reasons unrelated to the event's location or the + // target's bounds. For example, overrides of this function may consider + // attributes such as the visibility or enabledness of |target|. Note that + // the location etc. of |event| is in |target|'s parent's coordinate system. + virtual bool SubtreeCanAcceptEvent(EventTarget* target, + const LocatedEvent& event) const; + + // Returns whether the location of the event is in an actionable region of the + // target. Note that the location etc. of |event| is in the |target|'s + // parent's coordinate system. + virtual bool EventLocationInsideBounds(EventTarget* target, + const LocatedEvent& event) const; }; } // namespace ui diff --git a/chromium/ui/events/event_unittest.cc b/chromium/ui/events/event_unittest.cc index 012fc608b66..186677969cb 100644 --- a/chromium/ui/events/event_unittest.cc +++ b/chromium/ui/events/event_unittest.cc @@ -58,17 +58,17 @@ TEST(EventTest, GetCharacter) { TEST(EventTest, ClickCount) { const gfx::Point origin(0, 0); - MouseEvent mouseev(ET_MOUSE_PRESSED, origin, origin, 0); + MouseEvent mouseev(ET_MOUSE_PRESSED, origin, origin, 0, 0); for (int i = 1; i <=3 ; ++i) { mouseev.SetClickCount(i); EXPECT_EQ(i, mouseev.GetClickCount()); } } -TEST(EventTest, Repeated) { +TEST(EventTest, RepeatedClick) { const gfx::Point origin(0, 0); - MouseEvent mouse_ev1(ET_MOUSE_PRESSED, origin, origin, 0); - MouseEvent mouse_ev2(ET_MOUSE_PRESSED, origin, origin, 0); + MouseEvent mouse_ev1(ET_MOUSE_PRESSED, origin, origin, 0, 0); + MouseEvent mouse_ev2(ET_MOUSE_PRESSED, origin, origin, 0, 0); LocatedEventTestApi test_ev1(&mouse_ev1); LocatedEventTestApi test_ev2(&mouse_ev2); @@ -98,6 +98,68 @@ TEST(EventTest, Repeated) { EXPECT_FALSE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2)); } +// Tests that an event only increases the click count and gets marked as a +// double click if a release event was seen for the previous click. This +// prevents the same PRESSED event from being processed twice: +// http://crbug.com/389162 +TEST(EventTest, DoubleClickRequiresRelease) { + const gfx::Point origin1(0, 0); + const gfx::Point origin2(100, 0); + scoped_ptr<MouseEvent> ev; + base::TimeDelta start = base::TimeDelta::FromMilliseconds(0); + + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin1, origin1, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin1, origin1, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin2, origin2, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin2, origin2, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin2, origin2, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin2, origin2, 0, 0)); + ev->set_time_stamp(start); + EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev)); + MouseEvent::ResetLastClickForTest(); +} + +// Tests that clicking right and then left clicking does not generate a double +// click. +TEST(EventTest, SingleClickRightLeft) { + const gfx::Point origin(0, 0); + scoped_ptr<MouseEvent> ev; + base::TimeDelta start = base::TimeDelta::FromMilliseconds(0); + + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin, + ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_RIGHT_MOUSE_BUTTON)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin, + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin, origin, + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); + ev->set_time_stamp(start); + EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev)); + ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin, + ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); + ev->set_time_stamp(start); + EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev)); + MouseEvent::ResetLastClickForTest(); +} + TEST(EventTest, KeyEvent) { static const struct { KeyboardCode key_code; @@ -331,4 +393,66 @@ TEST(EventTest, KeyEventCode) { #endif // OS_WIN } +#if defined(USE_X11) || defined(OS_WIN) +TEST(EventTest, AutoRepeat) { + KeycodeConverter* conv = KeycodeConverter::GetInstance(); + + const uint16 kNativeCodeA = conv->CodeToNativeKeycode("KeyA"); + const uint16 kNativeCodeB = conv->CodeToNativeKeycode("KeyB"); +#if defined(USE_X11) + ScopedXI2Event native_event_a_pressed; + native_event_a_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_A, kNativeCodeA); + ScopedXI2Event native_event_a_released; + native_event_a_released.InitKeyEvent(ET_KEY_RELEASED, VKEY_A, kNativeCodeA); + ScopedXI2Event native_event_b_pressed; + native_event_b_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_B, kNativeCodeB); + ScopedXI2Event native_event_a_pressed_nonstandard_state; + native_event_a_pressed_nonstandard_state.InitKeyEvent( + ET_KEY_PRESSED, VKEY_A, kNativeCodeA); + // IBUS-GTK uses the mask (1 << 25) to detect reposted event. + static_cast<XEvent*>(native_event_a_pressed_nonstandard_state)->xkey.state |= + 1 << 25; +#elif defined(OS_WIN) + const LPARAM lParam_a = GetLParamFromScanCode(kNativeCodeA); + const LPARAM lParam_b = GetLParamFromScanCode(kNativeCodeB); + MSG native_event_a_pressed = { NULL, WM_KEYDOWN, VKEY_A, lParam_a }; + MSG native_event_a_released = { NULL, WM_KEYUP, VKEY_A, lParam_a }; + MSG native_event_b_pressed = { NULL, WM_KEYUP, VKEY_B, lParam_b }; +#endif + KeyEvent key_a1(native_event_a_pressed, false); + EXPECT_FALSE(key_a1.IsRepeat()); + KeyEvent key_a1_released(native_event_a_released, false); + EXPECT_FALSE(key_a1_released.IsRepeat()); + + KeyEvent key_a2(native_event_a_pressed, false); + EXPECT_FALSE(key_a2.IsRepeat()); + KeyEvent key_a2_repeated(native_event_a_pressed, false); + EXPECT_TRUE(key_a2_repeated.IsRepeat()); + KeyEvent key_a2_released(native_event_a_released, false); + EXPECT_FALSE(key_a2_released.IsRepeat()); + + KeyEvent key_a3(native_event_a_pressed, false); + EXPECT_FALSE(key_a3.IsRepeat()); + KeyEvent key_b(native_event_b_pressed, false); + EXPECT_FALSE(key_b.IsRepeat()); + KeyEvent key_a3_again(native_event_a_pressed, false); + EXPECT_FALSE(key_a3_again.IsRepeat()); + KeyEvent key_a3_repeated(native_event_a_pressed, false); + EXPECT_TRUE(key_a3_repeated.IsRepeat()); + KeyEvent key_a3_repeated2(native_event_a_pressed, false); + EXPECT_TRUE(key_a3_repeated2.IsRepeat()); + KeyEvent key_a3_released(native_event_a_released, false); + EXPECT_FALSE(key_a3_released.IsRepeat()); + +#if defined(USE_X11) + KeyEvent key_a4_pressed(native_event_a_pressed, false); + EXPECT_FALSE(key_a4_pressed.IsRepeat()); + + KeyEvent key_a4_pressed_nonstandard_state( + native_event_a_pressed_nonstandard_state, false); + EXPECT_FALSE(key_a4_pressed_nonstandard_state.IsRepeat()); +#endif +} +#endif // USE_X11 || OS_WIN + } // namespace ui diff --git a/chromium/ui/events/event_utils.cc b/chromium/ui/events/event_utils.cc index a8c95da6f8f..e4b384a4744 100644 --- a/chromium/ui/events/event_utils.cc +++ b/chromium/ui/events/event_utils.cc @@ -16,8 +16,51 @@ namespace { int g_custom_event_types = ET_LAST; } // namespace -bool EventCanceledDefaultHandling(const Event& event) { - return event.phase() == EP_POSTTARGET && event.result() != ER_UNHANDLED; +scoped_ptr<Event> EventFromNative(const base::NativeEvent& native_event) { + scoped_ptr<Event> event; + EventType type = EventTypeFromNative(native_event); + switch(type) { + case ET_KEY_PRESSED: + case ET_KEY_RELEASED: + event.reset(new KeyEvent(native_event, false)); + break; + + case ET_TRANSLATED_KEY_PRESS: + case ET_TRANSLATED_KEY_RELEASE: + // These should not be generated by native events. + NOTREACHED(); + break; + + case ET_MOUSE_PRESSED: + case ET_MOUSE_DRAGGED: + case ET_MOUSE_RELEASED: + case ET_MOUSE_MOVED: + case ET_MOUSE_ENTERED: + case ET_MOUSE_EXITED: + event.reset(new MouseEvent(native_event)); + break; + + case ET_MOUSEWHEEL: + event.reset(new MouseWheelEvent(native_event)); + break; + + case ET_SCROLL_FLING_START: + case ET_SCROLL_FLING_CANCEL: + case ET_SCROLL: + event.reset(new ScrollEvent(native_event)); + break; + + case ET_TOUCH_RELEASED: + case ET_TOUCH_PRESSED: + case ET_TOUCH_MOVED: + case ET_TOUCH_CANCELLED: + event.reset(new TouchEvent(native_event)); + break; + + default: + break; + } + return event.Pass(); } int RegisterCustomEventType() { @@ -30,18 +73,22 @@ base::TimeDelta EventTimeForNow() { } bool ShouldDefaultToNaturalScroll() { + return GetInternalDisplayTouchSupport() == + gfx::Display::TOUCH_SUPPORT_AVAILABLE; +} + +gfx::Display::TouchSupport GetInternalDisplayTouchSupport() { gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE); + // No screen in some unit tests. if (!screen) - return false; + return gfx::Display::TOUCH_SUPPORT_UNKNOWN; const std::vector<gfx::Display>& displays = screen->GetAllDisplays(); for (std::vector<gfx::Display>::const_iterator it = displays.begin(); it != displays.end(); ++it) { - const gfx::Display& display = *it; - if (display.IsInternal() && - display.touch_support() == gfx::Display::TOUCH_SUPPORT_AVAILABLE) - return true; + if (it->IsInternal()) + return it->touch_support(); } - return false; + return gfx::Display::TOUCH_SUPPORT_UNAVAILABLE; } } // namespace ui diff --git a/chromium/ui/events/event_utils.h b/chromium/ui/events/event_utils.h index e67a74111d6..87b720d5884 100644 --- a/chromium/ui/events/event_utils.h +++ b/chromium/ui/events/event_utils.h @@ -6,8 +6,10 @@ #define UI_EVENTS_EVENT_UTILS_H_ #include "base/event_types.h" +#include "base/memory/scoped_ptr.h" #include "ui/events/event_constants.h" #include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/display.h" #include "ui/gfx/native_widget_types.h" #include "ui/events/events_export.h" @@ -31,6 +33,11 @@ class Event; // Updates the list of devices for cached properties. EVENTS_EXPORT void UpdateDeviceList(); +// Returns a ui::Event wrapping a native event. Ownership of the returned value +// is transferred to the caller. +EVENTS_EXPORT scoped_ptr<Event> EventFromNative( + const base::NativeEvent& native_event); + // Get the EventType from a native event. EVENTS_EXPORT EventType EventTypeFromNative( const base::NativeEvent& native_event); @@ -48,7 +55,8 @@ EVENTS_EXPORT base::TimeDelta EventTimeForNow(); // Get the location from a native event. The coordinate system of the resultant // |Point| has the origin at top-left of the "root window". The nature of // this "root window" and how it maps to platform-specific drawing surfaces is -// defined in ui/aura/root_window.* and ui/aura/root_window_host*. +// defined in ui/aura/root_window.* and ui/aura/window_tree_host*. +// TODO(tdresser): Return gfx::PointF here. See crbug.com/337827. EVENTS_EXPORT gfx::Point EventLocationFromNative( const base::NativeEvent& native_event); @@ -74,8 +82,9 @@ EVENTS_EXPORT KeyboardCode KeyboardCodeFromNative( EVENTS_EXPORT const char* CodeFromNative( const base::NativeEvent& native_event); -// Returns true if the message is a mouse event. -EVENTS_EXPORT bool IsMouseEvent(const base::NativeEvent& native_event); +// Returns the platform related key code. For X11, it is xksym value. +EVENTS_EXPORT uint32 PlatformKeycodeFromNative( + const base::NativeEvent& native_event); // Returns the flags of the button that changed during a press/release. EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative( @@ -85,6 +94,15 @@ EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative( EVENTS_EXPORT gfx::Vector2d GetMouseWheelOffset( const base::NativeEvent& native_event); +// Returns a copy of |native_event|. Depending on the platform, this copy may +// need to be deleted with ReleaseCopiedNativeEvent(). +base::NativeEvent CopyNativeEvent( + const base::NativeEvent& native_event); + +// Delete a |native_event| previously created by CopyNativeEvent(). +void ReleaseCopiedNativeEvent( + const base::NativeEvent& native_event); + // Gets the touch id from a native event. EVENTS_EXPORT int GetTouchId(const base::NativeEvent& native_event); @@ -124,26 +142,17 @@ EVENTS_EXPORT bool GetGestureTimes(const base::NativeEvent& native_event, double* start_time, double* end_time); -// Enable/disable natural scrolling for touchpads. -EVENTS_EXPORT void SetNaturalScroll(bool enabled); - -// In natural scrolling enabled for touchpads? -EVENTS_EXPORT bool IsNaturalScrollEnabled(); - // Returns whether natural scrolling should be used for touchpad. EVENTS_EXPORT bool ShouldDefaultToNaturalScroll(); +// Returns whether or not the internal display produces touch events. +EVENTS_EXPORT gfx::Display::TouchSupport GetInternalDisplayTouchSupport(); + // Was this event generated by a touchpad device? // The caller is responsible for ensuring that this is a mouse/touchpad event // before calling this function. EVENTS_EXPORT bool IsTouchpadEvent(const base::NativeEvent& event); -// Returns true if event is noop. -EVENTS_EXPORT bool IsNoopEvent(const base::NativeEvent& event); - -// Creates and returns no-op event. -EVENTS_EXPORT base::NativeEvent CreateNoopEvent(); - #if defined(OS_WIN) EVENTS_EXPORT int GetModifiersFromACCEL(const ACCEL& accel); EVENTS_EXPORT int GetModifiersFromKeyState(); @@ -156,11 +165,8 @@ EVENTS_EXPORT bool IsMouseEventFromTouch(UINT message); // representing an extended key contains 0xE000 bits. EVENTS_EXPORT uint16 GetScanCodeFromLParam(LPARAM lParam); EVENTS_EXPORT LPARAM GetLParamFromScanCode(uint16 scan_code); -#endif -// Returns true if default post-target handling was canceled for |event| after -// its dispatch to its target. -EVENTS_EXPORT bool EventCanceledDefaultHandling(const Event& event); +#endif // Registers a custom event type. EVENTS_EXPORT int RegisterCustomEventType(); diff --git a/chromium/ui/events/events.gyp b/chromium/ui/events/events.gyp index b7a43136822..890708dab2f 100644 --- a/chromium/ui/events/events.gyp +++ b/chromium/ui/events/events.gyp @@ -25,21 +25,27 @@ 'dependencies': [ '<(DEPTH)/base/base.gyp:base', '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '<(DEPTH)/skia/skia.gyp:skia', + '../gfx/gfx.gyp:gfx', + '../gfx/gfx.gyp:gfx_geometry', 'dom4_keycode_converter', ], 'defines': [ 'EVENTS_BASE_IMPLEMENTATION', ], 'sources': [ - 'events_base_export.h', + 'event_constants.h', 'event_switches.cc', 'event_switches.h', + 'events_base_export.h', + 'gesture_event_details.cc', + 'gesture_event_details.h', + 'gestures/gesture_configuration.cc', + 'gestures/gesture_configuration.h', 'keycodes/keyboard_code_conversion.cc', 'keycodes/keyboard_code_conversion.h', 'keycodes/keyboard_code_conversion_android.cc', 'keycodes/keyboard_code_conversion_android.h', - 'keycodes/keyboard_code_conversion_gtk.cc', - 'keycodes/keyboard_code_conversion_gtk.h', 'keycodes/keyboard_code_conversion_mac.h', 'keycodes/keyboard_code_conversion_mac.mm', 'keycodes/keyboard_code_conversion_win.cc', @@ -49,17 +55,18 @@ 'keycodes/keyboard_codes.h', 'latency_info.cc', 'latency_info.h', - 'x/device_list_cache_x.cc', - 'x/device_list_cache_x.h', 'x/device_data_manager.cc', 'x/device_data_manager.h', + 'x/device_list_cache_x.cc', + 'x/device_list_cache_x.h', 'x/touch_factory_x11.cc', 'x/touch_factory_x11.h', ], 'conditions': [ ['use_x11==1', { 'dependencies': [ - '<(DEPTH)/build/linux/system.gyp:x11', + '../../build/linux/system.gyp:x11', + '../gfx/x/gfx_x11.gyp:gfx_x11', ], }], ], @@ -72,21 +79,26 @@ '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '<(DEPTH)/skia/skia.gyp:skia', '../gfx/gfx.gyp:gfx', + '../gfx/gfx.gyp:gfx_geometry', 'events_base', + 'gesture_detection', ], 'defines': [ 'EVENTS_IMPLEMENTATION', ], 'sources': [ + 'cocoa/cocoa_event_utils.h', + 'cocoa/cocoa_event_utils.mm', + 'cocoa/events_mac.mm', 'event.cc', 'event.h', - 'event_constants.h', 'event_dispatcher.cc', 'event_dispatcher.h', 'event_handler.cc', 'event_handler.h', 'event_processor.cc', 'event_processor.h', + 'event_rewriter.h', 'event_source.cc', 'event_source.h', 'event_target.cc', @@ -98,54 +110,113 @@ 'event_utils.h', 'events_export.h', 'events_stub.cc', - 'gestures/gesture_configuration.cc', - 'gestures/gesture_configuration.h', 'gestures/gesture_point.cc', 'gestures/gesture_point.h', + 'gestures/gesture_provider_aura.cc', + 'gestures/gesture_provider_aura.h', 'gestures/gesture_recognizer.h', 'gestures/gesture_recognizer_impl.cc', 'gestures/gesture_recognizer_impl.h', + 'gestures/gesture_recognizer_impl_mac.cc', 'gestures/gesture_sequence.cc', 'gestures/gesture_sequence.h', - 'gestures/gesture_types.cc', 'gestures/gesture_types.h', - 'gestures/gesture_util.cc', - 'gestures/gesture_util.h', + 'gestures/motion_event_aura.cc', + 'gestures/motion_event_aura.h', + 'gestures/unified_gesture_detector_enabled.cc', + 'gestures/unified_gesture_detector_enabled.h', 'gestures/velocity_calculator.cc', 'gestures/velocity_calculator.h', - 'ozone/evdev/event_device_info.cc', - 'ozone/evdev/event_device_info.h', - 'ozone/evdev/event_factory.cc', - 'ozone/evdev/event_factory.h', - 'ozone/evdev/event_modifiers.cc', - 'ozone/evdev/event_modifiers.h', - 'ozone/evdev/key_event_converter.cc', - 'ozone/evdev/key_event_converter.h', - 'ozone/evdev/touch_event_converter.cc', - 'ozone/evdev/touch_event_converter.h', - 'ozone/event_converter_ozone.cc', - 'ozone/event_converter_ozone.h', - 'ozone/event_factory_ozone.cc', - 'ozone/event_factory_ozone.h', 'ozone/events_ozone.cc', 'win/events_win.cc', 'x/events_x.cc', + 'linux/text_edit_command_auralinux.cc', + 'linux/text_edit_command_auralinux.h', + 'linux/text_edit_key_bindings_delegate_auralinux.cc', + 'linux/text_edit_key_bindings_delegate_auralinux.h', ], 'conditions': [ + ['use_aura==0', { + 'sources!': [ + 'gestures/gesture_point.cc', + 'gestures/gesture_point.h', + 'gestures/gesture_provider_aura.cc', + 'gestures/gesture_provider_aura.h', + 'gestures/gesture_recognizer.h', + 'gestures/gesture_recognizer_impl.cc', + 'gestures/gesture_recognizer_impl.h', + 'gestures/gesture_sequence.cc', + 'gestures/gesture_sequence.h', + 'gestures/gesture_types.h', + 'gestures/motion_event_aura.cc', + 'gestures/motion_event_aura.h', + 'gestures/velocity_calculator.cc', + 'gestures/velocity_calculator.h', + ], + }], # We explicitly enumerate the platforms we _do_ provide native cracking # for here. - ['OS=="win" or use_x11==1 or use_ozone==1', { + ['OS=="win" or OS=="mac" or use_x11==1 or use_ozone==1', { 'sources!': [ 'events_stub.cc', ], }], - ['use_x11==1', { - 'dependencies': [ - '<(DEPTH)/build/linux/system.gyp:x11', + ['chromeos==1', { + 'sources!': [ + 'linux/text_edit_command_auralinux.cc', + 'linux/text_edit_command_auralinux.h', + 'linux/text_edit_key_bindings_delegate_auralinux.cc', + 'linux/text_edit_key_bindings_delegate_auralinux.h', ], }], - ['use_ozone_evdev==1', { - 'defines': ['USE_OZONE_EVDEV=1'], + ], + }, + { + 'target_name': 'gesture_detection', + 'type': '<(component)', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../gfx/gfx.gyp:gfx', + '../gfx/gfx.gyp:gfx_geometry', + 'events_base', + ], + 'defines': [ + 'GESTURE_DETECTION_IMPLEMENTATION', + ], + 'sources': [ + 'gesture_detection/bitset_32.h', + 'gesture_detection/filtered_gesture_provider.cc', + 'gesture_detection/filtered_gesture_provider.h', + 'gesture_detection/gesture_config_helper.h', + 'gesture_detection/gesture_config_helper_android.cc', + 'gesture_detection/gesture_config_helper_aura.cc', + 'gesture_detection/gesture_detection_export.h', + 'gesture_detection/gesture_detector.cc', + 'gesture_detection/gesture_detector.h', + 'gesture_detection/gesture_event_data.cc', + 'gesture_detection/gesture_event_data.h', + 'gesture_detection/gesture_event_data_packet.cc', + 'gesture_detection/gesture_event_data_packet.h', + 'gesture_detection/gesture_provider.cc', + 'gesture_detection/gesture_provider.h', + 'gesture_detection/motion_event.h', + 'gesture_detection/scale_gesture_detector.cc', + 'gesture_detection/scale_gesture_detector.h', + 'gesture_detection/snap_scroll_controller.cc', + 'gesture_detection/snap_scroll_controller.h', + 'gesture_detection/touch_disposition_gesture_filter.cc', + 'gesture_detection/touch_disposition_gesture_filter.h', + 'gesture_detection/velocity_tracker_state.cc', + 'gesture_detection/velocity_tracker_state.h', + 'gesture_detection/velocity_tracker.cc', + 'gesture_detection/velocity_tracker.h', + ], + 'conditions': [ + ['use_aura!=1 and OS!="android"', { + 'sources': [ + 'gesture_detection/gesture_config_helper.cc', + ], }], ], }, @@ -153,14 +224,22 @@ 'target_name': 'events_test_support', 'type': 'static_library', 'dependencies': [ + '<(DEPTH)/skia/skia.gyp:skia', 'events', 'events_base', + 'platform/events_platform.gyp:events_platform', ], 'sources': [ + 'test/cocoa_test_event_utils.h', + 'test/cocoa_test_event_utils.mm', 'test/events_test_utils.cc', 'test/events_test_utils.h', 'test/events_test_utils_x11.cc', 'test/events_test_utils_x11.h', + 'test/platform_event_waiter.cc', + 'test/platform_event_waiter.h', + 'test/test_event_handler.cc', + 'test/test_event_handler.h', 'test/test_event_processor.cc', 'test/test_event_processor.h', 'test/test_event_target.cc', @@ -169,9 +248,14 @@ 'conditions': [ ['use_x11==1', { 'dependencies': [ - '<(DEPTH)/build/linux/system.gyp:x11', + '../../build/linux/system.gyp:x11', + '../gfx/x/gfx_x11.gyp:gfx_x11', ], }], + ['OS=="ios"', { + # The cocoa files don't apply to iOS. + 'sources/': [['exclude', 'cocoa']], + }], ], }, { @@ -179,35 +263,90 @@ 'type': '<(gtest_target_type)', 'dependencies': [ '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/base/base.gyp:run_all_unittests', '<(DEPTH)/base/base.gyp:test_support_base', + '<(DEPTH)/skia/skia.gyp:skia', '<(DEPTH)/testing/gtest.gyp:gtest', - '../gfx/gfx.gyp:gfx', + '../gfx/gfx.gyp:gfx_geometry', + '../gfx/gfx.gyp:gfx_test_support', 'dom4_keycode_converter', - 'events_base', 'events', + 'events_base', 'events_test_support', + 'gesture_detection', + 'platform/events_platform.gyp:events_platform', ], 'sources': [ + 'cocoa/events_mac_unittest.mm', 'event_dispatcher_unittest.cc', 'event_processor_unittest.cc', + 'event_rewriter_unittest.cc', 'event_unittest.cc', + 'gestures/motion_event_aura_unittest.cc', 'gestures/velocity_calculator_unittest.cc', + 'gesture_detection/bitset_32_unittest.cc', + 'gesture_detection/gesture_provider_unittest.cc', + 'gesture_detection/mock_motion_event.h', + 'gesture_detection/mock_motion_event.cc', + 'gesture_detection/velocity_tracker_unittest.cc', + 'gesture_detection/touch_disposition_gesture_filter_unittest.cc', 'keycodes/dom4/keycode_converter_unittest.cc', 'latency_info_unittest.cc', - 'test/run_all_unittests.cc', - 'test/test_suite.cc', - 'test/test_suite.h', - 'ozone/evdev/key_event_converter_unittest.cc', - 'ozone/evdev/touch_event_converter_unittest.cc', + 'platform/platform_event_source_unittest.cc', 'x/events_x_unittest.cc', ], 'conditions': [ - ['OS=="linux" and linux_use_tcmalloc==1', { + ['use_ozone==1', { + 'sources': [ + 'ozone/evdev/key_event_converter_evdev_unittest.cc', + 'ozone/evdev/touch_event_converter_evdev_unittest.cc', + ], + 'dependencies': [ + 'ozone/events_ozone.gyp:events_ozone', + 'ozone/events_ozone.gyp:events_ozone_evdev', + ] + }], + ['use_aura==0', { + 'sources!': [ + 'gestures/motion_event_aura_unittest.cc', + 'gestures/velocity_calculator_unittest.cc', + ], + }], + ['OS=="linux" and use_allocator!="none"', { 'dependencies': [ '<(DEPTH)/base/allocator/allocator.gyp:allocator', ], }], + # Exclude tests that rely on event_utils.h for platforms that do not + # provide native cracking, i.e., platforms that use events_stub.cc. + ['OS!="win" and use_x11!=1 and use_ozone!=1', { + 'sources!': [ + 'event_unittest.cc', + ], + }], + ['OS == "android"', { + 'dependencies': [ + '../../testing/android/native_test.gyp:native_test_native_code', + ], + }], ], }, ], + 'conditions': [ + ['OS == "android"', { + 'targets': [ + { + 'target_name': 'events_unittests_apk', + 'type': 'none', + 'dependencies': [ + 'events_unittests', + ], + 'variables': { + 'test_suite_name': 'events_unittests', + }, + 'includes': [ '../../build/apk_test.gypi' ], + }, + ], + }], + ], } diff --git a/chromium/ui/events/events_stub.cc b/chromium/ui/events/events_stub.cc index 3d63e4ddf63..2ce911361c1 100644 --- a/chromium/ui/events/events_stub.cc +++ b/chromium/ui/events/events_stub.cc @@ -4,6 +4,7 @@ #include "base/logging.h" #include "base/time/time.h" +#include "build/build_config.h" #include "ui/events/event_utils.h" #include "ui/gfx/point.h" #include "ui/gfx/vector2d.h" @@ -48,11 +49,6 @@ int EventButtonFromNative(const base::NativeEvent& native_event) { return 0; } -bool IsMouseEvent(const base::NativeEvent& native_event) { - NOTIMPLEMENTED(); - return false; -} - int GetChangedMouseButtonFlagsFromNative( const base::NativeEvent& native_event) { NOTIMPLEMENTED(); @@ -64,6 +60,15 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { return gfx::Vector2d(); } +base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { + NOTIMPLEMENTED() << + "Don't know how to copy base::NativeEvent for this platform"; + return NULL; +} + +void ReleaseCopiedNativeEvent(const base::NativeEvent& event) { +} + void ClearTouchIdIfReleased(const base::NativeEvent& native_event) { NOTIMPLEMENTED(); } @@ -134,15 +139,6 @@ bool IsTouchpadEvent(const base::NativeEvent& native_event) { return false; } -bool IsNoopEvent(const base::NativeEvent& native_event) { - NOTIMPLEMENTED(); - return false; -} - -base::NativeEvent CreateNoopEvent() { - return base::NativeEvent(); -} - KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) { NOTIMPLEMENTED(); return static_cast<KeyboardCode>(0); @@ -153,4 +149,9 @@ const char* CodeFromNative(const base::NativeEvent& native_event) { return ""; } +uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) { + NOTIMPLEMENTED(); + return 0; +} + } // namespace ui diff --git a/chromium/ui/events/gesture_detection/OWNERS b/chromium/ui/events/gesture_detection/OWNERS new file mode 100644 index 00000000000..3e9a72974f2 --- /dev/null +++ b/chromium/ui/events/gesture_detection/OWNERS @@ -0,0 +1,2 @@ +jdduke@chromium.org +tdresser@chromium.org diff --git a/chromium/ui/events/gesture_detection/bitset_32.h b/chromium/ui/events/gesture_detection/bitset_32.h new file mode 100644 index 00000000000..9cb9d48aee6 --- /dev/null +++ b/chromium/ui/events/gesture_detection/bitset_32.h @@ -0,0 +1,128 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_ +#define UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_ + +#include "base/basictypes.h" + +namespace ui { + +// Port of BitSet32 from Android +// * platform/system/core/include/utils/BitSet.h +// * Change-Id: I9bbf41f9d2d4a2593b0e6d7d8be7e283f985bade +// * Please update the Change-Id as upstream Android changes are pulled. +struct BitSet32 { + uint32_t value; + + inline BitSet32() : value(0) {} + explicit inline BitSet32(uint32_t value) : value(value) {} + + // Gets the value associated with a particular bit index. + static inline uint32_t value_for_bit(uint32_t n) { return 0x80000000 >> n; } + + // Clears the bit set. + inline void clear() { value = 0; } + + // Returns the number of marked bits in the set. + inline uint32_t count() const { return popcnt(value); } + + // Returns true if the bit set does not contain any marked bits. + inline bool is_empty() const { return !value; } + + // Returns true if the bit set does not contain any unmarked bits. + inline bool is_full() const { return value == 0xffffffff; } + + // Returns true if the specified bit is marked. + inline bool has_bit(uint32_t n) const { + return (value & value_for_bit(n)) != 0; + } + + // Marks the specified bit. + inline void mark_bit(uint32_t n) { value |= value_for_bit(n); } + + // Clears the specified bit. + inline void clear_bit(uint32_t n) { value &= ~value_for_bit(n); } + + // Finds the first marked bit in the set. + // Result is undefined if all bits are unmarked. + inline uint32_t first_marked_bit() const { return clz(value); } + + // Finds the first unmarked bit in the set. + // Result is undefined if all bits are marked. + inline uint32_t first_unmarked_bit() const { return clz(~value); } + + // Finds the last marked bit in the set. + // Result is undefined if all bits are unmarked. + inline uint32_t last_marked_bit() const { return 31 - ctz(value); } + + // Finds the first marked bit in the set and clears it. Returns the bit + // index. + // Result is undefined if all bits are unmarked. + inline uint32_t clear_first_marked_bit() { + uint32_t n = first_marked_bit(); + clear_bit(n); + return n; + } + + // Finds the first unmarked bit in the set and marks it. Returns the bit + // index. + // Result is undefined if all bits are marked. + inline uint32_t mark_first_unmarked_bit() { + uint32_t n = first_unmarked_bit(); + mark_bit(n); + return n; + } + + // Finds the last marked bit in the set and clears it. Returns the bit index. + // Result is undefined if all bits are unmarked. + inline uint32_t clear_last_marked_bit() { + uint32_t n = last_marked_bit(); + clear_bit(n); + return n; + } + + // Gets the inde of the specified bit in the set, which is the number of + // marked bits that appear before the specified bit. + inline uint32_t get_index_of_bit(uint32_t n) const { + return popcnt(value & ~(0xffffffffUL >> n)); + } + + inline bool operator==(const BitSet32& other) const { + return value == other.value; + } + inline bool operator!=(const BitSet32& other) const { + return value != other.value; + } + + private: +#if defined(COMPILER_GCC) || defined(__clang__) + static inline uint32_t popcnt(uint32_t v) { return __builtin_popcount(v); } + static inline uint32_t clz(uint32_t v) { return __builtin_clz(v); } + static inline uint32_t ctz(uint32_t v) { return __builtin_ctz(v); } +#else + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + static inline uint32_t popcnt(uint32_t v) { + v = v - ((v >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + return (((v + (v >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; + } + // TODO(jdduke): Use intrinsics (BitScan{Forward,Reverse}) with MSVC. + static inline uint32_t clz(uint32_t v) { + v |= (v >> 1); + v |= (v >> 2); + v |= (v >> 4); + v |= (v >> 8); + v |= (v >> 16); + return 32 - popcnt(v); + } + static inline uint32_t ctz(uint32_t v) { + return popcnt((v & static_cast<uint32_t>(-static_cast<int>(v))) - 1); + } +#endif +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_ diff --git a/chromium/ui/events/gesture_detection/bitset_32_unittest.cc b/chromium/ui/events/gesture_detection/bitset_32_unittest.cc new file mode 100644 index 00000000000..2e5d2711e02 --- /dev/null +++ b/chromium/ui/events/gesture_detection/bitset_32_unittest.cc @@ -0,0 +1,100 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/gesture_detection/bitset_32.h" + +namespace ui { + +class BitSet32Test : public testing::Test {}; + +TEST_F(BitSet32Test, Basic) { + BitSet32 bits; + + // Test the empty set. + EXPECT_EQ(0U, bits.count()); + EXPECT_TRUE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_FALSE(bits.has_bit(0)); + EXPECT_FALSE(bits.has_bit(31)); + + // Mark the first bit. + bits.mark_bit(0); + EXPECT_EQ(1U, bits.count()); + EXPECT_FALSE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_TRUE(bits.has_bit(0)); + EXPECT_FALSE(bits.has_bit(31)); + EXPECT_EQ(0U, bits.first_marked_bit()); + EXPECT_EQ(0U, bits.last_marked_bit()); + EXPECT_EQ(1U, bits.first_unmarked_bit()); + + // Mark the last bit. + bits.mark_bit(31); + EXPECT_EQ(2U, bits.count()); + EXPECT_FALSE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_TRUE(bits.has_bit(0)); + EXPECT_TRUE(bits.has_bit(31)); + EXPECT_FALSE(bits.has_bit(15)); + EXPECT_EQ(0U, bits.first_marked_bit()); + EXPECT_EQ(31U, bits.last_marked_bit()); + EXPECT_EQ(1U, bits.first_unmarked_bit()); + EXPECT_EQ(0U, bits.get_index_of_bit(0)); + EXPECT_EQ(1U, bits.get_index_of_bit(1)); + EXPECT_EQ(1U, bits.get_index_of_bit(2)); + EXPECT_EQ(1U, bits.get_index_of_bit(31)); + + // Clear the first bit. + bits.clear_first_marked_bit(); + EXPECT_EQ(1U, bits.count()); + EXPECT_FALSE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_FALSE(bits.has_bit(0)); + EXPECT_TRUE(bits.has_bit(31)); + EXPECT_EQ(31U, bits.first_marked_bit()); + EXPECT_EQ(31U, bits.last_marked_bit()); + EXPECT_EQ(0U, bits.first_unmarked_bit()); + EXPECT_EQ(0U, bits.get_index_of_bit(0)); + EXPECT_EQ(0U, bits.get_index_of_bit(1)); + EXPECT_EQ(0U, bits.get_index_of_bit(31)); + + // Clear the last bit (the set should be empty). + bits.clear_last_marked_bit(); + EXPECT_EQ(0U, bits.count()); + EXPECT_TRUE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_FALSE(bits.has_bit(0)); + EXPECT_FALSE(bits.has_bit(31)); + EXPECT_EQ(0U, bits.get_index_of_bit(0)); + EXPECT_EQ(0U, bits.get_index_of_bit(31)); + EXPECT_EQ(BitSet32(), bits); + + // Mark the first unmarked bit (bit 0). + bits.mark_first_unmarked_bit(); + EXPECT_EQ(1U, bits.count()); + EXPECT_FALSE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_TRUE(bits.has_bit(0)); + EXPECT_EQ(0U, bits.first_marked_bit()); + EXPECT_EQ(0U, bits.last_marked_bit()); + EXPECT_EQ(1U, bits.first_unmarked_bit()); + + // Mark the next unmarked bit (bit 1). + bits.mark_first_unmarked_bit(); + EXPECT_EQ(2U, bits.count()); + EXPECT_FALSE(bits.is_empty()); + EXPECT_FALSE(bits.is_full()); + EXPECT_TRUE(bits.has_bit(0)); + EXPECT_TRUE(bits.has_bit(1)); + EXPECT_EQ(0U, bits.first_marked_bit()); + EXPECT_EQ(1U, bits.last_marked_bit()); + EXPECT_EQ(2U, bits.first_unmarked_bit()); + EXPECT_EQ(0U, bits.get_index_of_bit(0)); + EXPECT_EQ(1U, bits.get_index_of_bit(1)); + EXPECT_EQ(2U, bits.get_index_of_bit(2)); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/filtered_gesture_provider.cc b/chromium/ui/events/gesture_detection/filtered_gesture_provider.cc new file mode 100644 index 00000000000..76ee5201d1a --- /dev/null +++ b/chromium/ui/events/gesture_detection/filtered_gesture_provider.cc @@ -0,0 +1,77 @@ +// Copyright 2014 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/gesture_detection/filtered_gesture_provider.h" + +#include "base/auto_reset.h" +#include "base/logging.h" +#include "ui/events/gesture_detection/motion_event.h" + +namespace ui { + +FilteredGestureProvider::FilteredGestureProvider( + const GestureProvider::Config& config, + GestureProviderClient* client) + : client_(client), + gesture_provider_(config, this), + gesture_filter_(this), + handling_event_(false) {} + +bool FilteredGestureProvider::OnTouchEvent(const MotionEvent& event) { + DCHECK(!handling_event_); + base::AutoReset<bool> handling_event(&handling_event_, true); + + pending_gesture_packet_ = GestureEventDataPacket::FromTouch(event); + + if (!gesture_provider_.OnTouchEvent(event)) + return false; + + TouchDispositionGestureFilter::PacketResult result = + gesture_filter_.OnGesturePacket(pending_gesture_packet_); + if (result != TouchDispositionGestureFilter::SUCCESS) { + NOTREACHED() << "Invalid touch gesture sequence detected."; + return false; + } + + return true; +} + +void FilteredGestureProvider::OnTouchEventAck(bool event_consumed) { + gesture_filter_.OnTouchEventAck(event_consumed); +} + +void FilteredGestureProvider::SetMultiTouchZoomSupportEnabled( + bool enabled) { + gesture_provider_.SetMultiTouchZoomSupportEnabled(enabled); +} + +void FilteredGestureProvider::SetDoubleTapSupportForPlatformEnabled( + bool enabled) { + gesture_provider_.SetDoubleTapSupportForPlatformEnabled(enabled); +} + +void FilteredGestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) { + gesture_provider_.SetDoubleTapSupportForPageEnabled(enabled); +} + +const ui::MotionEvent* FilteredGestureProvider::GetCurrentDownEvent() const { + return gesture_provider_.current_down_event(); +} + +void FilteredGestureProvider::OnGestureEvent(const GestureEventData& event) { + if (handling_event_) { + pending_gesture_packet_.Push(event); + return; + } + + gesture_filter_.OnGesturePacket( + GestureEventDataPacket::FromTouchTimeout(event)); +} + +void FilteredGestureProvider::ForwardGestureEvent( + const GestureEventData& event) { + client_->OnGestureEvent(event); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/filtered_gesture_provider.h b/chromium/ui/events/gesture_detection/filtered_gesture_provider.h new file mode 100644 index 00000000000..4d63be64a83 --- /dev/null +++ b/chromium/ui/events/gesture_detection/filtered_gesture_provider.h @@ -0,0 +1,61 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_ +#define UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_ + +#include "base/basictypes.h" +#include "ui/events/gesture_detection/gesture_event_data_packet.h" +#include "ui/events/gesture_detection/gesture_provider.h" +#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h" + +namespace ui { + +// Provides filtered gesture detection and dispatch given a sequence of touch +// events and touch event acks. +class GESTURE_DETECTION_EXPORT FilteredGestureProvider + : public ui::TouchDispositionGestureFilterClient, + public ui::GestureProviderClient { + public: + // |client| will be offered all gestures detected by the |gesture_provider_| + // and allowed by the |gesture_filter_|. + FilteredGestureProvider(const GestureProvider::Config& config, + GestureProviderClient* client); + + // Returns true if |event| was both valid and successfully handled by the + // gesture provider. Otherwise, returns false, in which case the caller + // should drop |event|, not forwarding it to the renderer. + bool OnTouchEvent(const MotionEvent& event); + + // To be called upon ack of an event that was forwarded after a successful + // call to |OnTouchEvent()|. + void OnTouchEventAck(bool event_consumed); + + // Methods delegated to |gesture_provider_|. + void SetMultiTouchZoomSupportEnabled(bool enabled); + void SetDoubleTapSupportForPlatformEnabled(bool enabled); + void SetDoubleTapSupportForPageEnabled(bool enabled); + const ui::MotionEvent* GetCurrentDownEvent() const; + + private: + // GestureProviderClient implementation. + virtual void OnGestureEvent(const ui::GestureEventData& event) OVERRIDE; + + // TouchDispositionGestureFilterClient implementation. + virtual void ForwardGestureEvent(const ui::GestureEventData& event) OVERRIDE; + + GestureProviderClient* const client_; + + ui::GestureProvider gesture_provider_; + ui::TouchDispositionGestureFilter gesture_filter_; + + bool handling_event_; + ui::GestureEventDataPacket pending_gesture_packet_; + + DISALLOW_COPY_AND_ASSIGN(FilteredGestureProvider); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_config_helper.cc b/chromium/ui/events/gesture_detection/gesture_config_helper.cc new file mode 100644 index 00000000000..0039e2c1f93 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_config_helper.cc @@ -0,0 +1,17 @@ +// Copyright 2014 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/gesture_detection/gesture_config_helper.h" + +#include "ui/gfx/screen.h" + +namespace ui { + +GestureProvider::Config DefaultGestureProviderConfig() { + GestureProvider::Config config; + config.display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); + return config; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_config_helper.h b/chromium/ui/events/gesture_detection/gesture_config_helper.h new file mode 100644 index 00000000000..0b54c368786 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_config_helper.h @@ -0,0 +1,20 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_ + +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/gesture_detector.h" +#include "ui/events/gesture_detection/gesture_provider.h" +#include "ui/events/gesture_detection/scale_gesture_detector.h" + +namespace ui { + +GESTURE_DETECTION_EXPORT GestureProvider::Config +DefaultGestureProviderConfig(); + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc b/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc new file mode 100644 index 00000000000..546cb84e119 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc @@ -0,0 +1,73 @@ +// Copyright 2014 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/gesture_detection/gesture_config_helper.h" + +#include "ui/gfx/android/view_configuration.h" +#include "ui/gfx/screen.h" + +using gfx::ViewConfiguration; + +namespace ui { +namespace { +// TODO(jdduke): Adopt GestureConfiguration on Android, crbug/339203. + +// This was the minimum tap/press size used on Android before the new gesture +// detection pipeline. +const float kMinGestureBoundsLengthDips = 24.f; + +GestureDetector::Config DefaultGestureDetectorConfig( + const gfx::Display& display) { + GestureDetector::Config config; + + config.longpress_timeout = base::TimeDelta::FromMilliseconds( + ViewConfiguration::GetLongPressTimeoutInMs()); + config.showpress_timeout = + base::TimeDelta::FromMilliseconds(ViewConfiguration::GetTapTimeoutInMs()); + config.double_tap_timeout = base::TimeDelta::FromMilliseconds( + ViewConfiguration::GetDoubleTapTimeoutInMs()); + + const float px_to_dp = 1.f / display.device_scale_factor(); + config.touch_slop = + ViewConfiguration::GetTouchSlopInPixels() * px_to_dp; + config.double_tap_slop = + ViewConfiguration::GetDoubleTapSlopInPixels() * px_to_dp; + config.minimum_fling_velocity = + ViewConfiguration::GetMinimumFlingVelocityInPixelsPerSecond() * px_to_dp; + config.maximum_fling_velocity = + ViewConfiguration::GetMaximumFlingVelocityInPixelsPerSecond() * px_to_dp; + + return config; +} + +ScaleGestureDetector::Config DefaultScaleGestureDetectorConfig( + const gfx::Display& display) { + ScaleGestureDetector::Config config; + + config.gesture_detector_config = DefaultGestureDetectorConfig(display); + config.quick_scale_enabled = true; + + const float px_to_dp = 1.f / display.device_scale_factor(); + config.min_scaling_touch_major = + ViewConfiguration::GetMinScalingTouchMajorInPixels() * px_to_dp; + config.min_scaling_span = + ViewConfiguration::GetMinScalingSpanInPixels() * px_to_dp; + + return config; +} + +} // namespace + +GestureProvider::Config DefaultGestureProviderConfig() { + GestureProvider::Config config; + config.display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); + config.gesture_detector_config = DefaultGestureDetectorConfig(config.display); + config.scale_gesture_detector_config = + DefaultScaleGestureDetectorConfig(config.display); + config.gesture_begin_end_types_enabled = false; + config.min_gesture_bounds_length = kMinGestureBoundsLengthDips; + return config; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc b/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc new file mode 100644 index 00000000000..36be122b772 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc @@ -0,0 +1,73 @@ +// Copyright 2014 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. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "ui/events/gesture_detection/gesture_config_helper.h" + +#include <cmath> + +#include "ui/events/gestures/gesture_configuration.h" +#include "ui/gfx/screen.h" + +namespace ui { +namespace { + +GestureDetector::Config DefaultGestureDetectorConfig() { + GestureDetector::Config config; + + config.longpress_timeout = base::TimeDelta::FromMilliseconds( + GestureConfiguration::long_press_time_in_seconds() * 1000.); + config.showpress_timeout = base::TimeDelta::FromMilliseconds( + GestureConfiguration::show_press_delay_in_ms()); + config.double_tap_timeout = base::TimeDelta::FromMilliseconds( + GestureConfiguration::semi_long_press_time_in_seconds() * 1000.); + config.touch_slop = + GestureConfiguration::max_touch_move_in_pixels_for_click(); + config.double_tap_slop = + GestureConfiguration::max_distance_between_taps_for_double_tap(); + config.minimum_fling_velocity = + GestureConfiguration::min_scroll_velocity(); + config.maximum_fling_velocity = GestureConfiguration::fling_velocity_cap(); + config.swipe_enabled = true; + config.minimum_swipe_velocity = GestureConfiguration::min_swipe_speed(); + config.maximum_swipe_deviation_angle = + atan2(1.f, GestureConfiguration::max_swipe_deviation_ratio()) * 180.0f / + static_cast<float>(M_PI); + config.two_finger_tap_enabled = true; + config.two_finger_tap_max_separation = + GestureConfiguration::max_distance_for_two_finger_tap_in_pixels(); + config.two_finger_tap_timeout = base::TimeDelta::FromMilliseconds( + GestureConfiguration::max_touch_down_duration_in_seconds_for_click() * + 1000.); + + return config; +} + +ScaleGestureDetector::Config DefaultScaleGestureDetectorConfig() { + ScaleGestureDetector::Config config; + + config.gesture_detector_config = DefaultGestureDetectorConfig(); + config.min_scaling_touch_major = GestureConfiguration::default_radius() * 2; + config.min_scaling_span = GestureConfiguration::min_scaling_span_in_pixels(); + config.min_pinch_update_span_delta = + GestureConfiguration::min_pinch_update_distance_in_pixels(); + return config; +} + +} // namespace + +GestureProvider::Config DefaultGestureProviderConfig() { + GestureProvider::Config config; + config.display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); + config.gesture_detector_config = DefaultGestureDetectorConfig(); + config.scale_gesture_detector_config = DefaultScaleGestureDetectorConfig(); + config.gesture_begin_end_types_enabled = true; + // Half the size of the default touch length is a reasonable minimum. + config.min_gesture_bounds_length = GestureConfiguration::default_radius(); + return config; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_detection_export.h b/chromium/ui/events/gesture_detection/gesture_detection_export.h new file mode 100644 index 00000000000..edef5244be3 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_detection_export.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GESTURE_DETECTION_IMPLEMENTATION) +#define GESTURE_DETECTION_EXPORT __declspec(dllexport) +#else +#define GESTURE_DETECTION_EXPORT __declspec(dllimport) +#endif // defined(GESTURES_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GESTURE_DETECTION_IMPLEMENTATION) +#define GESTURE_DETECTION_EXPORT __attribute__((visibility("default"))) +#else +#define GESTURE_DETECTION_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GESTURE_DETECTION_EXPORT +#endif + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_detector.cc b/chromium/ui/events/gesture_detection/gesture_detector.cc new file mode 100644 index 00000000000..5dc61938b1a --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_detector.cc @@ -0,0 +1,553 @@ +// Copyright 2014 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. + +// MSVC++ requires this to be set before any other includes to get M_PI. +#define _USE_MATH_DEFINES + +#include "ui/events/gesture_detection/gesture_detector.h" + +#include <cmath> + +#include "base/timer/timer.h" +#include "ui/events/gesture_detection/motion_event.h" + +namespace ui { +namespace { + +// Using a small epsilon when comparing slop distances allows pixel perfect +// slop determination when using fractional DIP coordinates (assuming the slop +// region and DPI scale are reasonably proportioned). +const float kSlopEpsilon = .05f; + +// Minimum distance a scroll must have traveled from the last scroll/focal point +// to trigger an |OnScroll| callback. +const float kScrollEpsilon = .1f; + +const float kDegreesToRadians = static_cast<float>(M_PI) / 180.0f; + +// Constants used by TimeoutGestureHandler. +enum TimeoutEvent { + SHOW_PRESS = 0, + LONG_PRESS, + TAP, + TIMEOUT_EVENT_COUNT +}; + +} // namespace + +// Note: These constants were taken directly from the default (unscaled) +// versions found in Android's ViewConfiguration. +GestureDetector::Config::Config() + : longpress_timeout(base::TimeDelta::FromMilliseconds(500)), + showpress_timeout(base::TimeDelta::FromMilliseconds(180)), + double_tap_timeout(base::TimeDelta::FromMilliseconds(300)), + double_tap_min_time(base::TimeDelta::FromMilliseconds(40)), + touch_slop(8), + double_tap_slop(100), + minimum_fling_velocity(50), + maximum_fling_velocity(8000), + swipe_enabled(false), + minimum_swipe_velocity(20), + maximum_swipe_deviation_angle(20.f), + two_finger_tap_enabled(false), + two_finger_tap_max_separation(300), + two_finger_tap_timeout(base::TimeDelta::FromMilliseconds(700)) { +} + +GestureDetector::Config::~Config() {} + +bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) { + return false; +} + +void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) { +} + +bool GestureDetector::SimpleGestureListener::OnSingleTapUp( + const MotionEvent& e) { + return false; +} + +void GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) { +} + +bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1, + const MotionEvent& e2, + float distance_x, + float distance_y) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnSwipe(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnTwoFingerTap( + const MotionEvent& e1, + const MotionEvent& e2) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed( + const MotionEvent& e) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) { + return false; +} + +bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent( + const MotionEvent& e) { + return false; +} + +class GestureDetector::TimeoutGestureHandler { + public: + TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector) + : gesture_detector_(gesture_detector) { + DCHECK(config.showpress_timeout <= config.longpress_timeout); + + timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout; + timeout_delays_[SHOW_PRESS] = config.showpress_timeout; + + timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout; + timeout_delays_[LONG_PRESS] = + config.longpress_timeout + config.showpress_timeout; + + timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout; + timeout_delays_[TAP] = config.double_tap_timeout; + } + + ~TimeoutGestureHandler() { + Stop(); + } + + void StartTimeout(TimeoutEvent event) { + timeout_timers_[event].Start(FROM_HERE, + timeout_delays_[event], + gesture_detector_, + timeout_callbacks_[event]); + } + + void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); } + + void Stop() { + for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i) + timeout_timers_[i].Stop(); + } + + bool HasTimeout(TimeoutEvent event) const { + return timeout_timers_[event].IsRunning(); + } + + private: + typedef void (GestureDetector::*ReceiverMethod)(); + + GestureDetector* const gesture_detector_; + base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT]; + ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT]; + base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT]; +}; + +GestureDetector::GestureDetector( + const Config& config, + GestureListener* listener, + DoubleTapListener* optional_double_tap_listener) + : timeout_handler_(new TimeoutGestureHandler(config, this)), + listener_(listener), + double_tap_listener_(optional_double_tap_listener), + touch_slop_square_(0), + double_tap_touch_slop_square_(0), + double_tap_slop_square_(0), + two_finger_tap_distance_square_(0), + min_fling_velocity_(1), + max_fling_velocity_(1), + min_swipe_velocity_(0), + min_swipe_direction_component_ratio_(0), + still_down_(false), + defer_confirm_single_tap_(false), + always_in_tap_region_(false), + always_in_bigger_tap_region_(false), + two_finger_tap_allowed_for_gesture_(false), + is_double_tapping_(false), + last_focus_x_(0), + last_focus_y_(0), + down_focus_x_(0), + down_focus_y_(0), + longpress_enabled_(true), + swipe_enabled_(false), + two_finger_tap_enabled_(false) { + DCHECK(listener_); + Init(config); +} + +GestureDetector::~GestureDetector() {} + +bool GestureDetector::OnTouchEvent(const MotionEvent& ev) { + const MotionEvent::Action action = ev.GetAction(); + + velocity_tracker_.AddMovement(ev); + + const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP; + const int skip_index = pointer_up ? ev.GetActionIndex() : -1; + + // Determine focal point. + float sum_x = 0, sum_y = 0; + const int count = static_cast<int>(ev.GetPointerCount()); + for (int i = 0; i < count; i++) { + if (skip_index == i) + continue; + sum_x += ev.GetX(i); + sum_y += ev.GetY(i); + } + const int div = pointer_up ? count - 1 : count; + const float focus_x = sum_x / div; + const float focus_y = sum_y / div; + + bool handled = false; + + switch (action) { + case MotionEvent::ACTION_POINTER_DOWN: { + down_focus_x_ = last_focus_x_ = focus_x; + down_focus_y_ = last_focus_y_ = focus_y; + // Cancel long press and taps. + CancelTaps(); + + if (!two_finger_tap_allowed_for_gesture_) + break; + + const int action_index = ev.GetActionIndex(); + const float dx = ev.GetX(action_index) - current_down_event_->GetX(); + const float dy = ev.GetY(action_index) - current_down_event_->GetY(); + + if (ev.GetPointerCount() == 2 && + dx * dx + dy * dy < two_finger_tap_distance_square_) { + secondary_pointer_down_event_ = ev.Clone(); + } else { + two_finger_tap_allowed_for_gesture_ = false; + } + } break; + + case MotionEvent::ACTION_POINTER_UP: { + down_focus_x_ = last_focus_x_ = focus_x; + down_focus_y_ = last_focus_y_ = focus_y; + + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_); + const int up_index = ev.GetActionIndex(); + const int id1 = ev.GetPointerId(up_index); + const float vx1 = velocity_tracker_.GetXVelocity(id1); + const float vy1 = velocity_tracker_.GetYVelocity(id1); + float vx_total = vx1; + float vy_total = vy1; + for (int i = 0; i < count; i++) { + if (i == up_index) + continue; + + const int id2 = ev.GetPointerId(i); + const float vx2 = velocity_tracker_.GetXVelocity(id2); + const float vy2 = velocity_tracker_.GetYVelocity(id2); + const float dot = vx1 * vx2 + vy1 * vy2; + if (dot < 0) { + vx_total = 0; + vy_total = 0; + velocity_tracker_.Clear(); + break; + } + vx_total += vx2; + vy_total += vy2; + } + + handled = HandleSwipeIfNeeded(ev, vx_total / count, vy_total / count); + + if (two_finger_tap_allowed_for_gesture_ && ev.GetPointerCount() == 2 && + (ev.GetEventTime() - secondary_pointer_down_event_->GetEventTime() <= + two_finger_tap_timeout_)) { + handled = listener_->OnTwoFingerTap(*current_down_event_, ev); + } + two_finger_tap_allowed_for_gesture_ = false; + } break; + + case MotionEvent::ACTION_DOWN: + if (double_tap_listener_) { + bool had_tap_message = timeout_handler_->HasTimeout(TAP); + if (had_tap_message) + timeout_handler_->StopTimeout(TAP); + if (current_down_event_ && previous_up_event_ && had_tap_message && + IsConsideredDoubleTap( + *current_down_event_, *previous_up_event_, ev)) { + // This is a second tap. + is_double_tapping_ = true; + // Give a callback with the first tap of the double-tap. + handled |= double_tap_listener_->OnDoubleTap(*current_down_event_); + // Give a callback with down event of the double-tap. + handled |= double_tap_listener_->OnDoubleTapEvent(ev); + } else { + // This is a first tap. + DCHECK(double_tap_timeout_ > base::TimeDelta()); + timeout_handler_->StartTimeout(TAP); + } + } + + down_focus_x_ = last_focus_x_ = focus_x; + down_focus_y_ = last_focus_y_ = focus_y; + current_down_event_ = ev.Clone(); + + secondary_pointer_down_event_.reset(); + always_in_tap_region_ = true; + always_in_bigger_tap_region_ = true; + still_down_ = true; + defer_confirm_single_tap_ = false; + two_finger_tap_allowed_for_gesture_ = two_finger_tap_enabled_; + + // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure + // proper timeout ordering. + timeout_handler_->StartTimeout(SHOW_PRESS); + if (longpress_enabled_) + timeout_handler_->StartTimeout(LONG_PRESS); + handled |= listener_->OnDown(ev); + break; + + case MotionEvent::ACTION_MOVE: + { + const float scroll_x = last_focus_x_ - focus_x; + const float scroll_y = last_focus_y_ - focus_y; + if (is_double_tapping_) { + // Give the move events of the double-tap. + DCHECK(double_tap_listener_); + handled |= double_tap_listener_->OnDoubleTapEvent(ev); + } else if (always_in_tap_region_) { + const float delta_x = focus_x - down_focus_x_; + const float delta_y = focus_y - down_focus_y_; + const float distance_square = delta_x * delta_x + delta_y * delta_y; + if (distance_square > touch_slop_square_) { + handled = listener_->OnScroll( + *current_down_event_, ev, scroll_x, scroll_y); + last_focus_x_ = focus_x; + last_focus_y_ = focus_y; + always_in_tap_region_ = false; + timeout_handler_->Stop(); + } + if (distance_square > double_tap_touch_slop_square_) + always_in_bigger_tap_region_ = false; + } else if (std::abs(scroll_x) > kScrollEpsilon || + std::abs(scroll_y) > kScrollEpsilon) { + handled = + listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y); + last_focus_x_ = focus_x; + last_focus_y_ = focus_y; + } + + if (!two_finger_tap_allowed_for_gesture_) + break; + + // Two-finger tap should be prevented if either pointer exceeds its + // (independent) slop region. + const int id0 = current_down_event_->GetPointerId(0); + const int ev_idx0 = ev.GetPointerId(0) == id0 ? 0 : 1; + + // Check if the primary pointer exceeded the slop region. + float dx = current_down_event_->GetX() - ev.GetX(ev_idx0); + float dy = current_down_event_->GetY() - ev.GetY(ev_idx0); + if (dx * dx + dy * dy > touch_slop_square_) { + two_finger_tap_allowed_for_gesture_ = false; + break; + } + if (ev.GetPointerCount() == 2) { + // Check if the secondary pointer exceeded the slop region. + const int ev_idx1 = ev_idx0 == 0 ? 1 : 0; + const int idx1 = secondary_pointer_down_event_->GetActionIndex(); + dx = secondary_pointer_down_event_->GetX(idx1) - ev.GetX(ev_idx1); + dy = secondary_pointer_down_event_->GetY(idx1) - ev.GetY(ev_idx1); + if (dx * dx + dy * dy > touch_slop_square_) + two_finger_tap_allowed_for_gesture_ = false; + } + } + break; + + case MotionEvent::ACTION_UP: + still_down_ = false; + { + if (is_double_tapping_) { + // Finally, give the up event of the double-tap. + DCHECK(double_tap_listener_); + handled |= double_tap_listener_->OnDoubleTapEvent(ev); + } else if (always_in_tap_region_) { + handled = listener_->OnSingleTapUp(ev); + if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) { + double_tap_listener_->OnSingleTapConfirmed(ev); + } + } else { + + // A fling must travel the minimum tap distance. + const int pointer_id = ev.GetPointerId(0); + velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_); + const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id); + const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id); + + if ((std::abs(velocity_y) > min_fling_velocity_) || + (std::abs(velocity_x) > min_fling_velocity_)) { + handled = listener_->OnFling( + *current_down_event_, ev, velocity_x, velocity_y); + } + + handled |= HandleSwipeIfNeeded(ev, velocity_x, velocity_y); + } + + previous_up_event_ = ev.Clone(); + + velocity_tracker_.Clear(); + is_double_tapping_ = false; + defer_confirm_single_tap_ = false; + timeout_handler_->StopTimeout(SHOW_PRESS); + timeout_handler_->StopTimeout(LONG_PRESS); + } + break; + + case MotionEvent::ACTION_CANCEL: + Cancel(); + break; + } + + return handled; +} + +void GestureDetector::SetDoubleTapListener( + DoubleTapListener* double_tap_listener) { + if (double_tap_listener == double_tap_listener_) + return; + + DCHECK(!is_double_tapping_); + + // Null'ing the double-tap listener should flush an active tap timeout. + if (!double_tap_listener) { + if (timeout_handler_->HasTimeout(TAP)) { + timeout_handler_->StopTimeout(TAP); + OnTapTimeout(); + } + } + + double_tap_listener_ = double_tap_listener; +} + +void GestureDetector::Init(const Config& config) { + DCHECK(listener_); + + const float touch_slop = config.touch_slop + kSlopEpsilon; + const float double_tap_touch_slop = touch_slop; + const float double_tap_slop = config.double_tap_slop + kSlopEpsilon; + touch_slop_square_ = touch_slop * touch_slop; + double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop; + double_tap_slop_square_ = double_tap_slop * double_tap_slop; + double_tap_timeout_ = config.double_tap_timeout; + double_tap_min_time_ = config.double_tap_min_time; + DCHECK(double_tap_min_time_ < double_tap_timeout_); + min_fling_velocity_ = config.minimum_fling_velocity; + max_fling_velocity_ = config.maximum_fling_velocity; + + swipe_enabled_ = config.swipe_enabled; + min_swipe_velocity_ = config.minimum_swipe_velocity; + DCHECK_GT(config.maximum_swipe_deviation_angle, 0); + DCHECK_LE(config.maximum_swipe_deviation_angle, 45); + const float maximum_swipe_deviation_angle = + std::min(45.f, std::max(0.001f, config.maximum_swipe_deviation_angle)); + min_swipe_direction_component_ratio_ = + 1.f / tan(maximum_swipe_deviation_angle * kDegreesToRadians); + + two_finger_tap_enabled_ = config.two_finger_tap_enabled; + two_finger_tap_distance_square_ = config.two_finger_tap_max_separation * + config.two_finger_tap_max_separation; + two_finger_tap_timeout_ = config.two_finger_tap_timeout; +} + +void GestureDetector::OnShowPressTimeout() { + listener_->OnShowPress(*current_down_event_); +} + +void GestureDetector::OnLongPressTimeout() { + timeout_handler_->StopTimeout(TAP); + defer_confirm_single_tap_ = false; + listener_->OnLongPress(*current_down_event_); +} + +void GestureDetector::OnTapTimeout() { + if (!double_tap_listener_) + return; + if (!still_down_) + double_tap_listener_->OnSingleTapConfirmed(*current_down_event_); + else + defer_confirm_single_tap_ = true; +} + +void GestureDetector::Cancel() { + CancelTaps(); + velocity_tracker_.Clear(); + still_down_ = false; +} + +void GestureDetector::CancelTaps() { + timeout_handler_->Stop(); + is_double_tapping_ = false; + always_in_tap_region_ = false; + always_in_bigger_tap_region_ = false; + defer_confirm_single_tap_ = false; +} + +bool GestureDetector::IsConsideredDoubleTap( + const MotionEvent& first_down, + const MotionEvent& first_up, + const MotionEvent& second_down) const { + if (!always_in_bigger_tap_region_) + return false; + + const base::TimeDelta delta_time = + second_down.GetEventTime() - first_up.GetEventTime(); + if (delta_time < double_tap_min_time_ || delta_time > double_tap_timeout_) + return false; + + const float delta_x = first_down.GetX() - second_down.GetX(); + const float delta_y = first_down.GetY() - second_down.GetY(); + return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_); +} + +bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent& up, + float vx, + float vy) { + if (!swipe_enabled_ || (!vx && !vy)) + return false; + float vx_abs = std::abs(vx); + float vy_abs = std::abs(vy); + + if (vx_abs < min_swipe_velocity_) + vx_abs = vx = 0; + if (vy_abs < min_swipe_velocity_) + vy_abs = vy = 0; + + // Note that the ratio will be 0 if both velocites are below the min. + float ratio = vx_abs > vy_abs ? vx_abs / std::max(vy_abs, 0.001f) + : vy_abs / std::max(vx_abs, 0.001f); + + if (ratio < min_swipe_direction_component_ratio_) + return false; + + if (vx_abs > vy_abs) + vy = 0; + else + vx = 0; + return listener_->OnSwipe(*current_down_event_, up, vx, vy); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_detector.h b/chromium/ui/events/gesture_detection/gesture_detector.h new file mode 100644 index 00000000000..db665f8cbcd --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_detector.h @@ -0,0 +1,214 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_ + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/velocity_tracker_state.h" + +namespace ui { + +class MotionEvent; + +// Port of GestureDetector.java from Android +// * platform/frameworks/base/core/java/android/view/GestureDetector.java +// * Change-Id: Ib470735ec929b0b358fca4597e92dc81084e675f +// * Please update the Change-Id as upstream Android changes are pulled. +class GestureDetector { + public: + struct GESTURE_DETECTION_EXPORT Config { + Config(); + ~Config(); + + base::TimeDelta longpress_timeout; + base::TimeDelta showpress_timeout; + base::TimeDelta double_tap_timeout; + + // The minimum duration between the first tap's up event and the second + // tap's down event for an interaction to be considered a double-tap. + base::TimeDelta double_tap_min_time; + + // Distance a touch can wander before a scroll will occur (in dips). + float touch_slop; + + // Distance the first touch can wander before it is no longer considered a + // double tap (in dips). + float double_tap_slop; + + // Minimum velocity to initiate a fling (in dips/second). + float minimum_fling_velocity; + + // Maximum velocity of an initiated fling (in dips/second). + float maximum_fling_velocity; + + // Whether |OnSwipe| should be called after a secondary touch is released + // while a logical swipe gesture is active. Defaults to false. + bool swipe_enabled; + + // Minimum velocity to initiate a swipe (in dips/second). + float minimum_swipe_velocity; + + // Maximum angle of the swipe from its dominant component axis, between + // (0, 45] degrees. The closer this is to 0, the closer the dominant + // direction of the swipe must be to up, down left or right. + float maximum_swipe_deviation_angle; + + // Whether |OnTwoFingerTap| should be called for two finger tap + // gestures. Defaults to false. + bool two_finger_tap_enabled; + + // Maximum distance between pointers for a two finger tap. + float two_finger_tap_max_separation; + + // Maximum time the second pointer can be active for a two finger tap. + base::TimeDelta two_finger_tap_timeout; + }; + + class GestureListener { + public: + virtual ~GestureListener() {} + virtual bool OnDown(const MotionEvent& e) = 0; + virtual void OnShowPress(const MotionEvent& e) = 0; + virtual bool OnSingleTapUp(const MotionEvent& e) = 0; + virtual void OnLongPress(const MotionEvent& e) = 0; + virtual bool OnScroll(const MotionEvent& e1, + const MotionEvent& e2, + float distance_x, + float distance_y) = 0; + virtual bool OnFling(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) = 0; + // Added for Chromium (Aura). + virtual bool OnSwipe(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) = 0; + virtual bool OnTwoFingerTap(const MotionEvent& e1, + const MotionEvent& e2) = 0; + }; + + class DoubleTapListener { + public: + virtual ~DoubleTapListener() {} + virtual bool OnSingleTapConfirmed(const MotionEvent& e) = 0; + virtual bool OnDoubleTap(const MotionEvent& e) = 0; + virtual bool OnDoubleTapEvent(const MotionEvent& e) = 0; + }; + + // A convenience class to extend when you only want to listen for a subset + // of all the gestures. This implements all methods in the + // |GestureListener| and |DoubleTapListener| but does + // nothing and returns false for all applicable methods. + class SimpleGestureListener : public GestureListener, + public DoubleTapListener { + public: + // GestureListener implementation. + virtual bool OnDown(const MotionEvent& e) OVERRIDE; + virtual void OnShowPress(const MotionEvent& e) OVERRIDE; + virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE; + virtual void OnLongPress(const MotionEvent& e) OVERRIDE; + virtual bool OnScroll(const MotionEvent& e1, + const MotionEvent& e2, + float distance_x, + float distance_y) OVERRIDE; + virtual bool OnFling(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) OVERRIDE; + virtual bool OnSwipe(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) OVERRIDE; + virtual bool OnTwoFingerTap(const MotionEvent& e1, + const MotionEvent& e2) OVERRIDE; + + // DoubleTapListener implementation. + virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE; + virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE; + virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE; + }; + + GestureDetector(const Config& config, + GestureListener* listener, + DoubleTapListener* optional_double_tap_listener); + ~GestureDetector(); + + bool OnTouchEvent(const MotionEvent& ev); + + // Setting a valid |double_tap_listener| will enable double-tap detection, + // wherein calls to |OnSimpleTapConfirmed| are delayed by the tap timeout. + // Note: The listener must never be changed while |is_double_tapping| is true. + void SetDoubleTapListener(DoubleTapListener* double_tap_listener); + + bool has_doubletap_listener() const { return double_tap_listener_ != NULL; } + + bool is_double_tapping() const { return is_double_tapping_; } + + void set_longpress_enabled(bool enabled) { longpress_enabled_ = enabled; } + + private: + void Init(const Config& config); + void OnShowPressTimeout(); + void OnLongPressTimeout(); + void OnTapTimeout(); + void Cancel(); + void CancelTaps(); + bool IsConsideredDoubleTap(const MotionEvent& first_down, + const MotionEvent& first_up, + const MotionEvent& second_down) const; + bool HandleSwipeIfNeeded(const MotionEvent& up, float vx, float vy); + + class TimeoutGestureHandler; + scoped_ptr<TimeoutGestureHandler> timeout_handler_; + GestureListener* const listener_; + DoubleTapListener* double_tap_listener_; + + float touch_slop_square_; + float double_tap_touch_slop_square_; + float double_tap_slop_square_; + float two_finger_tap_distance_square_; + float min_fling_velocity_; + float max_fling_velocity_; + float min_swipe_velocity_; + float min_swipe_direction_component_ratio_; + base::TimeDelta double_tap_timeout_; + base::TimeDelta two_finger_tap_timeout_; + base::TimeDelta double_tap_min_time_; + + bool still_down_; + bool defer_confirm_single_tap_; + bool always_in_tap_region_; + bool always_in_bigger_tap_region_; + bool two_finger_tap_allowed_for_gesture_; + + scoped_ptr<MotionEvent> current_down_event_; + scoped_ptr<MotionEvent> previous_up_event_; + scoped_ptr<MotionEvent> secondary_pointer_down_event_; + + // True when the user is still touching for the second tap (down, move, and + // up events). Can only be true if there is a double tap listener attached. + bool is_double_tapping_; + + float last_focus_x_; + float last_focus_y_; + float down_focus_x_; + float down_focus_y_; + + bool longpress_enabled_; + bool swipe_enabled_; + bool two_finger_tap_enabled_; + + // Determines speed during touch scrolling. + VelocityTrackerState velocity_tracker_; + + DISALLOW_COPY_AND_ASSIGN(GestureDetector); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_event_data.cc b/chromium/ui/events/gesture_detection/gesture_event_data.cc new file mode 100644 index 00000000000..42d1d009992 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_event_data.cc @@ -0,0 +1,50 @@ +// Copyright 2014 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/gesture_detection/gesture_event_data.h" + +#include "base/logging.h" + +namespace ui { + +GestureEventData::GestureEventData(const GestureEventDetails& details, + int motion_event_id, + base::TimeTicks time, + float x, + float y, + float raw_x, + float raw_y, + size_t touch_point_count, + const gfx::RectF& bounding_box) + : details(details), + motion_event_id(motion_event_id), + time(time), + x(x), + y(y), + raw_x(raw_x), + raw_y(raw_y) { + DCHECK_GE(motion_event_id, 0); + DCHECK_NE(0U, touch_point_count); + this->details.set_touch_points(static_cast<int>(touch_point_count)); + this->details.set_bounding_box(bounding_box); +} + +GestureEventData::GestureEventData(EventType type, + const GestureEventData& other) + : details(type, 0, 0), + motion_event_id(other.motion_event_id), + time(other.time), + x(other.x), + y(other.y), + raw_x(other.raw_x), + raw_y(other.raw_y) { + details.set_touch_points(other.details.touch_points()); + details.set_bounding_box(other.details.bounding_box_f()); +} + +GestureEventData::GestureEventData() + : motion_event_id(0), x(0), y(0), raw_x(0), raw_y(0) { +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_event_data.h b/chromium/ui/events/gesture_detection/gesture_event_data.h new file mode 100644 index 00000000000..35eb41e2c77 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_event_data.h @@ -0,0 +1,48 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_ + +#include "base/time/time.h" +#include "ui/events/event_constants.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_event_details.h" + +namespace ui { + +class GestureEventDataPacket; + +struct GESTURE_DETECTION_EXPORT GestureEventData { + GestureEventData(const GestureEventDetails&, + int motion_event_id, + base::TimeTicks time, + float x, + float y, + float raw_x, + float raw_y, + size_t touch_point_count, + const gfx::RectF& bounding_box); + GestureEventData(EventType type, const GestureEventData&); + + EventType type() const { return details.type(); } + + GestureEventDetails details; + int motion_event_id; + base::TimeTicks time; + float x; + float y; + float raw_x; + float raw_y; + + private: + friend class GestureEventDataPacket; + + // Initializes type to GESTURE_TYPE_INVALID. + GestureEventData(); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_event_data_packet.cc b/chromium/ui/events/gesture_detection/gesture_event_data_packet.cc new file mode 100644 index 00000000000..09adceca475 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_event_data_packet.cc @@ -0,0 +1,96 @@ +// Copyright 2014 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/gesture_detection/gesture_event_data_packet.h" + +#include "base/logging.h" +#include "ui/events/gesture_detection/motion_event.h" + +namespace ui { +namespace { + +GestureEventDataPacket::GestureSource ToGestureSource( + const ui::MotionEvent& event) { + switch (event.GetAction()) { + case ui::MotionEvent::ACTION_DOWN: + return GestureEventDataPacket::TOUCH_SEQUENCE_START; + case ui::MotionEvent::ACTION_UP: + return GestureEventDataPacket::TOUCH_SEQUENCE_END; + case ui::MotionEvent::ACTION_MOVE: + return GestureEventDataPacket::TOUCH_MOVE; + case ui::MotionEvent::ACTION_CANCEL: + return GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL; + case ui::MotionEvent::ACTION_POINTER_DOWN: + return GestureEventDataPacket::TOUCH_START; + case ui::MotionEvent::ACTION_POINTER_UP: + return GestureEventDataPacket::TOUCH_END; + }; + NOTREACHED() << "Invalid ui::MotionEvent action: " << event.GetAction(); + return GestureEventDataPacket::INVALID; +} + +} // namespace + +GestureEventDataPacket::GestureEventDataPacket() + : gesture_source_(UNDEFINED) { +} + +GestureEventDataPacket::GestureEventDataPacket( + base::TimeTicks timestamp, + GestureSource source, + const gfx::PointF& touch_location, + const gfx::PointF& raw_touch_location) + : timestamp_(timestamp), + touch_location_(touch_location), + raw_touch_location_(raw_touch_location), + gesture_source_(source) { + DCHECK_NE(gesture_source_, UNDEFINED); +} + +GestureEventDataPacket::GestureEventDataPacket( + const GestureEventDataPacket& other) + : timestamp_(other.timestamp_), + gestures_(other.gestures_), + touch_location_(other.touch_location_), + raw_touch_location_(other.raw_touch_location_), + gesture_source_(other.gesture_source_) { +} + +GestureEventDataPacket::~GestureEventDataPacket() { +} + +GestureEventDataPacket& GestureEventDataPacket::operator=( + const GestureEventDataPacket& other) { + timestamp_ = other.timestamp_; + gesture_source_ = other.gesture_source_; + touch_location_ = other.touch_location_; + raw_touch_location_ = other.raw_touch_location_; + gestures_ = other.gestures_; + return *this; +} + +void GestureEventDataPacket::Push(const GestureEventData& gesture) { + DCHECK_NE(ET_UNKNOWN, gesture.type()); + gestures_.push_back(gesture); +} + +GestureEventDataPacket GestureEventDataPacket::FromTouch( + const ui::MotionEvent& touch) { + return GestureEventDataPacket(touch.GetEventTime(), + ToGestureSource(touch), + gfx::PointF(touch.GetX(), touch.GetY()), + gfx::PointF(touch.GetRawX(), touch.GetRawY())); +} + +GestureEventDataPacket GestureEventDataPacket::FromTouchTimeout( + const GestureEventData& gesture) { + GestureEventDataPacket packet(gesture.time, + TOUCH_TIMEOUT, + gfx::PointF(gesture.x, gesture.y), + gfx::PointF(gesture.raw_x, gesture.raw_y)); + packet.Push(gesture); + return packet; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_event_data_packet.h b/chromium/ui/events/gesture_detection/gesture_event_data_packet.h new file mode 100644 index 00000000000..0c40f576784 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_event_data_packet.h @@ -0,0 +1,68 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_ + +#include <vector> + +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/gesture_event_data.h" + +namespace ui { + +class MotionEvent; + +// Acts as a transport container for gestures created (directly or indirectly) +// by a touch event. +class GESTURE_DETECTION_EXPORT GestureEventDataPacket { + public: + enum GestureSource { + UNDEFINED = -1, // Used only for a default-constructed packet. + INVALID, // The source of the gesture was invalid. + TOUCH_SEQUENCE_START, // The start of a new gesture sequence. + TOUCH_SEQUENCE_END, // The end of a gesture sequence. + TOUCH_SEQUENCE_CANCEL, // The gesture sequence was cancelled. + TOUCH_START, // A touch down occured during a gesture sequence. + TOUCH_MOVE, // A touch move occured during a gesture sequence. + TOUCH_END, // A touch up occured during a gesture sequence. + TOUCH_TIMEOUT, // Timeout from an existing gesture sequence. + }; + + GestureEventDataPacket(); + GestureEventDataPacket(const GestureEventDataPacket& other); + ~GestureEventDataPacket(); + GestureEventDataPacket& operator=(const GestureEventDataPacket& other); + + // Factory methods for creating a packet from a particular event. + static GestureEventDataPacket FromTouch(const ui::MotionEvent& touch); + static GestureEventDataPacket FromTouchTimeout( + const GestureEventData& gesture); + + void Push(const GestureEventData& gesture); + + const base::TimeTicks& timestamp() const { return timestamp_; } + const GestureEventData& gesture(size_t i) const { return gestures_[i]; } + size_t gesture_count() const { return gestures_.size(); } + GestureSource gesture_source() const { return gesture_source_; } + const gfx::PointF& touch_location() const { return touch_location_; } + const gfx::PointF& raw_touch_location() const { return raw_touch_location_; } + + private: + GestureEventDataPacket(base::TimeTicks timestamp, + GestureSource source, + const gfx::PointF& touch_location, + const gfx::PointF& raw_touch_location); + + base::TimeTicks timestamp_; + // TODO(jdduke): This vector is in general very short. Optimize? + std::vector<GestureEventData> gestures_; + gfx::PointF touch_location_; + gfx::PointF raw_touch_location_; + GestureSource gesture_source_; +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_provider.cc b/chromium/ui/events/gesture_detection/gesture_provider.cc new file mode 100644 index 00000000000..0255ac74bac --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_provider.cc @@ -0,0 +1,810 @@ +// Copyright 2014 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/gesture_detection/gesture_provider.h" + +#include <cmath> + +#include "base/auto_reset.h" +#include "base/debug/trace_event.h" +#include "ui/events/event_constants.h" +#include "ui/events/gesture_detection/gesture_event_data.h" +#include "ui/events/gesture_detection/motion_event.h" + +namespace ui { +namespace { + +// Double-tap drag zoom sensitivity (speed). +const float kDoubleTapDragZoomSpeed = 0.005f; + +const char* GetMotionEventActionName(MotionEvent::Action action) { + switch(action) { + case MotionEvent::ACTION_POINTER_DOWN: return "ACTION_POINTER_DOWN"; + case MotionEvent::ACTION_POINTER_UP: return "ACTION_POINTER_UP"; + case MotionEvent::ACTION_DOWN: return "ACTION_DOWN"; + case MotionEvent::ACTION_UP: return "ACTION_UP"; + case MotionEvent::ACTION_CANCEL: return "ACTION_CANCEL"; + case MotionEvent::ACTION_MOVE: return "ACTION_MOVE"; + } + return ""; +} + +gfx::RectF GetBoundingBox(const MotionEvent& event) { + gfx::RectF bounds; + for (size_t i = 0; i < event.GetPointerCount(); ++i) { + float diameter = event.GetTouchMajor(i); + bounds.Union(gfx::RectF(event.GetX(i) - diameter / 2, + event.GetY(i) - diameter / 2, + diameter, + diameter)); + } + return bounds; +} + +GestureEventData CreateGesture(const GestureEventDetails& details, + int motion_event_id, + base::TimeTicks time, + float x, + float y, + float raw_x, + float raw_y, + size_t touch_point_count, + const gfx::RectF& bounding_box) { + return GestureEventData(details, + motion_event_id, + time, + x, + y, + raw_x, + raw_y, + touch_point_count, + bounding_box); +} + +GestureEventData CreateGesture(EventType type, + int motion_event_id, + base::TimeTicks time, + float x, + float y, + float raw_x, + float raw_y, + size_t touch_point_count, + const gfx::RectF& bounding_box) { + return GestureEventData(GestureEventDetails(type, 0, 0), + motion_event_id, + time, + x, + y, + raw_x, + raw_y, + touch_point_count, + bounding_box); +} + +GestureEventData CreateGesture(const GestureEventDetails& details, + const MotionEvent& event) { + return GestureEventData(details, + event.GetId(), + event.GetEventTime(), + event.GetX(), + event.GetY(), + event.GetRawX(), + event.GetRawY(), + event.GetPointerCount(), + GetBoundingBox(event)); +} + +GestureEventData CreateGesture(EventType type, const MotionEvent& event) { + return CreateGesture(GestureEventDetails(type, 0, 0), event); +} + +GestureEventDetails CreateTapGestureDetails(EventType type) { + // Set the tap count to 1 even for ET_GESTURE_DOUBLE_TAP, in order to be + // consistent with double tap behavior on a mobile viewport. See + // crbug.com/234986 for context. + GestureEventDetails tap_details(type, 1, 0); + return tap_details; +} + +} // namespace + +// GestureProvider:::Config + +GestureProvider::Config::Config() + : display(gfx::Display::kInvalidDisplayID, gfx::Rect(1, 1)), + disable_click_delay(false), + gesture_begin_end_types_enabled(false), + min_gesture_bounds_length(0) {} + +GestureProvider::Config::~Config() {} + +// GestureProvider::ScaleGestureListener + +class GestureProvider::ScaleGestureListenerImpl + : public ScaleGestureDetector::ScaleGestureListener { + public: + ScaleGestureListenerImpl(const ScaleGestureDetector::Config& config, + GestureProvider* provider) + : scale_gesture_detector_(config, this), + provider_(provider), + ignore_multitouch_events_(false), + pinch_event_sent_(false), + min_pinch_update_span_delta_(config.min_pinch_update_span_delta) {} + + bool OnTouchEvent(const MotionEvent& event) { + // TODO: Need to deal with multi-touch transition. + const bool in_scale_gesture = IsScaleGestureDetectionInProgress(); + bool handled = scale_gesture_detector_.OnTouchEvent(event); + if (!in_scale_gesture && + (event.GetAction() == MotionEvent::ACTION_UP || + event.GetAction() == MotionEvent::ACTION_CANCEL)) { + return false; + } + return handled; + } + + // ScaleGestureDetector::ScaleGestureListener implementation. + virtual bool OnScaleBegin(const ScaleGestureDetector& detector, + const MotionEvent& e) OVERRIDE { + if (ignore_multitouch_events_ && !detector.InDoubleTapMode()) + return false; + pinch_event_sent_ = false; + return true; + } + + virtual void OnScaleEnd(const ScaleGestureDetector& detector, + const MotionEvent& e) OVERRIDE { + if (!pinch_event_sent_) + return; + provider_->Send(CreateGesture(ET_GESTURE_PINCH_END, e)); + pinch_event_sent_ = false; + } + + virtual bool OnScale(const ScaleGestureDetector& detector, + const MotionEvent& e) OVERRIDE { + if (ignore_multitouch_events_ && !detector.InDoubleTapMode()) + return false; + if (!pinch_event_sent_) { + pinch_event_sent_ = true; + provider_->Send(CreateGesture(ET_GESTURE_PINCH_BEGIN, + e.GetId(), + detector.GetEventTime(), + detector.GetFocusX(), + detector.GetFocusY(), + detector.GetFocusX() + e.GetRawOffsetX(), + detector.GetFocusY() + e.GetRawOffsetY(), + e.GetPointerCount(), + GetBoundingBox(e))); + } + + if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) < + min_pinch_update_span_delta_) { + return false; + } + + float scale = detector.GetScaleFactor(); + if (scale == 1) + return true; + + if (detector.InDoubleTapMode()) { + // Relative changes in the double-tap scale factor computed by |detector| + // diminish as the touch moves away from the original double-tap focus. + // For historical reasons, Chrome has instead adopted a scale factor + // computation that is invariant to the focal distance, where + // the scale delta remains constant if the touch velocity is constant. + float dy = + (detector.GetCurrentSpanY() - detector.GetPreviousSpanY()) * 0.5f; + scale = std::pow(scale > 1 ? 1.0f + kDoubleTapDragZoomSpeed + : 1.0f - kDoubleTapDragZoomSpeed, + std::abs(dy)); + } + GestureEventDetails pinch_details(ET_GESTURE_PINCH_UPDATE, scale, 0); + provider_->Send(CreateGesture(pinch_details, + e.GetId(), + detector.GetEventTime(), + detector.GetFocusX(), + detector.GetFocusY(), + detector.GetFocusX() + e.GetRawOffsetX(), + detector.GetFocusY() + e.GetRawOffsetY(), + e.GetPointerCount(), + GetBoundingBox(e))); + return true; + } + + void SetDoubleTapEnabled(bool enabled) { + DCHECK(!IsDoubleTapInProgress()); + scale_gesture_detector_.SetQuickScaleEnabled(enabled); + } + + void SetMultiTouchEnabled(bool enabled) { + // Note that returning false from OnScaleBegin / OnScale makes the + // gesture detector not to emit further scaling notifications + // related to this gesture. Thus, if detector events are enabled in + // the middle of the gesture, we don't need to do anything. + ignore_multitouch_events_ = !enabled; + } + + bool IsDoubleTapInProgress() const { + return IsScaleGestureDetectionInProgress() && InDoubleTapMode(); + } + + bool IsScaleGestureDetectionInProgress() const { + return scale_gesture_detector_.IsInProgress(); + } + + private: + bool InDoubleTapMode() const { + return scale_gesture_detector_.InDoubleTapMode(); + } + + ScaleGestureDetector scale_gesture_detector_; + + GestureProvider* const provider_; + + // Completely silence multi-touch (pinch) scaling events. Used in WebView when + // zoom support is turned off. + bool ignore_multitouch_events_; + + // Whether any pinch zoom event has been sent to native. + bool pinch_event_sent_; + + // The minimum change in span required before this is considered a pinch. See + // crbug.com/373318. + float min_pinch_update_span_delta_; + + DISALLOW_COPY_AND_ASSIGN(ScaleGestureListenerImpl); +}; + +// GestureProvider::GestureListener + +class GestureProvider::GestureListenerImpl + : public GestureDetector::GestureListener, + public GestureDetector::DoubleTapListener { + public: + GestureListenerImpl( + const gfx::Display& display, + const GestureDetector::Config& gesture_detector_config, + bool disable_click_delay, + GestureProvider* provider) + : gesture_detector_(gesture_detector_config, this, this), + snap_scroll_controller_(display), + provider_(provider), + disable_click_delay_(disable_click_delay), + touch_slop_(gesture_detector_config.touch_slop), + double_tap_timeout_(gesture_detector_config.double_tap_timeout), + ignore_single_tap_(false), + seen_first_scroll_event_(false) {} + + virtual ~GestureListenerImpl() {} + + bool OnTouchEvent(const MotionEvent& e, + bool is_scale_gesture_detection_in_progress) { + snap_scroll_controller_.SetSnapScrollingMode( + e, is_scale_gesture_detection_in_progress); + + if (is_scale_gesture_detection_in_progress) + SetIgnoreSingleTap(true); + + if (e.GetAction() == MotionEvent::ACTION_DOWN) + gesture_detector_.set_longpress_enabled(true); + + return gesture_detector_.OnTouchEvent(e); + } + + // GestureDetector::GestureListener implementation. + virtual bool OnDown(const MotionEvent& e) OVERRIDE { + current_down_time_ = e.GetEventTime(); + ignore_single_tap_ = false; + seen_first_scroll_event_ = false; + + GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN, 0, 0); + provider_->Send(CreateGesture(tap_details, e)); + + // Return true to indicate that we want to handle touch. + return true; + } + + virtual bool OnScroll(const MotionEvent& e1, + const MotionEvent& e2, + float raw_distance_x, + float raw_distance_y) OVERRIDE { + float distance_x = raw_distance_x; + float distance_y = raw_distance_y; + if (!seen_first_scroll_event_) { + // Remove the touch slop region from the first scroll event to avoid a + // jump. + seen_first_scroll_event_ = true; + double distance = + std::sqrt(distance_x * distance_x + distance_y * distance_y); + double epsilon = 1e-3; + if (distance > epsilon) { + double ratio = std::max(0., distance - touch_slop_) / distance; + distance_x *= ratio; + distance_y *= ratio; + } + } + snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y); + if (snap_scroll_controller_.IsSnappingScrolls()) { + if (snap_scroll_controller_.IsSnapHorizontal()) { + distance_y = 0; + } else { + distance_x = 0; + } + } + + if (!provider_->IsScrollInProgress()) { + // Note that scroll start hints are in distance traveled, where + // scroll deltas are in the opposite direction. + GestureEventDetails scroll_details( + ET_GESTURE_SCROLL_BEGIN, -raw_distance_x, -raw_distance_y); + + // Use the co-ordinates from the touch down, as these co-ordinates are + // used to determine which layer the scroll should affect. + provider_->Send(CreateGesture(scroll_details, + e2.GetId(), + e2.GetEventTime(), + e1.GetX(), + e1.GetY(), + e1.GetRawX(), + e1.GetRawY(), + e2.GetPointerCount(), + GetBoundingBox(e2))); + } + + if (distance_x || distance_y) { + const gfx::RectF bounding_box = GetBoundingBox(e2); + const gfx::PointF center = bounding_box.CenterPoint(); + const gfx::PointF raw_center = + center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY()); + GestureEventDetails scroll_details( + ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y); + provider_->Send(CreateGesture(scroll_details, + e2.GetId(), + e2.GetEventTime(), + center.x(), + center.y(), + raw_center.x(), + raw_center.y(), + e2.GetPointerCount(), + bounding_box)); + } + + return true; + } + + virtual bool OnFling(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) OVERRIDE { + if (snap_scroll_controller_.IsSnappingScrolls()) { + if (snap_scroll_controller_.IsSnapHorizontal()) { + velocity_y = 0; + } else { + velocity_x = 0; + } + } + + provider_->Fling(e2, velocity_x, velocity_y); + return true; + } + + virtual bool OnSwipe(const MotionEvent& e1, + const MotionEvent& e2, + float velocity_x, + float velocity_y) OVERRIDE { + GestureEventDetails swipe_details(ET_GESTURE_SWIPE, velocity_x, velocity_y); + provider_->Send(CreateGesture(swipe_details, e2)); + return true; + } + + virtual bool OnTwoFingerTap(const MotionEvent& e1, + const MotionEvent& e2) OVERRIDE { + // The location of the two finger tap event should be the location of the + // primary pointer. + GestureEventDetails two_finger_tap_details(ET_GESTURE_TWO_FINGER_TAP, + e1.GetTouchMajor(), + e1.GetTouchMajor()); + provider_->Send(CreateGesture(two_finger_tap_details, + e2.GetId(), + e2.GetEventTime(), + e1.GetX(), + e1.GetY(), + e1.GetRawX(), + e1.GetRawY(), + e2.GetPointerCount(), + GetBoundingBox(e2))); + return true; + } + + virtual void OnShowPress(const MotionEvent& e) OVERRIDE { + GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS, 0, 0); + provider_->Send(CreateGesture(show_press_details, e)); + } + + virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE { + // This is a hack to address the issue where user hovers + // over a link for longer than double_tap_timeout_, then + // OnSingleTapConfirmed() is not triggered. But we still + // want to trigger the tap event at UP. So we override + // OnSingleTapUp() in this case. This assumes singleTapUp + // gets always called before singleTapConfirmed. + if (!ignore_single_tap_) { + if (e.GetEventTime() - current_down_time_ > double_tap_timeout_) { + return OnSingleTapConfirmed(e); + } else if (!IsDoubleTapEnabled() || disable_click_delay_) { + // If double-tap has been disabled, there is no need to wait + // for the double-tap timeout. + return OnSingleTapConfirmed(e); + } else { + // Notify Blink about this tapUp event anyway, when none of the above + // conditions applied. + provider_->Send(CreateGesture( + CreateTapGestureDetails(ET_GESTURE_TAP_UNCONFIRMED), e)); + } + } + + return provider_->SendLongTapIfNecessary(e); + } + + // GestureDetector::DoubleTapListener implementation. + virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE { + // Long taps in the edges of the screen have their events delayed by + // ContentViewHolder for tab swipe operations. As a consequence of the delay + // this method might be called after receiving the up event. + // These corner cases should be ignored. + if (ignore_single_tap_) + return true; + + ignore_single_tap_ = true; + + provider_->Send(CreateGesture(CreateTapGestureDetails(ET_GESTURE_TAP), e)); + return true; + } + + virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE { return false; } + + virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE { + switch (e.GetAction()) { + case MotionEvent::ACTION_DOWN: + gesture_detector_.set_longpress_enabled(false); + break; + + case MotionEvent::ACTION_UP: + if (!provider_->IsPinchInProgress() && + !provider_->IsScrollInProgress()) { + provider_->Send( + CreateGesture(CreateTapGestureDetails(ET_GESTURE_DOUBLE_TAP), e)); + return true; + } + break; + default: + break; + } + return false; + } + + virtual void OnLongPress(const MotionEvent& e) OVERRIDE { + DCHECK(!IsDoubleTapInProgress()); + SetIgnoreSingleTap(true); + + GestureEventDetails long_press_details(ET_GESTURE_LONG_PRESS, 0, 0); + provider_->Send(CreateGesture(long_press_details, e)); + } + + void SetDoubleTapEnabled(bool enabled) { + DCHECK(!IsDoubleTapInProgress()); + gesture_detector_.SetDoubleTapListener(enabled ? this : NULL); + } + + bool IsDoubleTapInProgress() const { + return gesture_detector_.is_double_tapping(); + } + + private: + void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; } + + bool IsDoubleTapEnabled() const { + return gesture_detector_.has_doubletap_listener(); + } + + GestureDetector gesture_detector_; + SnapScrollController snap_scroll_controller_; + + GestureProvider* const provider_; + + // Whether the click delay should always be disabled by sending clicks for + // double-tap gestures. + const bool disable_click_delay_; + + const float touch_slop_; + + const base::TimeDelta double_tap_timeout_; + + base::TimeTicks current_down_time_; + + // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch, + // always_in_tap_region_ is not reset. So when the last finger is up, + // OnSingleTapUp() will be mistakenly fired. + bool ignore_single_tap_; + + // Used to remove the touch slop from the initial scroll event in a scroll + // gesture. + bool seen_first_scroll_event_; + + DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl); +}; + +// GestureProvider + +GestureProvider::GestureProvider(const Config& config, + GestureProviderClient* client) + : client_(client), + touch_scroll_in_progress_(false), + pinch_in_progress_(false), + double_tap_support_for_page_(true), + double_tap_support_for_platform_(true), + gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled), + min_gesture_bounds_length_(config.min_gesture_bounds_length) { + DCHECK(client); + InitGestureDetectors(config); +} + +GestureProvider::~GestureProvider() {} + +bool GestureProvider::OnTouchEvent(const MotionEvent& event) { + TRACE_EVENT1("input", "GestureProvider::OnTouchEvent", + "action", GetMotionEventActionName(event.GetAction())); + + DCHECK_NE(0u, event.GetPointerCount()); + + if (!CanHandle(event)) + return false; + + const bool in_scale_gesture = + scale_gesture_listener_->IsScaleGestureDetectionInProgress(); + + OnTouchEventHandlingBegin(event); + gesture_listener_->OnTouchEvent(event, in_scale_gesture); + scale_gesture_listener_->OnTouchEvent(event); + OnTouchEventHandlingEnd(event); + return true; +} + +void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) { + scale_gesture_listener_->SetMultiTouchEnabled(enabled); +} + +void GestureProvider::SetDoubleTapSupportForPlatformEnabled(bool enabled) { + if (double_tap_support_for_platform_ == enabled) + return; + double_tap_support_for_platform_ = enabled; + UpdateDoubleTapDetectionSupport(); +} + +void GestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) { + if (double_tap_support_for_page_ == enabled) + return; + double_tap_support_for_page_ = enabled; + UpdateDoubleTapDetectionSupport(); +} + +bool GestureProvider::IsScrollInProgress() const { + // TODO(wangxianzhu): Also return true when fling is active once the UI knows + // exactly when the fling ends. + return touch_scroll_in_progress_; +} + +bool GestureProvider::IsPinchInProgress() const { return pinch_in_progress_; } + +bool GestureProvider::IsDoubleTapInProgress() const { + return gesture_listener_->IsDoubleTapInProgress() || + scale_gesture_listener_->IsDoubleTapInProgress(); +} + +void GestureProvider::InitGestureDetectors(const Config& config) { + TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors"); + gesture_listener_.reset( + new GestureListenerImpl(config.display, + config.gesture_detector_config, + config.disable_click_delay, + this)); + + scale_gesture_listener_.reset( + new ScaleGestureListenerImpl(config.scale_gesture_detector_config, this)); + + UpdateDoubleTapDetectionSupport(); +} + +bool GestureProvider::CanHandle(const MotionEvent& event) const { + return event.GetAction() == MotionEvent::ACTION_DOWN || current_down_event_; +} + +void GestureProvider::Fling(const MotionEvent& event, + float velocity_x, + float velocity_y) { + if (!velocity_x && !velocity_y) { + EndTouchScrollIfNecessary(event, true); + return; + } + + if (!touch_scroll_in_progress_) { + // The native side needs a ET_GESTURE_SCROLL_BEGIN before + // ET_SCROLL_FLING_START to send the fling to the correct target. Send if it + // has not sent. The distance traveled in one second is a reasonable scroll + // start hint. + GestureEventDetails scroll_details( + ET_GESTURE_SCROLL_BEGIN, velocity_x, velocity_y); + Send(CreateGesture(scroll_details, event)); + } + EndTouchScrollIfNecessary(event, false); + + GestureEventDetails fling_details( + ET_SCROLL_FLING_START, velocity_x, velocity_y); + Send(CreateGesture(fling_details, event)); +} + +void GestureProvider::Send(GestureEventData gesture) { + DCHECK(!gesture.time.is_null()); + // The only valid events that should be sent without an active touch sequence + // are SHOW_PRESS and TAP, potentially triggered by the double-tap + // delay timing out. + DCHECK(current_down_event_ || gesture.type() == ET_GESTURE_TAP || + gesture.type() == ET_GESTURE_SHOW_PRESS); + + // TODO(jdduke): Provide a way of skipping this clamping for stylus and/or + // mouse-based input, perhaps by exposing the source type on MotionEvent. + const gfx::RectF& gesture_bounds = gesture.details.bounding_box_f(); + gesture.details.set_bounding_box(gfx::RectF( + gesture_bounds.x(), + gesture_bounds.y(), + std::max(min_gesture_bounds_length_, gesture_bounds.width()), + std::max(min_gesture_bounds_length_, gesture_bounds.height()))); + + switch (gesture.type()) { + case ET_GESTURE_LONG_PRESS: + DCHECK(!scale_gesture_listener_->IsScaleGestureDetectionInProgress()); + current_longpress_time_ = gesture.time; + break; + case ET_GESTURE_LONG_TAP: + current_longpress_time_ = base::TimeTicks(); + break; + case ET_GESTURE_SCROLL_BEGIN: + DCHECK(!touch_scroll_in_progress_); + touch_scroll_in_progress_ = true; + break; + case ET_GESTURE_SCROLL_END: + DCHECK(touch_scroll_in_progress_); + if (pinch_in_progress_) + Send(GestureEventData(ET_GESTURE_PINCH_END, gesture)); + touch_scroll_in_progress_ = false; + break; + case ET_GESTURE_PINCH_BEGIN: + DCHECK(!pinch_in_progress_); + if (!touch_scroll_in_progress_) + Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture)); + pinch_in_progress_ = true; + break; + case ET_GESTURE_PINCH_END: + DCHECK(pinch_in_progress_); + pinch_in_progress_ = false; + break; + case ET_GESTURE_SHOW_PRESS: + // It's possible that a double-tap drag zoom (from ScaleGestureDetector) + // will start before the press gesture fires (from GestureDetector), in + // which case the press should simply be dropped. + if (pinch_in_progress_ || touch_scroll_in_progress_) + return; + default: + break; + }; + + client_->OnGestureEvent(gesture); +} + +bool GestureProvider::SendLongTapIfNecessary(const MotionEvent& event) { + if (event.GetAction() == MotionEvent::ACTION_UP && + !current_longpress_time_.is_null() && + !scale_gesture_listener_->IsScaleGestureDetectionInProgress()) { + GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP, 0, 0); + Send(CreateGesture(long_tap_details, event)); + return true; + } + return false; +} + +void GestureProvider::EndTouchScrollIfNecessary(const MotionEvent& event, + bool send_scroll_end_event) { + if (!touch_scroll_in_progress_) + return; + if (send_scroll_end_event) + Send(CreateGesture(ET_GESTURE_SCROLL_END, event)); + touch_scroll_in_progress_ = false; +} + +void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) { + switch (event.GetAction()) { + case MotionEvent::ACTION_DOWN: + current_down_event_ = event.Clone(); + touch_scroll_in_progress_ = false; + pinch_in_progress_ = false; + current_longpress_time_ = base::TimeTicks(); + if (gesture_begin_end_types_enabled_) + Send(CreateGesture(ET_GESTURE_BEGIN, event)); + break; + case MotionEvent::ACTION_POINTER_DOWN: + if (gesture_begin_end_types_enabled_) { + const int action_index = event.GetActionIndex(); + Send(CreateGesture(ET_GESTURE_BEGIN, + event.GetId(), + event.GetEventTime(), + event.GetX(action_index), + event.GetY(action_index), + event.GetRawX(action_index), + event.GetRawY(action_index), + event.GetPointerCount(), + GetBoundingBox(event))); + } + break; + case MotionEvent::ACTION_POINTER_UP: + case MotionEvent::ACTION_UP: + case MotionEvent::ACTION_CANCEL: + case MotionEvent::ACTION_MOVE: + break; + } +} + +void GestureProvider::OnTouchEventHandlingEnd(const MotionEvent& event) { + switch (event.GetAction()) { + case MotionEvent::ACTION_UP: + case MotionEvent::ACTION_CANCEL: { + // Note: This call will have no effect if a fling was just generated, as + // |Fling()| will have already signalled an end to touch-scrolling. + EndTouchScrollIfNecessary(event, true); + + const gfx::RectF bounding_box = GetBoundingBox(event); + + if (gesture_begin_end_types_enabled_) { + for (size_t i = 0; i < event.GetPointerCount(); ++i) { + Send(CreateGesture(ET_GESTURE_END, + event.GetId(), + event.GetEventTime(), + event.GetX(i), + event.GetY(i), + event.GetRawX(i), + event.GetRawY(i), + event.GetPointerCount() - i, + bounding_box)); + } + } + + current_down_event_.reset(); + + UpdateDoubleTapDetectionSupport(); + break; + } + case MotionEvent::ACTION_POINTER_UP: + if (gesture_begin_end_types_enabled_) + Send(CreateGesture(ET_GESTURE_END, event)); + break; + case MotionEvent::ACTION_DOWN: + case MotionEvent::ACTION_POINTER_DOWN: + case MotionEvent::ACTION_MOVE: + break; + } +} + +void GestureProvider::UpdateDoubleTapDetectionSupport() { + // The GestureDetector requires that any provided DoubleTapListener remain + // attached to it for the duration of a touch sequence. Defer any potential + // null'ing of the listener until the sequence has ended. + if (current_down_event_) + return; + + const bool double_tap_enabled = double_tap_support_for_page_ && + double_tap_support_for_platform_; + gesture_listener_->SetDoubleTapEnabled(double_tap_enabled); + scale_gesture_listener_->SetDoubleTapEnabled(double_tap_enabled); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/gesture_provider.h b/chromium/ui/events/gesture_detection/gesture_provider.h new file mode 100644 index 00000000000..e9407c7e699 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_provider.h @@ -0,0 +1,136 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/gesture_detector.h" +#include "ui/events/gesture_detection/gesture_event_data.h" +#include "ui/events/gesture_detection/scale_gesture_detector.h" +#include "ui/events/gesture_detection/snap_scroll_controller.h" +#include "ui/gfx/display.h" + +namespace ui { + +class GESTURE_DETECTION_EXPORT GestureProviderClient { + public: + virtual ~GestureProviderClient() {} + virtual void OnGestureEvent(const GestureEventData& gesture) = 0; +}; + +// Given a stream of |MotionEvent|'s, provides gesture detection and gesture +// event dispatch. +class GESTURE_DETECTION_EXPORT GestureProvider { + public: + struct GESTURE_DETECTION_EXPORT Config { + Config(); + ~Config(); + gfx::Display display; + GestureDetector::Config gesture_detector_config; + ScaleGestureDetector::Config scale_gesture_detector_config; + + // If |disable_click_delay| is true and double-tap support is disabled, + // there will be no delay before tap events. When double-tap support is + // enabled, there will always be a delay before a tap event is fired, in + // order to allow the double tap gesture to occur without firing any tap + // events. + bool disable_click_delay; + + // If |gesture_begin_end_types_enabled| is true, fire an ET_GESTURE_BEGIN + // event for every added touch point, and an ET_GESTURE_END event for every + // removed touch point. Defaults to false. + bool gesture_begin_end_types_enabled; + + // The minimum size (both length and width, in dips) of the generated + // bounding box for all gesture types. This is useful for touch streams + // that may report zero or unreasonably small touch sizes. + // Defaults to 0. + float min_gesture_bounds_length; + }; + + GestureProvider(const Config& config, GestureProviderClient* client); + ~GestureProvider(); + + // Handle the incoming MotionEvent, returning false if the event could not + // be handled. + bool OnTouchEvent(const MotionEvent& event); + + // Update whether multi-touch pinch zoom is supported by the platform. + void SetMultiTouchZoomSupportEnabled(bool enabled); + + // Update whether double-tap gestures are supported by the platform. + void SetDoubleTapSupportForPlatformEnabled(bool enabled); + + // Update whether double-tap gesture detection should be suppressed, e.g., + // if the page scale is fixed or the page has a mobile viewport. This disables + // the tap delay, allowing rapid and responsive single-tap gestures. + void SetDoubleTapSupportForPageEnabled(bool enabled); + + // Whether a scroll gesture is in-progress. + bool IsScrollInProgress() const; + + // Whether a pinch gesture is in-progress (i.e. a pinch update has been + // forwarded and detection is still active). + bool IsPinchInProgress() const; + + // Whether a double-tap gesture is in-progress (either double-tap or + // double-tap drag zoom). + bool IsDoubleTapInProgress() const; + + // May be NULL if there is no currently active touch sequence. + const ui::MotionEvent* current_down_event() const { + return current_down_event_.get(); + } + + private: + void InitGestureDetectors(const Config& config); + + bool CanHandle(const MotionEvent& event) const; + + void Fling(const MotionEvent& e, float velocity_x, float velocity_y); + void Send(GestureEventData gesture); + bool SendLongTapIfNecessary(const MotionEvent& event); + void EndTouchScrollIfNecessary(const MotionEvent& event, + bool send_scroll_end_event); + void OnTouchEventHandlingBegin(const MotionEvent& event); + void OnTouchEventHandlingEnd(const MotionEvent& event); + void UpdateDoubleTapDetectionSupport(); + + GestureProviderClient* const client_; + + class GestureListenerImpl; + friend class GestureListenerImpl; + scoped_ptr<GestureListenerImpl> gesture_listener_; + + class ScaleGestureListenerImpl; + friend class ScaleGestureListenerImpl; + scoped_ptr<ScaleGestureListenerImpl> scale_gesture_listener_; + + scoped_ptr<MotionEvent> current_down_event_; + + // Whether the respective {SCROLL,PINCH}_BEGIN gestures have been terminated + // with a {SCROLL,PINCH}_END. + bool touch_scroll_in_progress_; + bool pinch_in_progress_; + + // Whether double-tap gesture detection is currently supported. + bool double_tap_support_for_page_; + bool double_tap_support_for_platform_; + + // Keeps track of the current GESTURE_LONG_PRESS event. If a context menu is + // opened after a GESTURE_LONG_PRESS, this is used to insert a + // GESTURE_TAP_CANCEL for removing any ::active styling. + base::TimeTicks current_longpress_time_; + + const bool gesture_begin_end_types_enabled_; + + const float min_gesture_bounds_length_; +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_ diff --git a/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc b/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc new file mode 100644 index 00000000000..985aa377999 --- /dev/null +++ b/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc @@ -0,0 +1,2298 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_constants.h" +#include "ui/events/gesture_detection/gesture_event_data.h" +#include "ui/events/gesture_detection/gesture_provider.h" +#include "ui/events/gesture_detection/mock_motion_event.h" +#include "ui/events/gesture_detection/motion_event.h" +#include "ui/gfx/geometry/point_f.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace ui { +namespace { + +const float kFakeCoordX = 42.f; +const float kFakeCoordY = 24.f; +const TimeDelta kOneSecond = TimeDelta::FromSeconds(1); +const TimeDelta kOneMicrosecond = TimeDelta::FromMicroseconds(1); +const TimeDelta kDeltaTimeForFlingSequences = TimeDelta::FromMilliseconds(5); +const float kMockTouchRadius = MockMotionEvent::TOUCH_MAJOR / 2; +const float kMaxTwoFingerTapSeparation = 300; + +GestureProvider::Config CreateDefaultConfig() { + GestureProvider::Config sConfig; + // The longpress timeout is non-zero only to indicate ordering with respect to + // the showpress timeout. + sConfig.gesture_detector_config.showpress_timeout = base::TimeDelta(); + sConfig.gesture_detector_config.longpress_timeout = kOneMicrosecond; + + // A valid doubletap timeout should always be non-zero. The value is used not + // only to trigger the timeout that confirms the tap event, but also to gate + // whether the second tap is in fact a double-tap (using a strict inequality + // between times for the first up and the second down events). We use 4 + // microseconds simply to allow several intermediate events to occur before + // the second tap at microsecond intervals. + sConfig.gesture_detector_config.double_tap_timeout = kOneMicrosecond * 4; + sConfig.gesture_detector_config.double_tap_min_time = kOneMicrosecond * 2; + + sConfig.scale_gesture_detector_config.gesture_detector_config = + sConfig.gesture_detector_config; + return sConfig; +} + +gfx::RectF BoundsForSingleMockTouchAtLocation(float x, float y) { + float diameter = MockMotionEvent::TOUCH_MAJOR; + return gfx::RectF(x - diameter / 2, y - diameter / 2, diameter, diameter); +} + +} // namespace + +class GestureProviderTest : public testing::Test, public GestureProviderClient { + public: + GestureProviderTest() {} + virtual ~GestureProviderTest() {} + + static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time, + MotionEvent::Action action, + float x, + float y) { + return MockMotionEvent(action, event_time, x, y); + } + + static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time, + MotionEvent::Action action, + float x0, + float y0, + float x1, + float y1) { + return MockMotionEvent(action, event_time, x0, y0, x1, y1); + } + + static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time, + MotionEvent::Action action, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) { + return MockMotionEvent(action, event_time, x0, y0, x1, y1, x2, y2); + } + + static MockMotionEvent ObtainMotionEvent( + base::TimeTicks event_time, + MotionEvent::Action action, + const std::vector<gfx::PointF>& positions) { + switch (positions.size()) { + case 1: + return MockMotionEvent( + action, event_time, positions[0].x(), positions[0].y()); + case 2: + return MockMotionEvent(action, + event_time, + positions[0].x(), + positions[0].y(), + positions[1].x(), + positions[1].y()); + case 3: + return MockMotionEvent(action, + event_time, + positions[0].x(), + positions[0].y(), + positions[1].x(), + positions[1].y(), + positions[2].x(), + positions[2].y()); + default: + CHECK(false) << "MockMotionEvent only supports 1-3 pointers"; + return MockMotionEvent(); + } + } + + static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time, + MotionEvent::Action action) { + return ObtainMotionEvent(event_time, action, kFakeCoordX, kFakeCoordY); + } + + // Test + virtual void SetUp() OVERRIDE { SetUpWithConfig(GetDefaultConfig()); } + + virtual void TearDown() OVERRIDE { + gestures_.clear(); + gesture_provider_.reset(); + } + + // GestureProviderClient + virtual void OnGestureEvent(const GestureEventData& gesture) OVERRIDE { + if (gesture.type() == ET_GESTURE_SCROLL_BEGIN) + active_scroll_begin_event_.reset(new GestureEventData(gesture)); + gestures_.push_back(gesture); + } + + void SetUpWithConfig(const GestureProvider::Config& config) { + gesture_provider_.reset(new GestureProvider(config, this)); + gesture_provider_->SetMultiTouchZoomSupportEnabled(false); + } + + void ResetGestureDetection() { + CancelActiveTouchSequence(); + gestures_.clear(); + } + bool CancelActiveTouchSequence() { + if (!gesture_provider_->current_down_event()) + return false; + return gesture_provider_->OnTouchEvent( + *gesture_provider_->current_down_event()->Cancel()); + } + + bool HasReceivedGesture(EventType type) const { + for (size_t i = 0; i < gestures_.size(); ++i) { + if (gestures_[i].type() == type) + return true; + } + return false; + } + + const GestureEventData& GetMostRecentGestureEvent() const { + EXPECT_FALSE(gestures_.empty()); + return gestures_.back(); + } + + EventType GetMostRecentGestureEventType() const { + EXPECT_FALSE(gestures_.empty()); + return gestures_.back().type(); + } + + size_t GetReceivedGestureCount() const { return gestures_.size(); } + + const GestureEventData& GetReceivedGesture(size_t index) const { + EXPECT_LT(index, GetReceivedGestureCount()); + return gestures_[index]; + } + + const GestureEventData* GetActiveScrollBeginEvent() const { + return active_scroll_begin_event_ ? active_scroll_begin_event_.get() : NULL; + } + + const GestureProvider::Config& GetDefaultConfig() const { + static GestureProvider::Config sConfig = CreateDefaultConfig(); + return sConfig; + } + + float GetTouchSlop() const { + return GetDefaultConfig().gesture_detector_config.touch_slop; + } + + float GetMinScalingSpan() const { + return GetDefaultConfig().scale_gesture_detector_config.min_scaling_span; + } + + float GetMinSwipeVelocity() const { + return GetDefaultConfig().gesture_detector_config.minimum_swipe_velocity; + } + + base::TimeDelta GetLongPressTimeout() const { + return GetDefaultConfig().gesture_detector_config.longpress_timeout; + } + + base::TimeDelta GetShowPressTimeout() const { + return GetDefaultConfig().gesture_detector_config.showpress_timeout; + } + + base::TimeDelta GetDoubleTapTimeout() const { + return GetDefaultConfig().gesture_detector_config.double_tap_timeout; + } + + base::TimeDelta GetDoubleTapMinTime() const { + return GetDefaultConfig().gesture_detector_config.double_tap_min_time; + } + + base::TimeDelta GetValidDoubleTapDelay() const { + return (GetDoubleTapTimeout() + GetDoubleTapMinTime()) / 2; + } + + void EnableBeginEndTypes() { + GestureProvider::Config config = GetDefaultConfig(); + config.gesture_begin_end_types_enabled = true; + SetUpWithConfig(config); + } + + void EnableSwipe() { + GestureProvider::Config config = GetDefaultConfig(); + config.gesture_detector_config.swipe_enabled = true; + SetUpWithConfig(config); + } + + void EnableTwoFingerTap(float max_distance_for_two_finger_tap, + base::TimeDelta two_finger_tap_timeout) { + GestureProvider::Config config = GetDefaultConfig(); + config.gesture_detector_config.two_finger_tap_enabled = true; + config.gesture_detector_config.two_finger_tap_max_separation = + max_distance_for_two_finger_tap; + config.gesture_detector_config.two_finger_tap_timeout = + two_finger_tap_timeout; + SetUpWithConfig(config); + } + + void SetMinPinchUpdateSpanDelta(float min_pinch_update_span_delta) { + GestureProvider::Config config = GetDefaultConfig(); + config.scale_gesture_detector_config.min_pinch_update_span_delta = + min_pinch_update_span_delta; + SetUpWithConfig(config); + } + + void SetMinGestureBoundsLength(float min_gesture_bound_length) { + GestureProvider::Config config = GetDefaultConfig(); + config.min_gesture_bounds_length = min_gesture_bound_length; + SetUpWithConfig(config); + } + + bool HasDownEvent() const { return gesture_provider_->current_down_event(); } + + protected: + void CheckScrollEventSequenceForEndActionType( + MotionEvent::Action end_action_type) { + base::TimeTicks event_time = base::TimeTicks::Now(); + const float scroll_to_x = kFakeCoordX + 100; + const float scroll_to_y = kFakeCoordY + 100; + int motion_event_id = 0; + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneSecond, + MotionEvent::ACTION_MOVE, + scroll_to_x, + scroll_to_y); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(gesture_provider_->IsScrollInProgress()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y), + GetMostRecentGestureEvent().details.bounding_box()); + ASSERT_EQ(3U, GetReceivedGestureCount()) << "Only TapDown, " + "ScrollBegin and ScrollBy " + "should have been sent"; + + EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type()); + EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id); + EXPECT_EQ(event_time + kOneSecond, GetReceivedGesture(1).time) + << "ScrollBegin should have the time of the ACTION_MOVE"; + + event = ObtainMotionEvent( + event_time + kOneSecond, end_action_type, scroll_to_x, scroll_to_y); + event.SetId(++motion_event_id); + + gesture_provider_->OnTouchEvent(event); + EXPECT_FALSE(gesture_provider_->IsScrollInProgress()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_END)); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y), + GetMostRecentGestureEvent().details.bounding_box()); + } + + void OneFingerSwipe(float vx, float vy) { + std::vector<gfx::Vector2dF> velocities; + velocities.push_back(gfx::Vector2dF(vx, vy)); + MultiFingerSwipe(velocities); + } + + void TwoFingerSwipe(float vx0, float vy0, float vx1, float vy1) { + std::vector<gfx::Vector2dF> velocities; + velocities.push_back(gfx::Vector2dF(vx0, vy0)); + velocities.push_back(gfx::Vector2dF(vx1, vy1)); + MultiFingerSwipe(velocities); + } + + void ThreeFingerSwipe(float vx0, + float vy0, + float vx1, + float vy1, + float vx2, + float vy2) { + std::vector<gfx::Vector2dF> velocities; + velocities.push_back(gfx::Vector2dF(vx0, vy0)); + velocities.push_back(gfx::Vector2dF(vx1, vy1)); + velocities.push_back(gfx::Vector2dF(vx2, vy2)); + MultiFingerSwipe(velocities); + } + + void MultiFingerSwipe(std::vector<gfx::Vector2dF> velocities) { + ASSERT_GT(velocities.size(), 0U); + + base::TimeTicks event_time = base::TimeTicks::Now(); + + std::vector<gfx::PointF> positions(velocities.size()); + for (size_t i = 0; i < positions.size(); ++i) + positions[i] = gfx::PointF(kFakeCoordX * (i + 1), kFakeCoordY * (i + 1)); + + float dt = kDeltaTimeForFlingSequences.InSecondsF(); + + // Each pointer down should be a separate event. + for (size_t i = 0; i < positions.size(); ++i) { + const size_t pointer_count = i + 1; + std::vector<gfx::PointF> event_positions(pointer_count); + event_positions.assign(positions.begin(), + positions.begin() + pointer_count); + MockMotionEvent event = + ObtainMotionEvent(event_time, + pointer_count > 1 ? MotionEvent::ACTION_POINTER_DOWN + : MotionEvent::ACTION_DOWN, + event_positions); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + } + + for (size_t i = 0; i < positions.size(); ++i) + positions[i] += gfx::ScaleVector2d(velocities[i], dt); + MockMotionEvent event = + ObtainMotionEvent(event_time + kDeltaTimeForFlingSequences, + MotionEvent::ACTION_MOVE, + positions); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + for (size_t i = 0; i < positions.size(); ++i) + positions[i] += gfx::ScaleVector2d(velocities[i], dt); + event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences, + MotionEvent::ACTION_MOVE, + positions); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences, + MotionEvent::ACTION_POINTER_UP, + positions); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + } + + static void RunTasksAndWait(base::TimeDelta delay) { + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::MessageLoop::QuitClosure(), delay); + base::MessageLoop::current()->Run(); + } + + std::vector<GestureEventData> gestures_; + scoped_ptr<GestureProvider> gesture_provider_; + scoped_ptr<GestureEventData> active_scroll_begin_event_; + base::MessageLoopForUI message_loop_; +}; + +// Verify that a DOWN followed shortly by an UP will trigger a single tap. +TEST_F(GestureProviderTest, GestureTap) { + base::TimeTicks event_time = base::TimeTicks::Now(); + int motion_event_id = 0; + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + // Ensure tap details have been set. + EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); +} + +// Verify that a DOWN followed shortly by an UP will trigger +// a ET_GESTURE_TAP_UNCONFIRMED event if double-tap is enabled. +TEST_F(GestureProviderTest, GestureTapWithDelay) { + base::TimeTicks event_time = base::TimeTicks::Now(); + int motion_event_id = 0; + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + // Ensure tap details have been set. + EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_TAP)); + RunTasksAndWait(GetDoubleTapTimeout()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_TAP)); +} + +// Verify that a DOWN followed by a MOVE will trigger fling (but not LONG). +TEST_F(GestureProviderTest, GestureFlingAndCancelLongPress) { + base::TimeTicks event_time = TimeTicks::Now(); + base::TimeDelta delta_time = kDeltaTimeForFlingSequences; + int motion_event_id = 0; + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(event_time + delta_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX * 10, + kFakeCoordY * 10); + event.SetId(++motion_event_id); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + delta_time * 2, + MotionEvent::ACTION_UP, + kFakeCoordX * 10, + kFakeCoordY * 10); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS)); + EXPECT_EQ( + BoundsForSingleMockTouchAtLocation(kFakeCoordX * 10, kFakeCoordY * 10), + GetMostRecentGestureEvent().details.bounding_box()); +} + +// Verify that for a normal scroll the following events are sent: +// - ET_GESTURE_SCROLL_BEGIN +// - ET_GESTURE_SCROLL_UPDATE +// - ET_GESTURE_SCROLL_END +TEST_F(GestureProviderTest, ScrollEventActionUpSequence) { + CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_UP); +} + +// Verify that for a cancelled scroll the following events are sent: +// - ET_GESTURE_SCROLL_BEGIN +// - ET_GESTURE_SCROLL_UPDATE +// - ET_GESTURE_SCROLL_END +TEST_F(GestureProviderTest, ScrollEventActionCancelSequence) { + CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_CANCEL); +} + +// Verify that for a normal fling (fling after scroll) the following events are +// sent: +// - ET_GESTURE_SCROLL_BEGIN +// - ET_SCROLL_FLING_START +TEST_F(GestureProviderTest, FlingEventSequence) { + base::TimeTicks event_time = base::TimeTicks::Now(); + base::TimeDelta delta_time = kDeltaTimeForFlingSequences; + int motion_event_id = 0; + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + delta_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX * 5, + kFakeCoordY * 5); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(gesture_provider_->IsScrollInProgress()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + ASSERT_EQ(3U, GetReceivedGestureCount()); + ASSERT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type()); + EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id); + + // We don't want to take a dependency here on exactly how hints are calculated + // for a fling (eg. may depend on velocity), so just validate the direction. + int hint_x = GetReceivedGesture(1).details.scroll_x_hint(); + int hint_y = GetReceivedGesture(1).details.scroll_y_hint(); + EXPECT_TRUE(hint_x > 0 && hint_y > 0 && hint_x > hint_y) + << "ScrollBegin hint should be in positive X axis"; + + event = ObtainMotionEvent(event_time + delta_time * 2, + MotionEvent::ACTION_UP, + kFakeCoordX * 10, + kFakeCoordY * 10); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(gesture_provider_->IsScrollInProgress()); + EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END)); + EXPECT_EQ(event_time + delta_time * 2, GetMostRecentGestureEvent().time) + << "FlingStart should have the time of the ACTION_UP"; +} + +TEST_F(GestureProviderTest, GestureCancelledWhenWindowFocusLost) { + const base::TimeTicks event_time = TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + + RunTasksAndWait(GetLongPressTimeout() + GetShowPressTimeout() + + kOneMicrosecond); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SHOW_PRESS)); + EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType()); + + // The long press triggers window focus loss by opening a context menu. + EXPECT_TRUE(CancelActiveTouchSequence()); + EXPECT_FALSE(HasDownEvent()); + + // A final ACTION_UP should have no effect. + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_UP); + EXPECT_FALSE(gesture_provider_->OnTouchEvent(event)); +} + +TEST_F(GestureProviderTest, NoTapAfterScrollBegins) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX + 50, + kFakeCoordY + 50); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + + event = ObtainMotionEvent(event_time + kOneSecond, + MotionEvent::ACTION_UP, + kFakeCoordX + 50, + kFakeCoordY + 50); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP)); +} + +TEST_F(GestureProviderTest, DoubleTap) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event_time += GetValidDoubleTapDelay(); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // Moving a very small amount of distance should not trigger the double tap + // drag zoom mode. + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 1); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 1); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + const GestureEventData& double_tap = GetMostRecentGestureEvent(); + EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, double_tap.type()); + // Ensure tap details have been set. + EXPECT_EQ(10, double_tap.details.bounding_box().width()); + EXPECT_EQ(10, double_tap.details.bounding_box().height()); + EXPECT_EQ(1, double_tap.details.tap_count()); +} + +TEST_F(GestureProviderTest, DoubleTapDragZoomBasic) { + const base::TimeTicks down_time_1 = TimeTicks::Now(); + const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay(); + + MockMotionEvent event = + ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent( + down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + ASSERT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType()); + EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 200), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType()); + EXPECT_GT(1.f, GetMostRecentGestureEvent().details.scale()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 4, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY - 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END)); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY - 200), + GetMostRecentGestureEvent().details.bounding_box()); +} + +// Generate a scroll gesture and verify that the resulting scroll motion event +// has both absolute and relative position information. +TEST_F(GestureProviderTest, ScrollUpdateValues) { + const float delta_x = 16; + const float delta_y = 84; + const float raw_offset_x = 17.3f; + const float raw_offset_y = 13.7f; + + const base::TimeTicks event_time = TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + // Move twice so that we get two ET_GESTURE_SCROLL_UPDATE events and can + // compare the relative and absolute coordinates. + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX - delta_x / 2, + kFakeCoordY - delta_y / 2); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX - delta_x, + kFakeCoordY - delta_y); + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + // Make sure the reported gesture event has all the expected details. + ASSERT_LT(0U, GetReceivedGestureCount()); + GestureEventData gesture = GetMostRecentGestureEvent(); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type()); + EXPECT_EQ(event_time + kOneMicrosecond * 2, gesture.time); + EXPECT_EQ(kFakeCoordX - delta_x, gesture.x); + EXPECT_EQ(kFakeCoordY - delta_y, gesture.y); + EXPECT_EQ(kFakeCoordX - delta_x + raw_offset_x, gesture.raw_x); + EXPECT_EQ(kFakeCoordY - delta_y + raw_offset_y, gesture.raw_y); + EXPECT_EQ(1, gesture.details.touch_points()); + + // No horizontal delta because of snapping. + EXPECT_EQ(0, gesture.details.scroll_x()); + EXPECT_EQ(-delta_y / 2, gesture.details.scroll_y()); +} + +// Verify that fractional scroll deltas are rounded as expected and that +// fractional scrolling doesn't break scroll snapping. +TEST_F(GestureProviderTest, FractionalScroll) { + const float delta_x = 0.4f; + const float delta_y = 5.2f; + + const base::TimeTicks event_time = TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + // Skip past the touch slop and move back. + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + // Now move up slowly, mostly vertically but with a (fractional) bit of + // horizontal motion. + for(int i = 1; i <= 10; i++) { + event = ObtainMotionEvent(event_time + kOneMicrosecond * i, + MotionEvent::ACTION_MOVE, + kFakeCoordX + delta_x * i, + kFakeCoordY + delta_y * i); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + ASSERT_LT(0U, GetReceivedGestureCount()); + GestureEventData gesture = GetMostRecentGestureEvent(); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type()); + EXPECT_EQ(event_time + kOneMicrosecond * i, gesture.time); + EXPECT_EQ(1, gesture.details.touch_points()); + + // Verify that the event co-ordinates are still the precise values we + // supplied. + EXPECT_EQ(kFakeCoordX + delta_x * i, gesture.x); + EXPECT_EQ(kFakeCoordY + delta_y * i, gesture.y); + + // Verify that we're scrolling vertically by the expected amount + // (modulo rounding). + EXPECT_GE(gesture.details.scroll_y(), (int)delta_y); + EXPECT_LE(gesture.details.scroll_y(), ((int)delta_y) + 1); + + // And that there has been no horizontal motion at all. + EXPECT_EQ(0, gesture.details.scroll_x()); + } +} + +// Generate a scroll gesture and verify that the resulting scroll begin event +// has the expected hint values. +TEST_F(GestureProviderTest, ScrollBeginValues) { + const float delta_x = 13; + const float delta_y = 89; + + const base::TimeTicks event_time = TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + // Move twice such that the first event isn't sufficient to start + // scrolling on it's own. + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX + 2, + kFakeCoordY + 1); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(gesture_provider_->IsScrollInProgress()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX + delta_x, + kFakeCoordY + delta_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(gesture_provider_->IsScrollInProgress()); + + const GestureEventData* scroll_begin_gesture = GetActiveScrollBeginEvent(); + ASSERT_TRUE(!!scroll_begin_gesture); + EXPECT_EQ(delta_x, scroll_begin_gesture->details.scroll_x_hint()); + EXPECT_EQ(delta_y, scroll_begin_gesture->details.scroll_y_hint()); +} + +TEST_F(GestureProviderTest, LongPressAndTapCancelledWhenScrollBegins) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX * 5, + kFakeCoordY * 5); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX * 10, + kFakeCoordY * 10); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + + // No LONG_TAP as the LONG_PRESS timer is cancelled. + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP)); +} + +// Verify that LONG_TAP is triggered after LONG_PRESS followed by an UP. +TEST_F(GestureProviderTest, GestureLongTap) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + + EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(event_time + kOneSecond, MotionEvent::ACTION_UP); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_LONG_TAP, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); +} + +TEST_F(GestureProviderTest, GestureLongPressDoesNotPreventScrolling) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + + EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + event = ObtainMotionEvent(event_time + long_press_timeout, + MotionEvent::ACTION_MOVE, + kFakeCoordX + 100, + kFakeCoordY + 100); + gesture_provider_->OnTouchEvent(event); + + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + + event = ObtainMotionEvent(event_time + long_press_timeout, + MotionEvent::ACTION_UP); + gesture_provider_->OnTouchEvent(event); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP)); +} + +TEST_F(GestureProviderTest, NoGestureLongPressDuringDoubleTap) { + base::TimeTicks event_time = base::TimeTicks::Now(); + int motion_event_id = 0; + + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event_time += GetValidDoubleTapDelay(); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress()); + + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS)); + + event = ObtainMotionEvent(event_time + long_press_timeout, + MotionEvent::ACTION_MOVE, + kFakeCoordX + 20, + kFakeCoordY + 20); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress()); + + event = ObtainMotionEvent(event_time + long_press_timeout + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 1); + event.SetId(++motion_event_id); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_FALSE(gesture_provider_->IsDoubleTapInProgress()); +} + +// Verify that the touch slop region is removed from the first scroll delta to +// avoid a jump when starting to scroll. +TEST_F(GestureProviderTest, TouchSlopRemovedFromScroll) { + const float touch_slop = GetTouchSlop(); + const float scroll_delta = 5; + + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + touch_slop + scroll_delta); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + GestureEventData gesture = GetMostRecentGestureEvent(); + EXPECT_EQ(0, gesture.details.scroll_x()); + EXPECT_EQ(scroll_delta, gesture.details.scroll_y()); + EXPECT_EQ(1, gesture.details.touch_points()); +} + +// Verify that movement within the touch slop region does not generate a scroll, +// and that the slop region is correct even when using fractional coordinates. +TEST_F(GestureProviderTest, NoScrollWithinTouchSlop) { + const float touch_slop = GetTouchSlop(); + const float scale_factor = 2.5f; + const int touch_slop_pixels = static_cast<int>(scale_factor * touch_slop); + + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX + touch_slop_pixels / scale_factor, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + touch_slop_pixels / scale_factor); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX - touch_slop_pixels / scale_factor, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY - touch_slop_pixels / scale_factor); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + + event = + ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + (touch_slop_pixels + 1.f) / scale_factor); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); +} + +TEST_F(GestureProviderTest, NoDoubleTapWhenTooRapid) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // If the second tap follows the first in too short a time span, no double-tap + // will occur. + event_time += (GetDoubleTapMinTime() / 2); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); +} + +TEST_F(GestureProviderTest, NoDoubleTapWhenExplicitlyDisabled) { + // Ensure that double-tap gestures can be disabled. + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + + base::TimeTicks event_time = base::TimeTicks::Now(); + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + + event_time += GetValidDoubleTapDelay(); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + + // Ensure that double-tap gestures can be interrupted. + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + event_time = base::TimeTicks::Now(); + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(5U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + + // Ensure that double-tap gestures can be resumed. + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + event_time += GetValidDoubleTapDelay(); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + + event_time += GetValidDoubleTapDelay(); + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_DOWN, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, GetMostRecentGestureEventType()); +} + +TEST_F(GestureProviderTest, NoDelayedTapWhenDoubleTapSupportToggled) { + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + base::TimeTicks event_time = base::TimeTicks::Now(); + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(2U, GetReceivedGestureCount()); + + // Disabling double-tap during the tap timeout should flush the delayed tap. + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + EXPECT_EQ(3U, GetReceivedGestureCount()); + + // No further timeout gestures should arrive. + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + EXPECT_EQ(3U, GetReceivedGestureCount()); +} + +TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPlatform) { + const base::TimeTicks down_time_1 = TimeTicks::Now(); + const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay(); + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + + MockMotionEvent event = + ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + + event = ObtainMotionEvent( + down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + + // The move should become a scroll, as doubletap drag zoom is disabled. + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(down_time_2 + kOneMicrosecond * 2, + GetMostRecentGestureEvent().time); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END)); +} + +// Verify that double tap drag zoom feature is not invoked when the gesture +// handler is told to disable double tap gesture detection. +// The second tap sequence should be treated just as the first would be. +TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPage) { + const base::TimeTicks down_time_1 = TimeTicks::Now(); + const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay(); + + gesture_provider_->SetDoubleTapSupportForPageEnabled(false); + + MockMotionEvent event = + ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + + event = ObtainMotionEvent( + down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + + // The move should become a scroll, as double tap drag zoom is disabled. + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END)); +} + +// Verify that updating double tap support during a double tap drag zoom +// disables double tap detection after the gesture has ended. +TEST_F(GestureProviderTest, FixedPageScaleDuringDoubleTapDragZoom) { + base::TimeTicks down_time_1 = TimeTicks::Now(); + base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay(); + + gesture_provider_->SetDoubleTapSupportForPageEnabled(true); + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + // Start a double-tap drag gesture. + MockMotionEvent event = + ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + event = ObtainMotionEvent( + down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // Simulate setting a fixed page scale (or a mobile viewport); + // this should not disrupt the current double-tap gesture. + gesture_provider_->SetDoubleTapSupportForPageEnabled(false); + + // Double tap zoom updates should continue. + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale()); + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END)); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // The double-tap gesture has finished, but the page scale is fixed. + // The same event sequence should not generate any double tap getsures. + gestures_.clear(); + down_time_1 += kOneMicrosecond * 40; + down_time_2 += kOneMicrosecond * 40; + + // Start a double-tap drag gesture. + event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(down_time_1 + kOneMicrosecond, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY); + gesture_provider_->OnTouchEvent(event); + event = ObtainMotionEvent( + down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 100); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN)); + + // Double tap zoom updates should not be sent. + // Instead, the second tap drag becomes a scroll gesture sequence. + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_UP, + kFakeCoordX, + kFakeCoordY + 200); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END)); +} + +// Verify that pinch zoom sends the proper event sequence. +TEST_F(GestureProviderTest, PinchZoom) { + base::TimeTicks event_time = base::TimeTicks::Now(); + const float touch_slop = GetTouchSlop(); + const float raw_offset_x = 3.2f; + const float raw_offset_y = 4.3f; + int motion_event_id = 0; + + gesture_provider_->SetDoubleTapSupportForPageEnabled(false); + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + gesture_provider_->SetMultiTouchZoomSupportEnabled(true); + + int secondary_coord_x = kFakeCoordX + 20 * touch_slop; + int secondary_coord_y = kFakeCoordY + 20 * touch_slop; + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(kFakeCoordX, GetMostRecentGestureEvent().x); + EXPECT_EQ(kFakeCoordY, GetMostRecentGestureEvent().y); + EXPECT_EQ(kFakeCoordX + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(kFakeCoordY + raw_offset_y, GetMostRecentGestureEvent().raw_y); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + // Toggling double-tap support should not take effect until the next sequence. + gesture_provider_->SetDoubleTapSupportForPageEnabled(true); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + event.SetId(++motion_event_id); + event.SetRawOffset(raw_offset_x, raw_offset_y); + + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(1U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY), + GetMostRecentGestureEvent().details.bounding_box()); + + secondary_coord_x += 5 * touch_slop; + secondary_coord_y += 5 * touch_slop; + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + event.SetId(++motion_event_id); + event.SetRawOffset(raw_offset_x, raw_offset_y); + + // Toggling double-tap support should not take effect until the next sequence. + gesture_provider_->SetDoubleTapSupportForPageEnabled(false); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE)); + + EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2, GetReceivedGesture(3).x); + EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2, GetReceivedGesture(3).y); + EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2 + raw_offset_x, + GetReceivedGesture(3).raw_x); + EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2 + raw_offset_y, + GetReceivedGesture(3).raw_y); + + EXPECT_EQ( + gfx::RectF(kFakeCoordX - kMockTouchRadius, + kFakeCoordY - kMockTouchRadius, + secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2, + secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); + + secondary_coord_x += 2 * touch_slop; + secondary_coord_y += 2 * touch_slop; + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + event.SetId(++motion_event_id); + + // Toggling double-tap support should not take effect until the next sequence. + gesture_provider_->SetDoubleTapSupportForPageEnabled(true); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE)); + EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale()); + EXPECT_EQ( + gfx::RectF(kFakeCoordX - kMockTouchRadius, + kFakeCoordY - kMockTouchRadius, + secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2, + secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_UP, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + event.SetId(++motion_event_id); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END)); + EXPECT_EQ( + gfx::RectF(kFakeCoordX - kMockTouchRadius, + kFakeCoordY - kMockTouchRadius, + secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2, + secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(gfx::RectF(kFakeCoordX - kMockTouchRadius, + kFakeCoordY - kMockTouchRadius, + kMockTouchRadius * 2, + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); +} + +// Verify that no accidental pinching occurs if the touch size is large relative +// to the min scaling span. +TEST_F(GestureProviderTest, NoPinchZoomWithFatFinger) { + base::TimeTicks event_time = base::TimeTicks::Now(); + const float kFatFingerSize = GetMinScalingSpan() * 3.f; + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + gesture_provider_->SetMultiTouchZoomSupportEnabled(true); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneSecond, + MotionEvent::ACTION_MOVE); + event.SetTouchMajor(0.1f); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(1U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneSecond * 2, + MotionEvent::ACTION_MOVE, + kFakeCoordX + 1.f, + kFakeCoordY); + event.SetTouchMajor(1.f); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(1U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneSecond * 3, + MotionEvent::ACTION_MOVE); + event.SetTouchMajor(kFatFingerSize * 3.5f); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(1U, GetReceivedGestureCount()); + + event = ObtainMotionEvent(event_time + kOneSecond * 4, + MotionEvent::ACTION_MOVE); + event.SetTouchMajor(kFatFingerSize * 5.f); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(1U, GetReceivedGestureCount()); +} + +// Verify that multi-finger swipe sends the proper event sequence. +TEST_F(GestureProviderTest, MultiFingerSwipe) { + EnableSwipe(); + gesture_provider_->SetMultiTouchZoomSupportEnabled(false); + const float min_swipe_velocity = GetMinSwipeVelocity(); + + // One finger - swipe right + OneFingerSwipe(2 * min_swipe_velocity, 0); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // One finger - swipe left + OneFingerSwipe(-2 * min_swipe_velocity, 0); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // One finger - swipe down + OneFingerSwipe(0, 2 * min_swipe_velocity); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // One finger - swipe up + OneFingerSwipe(0, -2 * min_swipe_velocity); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // Two fingers + // Swipe right. + TwoFingerSwipe(min_swipe_velocity * 2, 0, min_swipe_velocity * 2, 0); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // Swipe left. + TwoFingerSwipe(-min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // No swipe with different touch directions. + TwoFingerSwipe(min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE)); + ResetGestureDetection(); + + // No swipe without a dominant direction. + TwoFingerSwipe(min_swipe_velocity * 2, + min_swipe_velocity * 2, + min_swipe_velocity * 2, + min_swipe_velocity * 2); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE)); + ResetGestureDetection(); + + // Swipe down with non-zero velocities on both axes and dominant direction. + TwoFingerSwipe(-min_swipe_velocity, + min_swipe_velocity * 4, + -min_swipe_velocity, + min_swipe_velocity * 4); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down()); + EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_left()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // Swipe up with non-zero velocities on both axes. + TwoFingerSwipe(min_swipe_velocity, + -min_swipe_velocity * 4, + min_swipe_velocity, + -min_swipe_velocity * 4); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up()); + EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // No swipe without sufficient velocity. + TwoFingerSwipe(min_swipe_velocity / 2, 0, 0, 0); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE)); + ResetGestureDetection(); + + // Swipe up with one small and one medium velocity in slightly different but + // not opposing directions. + TwoFingerSwipe(min_swipe_velocity / 2, + min_swipe_velocity / 2, + 0, + min_swipe_velocity * 2); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down()); + EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // No swipe in orthogonal directions. + TwoFingerSwipe(min_swipe_velocity * 2, 0, 0, min_swipe_velocity * 7); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE)); + ResetGestureDetection(); + + // Three finger swipe in same directions. + ThreeFingerSwipe(min_swipe_velocity * 2, + 0, + min_swipe_velocity * 3, + 0, + min_swipe_velocity * 4, + 0); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE)); + EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right()); + EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points()); + ResetGestureDetection(); + + // No three finger swipe in different directions. + ThreeFingerSwipe(min_swipe_velocity * 2, + 0, + 0, + min_swipe_velocity * 3, + min_swipe_velocity * 4, + 0); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE)); +} + +// Verify that the timer of LONG_PRESS will be cancelled when scrolling begins +// so LONG_PRESS and LONG_TAP won't be triggered. +TEST_F(GestureProviderTest, GesturesCancelledAfterLongPressCausesLostFocus) { + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + const base::TimeDelta long_press_timeout = + GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond; + RunTasksAndWait(long_press_timeout); + EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + EXPECT_TRUE(CancelActiveTouchSequence()); + EXPECT_FALSE(HasDownEvent()); + + event = ObtainMotionEvent(event_time + long_press_timeout, + MotionEvent::ACTION_UP); + gesture_provider_->OnTouchEvent(event); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP)); +} + +// Verify that inserting a touch cancel event will trigger proper touch and +// gesture sequence cancellation. +TEST_F(GestureProviderTest, CancelActiveTouchSequence) { + base::TimeTicks event_time = base::TimeTicks::Now(); + int motion_event_id = 0; + + EXPECT_FALSE(CancelActiveTouchSequence()); + EXPECT_EQ(0U, GetReceivedGestureCount()); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + event.SetId(++motion_event_id); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + ASSERT_TRUE(CancelActiveTouchSequence()); + EXPECT_FALSE(HasDownEvent()); + + // Subsequent MotionEvent's are dropped until ACTION_DOWN. + event = ObtainMotionEvent(event_time + kOneMicrosecond, + MotionEvent::ACTION_MOVE); + EXPECT_FALSE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 2, + MotionEvent::ACTION_UP); + EXPECT_FALSE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneMicrosecond * 3, + MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); +} + +TEST_F(GestureProviderTest, DoubleTapDragZoomCancelledOnSecondaryPointerDown) { + const base::TimeTicks down_time_1 = TimeTicks::Now(); + const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay(); + + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + + MockMotionEvent event = + ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = + ObtainMotionEvent(down_time_1 + kOneMicrosecond, MotionEvent::ACTION_UP); + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(down_time_2, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY - 30); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY - 30, + kFakeCoordX + 50, + kFakeCoordY + 50); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + + const size_t gesture_count = GetReceivedGestureCount(); + event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3, + MotionEvent::ACTION_POINTER_UP, + kFakeCoordX, + kFakeCoordY - 30, + kFakeCoordX + 50, + kFakeCoordY + 50); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(gesture_count, GetReceivedGestureCount()); + + event = ObtainMotionEvent(down_time_2 + kOneSecond, + MotionEvent::ACTION_UP); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(gesture_count + 1, GetReceivedGestureCount()); + EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); +} + +// Verify that gesture begin and gesture end events are dispatched correctly. +TEST_F(GestureProviderTest, GestureBeginAndEnd) { + EnableBeginEndTypes(); + base::TimeTicks event_time = base::TimeTicks::Now(); + const float raw_offset_x = 7.5f; + const float raw_offset_y = 5.7f; + + EXPECT_EQ(0U, GetReceivedGestureCount()); + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1); + event.pointer_count = 1; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type()); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(2U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(1, GetMostRecentGestureEvent().x); + EXPECT_EQ(1, GetMostRecentGestureEvent().y); + EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius, + 1 - kMockTouchRadius, + kMockTouchRadius * 2, + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2); + event.pointer_count = 2; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(3U, GetReceivedGestureCount()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(2, GetMostRecentGestureEvent().x); + EXPECT_EQ(2, GetMostRecentGestureEvent().y); + EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3); + event.pointer_count = 3; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(4U, GetReceivedGestureCount()); + EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(3, GetMostRecentGestureEvent().x); + EXPECT_EQ(3, GetMostRecentGestureEvent().y); + EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_UP, 1, 1, 2, 2, 3, 3); + event.pointer_count = 2; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType()); + EXPECT_EQ(5U, GetReceivedGestureCount()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(1, GetMostRecentGestureEvent().x); + EXPECT_EQ(1, GetMostRecentGestureEvent().y); + EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_DOWN, 2, 2, 3, 3, 4, 4); + event.SetRawOffset(raw_offset_x, raw_offset_y); + event.pointer_count = 3; + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(6U, GetReceivedGestureCount()); + EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(4, GetMostRecentGestureEvent().x); + EXPECT_EQ(4, GetMostRecentGestureEvent().y); + EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_UP, 2, 2, 3, 3, 4, 4); + event.pointer_count = 2; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType()); + EXPECT_EQ(7U, GetReceivedGestureCount()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(2, GetMostRecentGestureEvent().x); + EXPECT_EQ(2, GetMostRecentGestureEvent().y); + EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_POINTER_UP, 3, 3, 4, 4); + event.pointer_count = 1; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType()); + EXPECT_EQ(8U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(3, GetMostRecentGestureEvent().x); + EXPECT_EQ(3, GetMostRecentGestureEvent().y); + EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y); + + + event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP, 4, 4); + event.pointer_count = 1; + event.SetRawOffset(raw_offset_x, raw_offset_y); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType()); + EXPECT_EQ(9U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(4, GetMostRecentGestureEvent().x); + EXPECT_EQ(4, GetMostRecentGestureEvent().y); + EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x); + EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y); +} + +// Verify that gesture begin and gesture end events are dispatched correctly +// when an ACTION_CANCEL is received. +TEST_F(GestureProviderTest, GestureBeginAndEndOnCancel) { + EnableBeginEndTypes(); + base::TimeTicks event_time = base::TimeTicks::Now(); + + EXPECT_EQ(0U, GetReceivedGestureCount()); + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1); + event.pointer_count = 1; + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type()); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(2U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius, + 1 - kMockTouchRadius, + kMockTouchRadius * 2, + kMockTouchRadius * 2), + GetMostRecentGestureEvent().details.bounding_box()); + EXPECT_EQ(1, GetMostRecentGestureEvent().x); + EXPECT_EQ(1, GetMostRecentGestureEvent().y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2); + event.pointer_count = 2; + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(3U, GetReceivedGestureCount()); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(2, GetMostRecentGestureEvent().x); + EXPECT_EQ(2, GetMostRecentGestureEvent().y); + + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3); + event.pointer_count = 3; + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType()); + EXPECT_EQ(4U, GetReceivedGestureCount()); + EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_EQ(3, GetMostRecentGestureEvent().x); + EXPECT_EQ(3, GetMostRecentGestureEvent().y); + + event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_CANCEL, 1, 1, 2, 2, 3, 3); + event.pointer_count = 3; + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(7U, GetReceivedGestureCount()); + EXPECT_EQ(3, GetReceivedGesture(4).details.touch_points()); + EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(4).type()); + EXPECT_EQ(2, GetReceivedGesture(5).details.touch_points()); + EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(5).type()); + EXPECT_EQ(1, GetReceivedGesture(6).details.touch_points()); + EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(6).type()); + EXPECT_EQ(1, GetReceivedGesture(4).x); + EXPECT_EQ(1, GetReceivedGesture(4).y); + EXPECT_EQ(2, GetReceivedGesture(5).x); + EXPECT_EQ(2, GetReceivedGesture(5).y); + EXPECT_EQ(3, GetReceivedGesture(6).x); + EXPECT_EQ(3, GetReceivedGesture(6).y); +} + +// Test a simple two finger tap +TEST_F(GestureProviderTest, TwoFingerTap) { + // The time between ACTION_POINTER_DOWN and ACTION_POINTER_UP must be <= the + // two finger tap delay. + EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta()); + const float scaled_touch_slop = GetTouchSlop(); + + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 0, 0); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + 0, + scaled_touch_slop / 2); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + 0, + 0, + kMaxTwoFingerTapSeparation / 2, + 0); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = + ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + 0, + -scaled_touch_slop / 2, + kMaxTwoFingerTapSeparation / 2 + scaled_touch_slop / 2, + 0); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_UP, + 0, + 0, + kMaxTwoFingerTapSeparation, + 0); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type()); + EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type()); + EXPECT_EQ(ET_GESTURE_TWO_FINGER_TAP, GetReceivedGesture(2).type()); + EXPECT_EQ(3U, GetReceivedGestureCount()); + + EXPECT_EQ(kMockTouchRadius * 2, + GetReceivedGesture(2).details.first_finger_width()); + EXPECT_EQ(kMockTouchRadius * 2, + GetReceivedGesture(2).details.first_finger_height()); +} + +// Test preventing a two finger tap via finger movement. +TEST_F(GestureProviderTest, TwoFingerTapCancelledByFingerMovement) { + EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta()); + const float scaled_touch_slop = GetTouchSlop(); + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX + scaled_touch_slop + 0.1, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_UP, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type()); + EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type()); + EXPECT_EQ(2U, GetReceivedGestureCount()); +} + +// Test preventing a two finger tap by waiting too long before releasing the +// secondary pointer. +TEST_F(GestureProviderTest, TwoFingerTapCancelledByDelay) { + base::TimeDelta two_finger_tap_timeout = kOneSecond; + EnableTwoFingerTap(kMaxTwoFingerTapSeparation, two_finger_tap_timeout); + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX + kMaxTwoFingerTapSeparation / 2, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time + kOneSecond + kOneMicrosecond, + MotionEvent::ACTION_POINTER_UP, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX + kMaxTwoFingerTapSeparation / 2, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type()); + EXPECT_EQ(1U, GetReceivedGestureCount()); +} + +// Test preventing a two finger tap by pressing the secondary pointer too far +// from the first +TEST_F(GestureProviderTest, TwoFingerTapCancelledByDistanceBetweenPointers) { + EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta()); + base::TimeTicks event_time = base::TimeTicks::Now(); + + MockMotionEvent event = ObtainMotionEvent( + event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX + kMaxTwoFingerTapSeparation, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_UP, + kFakeCoordX, + kFakeCoordY, + kFakeCoordX + kMaxTwoFingerTapSeparation, + kFakeCoordY); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type()); + EXPECT_EQ(1U, GetReceivedGestureCount()); +} + +// Verify that pinch zoom only sends updates which exceed the +// min_pinch_update_span_delta. +TEST_F(GestureProviderTest, PinchZoomWithThreshold) { + const float kMinPinchUpdateDistance = 5; + + base::TimeTicks event_time = base::TimeTicks::Now(); + const float touch_slop = GetTouchSlop(); + + SetMinPinchUpdateSpanDelta(kMinPinchUpdateDistance); + gesture_provider_->SetDoubleTapSupportForPageEnabled(false); + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true); + gesture_provider_->SetMultiTouchZoomSupportEnabled(true); + + int secondary_coord_x = kFakeCoordX + 20 * touch_slop; + int secondary_coord_y = kFakeCoordY + 20 * touch_slop; + + // First finger down. + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // Second finger down. + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_POINTER_DOWN, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + + gesture_provider_->OnTouchEvent(event); + EXPECT_EQ(1U, GetReceivedGestureCount()); + EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points()); + + // Move second finger. + secondary_coord_x += 5 * touch_slop; + secondary_coord_y += 5 * touch_slop; + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x, + secondary_coord_y); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE)); + + // Small move, shouldn't trigger pinch. + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x + kMinPinchUpdateDistance, + secondary_coord_y); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); + + // Small move, but combined with the previous move, should trigger pinch. We + // need to overshoot kMinPinchUpdateDistance by a fair bit, as the span + // calculation factors in touch radius. + const float kOvershootMinPinchUpdateDistance = 3; + event = ObtainMotionEvent(event_time, + MotionEvent::ACTION_MOVE, + kFakeCoordX, + kFakeCoordY, + secondary_coord_x + kMinPinchUpdateDistance + + kOvershootMinPinchUpdateDistance, + secondary_coord_y); + + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE)); + EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points()); +} + +// Verify that the min gesture bound setting is honored. +TEST_F(GestureProviderTest, MinGestureBoundsLength) { + const float kMinGestureBoundsLength = 10.f * kMockTouchRadius; + SetMinGestureBoundsLength(kMinGestureBoundsLength); + gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false); + + base::TimeTicks event_time = base::TimeTicks::Now(); + MockMotionEvent event = + ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + + EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType()); + EXPECT_EQ(kMinGestureBoundsLength, + GetMostRecentGestureEvent().details.bounding_box_f().width()); + EXPECT_EQ(kMinGestureBoundsLength, + GetMostRecentGestureEvent().details.bounding_box_f().height()); + + event = + ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP); + EXPECT_TRUE(gesture_provider_->OnTouchEvent(event)); + EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType()); + EXPECT_EQ(kMinGestureBoundsLength, + GetMostRecentGestureEvent().details.bounding_box_f().width()); + EXPECT_EQ(kMinGestureBoundsLength, + GetMostRecentGestureEvent().details.bounding_box_f().height()); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/mock_motion_event.cc b/chromium/ui/events/gesture_detection/mock_motion_event.cc new file mode 100644 index 00000000000..d6e0a91b5a1 --- /dev/null +++ b/chromium/ui/events/gesture_detection/mock_motion_event.cc @@ -0,0 +1,204 @@ +// Copyright 2014 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/gesture_detection/mock_motion_event.h" + +#include "base/logging.h" + +using base::TimeTicks; + +namespace ui { + +MockMotionEvent::MockMotionEvent() + : action(ACTION_CANCEL), pointer_count(1), touch_major(TOUCH_MAJOR), id(0) { +} + +MockMotionEvent::MockMotionEvent(Action action) + : action(action), pointer_count(1), touch_major(TOUCH_MAJOR), id(0) { +} + +MockMotionEvent::MockMotionEvent(Action action, + TimeTicks time, + float x, + float y) + : action(action), + pointer_count(1), + time(time), + touch_major(TOUCH_MAJOR), + id(0) { + points[0].SetPoint(x, y); +} + +MockMotionEvent::MockMotionEvent(Action action, + TimeTicks time, + float x0, + float y0, + float x1, + float y1) + : action(action), + pointer_count(2), + time(time), + touch_major(TOUCH_MAJOR), + id(0) { + points[0].SetPoint(x0, y0); + points[1].SetPoint(x1, y1); +} + +MockMotionEvent::MockMotionEvent(Action action, + TimeTicks time, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2) + : action(action), + pointer_count(3), + time(time), + touch_major(TOUCH_MAJOR), + id(0) { + points[0].SetPoint(x0, y0); + points[1].SetPoint(x1, y1); + points[2].SetPoint(x2, y2); +} + +MockMotionEvent::MockMotionEvent(const MockMotionEvent& other) + : action(other.action), + pointer_count(other.pointer_count), + time(other.time), + touch_major(other.touch_major), + id(other.GetId()) { + for (size_t i = 0; i < pointer_count; ++i) + points[i] = other.points[i]; +} + +MockMotionEvent::~MockMotionEvent() {} + +MotionEvent::Action MockMotionEvent::GetAction() const { return action; } + +int MockMotionEvent::GetActionIndex() const { + return static_cast<int>(pointer_count) - 1; +} + +size_t MockMotionEvent::GetPointerCount() const { return pointer_count; } + +int MockMotionEvent::GetId() const { + return id; +} + +int MockMotionEvent::GetPointerId(size_t pointer_index) const { + DCHECK(pointer_index < pointer_count); + return static_cast<int>(pointer_index); +} + +float MockMotionEvent::GetX(size_t pointer_index) const { + return points[pointer_index].x(); +} + +float MockMotionEvent::GetY(size_t pointer_index) const { + return points[pointer_index].y(); +} + +float MockMotionEvent::GetRawX(size_t pointer_index) const { + return GetX(pointer_index) + raw_offset.x(); +} + +float MockMotionEvent::GetRawY(size_t pointer_index) const { + return GetY(pointer_index) + raw_offset.y(); +} + +float MockMotionEvent::GetTouchMajor(size_t pointer_index) const { + return touch_major; +} + +float MockMotionEvent::GetPressure(size_t pointer_index) const { + return 0; +} + +TimeTicks MockMotionEvent::GetEventTime() const { return time; } + +size_t MockMotionEvent::GetHistorySize() const { return 0; } + +TimeTicks MockMotionEvent::GetHistoricalEventTime( + size_t historical_index) const { + return TimeTicks(); +} + +float MockMotionEvent::GetHistoricalTouchMajor(size_t pointer_index, + size_t historical_index) const { + return 0; +} + +float MockMotionEvent::GetHistoricalX(size_t pointer_index, + size_t historical_index) const { + return 0; +} + +float MockMotionEvent::GetHistoricalY(size_t pointer_index, + size_t historical_index) const { + return 0; +} + +scoped_ptr<MotionEvent> MockMotionEvent::Clone() const { + return scoped_ptr<MotionEvent>(new MockMotionEvent(*this)); +} + +scoped_ptr<MotionEvent> MockMotionEvent::Cancel() const { + scoped_ptr<MockMotionEvent> cancel_event(new MockMotionEvent(*this)); + cancel_event->action = MotionEvent::ACTION_CANCEL; + return cancel_event.PassAs<MotionEvent>(); +} + +void MockMotionEvent::SetId(int new_id) { + id = new_id; +} + +void MockMotionEvent::SetTime(base::TimeTicks new_time) { + time = new_time; +} + +void MockMotionEvent::PressPoint(float x, float y) { + // Reset the pointer count if the previously released and/or cancelled pointer + // was the last pointer in the event. + if (pointer_count == 1 && (action == ACTION_UP || action == ACTION_CANCEL)) + pointer_count = 0; + + DCHECK_LT(pointer_count, static_cast<size_t>(MAX_POINTERS)); + points[pointer_count++] = gfx::PointF(x, y); + action = pointer_count > 1 ? ACTION_POINTER_DOWN : ACTION_DOWN; +} + +void MockMotionEvent::MovePoint(size_t index, float x, float y) { + DCHECK_LT(index, pointer_count); + points[index] = gfx::PointF(x, y); + action = ACTION_MOVE; +} + +void MockMotionEvent::ReleasePoint() { + DCHECK_GT(pointer_count, 0U); + if (pointer_count > 1) { + --pointer_count; + action = ACTION_POINTER_UP; + } else { + action = ACTION_UP; + } +} + +void MockMotionEvent::CancelPoint() { + DCHECK_GT(pointer_count, 0U); + if (pointer_count > 1) + --pointer_count; + action = ACTION_CANCEL; +} + +void MockMotionEvent::SetTouchMajor(float new_touch_major) { + touch_major = new_touch_major; +} + +void MockMotionEvent::SetRawOffset(float raw_offset_x, float raw_offset_y) { + raw_offset.set_x(raw_offset_x); + raw_offset.set_y(raw_offset_y); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/mock_motion_event.h b/chromium/ui/events/gesture_detection/mock_motion_event.h new file mode 100644 index 00000000000..433de7b3b08 --- /dev/null +++ b/chromium/ui/events/gesture_detection/mock_motion_event.h @@ -0,0 +1,86 @@ +// Copyright 2014 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 <vector> + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "ui/events/gesture_detection/motion_event.h" +#include "ui/gfx/geometry/point_f.h" + +namespace ui { + +struct MockMotionEvent : public MotionEvent { + enum { MAX_POINTERS = 3 }; + enum { TOUCH_MAJOR = 10 }; + + MockMotionEvent(); + explicit MockMotionEvent(Action action); + MockMotionEvent(Action action, base::TimeTicks time, float x, float y); + MockMotionEvent(Action action, + base::TimeTicks time, + float x0, + float y0, + float x1, + float y1); + MockMotionEvent(Action action, + base::TimeTicks time, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2); + MockMotionEvent(Action action, + base::TimeTicks time, + const std::vector<gfx::PointF>& positions); + MockMotionEvent(const MockMotionEvent& other); + virtual ~MockMotionEvent(); + + // MotionEvent methods. + virtual Action GetAction() const OVERRIDE; + virtual int GetActionIndex() const OVERRIDE; + virtual size_t GetPointerCount() const OVERRIDE; + virtual int GetId() const OVERRIDE; + virtual int GetPointerId(size_t pointer_index) const OVERRIDE; + virtual float GetX(size_t pointer_index) const OVERRIDE; + virtual float GetY(size_t pointer_index) const OVERRIDE; + virtual float GetRawX(size_t pointer_index) const OVERRIDE; + virtual float GetRawY(size_t pointer_index) const OVERRIDE; + virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE; + virtual float GetPressure(size_t pointer_index) const OVERRIDE; + virtual base::TimeTicks GetEventTime() const OVERRIDE; + virtual size_t GetHistorySize() const OVERRIDE; + virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const + OVERRIDE; + virtual float GetHistoricalTouchMajor(size_t pointer_index, + size_t historical_index) const OVERRIDE; + virtual float GetHistoricalX(size_t pointer_index, + size_t historical_index) const OVERRIDE; + virtual float GetHistoricalY(size_t pointer_index, + size_t historical_index) const OVERRIDE; + + virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE; + virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE; + + // Utility methods. + void SetId(int new_id); + void SetTime(base::TimeTicks new_time); + void PressPoint(float x, float y); + void MovePoint(size_t index, float x, float y); + void ReleasePoint(); + void CancelPoint(); + void SetTouchMajor(float new_touch_major); + void SetRawOffset(float raw_offset_x, float raw_offset_y); + + MotionEvent::Action action; + size_t pointer_count; + gfx::PointF points[MAX_POINTERS]; + gfx::Vector2dF raw_offset; + base::TimeTicks time; + float touch_major; + int id; +}; + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/motion_event.h b/chromium/ui/events/gesture_detection/motion_event.h new file mode 100644 index 00000000000..3cf4235b78a --- /dev/null +++ b/chromium/ui/events/gesture_detection/motion_event.h @@ -0,0 +1,73 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_ +#define UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" + +namespace ui { + +// Abstract class for a generic motion-related event, patterned after that +// subset of Android's MotionEvent API used in gesture detection. +class GESTURE_DETECTION_EXPORT MotionEvent { + public: + enum Action { + ACTION_DOWN, + ACTION_UP, + ACTION_MOVE, + ACTION_CANCEL, + ACTION_POINTER_DOWN, + ACTION_POINTER_UP, + }; + + // The implementer promises that |GetPointerId()| will never exceed this. + enum { MAX_POINTER_ID = 31 }; + + virtual ~MotionEvent() {} + + virtual int GetId() const = 0; + virtual Action GetAction() const = 0; + // Only valid if |GetAction()| returns ACTION_POINTER_UP or + // ACTION_POINTER_DOWN. + virtual int GetActionIndex() const = 0; + virtual size_t GetPointerCount() const = 0; + virtual int GetPointerId(size_t pointer_index) const = 0; + virtual float GetX(size_t pointer_index) const = 0; + virtual float GetY(size_t pointer_index) const = 0; + virtual float GetRawX(size_t pointer_index) const = 0; + virtual float GetRawY(size_t pointer_index) const = 0; + virtual float GetTouchMajor(size_t pointer_index) const = 0; + virtual float GetPressure(size_t pointer_index) const = 0; + virtual base::TimeTicks GetEventTime() const = 0; + + virtual size_t GetHistorySize() const = 0; + virtual base::TimeTicks GetHistoricalEventTime( + size_t historical_index) const = 0; + virtual float GetHistoricalTouchMajor(size_t pointer_index, + size_t historical_index) const = 0; + virtual float GetHistoricalX(size_t pointer_index, + size_t historical_index) const = 0; + virtual float GetHistoricalY(size_t pointer_index, + size_t historical_index) const = 0; + + virtual scoped_ptr<MotionEvent> Clone() const = 0; + virtual scoped_ptr<MotionEvent> Cancel() const = 0; + + // Utility accessor methods for convenience. + float GetX() const { return GetX(0); } + float GetY() const { return GetY(0); } + float GetRawX() const { return GetRawX(0); } + float GetRawY() const { return GetRawY(0); } + float GetRawOffsetX() const { return GetRawX() - GetX(); } + float GetRawOffsetY() const { return GetRawY() - GetY(); } + float GetTouchMajor() const { return GetTouchMajor(0); } + float GetPressure() const { return GetPressure(0); } +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_ diff --git a/chromium/ui/events/gesture_detection/scale_gesture_detector.cc b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc new file mode 100644 index 00000000000..18fd6d85b59 --- /dev/null +++ b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc @@ -0,0 +1,383 @@ +// Copyright 2014 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/gesture_detection/scale_gesture_detector.h" + +#include <limits.h> +#include <cmath> + +#include "base/float_util.h" +#include "base/logging.h" +#include "ui/events/gesture_detection/motion_event.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace ui { +namespace { + +// Using a small epsilon when comparing slop distances allows pixel perfect +// slop determination when using fractional DPI coordinates (assuming the slop +// region and DPI scale are reasonably proportioned). +const float kSlopEpsilon = .05f; + +const int kTouchStabilizeTimeMs = 128; + +const float kScaleFactor = .5f; + +} // namespace + +// Note: These constants were taken directly from the default (unscaled) +// versions found in Android's ViewConfiguration. +ScaleGestureDetector::Config::Config() + : min_scaling_touch_major(48), + min_scaling_span(200), + quick_scale_enabled(true), + min_pinch_update_span_delta(0) { +} + +ScaleGestureDetector::Config::~Config() {} + +bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale( + const ScaleGestureDetector&, const MotionEvent&) { + return false; +} + +bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin( + const ScaleGestureDetector&, const MotionEvent&) { + return true; +} + +void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd( + const ScaleGestureDetector&, const MotionEvent&) {} + +ScaleGestureDetector::ScaleGestureDetector(const Config& config, + ScaleGestureListener* listener) + : listener_(listener), + config_(config), + focus_x_(0), + focus_y_(0), + quick_scale_enabled_(false), + curr_span_(0), + prev_span_(0), + initial_span_(0), + curr_span_x_(0), + curr_span_y_(0), + prev_span_x_(0), + prev_span_y_(0), + in_progress_(0), + span_slop_(0), + min_span_(0), + touch_upper_(0), + touch_lower_(0), + touch_history_last_accepted_(0), + touch_history_direction_(0), + touch_min_major_(0), + double_tap_focus_x_(0), + double_tap_focus_y_(0), + double_tap_mode_(DOUBLE_TAP_MODE_NONE), + event_before_or_above_starting_gesture_event_(false) { + DCHECK(listener_); + span_slop_ = + (config.gesture_detector_config.touch_slop + kSlopEpsilon) * 2; + touch_min_major_ = config.min_scaling_touch_major; + min_span_ = config.min_scaling_span + kSlopEpsilon; + ResetTouchHistory(); + SetQuickScaleEnabled(config.quick_scale_enabled); +} + +ScaleGestureDetector::~ScaleGestureDetector() {} + +bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) { + curr_time_ = event.GetEventTime(); + + const int action = event.GetAction(); + + // Forward the event to check for double tap gesture. + if (quick_scale_enabled_) { + DCHECK(gesture_detector_); + gesture_detector_->OnTouchEvent(event); + } + + const bool stream_complete = + action == MotionEvent::ACTION_UP || + action == MotionEvent::ACTION_CANCEL || + (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode()); + + if (action == MotionEvent::ACTION_DOWN || stream_complete) { + // Reset any scale in progress with the listener. + // If it's an ACTION_DOWN we're beginning a new event stream. + // This means the app probably didn't give us all the events. Shame on it. + if (in_progress_) { + listener_->OnScaleEnd(*this, event); + ResetScaleWithSpan(0); + } else if (InDoubleTapMode() && stream_complete) { + ResetScaleWithSpan(0); + } + + if (stream_complete) { + ResetTouchHistory(); + return true; + } + } + + const bool config_changed = action == MotionEvent::ACTION_DOWN || + action == MotionEvent::ACTION_POINTER_UP || + action == MotionEvent::ACTION_POINTER_DOWN; + + const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP; + const int skip_index = pointer_up ? event.GetActionIndex() : -1; + + // Determine focal point. + float sum_x = 0, sum_y = 0; + const int count = static_cast<int>(event.GetPointerCount()); + const int unreleased_point_count = pointer_up ? count - 1 : count; + const float inverse_unreleased_point_count = 1.0f / unreleased_point_count; + + float focus_x; + float focus_y; + if (InDoubleTapMode()) { + // In double tap mode, the focal pt is always where the double tap + // gesture started. + focus_x = double_tap_focus_x_; + focus_y = double_tap_focus_y_; + if (event.GetY() < focus_y) { + event_before_or_above_starting_gesture_event_ = true; + } else { + event_before_or_above_starting_gesture_event_ = false; + } + } else { + for (int i = 0; i < count; i++) { + if (skip_index == i) + continue; + sum_x += event.GetX(i); + sum_y += event.GetY(i); + } + + focus_x = sum_x * inverse_unreleased_point_count; + focus_y = sum_y * inverse_unreleased_point_count; + } + + AddTouchHistory(event); + + // Determine average deviation from focal point. + float dev_sum_x = 0, dev_sum_y = 0; + for (int i = 0; i < count; i++) { + if (skip_index == i) + continue; + + dev_sum_x += std::abs(event.GetX(i) - focus_x); + dev_sum_y += std::abs(event.GetY(i) - focus_y); + } + // Convert the resulting diameter into a radius, to include touch + // radius in overall deviation. + const float touch_size = touch_history_last_accepted_ / 2; + + const float dev_x = (dev_sum_x * inverse_unreleased_point_count) + touch_size; + const float dev_y = (dev_sum_y * inverse_unreleased_point_count) + touch_size; + + // Span is the average distance between touch points through the focal point; + // i.e. the diameter of the circle with a radius of the average deviation from + // the focal point. + const float span_x = dev_x * 2; + const float span_y = dev_y * 2; + float span; + if (InDoubleTapMode()) { + span = span_y; + } else { + span = std::sqrt(span_x * span_x + span_y * span_y); + } + + // Dispatch begin/end events as needed. + // If the configuration changes, notify the app to reset its current state by + // beginning a fresh scale event stream. + const bool was_in_progress = in_progress_; + focus_x_ = focus_x; + focus_y_ = focus_y; + if (!InDoubleTapMode() && in_progress_ && + (span < min_span_ || config_changed)) { + listener_->OnScaleEnd(*this, event); + ResetScaleWithSpan(span); + } + if (config_changed) { + prev_span_x_ = curr_span_x_ = span_x; + prev_span_y_ = curr_span_y_ = span_y; + initial_span_ = prev_span_ = curr_span_ = span; + } + + const float min_span = InDoubleTapMode() ? span_slop_ : min_span_; + if (!in_progress_ && span >= min_span && (InDoubleTapMode() || count > 1) && + (was_in_progress || std::abs(span - initial_span_) > span_slop_)) { + prev_span_x_ = curr_span_x_ = span_x; + prev_span_y_ = curr_span_y_ = span_y; + prev_span_ = curr_span_ = span; + prev_time_ = curr_time_; + in_progress_ = listener_->OnScaleBegin(*this, event); + } + + // Handle motion; focal point and span/scale factor are changing. + if (action == MotionEvent::ACTION_MOVE) { + curr_span_x_ = span_x; + curr_span_y_ = span_y; + curr_span_ = span; + + bool update_prev = true; + + if (in_progress_) { + update_prev = listener_->OnScale(*this, event); + } + + if (update_prev) { + prev_span_x_ = curr_span_x_; + prev_span_y_ = curr_span_y_; + prev_span_ = curr_span_; + prev_time_ = curr_time_; + } + } + + return true; +} + +void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) { + quick_scale_enabled_ = scales; + if (quick_scale_enabled_ && !gesture_detector_) { + gesture_detector_.reset( + new GestureDetector(config_.gesture_detector_config, this, this)); + } +} + +bool ScaleGestureDetector::IsQuickScaleEnabled() const { + return quick_scale_enabled_; +} + +bool ScaleGestureDetector::IsInProgress() const { return in_progress_; } + +bool ScaleGestureDetector::InDoubleTapMode() const { + return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS; +} + +float ScaleGestureDetector::GetFocusX() const { return focus_x_; } + +float ScaleGestureDetector::GetFocusY() const { return focus_y_; } + +float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; } + +float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; } + +float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; } + +float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; } + +float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; } + +float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; } + +float ScaleGestureDetector::GetScaleFactor() const { + if (InDoubleTapMode()) { + // Drag is moving up; the further away from the gesture start, the smaller + // the span should be, the closer, the larger the span, and therefore the + // larger the scale. + const bool scale_up = (event_before_or_above_starting_gesture_event_ && + (curr_span_ < prev_span_)) || + (!event_before_or_above_starting_gesture_event_ && + (curr_span_ > prev_span_)); + const float span_diff = + (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor); + return prev_span_ <= 0 ? 1.f + : (scale_up ? (1.f + span_diff) : (1.f - span_diff)); + } + return prev_span_ > 0 ? curr_span_ / prev_span_ : 1; +} + +base::TimeDelta ScaleGestureDetector::GetTimeDelta() const { + return curr_time_ - prev_time_; +} + +base::TimeTicks ScaleGestureDetector::GetEventTime() const { + return curr_time_; +} + +bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) { + // Double tap: start watching for a swipe. + double_tap_focus_x_ = ev.GetX(); + double_tap_focus_y_ = ev.GetY(); + double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS; + return true; +} + +void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) { + const base::TimeTicks current_time = ev.GetEventTime(); + DCHECK(!current_time.is_null()); + const int count = static_cast<int>(ev.GetPointerCount()); + bool accept = touch_history_last_accepted_time_.is_null() || + (current_time - touch_history_last_accepted_time_) >= + base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs); + float total = 0; + int sample_count = 0; + for (int i = 0; i < count; i++) { + const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_); + const int history_size = static_cast<int>(ev.GetHistorySize()); + const int pointersample_count = history_size + 1; + for (int h = 0; h < pointersample_count; h++) { + float major; + if (h < history_size) { + major = ev.GetHistoricalTouchMajor(i, h); + } else { + major = ev.GetTouchMajor(i); + } + if (major < touch_min_major_) + major = touch_min_major_; + total += major; + + if (base::IsNaN(touch_upper_) || major > touch_upper_) { + touch_upper_ = major; + } + if (base::IsNaN(touch_lower_) || major < touch_lower_) { + touch_lower_ = major; + } + + if (has_last_accepted) { + const float major_delta = major - touch_history_last_accepted_; + const int direction_sig = + major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0); + if (direction_sig != touch_history_direction_ || + (direction_sig == 0 && touch_history_direction_ == 0)) { + touch_history_direction_ = direction_sig; + touch_history_last_accepted_time_ = h < history_size + ? ev.GetHistoricalEventTime(h) + : ev.GetEventTime(); + accept = false; + } + } + } + sample_count += pointersample_count; + } + + const float avg = total / sample_count; + + if (accept) { + float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3; + touch_upper_ = (touch_upper_ + new_accepted) / 2; + touch_lower_ = (touch_lower_ + new_accepted) / 2; + touch_history_last_accepted_ = new_accepted; + touch_history_direction_ = 0; + touch_history_last_accepted_time_ = ev.GetEventTime(); + } +} + +void ScaleGestureDetector::ResetTouchHistory() { + touch_upper_ = std::numeric_limits<float>::quiet_NaN(); + touch_lower_ = std::numeric_limits<float>::quiet_NaN(); + touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN(); + touch_history_direction_ = 0; + touch_history_last_accepted_time_ = base::TimeTicks(); +} + +void ScaleGestureDetector::ResetScaleWithSpan(float span) { + in_progress_ = false; + initial_span_ = span; + double_tap_mode_ = DOUBLE_TAP_MODE_NONE; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/scale_gesture_detector.h b/chromium/ui/events/gesture_detection/scale_gesture_detector.h new file mode 100644 index 00000000000..b07f78d5785 --- /dev/null +++ b/chromium/ui/events/gesture_detection/scale_gesture_detector.h @@ -0,0 +1,158 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_ +#define UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/gesture_detector.h" + +namespace ui { + +class MotionEvent; + +// Port of ScaleGestureDetector.java from Android +// * platform/frameworks/base/core/java/android/view/ScaleGestureDetector.java +// * Change-Id: I3e7926a4f6f9ab4951f380bd004499c78b3bda69 +// * Please update the Change-Id as upstream Android changes are pulled. +class ScaleGestureDetector : public GestureDetector::SimpleGestureListener { + public: + struct GESTURE_DETECTION_EXPORT Config { + Config(); + ~Config(); + GestureDetector::Config gesture_detector_config; + + // Minimum accepted value for TouchMajor while scaling (in dips). + float min_scaling_touch_major; + + // Minimum span needed to initiate a scaling gesture (in dips). + float min_scaling_span; + + // Whether double-tap drag scaling is enabled. + bool quick_scale_enabled; + + // Minimum pinch span change before pinch occurs (in dips). See + // crbug.com/373318. + float min_pinch_update_span_delta; + }; + + class ScaleGestureListener { + public: + virtual ~ScaleGestureListener() {} + virtual bool OnScale(const ScaleGestureDetector& detector, + const MotionEvent& e) = 0; + virtual bool OnScaleBegin(const ScaleGestureDetector& detector, + const MotionEvent& e) = 0; + virtual void OnScaleEnd(const ScaleGestureDetector& detector, + const MotionEvent& e) = 0; + }; + + // A convenience class to extend when you only want to listen for a subset of + // scaling-related events. This implements all methods in + // |ScaleGestureListener| but does nothing. + // |OnScale()| returns false so that a subclass can retrieve the accumulated + // scale factor in an overridden |OnScaleEnd()|. + // |OnScaleBegin() returns true. + class SimpleScaleGestureListener : public ScaleGestureListener { + public: + // ScaleGestureListener implementation. + virtual bool OnScale(const ScaleGestureDetector&, + const MotionEvent&) OVERRIDE; + virtual bool OnScaleBegin(const ScaleGestureDetector&, + const MotionEvent&) OVERRIDE; + virtual void OnScaleEnd(const ScaleGestureDetector&, + const MotionEvent&) OVERRIDE; + }; + + ScaleGestureDetector(const Config& config, ScaleGestureListener* listener); + virtual ~ScaleGestureDetector(); + + // Accepts MotionEvents and dispatches events to a |ScaleGestureListener| + // when appropriate. + // + // Note: Applications should pass a complete and consistent event stream to + // this method. A complete and consistent event stream involves all + // MotionEvents from the initial ACTION_DOWN to the final ACTION_UP or + // ACTION_CANCEL. + // + // Returns true if the event was processed and the detector wants to receive + // the rest of the MotionEvents in this event stream. + bool OnTouchEvent(const MotionEvent& event); + + // Set whether the associated |ScaleGestureListener| should receive + // OnScale callbacks when the user performs a doubletap followed by a swipe. + void SetQuickScaleEnabled(bool scales); + bool IsQuickScaleEnabled() const; + bool IsInProgress() const; + bool InDoubleTapMode() const; + float GetFocusX() const; + float GetFocusY() const; + float GetCurrentSpan() const; + float GetCurrentSpanX() const; + float GetCurrentSpanY() const; + float GetPreviousSpan() const; + float GetPreviousSpanX() const; + float GetPreviousSpanY() const; + float GetScaleFactor() const; + base::TimeDelta GetTimeDelta() const; + base::TimeTicks GetEventTime() const; + + private: + enum DoubleTapMode { DOUBLE_TAP_MODE_NONE, DOUBLE_TAP_MODE_IN_PROGRESS }; + + // DoubleTapListener implementation. + virtual bool OnDoubleTap(const MotionEvent& ev) OVERRIDE; + + // The TouchMajor/TouchMinor elements of a MotionEvent can flutter/jitter on + // some hardware/driver combos. Smooth out to get kinder, gentler behavior. + void AddTouchHistory(const MotionEvent& ev); + void ResetTouchHistory(); + + void ResetScaleWithSpan(float span); + + ScaleGestureListener* const listener_; + + Config config_; + + float focus_x_; + float focus_y_; + + bool quick_scale_enabled_; + + float curr_span_; + float prev_span_; + float initial_span_; + float curr_span_x_; + float curr_span_y_; + float prev_span_x_; + float prev_span_y_; + base::TimeTicks curr_time_; + base::TimeTicks prev_time_; + bool in_progress_; + float span_slop_; + float min_span_; + + // Bounds for recently seen values. + float touch_upper_; + float touch_lower_; + float touch_history_last_accepted_; + int touch_history_direction_; + base::TimeTicks touch_history_last_accepted_time_; + float touch_min_major_; + float double_tap_focus_x_; + float double_tap_focus_y_; + DoubleTapMode double_tap_mode_; + + bool event_before_or_above_starting_gesture_event_; + + scoped_ptr<GestureDetector> gesture_detector_; + + DISALLOW_COPY_AND_ASSIGN(ScaleGestureDetector); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_ diff --git a/chromium/ui/events/gesture_detection/snap_scroll_controller.cc b/chromium/ui/events/gesture_detection/snap_scroll_controller.cc new file mode 100644 index 00000000000..bde5a35602d --- /dev/null +++ b/chromium/ui/events/gesture_detection/snap_scroll_controller.cc @@ -0,0 +1,106 @@ +// Copyright 2014 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/gesture_detection/snap_scroll_controller.h" + +#include <cmath> + +#include "ui/events/gesture_detection/motion_event.h" +#include "ui/gfx/display.h" + +namespace ui { +namespace { +const int kSnapBound = 16; +const float kMinSnapChannelDistance = kSnapBound; +const float kMaxSnapChannelDistance = kMinSnapChannelDistance * 3.f; +const float kSnapChannelDipsPerScreenDip = kMinSnapChannelDistance / 480.f; + +float CalculateChannelDistance(const gfx::Display& display) { + if (display.bounds().IsEmpty()) + return kMinSnapChannelDistance; + + float screen_size = + std::abs(hypot(static_cast<float>(display.bounds().width()), + static_cast<float>(display.bounds().height()))); + + float snap_channel_distance = screen_size * kSnapChannelDipsPerScreenDip; + return std::max(kMinSnapChannelDistance, + std::min(kMaxSnapChannelDistance, snap_channel_distance)); +} + +} // namespace + + +SnapScrollController::SnapScrollController(const gfx::Display& display) + : channel_distance_(CalculateChannelDistance(display)), + snap_scroll_mode_(SNAP_NONE), + first_touch_x_(-1), + first_touch_y_(-1), + distance_x_(0), + distance_y_(0) {} + +SnapScrollController::~SnapScrollController() {} + +void SnapScrollController::UpdateSnapScrollMode(float distance_x, + float distance_y) { + if (snap_scroll_mode_ == SNAP_HORIZ || snap_scroll_mode_ == SNAP_VERT) { + distance_x_ += std::abs(distance_x); + distance_y_ += std::abs(distance_y); + if (snap_scroll_mode_ == SNAP_HORIZ) { + if (distance_y_ > channel_distance_) { + snap_scroll_mode_ = SNAP_NONE; + } else if (distance_x_ > channel_distance_) { + distance_x_ = 0; + distance_y_ = 0; + } + } else { + if (distance_x_ > channel_distance_) { + snap_scroll_mode_ = SNAP_NONE; + } else if (distance_y_ > channel_distance_) { + distance_x_ = 0; + distance_y_ = 0; + } + } + } +} + +void SnapScrollController::SetSnapScrollingMode( + const MotionEvent& event, + bool is_scale_gesture_detection_in_progress) { + switch (event.GetAction()) { + case MotionEvent::ACTION_DOWN: + snap_scroll_mode_ = SNAP_NONE; + first_touch_x_ = event.GetX(); + first_touch_y_ = event.GetY(); + break; + // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds kSnapBound + // and movement towards y-axis is trivial. + // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds kSnapBound + // and movement towards x-axis is trivial. + // Scrolling mode will remain in SNAP_NONE for other conditions. + case MotionEvent::ACTION_MOVE: + if (!is_scale_gesture_detection_in_progress && + snap_scroll_mode_ == SNAP_NONE) { + int x_diff = static_cast<int>(std::abs(event.GetX() - first_touch_x_)); + int y_diff = static_cast<int>(std::abs(event.GetY() - first_touch_y_)); + if (x_diff > kSnapBound && y_diff < kSnapBound) { + snap_scroll_mode_ = SNAP_HORIZ; + } else if (x_diff < kSnapBound && y_diff > kSnapBound) { + snap_scroll_mode_ = SNAP_VERT; + } + } + break; + case MotionEvent::ACTION_UP: + case MotionEvent::ACTION_CANCEL: + first_touch_x_ = -1; + first_touch_y_ = -1; + distance_x_ = 0; + distance_y_ = 0; + break; + default: + break; + } +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/snap_scroll_controller.h b/chromium/ui/events/gesture_detection/snap_scroll_controller.h new file mode 100644 index 00000000000..753c2bcb201 --- /dev/null +++ b/chromium/ui/events/gesture_detection/snap_scroll_controller.h @@ -0,0 +1,60 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_ +#define UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_ + +#include "base/basictypes.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" + +namespace gfx { +class Display; +} + +namespace ui { + +class MotionEvent; +class ZoomManager; + +// Port of SnapScrollController.java from Chromium +// Controls the scroll snapping behavior based on scroll updates. +class SnapScrollController { + public: + explicit SnapScrollController(const gfx::Display& display); + ~SnapScrollController(); + + // Updates the snap scroll mode based on the given X and Y distance to be + // moved on scroll. If the scroll update is above a threshold, the snapping + // behavior is reset. + void UpdateSnapScrollMode(float distance_x, float distance_y); + + // Sets the snap scroll mode based on the event type. + void SetSnapScrollingMode(const MotionEvent& event, + bool is_scale_gesture_detection_in_progress); + + void ResetSnapScrollMode() { snap_scroll_mode_ = SNAP_NONE; } + bool IsSnapVertical() const { return snap_scroll_mode_ == SNAP_VERT; } + bool IsSnapHorizontal() const { return snap_scroll_mode_ == SNAP_HORIZ; } + bool IsSnappingScrolls() const { return snap_scroll_mode_ != SNAP_NONE; } + + private: + enum SnapMode { + SNAP_NONE, + SNAP_HORIZ, + SNAP_VERT + }; + + float channel_distance_; + SnapMode snap_scroll_mode_; + float first_touch_x_; + float first_touch_y_; + float distance_x_; + float distance_y_; + + DISALLOW_COPY_AND_ASSIGN(SnapScrollController); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_ diff --git a/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc new file mode 100644 index 00000000000..0e775dd9c19 --- /dev/null +++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc @@ -0,0 +1,400 @@ +// Copyright 2014 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/gesture_detection/touch_disposition_gesture_filter.h" + +#include "base/auto_reset.h" +#include "base/logging.h" +#include "ui/events/gesture_event_details.h" + +namespace ui { +namespace { + +// A BitSet32 is used for tracking dropped gesture types. +COMPILE_ASSERT(ET_GESTURE_TYPE_END - ET_GESTURE_TYPE_START < 32, + gesture_type_count_too_large); + +GestureEventData CreateGesture(EventType type, + int motion_event_id, + const GestureEventDataPacket& packet) { + return GestureEventData(GestureEventDetails(type, 0, 0), + motion_event_id, + packet.timestamp(), + packet.touch_location().x(), + packet.touch_location().y(), + packet.raw_touch_location().x(), + packet.raw_touch_location().y(), + 1, + gfx::RectF(packet.touch_location(), gfx::SizeF())); +} + +enum RequiredTouches { + RT_NONE = 0, + RT_START = 1 << 0, + RT_CURRENT = 1 << 1, +}; + +struct DispositionHandlingInfo { + // A bitwise-OR of |RequiredTouches|. + int required_touches; + EventType antecedent_event_type; + + explicit DispositionHandlingInfo(int required_touches) + : required_touches(required_touches), antecedent_event_type(ET_UNKNOWN) {} + + DispositionHandlingInfo(int required_touches, + EventType antecedent_event_type) + : required_touches(required_touches), + antecedent_event_type(antecedent_event_type) {} +}; + +DispositionHandlingInfo Info(int required_touches) { + return DispositionHandlingInfo(required_touches); +} + +DispositionHandlingInfo Info(int required_touches, + EventType antecedent_event_type) { + return DispositionHandlingInfo(required_touches, antecedent_event_type); +} + +// This approach to disposition handling is described at http://goo.gl/5G8PWJ. +DispositionHandlingInfo GetDispositionHandlingInfo(EventType type) { + switch (type) { + case ET_GESTURE_TAP_DOWN: + return Info(RT_START); + case ET_GESTURE_TAP_CANCEL: + return Info(RT_START); + case ET_GESTURE_SHOW_PRESS: + return Info(RT_START); + case ET_GESTURE_LONG_PRESS: + return Info(RT_START); + case ET_GESTURE_LONG_TAP: + return Info(RT_START | RT_CURRENT); + case ET_GESTURE_TAP: + return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED); + case ET_GESTURE_TAP_UNCONFIRMED: + return Info(RT_START | RT_CURRENT); + case ET_GESTURE_DOUBLE_TAP: + return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED); + case ET_GESTURE_SCROLL_BEGIN: + return Info(RT_START | RT_CURRENT); + case ET_GESTURE_SCROLL_UPDATE: + return Info(RT_CURRENT, ET_GESTURE_SCROLL_BEGIN); + case ET_GESTURE_SCROLL_END: + return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN); + case ET_SCROLL_FLING_START: + // We rely on |EndScrollGestureIfNecessary| to end the scroll if the fling + // start is prevented. + return Info(RT_NONE, ET_GESTURE_SCROLL_UPDATE); + case ET_SCROLL_FLING_CANCEL: + return Info(RT_NONE, ET_SCROLL_FLING_START); + case ET_GESTURE_PINCH_BEGIN: + return Info(RT_START, ET_GESTURE_SCROLL_BEGIN); + case ET_GESTURE_PINCH_UPDATE: + return Info(RT_CURRENT, ET_GESTURE_PINCH_BEGIN); + case ET_GESTURE_PINCH_END: + return Info(RT_NONE, ET_GESTURE_PINCH_BEGIN); + case ET_GESTURE_BEGIN: + return Info(RT_START); + case ET_GESTURE_END: + return Info(RT_NONE, ET_GESTURE_BEGIN); + case ET_GESTURE_SWIPE: + return Info(RT_START, ET_GESTURE_SCROLL_BEGIN); + case ET_GESTURE_TWO_FINGER_TAP: + return Info(RT_START); + default: + break; + } + NOTREACHED(); + return Info(RT_NONE); +} + +int GetGestureTypeIndex(EventType type) { + DCHECK_GE(type, ET_GESTURE_TYPE_START); + DCHECK_LE(type, ET_GESTURE_TYPE_END); + return type - ET_GESTURE_TYPE_START; +} + +bool IsTouchStartEvent(GestureEventDataPacket::GestureSource gesture_source) { + return gesture_source == GestureEventDataPacket::TOUCH_SEQUENCE_START || + gesture_source == GestureEventDataPacket::TOUCH_START; +} + +} // namespace + +// TouchDispositionGestureFilter + +TouchDispositionGestureFilter::TouchDispositionGestureFilter( + TouchDispositionGestureFilterClient* client) + : client_(client), + needs_tap_ending_event_(false), + needs_show_press_event_(false), + needs_fling_ending_event_(false), + needs_scroll_ending_event_(false) { + DCHECK(client_); +} + +TouchDispositionGestureFilter::~TouchDispositionGestureFilter() { +} + +TouchDispositionGestureFilter::PacketResult +TouchDispositionGestureFilter::OnGesturePacket( + const GestureEventDataPacket& packet) { + if (packet.gesture_source() == GestureEventDataPacket::UNDEFINED || + packet.gesture_source() == GestureEventDataPacket::INVALID) + return INVALID_PACKET_TYPE; + + if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START) + sequences_.push(GestureSequence()); + + if (IsEmpty()) + return INVALID_PACKET_ORDER; + + if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT && + Tail().empty()) { + // Handle the timeout packet immediately if the packet preceding the timeout + // has already been dispatched. + FilterAndSendPacket(packet); + return SUCCESS; + } + + Tail().push(packet); + return SUCCESS; +} + +void TouchDispositionGestureFilter::OnTouchEventAck(bool event_consumed) { + // Spurious touch acks from the renderer should not trigger a crash. + if (IsEmpty() || (Head().empty() && sequences_.size() == 1)) + return; + + if (Head().empty()) + PopGestureSequence(); + + GestureSequence& sequence = Head(); + + // Dispatch the packet corresponding to the ack'ed touch, as well as any + // additional timeout-based packets queued before the ack was received. + bool touch_packet_for_current_ack_handled = false; + while (!sequence.empty()) { + DCHECK_NE(sequence.front().gesture_source(), + GestureEventDataPacket::UNDEFINED); + DCHECK_NE(sequence.front().gesture_source(), + GestureEventDataPacket::INVALID); + + GestureEventDataPacket::GestureSource source = + sequence.front().gesture_source(); + if (source != GestureEventDataPacket::TOUCH_TIMEOUT) { + // We should handle at most one non-timeout based packet. + if (touch_packet_for_current_ack_handled) + break; + state_.OnTouchEventAck(event_consumed, IsTouchStartEvent(source)); + touch_packet_for_current_ack_handled = true; + } + // We need to pop the current sequence before sending the packet, because + // sending the packet could result in this method being re-entered (e.g. on + // Aura, we could trigger a touch-cancel). As popping the sequence destroys + // the packet, we copy the packet before popping it. + const GestureEventDataPacket packet = sequence.front(); + sequence.pop(); + FilterAndSendPacket(packet); + } + DCHECK(touch_packet_for_current_ack_handled); +} + +bool TouchDispositionGestureFilter::IsEmpty() const { + return sequences_.empty(); +} + +void TouchDispositionGestureFilter::FilterAndSendPacket( + const GestureEventDataPacket& packet) { + if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START) { + CancelTapIfNecessary(packet); + EndScrollIfNecessary(packet); + CancelFlingIfNecessary(packet); + } else if (packet.gesture_source() == GestureEventDataPacket::TOUCH_START) { + CancelTapIfNecessary(packet); + } + + for (size_t i = 0; i < packet.gesture_count(); ++i) { + const GestureEventData& gesture = packet.gesture(i); + DCHECK_GE(gesture.details.type(), ET_GESTURE_TYPE_START); + DCHECK_LE(gesture.details.type(), ET_GESTURE_TYPE_END); + if (state_.Filter(gesture.details.type())) { + CancelTapIfNecessary(packet); + continue; + } + if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT) { + // Sending a timed gesture could delete |this|, so we need to return + // directly after the |SendGesture| call. + SendGesture(gesture, packet); + return; + } + + SendGesture(gesture, packet); + } + + if (packet.gesture_source() == + GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL) { + EndScrollIfNecessary(packet); + CancelTapIfNecessary(packet); + } else if (packet.gesture_source() == + GestureEventDataPacket::TOUCH_SEQUENCE_END) { + EndScrollIfNecessary(packet); + } +} + +void TouchDispositionGestureFilter::SendGesture( + const GestureEventData& event, + const GestureEventDataPacket& packet_being_sent) { + // TODO(jdduke): Factor out gesture stream reparation code into a standalone + // utility class. + switch (event.type()) { + case ET_GESTURE_LONG_TAP: + if (!needs_tap_ending_event_) + return; + CancelTapIfNecessary(packet_being_sent); + CancelFlingIfNecessary(packet_being_sent); + break; + case ET_GESTURE_TAP_DOWN: + DCHECK(!needs_tap_ending_event_); + ending_event_motion_event_id_ = event.motion_event_id; + needs_show_press_event_ = true; + needs_tap_ending_event_ = true; + break; + case ET_GESTURE_SHOW_PRESS: + if (!needs_show_press_event_) + return; + needs_show_press_event_ = false; + break; + case ET_GESTURE_DOUBLE_TAP: + CancelTapIfNecessary(packet_being_sent); + needs_show_press_event_ = false; + break; + case ET_GESTURE_TAP: + DCHECK(needs_tap_ending_event_); + if (needs_show_press_event_) { + SendGesture(GestureEventData(ET_GESTURE_SHOW_PRESS, event), + packet_being_sent); + DCHECK(!needs_show_press_event_); + } + needs_tap_ending_event_ = false; + break; + case ET_GESTURE_TAP_CANCEL: + needs_show_press_event_ = false; + needs_tap_ending_event_ = false; + break; + case ET_GESTURE_SCROLL_BEGIN: + CancelTapIfNecessary(packet_being_sent); + CancelFlingIfNecessary(packet_being_sent); + EndScrollIfNecessary(packet_being_sent); + ending_event_motion_event_id_ = event.motion_event_id; + needs_scroll_ending_event_ = true; + break; + case ET_GESTURE_SCROLL_END: + needs_scroll_ending_event_ = false; + break; + case ET_SCROLL_FLING_START: + CancelFlingIfNecessary(packet_being_sent); + ending_event_motion_event_id_ = event.motion_event_id; + needs_fling_ending_event_ = true; + needs_scroll_ending_event_ = false; + break; + case ET_SCROLL_FLING_CANCEL: + needs_fling_ending_event_ = false; + break; + default: + break; + } + client_->ForwardGestureEvent(event); +} + +void TouchDispositionGestureFilter::CancelTapIfNecessary( + const GestureEventDataPacket& packet_being_sent) { + if (!needs_tap_ending_event_) + return; + + SendGesture(CreateGesture(ET_GESTURE_TAP_CANCEL, + ending_event_motion_event_id_, + packet_being_sent), + packet_being_sent); + DCHECK(!needs_tap_ending_event_); +} + +void TouchDispositionGestureFilter::CancelFlingIfNecessary( + const GestureEventDataPacket& packet_being_sent) { + if (!needs_fling_ending_event_) + return; + + SendGesture(CreateGesture(ET_SCROLL_FLING_CANCEL, + ending_event_motion_event_id_, + packet_being_sent), + packet_being_sent); + DCHECK(!needs_fling_ending_event_); +} + +void TouchDispositionGestureFilter::EndScrollIfNecessary( + const GestureEventDataPacket& packet_being_sent) { + if (!needs_scroll_ending_event_) + return; + + SendGesture(CreateGesture(ET_GESTURE_SCROLL_END, + ending_event_motion_event_id_, + packet_being_sent), + packet_being_sent); + DCHECK(!needs_scroll_ending_event_); +} + +void TouchDispositionGestureFilter::PopGestureSequence() { + DCHECK(Head().empty()); + state_ = GestureHandlingState(); + sequences_.pop(); +} + +TouchDispositionGestureFilter::GestureSequence& +TouchDispositionGestureFilter::Head() { + DCHECK(!sequences_.empty()); + return sequences_.front(); +} + +TouchDispositionGestureFilter::GestureSequence& +TouchDispositionGestureFilter::Tail() { + DCHECK(!sequences_.empty()); + return sequences_.back(); +} + +// TouchDispositionGestureFilter::GestureHandlingState + +TouchDispositionGestureFilter::GestureHandlingState::GestureHandlingState() + : start_touch_consumed_(false), + current_touch_consumed_(false) {} + +void TouchDispositionGestureFilter::GestureHandlingState::OnTouchEventAck( + bool event_consumed, + bool is_touch_start_event) { + current_touch_consumed_ = event_consumed; + if (event_consumed && is_touch_start_event) + start_touch_consumed_ = true; +} + +bool TouchDispositionGestureFilter::GestureHandlingState::Filter( + EventType gesture_type) { + DispositionHandlingInfo disposition_handling_info = + GetDispositionHandlingInfo(gesture_type); + + int required_touches = disposition_handling_info.required_touches; + EventType antecedent_event_type = + disposition_handling_info.antecedent_event_type; + if ((required_touches & RT_START && start_touch_consumed_) || + (required_touches & RT_CURRENT && current_touch_consumed_) || + (antecedent_event_type != ET_UNKNOWN && + last_gesture_of_type_dropped_.has_bit( + GetGestureTypeIndex(antecedent_event_type)))) { + last_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type)); + return true; + } + last_gesture_of_type_dropped_.clear_bit(GetGestureTypeIndex(gesture_type)); + return false; +} + +} // namespace content diff --git a/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h new file mode 100644 index 00000000000..6e57bdb5235 --- /dev/null +++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h @@ -0,0 +1,108 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_ +#define UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_ + +#include <queue> + +#include "ui/events/event_constants.h" +#include "ui/events/gesture_detection/bitset_32.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/gesture_event_data_packet.h" + +namespace ui { + +// Interface with which the |TouchDispositionGestureFilter| forwards gestures +// for a given touch event. +class GESTURE_DETECTION_EXPORT TouchDispositionGestureFilterClient { + public: + virtual void ForwardGestureEvent(const GestureEventData&) = 0; +}; + +// Given a stream of touch-derived gesture packets, produces a refined gesture +// sequence based on the ack dispositions of the generating touch events. +class GESTURE_DETECTION_EXPORT TouchDispositionGestureFilter { + public: + explicit TouchDispositionGestureFilter( + TouchDispositionGestureFilterClient* client); + ~TouchDispositionGestureFilter(); + + // To be called upon production of touch-derived gestures by the platform, + // *prior* to the generating touch being forward to the renderer. In + // particular, |packet| contains [0, n] gestures that correspond to a given + // touch event. It is imperative that a single packet is received for + // *each* touch event, even those that did not produce a gesture. + enum PacketResult { + SUCCESS, // Packet successfully queued. + INVALID_PACKET_ORDER, // Packets were received in the wrong order, i.e., + // TOUCH_BEGIN should always precede other packets. + INVALID_PACKET_TYPE, // Packet had an invalid type. + }; + PacketResult OnGesturePacket(const GestureEventDataPacket& packet); + + // To be called upon receipt of *all* touch event acks. + void OnTouchEventAck(bool event_consumed); + + // Whether there are any active gesture sequences still queued in the filter. + bool IsEmpty() const; + + private: + // A single GestureSequence corresponds to all gestures created + // between the first finger down and the last finger up, including gestures + // generated by timeouts from a statinoary finger. + typedef std::queue<GestureEventDataPacket> GestureSequence; + + // Utility class for maintaining the touch and gesture handling state for the + // current gesture sequence. + class GestureHandlingState { + public: + GestureHandlingState(); + + // To be called on each touch event ack. + void OnTouchEventAck(bool event_consumed, bool is_touch_start_event); + + // Returns true iff the gesture should be dropped. + bool Filter(EventType type); + + private: + // True iff the sequence has had any touch down event consumed. + bool start_touch_consumed_; + // True iff the most recently ack'ed touch event was consumed. + bool current_touch_consumed_; + // If the previous gesture of a given type was dropped instead of being + // dispatched, its type will occur in this set. + BitSet32 last_gesture_of_type_dropped_; + }; + + void FilterAndSendPacket(const GestureEventDataPacket& packet); + void SendGesture(const GestureEventData& gesture, + const GestureEventDataPacket& packet); + void CancelTapIfNecessary(const GestureEventDataPacket& packet); + void CancelFlingIfNecessary(const GestureEventDataPacket& packet); + void EndScrollIfNecessary(const GestureEventDataPacket& packet); + void PopGestureSequence(); + GestureSequence& Head(); + GestureSequence& Tail(); + + TouchDispositionGestureFilterClient* client_; + std::queue<GestureSequence> sequences_; + + GestureHandlingState state_; + + // Bookkeeping for inserting synthetic Gesture{Tap,Fling}Cancel events + // when necessary, e.g., GestureTapCancel when scrolling begins, or + // GestureFlingCancel when a user taps following a GestureFlingStart. + int ending_event_motion_event_id_; + bool needs_tap_ending_event_; + bool needs_show_press_event_; + bool needs_fling_ending_event_; + bool needs_scroll_ending_event_; + + DISALLOW_COPY_AND_ASSIGN(TouchDispositionGestureFilter); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_ diff --git a/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc new file mode 100644 index 00000000000..a5471f73cff --- /dev/null +++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc @@ -0,0 +1,1059 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/gesture_detection/mock_motion_event.h" +#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h" + +namespace ui { + +class TouchDispositionGestureFilterTest + : public testing::Test, + public TouchDispositionGestureFilterClient { + public: + TouchDispositionGestureFilterTest() + : cancel_after_next_gesture_(false), sent_gesture_count_(0) {} + virtual ~TouchDispositionGestureFilterTest() {} + + // testing::Test + virtual void SetUp() OVERRIDE { + queue_.reset(new TouchDispositionGestureFilter(this)); + } + + virtual void TearDown() OVERRIDE { + queue_.reset(); + } + + // TouchDispositionGestureFilterClient + virtual void ForwardGestureEvent(const GestureEventData& event) OVERRIDE { + ++sent_gesture_count_; + last_sent_gesture_time_ = event.time; + sent_gestures_.push_back(event.type()); + last_sent_gesture_location_ = gfx::PointF(event.x, event.y); + last_sent_gesture_raw_location_ = gfx::PointF(event.raw_x, event.raw_y); + if (cancel_after_next_gesture_) { + CancelTouchPoint(); + SendTouchNotConsumedAck(); + cancel_after_next_gesture_ = false; + } + } + + protected: + typedef std::vector<EventType> GestureList; + + ::testing::AssertionResult GesturesMatch(const GestureList& expected, + const GestureList& actual) { + if (expected.size() != actual.size()) { + return ::testing::AssertionFailure() + << "actual.size(" << actual.size() + << ") != expected.size(" << expected.size() << ")"; + } + + for (size_t i = 0; i < expected.size(); ++i) { + if (expected[i] != actual[i]) { + return ::testing::AssertionFailure() + << "actual[" << i << "] (" + << actual[i] + << ") != expected[" << i << "] (" + << expected[i] << ")"; + } + } + + return ::testing::AssertionSuccess(); + } + + GestureList Gestures(EventType type) { + return GestureList(1, type); + } + + GestureList Gestures(EventType type0, EventType type1) { + GestureList gestures(2); + gestures[0] = type0; + gestures[1] = type1; + return gestures; + } + + GestureList Gestures(EventType type0, + EventType type1, + EventType type2) { + GestureList gestures(3); + gestures[0] = type0; + gestures[1] = type1; + gestures[2] = type2; + return gestures; + } + + GestureList Gestures(EventType type0, + EventType type1, + EventType type2, + EventType type3) { + GestureList gestures(4); + gestures[0] = type0; + gestures[1] = type1; + gestures[2] = type2; + gestures[3] = type3; + return gestures; + } + + void SendTouchGestures() { + touch_event_.SetTime(base::TimeTicks::Now()); + EXPECT_EQ(TouchDispositionGestureFilter::SUCCESS, + SendTouchGestures(touch_event_, pending_gesture_packet_)); + GestureEventDataPacket gesture_packet; + std::swap(gesture_packet, pending_gesture_packet_); + } + + TouchDispositionGestureFilter::PacketResult + SendTouchGestures(const MotionEvent& touch, + const GestureEventDataPacket& packet) { + GestureEventDataPacket touch_packet = + GestureEventDataPacket::FromTouch(touch); + for (size_t i = 0; i < packet.gesture_count(); ++i) + touch_packet.Push(packet.gesture(i)); + return queue_->OnGesturePacket(touch_packet); + } + + TouchDispositionGestureFilter::PacketResult + SendTimeoutGesture(EventType type) { + return queue_->OnGesturePacket( + GestureEventDataPacket::FromTouchTimeout(CreateGesture(type))); + } + + TouchDispositionGestureFilter::PacketResult + SendGesturePacket(const GestureEventDataPacket& packet) { + return queue_->OnGesturePacket(packet); + } + + void SendTouchEventAck(bool event_consumed) { + queue_->OnTouchEventAck(event_consumed); + } + + void SendTouchConsumedAck() { SendTouchEventAck(true); } + + void SendTouchNotConsumedAck() { SendTouchEventAck(false); } + + void PushGesture(EventType type) { + pending_gesture_packet_.Push(CreateGesture(type)); + } + + void PressTouchPoint(int x, int y) { + touch_event_.PressPoint(x, y); + SendTouchGestures(); + } + + void MoveTouchPoint(size_t index, int x, int y) { + touch_event_.MovePoint(index, x, y); + SendTouchGestures(); + } + + void ReleaseTouchPoint() { + touch_event_.ReleasePoint(); + SendTouchGestures(); + } + + void CancelTouchPoint() { + touch_event_.CancelPoint(); + SendTouchGestures(); + } + + void SetRawTouchOffset(const gfx::Vector2dF& raw_offset) { + touch_event_.SetRawOffset(raw_offset.x(), raw_offset.y()); + } + + void ResetTouchPoints() { touch_event_ = MockMotionEvent(); } + + bool GesturesSent() const { return !sent_gestures_.empty(); } + + base::TimeTicks LastSentGestureTime() const { + return last_sent_gesture_time_; + } + + base::TimeTicks CurrentTouchTime() const { + return touch_event_.GetEventTime(); + } + + bool IsEmpty() const { return queue_->IsEmpty(); } + + GestureList GetAndResetSentGestures() { + GestureList sent_gestures; + sent_gestures.swap(sent_gestures_); + return sent_gestures; + } + + const gfx::PointF& LastSentGestureLocation() const { + return last_sent_gesture_location_; + } + + const gfx::PointF& LastSentGestureRawLocation() const { + return last_sent_gesture_raw_location_; + } + + void SetCancelAfterNextGesture(bool cancel_after_next_gesture) { + cancel_after_next_gesture_ = cancel_after_next_gesture; + } + + GestureEventData CreateGesture(EventType type) { + return GestureEventData(GestureEventDetails(type, 0, 0), + 0, + base::TimeTicks(), + touch_event_.GetX(0), + touch_event_.GetY(0), + touch_event_.GetRawX(0), + touch_event_.GetRawY(0), + 1, + gfx::RectF(0, 0, 0, 0)); + } + + private: + scoped_ptr<TouchDispositionGestureFilter> queue_; + bool cancel_after_next_gesture_; + MockMotionEvent touch_event_; + GestureEventDataPacket pending_gesture_packet_; + size_t sent_gesture_count_; + base::TimeTicks last_sent_gesture_time_; + GestureList sent_gestures_; + gfx::PointF last_sent_gesture_location_; + gfx::PointF last_sent_gesture_raw_location_; +}; + +TEST_F(TouchDispositionGestureFilterTest, BasicNoGestures) { + PressTouchPoint(1, 1); + EXPECT_FALSE(GesturesSent()); + + MoveTouchPoint(0, 2, 2); + EXPECT_FALSE(GesturesSent()); + + // No gestures should be dispatched by the ack, as the queued packets + // contained no gestures. + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // Release the touch gesture. + ReleaseTouchPoint(); + SendTouchConsumedAck(); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, BasicGestures) { + // An unconsumed touch's gesture should be sent. + PushGesture(ET_GESTURE_BEGIN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + EXPECT_FALSE(GesturesSent()); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // Multiple gestures can be queued for a single event. + PushGesture(ET_SCROLL_FLING_START); + PushGesture(ET_SCROLL_FLING_CANCEL); + PushGesture(ET_GESTURE_END); + ReleaseTouchPoint(); + EXPECT_FALSE(GesturesSent()); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START, + ET_SCROLL_FLING_CANCEL, + ET_GESTURE_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, BasicGesturesConsumed) { + // A consumed touch's gesture should not be sent. + PushGesture(ET_GESTURE_BEGIN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_SCROLL_FLING_START); + PushGesture(ET_SCROLL_FLING_CANCEL); + PushGesture(ET_GESTURE_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, ConsumedThenNotConsumed) { + // A consumed touch's gesture should not be sent. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // Even if the subsequent touch is not consumed, continue dropping gestures. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // Even if the subsequent touch had no consumer, continue dropping gestures. + PushGesture(ET_SCROLL_FLING_START); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, NotConsumedThenConsumed) { + // A not consumed touch's gesture should be sent. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // A newly consumed gesture should not be sent. + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(10, 10); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // And subsequent non-consumed pinch updates should not be sent. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + PushGesture(ET_GESTURE_PINCH_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE), + GetAndResetSentGestures())); + + // End events dispatched only when their start events were. + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, ScrollAlternatelyConsumed) { + // A consumed touch's gesture should not be sent. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + for (size_t i = 0; i < 3; ++i) { + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 3, 3); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE), + GetAndResetSentGestures())); + } + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, NotConsumedThenNoConsumer) { + // An unconsumed touch's gesture should be sent. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // If the subsequent touch has no consumer (e.g., a secondary pointer is + // pressed but not on a touch handling rect), send the gesture. + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(2, 2); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN), + GetAndResetSentGestures())); + + // End events should be dispatched when their start events were, independent + // of the ack state. + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, EndingEventsSent) { + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(2, 2); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN), + GetAndResetSentGestures())); + + // Consuming the touchend event can't suppress the match end gesture. + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END), + GetAndResetSentGestures())); + + // But other events in the same packet are still suppressed. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); + + // ET_GESTURE_SCROLL_END and ET_SCROLL_FLING_START behave the same in this + // regard. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_SCROLL_FLING_START); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, EndingEventsNotSent) { + // Consuming a begin event ensures no end events are sent. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(2, 2); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, UpdateEventsSuppressedPerEvent) { + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // Consuming a single scroll or pinch update should suppress only that event. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(2, 2); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_PINCH_UPDATE); + MoveTouchPoint(1, 2, 3); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // Subsequent updates should not be affected. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 4, 4); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_PINCH_UPDATE); + MoveTouchPoint(0, 4, 5); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_UPDATE), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, UpdateEventsDependOnBeginEvents) { + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // Scroll and pinch gestures depend on the scroll begin gesture being + // dispatched. + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(2, 2); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_UPDATE); + MoveTouchPoint(1, 2, 3); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, MultipleTouchSequences) { + // Queue two touch-to-gestures sequences. + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + PushGesture(ET_GESTURE_TAP); + ReleaseTouchPoint(); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + PushGesture(ET_GESTURE_SCROLL_END); + ReleaseTouchPoint(); + + // The first gesture sequence should not be allowed. + SendTouchConsumedAck(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + // The subsequent sequence should "reset" allowance. + SendTouchNotConsumedAck(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN, + ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, FlingCancelledOnNewTouchSequence) { + const gfx::Vector2dF raw_offset(1.3f, 3.7f); + SetRawTouchOffset(raw_offset); + // Simulate a fling. + PushGesture(ET_GESTURE_TAP_DOWN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch( + Gestures( + ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + PushGesture(ET_SCROLL_FLING_START); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START), + GetAndResetSentGestures())); + + // A new touch sequence should cancel the outstanding fling. + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); + EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, ScrollEndedOnTouchReleaseIfNoFling) { + // Simulate a scroll. + PushGesture(ET_GESTURE_TAP_DOWN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch( + Gestures( + ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); +} + +TEST_F(TouchDispositionGestureFilterTest, ScrollEndedOnNewTouchSequence) { + // Simulate a scroll. + PushGesture(ET_GESTURE_TAP_DOWN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch( + Gestures( + ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // A new touch sequence should end the outstanding scroll. + ResetTouchPoints(); + PressTouchPoint(2, 3); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 3)); +} + +TEST_F(TouchDispositionGestureFilterTest, FlingCancelledOnScrollBegin) { + // Simulate a fling sequence. + PushGesture(ET_GESTURE_TAP_DOWN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PushGesture(ET_SCROLL_FLING_START); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN, + ET_GESTURE_TAP_CANCEL, + ET_GESTURE_SCROLL_BEGIN, + ET_SCROLL_FLING_START), + GetAndResetSentGestures())); + + // The new fling should cancel the preceding one. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PushGesture(ET_SCROLL_FLING_START); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_CANCEL, + ET_GESTURE_SCROLL_BEGIN, + ET_SCROLL_FLING_START), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, FlingNotCancelledIfGFCEventReceived) { + // Simulate a fling that is started then cancelled. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + PushGesture(ET_SCROLL_FLING_START); + MoveTouchPoint(0, 2, 3); + SendTouchNotConsumedAck(); + PushGesture(ET_SCROLL_FLING_CANCEL); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN, + ET_SCROLL_FLING_START, + ET_SCROLL_FLING_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 3)); + + // A new touch sequence will not inject a ET_SCROLL_FLING_CANCEL, as the fling + // has already been cancelled. + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, TapCancelledWhenScrollBegins) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + // If the subsequent touch turns into a scroll, the tap should be cancelled. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + MoveTouchPoint(0, 2, 2); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, + ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, TapCancelledWhenTouchConsumed) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + // If the subsequent touch is consumed, the tap should be cancelled. + PushGesture(ET_GESTURE_SCROLL_BEGIN); + MoveTouchPoint(0, 2, 2); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 2)); +} + +TEST_F(TouchDispositionGestureFilterTest, + TapNotCancelledIfTapEndingEventReceived) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE( + GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_TAP); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP), + GetAndResetSentGestures())); + + // The tap should not be cancelled as it was terminated by a |ET_GESTURE_TAP|. + PressTouchPoint(2, 2); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, TimeoutGestures) { + // If the sequence is allowed, and there are no preceding gestures, the + // timeout gestures should be forwarded immediately. + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + SendTimeoutGesture(ET_GESTURE_SHOW_PRESS); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS), + GetAndResetSentGestures())); + + SendTimeoutGesture(ET_GESTURE_LONG_PRESS); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_LONG_PRESS), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_LONG_TAP); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, + ET_GESTURE_LONG_TAP), + GetAndResetSentGestures())); + + // If the sequence is disallowed, and there are no preceding gestures, the + // timeout gestures should be dropped immediately. + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + SendTimeoutGesture(ET_GESTURE_SHOW_PRESS); + EXPECT_FALSE(GesturesSent()); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + + // If the sequence has a pending ack, the timeout gestures should + // remain queued until the ack is received. + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + EXPECT_FALSE(GesturesSent()); + + SendTimeoutGesture(ET_GESTURE_LONG_PRESS); + EXPECT_FALSE(GesturesSent()); + + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN, + ET_GESTURE_LONG_PRESS), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, SpuriousAcksIgnored) { + // Acks received when the queue is empty will be safely ignored. + ASSERT_TRUE(IsEmpty()); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 3,3); + SendTouchNotConsumedAck(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN, + ET_GESTURE_SCROLL_UPDATE), + GetAndResetSentGestures())); + + // Even if all packets have been dispatched, the filter may not be empty as + // there could be follow-up timeout events. Spurious acks in such cases + // should also be safely ignored. + ASSERT_FALSE(IsEmpty()); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, PacketWithInvalidTypeIgnored) { + GestureEventDataPacket packet; + EXPECT_EQ(TouchDispositionGestureFilter::INVALID_PACKET_TYPE, + SendGesturePacket(packet)); + EXPECT_TRUE(IsEmpty()); +} + +TEST_F(TouchDispositionGestureFilterTest, PacketsWithInvalidOrderIgnored) { + EXPECT_EQ(TouchDispositionGestureFilter::INVALID_PACKET_ORDER, + SendTimeoutGesture(ET_GESTURE_SHOW_PRESS)); + EXPECT_TRUE(IsEmpty()); +} + +TEST_F(TouchDispositionGestureFilterTest, ConsumedTouchCancel) { + // An unconsumed touch's gesture should be sent. + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + EXPECT_FALSE(GesturesSent()); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_TAP_CANCEL); + PushGesture(ET_GESTURE_SCROLL_END); + CancelTouchPoint(); + EXPECT_FALSE(GesturesSent()); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, + ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, TimeoutEventAfterRelease) { + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + PushGesture(ET_GESTURE_TAP_DOWN); + PushGesture(ET_GESTURE_TAP_UNCONFIRMED); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE( + GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_UNCONFIRMED), + GetAndResetSentGestures())); + + SendTimeoutGesture(ET_GESTURE_TAP); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, ShowPressInsertedBeforeTap) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + SendTimeoutGesture(ET_GESTURE_TAP_UNCONFIRMED); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_UNCONFIRMED), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_TAP); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, + ET_GESTURE_TAP), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, ShowPressNotInsertedIfAlreadySent) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + SendTimeoutGesture(ET_GESTURE_SHOW_PRESS); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_TAP); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, TapAndScrollCancelledOnTouchCancel) { + const gfx::Vector2dF raw_offset(1.3f, 3.7f); + SetRawTouchOffset(raw_offset); + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + // A cancellation motion event should cancel the tap. + CancelTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); + EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset); + + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + // A cancellation motion event should end the scroll, even if the touch was + // consumed. + CancelTouchPoint(); + SendTouchConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); + EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset); +} + +TEST_F(TouchDispositionGestureFilterTest, + ConsumedScrollUpdateMakesFlingScrollEnd) { + // A consumed touch's gesture should not be sent. + PushGesture(ET_GESTURE_BEGIN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + + EXPECT_TRUE( + GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_SCROLL_UPDATE); + MoveTouchPoint(0, 2, 2); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_SCROLL_FLING_START); + PushGesture(ET_SCROLL_FLING_CANCEL); + PushGesture(ET_GESTURE_END); + ReleaseTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_END, ET_GESTURE_SCROLL_END), + GetAndResetSentGestures())); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 2)); + + PushGesture(ET_GESTURE_BEGIN); + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); +} + +TEST_F(TouchDispositionGestureFilterTest, TapCancelledOnTouchCancel) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + // A cancellation motion event should cancel the tap. + CancelTouchPoint(); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime()); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); +} + +// Test that a GestureEvent whose dispatch causes a cancel event to be fired +// won't cause a crash. +TEST_F(TouchDispositionGestureFilterTest, TestCancelMidGesture) { + SetCancelAfterNextGesture(true); + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN, + ET_GESTURE_TAP_CANCEL), + GetAndResetSentGestures())); + EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1)); +} + +// Test that a MultiFingerSwipe event is dispatched when appropriate. +TEST_F(TouchDispositionGestureFilterTest, TestAllowedMultiFingerSwipe) { + PushGesture(ET_GESTURE_SCROLL_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN), + GetAndResetSentGestures())); + + PushGesture(ET_GESTURE_SWIPE); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SWIPE), + GetAndResetSentGestures())); +} + + // Test that a MultiFingerSwipe event is dispatched when appropriate. +TEST_F(TouchDispositionGestureFilterTest, TestDisallowedMultiFingerSwipe) { + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + + PushGesture(ET_GESTURE_SCROLL_BEGIN); + MoveTouchPoint(0, 0, 0); + SendTouchConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_PINCH_BEGIN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); + + PushGesture(ET_GESTURE_SWIPE); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_FALSE(GesturesSent()); +} + +TEST_F(TouchDispositionGestureFilterTest, TapCancelOnSecondFingerDown) { + PushGesture(ET_GESTURE_TAP_DOWN); + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), + GetAndResetSentGestures())); + + PressTouchPoint(1, 1); + SendTouchNotConsumedAck(); + EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL), + GetAndResetSentGestures())); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/velocity_tracker.cc b/chromium/ui/events/gesture_detection/velocity_tracker.cc new file mode 100644 index 00000000000..da9f8a7bf15 --- /dev/null +++ b/chromium/ui/events/gesture_detection/velocity_tracker.cc @@ -0,0 +1,805 @@ +// Copyright 2014 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/gesture_detection/velocity_tracker.h" + +#include <cmath> + +#include "base/logging.h" +#include "ui/events/gesture_detection/motion_event.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace ui { + +// Implements a particular velocity tracker algorithm. +class VelocityTrackerStrategy { + public: + virtual ~VelocityTrackerStrategy() {} + + virtual void Clear() = 0; + virtual void ClearPointers(BitSet32 id_bits) = 0; + virtual void AddMovement(const base::TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) = 0; + virtual bool GetEstimator(uint32_t id, Estimator* out_estimator) const = 0; + + protected: + VelocityTrackerStrategy() {} +}; + +namespace { + +COMPILE_ASSERT(MotionEvent::MAX_POINTER_ID < 32, max_pointer_id_too_large); + +// Threshold for determining that a pointer has stopped moving. +// Some input devices do not send ACTION_MOVE events in the case where a pointer +// has stopped. We need to detect this case so that we can accurately predict +// the velocity after the pointer starts moving again. +const int kAssumePointerStoppedTimeMs = 40; + +struct Position { + float x, y; +}; + +struct Estimator { + enum { MAX_DEGREE = 4 }; + + // Estimator time base. + TimeTicks time; + + // Polynomial coefficients describing motion in X and Y. + float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 1]; + + // Polynomial degree (number of coefficients), or zero if no information is + // available. + uint32_t degree; + + // Confidence (coefficient of determination), between 0 (no fit) + // and 1 (perfect fit). + float confidence; + + inline void Clear() { + time = TimeTicks(); + degree = 0; + confidence = 0; + for (size_t i = 0; i <= MAX_DEGREE; i++) { + xcoeff[i] = 0; + ycoeff[i] = 0; + } + } +}; + +float VectorDot(const float* a, const float* b, uint32_t m) { + float r = 0; + while (m--) { + r += *(a++) * *(b++); + } + return r; +} + +float VectorNorm(const float* a, uint32_t m) { + float r = 0; + while (m--) { + float t = *(a++); + r += t * t; + } + return sqrtf(r); +} + +// Velocity tracker algorithm based on least-squares linear regression. +class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { + public: + enum Weighting { + // No weights applied. All data points are equally reliable. + WEIGHTING_NONE, + + // Weight by time delta. Data points clustered together are weighted less. + WEIGHTING_DELTA, + + // Weight such that points within a certain horizon are weighed more than + // those outside of that horizon. + WEIGHTING_CENTRAL, + + // Weight such that points older than a certain amount are weighed less. + WEIGHTING_RECENT, + }; + + // Number of samples to keep. + enum { HISTORY_SIZE = 20 }; + + // Degree must be no greater than Estimator::MAX_DEGREE. + LeastSquaresVelocityTrackerStrategy(uint32_t degree, + Weighting weighting = WEIGHTING_NONE); + virtual ~LeastSquaresVelocityTrackerStrategy(); + + virtual void Clear() OVERRIDE; + virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; + virtual void AddMovement(const TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) OVERRIDE; + virtual bool GetEstimator(uint32_t id, + Estimator* out_estimator) const OVERRIDE; + + private: + // Sample horizon. + // We don't use too much history by default since we want to react to quick + // changes in direction. + enum { HORIZON_MS = 100 }; + + struct Movement { + TimeTicks event_time; + BitSet32 id_bits; + Position positions[VelocityTracker::MAX_POINTERS]; + + inline const Position& GetPosition(uint32_t id) const { + return positions[id_bits.get_index_of_bit(id)]; + } + }; + + float ChooseWeight(uint32_t index) const; + + const uint32_t degree_; + const Weighting weighting_; + uint32_t index_; + Movement movements_[HISTORY_SIZE]; +}; + +// Velocity tracker algorithm that uses an IIR filter. +class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy { + public: + // Degree must be 1 or 2. + explicit IntegratingVelocityTrackerStrategy(uint32_t degree); + virtual ~IntegratingVelocityTrackerStrategy(); + + virtual void Clear() OVERRIDE; + virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; + virtual void AddMovement(const TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) OVERRIDE; + virtual bool GetEstimator(uint32_t id, + Estimator* out_estimator) const OVERRIDE; + + private: + // Current state estimate for a particular pointer. + struct State { + TimeTicks update_time; + uint32_t degree; + + float xpos, xvel, xaccel; + float ypos, yvel, yaccel; + }; + + const uint32_t degree_; + BitSet32 pointer_id_bits_; + State mPointerState[MotionEvent::MAX_POINTER_ID + 1]; + + void InitState(State& state, + const TimeTicks& event_time, + float xpos, + float ypos) const; + void UpdateState(State& state, + const TimeTicks& event_time, + float xpos, + float ypos) const; + void PopulateEstimator(const State& state, Estimator* out_estimator) const; +}; + +VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) { + switch (strategy) { + case VelocityTracker::LSQ1: + return new LeastSquaresVelocityTrackerStrategy(1); + case VelocityTracker::LSQ2: + return new LeastSquaresVelocityTrackerStrategy(2); + case VelocityTracker::LSQ3: + return new LeastSquaresVelocityTrackerStrategy(3); + case VelocityTracker::WLSQ2_DELTA: + return new LeastSquaresVelocityTrackerStrategy( + 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA); + case VelocityTracker::WLSQ2_CENTRAL: + return new LeastSquaresVelocityTrackerStrategy( + 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL); + case VelocityTracker::WLSQ2_RECENT: + return new LeastSquaresVelocityTrackerStrategy( + 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT); + case VelocityTracker::INT1: + return new IntegratingVelocityTrackerStrategy(1); + case VelocityTracker::INT2: + return new IntegratingVelocityTrackerStrategy(2); + } + NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy; + return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT); +} + +} // namespace + +// --- VelocityTracker --- + +VelocityTracker::VelocityTracker() + : current_pointer_id_bits_(0), + active_pointer_id_(-1), + strategy_(CreateStrategy(STRATEGY_DEFAULT)) {} + +VelocityTracker::VelocityTracker(Strategy strategy) + : current_pointer_id_bits_(0), + active_pointer_id_(-1), + strategy_(CreateStrategy(strategy)) {} + +VelocityTracker::~VelocityTracker() {} + +void VelocityTracker::Clear() { + current_pointer_id_bits_.clear(); + active_pointer_id_ = -1; + strategy_->Clear(); +} + +void VelocityTracker::ClearPointers(BitSet32 id_bits) { + BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value); + current_pointer_id_bits_ = remaining_id_bits; + + if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) { + active_pointer_id_ = !remaining_id_bits.is_empty() + ? remaining_id_bits.first_marked_bit() + : -1; + } + + strategy_->ClearPointers(id_bits); +} + +void VelocityTracker::AddMovement(const TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) { + while (id_bits.count() > MAX_POINTERS) + id_bits.clear_last_marked_bit(); + + if ((current_pointer_id_bits_.value & id_bits.value) && + event_time >= (last_event_time_ + base::TimeDelta::FromMilliseconds( + kAssumePointerStoppedTimeMs))) { + // We have not received any movements for too long. Assume that all + // pointers + // have stopped. + strategy_->Clear(); + } + last_event_time_ = event_time; + + current_pointer_id_bits_ = id_bits; + if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_)) + active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit(); + + strategy_->AddMovement(event_time, id_bits, positions); +} + +void VelocityTracker::AddMovement(const MotionEvent& event) { + int32_t actionMasked = event.GetAction(); + + switch (actionMasked) { + case MotionEvent::ACTION_DOWN: + // case MotionEvent::HOVER_ENTER: + // Clear all pointers on down before adding the new movement. + Clear(); + break; + case MotionEvent::ACTION_POINTER_DOWN: { + // Start a new movement trace for a pointer that just went down. + // We do this on down instead of on up because the client may want to + // query the final velocity for a pointer that just went up. + BitSet32 downIdBits; + downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex())); + ClearPointers(downIdBits); + break; + } + case MotionEvent::ACTION_MOVE: + // case MotionEvent::ACTION_HOVER_MOVE: + break; + default: + // Ignore all other actions because they do not convey any new information + // about pointer movement. We also want to preserve the last known + // velocity of the pointers. + // Note that ACTION_UP and ACTION_POINTER_UP always report the last known + // position of the pointers that went up. ACTION_POINTER_UP does include + // the new position of pointers that remained down but we will also + // receive an ACTION_MOVE with this information if any of them actually + // moved. Since we don't know how many pointers will be going up at once + // it makes sense to just wait for the following ACTION_MOVE before adding + // the movement. + return; + } + + size_t pointer_count = event.GetPointerCount(); + if (pointer_count > MAX_POINTERS) { + pointer_count = MAX_POINTERS; + } + + BitSet32 id_bits; + for (size_t i = 0; i < pointer_count; i++) { + id_bits.mark_bit(event.GetPointerId(i)); + } + + uint32_t pointer_index[MAX_POINTERS]; + for (size_t i = 0; i < pointer_count; i++) { + pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i)); + } + + Position positions[MAX_POINTERS]; + size_t historySize = event.GetHistorySize(); + for (size_t h = 0; h < historySize; h++) { + for (size_t i = 0; i < pointer_count; i++) { + uint32_t index = pointer_index[i]; + positions[index].x = event.GetHistoricalX(i, h); + positions[index].y = event.GetHistoricalY(i, h); + } + AddMovement(event.GetHistoricalEventTime(h), id_bits, positions); + } + + for (size_t i = 0; i < pointer_count; i++) { + uint32_t index = pointer_index[i]; + positions[index].x = event.GetX(i); + positions[index].y = event.GetY(i); + } + AddMovement(event.GetEventTime(), id_bits, positions); +} + +bool VelocityTracker::GetVelocity(uint32_t id, + float* out_vx, + float* out_vy) const { + Estimator estimator; + if (GetEstimator(id, &estimator) && estimator.degree >= 1) { + *out_vx = estimator.xcoeff[1]; + *out_vy = estimator.ycoeff[1]; + return true; + } + *out_vx = 0; + *out_vy = 0; + return false; +} + +void LeastSquaresVelocityTrackerStrategy::AddMovement( + const TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) { + if (++index_ == HISTORY_SIZE) { + index_ = 0; + } + + Movement& movement = movements_[index_]; + movement.event_time = event_time; + movement.id_bits = id_bits; + uint32_t count = id_bits.count(); + for (uint32_t i = 0; i < count; i++) { + movement.positions[i] = positions[i]; + } +} + +bool VelocityTracker::GetEstimator(uint32_t id, + Estimator* out_estimator) const { + return strategy_->GetEstimator(id, out_estimator); +} + +// --- LeastSquaresVelocityTrackerStrategy --- + +LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( + uint32_t degree, + Weighting weighting) + : degree_(degree), weighting_(weighting) { + DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE)); + Clear(); +} + +LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} + +void LeastSquaresVelocityTrackerStrategy::Clear() { + index_ = 0; + movements_[0].id_bits.clear(); +} + +/** + * Solves a linear least squares problem to obtain a N degree polynomial that + * fits the specified input data as nearly as possible. + * + * Returns true if a solution is found, false otherwise. + * + * The input consists of two vectors of data points X and Y with indices 0..m-1 + * along with a weight vector W of the same size. + * + * The output is a vector B with indices 0..n that describes a polynomial + * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] + * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is + * minimized. + * + * Accordingly, the weight vector W should be initialized by the caller with the + * reciprocal square root of the variance of the error in each input data point. + * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / + * stddev(Y[i]). + * The weights express the relative importance of each data point. If the + * weights are* all 1, then the data points are considered to be of equal + * importance when fitting the polynomial. It is a good idea to choose weights + * that diminish the importance of data points that may have higher than usual + * error margins. + * + * Errors among data points are assumed to be independent. W is represented + * here as a vector although in the literature it is typically taken to be a + * diagonal matrix. + * + * That is to say, the function that generated the input data can be + * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. + * + * The coefficient of determination (R^2) is also returned to describe the + * goodness of fit of the model for the given data. It is a value between 0 + * and 1, where 1 indicates perfect correspondence. + * + * This function first expands the X vector to a m by n matrix A such that + * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then + * multiplies it by w[i]./ + * + * Then it calculates the QR decomposition of A yielding an m by m orthonormal + * matrix Q and an m by n upper triangular matrix R. Because R is upper + * triangular (lower part is all zeroes), we can simplify the decomposition into + * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1. + * + * Finally we solve the system of linear equations given by + * R1 B = (Qtranspose W Y) to find B. + * + * For efficiency, we lay out A and Q column-wise in memory because we + * frequently operate on the column vectors. Conversely, we lay out R row-wise. + * + * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares + * http://en.wikipedia.org/wiki/Gram-Schmidt + */ +static bool SolveLeastSquares(const float* x, + const float* y, + const float* w, + uint32_t m, + uint32_t n, + float* out_b, + float* out_det) { + // MSVC does not support variable-length arrays (used by the original Android + // implementation of this function). +#if defined(COMPILER_MSVC) + enum { + M_ARRAY_LENGTH = LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE, + N_ARRAY_LENGTH = Estimator::MAX_DEGREE + }; + DCHECK_LE(m, static_cast<uint32_t>(M_ARRAY_LENGTH)); + DCHECK_LE(n, static_cast<uint32_t>(N_ARRAY_LENGTH)); +#else + const uint32_t M_ARRAY_LENGTH = m; + const uint32_t N_ARRAY_LENGTH = n; +#endif + + // Expand the X vector to a matrix A, pre-multiplied by the weights. + float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; // column-major order + for (uint32_t h = 0; h < m; h++) { + a[0][h] = w[h]; + for (uint32_t i = 1; i < n; i++) { + a[i][h] = a[i - 1][h] * x[h]; + } + } + + // Apply the Gram-Schmidt process to A to obtain its QR decomposition. + + // Orthonormal basis, column-major order. + float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; + // Upper triangular matrix, row-major order. + float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH]; + for (uint32_t j = 0; j < n; j++) { + for (uint32_t h = 0; h < m; h++) { + q[j][h] = a[j][h]; + } + for (uint32_t i = 0; i < j; i++) { + float dot = VectorDot(&q[j][0], &q[i][0], m); + for (uint32_t h = 0; h < m; h++) { + q[j][h] -= dot * q[i][h]; + } + } + + float norm = VectorNorm(&q[j][0], m); + if (norm < 0.000001f) { + // vectors are linearly dependent or zero so no solution + return false; + } + + float invNorm = 1.0f / norm; + for (uint32_t h = 0; h < m; h++) { + q[j][h] *= invNorm; + } + for (uint32_t i = 0; i < n; i++) { + r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m); + } + } + + // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. + // We just work from bottom-right to top-left calculating B's coefficients. + float wy[M_ARRAY_LENGTH]; + for (uint32_t h = 0; h < m; h++) { + wy[h] = y[h] * w[h]; + } + for (uint32_t i = n; i-- != 0;) { + out_b[i] = VectorDot(&q[i][0], wy, m); + for (uint32_t j = n - 1; j > i; j--) { + out_b[i] -= r[i][j] * out_b[j]; + } + out_b[i] /= r[i][i]; + } + + // Calculate the coefficient of determination as 1 - (SSerr / SStot) where + // SSerr is the residual sum of squares (variance of the error), + // and SStot is the total sum of squares (variance of the data) where each + // has been weighted. + float ymean = 0; + for (uint32_t h = 0; h < m; h++) { + ymean += y[h]; + } + ymean /= m; + + float sserr = 0; + float sstot = 0; + for (uint32_t h = 0; h < m; h++) { + float err = y[h] - out_b[0]; + float term = 1; + for (uint32_t i = 1; i < n; i++) { + term *= x[h]; + err -= term * out_b[i]; + } + sserr += w[h] * w[h] * err * err; + float var = y[h] - ymean; + sstot += w[h] * w[h] * var * var; + } + *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; + return true; +} + +void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { + BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value); + movements_[index_].id_bits = remaining_id_bits; +} + +bool LeastSquaresVelocityTrackerStrategy::GetEstimator( + uint32_t id, + Estimator* out_estimator) const { + out_estimator->Clear(); + + // Iterate over movement samples in reverse time order and collect samples. + float x[HISTORY_SIZE]; + float y[HISTORY_SIZE]; + float w[HISTORY_SIZE]; + float time[HISTORY_SIZE]; + uint32_t m = 0; + uint32_t index = index_; + const base::TimeDelta horizon = base::TimeDelta::FromMilliseconds(HORIZON_MS); + const Movement& newest_movement = movements_[index_]; + do { + const Movement& movement = movements_[index]; + if (!movement.id_bits.has_bit(id)) + break; + + TimeDelta age = newest_movement.event_time - movement.event_time; + if (age > horizon) + break; + + const Position& position = movement.GetPosition(id); + x[m] = position.x; + y[m] = position.y; + w[m] = ChooseWeight(index); + time[m] = -age.InSecondsF(); + index = (index == 0 ? HISTORY_SIZE : index) - 1; + } while (++m < HISTORY_SIZE); + + if (m == 0) + return false; // no data + + // Calculate a least squares polynomial fit. + uint32_t degree = degree_; + if (degree > m - 1) + degree = m - 1; + + if (degree >= 1) { + float xdet, ydet; + uint32_t n = degree + 1; + if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) && + SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) { + out_estimator->time = newest_movement.event_time; + out_estimator->degree = degree; + out_estimator->confidence = xdet * ydet; + return true; + } + } + + // No velocity data available for this pointer, but we do have its current + // position. + out_estimator->xcoeff[0] = x[0]; + out_estimator->ycoeff[0] = y[0]; + out_estimator->time = newest_movement.event_time; + out_estimator->degree = 0; + out_estimator->confidence = 1; + return true; +} + +float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const { + switch (weighting_) { + case WEIGHTING_DELTA: { + // Weight points based on how much time elapsed between them and the next + // point so that points that "cover" a shorter time span are weighed less. + // delta 0ms: 0.5 + // delta 10ms: 1.0 + if (index == index_) { + return 1.0f; + } + uint32_t next_index = (index + 1) % HISTORY_SIZE; + float delta_millis = + static_cast<float>((movements_[next_index].event_time - + movements_[index].event_time).InMillisecondsF()); + if (delta_millis < 0) + return 0.5f; + if (delta_millis < 10) + return 0.5f + delta_millis * 0.05; + + return 1.0f; + } + + case WEIGHTING_CENTRAL: { + // Weight points based on their age, weighing very recent and very old + // points less. + // age 0ms: 0.5 + // age 10ms: 1.0 + // age 50ms: 1.0 + // age 60ms: 0.5 + float age_millis = + static_cast<float>((movements_[index_].event_time - + movements_[index].event_time).InMillisecondsF()); + if (age_millis < 0) + return 0.5f; + if (age_millis < 10) + return 0.5f + age_millis * 0.05; + if (age_millis < 50) + return 1.0f; + if (age_millis < 60) + return 0.5f + (60 - age_millis) * 0.05; + + return 0.5f; + } + + case WEIGHTING_RECENT: { + // Weight points based on their age, weighing older points less. + // age 0ms: 1.0 + // age 50ms: 1.0 + // age 100ms: 0.5 + float age_millis = + static_cast<float>((movements_[index_].event_time - + movements_[index].event_time).InMillisecondsF()); + if (age_millis < 50) { + return 1.0f; + } + if (age_millis < 100) { + return 0.5f + (100 - age_millis) * 0.01f; + } + return 0.5f; + } + + case WEIGHTING_NONE: + default: + return 1.0f; + } +} + +// --- IntegratingVelocityTrackerStrategy --- + +IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy( + uint32_t degree) + : degree_(degree) { + DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE)); +} + +IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {} + +void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); } + +void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { + pointer_id_bits_.value &= ~id_bits.value; +} + +void IntegratingVelocityTrackerStrategy::AddMovement( + const TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions) { + uint32_t index = 0; + for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) { + uint32_t id = iter_id_bits.clear_first_marked_bit(); + State& state = mPointerState[id]; + const Position& position = positions[index++]; + if (pointer_id_bits_.has_bit(id)) + UpdateState(state, event_time, position.x, position.y); + else + InitState(state, event_time, position.x, position.y); + } + + pointer_id_bits_ = id_bits; +} + +bool IntegratingVelocityTrackerStrategy::GetEstimator( + uint32_t id, + Estimator* out_estimator) const { + out_estimator->Clear(); + + if (pointer_id_bits_.has_bit(id)) { + const State& state = mPointerState[id]; + PopulateEstimator(state, out_estimator); + return true; + } + + return false; +} + +void IntegratingVelocityTrackerStrategy::InitState(State& state, + const TimeTicks& event_time, + float xpos, + float ypos) const { + state.update_time = event_time; + state.degree = 0; + state.xpos = xpos; + state.xvel = 0; + state.xaccel = 0; + state.ypos = ypos; + state.yvel = 0; + state.yaccel = 0; +} + +void IntegratingVelocityTrackerStrategy::UpdateState( + State& state, + const TimeTicks& event_time, + float xpos, + float ypos) const { + const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2); + const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds + + if (event_time <= state.update_time + MIN_TIME_DELTA) + return; + + float dt = static_cast<float>((event_time - state.update_time).InSecondsF()); + state.update_time = event_time; + + float xvel = (xpos - state.xpos) / dt; + float yvel = (ypos - state.ypos) / dt; + if (state.degree == 0) { + state.xvel = xvel; + state.yvel = yvel; + state.degree = 1; + } else { + float alpha = dt / (FILTER_TIME_CONSTANT + dt); + if (degree_ == 1) { + state.xvel += (xvel - state.xvel) * alpha; + state.yvel += (yvel - state.yvel) * alpha; + } else { + float xaccel = (xvel - state.xvel) / dt; + float yaccel = (yvel - state.yvel) / dt; + if (state.degree == 1) { + state.xaccel = xaccel; + state.yaccel = yaccel; + state.degree = 2; + } else { + state.xaccel += (xaccel - state.xaccel) * alpha; + state.yaccel += (yaccel - state.yaccel) * alpha; + } + state.xvel += (state.xaccel * dt) * alpha; + state.yvel += (state.yaccel * dt) * alpha; + } + } + state.xpos = xpos; + state.ypos = ypos; +} + +void IntegratingVelocityTrackerStrategy::PopulateEstimator( + const State& state, + Estimator* out_estimator) const { + out_estimator->time = state.update_time; + out_estimator->confidence = 1.0f; + out_estimator->degree = state.degree; + out_estimator->xcoeff[0] = state.xpos; + out_estimator->xcoeff[1] = state.xvel; + out_estimator->xcoeff[2] = state.xaccel / 2; + out_estimator->ycoeff[0] = state.ypos; + out_estimator->ycoeff[1] = state.yvel; + out_estimator->ycoeff[2] = state.yaccel / 2; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/velocity_tracker.h b/chromium/ui/events/gesture_detection/velocity_tracker.h new file mode 100644 index 00000000000..8147695dd25 --- /dev/null +++ b/chromium/ui/events/gesture_detection/velocity_tracker.h @@ -0,0 +1,150 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_ +#define UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/events/gesture_detection/bitset_32.h" + +namespace ui { + +class MotionEvent; +class VelocityTrackerStrategy; + +namespace { +struct Estimator; +struct Position; +} + +// Port of VelocityTracker from Android +// * platform/frameworks/native/include/input/VelocityTracker.h +// * Change-Id: I4983db61b53e28479fc90d9211fafff68f7f49a6 +// * Please update the Change-Id as upstream Android changes are pulled. +class VelocityTracker { + public: + enum { + // The maximum number of pointers to use when computing the velocity. + // Note that the supplied MotionEvent may expose more than 16 pointers, but + // at most |MAX_POINTERS| will be used. + MAX_POINTERS = 16, + }; + + enum Strategy { + // 1st order least squares. Quality: POOR. + // Frequently underfits the touch data especially when the finger + // accelerates or changes direction. Often underestimates velocity. The + // direction is overly influenced by historical touch points. + LSQ1, + + // 2nd order least squares. Quality: VERY GOOD. + // Pretty much ideal, but can be confused by certain kinds of touch data, + // particularly if the panel has a tendency to generate delayed, + // duplicate or jittery touch coordinates when the finger is released. + LSQ2, + + // 3rd order least squares. Quality: UNUSABLE. + // Frequently overfits the touch data yielding wildly divergent estimates + // of the velocity when the finger is released. + LSQ3, + + // 2nd order weighted least squares, delta weighting. + // Quality: EXPERIMENTAL + WLSQ2_DELTA, + + // 2nd order weighted least squares, central weighting. + // Quality: EXPERIMENTAL + WLSQ2_CENTRAL, + + // 2nd order weighted least squares, recent weighting. + // Quality: EXPERIMENTAL + WLSQ2_RECENT, + + // 1st order integrating filter. Quality: GOOD. + // Not as good as 'lsq2' because it cannot estimate acceleration but it is + // more tolerant of errors. Like 'lsq1', this strategy tends to + // underestimate + // the velocity of a fling but this strategy tends to respond to changes in + // direction more quickly and accurately. + INT1, + + // 2nd order integrating filter. Quality: EXPERIMENTAL. + // For comparison purposes only. Unlike 'int1' this strategy can compensate + // for acceleration but it typically overestimates the effect. + INT2, + STRATEGY_MAX = INT2, + + // The default velocity tracker strategy. + // Although other strategies are available for testing and comparison + // purposes, this is the strategy that applications will actually use. Be + // very careful when adjusting the default strategy because it can + // dramatically affect (often in a bad way) the user experience. + STRATEGY_DEFAULT = LSQ2, + }; + + // Creates a velocity tracker using the default strategy for the platform. + VelocityTracker(); + + // Creates a velocity tracker using the specified strategy. + // If strategy is NULL, uses the default strategy for the platform. + explicit VelocityTracker(Strategy strategy); + + ~VelocityTracker(); + + // Resets the velocity tracker state. + void Clear(); + + // Adds movement information for all pointers in a MotionEvent, including + // historical samples. + void AddMovement(const MotionEvent& event); + + // Gets the velocity of the specified pointer id in position units per second. + // Returns false and sets the velocity components to zero if there is + // insufficient movement information for the pointer. + bool GetVelocity(uint32_t id, float* outVx, float* outVy) const; + + // Gets the active pointer id, or -1 if none. + inline int32_t GetActivePointerId() const { return active_pointer_id_; } + + // Gets a bitset containing all pointer ids from the most recent movement. + inline BitSet32 GetCurrentPointerIdBits() const { + return current_pointer_id_bits_; + } + + private: + // Resets the velocity tracker state for specific pointers. + // Call this method when some pointers have changed and may be reusing + // an id that was assigned to a different pointer earlier. + void ClearPointers(BitSet32 id_bits); + + // Adds movement information for a set of pointers. + // The id_bits bitfield specifies the pointer ids of the pointers whose + // positions + // are included in the movement. + // The positions array contains position information for each pointer in order + // by + // increasing id. Its size should be equal to the number of one bits in + // id_bits. + void AddMovement(const base::TimeTicks& event_time, + BitSet32 id_bits, + const Position* positions); + + // Gets an estimator for the recent movements of the specified pointer id. + // Returns false and clears the estimator if there is no information available + // about the pointer. + bool GetEstimator(uint32_t id, Estimator* out_estimator) const; + + base::TimeTicks last_event_time_; + BitSet32 current_pointer_id_bits_; + int32_t active_pointer_id_; + scoped_ptr<VelocityTrackerStrategy> strategy_; + + DISALLOW_COPY_AND_ASSIGN(VelocityTracker); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_ diff --git a/chromium/ui/events/gesture_detection/velocity_tracker_state.cc b/chromium/ui/events/gesture_detection/velocity_tracker_state.cc new file mode 100644 index 00000000000..12168560dbd --- /dev/null +++ b/chromium/ui/events/gesture_detection/velocity_tracker_state.cc @@ -0,0 +1,105 @@ +// Copyright 2014 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/gesture_detection/velocity_tracker_state.h" + +#include "base/logging.h" +#include "ui/events/gesture_detection/motion_event.h" + +namespace ui { +namespace { +// Special constant to request the velocity of the active pointer. +const int ACTIVE_POINTER_ID = -1; +} + +VelocityTrackerState::VelocityTrackerState() + : velocity_tracker_(VelocityTracker::STRATEGY_DEFAULT), + active_pointer_id_(ACTIVE_POINTER_ID) {} + +VelocityTrackerState::VelocityTrackerState(VelocityTracker::Strategy strategy) + : velocity_tracker_(strategy), active_pointer_id_(ACTIVE_POINTER_ID) {} + +VelocityTrackerState::~VelocityTrackerState() {} + +void VelocityTrackerState::Clear() { + velocity_tracker_.Clear(); + active_pointer_id_ = ACTIVE_POINTER_ID; + calculated_id_bits_.clear(); +} + +void VelocityTrackerState::AddMovement(const MotionEvent& event) { + velocity_tracker_.AddMovement(event); +} + +void VelocityTrackerState::ComputeCurrentVelocity(int32_t units, + float max_velocity) { + DCHECK_GE(max_velocity, 0); + + BitSet32 id_bits(velocity_tracker_.GetCurrentPointerIdBits()); + calculated_id_bits_ = id_bits; + + for (uint32_t index = 0; !id_bits.is_empty(); index++) { + uint32_t id = id_bits.clear_first_marked_bit(); + + float vx, vy; + velocity_tracker_.GetVelocity(id, &vx, &vy); + + vx = vx * units / 1000.f; + vy = vy * units / 1000.f; + + if (vx > max_velocity) + vx = max_velocity; + else if (vx < -max_velocity) + vx = -max_velocity; + + if (vy > max_velocity) + vy = max_velocity; + else if (vy < -max_velocity) + vy = -max_velocity; + + Velocity& velocity = calculated_velocity_[index]; + velocity.vx = vx; + velocity.vy = vy; + } +} + +float VelocityTrackerState::GetXVelocity(int32_t id) const { + float vx; + GetVelocity(id, &vx, NULL); + return vx; +} + +float VelocityTrackerState::GetYVelocity(int32_t id) const { + float vy; + GetVelocity(id, NULL, &vy); + return vy; +} + +void VelocityTrackerState::GetVelocity(int32_t id, + float* out_vx, + float* out_vy) const { + DCHECK(out_vx || out_vy); + if (id == ACTIVE_POINTER_ID) + id = velocity_tracker_.GetActivePointerId(); + + float vx, vy; + if (id >= 0 && id <= MotionEvent::MAX_POINTER_ID && + calculated_id_bits_.has_bit(id)) { + uint32_t index = calculated_id_bits_.get_index_of_bit(id); + const Velocity& velocity = calculated_velocity_[index]; + vx = velocity.vx; + vy = velocity.vy; + } else { + vx = 0; + vy = 0; + } + + if (out_vx) + *out_vx = vx; + + if (out_vy) + *out_vy = vy; +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_detection/velocity_tracker_state.h b/chromium/ui/events/gesture_detection/velocity_tracker_state.h new file mode 100644 index 00000000000..780871cb80b --- /dev/null +++ b/chromium/ui/events/gesture_detection/velocity_tracker_state.h @@ -0,0 +1,50 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_ +#define UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_ + +#include "base/basictypes.h" +#include "ui/events/gesture_detection/bitset_32.h" +#include "ui/events/gesture_detection/gesture_detection_export.h" +#include "ui/events/gesture_detection/velocity_tracker.h" + +namespace ui { + +class MotionEvent; + +// Port of VelocityTrackerState from Android +// * platform/frameworks/base/core/jni/android_view_VelocityTracker.cpp +// * Change-Id: I3517881b87b47dcc209d80dbd0ac6b5cf29a766f +// * Please update the Change-Id as upstream Android changes are pulled. +class GESTURE_DETECTION_EXPORT VelocityTrackerState { + public: + VelocityTrackerState(); + explicit VelocityTrackerState(VelocityTracker::Strategy strategy); + ~VelocityTrackerState(); + + void Clear(); + void AddMovement(const MotionEvent& event); + void ComputeCurrentVelocity(int32_t units, float max_velocity); + float GetXVelocity(int32_t id) const; + float GetYVelocity(int32_t id) const; + + private: + struct Velocity { + float vx, vy; + }; + + void GetVelocity(int32_t id, float* out_vx, float* out_vy) const; + + VelocityTracker velocity_tracker_; + int32_t active_pointer_id_; + BitSet32 calculated_id_bits_; + Velocity calculated_velocity_[VelocityTracker::MAX_POINTERS]; + + DISALLOW_COPY_AND_ASSIGN(VelocityTrackerState); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_ diff --git a/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc b/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc new file mode 100644 index 00000000000..eda1606d141 --- /dev/null +++ b/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc @@ -0,0 +1,193 @@ +// Copyright 2014 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 "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/gesture_detection/mock_motion_event.h" +#include "ui/events/gesture_detection/velocity_tracker_state.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace ui { +namespace { + +const TimeDelta kTenMillis = TimeDelta::FromMilliseconds(10); +const TimeDelta kOneSecond = TimeDelta::FromSeconds(1); +const float kEpsilson = .01f; + +const char* GetStrategyName(VelocityTracker::Strategy strategy) { + switch (strategy) { + case VelocityTracker::LSQ1: return "LSQ1"; + case VelocityTracker::LSQ2: return "LSQ2"; + case VelocityTracker::LSQ3: return "LSQ3"; + case VelocityTracker::WLSQ2_DELTA: return "WLSQ2_DELTA"; + case VelocityTracker::WLSQ2_CENTRAL: return "WLSQ2_CENTRAL"; + case VelocityTracker::WLSQ2_RECENT: return "WLSQ2_RECENT"; + case VelocityTracker::INT1: return "INT1"; + case VelocityTracker::INT2: return "INT2"; + }; + NOTREACHED() << "Invalid strategy"; + return ""; +} + +} // namespace + +class VelocityTrackerTest : public testing::Test { + public: + VelocityTrackerTest() {} + virtual ~VelocityTrackerTest() {} + + protected: + static MockMotionEvent Sample(MotionEvent::Action action, + gfx::PointF p0, + TimeTicks t0, + gfx::Vector2dF v, + TimeDelta dt) { + const gfx::PointF p = p0 + ScaleVector2d(v, dt.InSecondsF()); + return MockMotionEvent(action, t0 + dt, p.x(), p.y()); + } + + static void ApplyMovementSequence(VelocityTrackerState* state, + gfx::PointF p0, + gfx::Vector2dF v, + TimeTicks t0, + TimeDelta t, + size_t samples) { + EXPECT_TRUE(!!samples); + if (!samples) + return; + const base::TimeDelta dt = t / samples; + state->AddMovement(Sample(MotionEvent::ACTION_DOWN, p0, t0, v, dt * 0)); + ApplyMovement(state, p0, v, t0, t, samples); + state->AddMovement(Sample(MotionEvent::ACTION_UP, p0, t0, v, t)); + } + + static void ApplyMovement(VelocityTrackerState* state, + gfx::PointF p0, + gfx::Vector2dF v, + TimeTicks t0, + TimeDelta t, + size_t samples) { + EXPECT_TRUE(!!samples); + if (!samples) + return; + const base::TimeDelta dt = t / samples; + for (size_t i = 0; i < samples; ++i) + state->AddMovement(Sample(MotionEvent::ACTION_MOVE, p0, t0, v, dt * i)); + } +}; + +TEST_F(VelocityTrackerTest, Basic) { + const gfx::PointF p0(0, 0); + const gfx::Vector2dF v(0, 500); + const size_t samples = 60; + + for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) { + VelocityTracker::Strategy strategy = + static_cast<VelocityTracker::Strategy>(i); + + SCOPED_TRACE(GetStrategyName(strategy)); + VelocityTrackerState state(strategy); + + // Default state should report zero velocity. + EXPECT_EQ(0, state.GetXVelocity(0)); + EXPECT_EQ(0, state.GetYVelocity(0)); + + // Sample a constant velocity sequence. + ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), kOneSecond, samples); + + // The computed velocity should match that of the input. + state.ComputeCurrentVelocity(1000, 20000); + EXPECT_NEAR(v.x(), state.GetXVelocity(0), kEpsilson * v.x()); + EXPECT_NEAR(v.y(), state.GetYVelocity(0), kEpsilson * v.y()); + + // A pointer ID of -1 should report the velocity of the active pointer. + EXPECT_NEAR(v.x(), state.GetXVelocity(-1), kEpsilson * v.x()); + EXPECT_NEAR(v.y(), state.GetYVelocity(-1), kEpsilson * v.y()); + + // Invalid pointer ID's should report zero velocity. + EXPECT_EQ(0, state.GetXVelocity(1)); + EXPECT_EQ(0, state.GetYVelocity(1)); + EXPECT_EQ(0, state.GetXVelocity(7)); + EXPECT_EQ(0, state.GetYVelocity(7)); + } +} + +TEST_F(VelocityTrackerTest, MaxVelocity) { + const gfx::PointF p0(0, 0); + const gfx::Vector2dF v(-50000, 50000); + const size_t samples = 3; + const base::TimeDelta dt = kTenMillis * 2; + + VelocityTrackerState state; + ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), dt, samples); + + // The computed velocity should be restricted to the provided maximum. + state.ComputeCurrentVelocity(1000, 100); + EXPECT_NEAR(-100, state.GetXVelocity(0), kEpsilson); + EXPECT_NEAR(100, state.GetYVelocity(0), kEpsilson); + + state.ComputeCurrentVelocity(1000, 1000); + EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson); + EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson); +} + +TEST_F(VelocityTrackerTest, VaryingVelocity) { + const gfx::PointF p0(0, 0); + const gfx::Vector2dF vFast(0, 500); + const gfx::Vector2dF vSlow = ScaleVector2d(vFast, 0.5f); + const size_t samples = 12; + + for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) { + VelocityTracker::Strategy strategy = + static_cast<VelocityTracker::Strategy>(i); + + SCOPED_TRACE(GetStrategyName(strategy)); + VelocityTrackerState state(strategy); + + base::TimeTicks t0 = base::TimeTicks::Now(); + base::TimeDelta dt = kTenMillis * 10; + state.AddMovement( + Sample(MotionEvent::ACTION_DOWN, p0, t0, vFast, base::TimeDelta())); + + // Apply some fast movement and compute the velocity. + gfx::PointF pCurr = p0; + base::TimeTicks tCurr = t0; + ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples); + state.ComputeCurrentVelocity(1000, 20000); + float vOldY = state.GetYVelocity(0); + + // Apply some slow movement. + pCurr += ScaleVector2d(vFast, dt.InSecondsF()); + tCurr += dt; + ApplyMovement(&state, pCurr, vSlow, tCurr, dt, samples); + + // The computed velocity should have decreased. + state.ComputeCurrentVelocity(1000, 20000); + float vCurrentY = state.GetYVelocity(0); + EXPECT_GT(vFast.y(), vCurrentY); + EXPECT_GT(vOldY, vCurrentY); + vOldY = vCurrentY; + + // Apply some additional fast movement. + pCurr += ScaleVector2d(vSlow, dt.InSecondsF()); + tCurr += dt; + ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples); + + // The computed velocity should have increased. + state.ComputeCurrentVelocity(1000, 20000); + vCurrentY = state.GetYVelocity(0); + EXPECT_LT(vSlow.y(), vCurrentY); + EXPECT_LT(vOldY, vCurrentY); + } +} + + +} // namespace ui diff --git a/chromium/ui/events/gesture_event_details.cc b/chromium/ui/events/gesture_event_details.cc new file mode 100644 index 00000000000..4026d06ecdb --- /dev/null +++ b/chromium/ui/events/gesture_event_details.cc @@ -0,0 +1,71 @@ +// Copyright 2014 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/gesture_event_details.h" + +namespace ui { + +GestureEventDetails::GestureEventDetails() : type_(ET_UNKNOWN) {} + +GestureEventDetails::GestureEventDetails(ui::EventType type, + float delta_x, + float delta_y) + : type_(type), + touch_points_(1) { + DCHECK_GE(type, ET_GESTURE_TYPE_START); + DCHECK_LE(type, ET_GESTURE_TYPE_END); + switch (type_) { + case ui::ET_GESTURE_SCROLL_BEGIN: + data.scroll_begin.x_hint = delta_x; + data.scroll_begin.y_hint = delta_y; + break; + + case ui::ET_GESTURE_SCROLL_UPDATE: + data.scroll_update.x = delta_x; + data.scroll_update.y = delta_y; + break; + + case ui::ET_SCROLL_FLING_START: + data.fling_velocity.x = delta_x; + data.fling_velocity.y = delta_y; + break; + + case ui::ET_GESTURE_TWO_FINGER_TAP: + data.first_finger_enclosing_rectangle.width = delta_x; + data.first_finger_enclosing_rectangle.height = delta_y; + break; + + case ui::ET_GESTURE_PINCH_UPDATE: + data.scale = delta_x; + CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for pinch"; + break; + + case ui::ET_GESTURE_SWIPE: + data.swipe.left = delta_x < 0; + data.swipe.right = delta_x > 0; + data.swipe.up = delta_y < 0; + data.swipe.down = delta_y > 0; + break; + + case ui::ET_GESTURE_TAP: + case ui::ET_GESTURE_DOUBLE_TAP: + case ui::ET_GESTURE_TAP_UNCONFIRMED: + data.tap_count = static_cast<int>(delta_x); + CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for tap."; + break; + + default: + if (delta_x != 0.f || delta_y != 0.f) { + DLOG(WARNING) << "A gesture event (" << type << ") had unknown data: (" + << delta_x << "," << delta_y; + } + break; + } +} + +GestureEventDetails::Details::Details() { + memset(this, 0, sizeof(Details)); +} + +} // namespace ui diff --git a/chromium/ui/events/gesture_event_details.h b/chromium/ui/events/gesture_event_details.h new file mode 100644 index 00000000000..32db808bf72 --- /dev/null +++ b/chromium/ui/events/gesture_event_details.h @@ -0,0 +1,171 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_ +#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_ + +#include "base/logging.h" +#include "ui/events/event_constants.h" +#include "ui/events/events_base_export.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_conversions.h" + +namespace ui { + +struct EVENTS_BASE_EXPORT GestureEventDetails { + public: + GestureEventDetails(); + GestureEventDetails(EventType type, float delta_x, float delta_y); + + EventType type() const { return type_; } + + int touch_points() const { return touch_points_; } + void set_touch_points(int touch_points) { + DCHECK_GT(touch_points, 0); + touch_points_ = touch_points; + } + + // TODO(tdresser): Return RectF. See crbug.com/337824. + const gfx::Rect bounding_box() const { + return ToEnclosingRect(bounding_box_); + } + + const gfx::RectF& bounding_box_f() const { + return bounding_box_; + } + + void set_bounding_box(const gfx::RectF& box) { bounding_box_ = box; } + + float scroll_x_hint() const { + DCHECK_EQ(ET_GESTURE_SCROLL_BEGIN, type_); + return data.scroll_begin.x_hint; + } + + float scroll_y_hint() const { + DCHECK_EQ(ET_GESTURE_SCROLL_BEGIN, type_); + return data.scroll_begin.y_hint; + } + + float scroll_x() const { + DCHECK_EQ(ET_GESTURE_SCROLL_UPDATE, type_); + return data.scroll_update.x; + } + + float scroll_y() const { + DCHECK_EQ(ET_GESTURE_SCROLL_UPDATE, type_); + return data.scroll_update.y; + } + + float velocity_x() const { + DCHECK_EQ(ET_SCROLL_FLING_START, type_); + return data.fling_velocity.x; + } + + float velocity_y() const { + DCHECK_EQ(ET_SCROLL_FLING_START, type_); + return data.fling_velocity.y; + } + + float first_finger_width() const { + DCHECK_EQ(ET_GESTURE_TWO_FINGER_TAP, type_); + return data.first_finger_enclosing_rectangle.width; + } + + float first_finger_height() const { + DCHECK_EQ(ET_GESTURE_TWO_FINGER_TAP, type_); + return data.first_finger_enclosing_rectangle.height; + } + + float scale() const { + DCHECK_EQ(ET_GESTURE_PINCH_UPDATE, type_); + return data.scale; + } + + bool swipe_left() const { + DCHECK_EQ(ET_GESTURE_SWIPE, type_); + return data.swipe.left; + } + + bool swipe_right() const { + DCHECK_EQ(ET_GESTURE_SWIPE, type_); + return data.swipe.right; + } + + bool swipe_up() const { + DCHECK_EQ(ET_GESTURE_SWIPE, type_); + return data.swipe.up; + } + + bool swipe_down() const { + DCHECK_EQ(ET_GESTURE_SWIPE, type_); + return data.swipe.down; + } + + int tap_count() const { + DCHECK(type_ == ET_GESTURE_TAP || + type_ == ET_GESTURE_TAP_UNCONFIRMED || + type_ == ET_GESTURE_DOUBLE_TAP); + return data.tap_count; + } + + void set_tap_count(int tap_count) { + DCHECK_GE(tap_count, 0); + DCHECK(type_ == ET_GESTURE_TAP || + type_ == ET_GESTURE_TAP_UNCONFIRMED || + type_ == ET_GESTURE_DOUBLE_TAP); + data.tap_count = tap_count; + } + + private: + EventType type_; + union Details { + Details(); + struct { // SCROLL start details. + // Distance that caused the scroll to start. Generally redundant with + // the x/y values from the first scroll_update. + float x_hint; + float y_hint; + } scroll_begin; + + struct { // SCROLL delta. + float x; + float y; + } scroll_update; + + float scale; // PINCH scale. + + struct { // FLING velocity. + float x; + float y; + } fling_velocity; + + // Dimensions of the first finger's enclosing rectangle for + // TWO_FINGER_TAP. + struct { + float width; + float height; + } first_finger_enclosing_rectangle; + + struct { // SWIPE direction. + bool left; + bool right; + bool up; + bool down; + } swipe; + + // Tap information must be set for ET_GESTURE_TAP, + // ET_GESTURE_TAP_UNCONFIRMED, and ET_GESTURE_DOUBLE_TAP events. + int tap_count; // TAP repeat count. + } data; + + int touch_points_; // Number of active touch points in the gesture. + + // Bounding box is an axis-aligned rectangle that contains all the + // enclosing rectangles of the touch-points in the gesture. + gfx::RectF bounding_box_; +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_ diff --git a/chromium/ui/events/gestures/OWNERS b/chromium/ui/events/gestures/OWNERS index 6a1dd965022..92704b0eb58 100644 --- a/chromium/ui/events/gestures/OWNERS +++ b/chromium/ui/events/gestures/OWNERS @@ -1,2 +1,3 @@ rjkroege@chromium.org sadrul@chromium.org +tdresser@chromium.org diff --git a/chromium/ui/events/gestures/gesture_configuration.cc b/chromium/ui/events/gestures/gesture_configuration.cc index e48ae6daed3..8623e9973ee 100644 --- a/chromium/ui/events/gestures/gesture_configuration.cc +++ b/chromium/ui/events/gestures/gesture_configuration.cc @@ -6,7 +6,7 @@ namespace ui { -int GestureConfiguration::default_radius_ = 15; +int GestureConfiguration::default_radius_ = 25; int GestureConfiguration::fling_max_cancel_to_down_time_in_ms_ = 400; int GestureConfiguration::fling_max_tap_gap_time_in_ms_ = 200; float GestureConfiguration::fling_velocity_cap_ = 17000.0f; @@ -21,20 +21,23 @@ double double GestureConfiguration::max_swipe_deviation_ratio_ = 3; double GestureConfiguration::max_touch_down_duration_in_seconds_for_click_ = 0.8; -double GestureConfiguration::max_touch_move_in_pixels_for_click_ = 25; +double GestureConfiguration::max_touch_move_in_pixels_for_click_ = 15; double GestureConfiguration::max_distance_between_taps_for_double_tap_ = 20; double GestureConfiguration::min_distance_for_pinch_scroll_in_pixels_ = 20; double GestureConfiguration::min_flick_speed_squared_ = 550.f * 550.f; double GestureConfiguration::min_pinch_update_distance_in_pixels_ = 5; double GestureConfiguration::min_rail_break_velocity_ = 200; double GestureConfiguration::min_scroll_delta_squared_ = 4 * 4; -int GestureConfiguration::min_scroll_successive_velocity_events_ = 4; float GestureConfiguration::min_scroll_velocity_ = 30.0f; double GestureConfiguration::min_swipe_speed_ = 20; double GestureConfiguration::scroll_prediction_seconds_ = 0.03; double GestureConfiguration::min_touch_down_duration_in_seconds_for_click_ = 0.01; +// If this is too small, we currently can get single finger pinch zoom. See +// crbug.com/357237 for details. +int GestureConfiguration::min_scaling_span_in_pixels_ = 125; + // The number of points used in the linear regression which determines // touch velocity. Velocity is reported for 2 or more touch move events. int GestureConfiguration::points_buffered_for_velocity_ = 8; @@ -42,6 +45,14 @@ double GestureConfiguration::rail_break_proportion_ = 15; double GestureConfiguration::rail_start_proportion_ = 2; int GestureConfiguration::show_press_delay_in_ms_ = 150; +// TODO(jdduke): Disable and remove entirely when issues with intermittent +// scroll end detection on the Pixel are resolved, crbug.com/353702. +#if defined(OS_CHROMEOS) +int GestureConfiguration::scroll_debounce_interval_in_ms_ = 30; +#else +int GestureConfiguration::scroll_debounce_interval_in_ms_ = 0; +#endif + // Coefficients for a function that computes fling acceleration. // These are empirically determined defaults. Do not adjust without // additional empirical validation. diff --git a/chromium/ui/events/gestures/gesture_configuration.h b/chromium/ui/events/gestures/gesture_configuration.h index a4bdcd3f4c2..08ce578eeeb 100644 --- a/chromium/ui/events/gestures/gesture_configuration.h +++ b/chromium/ui/events/gestures/gesture_configuration.h @@ -6,7 +6,7 @@ #define UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_ #include "base/basictypes.h" -#include "ui/events/events_export.h" +#include "ui/events/events_base_export.h" namespace ui { @@ -14,7 +14,7 @@ namespace ui { // approaches (windows, chrome, others). This would turn into an // abstract base class. -class EVENTS_EXPORT GestureConfiguration { +class EVENTS_BASE_EXPORT GestureConfiguration { public: // Number of parameters in the array of parameters for the fling acceleration // curve. @@ -125,12 +125,6 @@ class EVENTS_EXPORT GestureConfiguration { static void set_min_scroll_delta_squared(double val) { min_scroll_delta_squared_ = val; } - static int min_scroll_successive_velocity_events() { - return min_scroll_successive_velocity_events_; - } - static void set_min_scroll_successive_velocity_events(int val) { - min_scroll_successive_velocity_events_ = val; - } static float min_scroll_velocity() { return min_scroll_velocity_; } @@ -149,6 +143,15 @@ class EVENTS_EXPORT GestureConfiguration { static void set_min_touch_down_duration_in_seconds_for_click(double val) { min_touch_down_duration_in_seconds_for_click_ = val; } + + static int min_scaling_span_in_pixels() { + return min_scaling_span_in_pixels_; + }; + + static void set_min_scaling_span_in_pixels(int val) { + min_scaling_span_in_pixels_ = val; + } + static int points_buffered_for_velocity() { return points_buffered_for_velocity_; } @@ -179,6 +182,12 @@ class EVENTS_EXPORT GestureConfiguration { static int set_show_press_delay_in_ms(int val) { return show_press_delay_in_ms_ = val; } + static int scroll_debounce_interval_in_ms() { + return scroll_debounce_interval_in_ms_; + } + static int set_scroll_debounce_interval_in_ms(int val) { + return scroll_debounce_interval_in_ms_ = val; + } static void set_fling_acceleration_curve_coefficients(int i, float val) { fling_acceleration_curve_coefficients_[i] = val; } @@ -238,15 +247,17 @@ class EVENTS_EXPORT GestureConfiguration { static double min_pinch_update_distance_in_pixels_; static double min_rail_break_velocity_; static double min_scroll_delta_squared_; - static int min_scroll_successive_velocity_events_; static float min_scroll_velocity_; static double min_swipe_speed_; static double min_touch_down_duration_in_seconds_for_click_; + static int min_scaling_span_in_pixels_; static int points_buffered_for_velocity_; static double rail_break_proportion_; static double rail_start_proportion_; static double scroll_prediction_seconds_; static int show_press_delay_in_ms_; + static int scroll_debounce_interval_in_ms_; + static float fling_acceleration_curve_coefficients_[NumAccelParams]; static float fling_velocity_cap_; // TODO(davemoore): Move into chrome/browser/ui. diff --git a/chromium/ui/events/gestures/gesture_point.cc b/chromium/ui/events/gestures/gesture_point.cc index 529471af8b4..33966235a10 100644 --- a/chromium/ui/events/gestures/gesture_point.cc +++ b/chromium/ui/events/gestures/gesture_point.cc @@ -11,7 +11,6 @@ #include "ui/events/event_constants.h" #include "ui/events/gestures/gesture_configuration.h" #include "ui/events/gestures/gesture_types.h" -#include "ui/events/gestures/gesture_util.h" namespace ui { @@ -24,7 +23,14 @@ GesturePoint::GesturePoint() velocity_calculator_( GestureConfiguration::points_buffered_for_velocity()), point_id_(-1), - touch_id_(-1) { + touch_id_(-1), + source_device_id_(-1) { + max_touch_move_in_pixels_for_click_squared_ = + GestureConfiguration::max_touch_move_in_pixels_for_click() * + GestureConfiguration::max_touch_move_in_pixels_for_click(); + max_distance_between_taps_for_double_tap_squared_ = + GestureConfiguration::max_distance_between_taps_for_double_tap() * + GestureConfiguration::max_distance_between_taps_for_double_tap(); } GesturePoint::~GesturePoint() {} @@ -34,14 +40,14 @@ void GesturePoint::Reset() { ResetVelocity(); point_id_ = -1; clear_enclosing_rectangle(); + source_device_id_ = -1; } void GesturePoint::ResetVelocity() { velocity_calculator_.ClearHistory(); - same_direction_count_ = gfx::Vector2d(); } -gfx::Vector2d GesturePoint::ScrollDelta() { +gfx::Vector2dF GesturePoint::ScrollDelta() const { return last_touch_position_ - second_last_touch_position_; } @@ -54,11 +60,10 @@ void GesturePoint::UpdateValues(const TouchEvent& event) { event_timestamp_microseconds); gfx::Vector2d sd(ScrollVelocityDirection(velocity_calculator_.XVelocity()), ScrollVelocityDirection(velocity_calculator_.YVelocity())); - same_direction_count_ = same_direction_count_ + sd; } last_touch_time_ = event.time_stamp().InSecondsF(); - last_touch_position_ = event.location(); + last_touch_position_ = event.location_f(); if (event.type() == ui::ET_TOUCH_PRESSED) { ResetVelocity(); @@ -86,31 +91,30 @@ void GesturePoint::UpdateForTap() { void GesturePoint::UpdateForScroll() { second_last_touch_position_ = last_touch_position_; second_last_touch_time_ = last_touch_time_; - same_direction_count_ = gfx::Vector2d(); } bool GesturePoint::IsInClickWindow(const TouchEvent& event) const { - return IsInClickTimeWindow() && IsInsideManhattanSquare(event); + return IsInClickTimeWindow() && IsInsideTouchSlopRegion(event); } bool GesturePoint::IsInDoubleClickWindow(const TouchEvent& event) const { return IsInClickAggregateTimeWindow(last_tap_time_, last_touch_time_) && - IsPointInsideManhattanSquare(event.location(), last_tap_position_); + IsPointInsideDoubleTapTouchSlopRegion( + event.location(), last_tap_position_); } bool GesturePoint::IsInTripleClickWindow(const TouchEvent& event) const { return IsInClickAggregateTimeWindow(last_tap_time_, last_touch_time_) && IsInClickAggregateTimeWindow(second_last_tap_time_, last_tap_time_) && - IsPointInsideManhattanSquare(event.location(), last_tap_position_) && - IsPointInsideManhattanSquare(last_tap_position_, + IsPointInsideDoubleTapTouchSlopRegion( + event.location(), last_tap_position_) && + IsPointInsideDoubleTapTouchSlopRegion(last_tap_position_, second_last_tap_position_); } bool GesturePoint::IsInScrollWindow(const TouchEvent& event) const { - if (IsConsistentScrollingActionUnderway()) - return true; return event.type() == ui::ET_TOUCH_MOVED && - !IsInsideManhattanSquare(event); + !IsInsideTouchSlopRegion(event); } bool GesturePoint::IsInFlickWindow(const TouchEvent& event) { @@ -128,28 +132,20 @@ int GesturePoint::ScrollVelocityDirection(float v) { } bool GesturePoint::DidScroll(const TouchEvent& event, int dist) const { - gfx::Vector2d d = last_touch_position_ - second_last_touch_position_; - return abs(d.x()) > dist || abs(d.y()) > dist; -} - -bool GesturePoint::IsConsistentScrollingActionUnderway() const { - int me = GestureConfiguration::min_scroll_successive_velocity_events(); - if (abs(same_direction_count_.x()) >= me || - abs(same_direction_count_.y()) >= me) - return true; - return false; + gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_; + return fabs(d.x()) > dist || fabs(d.y()) > dist; } bool GesturePoint::IsInHorizontalRailWindow() const { - gfx::Vector2d d = last_touch_position_ - second_last_touch_position_; - return abs(d.x()) > - GestureConfiguration::rail_start_proportion() * abs(d.y()); + gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_; + return std::abs(d.x()) > + GestureConfiguration::rail_start_proportion() * std::abs(d.y()); } bool GesturePoint::IsInVerticalRailWindow() const { - gfx::Vector2d d = last_touch_position_ - second_last_touch_position_; - return abs(d.y()) > - GestureConfiguration::rail_start_proportion() * abs(d.x()); + gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_; + return std::abs(d.y()) > + GestureConfiguration::rail_start_proportion() * std::abs(d.x()); } bool GesturePoint::BreaksHorizontalRail() { @@ -180,16 +176,21 @@ bool GesturePoint::IsInClickAggregateTimeWindow(double before, return duration < GestureConfiguration::max_seconds_between_double_click(); } -bool GesturePoint::IsInsideManhattanSquare(const TouchEvent& event) const { - return ui::gestures::IsInsideManhattanSquare(event.location(), - first_touch_position_); +bool GesturePoint::IsInsideTouchSlopRegion(const TouchEvent& event) const { + const gfx::PointF& p1 = event.location(); + const gfx::PointF& p2 = first_touch_position_; + float dx = p1.x() - p2.x(); + float dy = p1.y() - p2.y(); + float distance = dx * dx + dy * dy; + return distance < max_touch_move_in_pixels_for_click_squared_; } -bool GesturePoint::IsPointInsideManhattanSquare(gfx::Point p1, - gfx::Point p2) const { - int manhattan_distance = abs(p1.x() - p2.x()) + abs(p1.y() - p2.y()); - return manhattan_distance < - GestureConfiguration::max_distance_between_taps_for_double_tap(); +bool GesturePoint::IsPointInsideDoubleTapTouchSlopRegion(gfx::PointF p1, + gfx::PointF p2) const { + float dx = p1.x() - p2.x(); + float dy = p1.y() - p2.y(); + float distance = dx * dx + dy * dy; + return distance < max_distance_between_taps_for_double_tap_squared_; } bool GesturePoint::IsOverMinFlickSpeed() { @@ -217,10 +218,10 @@ void GesturePoint::UpdateEnclosingRectangle(const TouchEvent& event) { else radius = GestureConfiguration::default_radius(); - gfx::Rect rect(event.location().x() - radius, - event.location().y() - radius, - radius * 2, - radius * 2); + gfx::RectF rect(event.location_f().x() - radius, + event.location_f().y() - radius, + radius * 2, + radius * 2); if (IsInClickWindow(event)) enclosing_rect_.Union(rect); else diff --git a/chromium/ui/events/gestures/gesture_point.h b/chromium/ui/events/gestures/gesture_point.h index 02aa4608592..f20cf9dc7ec 100644 --- a/chromium/ui/events/gestures/gesture_point.h +++ b/chromium/ui/events/gestures/gesture_point.h @@ -43,20 +43,22 @@ class GesturePoint { bool IsInFlickWindow(const TouchEvent& event); bool IsInHorizontalRailWindow() const; bool IsInVerticalRailWindow() const; - bool IsInsideManhattanSquare(const TouchEvent& event) const; + bool IsInsideTouchSlopRegion(const TouchEvent& event) const; bool IsInScrollWindow(const TouchEvent& event) const; bool BreaksHorizontalRail(); bool BreaksVerticalRail(); bool DidScroll(const TouchEvent& event, int distance) const; - const gfx::Point& first_touch_position() const { + const gfx::PointF& first_touch_position() const { return first_touch_position_; } double last_touch_time() const { return last_touch_time_; } - const gfx::Point& last_touch_position() const { return last_touch_position_; } - int x() const { return last_touch_position_.x(); } - int y() const { return last_touch_position_.y(); } + const gfx::PointF& last_touch_position() const { + return last_touch_position_; + } + float x() const { return last_touch_position_.x(); } + float y() const { return last_touch_position_.y(); } // point_id_ is used to drive GestureSequence::ProcessTouchEventForGesture. // point_ids are maintained such that the set of point_ids is always @@ -72,22 +74,25 @@ class GesturePoint { bool in_use() const { return point_id_ >= 0; } - gfx::Vector2d ScrollDelta(); + gfx::Vector2dF ScrollDelta() const; float XVelocity() { return velocity_calculator_.XVelocity(); } float YVelocity() { return velocity_calculator_.YVelocity(); } - const gfx::Rect& enclosing_rectangle() const { return enclosing_rect_; } + const gfx::RectF& enclosing_rectangle() const { return enclosing_rect_; } + + void set_source_device_id(int source_device_id) { + source_device_id_ = source_device_id; + } + int source_device_id() const { return source_device_id_; } private: // Various statistical functions to manipulate gestures. - // Tests if the point has a consistent scroll vector across a window of touch - // move events. - bool IsConsistentScrollingActionUnderway() const; bool IsInClickTimeWindow() const; bool IsInClickAggregateTimeWindow(double before, double after) const; - bool IsPointInsideManhattanSquare(gfx::Point p1, gfx::Point p2) const; + bool IsPointInsideDoubleTapTouchSlopRegion( + gfx::PointF p1, gfx::PointF p2) const; bool IsOverMinFlickSpeed(); // Returns -1, 0, 1 for |v| below the negative velocity threshold, @@ -101,23 +106,23 @@ class GesturePoint { // cleared on a ET_TOUCH_PRESSED event (i.e., at the beginning of a possible // GESTURE_TAP event) or when Reset is called. void UpdateEnclosingRectangle(const TouchEvent& event); - void clear_enclosing_rectangle() { enclosing_rect_ = gfx::Rect(); } + void clear_enclosing_rectangle() { enclosing_rect_ = gfx::RectF(); } // The position of the first touchdown event. - gfx::Point first_touch_position_; + gfx::PointF first_touch_position_; double first_touch_time_; - gfx::Point second_last_touch_position_; + gfx::PointF second_last_touch_position_; double second_last_touch_time_; - gfx::Point last_touch_position_; + gfx::PointF last_touch_position_; double last_touch_time_; double second_last_tap_time_; - gfx::Point second_last_tap_position_; + gfx::PointF second_last_tap_position_; double last_tap_time_; - gfx::Point last_tap_position_; + gfx::PointF last_tap_position_; VelocityCalculator velocity_calculator_; @@ -126,10 +131,12 @@ class GesturePoint { // Represents the rectangle that encloses the circles/ellipses // generated by a sequence of touch events - gfx::Rect enclosing_rect_; + gfx::RectF enclosing_rect_; + + int source_device_id_; - // Count of the number of events with same direction. - gfx::Vector2d same_direction_count_; + float max_touch_move_in_pixels_for_click_squared_; + float max_distance_between_taps_for_double_tap_squared_; DISALLOW_COPY_AND_ASSIGN(GesturePoint); }; diff --git a/chromium/ui/events/gestures/gesture_provider_aura.cc b/chromium/ui/events/gestures/gesture_provider_aura.cc new file mode 100644 index 00000000000..eb902f507f8 --- /dev/null +++ b/chromium/ui/events/gestures/gesture_provider_aura.cc @@ -0,0 +1,142 @@ +// Copyright 2014 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/gestures/gesture_provider_aura.h" + +#include "base/auto_reset.h" +#include "base/logging.h" +#include "ui/events/event.h" +#include "ui/events/gesture_detection/gesture_config_helper.h" +#include "ui/events/gesture_detection/gesture_event_data.h" +#include "ui/events/gestures/gesture_configuration.h" + +namespace ui { + +GestureProviderAura::GestureProviderAura(GestureProviderAuraClient* client) + : client_(client), + filtered_gesture_provider_(ui::DefaultGestureProviderConfig(), this), + handling_event_(false) { + filtered_gesture_provider_.SetDoubleTapSupportForPlatformEnabled(false); +} + +GestureProviderAura::~GestureProviderAura() {} + +bool GestureProviderAura::OnTouchEvent(const TouchEvent& event) { + bool pointer_id_is_active = false; + for (size_t i = 0; i < pointer_state_.GetPointerCount(); ++i) { + if (event.touch_id() != pointer_state_.GetPointerId(i)) + continue; + pointer_id_is_active = true; + break; + } + + if (event.type() == ET_TOUCH_PRESSED && pointer_id_is_active) { + // Ignore touch press events if we already believe the pointer is down. + return false; + } else if (event.type() != ET_TOUCH_PRESSED && !pointer_id_is_active) { + // We could have an active touch stream transfered to us, resulting in touch + // move or touch up events without associated touch down events. Ignore + // them. + return false; + } + + last_touch_event_flags_ = event.flags(); + last_touch_event_latency_info_ = *event.latency(); + pointer_state_.OnTouch(event); + + bool result = filtered_gesture_provider_.OnTouchEvent(pointer_state_); + pointer_state_.CleanupRemovedTouchPoints(event); + return result; +} + +void GestureProviderAura::OnTouchEventAck(bool event_consumed) { + DCHECK(pending_gestures_.empty()); + DCHECK(!handling_event_); + base::AutoReset<bool> handling_event(&handling_event_, true); + filtered_gesture_provider_.OnTouchEventAck(event_consumed); + last_touch_event_latency_info_.Clear(); +} + +void GestureProviderAura::OnGestureEvent( + const GestureEventData& gesture) { + GestureEventDetails details = gesture.details; + + if (gesture.type() == ET_GESTURE_TAP) { + int tap_count = 1; + if (previous_tap_ && IsConsideredDoubleTap(*previous_tap_, gesture)) + tap_count = 1 + (previous_tap_->details.tap_count() % 3); + details.set_tap_count(tap_count); + if (!previous_tap_) + previous_tap_.reset(new GestureEventData(gesture)); + else + *previous_tap_ = gesture; + previous_tap_->details = details; + } else if (gesture.type() == ET_GESTURE_TAP_CANCEL) { + previous_tap_.reset(); + } + + scoped_ptr<ui::GestureEvent> event( + new ui::GestureEvent(gesture.type(), + gesture.x, + gesture.y, + last_touch_event_flags_, + gesture.time - base::TimeTicks(), + details, + // ui::GestureEvent stores a bitfield indicating the + // ids of active touch points. This is currently only + // used when one finger is down, and will eventually + // be cleaned up. See crbug.com/366707. + 1 << gesture.motion_event_id)); + + + ui::LatencyInfo* gesture_latency = event->latency(); + + gesture_latency->CopyLatencyFrom( + last_touch_event_latency_info_, + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT); + gesture_latency->CopyLatencyFrom( + last_touch_event_latency_info_, + ui::INPUT_EVENT_LATENCY_UI_COMPONENT); + gesture_latency->CopyLatencyFrom( + last_touch_event_latency_info_, + ui::INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT); + + if (!handling_event_) { + // Dispatching event caused by timer. + client_->OnGestureEvent(event.get()); + } else { + // Memory managed by ScopedVector pending_gestures_. + pending_gestures_.push_back(event.release()); + } +} + +ScopedVector<GestureEvent>* GestureProviderAura::GetAndResetPendingGestures() { + if (pending_gestures_.empty()) + return NULL; + // Caller is responsible for deleting old_pending_gestures. + ScopedVector<GestureEvent>* old_pending_gestures = + new ScopedVector<GestureEvent>(); + old_pending_gestures->swap(pending_gestures_); + return old_pending_gestures; +} + +bool GestureProviderAura::IsConsideredDoubleTap( + const GestureEventData& previous_tap, + const GestureEventData& current_tap) const { + if (current_tap.time - previous_tap.time > + base::TimeDelta::FromMilliseconds( + ui::GestureConfiguration::max_seconds_between_double_click() * + 1000)) { + return false; + } + + double double_tap_slop_square = + GestureConfiguration::max_distance_between_taps_for_double_tap(); + double_tap_slop_square *= double_tap_slop_square; + const float delta_x = previous_tap.x - current_tap.x; + const float delta_y = previous_tap.y - current_tap.y; + return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square); +} + +} // namespace content diff --git a/chromium/ui/events/gestures/gesture_provider_aura.h b/chromium/ui/events/gestures/gesture_provider_aura.h new file mode 100644 index 00000000000..ff7befce15e --- /dev/null +++ b/chromium/ui/events/gestures/gesture_provider_aura.h @@ -0,0 +1,59 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_ +#define UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_ + +#include "base/basictypes.h" +#include "ui/events/event.h" +#include "ui/events/events_export.h" +#include "ui/events/gesture_detection/filtered_gesture_provider.h" +#include "ui/events/gesture_detection/gesture_event_data_packet.h" +#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h" +#include "ui/events/gestures/motion_event_aura.h" + +namespace ui { + +class EVENTS_EXPORT GestureProviderAuraClient { + public: + virtual ~GestureProviderAuraClient() {} + virtual void OnGestureEvent(GestureEvent* event) = 0; +}; + +// Provides gesture detection and dispatch given a sequence of touch events +// and touch event acks. +class EVENTS_EXPORT GestureProviderAura : public GestureProviderClient { + public: + GestureProviderAura(GestureProviderAuraClient* client); + virtual ~GestureProviderAura(); + + bool OnTouchEvent(const TouchEvent& event); + void OnTouchEventAck(bool event_consumed); + const MotionEventAura& pointer_state() { return pointer_state_; } + ScopedVector<GestureEvent>* GetAndResetPendingGestures(); + + // GestureProviderClient implementation + virtual void OnGestureEvent(const GestureEventData& gesture) OVERRIDE; + + private: + bool IsConsideredDoubleTap(const GestureEventData& previous_tap, + const GestureEventData& current_tap) const; + + scoped_ptr<GestureEventData> previous_tap_; + + GestureProviderAuraClient* client_; + MotionEventAura pointer_state_; + FilteredGestureProvider filtered_gesture_provider_; + + int last_touch_event_flags_; + ui::LatencyInfo last_touch_event_latency_info_; + bool handling_event_; + ScopedVector<GestureEvent> pending_gestures_; + + DISALLOW_COPY_AND_ASSIGN(GestureProviderAura); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_ diff --git a/chromium/ui/events/gestures/gesture_recognizer.h b/chromium/ui/events/gestures/gesture_recognizer.h index 92d225a7118..4b31e57bd71 100644 --- a/chromium/ui/events/gestures/gesture_recognizer.h +++ b/chromium/ui/events/gestures/gesture_recognizer.h @@ -11,6 +11,7 @@ #include "ui/events/event_constants.h" #include "ui/events/events_export.h" #include "ui/events/gestures/gesture_types.h" +#include "ui/gfx/geometry/point_f.h" namespace ui { // A GestureRecognizer is an abstract base class for conversion of touch events @@ -19,6 +20,7 @@ class EVENTS_EXPORT GestureRecognizer { public: static GestureRecognizer* Create(); static GestureRecognizer* Get(); + static void Reset(); // List of GestureEvent*. typedef ScopedVector<GestureEvent> Gestures; @@ -34,8 +36,9 @@ class EVENTS_EXPORT GestureRecognizer { GestureConsumer* consumer) = 0; // This is called when the consumer is destroyed. So this should cleanup any - // internal state maintained for |consumer|. - virtual void CleanupStateForConsumer(GestureConsumer* consumer) = 0; + // internal state maintained for |consumer|. Returns true iff there was + // state relating to |consumer| to clean up. + virtual bool CleanupStateForConsumer(GestureConsumer* consumer) = 0; // Return the window which should handle this TouchEvent, in the case where // the touch is already associated with a target. @@ -46,10 +49,12 @@ class EVENTS_EXPORT GestureRecognizer { virtual GestureConsumer* GetTargetForGestureEvent( const GestureEvent& event) = 0; - // If there is an active touch within - // GestureConfiguration::max_separation_for_gesture_touches_in_pixels, - // of |location|, returns the target of the nearest active touch. - virtual GestureConsumer* GetTargetForLocation(const gfx::Point& location) = 0; + // Returns the target of the nearest active touch with source device of + // |source_device_id|, within + // GestureConfiguration::max_separation_for_gesture_touches_in_pixels of + // |location|, or NULL if no such point exists. + virtual GestureConsumer* GetTargetForLocation( + const gfx::PointF& location, int source_device_id) = 0; // Makes |new_consumer| the target for events previously targeting // |current_consumer|. All other targets are canceled. @@ -65,10 +70,11 @@ class EVENTS_EXPORT GestureRecognizer { // point and true is returned. If no touch events have been processed for // |consumer| false is returned and |point| is untouched. virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer, - gfx::Point* point) = 0; + gfx::PointF* point) = 0; - // Sends a touch cancel event for every active touch. - virtual void CancelActiveTouches(GestureConsumer* consumer) = 0; + // Sends a touch cancel event for every active touch. Returns true iff any + // touch cancels were sent. + virtual bool CancelActiveTouches(GestureConsumer* consumer) = 0; // Subscribes |helper| for dispatching async gestures such as long press. // The Gesture Recognizer does NOT take ownership of |helper| and it is the diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl.cc b/chromium/ui/events/gestures/gesture_recognizer_impl.cc index 4a599345c84..e145334454b 100644 --- a/chromium/ui/events/gestures/gesture_recognizer_impl.cc +++ b/chromium/ui/events/gestures/gesture_recognizer_impl.cc @@ -4,15 +4,21 @@ #include "ui/events/gestures/gesture_recognizer_impl.h" +#include <limits> + +#include "base/command_line.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" #include "base/time/time.h" #include "ui/events/event.h" #include "ui/events/event_constants.h" +#include "ui/events/event_switches.h" #include "ui/events/event_utils.h" #include "ui/events/gestures/gesture_configuration.h" #include "ui/events/gestures/gesture_sequence.h" #include "ui/events/gestures/gesture_types.h" +#include "ui/events/gestures/unified_gesture_detector_enabled.h" namespace ui { @@ -28,15 +34,19 @@ void TransferConsumer(GestureConsumer* current_consumer, } } -void RemoveConsumerFromMap(GestureConsumer* consumer, +bool RemoveConsumerFromMap(GestureConsumer* consumer, GestureRecognizerImpl::TouchIdToConsumerMap* map) { + bool consumer_removed = false; for (GestureRecognizerImpl::TouchIdToConsumerMap::iterator i = map->begin(); i != map->end();) { - if (i->second == consumer) + if (i->second == consumer) { map->erase(i++); - else + consumer_removed = true; + } else { ++i; + } } + return consumer_removed; } void TransferTouchIdToConsumerMap( @@ -50,16 +60,22 @@ void TransferTouchIdToConsumerMap( } } +GestureProviderAura* CreateGestureProvider(GestureProviderAuraClient* client) { + return new GestureProviderAura(client); +} + } // namespace //////////////////////////////////////////////////////////////////////////////// // GestureRecognizerImpl, public: GestureRecognizerImpl::GestureRecognizerImpl() { + use_unified_gesture_detector_ = IsUnifiedGestureDetectorEnabled(); } GestureRecognizerImpl::~GestureRecognizerImpl() { STLDeleteValues(&consumer_sequence_); + STLDeleteValues(&consumer_gesture_provider_); } // Checks if this finger is already down, if so, returns the current target. @@ -78,33 +94,66 @@ GestureConsumer* GestureRecognizerImpl::GetTargetForGestureEvent( } GestureConsumer* GestureRecognizerImpl::GetTargetForLocation( - const gfx::Point& location) { - const GesturePoint* closest_point = NULL; - int64 closest_distance_squared = 0; - std::map<GestureConsumer*, GestureSequence*>::iterator i; - for (i = consumer_sequence_.begin(); i != consumer_sequence_.end(); ++i) { - const GesturePoint* points = i->second->points(); - for (int j = 0; j < GestureSequence::kMaxGesturePoints; ++j) { - if (!points[j].in_use()) - continue; - gfx::Vector2d delta = points[j].last_touch_position() - location; - // Relative distance is all we need here, so LengthSquared() is - // appropriate, and cheaper than Length(). - int64 distance_squared = delta.LengthSquared(); - if (!closest_point || distance_squared < closest_distance_squared) { - closest_point = &points[j]; - closest_distance_squared = distance_squared; + const gfx::PointF& location, int source_device_id) { + const int max_distance = + GestureConfiguration::max_separation_for_gesture_touches_in_pixels(); + + if (!use_unified_gesture_detector_) { + const GesturePoint* closest_point = NULL; + int64 closest_distance_squared = 0; + std::map<GestureConsumer*, GestureSequence*>::iterator i; + for (i = consumer_sequence_.begin(); i != consumer_sequence_.end(); ++i) { + const GesturePoint* points = i->second->points(); + for (int j = 0; j < GestureSequence::kMaxGesturePoints; ++j) { + if (!points[j].in_use() || + source_device_id != points[j].source_device_id()) { + continue; + } + gfx::Vector2dF delta = points[j].last_touch_position() - location; + // Relative distance is all we need here, so LengthSquared() is + // appropriate, and cheaper than Length(). + int64 distance_squared = delta.LengthSquared(); + if (!closest_point || distance_squared < closest_distance_squared) { + closest_point = &points[j]; + closest_distance_squared = distance_squared; + } } } - } - const int max_distance = - GestureConfiguration::max_separation_for_gesture_touches_in_pixels(); + if (closest_distance_squared < max_distance * max_distance && closest_point) + return touch_id_target_[closest_point->touch_id()]; + else + return NULL; + } else { + gfx::PointF closest_point; + int closest_touch_id; + float closest_distance_squared = std::numeric_limits<float>::infinity(); + + std::map<GestureConsumer*, GestureProviderAura*>::iterator i; + for (i = consumer_gesture_provider_.begin(); + i != consumer_gesture_provider_.end(); + ++i) { + const MotionEventAura& pointer_state = i->second->pointer_state(); + for (size_t j = 0; j < pointer_state.GetPointerCount(); ++j) { + if (source_device_id != pointer_state.GetSourceDeviceId(j)) + continue; + gfx::PointF point(pointer_state.GetX(j), pointer_state.GetY(j)); + // Relative distance is all we need here, so LengthSquared() is + // appropriate, and cheaper than Length(). + float distance_squared = (point - location).LengthSquared(); + if (distance_squared < closest_distance_squared) { + closest_point = point; + closest_touch_id = pointer_state.GetPointerId(j); + closest_distance_squared = distance_squared; + } + } + } - if (closest_distance_squared < max_distance * max_distance && closest_point) - return touch_id_target_[closest_point->touch_id()]; - else - return NULL; + if (closest_distance_squared < max_distance * max_distance) + return touch_id_target_[closest_touch_id]; + else + return NULL; + } } void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer, @@ -134,29 +183,42 @@ void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer, &touch_id_target_); TransferTouchIdToConsumerMap(current_consumer, new_consumer, &touch_id_target_for_gestures_); - TransferConsumer(current_consumer, new_consumer, &consumer_sequence_); + if (!use_unified_gesture_detector_) + TransferConsumer(current_consumer, new_consumer, &consumer_sequence_); + else + TransferConsumer( + current_consumer, new_consumer, &consumer_gesture_provider_); } } bool GestureRecognizerImpl::GetLastTouchPointForTarget( GestureConsumer* consumer, - gfx::Point* point) { - if (consumer_sequence_.count(consumer) == 0) - return false; - - *point = consumer_sequence_[consumer]->last_touch_location(); - return true; + gfx::PointF* point) { + if (!use_unified_gesture_detector_) { + if (consumer_sequence_.count(consumer) == 0) + return false; + *point = consumer_sequence_[consumer]->last_touch_location(); + return true; + } else { + if (consumer_gesture_provider_.count(consumer) == 0) + return false; + const MotionEvent& pointer_state = + consumer_gesture_provider_[consumer]->pointer_state(); + *point = gfx::PointF(pointer_state.GetX(), pointer_state.GetY()); + return true; + } } -void GestureRecognizerImpl::CancelActiveTouches( - GestureConsumer* consumer) { +bool GestureRecognizerImpl::CancelActiveTouches(GestureConsumer* consumer) { std::vector<std::pair<int, GestureConsumer*> > ids; for (TouchIdToConsumerMap::const_iterator i = touch_id_target_.begin(); i != touch_id_target_.end(); ++i) { if (i->second == consumer) ids.push_back(std::make_pair(i->first, i->second)); } + bool cancelled_touch = !ids.empty(); CancelTouches(&ids); + return cancelled_touch; } //////////////////////////////////////////////////////////////////////////////// @@ -180,6 +242,16 @@ GestureSequence* GestureRecognizerImpl::GetGestureSequenceForConsumer( return gesture_sequence; } +GestureProviderAura* GestureRecognizerImpl::GetGestureProviderForConsumer( + GestureConsumer* consumer) { + GestureProviderAura* gesture_provider = consumer_gesture_provider_[consumer]; + if (!gesture_provider) { + gesture_provider = CreateGestureProvider(this); + consumer_gesture_provider_[consumer] = gesture_provider; + } + return gesture_provider; +} + void GestureRecognizerImpl::SetupTargets(const TouchEvent& event, GestureConsumer* target) { if (event.type() == ui::ET_TOUCH_RELEASED || @@ -197,7 +269,7 @@ void GestureRecognizerImpl::CancelTouches( while (!touches->empty()) { int touch_id = touches->begin()->first; GestureConsumer* target = touches->begin()->second; - TouchEvent touch_event(ui::ET_TOUCH_CANCELLED, gfx::Point(0, 0), + TouchEvent touch_event(ui::ET_TOUCH_CANCELLED, gfx::PointF(0, 0), ui::EF_IS_SYNTHESIZED, touch_id, ui::EventTimeForNow(), 0.0f, 0.0f, 0.0f, 0.0f); GestureEventHelper* helper = FindDispatchHelperForConsumer(target); @@ -207,23 +279,60 @@ void GestureRecognizerImpl::CancelTouches( } } -GestureSequence::Gestures* GestureRecognizerImpl::ProcessTouchEventForGesture( +void GestureRecognizerImpl::DispatchGestureEvent(GestureEvent* event) { + GestureConsumer* consumer = GetTargetForGestureEvent(*event); + if (consumer) { + GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer); + if (helper) + helper->DispatchGestureEvent(event); + } +} + +ScopedVector<GestureEvent>* GestureRecognizerImpl::ProcessTouchEventForGesture( const TouchEvent& event, ui::EventResult result, GestureConsumer* target) { SetupTargets(event, target); - GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target); - return gesture_sequence->ProcessTouchEventForGesture(event, result); + + if (!use_unified_gesture_detector_) { + GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target); + return gesture_sequence->ProcessTouchEventForGesture(event, result); + } else { + GestureProviderAura* gesture_provider = + GetGestureProviderForConsumer(target); + // TODO(tdresser) - detect gestures eagerly. + if (!(result & ER_CONSUMED)) { + if (gesture_provider->OnTouchEvent(event)) { + gesture_provider->OnTouchEventAck(result != ER_UNHANDLED); + return gesture_provider->GetAndResetPendingGestures(); + } + } + return NULL; + } } -void GestureRecognizerImpl::CleanupStateForConsumer(GestureConsumer* consumer) { - if (consumer_sequence_.count(consumer)) { - delete consumer_sequence_[consumer]; - consumer_sequence_.erase(consumer); +bool GestureRecognizerImpl::CleanupStateForConsumer( + GestureConsumer* consumer) { + bool state_cleaned_up = false; + + if (!use_unified_gesture_detector_) { + if (consumer_sequence_.count(consumer)) { + state_cleaned_up = true; + delete consumer_sequence_[consumer]; + consumer_sequence_.erase(consumer); + } + } else { + if (consumer_gesture_provider_.count(consumer)) { + state_cleaned_up = true; + delete consumer_gesture_provider_[consumer]; + consumer_gesture_provider_.erase(consumer); + } } - RemoveConsumerFromMap(consumer, &touch_id_target_); - RemoveConsumerFromMap(consumer, &touch_id_target_for_gestures_); + state_cleaned_up |= RemoveConsumerFromMap(consumer, &touch_id_target_); + state_cleaned_up |= + RemoveConsumerFromMap(consumer, &touch_id_target_for_gestures_); + return state_cleaned_up; } void GestureRecognizerImpl::AddGestureEventHelper(GestureEventHelper* helper) { @@ -239,12 +348,11 @@ void GestureRecognizerImpl::RemoveGestureEventHelper( } void GestureRecognizerImpl::DispatchPostponedGestureEvent(GestureEvent* event) { - GestureConsumer* consumer = GetTargetForGestureEvent(*event); - if (consumer) { - GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer); - if (helper) - helper->DispatchPostponedGestureEvent(event); - } + DispatchGestureEvent(event); +} + +void GestureRecognizerImpl::OnGestureEvent(GestureEvent* event) { + DispatchGestureEvent(event); } GestureEventHelper* GestureRecognizerImpl::FindDispatchHelperForConsumer( @@ -271,6 +379,12 @@ GestureRecognizer* GestureRecognizer::Get() { return g_gesture_recognizer_instance; } +// GestureRecognizer, static +void GestureRecognizer::Reset() { + delete g_gesture_recognizer_instance; + g_gesture_recognizer_instance = NULL; +} + void SetGestureRecognizerForTesting(GestureRecognizer* gesture_recognizer) { // Transfer helpers to the new GR. std::vector<GestureEventHelper*>& helpers = diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl.h b/chromium/ui/events/gestures/gesture_recognizer_impl.h index 59c92cfb4a4..80a3007030d 100644 --- a/chromium/ui/events/gestures/gesture_recognizer_impl.h +++ b/chromium/ui/events/gestures/gesture_recognizer_impl.h @@ -12,6 +12,7 @@ #include "base/memory/scoped_ptr.h" #include "ui/events/event_constants.h" #include "ui/events/events_export.h" +#include "ui/events/gestures/gesture_provider_aura.h" #include "ui/events/gestures/gesture_recognizer.h" #include "ui/events/gestures/gesture_sequence.h" #include "ui/gfx/point.h" @@ -23,8 +24,12 @@ class GestureEventHelper; class GestureSequence; class TouchEvent; +// TODO(tdresser): Once the unified gesture recognition process sticks +// (crbug.com/332418), GestureRecognizerImpl can be cleaned up +// significantly. class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer, - public GestureSequenceDelegate { + public GestureSequenceDelegate, + public GestureProviderAuraClient { public: typedef std::map<int, GestureConsumer*> TouchIdToConsumerMap; @@ -39,39 +44,49 @@ class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer, virtual GestureConsumer* GetTargetForGestureEvent( const GestureEvent& event) OVERRIDE; virtual GestureConsumer* GetTargetForLocation( - const gfx::Point& location) OVERRIDE; + const gfx::PointF& location, int source_device_id) OVERRIDE; virtual void TransferEventsTo(GestureConsumer* current_consumer, GestureConsumer* new_consumer) OVERRIDE; virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer, - gfx::Point* point) OVERRIDE; - virtual void CancelActiveTouches(GestureConsumer* consumer) OVERRIDE; + gfx::PointF* point) OVERRIDE; + virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE; protected: - virtual GestureSequence* CreateSequence(GestureSequenceDelegate* delegate); virtual GestureSequence* GetGestureSequenceForConsumer(GestureConsumer* c); + virtual GestureProviderAura* GetGestureProviderForConsumer( + GestureConsumer* c); + virtual GestureSequence* CreateSequence( + ui::GestureSequenceDelegate* delegate); private: // Sets up the target consumer for gestures based on the touch-event. void SetupTargets(const TouchEvent& event, GestureConsumer* consumer); void CancelTouches(std::vector<std::pair<int, GestureConsumer*> >* touches); + void DispatchGestureEvent(GestureEvent* event); + // Overridden from GestureRecognizer virtual Gestures* ProcessTouchEventForGesture( const TouchEvent& event, ui::EventResult result, GestureConsumer* target) OVERRIDE; - virtual void CleanupStateForConsumer(GestureConsumer* consumer) OVERRIDE; + virtual bool CleanupStateForConsumer(GestureConsumer* consumer) + OVERRIDE; virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE; virtual void RemoveGestureEventHelper(GestureEventHelper* helper) OVERRIDE; // Overridden from ui::GestureSequenceDelegate. virtual void DispatchPostponedGestureEvent(GestureEvent* event) OVERRIDE; + // Overridden from GestureProviderAuraClient + virtual void OnGestureEvent(GestureEvent* event) OVERRIDE; + // Convenience method to find the GestureEventHelper that can dispatch events // to a specific |consumer|. GestureEventHelper* FindDispatchHelperForConsumer(GestureConsumer* consumer); std::map<GestureConsumer*, GestureSequence*> consumer_sequence_; + std::map<GestureConsumer*, GestureProviderAura*> consumer_gesture_provider_; // Both |touch_id_target_| and |touch_id_target_for_gestures_| map a touch-id // to its target window. touch-ids are removed from |touch_id_target_| on @@ -82,6 +97,8 @@ class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer, std::vector<GestureEventHelper*> helpers_; + bool use_unified_gesture_detector_; + DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImpl); }; diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc b/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc new file mode 100644 index 00000000000..46dde02fad9 --- /dev/null +++ b/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc @@ -0,0 +1,64 @@ +// Copyright 2014 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 "base/macros.h" +#include "ui/events/gestures/gesture_recognizer.h" + +namespace ui { + +namespace { + +// Stub implementation of GestureRecognizer for Mac. Currently only serves to +// provide a no-op implementation of TransferEventsTo(). +class GestureRecognizerImplMac : public GestureRecognizer { + public: + GestureRecognizerImplMac() {} + virtual ~GestureRecognizerImplMac() {} + + private: + virtual Gestures* ProcessTouchEventForGesture( + const TouchEvent& event, + ui::EventResult result, + GestureConsumer* consumer) OVERRIDE { + return NULL; + } + virtual bool CleanupStateForConsumer(GestureConsumer* consumer) OVERRIDE { + return false; + } + virtual GestureConsumer* GetTouchLockedTarget( + const TouchEvent& event) OVERRIDE { + return NULL; + } + virtual GestureConsumer* GetTargetForGestureEvent( + const GestureEvent& event) OVERRIDE { + return NULL; + } + virtual GestureConsumer* GetTargetForLocation(const gfx::PointF& location, + int source_device_id) OVERRIDE { + return NULL; + } + virtual void TransferEventsTo(GestureConsumer* current_consumer, + GestureConsumer* new_consumer) OVERRIDE {} + virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer, + gfx::PointF* point) OVERRIDE { + return false; + } + virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE { + return false; + } + virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE {} + virtual void RemoveGestureEventHelper(GestureEventHelper* helper) OVERRIDE {} + + DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImplMac); +}; + +} // namespace + +// static +GestureRecognizer* GestureRecognizer::Get() { + CR_DEFINE_STATIC_LOCAL(GestureRecognizerImplMac, instance, ()); + return &instance; +} + +} // namespace ui diff --git a/chromium/ui/events/gestures/gesture_sequence.cc b/chromium/ui/events/gestures/gesture_sequence.cc index 7c195199454..14824d0f676 100644 --- a/chromium/ui/events/gestures/gesture_sequence.cc +++ b/chromium/ui/events/gestures/gesture_sequence.cc @@ -6,6 +6,7 @@ #include <stdlib.h> #include <cmath> +#include <limits> #include "base/command_line.h" #include "base/logging.h" @@ -16,7 +17,6 @@ #include "ui/events/event_constants.h" #include "ui/events/event_switches.h" #include "ui/events/gestures/gesture_configuration.h" -#include "ui/events/gestures/gesture_util.h" #include "ui/gfx/rect.h" namespace ui { @@ -29,7 +29,6 @@ enum TouchState { TS_RELEASED, TS_PRESSED, TS_MOVED, - TS_STATIONARY, TS_CANCELLED, TS_UNKNOWN, }; @@ -60,8 +59,6 @@ TouchState TouchEventTypeToTouchState(ui::EventType type) { return TS_PRESSED; case ui::ET_TOUCH_MOVED: return TS_MOVED; - case ui::ET_TOUCH_STATIONARY: - return TS_STATIONARY; case ui::ET_TOUCH_CANCELLED: return TS_CANCELLED; default: @@ -99,9 +96,6 @@ enum EdgeStateSignatureType { GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED = G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_PROCESSED), - GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY = - G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_STATIONARY, TSI_NOT_PROCESSED), - GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED = G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_CANCELLED, TSI_ALWAYS), @@ -120,21 +114,26 @@ enum EdgeStateSignatureType { GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED = G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_MOVED, TSI_ALWAYS), - GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY = - G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_STATIONARY, TSI_ALWAYS), - GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED = G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS), GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED = G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 1, TS_PRESSED, TSI_NOT_PROCESSED), + GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED = + G(GS_SYNTHETIC_CLICK_ABORTED, 0, TS_RELEASED, TSI_ALWAYS), + + GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED = + G(GS_SYNTHETIC_CLICK_ABORTED, 1, TS_PRESSED, TSI_NOT_PROCESSED), + GST_SCROLL_FIRST_RELEASED = G(GS_SCROLL, 0, TS_RELEASED, TSI_ALWAYS), - // Once scroll has started, process all touch-move events. GST_SCROLL_FIRST_MOVED = - G(GS_SCROLL, 0, TS_MOVED, TSI_ALWAYS), + G(GS_SCROLL, 0, TS_MOVED, TSI_NOT_PROCESSED), + + GST_SCROLL_FIRST_MOVED_HANDLED = + G(GS_SCROLL, 0, TS_MOVED, TSI_PROCESSED), GST_SCROLL_FIRST_CANCELLED = G(GS_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS), @@ -333,17 +332,18 @@ EdgeStateSignatureType Signature(GestureState gesture_state, case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED: case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED: case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED: - case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY: case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED: case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED: case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED: case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED: case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED: - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY: case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED: case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED: + case GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED: + case GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED: case GST_SCROLL_FIRST_RELEASED: case GST_SCROLL_FIRST_MOVED: + case GST_SCROLL_FIRST_MOVED_HANDLED: case GST_SCROLL_FIRST_CANCELLED: case GST_SCROLL_SECOND_PRESSED: case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED: @@ -412,7 +412,7 @@ EdgeStateSignatureType Signature(GestureState gesture_state, } #undef G -float BoundingBoxDiagonal(const gfx::Rect& rect) { +float BoundingBoxDiagonal(const gfx::RectF& rect) { float width = rect.width() * rect.width(); float height = rect.height() * rect.height(); return sqrt(width + height); @@ -449,28 +449,28 @@ float CalibrateFlingVelocity(float velocity) { void UpdateGestureEventLatencyInfo(const TouchEvent& event, GestureSequence::Gestures* gestures) { - // If the touch event does not cause any rendering scheduled, we first - // end the touch event's LatencyInfo. Then we copy the touch event's - // LatencyInfo into the generated gesture's LatencyInfo. Since one touch - // event can generate multiple gesture events, we have to clear the gesture - // event's trace_id, remove its ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, - // so when the gesture event passes through RWHI, a new trace_id will be - // assigned and new ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT will be added. - if (!event.latency()->FindLatency( - ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT, 0, NULL)) { - ui::LatencyInfo* touch_latency = - const_cast<ui::LatencyInfo*>(event.latency()); - touch_latency->AddLatencyNumber( - ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT, 0, 0); - GestureSequence::Gestures::iterator it = gestures->begin(); - for (; it != gestures->end(); it++) { - ui::LatencyInfo* gesture_latency = (*it)->latency(); - *gesture_latency = *touch_latency; - gesture_latency->trace_id = -1; - gesture_latency->terminated = false; - gesture_latency->RemoveLatency( - ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); - } + // Copy some of the touch event's LatencyInfo into the generated gesture's + // LatencyInfo so we can compute touch to scroll latency from gesture + // event's LatencyInfo. + GestureSequence::Gestures::iterator it = gestures->begin(); + for (; it != gestures->end(); it++) { + ui::LatencyInfo* gesture_latency = (*it)->latency(); + gesture_latency->CopyLatencyFrom( + *event.latency(), ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT); + gesture_latency->CopyLatencyFrom( + *event.latency(), ui::INPUT_EVENT_LATENCY_UI_COMPONENT); + gesture_latency->CopyLatencyFrom( + *event.latency(), ui::INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT); + } +} + +bool GestureStateSupportsActiveTimer(GestureState state) { + switch(state) { + case GS_PENDING_SYNTHETIC_CLICK: + case GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL: + return true; + default: + return false; } } @@ -520,6 +520,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( } new_point->set_point_id(point_count_++); new_point->set_touch_id(event.touch_id()); + new_point->set_source_device_id(event.source_device_id()); } GestureState last_state = state_; @@ -556,6 +557,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( set_state(GS_PENDING_SYNTHETIC_CLICK); break; case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED: + case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED: if (Click(event, point, gestures.get())) point.UpdateForTap(); else @@ -563,39 +565,41 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( set_state(GS_NO_GESTURE); break; case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED: - case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY: if (ScrollStart(event, point, gestures.get())) { PrependTapCancelGestureEvent(point, gestures.get()); set_state(GS_SCROLL); - if (ScrollUpdate(event, point, gestures.get())) + if (ScrollUpdate(event, point, gestures.get(), FS_FIRST_SCROLL)) point.UpdateForScroll(); } break; - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED: - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY: - // No scrolling allowed, so nothing happens. - break; case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED: + case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED: if (point.IsInScrollWindow(event)) { PrependTapCancelGestureEvent(point, gestures.get()); + set_state(GS_SYNTHETIC_CLICK_ABORTED); + } else { set_state(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL); } break; case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED: case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED: + case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED: + case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED: PrependTapCancelGestureEvent(point, gestures.get()); set_state(GS_NO_GESTURE); break; - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED: - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED: - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED: + case GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED: set_state(GS_NO_GESTURE); break; case GST_SCROLL_FIRST_MOVED: if (scroll_type_ == ST_VERTICAL || scroll_type_ == ST_HORIZONTAL) BreakRailScroll(event, point, gestures.get()); - if (ScrollUpdate(event, point, gestures.get())) + if (ScrollUpdate(event, point, gestures.get(), FS_NOT_FIRST_SCROLL)) + point.UpdateForScroll(); + break; + case GST_SCROLL_FIRST_MOVED_HANDLED: + if (point.DidScroll(event, 0)) point.UpdateForScroll(); break; case GST_SCROLL_FIRST_RELEASED: @@ -604,10 +608,11 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( set_state(GS_NO_GESTURE); break; case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED: + case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED: PrependTapCancelGestureEvent(point, gestures.get()); TwoFingerTapOrPinch(event, point, gestures.get()); break; - case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED: + case GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED: TwoFingerTapOrPinch(event, point, gestures.get()); break; case GST_SCROLL_SECOND_PRESSED: @@ -617,7 +622,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED: case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED: TwoFingerTouchReleased(event, point, gestures.get()); - set_state(GS_SCROLL); + StartRailFreeScroll(point, gestures.get()); break; case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED: case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED: @@ -632,8 +637,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED: case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED: case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED: - scroll_type_ = ST_FREE; - set_state(GS_SCROLL); + StartRailFreeScroll(point, gestures.get()); break; case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED: set_state(GS_PENDING_PINCH); @@ -645,18 +649,17 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_RELEASED: case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_RELEASED: TwoFingerTouchReleased(event, point, gestures.get()); - // We transit into GS_SCROLL even though the touch move can be - // consumed and no scroll should happen. crbug.com/240399. - set_state(GS_SCROLL); + // We transition into GS_SCROLL even though the touch move can be consumed + // and no scroll should happen. crbug.com/240399. + StartRailFreeScroll(point, gestures.get()); break; case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_RELEASED_HANDLED: case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_RELEASED_HANDLED: case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_CANCELLED: case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_CANCELLED: - // We transit into GS_SCROLL even though the touch move can be - // consumed and no scroll should happen. crbug.com/240399. - scroll_type_ = ST_FREE; - set_state(GS_SCROLL); + // We transition into GS_SCROLL even though the touch move can be consumed + // and no scroll should happen. crbug.com/240399. + StartRailFreeScroll(point, gestures.get()); break; case GST_PENDING_PINCH_FIRST_MOVED: case GST_PENDING_PINCH_SECOND_MOVED: @@ -671,10 +674,9 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( case GST_PENDING_PINCH_SECOND_RELEASED: case GST_PENDING_PINCH_FIRST_CANCELLED: case GST_PENDING_PINCH_SECOND_CANCELLED: - // We transit into GS_SCROLL even though the touch move can be - // consumed and no scroll should happen. crbug.com/240399. - scroll_type_ = ST_FREE; - set_state(GS_SCROLL); + // We transition into GS_SCROLL even though the touch move can be consumed + // and no scroll should happen. crbug.com/240399. + StartRailFreeScroll(point, gestures.get()); break; case GST_PENDING_PINCH_NO_PINCH_FIRST_MOVED: case GST_PENDING_PINCH_NO_PINCH_SECOND_MOVED: @@ -684,16 +686,19 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( case GST_PENDING_PINCH_NO_PINCH_SECOND_RELEASED: case GST_PENDING_PINCH_NO_PINCH_FIRST_CANCELLED: case GST_PENDING_PINCH_NO_PINCH_SECOND_CANCELLED: - // We transit into GS_SCROLL even though the touch move can be - // consumed and no scroll should happen. crbug.com/240399. - scroll_type_ = ST_FREE; - set_state(GS_SCROLL); + // We transition into GS_SCROLL even though the touch move can be consumed + // and no scroll should happen. crbug.com/240399. + StartRailFreeScroll(point, gestures.get()); break; case GST_PINCH_FIRST_MOVED_HANDLED: case GST_PINCH_SECOND_MOVED_HANDLED: case GST_PINCH_THIRD_MOVED_HANDLED: case GST_PINCH_FOURTH_MOVED_HANDLED: case GST_PINCH_FIFTH_MOVED_HANDLED: + // If touches are consumed for a while, and then left unconsumed, we don't + // want a PinchUpdate or ScrollUpdate with a massive delta. + latest_multi_scroll_update_location_ = bounding_box_.CenterPoint(); + pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_); break; case GST_PINCH_FIRST_MOVED: case GST_PINCH_SECOND_MOVED: @@ -750,7 +755,10 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture( << " State: " << state_ << " touch id: " << event.touch_id(); - if (last_state == GS_PENDING_SYNTHETIC_CLICK && state_ != last_state) { + // If the state has changed from one in which a long/show press is possible to + // one in which they are not possible, cancel the timers. + if (GestureStateSupportsActiveTimer(last_state) && + !GestureStateSupportsActiveTimer(state_)) { GetLongPressTimer()->Stop(); GetShowPressTimer()->Stop(); } @@ -789,8 +797,10 @@ void GestureSequence::RecreateBoundingBox() { } else if (point_count_ == 1) { bounding_box_ = GetPointByPointId(0)->enclosing_rectangle(); } else { - int left = INT_MAX / 20, top = INT_MAX / 20; - int right = INT_MIN / 20, bottom = INT_MIN / 20; + float left = std::numeric_limits<float>::max(); + float top = std::numeric_limits<float>::max(); + float right = -std::numeric_limits<float>::max(); + float bottom = -std::numeric_limits<float>::max(); for (int i = 0; i < kMaxGesturePoints; ++i) { if (!points_[i].in_use()) continue; @@ -798,7 +808,7 @@ void GestureSequence::RecreateBoundingBox() { // However, this becomes brittle especially when a finger is in motion // because the change in radius can overshadow the actual change in // position. So the actual position of the point is used instead. - const gfx::Point& point = points_[i].last_touch_position(); + const gfx::PointF& point = points_[i].last_touch_position(); left = std::min(left, point.x()); right = std::max(right, point.x()); top = std::min(top, point.y()); @@ -854,8 +864,8 @@ GesturePoint* GestureSequence::GetPointByPointId(int point_id) { } bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() { - gfx::Point p1 = GetPointByPointId(0)->last_touch_position(); - gfx::Point p2 = GetPointByPointId(1)->last_touch_position(); + gfx::PointF p1 = GetPointByPointId(0)->last_touch_position(); + gfx::PointF p2 = GetPointByPointId(1)->last_touch_position(); double max_distance = GestureConfiguration::max_distance_for_two_finger_tap_in_pixels(); double distance = (p1.x() - p2.x()) * (p1.x() - p2.x()) + @@ -867,7 +877,7 @@ bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() { GestureEvent* GestureSequence::CreateGestureEvent( const GestureEventDetails& details, - const gfx::Point& location, + const gfx::PointF& location, int flags, base::Time timestamp, unsigned int touch_id_bitmask) { @@ -915,7 +925,7 @@ void GestureSequence::AppendEndGestureEvent(const GesturePoint& point, Gestures* gestures) { gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_END, 0, 0), - point.first_touch_position(), + point.last_touch_position(), flags_, base::Time::FromDoubleT(point.last_touch_time()), 1 << point.touch_id())); @@ -924,8 +934,8 @@ void GestureSequence::AppendEndGestureEvent(const GesturePoint& point, void GestureSequence::AppendClickGestureEvent(const GesturePoint& point, int tap_count, Gestures* gestures) { - gfx::Rect er = point.enclosing_rectangle(); - gfx::Point center = er.CenterPoint(); + gfx::RectF er = point.enclosing_rectangle(); + gfx::PointF center = er.CenterPoint(); gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_TAP, tap_count, 0), center, @@ -935,10 +945,11 @@ void GestureSequence::AppendClickGestureEvent(const GesturePoint& point, } void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point, - const gfx::Point& location, + const gfx::PointF& location, Gestures* gestures) { + gfx::Vector2dF d = point.ScrollDelta(); gestures->push_back(CreateGestureEvent( - GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0), + GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, d.x(), d.y()), location, flags_, base::Time::FromDoubleT(point.last_touch_time()), @@ -946,7 +957,7 @@ void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point, } void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point, - const gfx::Point& location, + const gfx::PointF& location, Gestures* gestures, float x_velocity, float y_velocity) { @@ -965,9 +976,7 @@ void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point, gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_SCROLL_FLING_START, CalibrateFlingVelocity(railed_x_velocity), - CalibrateFlingVelocity(railed_y_velocity), - CalibrateFlingVelocity(x_velocity), - CalibrateFlingVelocity(y_velocity)), + CalibrateFlingVelocity(railed_y_velocity)), location, flags_, base::Time::FromDoubleT(point.last_touch_time()), @@ -983,11 +992,12 @@ void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point, } void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point, - Gestures* gestures) { + Gestures* gestures, + IsFirstScroll is_first_scroll) { static bool use_scroll_prediction = CommandLine::ForCurrentProcess()-> HasSwitch(switches::kEnableScrollPrediction); gfx::Vector2dF d; - gfx::Point location; + gfx::PointF location; if (point_count_ == 1) { d = point.ScrollDelta(); location = point.last_touch_position(); @@ -1006,11 +1016,18 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point, last_scroll_prediction_offset_.set_y( GestureConfiguration::scroll_prediction_seconds() * point.YVelocity()); d += last_scroll_prediction_offset_; - location += gfx::Vector2d(last_scroll_prediction_offset_.x(), - last_scroll_prediction_offset_.y()); + location += gfx::Vector2dF(last_scroll_prediction_offset_.x(), + last_scroll_prediction_offset_.y()); } - gfx::Vector2dF o = d; + if (is_first_scroll == FS_FIRST_SCROLL) { + float slop = GestureConfiguration::max_touch_move_in_pixels_for_click(); + float length = d.Length(); + float ratio = std::max((length - slop) / length, 0.0f); + + d.set_x(d.x() * ratio); + d.set_y(d.y() * ratio); + } if (scroll_type_ == ST_HORIZONTAL) d.set_y(0); @@ -1019,13 +1036,7 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point, if (d.IsZero()) return; - GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE, - d.x(), d.y(), o.x(), o.y()); - details.SetScrollVelocity( - scroll_type_ == ST_VERTICAL ? 0 : point.XVelocity(), - scroll_type_ == ST_HORIZONTAL ? 0 : point.YVelocity(), - point.XVelocity(), - point.YVelocity()); + GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE, d.x(), d.y()); gestures->push_back(CreateGestureEvent( details, location, @@ -1037,7 +1048,7 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point, void GestureSequence::AppendPinchGestureBegin(const GesturePoint& p1, const GesturePoint& p2, Gestures* gestures) { - gfx::Point center = bounding_box_.CenterPoint(); + gfx::PointF center = bounding_box_.CenterPoint(); gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_PINCH_BEGIN, 0, 0), center, @@ -1050,7 +1061,7 @@ void GestureSequence::AppendPinchGestureEnd(const GesturePoint& p1, const GesturePoint& p2, float scale, Gestures* gestures) { - gfx::Point center = bounding_box_.CenterPoint(); + gfx::PointF center = bounding_box_.CenterPoint(); gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_PINCH_END, 0, 0), center, @@ -1077,7 +1088,7 @@ void GestureSequence::AppendSwipeGesture(const GesturePoint& point, int swipe_y, Gestures* gestures) { gestures->push_back(CreateGestureEvent( - GestureEventDetails(ui::ET_GESTURE_MULTIFINGER_SWIPE, swipe_x, swipe_y), + GestureEventDetails(ui::ET_GESTURE_SWIPE, swipe_x, swipe_y), bounding_box_.CenterPoint(), flags_, base::Time::FromDoubleT(point.last_touch_time()), @@ -1086,7 +1097,7 @@ void GestureSequence::AppendSwipeGesture(const GesturePoint& point, void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) { const GesturePoint* point = GetPointByPointId(0); - const gfx::Rect rect = point->enclosing_rectangle(); + const gfx::RectF& rect = point->enclosing_rectangle(); gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_TWO_FINGER_TAP, rect.width(), @@ -1100,7 +1111,8 @@ void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) { bool GestureSequence::Click(const TouchEvent& event, const GesturePoint& point, Gestures* gestures) { - DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK); + DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK || + state_ == GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL); if (point.IsInClickWindow(event)) { int tap_count = 1; if (point.IsInTripleClickWindow(event)) @@ -1113,7 +1125,7 @@ bool GestureSequence::Click(const TouchEvent& event, } AppendClickGestureEvent(point, tap_count, gestures); return true; - } else if (point.IsInsideManhattanSquare(event) && + } else if (point.IsInsideTouchSlopRegion(event) && !GetLongPressTimer()->IsRunning()) { AppendLongTapGestureEvent(point, gestures); } @@ -1150,11 +1162,12 @@ void GestureSequence::BreakRailScroll(const TouchEvent& event, bool GestureSequence::ScrollUpdate(const TouchEvent& event, GesturePoint& point, - Gestures* gestures) { + Gestures* gestures, + IsFirstScroll is_first_scroll) { DCHECK(state_ == GS_SCROLL); if (!point.DidScroll(event, 0)) return false; - AppendScrollGestureUpdate(point, gestures); + AppendScrollGestureUpdate(point, gestures, is_first_scroll); return true; } @@ -1185,11 +1198,13 @@ bool GestureSequence::TwoFingerTouchDown(const TouchEvent& event, Gestures* gestures) { DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK || state_ == GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL || + state_ == GS_SYNTHETIC_CLICK_ABORTED || state_ == GS_SCROLL); if (state_ == GS_SCROLL) { - AppendScrollGestureEnd(point, point.last_touch_position(), gestures, - 0.f, 0.f); + AppendScrollGestureEnd(point, + point.last_touch_position(), + gestures, 0.f, 0.f); } second_touch_time_ = event.time_stamp(); return true; @@ -1204,7 +1219,7 @@ bool GestureSequence::TwoFingerTouchMove(const TouchEvent& event, base::TimeDelta time_delta = event.time_stamp() - second_touch_time_; base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 * ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click()); - if (time_delta > max_delta || !point.IsInsideManhattanSquare(event)) { + if (time_delta > max_delta || !point.IsInsideTouchSlopRegion(event)) { PinchStart(event, point, gestures); return true; } @@ -1219,7 +1234,7 @@ bool GestureSequence::TwoFingerTouchReleased(const TouchEvent& event, base::TimeDelta time_delta = event.time_stamp() - second_touch_time_; base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 * ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click()); - if (time_delta < max_delta && point.IsInsideManhattanSquare(event)) + if (time_delta < max_delta && point.IsInsideTouchSlopRegion(event)) AppendTwoFingerTapGestureEvent(gestures); return true; } @@ -1248,11 +1263,9 @@ void GestureSequence::AppendShowPressGestureEvent() { void GestureSequence::AppendLongTapGestureEvent(const GesturePoint& point, Gestures* gestures) { - gfx::Rect er = point.enclosing_rectangle(); - gfx::Point center = er.CenterPoint(); gestures->push_back(CreateGestureEvent( GestureEventDetails(ui::ET_GESTURE_LONG_TAP, 0, 0), - center, + point.enclosing_rectangle().CenterPoint(), flags_, base::Time::FromDoubleT(point.last_touch_time()), 1 << point.touch_id())); @@ -1263,11 +1276,14 @@ bool GestureSequence::ScrollEnd(const TouchEvent& event, Gestures* gestures) { DCHECK(state_ == GS_SCROLL); if (point.IsInFlickWindow(event)) { - AppendScrollGestureEnd(point, point.last_touch_position(), gestures, - point.XVelocity(), point.YVelocity()); + AppendScrollGestureEnd(point, + point.last_touch_position(), + gestures, + point.XVelocity(), point.YVelocity()); } else { - AppendScrollGestureEnd(point, point.last_touch_position(), gestures, - 0.f, 0.f); + AppendScrollGestureEnd(point, + point.last_touch_position(), + gestures, 0.f, 0.f); } return true; } @@ -1285,17 +1301,16 @@ bool GestureSequence::PinchStart(const TouchEvent& event, const GesturePoint* point1 = GetPointByPointId(0); const GesturePoint* point2 = GetPointByPointId(1); + if (state_ == GS_PENDING_TWO_FINGER_TAP || + state_ == GS_PENDING_PINCH) { + AppendScrollGestureBegin(point, bounding_box_.CenterPoint(), gestures); + } + pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_); pinch_distance_start_ = pinch_distance_current_; latest_multi_scroll_update_location_ = bounding_box_.CenterPoint(); AppendPinchGestureBegin(*point1, *point2, gestures); - if (state_ == GS_PENDING_TWO_FINGER_TAP || - state_ == GS_PENDING_PINCH) { - gfx::Point center = bounding_box_.CenterPoint(); - AppendScrollGestureBegin(point, center, gestures); - } - return true; } @@ -1322,13 +1337,13 @@ bool GestureSequence::PinchUpdate(const TouchEvent& event, float distance = BoundingBoxDiagonal(bounding_box_); - if (abs(distance - pinch_distance_current_) >= + if (std::abs(distance - pinch_distance_current_) >= GestureConfiguration::min_pinch_update_distance_in_pixels()) { AppendPinchGestureUpdate(point, distance / pinch_distance_current_, gestures); pinch_distance_current_ = distance; } - AppendScrollGestureUpdate(point, gestures); + AppendScrollGestureUpdate(point, gestures, FS_NOT_FIRST_SCROLL); return true; } @@ -1385,7 +1400,6 @@ bool GestureSequence::MaybeSwipe(const TouchEvent& event, } float min_velocity = GestureConfiguration::min_swipe_speed(); - min_velocity *= min_velocity; velocity_x = fabs(velocity_x / point_count_); velocity_y = fabs(velocity_y / point_count_); @@ -1437,11 +1451,17 @@ void GestureSequence::StopTimersIfRequired(const TouchEvent& event) { // Since a timer is running, there should be a non-NULL point. const GesturePoint* point = GetPointByPointId(0); - if (!ui::gestures::IsInsideManhattanSquare(point->first_touch_position(), - event.location())) { + if (!point->IsInsideTouchSlopRegion(event)) { GetLongPressTimer()->Stop(); GetShowPressTimer()->Stop(); } } +void GestureSequence::StartRailFreeScroll(const GesturePoint& point, + Gestures* gestures) { + AppendScrollGestureBegin(point, point.first_touch_position(), gestures); + scroll_type_ = ST_FREE; + set_state(GS_SCROLL); +} + } // namespace ui diff --git a/chromium/ui/events/gestures/gesture_sequence.h b/chromium/ui/events/gestures/gesture_sequence.h index b3255df573c..62676e940ae 100644 --- a/chromium/ui/events/gestures/gesture_sequence.h +++ b/chromium/ui/events/gestures/gesture_sequence.h @@ -7,6 +7,7 @@ #include "base/timer/timer.h" #include "ui/events/event_constants.h" +#include "ui/events/gesture_event_details.h" #include "ui/events/gestures/gesture_point.h" #include "ui/events/gestures/gesture_recognizer.h" #include "ui/gfx/rect.h" @@ -19,7 +20,12 @@ class GestureEvent; enum GestureState { GS_NO_GESTURE, GS_PENDING_SYNTHETIC_CLICK, + // One finger is down: tap could occur, but scroll cannot until the number of + // active touch points changes. GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, + // One finger is down: no gestures can occur until the number of active touch + // points changes. + GS_SYNTHETIC_CLICK_ABORTED, GS_SCROLL, GS_PINCH, GS_PENDING_TWO_FINGER_TAP, @@ -34,6 +40,11 @@ enum ScrollType { ST_VERTICAL, }; +enum IsFirstScroll { + FS_FIRST_SCROLL, + FS_NOT_FIRST_SCROLL, +}; + // Delegates dispatch of gesture events for which the GestureSequence does not // have enough context to dispatch itself. class EVENTS_EXPORT GestureSequenceDelegate { @@ -64,7 +75,9 @@ class EVENTS_EXPORT GestureSequence { const GesturePoint* points() const { return points_; } int point_count() const { return point_count_; } - const gfx::Point& last_touch_location() const { return last_touch_location_; } + const gfx::PointF& last_touch_location() const { + return last_touch_location_; + } protected: virtual base::OneShotTimer<GestureSequence>* CreateTimer(); @@ -90,7 +103,7 @@ class EVENTS_EXPORT GestureSequence { // includes some common information (e.g. number of touch-points in the // gesture etc.) in the gesture event as well. GestureEvent* CreateGestureEvent(const GestureEventDetails& details, - const gfx::Point& location, + const gfx::PointF& location, int flags, base::Time timestamp, unsigned int touch_id_bitmask); @@ -115,15 +128,16 @@ class EVENTS_EXPORT GestureSequence { // Scroll gestures. void AppendScrollGestureBegin(const GesturePoint& point, - const gfx::Point& location, + const gfx::PointF& location, Gestures* gestures); void AppendScrollGestureEnd(const GesturePoint& point, - const gfx::Point& location, + const gfx::PointF& location, Gestures* gestures, float x_velocity, float y_velocity); void AppendScrollGestureUpdate(GesturePoint& point, - Gestures* gestures); + Gestures* gestures, + IsFirstScroll is_first_scroll); // Pinch gestures. void AppendPinchGestureBegin(const GesturePoint& p1, @@ -158,7 +172,8 @@ class EVENTS_EXPORT GestureSequence { Gestures* gestures); bool ScrollUpdate(const TouchEvent& event, GesturePoint& point, - Gestures* gestures); + Gestures* gestures, + IsFirstScroll is_first_scroll); bool TouchDown(const TouchEvent& event, const GesturePoint& point, Gestures* gestures); @@ -193,6 +208,8 @@ class EVENTS_EXPORT GestureSequence { void StopTimersIfRequired(const TouchEvent& event); + void StartRailFreeScroll(const GesturePoint& point, Gestures* gestures); + // Current state of gesture recognizer. GestureState state_; @@ -201,11 +218,11 @@ class EVENTS_EXPORT GestureSequence { // We maintain the smallest axis-aligned rectangle that contains all the // current touch-points. This box is updated after every touch-event. - gfx::Rect bounding_box_; + gfx::RectF bounding_box_; // The center of the bounding box used in the latest multi-finger scroll // update gesture. - gfx::Point latest_multi_scroll_update_location_; + gfx::PointF latest_multi_scroll_update_location_; // The last scroll update prediction offset. This is removed from the scroll // distance on the next update since the page has already been scrolled this @@ -233,7 +250,7 @@ class EVENTS_EXPORT GestureSequence { int point_count_; // Location of the last touch event. - gfx::Point last_touch_location_; + gfx::PointF last_touch_location_; GestureSequenceDelegate* delegate_; diff --git a/chromium/ui/events/gestures/gesture_types.cc b/chromium/ui/events/gestures/gesture_types.cc deleted file mode 100644 index da91bd07db0..00000000000 --- a/chromium/ui/events/gestures/gesture_types.cc +++ /dev/null @@ -1,105 +0,0 @@ -// 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/gestures/gesture_types.h" - -namespace ui { - -GestureEventDetails::GestureEventDetails(ui::EventType type, - float delta_x, - float delta_y) - : type_(type), - touch_points_(1) { - switch (type_) { - case ui::ET_GESTURE_SCROLL_UPDATE: - data.scroll_update.x = delta_x; - data.scroll_update.y = delta_y; - data.scroll_update.x_ordinal = delta_x; - data.scroll_update.y_ordinal = delta_y; - break; - - case ui::ET_SCROLL_FLING_START: - data.fling_velocity.x = delta_x; - data.fling_velocity.y = delta_y; - data.fling_velocity.x_ordinal = delta_x; - data.fling_velocity.y_ordinal = delta_y; - break; - - case ui::ET_GESTURE_LONG_PRESS: - data.touch_id = static_cast<int>(delta_x); - CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for long press."; - break; - - case ui::ET_GESTURE_TWO_FINGER_TAP: - data.first_finger_enclosing_rectangle.width = delta_x; - data.first_finger_enclosing_rectangle.height = delta_y; - break; - - case ui::ET_GESTURE_PINCH_UPDATE: - data.scale = delta_x; - CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for pinch"; - break; - - case ui::ET_GESTURE_MULTIFINGER_SWIPE: - data.swipe.left = delta_x < 0; - data.swipe.right = delta_x > 0; - data.swipe.up = delta_y < 0; - data.swipe.down = delta_y > 0; - break; - - case ui::ET_GESTURE_TAP: - data.tap_count = static_cast<int>(delta_x); - CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for tap."; - break; - - default: - if (delta_x != 0.f || delta_y != 0.f) { - DLOG(WARNING) << "A gesture event (" << type << ") had unknown data: (" - << delta_x << "," << delta_y; - } - break; - } -} - -GestureEventDetails::GestureEventDetails(ui::EventType type, - float delta_x, - float delta_y, - float delta_x_ordinal, - float delta_y_ordinal) - : type_(type), - touch_points_(1) { - CHECK(type == ui::ET_GESTURE_SCROLL_UPDATE || - type == ui::ET_SCROLL_FLING_START); - switch (type_) { - case ui::ET_GESTURE_SCROLL_UPDATE: - data.scroll_update.x = delta_x; - data.scroll_update.y = delta_y; - data.scroll_update.x_ordinal = delta_x_ordinal; - data.scroll_update.y_ordinal = delta_y_ordinal; - break; - - case ui::ET_SCROLL_FLING_START: - data.fling_velocity.x = delta_x; - data.fling_velocity.y = delta_y; - data.fling_velocity.x_ordinal = delta_x_ordinal; - data.fling_velocity.y_ordinal = delta_y_ordinal; - break; - - default: - break; - } -} - -void GestureEventDetails::SetScrollVelocity(float velocity_x, - float velocity_y, - float velocity_x_ordinal, - float velocity_y_ordinal) { - CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_); - data.scroll_update.velocity_x = velocity_x; - data.scroll_update.velocity_y = velocity_y; - data.scroll_update.velocity_x_ordinal = velocity_x_ordinal; - data.scroll_update.velocity_y_ordinal = velocity_y_ordinal; -} - -} // namespace ui diff --git a/chromium/ui/events/gestures/gesture_types.h b/chromium/ui/events/gestures/gesture_types.h index b0670fe8f6d..c15a2fdca47 100644 --- a/chromium/ui/events/gestures/gesture_types.h +++ b/chromium/ui/events/gestures/gesture_types.h @@ -5,178 +5,13 @@ #ifndef UI_EVENTS_GESTURES_GESTURE_TYPES_H_ #define UI_EVENTS_GESTURES_GESTURE_TYPES_H_ -#include "base/logging.h" -#include "ui/events/event_constants.h" #include "ui/events/events_export.h" -#include "ui/gfx/rect.h" namespace ui { class GestureEvent; class TouchEvent; -struct EVENTS_EXPORT GestureEventDetails { - public: - GestureEventDetails(EventType type, float delta_x, float delta_y); - GestureEventDetails(EventType type, - float delta_x, float delta_y, - float delta_x_ordinal, float delta_y_ordinal); - - EventType type() const { return type_; } - - int touch_points() const { return touch_points_; } - void set_touch_points(int touch_points) { touch_points_ = touch_points; } - - const gfx::Rect& bounding_box() const { return bounding_box_; } - void set_bounding_box(const gfx::Rect& box) { bounding_box_ = box; } - - void SetScrollVelocity(float velocity_x, float velocity_y, - float velocity_x_ordinal, float velocity_y_ordinal); - - float scroll_x() const { - CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_); - return data.scroll_update.x; - } - - float scroll_y() const { - CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_); - return data.scroll_update.y; - } - - float velocity_x() const { - CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE || - type_ == ui::ET_SCROLL_FLING_START); - return type_ == ui::ET_SCROLL_FLING_START ? data.fling_velocity.x : - data.scroll_update.velocity_x; - } - - float velocity_y() const { - CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE || - type_ == ui::ET_SCROLL_FLING_START); - return type_ == ui::ET_SCROLL_FLING_START ? data.fling_velocity.y : - data.scroll_update.velocity_y; - } - - // *_ordinal values are unmodified by rail based clamping. - float scroll_x_ordinal() const { - CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_); - return data.scroll_update.x_ordinal; - } - - float scroll_y_ordinal() const { - CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_); - return data.scroll_update.y_ordinal; - } - - float velocity_x_ordinal() const { - CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE || - type_ == ui::ET_SCROLL_FLING_START); - return type_ == ui::ET_SCROLL_FLING_START ? - data.fling_velocity.x_ordinal : - data.scroll_update.velocity_x_ordinal; - } - - float velocity_y_ordinal() const { - CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE || - type_ == ui::ET_SCROLL_FLING_START); - return type_ == ui::ET_SCROLL_FLING_START ? - data.fling_velocity.y_ordinal : - data.scroll_update.velocity_y_ordinal; - } - - int touch_id() const { - CHECK_EQ(ui::ET_GESTURE_LONG_PRESS, type_); - return data.touch_id; - } - - float first_finger_width() const { - CHECK_EQ(ui::ET_GESTURE_TWO_FINGER_TAP, type_); - return data.first_finger_enclosing_rectangle.width; - } - - float first_finger_height() const { - CHECK_EQ(ui::ET_GESTURE_TWO_FINGER_TAP, type_); - return data.first_finger_enclosing_rectangle.height; - } - - float scale() const { - CHECK_EQ(ui::ET_GESTURE_PINCH_UPDATE, type_); - return data.scale; - } - - bool swipe_left() const { - CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_); - return data.swipe.left; - } - - bool swipe_right() const { - CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_); - return data.swipe.right; - } - - bool swipe_up() const { - CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_); - return data.swipe.up; - } - - bool swipe_down() const { - CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_); - return data.swipe.down; - } - - int tap_count() const { - CHECK_EQ(ui::ET_GESTURE_TAP, type_); - return data.tap_count; - } - - private: - ui::EventType type_; - union { - struct { // SCROLL delta. - float x; - float y; - float velocity_x; - float velocity_y; - float x_ordinal; - float y_ordinal; - float velocity_x_ordinal; - float velocity_y_ordinal; - } scroll_update; - - float scale; // PINCH scale. - - struct { // FLING velocity. - float x; - float y; - float x_ordinal; - float y_ordinal; - } fling_velocity; - - int touch_id; // LONG_PRESS touch-id. - - // Dimensions of the first finger's enclosing rectangle for TWO_FINGER_TAP. - struct { - float width; - float height; - } first_finger_enclosing_rectangle; - - struct { // SWIPE direction. - bool left; - bool right; - bool up; - bool down; - } swipe; - - int tap_count; // TAP repeat count. - } data; - - int touch_points_; // Number of active touch points in the gesture. - - // Bounding box is an axis-aligned rectangle that contains all the - // enclosing rectangles of the touch-points in the gesture. - gfx::Rect bounding_box_; -}; - // An abstract type for consumers of gesture-events created by the // gesture-recognizer. class EVENTS_EXPORT GestureConsumer { @@ -193,7 +28,7 @@ class EVENTS_EXPORT GestureEventHelper { // Returns true if this helper can dispatch events to |consumer|. virtual bool CanDispatchToConsumer(GestureConsumer* consumer) = 0; - virtual void DispatchPostponedGestureEvent(GestureEvent* event) = 0; + virtual void DispatchGestureEvent(GestureEvent* event) = 0; virtual void DispatchCancelTouchEvent(TouchEvent* event) = 0; }; diff --git a/chromium/ui/events/gestures/gesture_util.cc b/chromium/ui/events/gestures/gesture_util.cc deleted file mode 100644 index 5a99039453c..00000000000 --- a/chromium/ui/events/gestures/gesture_util.cc +++ /dev/null @@ -1,23 +0,0 @@ -// 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/gestures/gesture_util.h" - -#include <stdlib.h> - -#include "ui/events/gestures/gesture_configuration.h" -#include "ui/gfx/point.h" - -namespace ui { -namespace gestures { - -bool IsInsideManhattanSquare(const gfx::Point& p1, - const gfx::Point& p2) { - int manhattan_distance = abs(p1.x() - p2.x()) + abs(p1.y() - p2.y()); - return manhattan_distance < - GestureConfiguration::max_touch_move_in_pixels_for_click(); -} - -} // namespace gestures -} // namespace ui diff --git a/chromium/ui/events/gestures/gesture_util.h b/chromium/ui/events/gestures/gesture_util.h deleted file mode 100644 index 36c1584e7b4..00000000000 --- a/chromium/ui/events/gestures/gesture_util.h +++ /dev/null @@ -1,26 +0,0 @@ -// 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. - -#ifndef UI_EVENTS_GESTURES_GESTURE_UTIL_H_ -#define UI_EVENTS_GESTURES_GESTURE_UTIL_H_ - -#include "ui/events/events_export.h" - -namespace gfx { -class Point; -} // namespace gfx - -namespace ui { -namespace gestures { - -// Returns true if the distance between points |p1| and |p2| is less than a -// threshold. This is generally used to determine if a touch point has moved -// enough to be no longer considered a tap. -EVENTS_EXPORT bool IsInsideManhattanSquare(const gfx::Point& p1, - const gfx::Point& p2); - -} // namespace gestures -} // namespace ui - -#endif // UI_EVENTS_GESTURES_GESTURE_UTIL_H_ diff --git a/chromium/ui/events/gestures/gestures.dot b/chromium/ui/events/gestures/gestures.dot index 07883f8d274..168d02f6fff 100644 --- a/chromium/ui/events/gestures/gestures.dot +++ b/chromium/ui/events/gestures/gestures.dot @@ -25,11 +25,16 @@ GS_PENDING_SYNTHETIC_CLICK -> GS_NO_GESTURE [label= "C0\n R0"]; GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL [label= "M0"]; GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_TWO_FINGER_TAP [label= "D1"]; GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_PINCH [label= "D1"]; +GS_PENDING_SYNTHETIC_CLICK -> GS_SYNTHETIC_CLICK_ABORTED [label= "M0"]; GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL [label= "M0\n S0"]; GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_NO_GESTURE [label= "C0\n R0"]; GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_TWO_FINGER_TAP [label= "D1"]; GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_PINCH [label= "D1"]; +GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_SYNTHETIC_CLICK_ABORTED [label= "M0"]; + +GS_SYNTHETIC_CLICK_ABORTED -> GS_NO_GESTURE [label= "C0\n R0"]; +GS_SYNTHETIC_CLICK_ABORTED -> GS_PENDING_PINCH [label= "D1"]; GS_SCROLL -> GS_SCROLL [label= "M0"]; GS_SCROLL -> GS_NO_GESTURE [label= "C0\n R0\n"]; diff --git a/chromium/ui/events/gestures/motion_event_aura.cc b/chromium/ui/events/gestures/motion_event_aura.cc new file mode 100644 index 00000000000..2bf4e9b3ce7 --- /dev/null +++ b/chromium/ui/events/gestures/motion_event_aura.cc @@ -0,0 +1,255 @@ +// Copyright 2014 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/gestures/motion_event_aura.h" + +#include "base/logging.h" +#include "ui/events/gestures/gesture_configuration.h" + +namespace ui { + +MotionEventAura::MotionEventAura() + : pointer_count_(0), cached_action_index_(-1) { +} + +MotionEventAura::MotionEventAura( + size_t pointer_count, + const base::TimeTicks& last_touch_time, + Action cached_action, + int cached_action_index, + const PointData (&active_touches)[GestureSequence::kMaxGesturePoints]) + : pointer_count_(pointer_count), + last_touch_time_(last_touch_time), + cached_action_(cached_action), + cached_action_index_(cached_action_index) { + DCHECK(pointer_count_); + for (size_t i = 0; i < pointer_count; ++i) + active_touches_[i] = active_touches[i]; +} + +MotionEventAura::~MotionEventAura() {} + +MotionEventAura::PointData MotionEventAura::GetPointDataFromTouchEvent( + const TouchEvent& touch) { + PointData point_data; + point_data.x = touch.x(); + point_data.y = touch.y(); + point_data.raw_x = touch.root_location_f().x(); + point_data.raw_y = touch.root_location_f().y(); + point_data.touch_id = touch.touch_id(); + point_data.pressure = touch.force(); + point_data.source_device_id = touch.source_device_id(); + + // TODO(tdresser): at some point we should start using both radii if they are + // available, but for now we use the max. + point_data.major_radius = std::max(touch.radius_x(), touch.radius_y()); + if (!point_data.major_radius) + point_data.major_radius = GestureConfiguration::default_radius(); + return point_data; +} + +void MotionEventAura::OnTouch(const TouchEvent& touch) { + switch (touch.type()) { + case ET_TOUCH_PRESSED: + AddTouch(touch); + break; + case ET_TOUCH_RELEASED: + case ET_TOUCH_CANCELLED: + // Removing these touch points needs to be postponed until after the + // MotionEvent has been dispatched. This cleanup occurs in + // CleanupRemovedTouchPoints. + UpdateTouch(touch); + break; + case ET_TOUCH_MOVED: + UpdateTouch(touch); + break; + default: + NOTREACHED(); + break; + } + + UpdateCachedAction(touch); + last_touch_time_ = touch.time_stamp() + base::TimeTicks(); +} + +int MotionEventAura::GetId() const { + return GetPointerId(0); +} + +MotionEvent::Action MotionEventAura::GetAction() const { + return cached_action_; +} + +int MotionEventAura::GetActionIndex() const { + DCHECK(cached_action_ == ACTION_POINTER_DOWN || + cached_action_ == ACTION_POINTER_UP); + DCHECK_GE(cached_action_index_, 0); + DCHECK_LE(cached_action_index_, static_cast<int>(pointer_count_)); + return cached_action_index_; +} + +size_t MotionEventAura::GetPointerCount() const { return pointer_count_; } + +int MotionEventAura::GetPointerId(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].touch_id; +} + +float MotionEventAura::GetX(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].x; +} + +float MotionEventAura::GetY(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].y; +} + +float MotionEventAura::GetRawX(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].raw_x; +} + +float MotionEventAura::GetRawY(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].raw_y; +} + +float MotionEventAura::GetTouchMajor(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].major_radius * 2; +} + +float MotionEventAura::GetPressure(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].pressure; +} + +base::TimeTicks MotionEventAura::GetEventTime() const { + return last_touch_time_; +} + +size_t MotionEventAura::GetHistorySize() const { return 0; } + +base::TimeTicks MotionEventAura::GetHistoricalEventTime( + size_t historical_index) const { + NOTIMPLEMENTED(); + return base::TimeTicks(); +} + +float MotionEventAura::GetHistoricalTouchMajor(size_t pointer_index, + size_t historical_index) const { + NOTIMPLEMENTED(); + return 0; +} + +float MotionEventAura::GetHistoricalX(size_t pointer_index, + size_t historical_index) const { + NOTIMPLEMENTED(); + return 0; +} + +float MotionEventAura::GetHistoricalY(size_t pointer_index, + size_t historical_index) const { + NOTIMPLEMENTED(); + return 0; +} + +scoped_ptr<MotionEvent> MotionEventAura::Clone() const { + return scoped_ptr<MotionEvent>(new MotionEventAura(pointer_count_, + last_touch_time_, + cached_action_, + cached_action_index_, + active_touches_)); +} +scoped_ptr<MotionEvent> MotionEventAura::Cancel() const { + return scoped_ptr<MotionEvent>(new MotionEventAura( + pointer_count_, last_touch_time_, ACTION_CANCEL, -1, active_touches_)); +} + +void MotionEventAura::CleanupRemovedTouchPoints(const TouchEvent& event) { + if (event.type() != ET_TOUCH_RELEASED && + event.type() != ET_TOUCH_CANCELLED) { + return; + } + + int index_to_delete = static_cast<int>(GetIndexFromId(event.touch_id())); + pointer_count_--; + active_touches_[index_to_delete] = active_touches_[pointer_count_]; +} + +MotionEventAura::PointData::PointData() + : x(0), + y(0), + raw_x(0), + raw_y(0), + touch_id(0), + pressure(0), + source_device_id(0), + major_radius(0) { +} + +int MotionEventAura::GetSourceDeviceId(size_t pointer_index) const { + DCHECK_LE(pointer_index, pointer_count_); + return active_touches_[pointer_index].source_device_id; +} + +void MotionEventAura::AddTouch(const TouchEvent& touch) { + if (pointer_count_ == static_cast<size_t>(GestureSequence::kMaxGesturePoints)) + return; + + active_touches_[pointer_count_] = GetPointDataFromTouchEvent(touch); + pointer_count_++; +} + + +void MotionEventAura::UpdateTouch(const TouchEvent& touch) { + active_touches_[GetIndexFromId(touch.touch_id())] = + GetPointDataFromTouchEvent(touch); +} + +void MotionEventAura::UpdateCachedAction(const TouchEvent& touch) { + DCHECK(pointer_count_); + switch (touch.type()) { + case ET_TOUCH_PRESSED: + if (pointer_count_ == 1) { + cached_action_ = ACTION_DOWN; + } else { + cached_action_ = ACTION_POINTER_DOWN; + cached_action_index_ = + static_cast<int>(GetIndexFromId(touch.touch_id())); + } + break; + case ET_TOUCH_RELEASED: + if (pointer_count_ == 1) { + cached_action_ = ACTION_UP; + } else { + cached_action_ = ACTION_POINTER_UP; + cached_action_index_ = + static_cast<int>(GetIndexFromId(touch.touch_id())); + DCHECK_LE(cached_action_index_, static_cast<int>(pointer_count_)); + } + break; + case ET_TOUCH_CANCELLED: + cached_action_ = ACTION_CANCEL; + break; + case ET_TOUCH_MOVED: + cached_action_ = ACTION_MOVE; + break; + default: + NOTREACHED(); + break; + } +} + +size_t MotionEventAura::GetIndexFromId(int id) const { + for (size_t i = 0; i < pointer_count_; ++i) { + if (active_touches_[i].touch_id == id) + return i; + } + NOTREACHED(); + return 0; +} + +} // namespace ui diff --git a/chromium/ui/events/gestures/motion_event_aura.h b/chromium/ui/events/gestures/motion_event_aura.h new file mode 100644 index 00000000000..7ebf264103f --- /dev/null +++ b/chromium/ui/events/gestures/motion_event_aura.h @@ -0,0 +1,106 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_ +#define UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_ + +#include "ui/events/gesture_detection/motion_event.h" + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "ui/events/event.h" +#include "ui/events/events_export.h" +#include "ui/events/gestures/gesture_sequence.h" + +namespace ui { + +// Implementation of MotionEvent which takes a stream of ui::TouchEvents. +class EVENTS_EXPORT MotionEventAura : public MotionEvent { + public: + MotionEventAura(); + virtual ~MotionEventAura(); + + void OnTouch(const TouchEvent& touch); + + // MotionEvent implementation. + virtual int GetId() const OVERRIDE; + virtual Action GetAction() const OVERRIDE; + virtual int GetActionIndex() const OVERRIDE; + virtual size_t GetPointerCount() const OVERRIDE; + virtual int GetPointerId(size_t pointer_index) const OVERRIDE; + virtual float GetX(size_t pointer_index) const OVERRIDE; + virtual float GetY(size_t pointer_index) const OVERRIDE; + virtual float GetRawX(size_t pointer_index) const OVERRIDE; + virtual float GetRawY(size_t pointer_index) const OVERRIDE; + virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE; + virtual float GetPressure(size_t pointer_index) const OVERRIDE; + virtual base::TimeTicks GetEventTime() const OVERRIDE; + + virtual size_t GetHistorySize() const OVERRIDE; + virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const + OVERRIDE; + virtual float GetHistoricalTouchMajor(size_t pointer_index, + size_t historical_index) const OVERRIDE; + virtual float GetHistoricalX(size_t pointer_index, + size_t historical_index) const OVERRIDE; + virtual float GetHistoricalY(size_t pointer_index, + size_t historical_index) const OVERRIDE; + + virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE; + virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE; + + int GetSourceDeviceId(size_t pointer_index) const; + + // We can't cleanup removed touch points immediately upon receipt of a + // TouchCancel or TouchRelease, as the MotionEvent needs to be able to report + // information about those touch events. Once the MotionEvent has been + // processed, we call CleanupRemovedTouchPoints to do the required + // book-keeping. + void CleanupRemovedTouchPoints(const TouchEvent& event); + + private: + struct PointData { + PointData(); + float x; + float y; + float raw_x; + float raw_y; + int touch_id; + float pressure; + int source_device_id; + float major_radius; + }; + + MotionEventAura( + size_t pointer_count, + const base::TimeTicks& last_touch_time, + Action cached_action, + int cached_action_index, + const PointData (&active_touches)[GestureSequence::kMaxGesturePoints]); + + static PointData GetPointDataFromTouchEvent(const TouchEvent& touch); + void AddTouch(const TouchEvent& touch); + void UpdateTouch(const TouchEvent& touch); + void UpdateCachedAction(const TouchEvent& touch); + size_t GetIndexFromId(int id) const; + + size_t pointer_count_; + base::TimeTicks last_touch_time_; + Action cached_action_; + // The index of the touch responsible for last ACTION_POINTER_DOWN or + // ACTION_POINTER_UP. -1 if no such action has occurred. + int cached_action_index_; + + // We want constant time indexing by pointer_index, and fast indexing by id. + // TODO(tdresser): figure out which constant to use here. + PointData active_touches_[GestureSequence::kMaxGesturePoints]; + + DISALLOW_COPY_AND_ASSIGN(MotionEventAura); +}; + +} // namespace ui + +#endif // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_ diff --git a/chromium/ui/events/gestures/motion_event_aura_unittest.cc b/chromium/ui/events/gestures/motion_event_aura_unittest.cc new file mode 100644 index 00000000000..c45348efeee --- /dev/null +++ b/chromium/ui/events/gestures/motion_event_aura_unittest.cc @@ -0,0 +1,323 @@ +// Copyright 2014 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 "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/events/gestures/motion_event_aura.h" + +namespace { + +ui::TouchEvent TouchWithType(ui::EventType type, int id) { + return ui::TouchEvent( + type, gfx::PointF(0, 0), id, base::TimeDelta::FromMilliseconds(0)); +} + +ui::TouchEvent TouchWithPosition(ui::EventType type, + int id, + float x, + float y, + float raw_x, + float raw_y, + float radius, + float pressure) { + ui::TouchEvent event(type, + gfx::PointF(x, y), + 0, + id, + base::TimeDelta::FromMilliseconds(0), + radius, + radius, + 0, + pressure); + event.set_root_location(gfx::PointF(raw_x, raw_y)); + return event; +} + +ui::TouchEvent TouchWithTime(ui::EventType type, int id, int ms) { + return ui::TouchEvent( + type, gfx::PointF(0, 0), id, base::TimeDelta::FromMilliseconds(ms)); +} + +base::TimeTicks MsToTicks(int ms) { + return base::TimeTicks() + base::TimeDelta::FromMilliseconds(ms); +} + +} // namespace + +namespace ui { + +TEST(MotionEventAuraTest, PointerCountAndIds) { + // Test that |PointerCount()| returns the correct number of pointers, and ids + // are assigned correctly. + int ids[] = {4, 6, 1}; + + MotionEventAura event; + EXPECT_EQ(0U, event.GetPointerCount()); + + TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]); + event.OnTouch(press0); + EXPECT_EQ(1U, event.GetPointerCount()); + + EXPECT_EQ(ids[0], event.GetPointerId(0)); + + TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]); + event.OnTouch(press1); + EXPECT_EQ(2U, event.GetPointerCount()); + + EXPECT_EQ(ids[0], event.GetPointerId(0)); + EXPECT_EQ(ids[1], event.GetPointerId(1)); + + TouchEvent press2 = TouchWithType(ET_TOUCH_PRESSED, ids[2]); + event.OnTouch(press2); + EXPECT_EQ(3U, event.GetPointerCount()); + + EXPECT_EQ(ids[0], event.GetPointerId(0)); + EXPECT_EQ(ids[1], event.GetPointerId(1)); + EXPECT_EQ(ids[2], event.GetPointerId(2)); + + TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]); + event.OnTouch(release1); + event.CleanupRemovedTouchPoints(release1); + EXPECT_EQ(2U, event.GetPointerCount()); + + EXPECT_EQ(ids[0], event.GetPointerId(0)); + EXPECT_EQ(ids[2], event.GetPointerId(1)); + + // Test cloning of pointer count and id information. + scoped_ptr<MotionEvent> clone = event.Clone(); + EXPECT_EQ(2U, clone->GetPointerCount()); + EXPECT_EQ(ids[0], clone->GetPointerId(0)); + EXPECT_EQ(ids[2], clone->GetPointerId(1)); + + TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[0]); + event.OnTouch(release0); + event.CleanupRemovedTouchPoints(release0); + EXPECT_EQ(1U, event.GetPointerCount()); + + EXPECT_EQ(ids[2], event.GetPointerId(0)); + + TouchEvent release2 = TouchWithType(ET_TOUCH_RELEASED, ids[2]); + event.OnTouch(release2); + event.CleanupRemovedTouchPoints(release2); + EXPECT_EQ(0U, event.GetPointerCount()); +} + +TEST(MotionEventAuraTest, GetActionIndexAfterRemoval) { + // Test that |GetActionIndex()| returns the correct index when points have + // been removed. + int ids[] = {4, 6, 9}; + + MotionEventAura event; + EXPECT_EQ(0U, event.GetPointerCount()); + + TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]); + event.OnTouch(press0); + TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]); + event.OnTouch(press1); + TouchEvent press2 = TouchWithType(ET_TOUCH_PRESSED, ids[2]); + event.OnTouch(press2); + EXPECT_EQ(3U, event.GetPointerCount()); + + TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]); + event.OnTouch(release1); + event.CleanupRemovedTouchPoints(release1); + EXPECT_EQ(1, event.GetActionIndex()); + EXPECT_EQ(2U, event.GetPointerCount()); + + TouchEvent release2 = TouchWithType(ET_TOUCH_RELEASED, ids[0]); + event.OnTouch(release2); + event.CleanupRemovedTouchPoints(release2); + EXPECT_EQ(0, event.GetActionIndex()); + EXPECT_EQ(1U, event.GetPointerCount()); + + TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[2]); + event.OnTouch(release0); + event.CleanupRemovedTouchPoints(release0); + EXPECT_EQ(0U, event.GetPointerCount()); +} + +TEST(MotionEventAuraTest, PointerLocations) { + // Test that location information is stored correctly. + MotionEventAura event; + + const float kRawOffsetX = 11.1f; + const float kRawOffsetY = 13.3f; + + int ids[] = {15, 13}; + float x; + float y; + float raw_x; + float raw_y; + float r; + float p; + + x = 14.4f; + y = 17.3f; + raw_x = x + kRawOffsetX; + raw_y = y + kRawOffsetY; + r = 25.7f; + p = 48.2f; + TouchEvent press0 = + TouchWithPosition(ET_TOUCH_PRESSED, ids[0], x, y, raw_x, raw_y, r, p); + event.OnTouch(press0); + + EXPECT_EQ(1U, event.GetPointerCount()); + EXPECT_FLOAT_EQ(x, event.GetX(0)); + EXPECT_FLOAT_EQ(y, event.GetY(0)); + EXPECT_FLOAT_EQ(raw_x, event.GetRawX(0)); + EXPECT_FLOAT_EQ(raw_y, event.GetRawY(0)); + EXPECT_FLOAT_EQ(r, event.GetTouchMajor(0) / 2); + EXPECT_FLOAT_EQ(p, event.GetPressure(0)); + + x = 17.8f; + y = 12.1f; + raw_x = x + kRawOffsetX; + raw_y = y + kRawOffsetY; + r = 21.2f; + p = 18.4f; + TouchEvent press1 = + TouchWithPosition(ET_TOUCH_PRESSED, ids[1], x, y, raw_x, raw_y, r, p); + event.OnTouch(press1); + + EXPECT_EQ(2U, event.GetPointerCount()); + EXPECT_FLOAT_EQ(x, event.GetX(1)); + EXPECT_FLOAT_EQ(y, event.GetY(1)); + EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1)); + EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1)); + EXPECT_FLOAT_EQ(r, event.GetTouchMajor(1) / 2); + EXPECT_FLOAT_EQ(p, event.GetPressure(1)); + + // Test cloning of pointer location information. + scoped_ptr<MotionEvent> clone = event.Clone(); + EXPECT_EQ(2U, clone->GetPointerCount()); + EXPECT_FLOAT_EQ(x, clone->GetX(1)); + EXPECT_FLOAT_EQ(y, clone->GetY(1)); + EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1)); + EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1)); + EXPECT_FLOAT_EQ(r, clone->GetTouchMajor(1) / 2); + EXPECT_FLOAT_EQ(p, clone->GetPressure(1)); + + x = 27.9f; + y = 22.3f; + raw_x = x + kRawOffsetX; + raw_y = y + kRawOffsetY; + r = 7.6f; + p = 82.1f; + TouchEvent move1 = + TouchWithPosition(ET_TOUCH_MOVED, ids[1], x, y, raw_x, raw_y, r, p); + event.OnTouch(move1); + + EXPECT_FLOAT_EQ(x, event.GetX(1)); + EXPECT_FLOAT_EQ(y, event.GetY(1)); + EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1)); + EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1)); + EXPECT_FLOAT_EQ(r, event.GetTouchMajor(1) / 2); + EXPECT_FLOAT_EQ(p, event.GetPressure(1)); + + x = 34.6f; + y = 23.8f; + raw_x = x + kRawOffsetX; + raw_y = y + kRawOffsetY; + r = 12.9f; + p = 14.2f; + TouchEvent move0 = + TouchWithPosition(ET_TOUCH_MOVED, ids[0], x, y, raw_x, raw_y, r, p); + event.OnTouch(move0); + + EXPECT_FLOAT_EQ(x, event.GetX(0)); + EXPECT_FLOAT_EQ(y, event.GetY(0)); + EXPECT_FLOAT_EQ(raw_x, event.GetRawX(0)); + EXPECT_FLOAT_EQ(raw_y, event.GetRawY(0)); + EXPECT_FLOAT_EQ(r, event.GetTouchMajor(0) / 2); + EXPECT_FLOAT_EQ(p, event.GetPressure(0)); +} + +TEST(MotionEventAuraTest, Timestamps) { + // Test that timestamp information is stored and converted correctly. + MotionEventAura event; + int ids[] = {7, 13}; + int times_in_ms[] = {59436, 60263, 82175}; + + TouchEvent press0 = TouchWithTime( + ui::ET_TOUCH_PRESSED, ids[0], times_in_ms[0]); + event.OnTouch(press0); + EXPECT_EQ(MsToTicks(times_in_ms[0]), event.GetEventTime()); + + TouchEvent press1 = TouchWithTime( + ui::ET_TOUCH_PRESSED, ids[1], times_in_ms[1]); + event.OnTouch(press1); + EXPECT_EQ(MsToTicks(times_in_ms[1]), event.GetEventTime()); + + TouchEvent move0 = TouchWithTime( + ui::ET_TOUCH_MOVED, ids[0], times_in_ms[2]); + event.OnTouch(move0); + EXPECT_EQ(MsToTicks(times_in_ms[2]), event.GetEventTime()); + + // Test cloning of timestamp information. + scoped_ptr<MotionEvent> clone = event.Clone(); + EXPECT_EQ(MsToTicks(times_in_ms[2]), clone->GetEventTime()); +} + +TEST(MotionEventAuraTest, CachedAction) { + // Test that the cached action and cached action index are correct. + int ids[] = {4, 6}; + MotionEventAura event; + + TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]); + event.OnTouch(press0); + EXPECT_EQ(MotionEvent::ACTION_DOWN, event.GetAction()); + EXPECT_EQ(1U, event.GetPointerCount()); + + TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]); + event.OnTouch(press1); + EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction()); + EXPECT_EQ(1, event.GetActionIndex()); + EXPECT_EQ(2U, event.GetPointerCount()); + + // Test cloning of CachedAction information. + scoped_ptr<MotionEvent> clone = event.Clone(); + EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, clone->GetAction()); + EXPECT_EQ(1, clone->GetActionIndex()); + + TouchEvent move0 = TouchWithType(ET_TOUCH_MOVED, ids[0]); + event.OnTouch(move0); + EXPECT_EQ(MotionEvent::ACTION_MOVE, event.GetAction()); + EXPECT_EQ(2U, event.GetPointerCount()); + + TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[0]); + event.OnTouch(release0); + EXPECT_EQ(MotionEvent::ACTION_POINTER_UP, event.GetAction()); + EXPECT_EQ(2U, event.GetPointerCount()); + event.CleanupRemovedTouchPoints(release0); + EXPECT_EQ(1U, event.GetPointerCount()); + + TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]); + event.OnTouch(release1); + EXPECT_EQ(MotionEvent::ACTION_UP, event.GetAction()); + EXPECT_EQ(1U, event.GetPointerCount()); + event.CleanupRemovedTouchPoints(release1); + EXPECT_EQ(0U, event.GetPointerCount()); +} + +TEST(MotionEventAuraTest, Cancel) { + int ids[] = {4, 6}; + MotionEventAura event; + + TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]); + event.OnTouch(press0); + EXPECT_EQ(MotionEvent::ACTION_DOWN, event.GetAction()); + EXPECT_EQ(1U, event.GetPointerCount()); + + TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]); + event.OnTouch(press1); + EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction()); + EXPECT_EQ(1, event.GetActionIndex()); + EXPECT_EQ(2U, event.GetPointerCount()); + + scoped_ptr<MotionEvent> cancel = event.Cancel(); + EXPECT_EQ(MotionEvent::ACTION_CANCEL, cancel->GetAction()); + EXPECT_EQ(2U, static_cast<MotionEventAura*>(cancel.get())->GetPointerCount()); +} + +} // namespace ui diff --git a/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc b/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc new file mode 100644 index 00000000000..cf221ff9d03 --- /dev/null +++ b/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc @@ -0,0 +1,37 @@ +// Copyright 2014 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 "base/command_line.h" +#include "base/logging.h" +#include "ui/events/event_switches.h" +#include "ui/events/gestures/unified_gesture_detector_enabled.h" + +namespace ui { + +bool IsUnifiedGestureDetectorEnabled() { + const bool kUseUnifiedGestureDetectorByDefault = true; + + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + const std::string unified_gd_enabled_switch = + command_line.HasSwitch(switches::kUnifiedGestureDetector) ? + command_line.GetSwitchValueASCII(switches::kUnifiedGestureDetector) : + switches::kUnifiedGestureDetectorAuto; + + if (unified_gd_enabled_switch.empty() || + unified_gd_enabled_switch == switches::kUnifiedGestureDetectorEnabled) { + return true; + } + + if (unified_gd_enabled_switch == switches::kUnifiedGestureDetectorDisabled) + return false; + + if (unified_gd_enabled_switch == switches::kUnifiedGestureDetectorAuto) + return kUseUnifiedGestureDetectorByDefault; + + LOG(ERROR) << "Invalid --unified-gesture-detector option: " + << unified_gd_enabled_switch; + return false; +} + +} // namespace ui diff --git a/chromium/ui/events/gestures/unified_gesture_detector_enabled.h b/chromium/ui/events/gestures/unified_gesture_detector_enabled.h new file mode 100644 index 00000000000..18fdc26d8ac --- /dev/null +++ b/chromium/ui/events/gestures/unified_gesture_detector_enabled.h @@ -0,0 +1,17 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_ +#define UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_ + +#include "ui/events/events_export.h" + +namespace ui { + +// Returns true iff the unified gesture detector is enabled for Aura. +EVENTS_EXPORT bool IsUnifiedGestureDetectorEnabled(); + +} // namespace ui + +#endif // UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_ diff --git a/chromium/ui/events/gestures/velocity_calculator.cc b/chromium/ui/events/gestures/velocity_calculator.cc index 5380d5126fa..37ec671b17f 100644 --- a/chromium/ui/events/gestures/velocity_calculator.cc +++ b/chromium/ui/events/gestures/velocity_calculator.cc @@ -30,7 +30,7 @@ float VelocityCalculator::YVelocity() { return y_velocity_; } -void VelocityCalculator::PointSeen(int x, int y, int64 time) { +void VelocityCalculator::PointSeen(float x, float y, int64 time) { buffer_[index_].x = x; buffer_[index_].y = y; buffer_[index_].time = time; diff --git a/chromium/ui/events/gestures/velocity_calculator.h b/chromium/ui/events/gestures/velocity_calculator.h index 1690438ae32..3678514f271 100644 --- a/chromium/ui/events/gestures/velocity_calculator.h +++ b/chromium/ui/events/gestures/velocity_calculator.h @@ -17,7 +17,7 @@ class EVENTS_EXPORT VelocityCalculator { public: explicit VelocityCalculator(int bufferSize); ~VelocityCalculator(); - void PointSeen(int x, int y, int64 time); + void PointSeen(float x, float y, int64 time); float XVelocity(); float YVelocity(); float VelocitySquared(); @@ -25,8 +25,8 @@ class EVENTS_EXPORT VelocityCalculator { private: struct Point { - int x; - int y; + float x; + float y; int64 time; }; diff --git a/chromium/ui/events/gestures/velocity_calculator_unittest.cc b/chromium/ui/events/gestures/velocity_calculator_unittest.cc index 3352acd6722..4e892170d04 100644 --- a/chromium/ui/events/gestures/velocity_calculator_unittest.cc +++ b/chromium/ui/events/gestures/velocity_calculator_unittest.cc @@ -74,7 +74,7 @@ TEST(VelocityCalculatorTest, IsAccurateWithLargeTimes) { EXPECT_GT(velocity_calculator.YVelocity(), -1270000); EXPECT_LT(velocity_calculator.YVelocity(), -1240000); - start_time = GG_LONGLONG(1223372036800000000); + start_time = 1223372036800000000LL; velocity_calculator.PointSeen(9, -11, start_time); velocity_calculator.PointSeen(21, -19, start_time + 8); velocity_calculator.PointSeen(30, -32, start_time + 16); diff --git a/chromium/ui/events/ipc/BUILD.gn b/chromium/ui/events/ipc/BUILD.gn new file mode 100644 index 00000000000..82ac834cf3d --- /dev/null +++ b/chromium/ui/events/ipc/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2014 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. + +import("//build/config/ui.gni") + +component("events_ipc") { + sources = [ + "latency_info_param_traits.cc", + "latency_info_param_traits.h", + ] + + defines = [ "EVENTS_IMPLEMENTATION" ] + + deps = [ "//ipc" ] +} + diff --git a/chromium/ui/events/ipc/OWNERS b/chromium/ui/events/ipc/OWNERS new file mode 100644 index 00000000000..2d12f8e4d21 --- /dev/null +++ b/chromium/ui/events/ipc/OWNERS @@ -0,0 +1,13 @@ +set noparent +cevans@chromium.org +dcheng@chromium.org +inferno@chromium.org +jln@chromium.org +jschuh@chromium.org +kenrb@chromium.org +nasko@chromium.org +palmer@chromium.org +tsepez@chromium.org + +per-file *.gyp*=* +per-file BUILD.gn=* diff --git a/chromium/ui/events/ipc/events_ipc.gyp b/chromium/ui/events/ipc/events_ipc.gyp new file mode 100644 index 00000000000..dd6433d33a6 --- /dev/null +++ b/chromium/ui/events/ipc/events_ipc.gyp @@ -0,0 +1,29 @@ +# Copyright 2014 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'events_ipc', + 'type': '<(component)', + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base', + '<(DEPTH)/ipc/ipc.gyp:ipc', + ], + 'defines': [ + 'EVENTS_IMPLEMENTATION', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'latency_info_param_traits.cc', + 'latency_info_param_traits.h', + ], + }, + ], +} diff --git a/chromium/ui/events/ipc/latency_info_param_traits.cc b/chromium/ui/events/ipc/latency_info_param_traits.cc new file mode 100644 index 00000000000..0b36c3121aa --- /dev/null +++ b/chromium/ui/events/ipc/latency_info_param_traits.cc @@ -0,0 +1,26 @@ +// Copyright 2014 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/ipc/latency_info_param_traits.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ +#include "ui/events/ipc/latency_info_param_traits.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ +#include "ui/events/ipc/latency_info_param_traits.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ +#include "ui/events/ipc/latency_info_param_traits.h" +} // namespace IPC diff --git a/chromium/ui/events/ipc/latency_info_param_traits.h b/chromium/ui/events/ipc/latency_info_param_traits.h new file mode 100644 index 00000000000..0206d7d52e6 --- /dev/null +++ b/chromium/ui/events/ipc/latency_info_param_traits.h @@ -0,0 +1,30 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ +#define UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ + +#include "ipc/ipc_message_macros.h" +#include "ui/events/events_export.h" +#include "ui/events/latency_info.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT EVENTS_EXPORT + +IPC_ENUM_TRAITS_MAX_VALUE(ui::LatencyComponentType, + ui::LATENCY_COMPONENT_TYPE_LAST) + +IPC_STRUCT_TRAITS_BEGIN(ui::LatencyInfo::LatencyComponent) + IPC_STRUCT_TRAITS_MEMBER(sequence_number) + IPC_STRUCT_TRAITS_MEMBER(event_time) + IPC_STRUCT_TRAITS_MEMBER(event_count) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(ui::LatencyInfo) + IPC_STRUCT_TRAITS_MEMBER(latency_components) + IPC_STRUCT_TRAITS_MEMBER(trace_id) + IPC_STRUCT_TRAITS_MEMBER(terminated) +IPC_STRUCT_TRAITS_END() + +#endif // UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_ diff --git a/chromium/ui/events/keycodes/DEPS b/chromium/ui/events/keycodes/DEPS new file mode 100644 index 00000000000..a2ede37fc05 --- /dev/null +++ b/chromium/ui/events/keycodes/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + # Remove DEPS allowed by ui/base + "-grit", + "-jni", + "-net", + "-skia", + "-third_party", + "-ui", + + "+ui/events", +] diff --git a/chromium/ui/events/keycodes/dom4/DEPS b/chromium/ui/events/keycodes/dom4/DEPS new file mode 100644 index 00000000000..b5489564447 --- /dev/null +++ b/chromium/ui/events/keycodes/dom4/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/events/keycodes/dom4", +] diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc b/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc deleted file mode 100644 index 9a396534e20..00000000000 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) 2011 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. - -/* - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. - * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com - * Copyright (C) 2007 Holger Hans Peter Freyther - * Copyright (C) 2008 Collabora, Ltd. All rights reserved. - * Copyright (C) 2008, 2009 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -// WindowsKeyCodeForGdkKeyCode is copied from platform/gtk/KeyEventGtk.cpp - -#include "ui/events/keycodes/keyboard_code_conversion_gtk.h" - -#include <gdk/gdk.h> -#include <gdk/gdkkeysyms.h> -#include <X11/keysym.h> - -#include "base/basictypes.h" -#include "build/build_config.h" -#include "ui/events/keycodes/keyboard_code_conversion_x.h" -#include "ui/events/keycodes/keyboard_codes_posix.h" - -namespace ui { - -KeyboardCode WindowsKeyCodeForGdkKeyCode(int keycode) { - // Gdk key codes (e.g. GDK_BackSpace) and X keysyms (e.g. XK_BackSpace) share - // the same values. - return KeyboardCodeFromXKeysym(keycode); -} - -int GdkKeyCodeForWindowsKeyCode(KeyboardCode keycode, bool shift) { - // Gdk key codes and X keysyms share the same values. - return XKeysymForWindowsKeyCode(keycode, shift); -} - -// Just in case, test whether Gdk key codes match X ones. -COMPILE_ASSERT(GDK_KP_0 == XK_KP_0, keycode_check); -COMPILE_ASSERT(GDK_A == XK_A, keycode_check); -COMPILE_ASSERT(GDK_Escape == XK_Escape, keycode_check); -COMPILE_ASSERT(GDK_F1 == XK_F1, keycode_check); -COMPILE_ASSERT(GDK_Kanji == XK_Kanji, keycode_check); -COMPILE_ASSERT(GDK_Page_Up == XK_Page_Up, keycode_check); -COMPILE_ASSERT(GDK_Tab == XK_Tab, keycode_check); -COMPILE_ASSERT(GDK_a == XK_a, keycode_check); -COMPILE_ASSERT(GDK_space == XK_space, keycode_check); - -int GdkNativeKeyCodeForWindowsKeyCode(KeyboardCode keycode, bool shift) { - int keyval = GdkKeyCodeForWindowsKeyCode(keycode, shift); - GdkKeymapKey* keys; - gint n_keys; - - int native_keycode = 0; - if (keyval && gdk_keymap_get_entries_for_keyval(0, keyval, &keys, &n_keys)) { - native_keycode = keys[0].keycode; - g_free(keys); - } - - return native_keycode; -} - -} // namespace ui diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h b/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h deleted file mode 100644 index 6258f4116c1..00000000000 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2011 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. - -/* - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. - * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com - * Copyright (C) 2007 Holger Hans Peter Freyther - * Copyright (C) 2008 Collabora, Ltd. All rights reserved. - * Copyright (C) 2008, 2009 Google Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -// WindowsKeyCodeForGdkKeyCode is copied from platform/gtk/KeyEventGtk.cpp - -#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_ -#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_ - -#include "ui/events/events_base_export.h" -#include "ui/events/keycodes/keyboard_codes_posix.h" - -typedef struct _GdkEventKey GdkEventKey; - -namespace ui { - -EVENTS_BASE_EXPORT KeyboardCode WindowsKeyCodeForGdkKeyCode(int keycode); - -EVENTS_BASE_EXPORT int GdkKeyCodeForWindowsKeyCode(KeyboardCode keycode, - bool shift); - -// For WebKit DRT testing: simulate the native keycode for the given -// input |keycode|. Return the native keycode. -EVENTS_BASE_EXPORT int GdkNativeKeyCodeForWindowsKeyCode(KeyboardCode keycode, - bool shift); - -} // namespace ui - -#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_ diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h index 719c922d139..fee39815d42 100644 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h +++ b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h @@ -16,8 +16,8 @@ namespace ui { // We use windows virtual keycodes throughout our keyboard event related code, // including unit tests. But Mac uses a different set of virtual keycodes. // This function converts a windows virtual keycode into Mac's virtual key code -// and corresponding unicode character. |flags| is the modifiers mask such -// as NSControlKeyMask, NSShiftKeyMask, etc. +// and corresponding unicode character. |flags| is the Cocoa modifiers mask +// such as NSControlKeyMask, NSShiftKeyMask, etc. // When success, the corresponding Mac's virtual key code will be returned. // The corresponding unicode character will be stored in |character|, and the // corresponding unicode character ignoring the modifiers will be stored in diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm index fdda790be1f..e354e58883c 100644 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm +++ b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm @@ -521,18 +521,6 @@ int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode, } } - // Control characters. - if (flags & NSControlKeyMask) { - if (keycode >= VKEY_A && keycode <= VKEY_Z) - *character = 1 + keycode - VKEY_A; - else if (macKeycode == kVK_ANSI_LeftBracket) - *character = 27; - else if (macKeycode == kVK_ANSI_Backslash) - *character = 28; - else if (macKeycode == kVK_ANSI_RightBracket) - *character = 29; - } - // TODO(suzhe): Support characters for Option key bindings. return macKeycode; } diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc b/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc index 821f8c47013..4f277ecff27 100644 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc +++ b/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc @@ -4,6 +4,8 @@ #include "ui/events/keycodes/keyboard_code_conversion_x.h" +#include <algorithm> + #define XK_3270 // for XK_3270_BackTab #include <X11/keysym.h> #include <X11/Xlib.h> @@ -13,23 +15,529 @@ #include "base/basictypes.h" #include "base/logging.h" #include "base/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "ui/events/keycodes/dom4/keycode_converter.h" +#define VKEY_UNSUPPORTED VKEY_UNKNOWN + namespace ui { +namespace { + +// MAP0 - MAP3: +// These are the generated VKEY code maps for all possible Latin keyboard +// layouts in Windows. And the maps are only for special letter keys excluding +// [a-z] & [0-9]. +// +// ch0: the keysym without modifier states. +// ch1: the keysym with shift state. +// ch2: the keysym with altgr state. +// sc: the hardware keycode (in Windows, it's called scan code). +// vk: the VKEY code. +// +// MAP0: maps from ch0 to vk. +// MAP1: maps from ch0+sc to vk. +// MAP2: maps from ch0+ch1+sc to vk. +// MAP3: maps from ch0+ch1+ch2+sc to vk. +// MAP0 - MAP3 are all sorted, so that finding VK can be binary search. +// +// Reason for creating these maps is because a hard-coded mapping in +// KeyboardCodeFromXKeysym() doesn't support non-US keyboard layouts. +// e.g. in UK keyboard, the key between Quote and Enter keys has the VKEY code +// VKEY_OEM_5 instead of VKEY_3. +// +// The key symbols which are not [a-zA-Z0-9] and functional/extend keys (e.g. +// TAB, ENTER, BS, Arrow keys, modifier keys, F1-F12, media/app keys, etc.) +// should go through these maps for correct VKEY codes. +// +// Please refer to crbug.com/386066. +// +const struct MAP0 { + KeySym ch0; + uint8 vk; + bool operator()(const MAP0& m1, const MAP0& m2) const { + return m1.ch0 < m2.ch0; + } +} map0[] = { + {0x0025, 0x35}, // XK_percent: VKEY_5 + {0x0026, 0x31}, // XK_ampersand: VKEY_1 + {0x003C, 0xDC}, // XK_less: VKEY_OEM_5 + {0x007B, 0xDE}, // XK_braceleft: VKEY_OEM_7 + {0x007C, 0xDC}, // XK_bar: VKEY_OEM_5 + {0x007D, 0xBF}, // XK_braceright: VKEY_OEM_2 + {0x007E, 0xDC}, // XK_asciitilde: VKEY_OEM_5 + {0x00A1, 0xDD}, // XK_exclamdown: VKEY_OEM_6 + {0x00AD, 0xC0}, // XK_hyphen: VKEY_OEM_3 + {0x00B2, 0xDE}, // XK_twosuperior: VKEY_OEM_7 + {0x00B5, 0xDC}, // XK_mu: VKEY_OEM_5 + {0x00BB, 0x39}, // XK_guillemotright: VKEY_9 + {0x00BD, 0xDC}, // XK_onehalf: VKEY_OEM_5 + {0x00BF, 0xDD}, // XK_questiondown: VKEY_OEM_6 + {0x00DF, 0xDB}, // XK_ssharp: VKEY_OEM_4 + {0x00E5, 0xDD}, // XK_aring: VKEY_OEM_6 + {0x00EA, 0x33}, // XK_ecircumflex: VKEY_3 + {0x00EB, 0xBA}, // XK_ediaeresis: VKEY_OEM_1 + {0x00EC, 0xDD}, // XK_igrave: VKEY_OEM_6 + {0x00EE, 0xDD}, // XK_icircumflex: VKEY_OEM_6 + {0x00F1, 0xC0}, // XK_ntilde: VKEY_OEM_3 + {0x00F2, 0xC0}, // XK_ograve: VKEY_OEM_3 + {0x00F5, 0xDB}, // XK_otilde: VKEY_OEM_4 + {0x00F7, 0xDD}, // XK_division: VKEY_OEM_6 + {0x00FD, 0x37}, // XK_yacute: VKEY_7 + {0x00FE, 0xBD}, // XK_thorn: VKEY_OEM_MINUS + {0x01A1, 0xDD}, // XK_ohorn: VKEY_OEM_6 + {0x01B0, 0xDB}, // XK_uhorn: VKEY_OEM_4 + {0x01B5, 0x32}, // XK_lcaron: VKEY_2 + {0x01B6, 0xDD}, // XK_zstroke: VKEY_OEM_6 + {0x01BB, 0x35}, // XK_tcaron: VKEY_5 + {0x01E6, 0xDE}, // XK_cacute: VKEY_OEM_7 + {0x01EC, 0x32}, // XK_ecaron: VKEY_2 + {0x01F2, 0xDC}, // XK_ncaron: VKEY_OEM_5 + {0x01F5, 0xDB}, // XK_odoubleacute: VKEY_OEM_4 + {0x01F8, 0x35}, // XK_rcaron: VKEY_5 + {0x01F9, 0xBA}, // XK_uring: VKEY_OEM_1 + {0x01FB, 0xDC}, // XK_udoubleacute: VKEY_OEM_5 + {0x01FE, 0xDE}, // XK_tcedilla: VKEY_OEM_7 + {0x0259, 0xC0}, // XK_schwa: VKEY_OEM_3 + {0x02B1, 0xDD}, // XK_hstroke: VKEY_OEM_6 + {0x02B9, 0xBA}, // XK_idotless: VKEY_OEM_1 + {0x02BB, 0xDD}, // XK_gbreve: VKEY_OEM_6 + {0x02E5, 0xC0}, // XK_cabovedot: VKEY_OEM_3 + {0x02F5, 0xDB}, // XK_gabovedot: VKEY_OEM_4 + {0x03B6, 0xBF}, // XK_lcedilla: VKEY_OEM_2 + {0x03BA, 0x57}, // XK_emacron: VKEY_W + {0x03E0, 0xDF}, // XK_amacron: VKEY_OEM_8 + {0x03EF, 0xDD}, // XK_imacron: VKEY_OEM_6 + {0x03F1, 0xDB}, // XK_ncedilla: VKEY_OEM_4 + {0x03F3, 0xDC}, // XK_kcedilla: VKEY_OEM_5 +}; + +const struct MAP1 { + KeySym ch0; + unsigned sc; + uint8 vk; + bool operator()(const MAP1& m1, const MAP1& m2) const { + if (m1.ch0 == m2.ch0) + return m1.sc < m2.sc; + return m1.ch0 < m2.ch0; + } +} map1[] = { + {0x0021, 0x0A, 0x31}, // XK_exclam+AE01: VKEY_1 + {0x0021, 0x11, 0x38}, // XK_exclam+AE08: VKEY_8 + {0x0021, 0x3D, 0xDF}, // XK_exclam+AB10: VKEY_OEM_8 + {0x0022, 0x0B, 0x32}, // XK_quotedbl+AE02: VKEY_2 + {0x0022, 0x0C, 0x33}, // XK_quotedbl+AE03: VKEY_3 + {0x0023, 0x31, 0xDE}, // XK_numbersign+TLDE: VKEY_OEM_7 + {0x0024, 0x23, 0xBA}, // XK_dollar+AD12: VKEY_OEM_1 + {0x0024, 0x33, 0xDF}, // XK_dollar+BKSL: VKEY_OEM_8 + {0x0027, 0x0D, 0x34}, // XK_quoteright+AE04: VKEY_4 + {0x0027, 0x18, 0xDE}, // XK_quoteright+AD01: VKEY_OEM_7 + {0x0027, 0x23, 0xBA}, // XK_quoteright+AD12: VKEY_OEM_1 + {0x0027, 0x3D, 0xDE}, // XK_quoteright+AB10: VKEY_OEM_7 + {0x0028, 0x0E, 0x35}, // XK_parenleft+AE05: VKEY_5 + {0x0028, 0x12, 0x39}, // XK_parenleft+AE09: VKEY_9 + {0x0028, 0x33, 0xDC}, // XK_parenleft+BKSL: VKEY_OEM_5 + {0x0029, 0x13, 0x30}, // XK_parenright+AE10: VKEY_0 + {0x0029, 0x14, 0xDB}, // XK_parenright+AE11: VKEY_OEM_4 + {0x0029, 0x23, 0xDD}, // XK_parenright+AD12: VKEY_OEM_6 + {0x002A, 0x23, 0xBA}, // XK_asterisk+AD12: VKEY_OEM_1 + {0x002A, 0x33, 0xDC}, // XK_asterisk+BKSL: VKEY_OEM_5 + {0x002B, 0x0A, 0x31}, // XK_plus+AE01: VKEY_1 + {0x002B, 0x15, 0xBB}, // XK_plus+AE12: VKEY_OEM_PLUS + {0x002B, 0x22, 0xBB}, // XK_plus+AD11: VKEY_OEM_PLUS + {0x002B, 0x23, 0xBB}, // XK_plus+AD12: VKEY_OEM_PLUS + {0x002B, 0x2F, 0xBB}, // XK_plus+AC10: VKEY_OEM_PLUS + {0x002B, 0x33, 0xBF}, // XK_plus+BKSL: VKEY_OEM_2 + {0x002C, 0x0C, 0x33}, // XK_comma+AE03: VKEY_3 + {0x002C, 0x0E, 0x35}, // XK_comma+AE05: VKEY_5 + {0x002C, 0x0F, 0x36}, // XK_comma+AE06: VKEY_6 + {0x002C, 0x12, 0x39}, // XK_comma+AE09: VKEY_9 + {0x002C, 0x19, 0xBC}, // XK_comma+AD02: VKEY_OEM_COMMA + {0x002C, 0x37, 0xBC}, // XK_comma+AB04: VKEY_OEM_COMMA + {0x002C, 0x3A, 0xBC}, // XK_comma+AB07: VKEY_OEM_COMMA + {0x002C, 0x3B, 0xBC}, // XK_comma+AB08: VKEY_OEM_COMMA + {0x002D, 0x0B, 0x32}, // XK_minus+AE02: VKEY_2 + {0x002D, 0x0F, 0x36}, // XK_minus+AE06: VKEY_6 + {0x002D, 0x14, 0xBD}, // XK_minus+AE11: VKEY_OEM_MINUS + {0x002D, 0x26, 0xBD}, // XK_minus+AC01: VKEY_OEM_MINUS + {0x002D, 0x30, 0xBD}, // XK_minus+AC11: VKEY_OEM_MINUS + {0x002E, 0x10, 0x37}, // XK_period+AE07: VKEY_7 + {0x002E, 0x11, 0x38}, // XK_period+AE08: VKEY_8 + {0x002E, 0x1A, 0xBE}, // XK_period+AD03: VKEY_OEM_PERIOD + {0x002E, 0x1B, 0xBE}, // XK_period+AD04: VKEY_OEM_PERIOD + {0x002E, 0x20, 0xBE}, // XK_period+AD09: VKEY_OEM_PERIOD + {0x002E, 0x30, 0xDE}, // XK_period+AC11: VKEY_OEM_7 + {0x002E, 0x3C, 0xBE}, // XK_period+AB09: VKEY_OEM_PERIOD + {0x002E, 0x3D, 0xBF}, // XK_period+AB10: VKEY_OEM_2 + {0x002F, 0x14, 0xDB}, // XK_slash+AE11: VKEY_OEM_4 + {0x002F, 0x22, 0xBF}, // XK_slash+AD11: VKEY_OEM_2 + {0x002F, 0x31, 0xDE}, // XK_slash+TLDE: VKEY_OEM_7 + {0x002F, 0x33, 0xDC}, // XK_slash+BKSL: VKEY_OEM_5 + {0x002F, 0x3D, 0xBF}, // XK_slash+AB10: VKEY_OEM_2 + {0x003A, 0x0A, 0x31}, // XK_colon+AE01: VKEY_1 + {0x003A, 0x0E, 0x35}, // XK_colon+AE05: VKEY_5 + {0x003A, 0x0F, 0x36}, // XK_colon+AE06: VKEY_6 + {0x003A, 0x3C, 0xBF}, // XK_colon+AB09: VKEY_OEM_2 + {0x003B, 0x0D, 0x34}, // XK_semicolon+AE04: VKEY_4 + {0x003B, 0x11, 0x38}, // XK_semicolon+AE08: VKEY_8 + {0x003B, 0x18, 0xBA}, // XK_semicolon+AD01: VKEY_OEM_1 + {0x003B, 0x22, 0xBA}, // XK_semicolon+AD11: VKEY_OEM_1 + {0x003B, 0x23, 0xDD}, // XK_semicolon+AD12: VKEY_OEM_6 + {0x003B, 0x2F, 0xBA}, // XK_semicolon+AC10: VKEY_OEM_1 + {0x003B, 0x31, 0xC0}, // XK_semicolon+TLDE: VKEY_OEM_3 + {0x003B, 0x34, 0xBA}, // XK_semicolon+AB01: VKEY_OEM_1 + {0x003B, 0x3B, 0xBE}, // XK_semicolon+AB08: VKEY_OEM_PERIOD + {0x003B, 0x3D, 0xBF}, // XK_semicolon+AB10: VKEY_OEM_2 + {0x003D, 0x11, 0x38}, // XK_equal+AE08: VKEY_8 + {0x003D, 0x15, 0xBB}, // XK_equal+AE12: VKEY_OEM_PLUS + {0x003D, 0x23, 0xBB}, // XK_equal+AD12: VKEY_OEM_PLUS + {0x003F, 0x0B, 0x32}, // XK_question+AE02: VKEY_2 + {0x003F, 0x10, 0x37}, // XK_question+AE07: VKEY_7 + {0x003F, 0x11, 0x38}, // XK_question+AE08: VKEY_8 + {0x003F, 0x14, 0xBB}, // XK_question+AE11: VKEY_OEM_PLUS + {0x0040, 0x23, 0xDD}, // XK_at+AD12: VKEY_OEM_6 + {0x0040, 0x31, 0xDE}, // XK_at+TLDE: VKEY_OEM_7 + {0x005B, 0x0A, 0xDB}, // XK_bracketleft+AE01: VKEY_OEM_4 + {0x005B, 0x14, 0xDB}, // XK_bracketleft+AE11: VKEY_OEM_4 + {0x005B, 0x22, 0xDB}, // XK_bracketleft+AD11: VKEY_OEM_4 + {0x005B, 0x23, 0xDD}, // XK_bracketleft+AD12: VKEY_OEM_6 + {0x005B, 0x30, 0xDE}, // XK_bracketleft+AC11: VKEY_OEM_7 + {0x005C, 0x15, 0xDB}, // XK_backslash+AE12: VKEY_OEM_4 + {0x005D, 0x0B, 0xDD}, // XK_bracketright+AE02: VKEY_OEM_6 + {0x005D, 0x15, 0xDD}, // XK_bracketright+AE12: VKEY_OEM_6 + {0x005D, 0x23, 0xDD}, // XK_bracketright+AD12: VKEY_OEM_6 + {0x005D, 0x31, 0xC0}, // XK_bracketright+TLDE: VKEY_OEM_3 + {0x005D, 0x33, 0xDC}, // XK_bracketright+BKSL: VKEY_OEM_5 + {0x005F, 0x11, 0x38}, // XK_underscore+AE08: VKEY_8 + {0x005F, 0x14, 0xBD}, // XK_underscore+AE11: VKEY_OEM_MINUS + {0x00A7, 0x0D, 0x34}, // XK_section+AE04: VKEY_4 + {0x00A7, 0x0F, 0x36}, // XK_section+AE06: VKEY_6 + {0x00A7, 0x30, 0xDE}, // XK_section+AC11: VKEY_OEM_7 + {0x00AB, 0x11, 0x38}, // XK_guillemotleft+AE08: VKEY_8 + {0x00AB, 0x15, 0xDD}, // XK_guillemotleft+AE12: VKEY_OEM_6 + {0x00B0, 0x15, 0xBF}, // XK_degree+AE12: VKEY_OEM_2 + {0x00B0, 0x31, 0xDE}, // XK_degree+TLDE: VKEY_OEM_7 + {0x00BA, 0x30, 0xDE}, // XK_masculine+AC11: VKEY_OEM_7 + {0x00BA, 0x31, 0xDC}, // XK_masculine+TLDE: VKEY_OEM_5 + {0x00E0, 0x13, 0x30}, // XK_agrave+AE10: VKEY_0 + {0x00E0, 0x33, 0xDC}, // XK_agrave+BKSL: VKEY_OEM_5 + {0x00E1, 0x11, 0x38}, // XK_aacute+AE08: VKEY_8 + {0x00E1, 0x30, 0xDE}, // XK_aacute+AC11: VKEY_OEM_7 + {0x00E2, 0x0B, 0x32}, // XK_acircumflex+AE02: VKEY_2 + {0x00E2, 0x33, 0xDC}, // XK_acircumflex+BKSL: VKEY_OEM_5 + {0x00E4, 0x23, 0xDD}, // XK_adiaeresis+AD12: VKEY_OEM_6 + {0x00E6, 0x2F, 0xC0}, // XK_ae+AC10: VKEY_OEM_3 + {0x00E6, 0x30, 0xDE}, // XK_ae+AC11: VKEY_OEM_7 + {0x00E7, 0x12, 0x39}, // XK_ccedilla+AE09: VKEY_9 + {0x00E7, 0x22, 0xDB}, // XK_ccedilla+AD11: VKEY_OEM_4 + {0x00E7, 0x23, 0xDD}, // XK_ccedilla+AD12: VKEY_OEM_6 + {0x00E7, 0x30, 0xDE}, // XK_ccedilla+AC11: VKEY_OEM_7 + {0x00E7, 0x33, 0xBF}, // XK_ccedilla+BKSL: VKEY_OEM_2 + {0x00E7, 0x3B, 0xBC}, // XK_ccedilla+AB08: VKEY_OEM_COMMA + {0x00E8, 0x10, 0x37}, // XK_egrave+AE07: VKEY_7 + {0x00E8, 0x22, 0xBA}, // XK_egrave+AD11: VKEY_OEM_1 + {0x00E8, 0x30, 0xC0}, // XK_egrave+AC11: VKEY_OEM_3 + {0x00E9, 0x0B, 0x32}, // XK_eacute+AE02: VKEY_2 + {0x00E9, 0x13, 0x30}, // XK_eacute+AE10: VKEY_0 + {0x00E9, 0x3D, 0xBF}, // XK_eacute+AB10: VKEY_OEM_2 + {0x00ED, 0x12, 0x39}, // XK_iacute+AE09: VKEY_9 + {0x00ED, 0x31, 0x30}, // XK_iacute+TLDE: VKEY_0 + {0x00F0, 0x22, 0xDD}, // XK_eth+AD11: VKEY_OEM_6 + {0x00F0, 0x23, 0xBA}, // XK_eth+AD12: VKEY_OEM_1 + {0x00F3, 0x15, 0xBB}, // XK_oacute+AE12: VKEY_OEM_PLUS + {0x00F3, 0x33, 0xDC}, // XK_oacute+BKSL: VKEY_OEM_5 + {0x00F4, 0x0D, 0x34}, // XK_ocircumflex+AE04: VKEY_4 + {0x00F4, 0x2F, 0xBA}, // XK_ocircumflex+AC10: VKEY_OEM_1 + {0x00F6, 0x13, 0xC0}, // XK_odiaeresis+AE10: VKEY_OEM_3 + {0x00F6, 0x14, 0xBB}, // XK_odiaeresis+AE11: VKEY_OEM_PLUS + {0x00F6, 0x22, 0xDB}, // XK_odiaeresis+AD11: VKEY_OEM_4 + {0x00F8, 0x2F, 0xC0}, // XK_oslash+AC10: VKEY_OEM_3 + {0x00F8, 0x30, 0xDE}, // XK_oslash+AC11: VKEY_OEM_7 + {0x00F9, 0x30, 0xC0}, // XK_ugrave+AC11: VKEY_OEM_3 + {0x00F9, 0x33, 0xBF}, // XK_ugrave+BKSL: VKEY_OEM_2 + {0x00FA, 0x22, 0xDB}, // XK_uacute+AD11: VKEY_OEM_4 + {0x00FA, 0x23, 0xDD}, // XK_uacute+AD12: VKEY_OEM_6 + {0x00FC, 0x19, 0x57}, // XK_udiaeresis+AD02: VKEY_W + {0x01B1, 0x0A, 0x31}, // XK_aogonek+AE01: VKEY_1 + {0x01B1, 0x18, 0x51}, // XK_aogonek+AD01: VKEY_Q + {0x01B1, 0x30, 0xDE}, // XK_aogonek+AC11: VKEY_OEM_7 + {0x01B3, 0x2F, 0xBA}, // XK_lstroke+AC10: VKEY_OEM_1 + {0x01B3, 0x33, 0xBF}, // XK_lstroke+BKSL: VKEY_OEM_2 + {0x01B9, 0x0C, 0x33}, // XK_scaron+AE03: VKEY_3 + {0x01B9, 0x0F, 0x36}, // XK_scaron+AE06: VKEY_6 + {0x01B9, 0x22, 0xDB}, // XK_scaron+AD11: VKEY_OEM_4 + {0x01B9, 0x26, 0xBA}, // XK_scaron+AC01: VKEY_OEM_1 + {0x01B9, 0x29, 0x46}, // XK_scaron+AC04: VKEY_F + {0x01B9, 0x3C, 0xBE}, // XK_scaron+AB09: VKEY_OEM_PERIOD + {0x01BA, 0x2F, 0xBA}, // XK_scedilla+AC10: VKEY_OEM_1 + {0x01BA, 0x3C, 0xBE}, // XK_scedilla+AB09: VKEY_OEM_PERIOD + {0x01BE, 0x0F, 0x36}, // XK_zcaron+AE06: VKEY_6 + {0x01BE, 0x15, 0xBB}, // XK_zcaron+AE12: VKEY_OEM_PLUS + {0x01BE, 0x19, 0x57}, // XK_zcaron+AD02: VKEY_W + {0x01BE, 0x22, 0x59}, // XK_zcaron+AD11: VKEY_Y + {0x01BE, 0x33, 0xDC}, // XK_zcaron+BKSL: VKEY_OEM_5 + {0x01BF, 0x22, 0xDB}, // XK_zabovedot+AD11: VKEY_OEM_4 + {0x01BF, 0x33, 0xDC}, // XK_zabovedot+BKSL: VKEY_OEM_5 + {0x01E3, 0x0A, 0x31}, // XK_abreve+AE01: VKEY_1 + {0x01E3, 0x22, 0xDB}, // XK_abreve+AD11: VKEY_OEM_4 + {0x01E8, 0x0B, 0x32}, // XK_ccaron+AE02: VKEY_2 + {0x01E8, 0x0D, 0x34}, // XK_ccaron+AE04: VKEY_4 + {0x01E8, 0x21, 0x58}, // XK_ccaron+AD10: VKEY_X + {0x01E8, 0x2F, 0xBA}, // XK_ccaron+AC10: VKEY_OEM_1 + {0x01E8, 0x3B, 0xBC}, // XK_ccaron+AB08: VKEY_OEM_COMMA + {0x01EA, 0x0C, 0x33}, // XK_eogonek+AE03: VKEY_3 + {0x01F0, 0x13, 0x30}, // XK_dstroke+AE10: VKEY_0 + {0x01F0, 0x23, 0xDD}, // XK_dstroke+AD12: VKEY_OEM_6 + {0x03E7, 0x0E, 0x35}, // XK_iogonek+AE05: VKEY_5 + {0x03EC, 0x0D, 0x34}, // XK_eabovedot+AE04: VKEY_4 + {0x03EC, 0x30, 0xDE}, // XK_eabovedot+AC11: VKEY_OEM_7 + {0x03F9, 0x10, 0x37}, // XK_uogonek+AE07: VKEY_7 + {0x03FE, 0x11, 0x38}, // XK_umacron+AE08: VKEY_8 + {0x03FE, 0x18, 0x51}, // XK_umacron+AD01: VKEY_Q + {0x03FE, 0x35, 0x58}, // XK_umacron+AB02: VKEY_X +}; + +const struct MAP2 { + KeySym ch0; + unsigned sc; + KeySym ch1; + uint8 vk; + bool operator()(const MAP2& m1, const MAP2& m2) const { + if (m1.ch0 == m2.ch0 && m1.sc == m2.sc) + return m1.ch1 < m2.ch1; + if (m1.ch0 == m2.ch0) + return m1.sc < m2.sc; + return m1.ch0 < m2.ch0; + } +} map2[] = { + {0x0023, 0x33, 0x0027, + 0xBF}, // XK_numbersign+BKSL+XK_quoteright: VKEY_OEM_2 + {0x0027, 0x30, 0x0022, + 0xDE}, // XK_quoteright+AC11+XK_quotedbl: VKEY_OEM_7 + {0x0027, 0x31, 0x0022, + 0xC0}, // XK_quoteright+TLDE+XK_quotedbl: VKEY_OEM_3 + {0x0027, 0x31, 0x00B7, + 0xDC}, // XK_quoteright+TLDE+XK_periodcentered: VKEY_OEM_5 + {0x0027, 0x33, 0x0000, 0xDC}, // XK_quoteright+BKSL+NoSymbol: VKEY_OEM_5 + {0x002D, 0x3D, 0x003D, 0xBD}, // XK_minus+AB10+XK_equal: VKEY_OEM_MINUS + {0x002F, 0x0C, 0x0033, 0x33}, // XK_slash+AE03+XK_3: VKEY_3 + {0x002F, 0x0C, 0x003F, 0xBF}, // XK_slash+AE03+XK_question: VKEY_OEM_2 + {0x002F, 0x13, 0x0030, 0x30}, // XK_slash+AE10+XK_0: VKEY_0 + {0x002F, 0x13, 0x003F, 0xBF}, // XK_slash+AE10+XK_question: VKEY_OEM_2 + {0x003D, 0x3D, 0x0025, 0xDF}, // XK_equal+AB10+XK_percent: VKEY_OEM_8 + {0x003D, 0x3D, 0x002B, 0xBB}, // XK_equal+AB10+XK_plus: VKEY_OEM_PLUS + {0x005C, 0x33, 0x002F, 0xDE}, // XK_backslash+BKSL+XK_slash: VKEY_OEM_7 + {0x005C, 0x33, 0x007C, 0xDC}, // XK_backslash+BKSL+XK_bar: VKEY_OEM_5 + {0x0060, 0x31, 0x0000, 0xC0}, // XK_quoteleft+TLDE+NoSymbol: VKEY_OEM_3 + {0x0060, 0x31, 0x00AC, 0xDF}, // XK_quoteleft+TLDE+XK_notsign: VKEY_OEM_8 + {0x00A7, 0x31, 0x00B0, 0xBF}, // XK_section+TLDE+XK_degree: VKEY_OEM_2 + {0x00A7, 0x31, 0x00BD, 0xDC}, // XK_section+TLDE+XK_onehalf: VKEY_OEM_5 + {0x00E0, 0x30, 0x00B0, 0xDE}, // XK_agrave+AC11+XK_degree: VKEY_OEM_7 + {0x00E0, 0x30, 0x00E4, 0xDC}, // XK_agrave+AC11+XK_adiaeresis: VKEY_OEM_5 + {0x00E4, 0x30, 0x00E0, 0xDC}, // XK_adiaeresis+AC11+XK_agrave: VKEY_OEM_5 + {0x00E9, 0x2F, 0x00C9, 0xBA}, // XK_eacute+AC10+XK_Eacute: VKEY_OEM_1 + {0x00E9, 0x2F, 0x00F6, 0xDE}, // XK_eacute+AC10+XK_odiaeresis: VKEY_OEM_7 + {0x00F6, 0x2F, 0x00E9, 0xDE}, // XK_odiaeresis+AC10+XK_eacute: VKEY_OEM_7 + {0x00FC, 0x22, 0x00E8, 0xBA}, // XK_udiaeresis+AD11+XK_egrave: VKEY_OEM_1 +}; + +const struct MAP3 { + KeySym ch0; + unsigned sc; + KeySym ch1; + KeySym ch2; + uint8 vk; + bool operator()(const MAP3& m1, const MAP3& m2) const { + if (m1.ch0 == m2.ch0 && m1.sc == m2.sc && m1.ch1 == m2.ch1) + return m1.ch2 < m2.ch2; + if (m1.ch0 == m2.ch0 && m1.sc == m2.sc) + return m1.ch1 < m2.ch1; + if (m1.ch0 == m2.ch0) + return m1.sc < m2.sc; + return m1.ch0 < m2.ch0; + } +} map3[] = { + {0x0023, 0x33, 0x007E, 0x0000, + 0xDE}, // XK_numbersign+BKSL+XK_asciitilde+NoSymbol: VKEY_OEM_7 + {0x0027, 0x14, 0x003F, 0x0000, + 0xDB}, // XK_quoteright+AE11+XK_question+NoSymbol: VKEY_OEM_4 + {0x0027, 0x14, 0x003F, 0x00DD, + 0xDB}, // XK_quoteright+AE11+XK_question+XK_Yacute: VKEY_OEM_4 + {0x0027, 0x15, 0x002A, 0x0000, + 0xBB}, // XK_quoteright+AE12+XK_asterisk+NoSymbol: VKEY_OEM_PLUS + {0x0027, 0x30, 0x0040, 0x0000, + 0xC0}, // XK_quoteright+AC11+XK_at+NoSymbol: VKEY_OEM_3 + {0x0027, 0x33, 0x002A, 0x0000, + 0xBF}, // XK_quoteright+BKSL+XK_asterisk+NoSymbol: VKEY_OEM_2 + {0x0027, 0x33, 0x002A, 0x00BD, + 0xDC}, // XK_quoteright+BKSL+XK_asterisk+XK_onehalf: VKEY_OEM_5 + {0x0027, 0x33, 0x002A, 0x01A3, + 0xBF}, // XK_quoteright+BKSL+XK_asterisk+XK_Lstroke: VKEY_OEM_2 + {0x0027, 0x34, 0x0022, 0x0000, + 0x5A}, // XK_quoteright+AB01+XK_quotedbl+NoSymbol: VKEY_Z + {0x0027, 0x34, 0x0022, 0x01D8, + 0xDE}, // XK_quoteright+AB01+XK_quotedbl+XK_Rcaron: VKEY_OEM_7 + {0x002B, 0x14, 0x003F, 0x0000, + 0xBB}, // XK_plus+AE11+XK_question+NoSymbol: VKEY_OEM_PLUS + {0x002B, 0x14, 0x003F, 0x005C, + 0xBD}, // XK_plus+AE11+XK_question+XK_backslash: VKEY_OEM_MINUS + {0x002B, 0x14, 0x003F, 0x01F5, + 0xBB}, // XK_plus+AE11+XK_question+XK_odoubleacute: VKEY_OEM_PLUS + {0x002D, 0x15, 0x005F, 0x0000, + 0xBD}, // XK_minus+AE12+XK_underscore+NoSymbol: VKEY_OEM_MINUS + {0x002D, 0x15, 0x005F, 0x03B3, + 0xDB}, // XK_minus+AE12+XK_underscore+XK_rcedilla: VKEY_OEM_4 + {0x002D, 0x3D, 0x005F, 0x0000, + 0xBD}, // XK_minus+AB10+XK_underscore+NoSymbol: VKEY_OEM_MINUS + {0x002D, 0x3D, 0x005F, 0x002A, + 0xBD}, // XK_minus+AB10+XK_underscore+XK_asterisk: VKEY_OEM_MINUS + {0x002D, 0x3D, 0x005F, 0x002F, + 0xBF}, // XK_minus+AB10+XK_underscore+XK_slash: VKEY_OEM_2 + {0x002D, 0x3D, 0x005F, 0x006E, + 0xBD}, // XK_minus+AB10+XK_underscore+XK_n: VKEY_OEM_MINUS + {0x003D, 0x14, 0x0025, 0x0000, + 0xBB}, // XK_equal+AE11+XK_percent+NoSymbol: VKEY_OEM_PLUS + {0x003D, 0x14, 0x0025, 0x002D, + 0xBD}, // XK_equal+AE11+XK_percent+XK_minus: VKEY_OEM_MINUS + {0x005C, 0x31, 0x007C, 0x0031, + 0xDC}, // XK_backslash+TLDE+XK_bar+XK_1: VKEY_OEM_5 + {0x005C, 0x31, 0x007C, 0x03D1, + 0xC0}, // XK_backslash+TLDE+XK_bar+XK_Ncedilla: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x0000, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+NoSymbol: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x0031, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+XK_1: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x003B, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+XK_semicolon: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x0060, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+XK_quoteleft: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x00BF, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+XK_questiondown: VKEY_OEM_3 + {0x0060, 0x31, 0x007E, 0x01F5, + 0xC0}, // XK_quoteleft+TLDE+XK_asciitilde+XK_odoubleacute: VKEY_OEM_3 + {0x00E4, 0x30, 0x00C4, 0x0000, + 0xDE}, // XK_adiaeresis+AC11+XK_Adiaeresis+NoSymbol: VKEY_OEM_7 + {0x00E4, 0x30, 0x00C4, 0x01A6, + 0xDE}, // XK_adiaeresis+AC11+XK_Adiaeresis+XK_Sacute: VKEY_OEM_7 + {0x00E4, 0x30, 0x00C4, 0x01F8, + 0xDE}, // XK_adiaeresis+AC11+XK_Adiaeresis+XK_rcaron: VKEY_OEM_7 + {0x00E7, 0x2F, 0x00C7, 0x0000, + 0xBA}, // XK_ccedilla+AC10+XK_Ccedilla+NoSymbol: VKEY_OEM_1 + {0x00E7, 0x2F, 0x00C7, 0x00DE, + 0xC0}, // XK_ccedilla+AC10+XK_Ccedilla+XK_Thorn: VKEY_OEM_3 + {0x00F6, 0x2F, 0x00D6, 0x0000, + 0xC0}, // XK_odiaeresis+AC10+XK_Odiaeresis+NoSymbol: VKEY_OEM_3 + {0x00F6, 0x2F, 0x00D6, 0x01DE, + 0xC0}, // XK_odiaeresis+AC10+XK_Odiaeresis+XK_Tcedilla: VKEY_OEM_3 + {0x00FC, 0x14, 0x00DC, 0x0000, + 0xBF}, // XK_udiaeresis+AE11+XK_Udiaeresis+NoSymbol: VKEY_OEM_2 + {0x00FC, 0x22, 0x00DC, 0x0000, + 0xBA}, // XK_udiaeresis+AD11+XK_Udiaeresis+NoSymbol: VKEY_OEM_1 + {0x00FC, 0x22, 0x00DC, 0x01A3, + 0xC0}, // XK_udiaeresis+AD11+XK_Udiaeresis+XK_Lstroke: VKEY_OEM_3 + {0x01EA, 0x3D, 0x01CA, 0x0000, + 0xBD}, // XK_eogonek+AB10+XK_Eogonek+NoSymbol: VKEY_OEM_MINUS + {0x01EA, 0x3D, 0x01CA, 0x006E, + 0xBF}, // XK_eogonek+AB10+XK_Eogonek+XK_n: VKEY_OEM_2 + {0x03E7, 0x22, 0x03C7, 0x0000, + 0xDB}, // XK_iogonek+AD11+XK_Iogonek+NoSymbol: VKEY_OEM_4 + {0x03F9, 0x2F, 0x03D9, 0x0000, + 0xC0}, // XK_uogonek+AC10+XK_Uogonek+NoSymbol: VKEY_OEM_3 + {0x03F9, 0x2F, 0x03D9, 0x01DE, + 0xBA}, // XK_uogonek+AC10+XK_Uogonek+XK_Tcedilla: VKEY_OEM_1 +}; + +template <class T_MAP> +KeyboardCode FindVK(const T_MAP& key, const T_MAP* map, size_t size) { + T_MAP comp = {0}; + const T_MAP* p = std::lower_bound(map, map + size, key, comp); + if (p != map + size && !comp(*p, key) && !comp(key, *p)) + return static_cast<KeyboardCode>(p->vk); + return VKEY_UNKNOWN; +} + +} // namespace + // Get an ui::KeyboardCode from an X keyevent KeyboardCode KeyboardCodeFromXKeyEvent(XEvent* xev) { + // Gets correct VKEY code from XEvent is performed as the following steps: + // 1. Gets the keysym without modifier states. + // 2. For [a-z] & [0-9] cases, returns the VKEY code accordingly. + // 3. Find keysym in map0. + // 4. If not found, fallback to find keysym + hardware_code in map1. + // 5. If not found, fallback to find keysym + keysym_shift + hardware_code + // in map2. + // 6. If not found, fallback to find keysym + keysym_shift + keysym_altgr + + // hardware_code in map3. + // 7. If not found, fallback to find in KeyboardCodeFromXKeysym(), which + // mainly for non-letter keys. + // 8. If not found, fallback to find with the hardware code in US layout. + + KeySym keysym = NoSymbol; + XKeyEvent xkey = xev->xkey; + xkey.state &= (~0xFF | Mod2Mask); // Clears the xkey's state except numlock. // XLookupKeysym does not take into consideration the state of the lock/shift // etc. keys. So it is necessary to use XLookupString instead. - KeySym keysym; - XLookupString(&xev->xkey, NULL, 0, &keysym, NULL); - KeyboardCode keycode = KeyboardCodeFromXKeysym(keysym); - if (keycode == VKEY_UNKNOWN) { - keysym = DefaultXKeysymFromHardwareKeycode(xev->xkey.keycode); - keycode = KeyboardCodeFromXKeysym(keysym); + XLookupString(&xkey, NULL, 0, &keysym, NULL); + + // [a-z] cases. + if (keysym >= XK_a && keysym <= XK_z) + return static_cast<KeyboardCode>(VKEY_A + keysym - XK_a); + + // [0-9] cases. + if (keysym >= XK_0 && keysym <= XK_9) + return static_cast<KeyboardCode>(VKEY_0 + keysym - XK_0); + + KeyboardCode keycode = VKEY_UNKNOWN; + + if (!IsKeypadKey(keysym) && !IsPrivateKeypadKey(keysym) && + !IsCursorKey(keysym) && !IsPFKey(keysym) && !IsFunctionKey(keysym) && + !IsModifierKey(keysym)) { + MAP0 key0 = {keysym & 0xFFFF, 0}; + keycode = FindVK(key0, map0, arraysize(map0)); + if (keycode != VKEY_UNKNOWN) + return keycode; + + MAP1 key1 = {keysym & 0xFFFF, xkey.keycode, 0}; + keycode = FindVK(key1, map1, arraysize(map1)); + if (keycode != VKEY_UNKNOWN) + return keycode; + + KeySym keysym_shift = NoSymbol; + xkey.state |= ShiftMask; + XLookupString(&xkey, NULL, 0, &keysym_shift, NULL); + MAP2 key2 = {keysym & 0xFFFF, xkey.keycode, keysym_shift & 0xFFFF, 0}; + keycode = FindVK(key2, map2, arraysize(map2)); + if (keycode != VKEY_UNKNOWN) + return keycode; + + KeySym keysym_altgr = NoSymbol; + xkey.state &= ~ShiftMask; + xkey.state |= Mod1Mask; + XLookupString(&xkey, NULL, 0, &keysym_altgr, NULL); + MAP3 key3 = {keysym & 0xFFFF, xkey.keycode, keysym_shift & 0xFFFF, + keysym_altgr & 0xFFFF, 0}; + keycode = FindVK(key3, map3, arraysize(map3)); + if (keycode != VKEY_UNKNOWN) + return keycode; + + // On Linux some keys has AltGr char but not on Windows. + // So if cannot find VKEY with (ch0+sc+ch1+ch2) in map3, tries to fallback + // to just find VKEY with (ch0+sc+ch1). This is the best we could do. + MAP3 key4 = {keysym & 0xFFFF, xkey.keycode, keysym_shift & 0xFFFF, 0xFFFF, + 0}; + const MAP3* p = + std::lower_bound(map3, map3 + arraysize(map3), key4, MAP3()); + if (p != map3 + arraysize(map3) && p->ch0 == key4.ch0 && p->sc == key4.sc && + p->ch1 == key4.ch1) + return static_cast<KeyboardCode>(p->vk); } + keycode = KeyboardCodeFromXKeysym(keysym); + if (keycode == VKEY_UNKNOWN) + keycode = DefaultKeyboardCodeFromHardwareKeycode(xkey.keycode); + return keycode; } @@ -99,117 +607,6 @@ KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) { return VKEY_NONCONVERT; case XK_Zenkaku_Hankaku: return VKEY_DBE_DBCSCHAR; - case XK_A: - case XK_a: - return VKEY_A; - case XK_B: - case XK_b: - return VKEY_B; - case XK_C: - case XK_c: - return VKEY_C; - case XK_D: - case XK_d: - return VKEY_D; - case XK_E: - case XK_e: - return VKEY_E; - case XK_F: - case XK_f: - return VKEY_F; - case XK_G: - case XK_g: - return VKEY_G; - case XK_H: - case XK_h: - return VKEY_H; - case XK_I: - case XK_i: - return VKEY_I; - case XK_J: - case XK_j: - return VKEY_J; - case XK_K: - case XK_k: - return VKEY_K; - case XK_L: - case XK_l: - return VKEY_L; - case XK_M: - case XK_m: - return VKEY_M; - case XK_N: - case XK_n: - return VKEY_N; - case XK_O: - case XK_o: - return VKEY_O; - case XK_P: - case XK_p: - return VKEY_P; - case XK_Q: - case XK_q: - return VKEY_Q; - case XK_R: - case XK_r: - return VKEY_R; - case XK_S: - case XK_s: - return VKEY_S; - case XK_T: - case XK_t: - return VKEY_T; - case XK_U: - case XK_u: - return VKEY_U; - case XK_V: - case XK_v: - return VKEY_V; - case XK_W: - case XK_w: - return VKEY_W; - case XK_X: - case XK_x: - return VKEY_X; - case XK_Y: - case XK_y: - return VKEY_Y; - case XK_Z: - case XK_z: - return VKEY_Z; - - case XK_0: - case XK_1: - case XK_2: - case XK_3: - case XK_4: - case XK_5: - case XK_6: - case XK_7: - case XK_8: - case XK_9: - return static_cast<KeyboardCode>(VKEY_0 + (keysym - XK_0)); - - case XK_parenright: - return VKEY_0; - case XK_exclam: - return VKEY_1; - case XK_at: - return VKEY_2; - case XK_numbersign: - return VKEY_3; - case XK_dollar: - return VKEY_4; - case XK_percent: - return VKEY_5; - case XK_asciicircum: - return VKEY_6; - case XK_ampersand: - return VKEY_7; - case XK_asterisk: - return VKEY_8; - case XK_parenleft: - return VKEY_9; case XK_KP_0: case XK_KP_1: @@ -371,20 +768,6 @@ KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) { case XF86XK_Launch9: return VKEY_F18; -#if defined(TOOLKIT_GTK) - case XF86XK_Refresh: - case XF86XK_History: - case XF86XK_OpenURL: - case XF86XK_AddFavorite: - case XF86XK_Go: - case XF86XK_ZoomIn: - case XF86XK_ZoomOut: - // ui::AcceleratorGtk tries to convert the XF86XK_ keysyms on Chrome - // startup. It's safe to return VKEY_UNKNOWN here since ui::AcceleratorGtk - // also checks a Gdk keysym. http://crbug.com/109843 - return VKEY_UNKNOWN; -#endif - // For supporting multimedia buttons on a USB keyboard. case XF86XK_Back: return VKEY_BROWSER_BACK; @@ -450,96 +833,178 @@ uint16 GetCharacterFromXEvent(XEvent* xev) { int bytes_written = XLookupString(&xev->xkey, buf, 6, NULL, NULL); DCHECK_LE(bytes_written, 6); - base::string16 result; - return (bytes_written > 0 && UTF8ToUTF16(buf, bytes_written, &result) && - result.length() == 1) ? result[0] : 0; + if (bytes_written <= 0) + return 0; + const base::string16& result = base::WideToUTF16( + base::SysNativeMBToWide(base::StringPiece(buf, bytes_written))); + return result.length() == 1 ? result[0] : 0; } -unsigned int DefaultXKeysymFromHardwareKeycode(unsigned int hardware_code) { - static const unsigned int kHardwareKeycodeMap[] = { - 0, // 0x00: - 0, // 0x01: - 0, // 0x02: - 0, // 0x03: - 0, // 0x04: - 0, // 0x05: - 0, // 0x06: - 0, // 0x07: - 0, // 0x08: - XK_Escape, // 0x09: XK_Escape - XK_1, // 0x0A: XK_1 - XK_2, // 0x0B: XK_2 - XK_3, // 0x0C: XK_3 - XK_4, // 0x0D: XK_4 - XK_5, // 0x0E: XK_5 - XK_6, // 0x0F: XK_6 - XK_7, // 0x10: XK_7 - XK_8, // 0x11: XK_8 - XK_9, // 0x12: XK_9 - XK_0, // 0x13: XK_0 - XK_minus, // 0x14: XK_minus - XK_equal, // 0x15: XK_equal - XK_BackSpace, // 0x16: XK_BackSpace - XK_Tab, // 0x17: XK_Tab - XK_q, // 0x18: XK_q - XK_w, // 0x19: XK_w - XK_e, // 0x1A: XK_e - XK_r, // 0x1B: XK_r - XK_t, // 0x1C: XK_t - XK_y, // 0x1D: XK_y - XK_u, // 0x1E: XK_u - XK_i, // 0x1F: XK_i - XK_o, // 0x20: XK_o - XK_p, // 0x21: XK_p - XK_bracketleft, // 0x22: XK_bracketleft - XK_bracketright, // 0x23: XK_bracketright - XK_Return, // 0x24: XK_Return - XK_Control_L, // 0x25: XK_Control_L - XK_a, // 0x26: XK_a - XK_s, // 0x27: XK_s - XK_d, // 0x28: XK_d - XK_f, // 0x29: XK_f - XK_g, // 0x2A: XK_g - XK_h, // 0x2B: XK_h - XK_j, // 0x2C: XK_j - XK_k, // 0x2D: XK_k - XK_l, // 0x2E: XK_l - XK_semicolon, // 0x2F: XK_semicolon - XK_apostrophe, // 0x30: XK_apostrophe - XK_grave, // 0x31: XK_grave - XK_Shift_L, // 0x32: XK_Shift_L - XK_backslash, // 0x33: XK_backslash - XK_z, // 0x34: XK_z - XK_x, // 0x35: XK_x - XK_c, // 0x36: XK_c - XK_v, // 0x37: XK_v - XK_b, // 0x38: XK_b - XK_n, // 0x39: XK_n - XK_m, // 0x3A: XK_m - XK_comma, // 0x3B: XK_comma - XK_period, // 0x3C: XK_period - XK_slash, // 0x3D: XK_slash - XK_Shift_R, // 0x3E: XK_Shift_R - 0, // 0x3F: XK_KP_Multiply - XK_Alt_L, // 0x40: XK_Alt_L - XK_space, // 0x41: XK_space - XK_Caps_Lock, // 0x42: XK_Caps_Lock - XK_F1, // 0x43: XK_F1 - XK_F2, // 0x44: XK_F2 - XK_F3, // 0x45: XK_F3 - XK_F4, // 0x46: XK_F4 - XK_F5, // 0x47: XK_F5 - XK_F6, // 0x48: XK_F6 - XK_F7, // 0x49: XK_F7 - XK_F8, // 0x4A: XK_F8 - XK_F9, // 0x4B: XK_F9 - XK_F10, // 0x4C: XK_F10 - XK_Num_Lock, // 0x4D: XK_Num_Lock - XK_Scroll_Lock, // 0x4E: XK_Scroll_Lock +KeyboardCode DefaultKeyboardCodeFromHardwareKeycode( + unsigned int hardware_code) { + // This function assumes that X11 is using evdev-based keycodes. + static const KeyboardCode kHardwareKeycodeMap[] = { + // Please refer to below links for the table content: + // http://www.w3.org/TR/DOM-Level-3-Events-code/#keyboard-101 + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode + // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf + VKEY_UNKNOWN, // 0x00: + VKEY_UNKNOWN, // 0x01: + VKEY_UNKNOWN, // 0x02: + VKEY_UNKNOWN, // 0x03: + VKEY_UNKNOWN, // 0x04: + VKEY_UNKNOWN, // 0x05: + VKEY_UNKNOWN, // 0x06: + VKEY_UNKNOWN, // XKB evdev (XKB - 8) X KeySym + VKEY_UNKNOWN, // === =============== ====== + VKEY_ESCAPE, // 0x09: KEY_ESC Escape + VKEY_1, // 0x0A: KEY_1 1 + VKEY_2, // 0x0B: KEY_2 2 + VKEY_3, // 0x0C: KEY_3 3 + VKEY_4, // 0x0D: KEY_4 4 + VKEY_5, // 0x0E: KEY_5 5 + VKEY_6, // 0x0F: KEY_6 6 + VKEY_7, // 0x10: KEY_7 7 + VKEY_8, // 0x11: KEY_8 8 + VKEY_9, // 0x12: KEY_9 9 + VKEY_0, // 0x13: KEY_0 0 + VKEY_OEM_MINUS, // 0x14: KEY_MINUS minus + VKEY_OEM_PLUS, // 0x15: KEY_EQUAL equal + VKEY_BACK, // 0x16: KEY_BACKSPACE BackSpace + VKEY_TAB, // 0x17: KEY_TAB Tab + VKEY_Q, // 0x18: KEY_Q q + VKEY_W, // 0x19: KEY_W w + VKEY_E, // 0x1A: KEY_E e + VKEY_R, // 0x1B: KEY_R r + VKEY_T, // 0x1C: KEY_T t + VKEY_Y, // 0x1D: KEY_Y y + VKEY_U, // 0x1E: KEY_U u + VKEY_I, // 0x1F: KEY_I i + VKEY_O, // 0x20: KEY_O o + VKEY_P, // 0x21: KEY_P p + VKEY_OEM_4, // 0x22: KEY_LEFTBRACE bracketleft + VKEY_OEM_6, // 0x23: KEY_RIGHTBRACE bracketright + VKEY_RETURN, // 0x24: KEY_ENTER Return + VKEY_LCONTROL, // 0x25: KEY_LEFTCTRL Control_L + VKEY_A, // 0x26: KEY_A a + VKEY_S, // 0x27: KEY_S s + VKEY_D, // 0x28: KEY_D d + VKEY_F, // 0x29: KEY_F f + VKEY_G, // 0x2A: KEY_G g + VKEY_H, // 0x2B: KEY_H h + VKEY_J, // 0x2C: KEY_J j + VKEY_K, // 0x2D: KEY_K k + VKEY_L, // 0x2E: KEY_L l + VKEY_OEM_1, // 0x2F: KEY_SEMICOLON semicolon + VKEY_OEM_7, // 0x30: KEY_APOSTROPHE apostrophe + VKEY_OEM_3, // 0x31: KEY_GRAVE grave + VKEY_LSHIFT, // 0x32: KEY_LEFTSHIFT Shift_L + VKEY_OEM_5, // 0x33: KEY_BACKSLASH backslash + VKEY_Z, // 0x34: KEY_Z z + VKEY_X, // 0x35: KEY_X x + VKEY_C, // 0x36: KEY_C c + VKEY_V, // 0x37: KEY_V v + VKEY_B, // 0x38: KEY_B b + VKEY_N, // 0x39: KEY_N n + VKEY_M, // 0x3A: KEY_M m + VKEY_OEM_COMMA, // 0x3B: KEY_COMMA comma + VKEY_OEM_PERIOD, // 0x3C: KEY_DOT period + VKEY_OEM_2, // 0x3D: KEY_SLASH slash + VKEY_RSHIFT, // 0x3E: KEY_RIGHTSHIFT Shift_R + VKEY_MULTIPLY, // 0x3F: KEY_KPASTERISK KP_Multiply + VKEY_LMENU, // 0x40: KEY_LEFTALT Alt_L + VKEY_SPACE, // 0x41: KEY_SPACE space + VKEY_CAPITAL, // 0x42: KEY_CAPSLOCK Caps_Lock + VKEY_F1, // 0x43: KEY_F1 F1 + VKEY_F2, // 0x44: KEY_F2 F2 + VKEY_F3, // 0x45: KEY_F3 F3 + VKEY_F4, // 0x46: KEY_F4 F4 + VKEY_F5, // 0x47: KEY_F5 F5 + VKEY_F6, // 0x48: KEY_F6 F6 + VKEY_F7, // 0x49: KEY_F7 F7 + VKEY_F8, // 0x4A: KEY_F8 F8 + VKEY_F9, // 0x4B: KEY_F9 F9 + VKEY_F10, // 0x4C: KEY_F10 F10 + VKEY_NUMLOCK, // 0x4D: KEY_NUMLOCK Num_Lock + VKEY_SCROLL, // 0x4E: KEY_SCROLLLOCK Scroll_Lock + VKEY_NUMPAD7, // 0x4F: KEY_KP7 KP_7 + VKEY_NUMPAD8, // 0x50: KEY_KP8 KP_8 + VKEY_NUMPAD9, // 0x51: KEY_KP9 KP_9 + VKEY_SUBTRACT, // 0x52: KEY_KPMINUS KP_Subtract + VKEY_NUMPAD4, // 0x53: KEY_KP4 KP_4 + VKEY_NUMPAD5, // 0x54: KEY_KP5 KP_5 + VKEY_NUMPAD6, // 0x55: KEY_KP6 KP_6 + VKEY_ADD, // 0x56: KEY_KPPLUS KP_Add + VKEY_NUMPAD1, // 0x57: KEY_KP1 KP_1 + VKEY_NUMPAD2, // 0x58: KEY_KP2 KP_2 + VKEY_NUMPAD3, // 0x59: KEY_KP3 KP_3 + VKEY_NUMPAD0, // 0x5A: KEY_KP0 KP_0 + VKEY_DECIMAL, // 0x5B: KEY_KPDOT KP_Decimal + VKEY_UNKNOWN, // 0x5C: + VKEY_DBE_DBCSCHAR, // 0x5D: KEY_ZENKAKUHANKAKU Zenkaku_Hankaku + VKEY_OEM_5, // 0x5E: KEY_102ND backslash + VKEY_F11, // 0x5F: KEY_F11 F11 + VKEY_F12, // 0x60: KEY_F12 F12 + VKEY_OEM_102, // 0x61: KEY_RO Romaji + VKEY_UNSUPPORTED, // 0x62: KEY_KATAKANA Katakana + VKEY_UNSUPPORTED, // 0x63: KEY_HIRAGANA Hiragana + VKEY_CONVERT, // 0x64: KEY_HENKAN Henkan + VKEY_UNSUPPORTED, // 0x65: KEY_KATAKANAHIRAGANA Hiragana_Katakana + VKEY_NONCONVERT, // 0x66: KEY_MUHENKAN Muhenkan + VKEY_SEPARATOR, // 0x67: KEY_KPJPCOMMA KP_Separator + VKEY_RETURN, // 0x68: KEY_KPENTER KP_Enter + VKEY_RCONTROL, // 0x69: KEY_RIGHTCTRL Control_R + VKEY_DIVIDE, // 0x6A: KEY_KPSLASH KP_Divide + VKEY_PRINT, // 0x6B: KEY_SYSRQ Print + VKEY_RMENU, // 0x6C: KEY_RIGHTALT Alt_R + VKEY_RETURN, // 0x6D: KEY_LINEFEED Linefeed + VKEY_HOME, // 0x6E: KEY_HOME Home + VKEY_UP, // 0x6F: KEY_UP Up + VKEY_PRIOR, // 0x70: KEY_PAGEUP Page_Up + VKEY_LEFT, // 0x71: KEY_LEFT Left + VKEY_RIGHT, // 0x72: KEY_RIGHT Right + VKEY_END, // 0x73: KEY_END End + VKEY_DOWN, // 0x74: KEY_DOWN Down + VKEY_NEXT, // 0x75: KEY_PAGEDOWN Page_Down + VKEY_INSERT, // 0x76: KEY_INSERT Insert + VKEY_DELETE, // 0x77: KEY_DELETE Delete + VKEY_UNSUPPORTED, // 0x78: KEY_MACRO + VKEY_VOLUME_MUTE, // 0x79: KEY_MUTE XF86AudioMute + VKEY_VOLUME_DOWN, // 0x7A: KEY_VOLUMEDOWN XF86AudioLowerVolume + VKEY_VOLUME_UP, // 0x7B: KEY_VOLUMEUP XF86AudioRaiseVolume + VKEY_POWER, // 0x7C: KEY_POWER XF86PowerOff + VKEY_OEM_PLUS, // 0x7D: KEY_KPEQUAL KP_Equal + VKEY_UNSUPPORTED, // 0x7E: KEY_KPPLUSMINUS plusminus + VKEY_PAUSE, // 0x7F: KEY_PAUSE Pause + VKEY_MEDIA_LAUNCH_APP1, // 0x80: KEY_SCALE XF86LaunchA + VKEY_DECIMAL, // 0x81: KEY_KPCOMMA KP_Decimal + VKEY_HANGUL, // 0x82: KEY_HANGUEL Hangul + VKEY_HANJA, // 0x83: KEY_HANJA Hangul_Hanja + VKEY_OEM_5, // 0x84: KEY_YEN yen + VKEY_LWIN, // 0x85: KEY_LEFTMETA Super_L + VKEY_RWIN, // 0x86: KEY_RIGHTMETA Super_R + VKEY_COMPOSE, // 0x87: KEY_COMPOSE Menu }; - return hardware_code < arraysize(kHardwareKeycodeMap) ? - kHardwareKeycodeMap[hardware_code] : 0; + if (hardware_code >= arraysize(kHardwareKeycodeMap)) { + // Additional keycodes used by the Chrome OS top row special function keys. + switch (hardware_code) { + case 0xA6: // KEY_BACK + return VKEY_BACK; + case 0xA7: // KEY_FORWARD + return VKEY_BROWSER_FORWARD; + case 0xB5: // KEY_REFRESH + return VKEY_BROWSER_REFRESH; + case 0xD4: // KEY_DASHBOARD + return VKEY_MEDIA_LAUNCH_APP2; + case 0xE8: // KEY_BRIGHTNESSDOWN + return VKEY_BRIGHTNESS_DOWN; + case 0xE9: // KEY_BRIGHTNESSUP + return VKEY_BRIGHTNESS_UP; + } + return VKEY_UNKNOWN; + } + return kHardwareKeycodeMap[hardware_code]; } // TODO(jcampan): this method might be incomplete. diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_x.h b/chromium/ui/events/keycodes/keyboard_code_conversion_x.h index b5d8fe87c37..f9186ca0c88 100644 --- a/chromium/ui/events/keycodes/keyboard_code_conversion_x.h +++ b/chromium/ui/events/keycodes/keyboard_code_conversion_x.h @@ -26,8 +26,9 @@ EVENTS_BASE_EXPORT uint16 GetCharacterFromXEvent(XEvent* xev); EVENTS_BASE_EXPORT int XKeysymForWindowsKeyCode(KeyboardCode keycode, bool shift); -// Converts an X keycode into an X KeySym. -unsigned int DefaultXKeysymFromHardwareKeycode(unsigned int keycode); +// Converts an X keycode into ui::KeyboardCode. +EVENTS_BASE_EXPORT KeyboardCode + DefaultKeyboardCodeFromHardwareKeycode(unsigned int hardware_code); } // namespace ui diff --git a/chromium/ui/events/latency_info.cc b/chromium/ui/events/latency_info.cc index a3a11828db0..3fedd67e736 100644 --- a/chromium/ui/events/latency_info.cc +++ b/chromium/ui/events/latency_info.cc @@ -11,10 +11,14 @@ #include <algorithm> namespace { + +const size_t kMaxLatencyInfoNumber = 100; + const char* GetComponentName(ui::LatencyComponentType type) { #define CASE_TYPE(t) case ui::t: return #t switch (type) { CASE_TYPE(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); + CASE_TYPE(INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT); @@ -29,6 +33,7 @@ const char* GetComponentName(ui::LatencyComponentType type) { CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT); CASE_TYPE(LATENCY_INFO_LIST_TERMINATED_OVERFLOW_COMPONENT); + CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT); default: DLOG(WARNING) << "Unhandled LatencyComponentType.\n"; break; @@ -46,6 +51,7 @@ bool IsTerminalComponent(ui::LatencyComponentType type) { case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT: case ui::INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT: case ui::LATENCY_INFO_LIST_TERMINATED_OVERFLOW_COMPONENT: + case ui::INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT: return true; default: return false; @@ -53,7 +59,8 @@ bool IsTerminalComponent(ui::LatencyComponentType type) { } bool IsBeginComponent(ui::LatencyComponentType type) { - return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT); + return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT || + type == ui::INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT); } // This class is for converting latency info to trace buffer friendly format. @@ -119,16 +126,28 @@ LatencyInfo::LatencyInfo() : trace_id(-1), terminated(false) { LatencyInfo::~LatencyInfo() { } -void LatencyInfo::MergeWith(const LatencyInfo& other) { +bool LatencyInfo::Verify(const std::vector<LatencyInfo>& latency_info, + const char* referring_msg) { + if (latency_info.size() > kMaxLatencyInfoNumber) { + LOG(ERROR) << referring_msg << ", LatencyInfo vector size " + << latency_info.size() << " is too big."; + return false; + } + return true; +} + +void LatencyInfo::CopyLatencyFrom(const LatencyInfo& other, + LatencyComponentType type) { for (LatencyMap::const_iterator it = other.latency_components.begin(); it != other.latency_components.end(); ++it) { - AddLatencyNumberWithTimestamp(it->first.first, - it->first.second, - it->second.sequence_number, - it->second.event_time, - it->second.event_count, - false); + if (it->first.first == type) { + AddLatencyNumberWithTimestamp(it->first.first, + it->first.second, + it->second.sequence_number, + it->second.event_time, + it->second.event_count); + } } } @@ -141,8 +160,7 @@ void LatencyInfo::AddNewLatencyFrom(const LatencyInfo& other) { it->first.second, it->second.sequence_number, it->second.event_time, - it->second.event_count, - false); + it->second.event_count); } } } @@ -151,22 +169,23 @@ void LatencyInfo::AddLatencyNumber(LatencyComponentType component, int64 id, int64 component_sequence_number) { AddLatencyNumberWithTimestamp(component, id, component_sequence_number, - base::TimeTicks::HighResNow(), 1, true); + base::TimeTicks::HighResNow(), 1); } void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component, int64 id, int64 component_sequence_number, base::TimeTicks time, - uint32 event_count, - bool dump_to_trace) { - if (dump_to_trace && IsBeginComponent(component)) { + uint32 event_count) { + if (IsBeginComponent(component)) { // Should only ever add begin component once. CHECK_EQ(-1, trace_id); trace_id = component_sequence_number; TRACE_EVENT_ASYNC_BEGIN0("benchmark", "InputLatency", TRACE_ID_DONT_MANGLE(trace_id)); + TRACE_EVENT_FLOW_BEGIN0( + "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id)); } LatencyMap::key_type key = std::make_pair(component, id); @@ -188,7 +207,7 @@ void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component, } } - if (dump_to_trace && IsTerminalComponent(component) && trace_id != -1) { + if (IsTerminalComponent(component) && trace_id != -1) { // Should only ever add terminal component once. CHECK(!terminated); terminated = true; @@ -196,6 +215,8 @@ void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component, "InputLatency", TRACE_ID_DONT_MANGLE(trace_id), "data", AsTraceableData(*this)); + TRACE_EVENT_FLOW_END0( + "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id)); } } diff --git a/chromium/ui/events/latency_info.h b/chromium/ui/events/latency_info.h index 3e50cc4f80c..619b0462f71 100644 --- a/chromium/ui/events/latency_info.h +++ b/chromium/ui/events/latency_info.h @@ -5,10 +5,11 @@ #ifndef UI_EVENTS_LATENCY_INFO_H_ #define UI_EVENTS_LATENCY_INFO_H_ -#include <map> #include <utility> +#include <vector> #include "base/basictypes.h" +#include "base/containers/small_map.h" #include "base/time/time.h" #include "ui/events/events_base_export.h" @@ -19,6 +20,8 @@ enum LatencyComponentType { // BEGIN COMPONENT is when we show the latency begin in chrome://tracing. // Timestamp when the input event is sent from RenderWidgetHost to renderer. INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, + // Timestamp when the input event is received in plugin. + INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT, // ---------------------------NORMAL COMPONENT------------------------------- // Timestamp when the scroll update gesture event is sent from RWH to // renderer. In Aura, touch event's LatencyInfo is carried over to the gesture @@ -64,6 +67,10 @@ enum LatencyComponentType { // This component indicates that the cached LatencyInfo number exceeds the // maximal allowed size. LATENCY_INFO_LIST_TERMINATED_OVERFLOW_COMPONENT, + // Timestamp when the input event is considered not cause any rendering + // damage in plugin and thus terminated. + INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT, + LATENCY_COMPONENT_TYPE_LAST = INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT }; struct EVENTS_BASE_EXPORT LatencyInfo { @@ -78,17 +85,31 @@ struct EVENTS_BASE_EXPORT LatencyInfo { uint32 event_count; }; + // Empirically determined constant based on a typical scroll sequence. + enum { kTypicalMaxComponentsPerLatencyInfo = 6 }; + // Map a Latency Component (with a component-specific int64 id) to a // component info. - typedef std::map<std::pair<LatencyComponentType, int64>, LatencyComponent> - LatencyMap; + typedef base::SmallMap< + std::map<std::pair<LatencyComponentType, int64>, LatencyComponent>, + kTypicalMaxComponentsPerLatencyInfo> LatencyMap; LatencyInfo(); ~LatencyInfo(); - // Merges the contents of another LatencyInfo into this one. - void MergeWith(const LatencyInfo& other); + // Returns true if the vector |latency_info| is valid. Returns false + // if it is not valid and log the |referring_msg|. + // This function is mainly used to check the latency_info vector that + // is passed between processes using IPC message has reasonable size + // so that we are confident the IPC message is not corrupted/compromised. + // This check will go away once the IPC system has better built-in scheme + // for corruption/compromise detection. + static bool Verify(const std::vector<LatencyInfo>& latency_info, + const char* referring_msg); + + // Copy LatencyComponents with type |type| from |other| into |this|. + void CopyLatencyFrom(const LatencyInfo& other, LatencyComponentType type); // Add LatencyComponents that are in |other| but not in |this|. void AddNewLatencyFrom(const LatencyInfo& other); @@ -101,13 +122,11 @@ struct EVENTS_BASE_EXPORT LatencyInfo { // Modifies the current sequence number and adds a certain number of events // for a specific component. - // TODO(miletus): Remove the |dump_to_trace| once we remove MergeWith(). void AddLatencyNumberWithTimestamp(LatencyComponentType component, int64 id, int64 component_sequence_number, base::TimeTicks time, - uint32 event_count, - bool dump_to_trace); + uint32 event_count); // Returns true if the a component with |type| and |id| is found in // the latency_components and the component is stored to |output| if diff --git a/chromium/ui/events/latency_info_nacl.gyp b/chromium/ui/events/latency_info_nacl.gyp new file mode 100644 index 00000000000..e42dbf87ee4 --- /dev/null +++ b/chromium/ui/events/latency_info_nacl.gyp @@ -0,0 +1,78 @@ +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../../build/common_untrusted.gypi', + ], + 'conditions': [ + ['disable_nacl==0 and disable_nacl_untrusted==0', { + 'targets': [ + { + 'target_name': 'latency_info_nacl', + 'type': 'none', + 'defines': [ + 'EVENTS_BASE_IMPLEMENTATION', + 'EVENTS_IMPLEMENTATION', + ], + 'include_dirs': [ + '../..', + ], + 'dependencies': [ + '<(DEPTH)/base/base_nacl.gyp:base_nacl', + '<(DEPTH)/ipc/ipc_nacl.gyp:ipc_nacl', + '<(DEPTH)/native_client/tools.gyp:prep_toolchain' + ], + 'variables': { + 'nacl_untrusted_build': 1, + 'nlib_target': 'liblatency_info_nacl.a', + 'build_glibc': 0, + 'build_newlib': 0, + 'build_irt': 1, + }, + 'sources': [ + 'latency_info.cc', + 'latency_info.h', + 'ipc/latency_info_param_traits.cc', + 'ipc/latency_info_param_traits.h', + ], + }, + ], + }], + ['disable_nacl!=1 and OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'latency_info_nacl_win64', + 'type' : '<(component)', + 'variables': { + 'nacl_win64_target': 1, + }, + 'dependencies': [ + '<(DEPTH)/base/base.gyp:base_win64', + '<(DEPTH)/ipc/ipc.gyp:ipc_win64', + ], + 'defines': [ + 'EVENTS_BASE_IMPLEMENTATION', + 'EVENTS_IMPLEMENTATION', + '<@(nacl_win64_defines)', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'latency_info.cc', + 'latency_info.h', + 'ipc/latency_info_param_traits.cc', + 'ipc/latency_info_param_traits.h', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + ], +} + diff --git a/chromium/ui/events/latency_info_unittest.cc b/chromium/ui/events/latency_info_unittest.cc index ec39a0dbcba..2f9ad0562be 100644 --- a/chromium/ui/events/latency_info_unittest.cc +++ b/chromium/ui/events/latency_info_unittest.cc @@ -14,14 +14,12 @@ TEST(LatencyInfoTest, AddTwoSeparateEvent) { 0, 1, base::TimeTicks::FromInternalValue(100), - 1, - true); + 1); info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, 5, base::TimeTicks::FromInternalValue(1000), - 2, - true); + 2); EXPECT_EQ(info.latency_components.size(), 2u); LatencyInfo::LatencyComponent component; @@ -47,14 +45,12 @@ TEST(LatencyInfoTest, AddTwoSameEvent) { 0, 30, base::TimeTicks::FromInternalValue(100), - 2, - true); + 2); info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 13, base::TimeTicks::FromInternalValue(200), - 3, - true); + 3); EXPECT_EQ(info.latency_components.size(), 1u); LatencyInfo::LatencyComponent component; @@ -69,79 +65,13 @@ TEST(LatencyInfoTest, AddTwoSameEvent) { EXPECT_EQ(component.event_time.ToInternalValue(), (100 * 2 + 200 * 3) / 5); } -TEST(LatencyInfoTest, MergeTwoSeparateEvent) { - LatencyInfo info1; - LatencyInfo info2; - info1.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, - 0, - 1, - base::TimeTicks::FromInternalValue(100), - 1, - true); - info2.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, - 1, - 5, - base::TimeTicks::FromInternalValue(1000), - 2, - true); - info1.MergeWith(info2); - - EXPECT_EQ(info1.latency_components.size(), 2u); - LatencyInfo::LatencyComponent component; - EXPECT_FALSE( - info1.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component)); - EXPECT_FALSE(info1.FindLatency( - INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 1, &component)); - EXPECT_TRUE(info1.FindLatency( - INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0, &component)); - EXPECT_EQ(component.sequence_number, 1); - EXPECT_EQ(component.event_count, 1u); - EXPECT_EQ(component.event_time.ToInternalValue(), 100); - EXPECT_TRUE( - info1.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component)); - EXPECT_EQ(component.sequence_number, 5); - EXPECT_EQ(component.event_count, 2u); - EXPECT_EQ(component.event_time.ToInternalValue(), 1000); -} - -TEST(LatencyInfoTest, MergeTwoSameEvent) { - LatencyInfo info1; - LatencyInfo info2; - info1.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, - 0, - 30, - base::TimeTicks::FromInternalValue(100), - 2, - true); - info2.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, - 0, - 13, - base::TimeTicks::FromInternalValue(200), - 3, - true); - info1.MergeWith(info2); - - EXPECT_EQ(info1.latency_components.size(), 1u); - LatencyInfo::LatencyComponent component; - EXPECT_FALSE( - info1.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component)); - EXPECT_FALSE( - info1.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component)); - EXPECT_TRUE( - info1.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, &component)); - EXPECT_EQ(component.sequence_number, 30); - EXPECT_EQ(component.event_count, 5u); - EXPECT_EQ(component.event_time.ToInternalValue(), (100 * 2 + 200 * 3) / 5); -} - TEST(LatencyInfoTest, ClearEvents) { LatencyInfo info; info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, 30, base::TimeTicks::FromInternalValue(100), - 2, - true); + 2); EXPECT_EQ(info.latency_components.size(), 1u); info.Clear(); diff --git a/chromium/ui/events/linux/text_edit_command_auralinux.cc b/chromium/ui/events/linux/text_edit_command_auralinux.cc new file mode 100644 index 00000000000..4429225f48c --- /dev/null +++ b/chromium/ui/events/linux/text_edit_command_auralinux.cc @@ -0,0 +1,124 @@ +// Copyright 2014 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/linux/text_edit_command_auralinux.h" + +#include "base/logging.h" + +namespace ui { + +std::string TextEditCommandAuraLinux::GetCommandString() const { + std::string base_name; + switch (command_id_) { + case COPY: + base_name = "Copy"; + break; + case CUT: + base_name = "Cut"; + break; + case DELETE_BACKWARD: + base_name = "DeleteBackward"; + break; + case DELETE_FORWARD: + base_name = "DeleteForward"; + break; + case DELETE_TO_BEGINING_OF_LINE: + base_name = "DeleteToBeginningOfLine"; + break; + case DELETE_TO_BEGINING_OF_PARAGRAPH: + base_name = "DeleteToBeginningOfParagraph"; + break; + case DELETE_TO_END_OF_LINE: + base_name = "DeleteToEndOfLine"; + break; + case DELETE_TO_END_OF_PARAGRAPH: + base_name = "DeleteToEndOfParagraph"; + break; + case DELETE_WORD_BACKWARD: + base_name = "DeleteWordBackward"; + break; + case DELETE_WORD_FORWARD: + base_name = "DeleteWordForward"; + break; + case INSERT_TEXT: + base_name = "InsertText"; + break; + case MOVE_BACKWARD: + base_name = "MoveBackward"; + break; + case MOVE_DOWN: + base_name = "MoveDown"; + break; + case MOVE_FORWARD: + base_name = "MoveForward"; + break; + case MOVE_LEFT: + base_name = "MoveLeft"; + break; + case MOVE_PAGE_DOWN: + base_name = "MovePageDown"; + break; + case MOVE_PAGE_UP: + base_name = "MovePageUp"; + break; + case MOVE_RIGHT: + base_name = "MoveRight"; + break; + case MOVE_TO_BEGINING_OF_DOCUMENT: + base_name = "MoveToBeginningOfDocument"; + break; + case MOVE_TO_BEGINING_OF_LINE: + base_name = "MoveToBeginningOfLine"; + break; + case MOVE_TO_BEGINING_OF_PARAGRAPH: + base_name = "MoveToBeginningOfParagraph"; + break; + case MOVE_TO_END_OF_DOCUMENT: + base_name = "MoveToEndOfDocument"; + break; + case MOVE_TO_END_OF_LINE: + base_name = "MoveToEndOfLine"; + break; + case MOVE_TO_END_OF_PARAGRAPH: + base_name = "MoveToEndOfParagraph"; + break; + case MOVE_UP: + base_name = "MoveUp"; + break; + case MOVE_WORD_BACKWARD: + base_name = "MoveWordBackward"; + break; + case MOVE_WORD_FORWARD: + base_name = "MoveWordForward"; + break; + case MOVE_WORD_LEFT: + base_name = "MoveWordLeft"; + break; + case MOVE_WORD_RIGHT: + base_name = "MoveWordRight"; + break; + case PASTE: + base_name = "Paste"; + break; + case SELECT_ALL: + base_name = "SelectAll"; + break; + case SET_MARK: + base_name = "SetMark"; + break; + case UNSELECT: + base_name = "Unselect"; + break; + case INVALID_COMMAND: + NOTREACHED(); + return std::string(); + } + + if (extend_selection()) + base_name += "AndModifySelection"; + + return base_name; +} + +} // namespace ui diff --git a/chromium/ui/events/linux/text_edit_command_auralinux.h b/chromium/ui/events/linux/text_edit_command_auralinux.h new file mode 100644 index 00000000000..82d3af24306 --- /dev/null +++ b/chromium/ui/events/linux/text_edit_command_auralinux.h @@ -0,0 +1,82 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_ +#define UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_ + +#include <string> + +#include "ui/events/events_export.h" + +namespace ui { + +// Represents a command that performs a specific operation on text. +// Copy and assignment are explicitly allowed; these objects live in vectors. +class EVENTS_EXPORT TextEditCommandAuraLinux { + public: + enum CommandId { + COPY, + CUT, + DELETE_BACKWARD, + DELETE_FORWARD, + DELETE_TO_BEGINING_OF_LINE, + DELETE_TO_BEGINING_OF_PARAGRAPH, + DELETE_TO_END_OF_LINE, + DELETE_TO_END_OF_PARAGRAPH, + DELETE_WORD_BACKWARD, + DELETE_WORD_FORWARD, + INSERT_TEXT, + MOVE_BACKWARD, + MOVE_DOWN, + MOVE_FORWARD, + MOVE_LEFT, + MOVE_PAGE_DOWN, + MOVE_PAGE_UP, + MOVE_RIGHT, + MOVE_TO_BEGINING_OF_DOCUMENT, + MOVE_TO_BEGINING_OF_LINE, + MOVE_TO_BEGINING_OF_PARAGRAPH, + MOVE_TO_END_OF_DOCUMENT, + MOVE_TO_END_OF_LINE, + MOVE_TO_END_OF_PARAGRAPH, + MOVE_UP, + MOVE_WORD_BACKWARD, + MOVE_WORD_FORWARD, + MOVE_WORD_LEFT, + MOVE_WORD_RIGHT, + PASTE, + SELECT_ALL, + SET_MARK, + UNSELECT, + INVALID_COMMAND + }; + + TextEditCommandAuraLinux(CommandId command_id, + const std::string& argument, + bool extend_selection) + : command_id_(command_id), + argument_(argument), + extend_selection_(extend_selection) {} + + CommandId command_id() const { return command_id_; } + const std::string& argument() const { return argument_; } + bool extend_selection() const { return extend_selection_; } + + // We communicate these commands back to blink with a string representation. + // This will combine the base command name with "AndModifySelection" if we + // have |extend_selection_| set. + std::string GetCommandString() const; + + private: + CommandId command_id_; + + std::string argument_; + + // In addition to executing the command, modify the selection. + bool extend_selection_; +}; + +} // namespace ui + +#endif // UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_ diff --git a/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc new file mode 100644 index 00000000000..e6a941bcda4 --- /dev/null +++ b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc @@ -0,0 +1,23 @@ +// Copyright 2014 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/linux/text_edit_key_bindings_delegate_auralinux.h" + +namespace ui { + +namespace { +// Optional delegate. Unowned pointer. +TextEditKeyBindingsDelegateAuraLinux* text_edit_keybinding_delegate_ = 0; +} + +void SetTextEditKeyBindingsDelegate( + TextEditKeyBindingsDelegateAuraLinux* delegate) { + text_edit_keybinding_delegate_ = delegate; +} + +TextEditKeyBindingsDelegateAuraLinux* GetTextEditKeyBindingsDelegate() { + return text_edit_keybinding_delegate_; +} + +} // namespace ui diff --git a/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h new file mode 100644 index 00000000000..d2916260380 --- /dev/null +++ b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_ +#define UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_ + +#include <vector> + +#include "ui/events/events_export.h" + +namespace ui { +class Event; +class TextEditCommandAuraLinux; + +// An interface which can interpret various text editing commands out of key +// events. +// +// On desktop Linux, we've traditionally supported the user's custom +// keybindings. We need to support this in both content/ and in views/. +class EVENTS_EXPORT TextEditKeyBindingsDelegateAuraLinux { + public: + // Matches a key event against the users' platform specific key bindings, + // false will be returned if the key event doesn't correspond to a predefined + // key binding. Edit commands matched with |event| will be stored in + // |edit_commands|, if |edit_commands| is non-NULL. + virtual bool MatchEvent(const ui::Event& event, + std::vector<TextEditCommandAuraLinux>* commands) = 0; + + protected: + virtual ~TextEditKeyBindingsDelegateAuraLinux() {} +}; + +// Sets/Gets the global TextEditKeyBindingsDelegateAuraLinux. No ownership +// changes. Can be NULL. +EVENTS_EXPORT void SetTextEditKeyBindingsDelegate( + TextEditKeyBindingsDelegateAuraLinux* delegate); +EVENTS_EXPORT TextEditKeyBindingsDelegateAuraLinux* + GetTextEditKeyBindingsDelegate(); + +} // namespace ui + +#endif // UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_ diff --git a/chromium/ui/events/ozone/BUILD.gn b/chromium/ui/events/ozone/BUILD.gn new file mode 100644 index 00000000000..a5054b7eb7b --- /dev/null +++ b/chromium/ui/events/ozone/BUILD.gn @@ -0,0 +1,101 @@ +# Copyright 2014 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. + +import("//build/config/features.gni") +import("//build/config/ui.gni") + +component("events_ozone") { + sources = [ + "device/device_event.cc", + "device/device_event.h", + "device/device_event_observer.h", + "device/device_manager.cc", + "device/device_manager.h", + "device/device_manager_manual.cc", + "device/device_manager_manual.h", + "device/udev/device_manager_udev.cc", + "device/udev/device_manager_udev.h", + "event_factory_ozone.cc", + "event_factory_ozone.h", + "events_ozone_export.h", + ] + + deps = [ + "//base", + ] + + defines = [ + "EVENTS_OZONE_IMPLEMENTATION", + ] + + if (!use_udev) { + sources -= [ + "device/udev/device_manager_udev.cc", + "device/udev/device_manager_udev.h", + ] + } + + if (use_ozone_evdev && use_udev) { + deps += [ + "//device/udev_linux", + ] + } +} + +component("events_ozone_evdev") { + sources = [ + "evdev/event_converter_evdev.cc", + "evdev/event_converter_evdev.h", + "evdev/event_device_info.cc", + "evdev/event_device_info.h", + "evdev/event_factory_evdev.cc", + "evdev/event_factory_evdev.h", + "evdev/event_modifiers_evdev.cc", + "evdev/event_modifiers_evdev.h", + "evdev/events_ozone_evdev_export.h", + "evdev/key_event_converter_evdev.cc", + "evdev/key_event_converter_evdev.h", + "evdev/touch_event_converter_evdev.cc", + "evdev/touch_event_converter_evdev.h", + ] + + defines = [ + "EVENTS_OZONE_EVDEV_IMPLEMENTATION", + ] + + deps = [ + ":events_ozone", + "//base", + "//ui/events/platform", + "//ui/gfx", + ] + + if (use_ozone_evdev) { + defines += [ + "USE_OZONE_EVDEV=1" + ] + } + + if (use_ozone_evdev && use_evdev_gestures) { + sources += [ + "evdev/libgestures_glue/event_reader_libevdev_cros.cc", + "evdev/libgestures_glue/event_reader_libevdev_cros.h", + "evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc", + "evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h", + "evdev/libgestures_glue/gesture_logging.cc", + "evdev/libgestures_glue/gesture_logging.h", + "evdev/libgestures_glue/gesture_timer_provider.cc", + "evdev/libgestures_glue/gesture_timer_provider.h", + ] + + defines += [ + "USE_EVDEV_GESTURES", + ] + + configs += [ + "//build/config/linux:libevdev-cros", + "//build/config/linux:libgestures", + ] + } +} diff --git a/chromium/ui/events/ozone/DEPS b/chromium/ui/events/ozone/DEPS new file mode 100644 index 00000000000..3f271147d36 --- /dev/null +++ b/chromium/ui/events/ozone/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+device/udev_linux", + "+ui/ozone/public", +] diff --git a/chromium/ui/events/ozone/OWNERS b/chromium/ui/events/ozone/OWNERS index 77f21b5cae7..479c4d806cc 100644 --- a/chromium/ui/events/ozone/OWNERS +++ b/chromium/ui/events/ozone/OWNERS @@ -1 +1,2 @@ rjkroege@chromium.org +spang@chromium.org diff --git a/chromium/ui/events/ozone/device/device_event.cc b/chromium/ui/events/ozone/device/device_event.cc new file mode 100644 index 00000000000..e20079a79cf --- /dev/null +++ b/chromium/ui/events/ozone/device/device_event.cc @@ -0,0 +1,16 @@ +// Copyright 2014 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/ozone/device/device_event.h" + +namespace ui { + +DeviceEvent::DeviceEvent(DeviceType type, + ActionType action, + const base::FilePath& path) + : device_type_(type), + action_type_(action), + path_(path) {} + +} // namespace ui diff --git a/chromium/ui/events/ozone/device/device_event.h b/chromium/ui/events/ozone/device/device_event.h new file mode 100644 index 00000000000..a24394c75af --- /dev/null +++ b/chromium/ui/events/ozone/device/device_event.h @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_DEVICE_EVENT_H_ +#define UI_EVENTS_OZONE_DEVICE_EVENT_H_ + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "ui/events/ozone/events_ozone_export.h" + +namespace ui { + +class EVENTS_OZONE_EXPORT DeviceEvent { + public: + enum DeviceType { + INPUT, + DISPLAY, + }; + + enum ActionType { + ADD, + REMOVE, + CHANGE, + }; + + DeviceEvent(DeviceType type, ActionType action, const base::FilePath& path); + + DeviceType device_type() const { return device_type_; } + ActionType action_type() const { return action_type_; } + base::FilePath path() const { return path_; } + + private: + DeviceType device_type_; + ActionType action_type_; + base::FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(DeviceEvent); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_DEVICE_EVENT_H_ diff --git a/chromium/ui/events/ozone/device/device_event_observer.h b/chromium/ui/events/ozone/device/device_event_observer.h new file mode 100644 index 00000000000..7ad2f4966c1 --- /dev/null +++ b/chromium/ui/events/ozone/device/device_event_observer.h @@ -0,0 +1,24 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_ +#define UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_ + +#include "ui/events/ozone/events_ozone_export.h" + +namespace ui { + +class DeviceEvent; + +class EVENTS_OZONE_EXPORT DeviceEventObserver { + public: + virtual ~DeviceEventObserver() {} + + virtual void OnDeviceEvent(const DeviceEvent& event) = 0; +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_ + diff --git a/chromium/ui/events/ozone/device/device_manager.cc b/chromium/ui/events/ozone/device/device_manager.cc new file mode 100644 index 00000000000..bbd5d800fa5 --- /dev/null +++ b/chromium/ui/events/ozone/device/device_manager.cc @@ -0,0 +1,23 @@ +// Copyright 2014 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/ozone/device/device_manager.h" + +#if defined(USE_UDEV) +#include "ui/events/ozone/device/udev/device_manager_udev.h" +#else +#include "ui/events/ozone/device/device_manager_manual.h" +#endif + +namespace ui { + +scoped_ptr<DeviceManager> CreateDeviceManager() { +#if defined(USE_UDEV) + return scoped_ptr<DeviceManager>(new DeviceManagerUdev()); +#else + return scoped_ptr<DeviceManager>(new DeviceManagerManual()); +#endif +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/device/device_manager.h b/chromium/ui/events/ozone/device/device_manager.h new file mode 100644 index 00000000000..0551e7ecf8b --- /dev/null +++ b/chromium/ui/events/ozone/device/device_manager.h @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_ +#define UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/ozone/events_ozone_export.h" + +namespace ui { + +class DeviceEventObserver; + +class EVENTS_OZONE_EXPORT DeviceManager { + public: + virtual ~DeviceManager() {} + + // Scans the currently available devices and notifies |observer| for each + // device found. If also registering for notifications through AddObserver(), + // the scan should happen after the registration otherwise the observer may + // miss events. + virtual void ScanDevices(DeviceEventObserver* observer) = 0; + + // Registers |observer| for device event notifications. + virtual void AddObserver(DeviceEventObserver* observer) = 0; + + // Removes |observer| from the list of observers notified. + virtual void RemoveObserver(DeviceEventObserver* observer) = 0; +}; + +EVENTS_OZONE_EXPORT scoped_ptr<DeviceManager> CreateDeviceManager(); + +} // namespace ui + +#endif // UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_ diff --git a/chromium/ui/events/ozone/device/device_manager_manual.cc b/chromium/ui/events/ozone/device/device_manager_manual.cc new file mode 100644 index 00000000000..c86ffccf62b --- /dev/null +++ b/chromium/ui/events/ozone/device/device_manager_manual.cc @@ -0,0 +1,33 @@ +// Copyright 2014 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/ozone/device/device_manager_manual.h" + +#include "base/files/file_enumerator.h" +#include "ui/events/ozone/device/device_event.h" +#include "ui/events/ozone/device/device_event_observer.h" + +namespace ui { + +DeviceManagerManual::DeviceManagerManual() {} + +DeviceManagerManual::~DeviceManagerManual() {} + +void DeviceManagerManual::ScanDevices(DeviceEventObserver* observer) { + base::FileEnumerator file_enum(base::FilePath("/dev/input"), + false, + base::FileEnumerator::FILES, + "event*[0-9]"); + for (base::FilePath path = file_enum.Next(); !path.empty(); + path = file_enum.Next()) { + DeviceEvent event(DeviceEvent::INPUT, DeviceEvent::ADD, path); + observer->OnDeviceEvent(event); + } +} + +void DeviceManagerManual::AddObserver(DeviceEventObserver* observer) {} + +void DeviceManagerManual::RemoveObserver(DeviceEventObserver* observer) {} + +} // namespace ui diff --git a/chromium/ui/events/ozone/device/device_manager_manual.h b/chromium/ui/events/ozone/device/device_manager_manual.h new file mode 100644 index 00000000000..9d1cf61d9d7 --- /dev/null +++ b/chromium/ui/events/ozone/device/device_manager_manual.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_ +#define UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_ + +#include "base/macros.h" +#include "ui/events/ozone/device/device_manager.h" + +namespace ui { + +class DeviceManagerManual : public DeviceManager { + public: + DeviceManagerManual(); + virtual ~DeviceManagerManual(); + + private: + // DeviceManager overrides: + virtual void ScanDevices(DeviceEventObserver* observer) OVERRIDE; + virtual void AddObserver(DeviceEventObserver* observer) OVERRIDE; + virtual void RemoveObserver(DeviceEventObserver* observer) OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(DeviceManagerManual); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_ diff --git a/chromium/ui/events/ozone/device/udev/device_manager_udev.cc b/chromium/ui/events/ozone/device/udev/device_manager_udev.cc new file mode 100644 index 00000000000..7f86bee4f84 --- /dev/null +++ b/chromium/ui/events/ozone/device/udev/device_manager_udev.cc @@ -0,0 +1,186 @@ +// Copyright 2014 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/ozone/device/udev/device_manager_udev.h" + +#include <libudev.h> + +#include "base/debug/trace_event.h" +#include "base/strings/stringprintf.h" +#include "ui/events/ozone/device/device_event.h" +#include "ui/events/ozone/device/device_event_observer.h" + +namespace ui { + +namespace { + +const char* kSubsystems[] = { + "input", + "drm", +}; + +// Severity levels from syslog.h. We can't include it directly as it +// conflicts with base/logging.h +enum { + SYS_LOG_EMERG = 0, + SYS_LOG_ALERT = 1, + SYS_LOG_CRIT = 2, + SYS_LOG_ERR = 3, + SYS_LOG_WARNING = 4, + SYS_LOG_NOTICE = 5, + SYS_LOG_INFO = 6, + SYS_LOG_DEBUG = 7, +}; + +// Log handler for messages generated from libudev. +void UdevLog(struct udev* udev, + int priority, + const char* file, + int line, + const char* fn, + const char* format, + va_list args) { + if (priority <= SYS_LOG_ERR) + LOG(ERROR) << "libudev: " << fn << ": " << base::StringPrintV(format, args); + else if (priority <= SYS_LOG_INFO) + VLOG(1) << "libudev: " << fn << ": " << base::StringPrintV(format, args); + else // SYS_LOG_DEBUG + VLOG(2) << "libudev: " << fn << ": " << base::StringPrintV(format, args); +} + +// Create libudev context. +device::ScopedUdevPtr UdevCreate() { + struct udev* udev = udev_new(); + if (udev) { + udev_set_log_fn(udev, UdevLog); + udev_set_log_priority(udev, SYS_LOG_DEBUG); + } + return device::ScopedUdevPtr(udev); +} + +// Start monitoring input device changes. +device::ScopedUdevMonitorPtr UdevCreateMonitor(struct udev* udev) { + struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (monitor) { + for (size_t i = 0; i < arraysize(kSubsystems); ++i) + udev_monitor_filter_add_match_subsystem_devtype( + monitor, kSubsystems[i], NULL); + + if (udev_monitor_enable_receiving(monitor)) + LOG(ERROR) << "Failed to start receiving events from udev"; + } else { + LOG(ERROR) << "Failed to create udev monitor"; + } + + return device::ScopedUdevMonitorPtr(monitor); +} + +} // namespace + +DeviceManagerUdev::DeviceManagerUdev() : udev_(UdevCreate()) { +} + +DeviceManagerUdev::~DeviceManagerUdev() { +} + +void DeviceManagerUdev::CreateMonitor() { + if (monitor_) + return; + monitor_ = UdevCreateMonitor(udev_.get()); + if (monitor_) { + int fd = udev_monitor_get_fd(monitor_.get()); + CHECK_GT(fd, 0); + base::MessageLoopForUI::current()->WatchFileDescriptor( + fd, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this); + } +} + +void DeviceManagerUdev::ScanDevices(DeviceEventObserver* observer) { + CreateMonitor(); + + device::ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get())); + if (!enumerate) + return; + + for (size_t i = 0; i < arraysize(kSubsystems); ++i) + udev_enumerate_add_match_subsystem(enumerate.get(), kSubsystems[i]); + udev_enumerate_scan_devices(enumerate.get()); + + struct udev_list_entry* devices = + udev_enumerate_get_list_entry(enumerate.get()); + struct udev_list_entry* entry; + + udev_list_entry_foreach(entry, devices) { + device::ScopedUdevDevicePtr device(udev_device_new_from_syspath( + udev_.get(), udev_list_entry_get_name(entry))); + if (!device) + continue; + + scoped_ptr<DeviceEvent> event = ProcessMessage(device.get()); + if (event) + observer->OnDeviceEvent(*event.get()); + } +} + +void DeviceManagerUdev::AddObserver(DeviceEventObserver* observer) { + observers_.AddObserver(observer); +} + +void DeviceManagerUdev::RemoveObserver(DeviceEventObserver* observer) { + observers_.RemoveObserver(observer); +} + +void DeviceManagerUdev::OnFileCanReadWithoutBlocking(int fd) { + // The netlink socket should never become disconnected. There's no need + // to handle broken connections here. + TRACE_EVENT1("ozone", "UdevDeviceChange", "socket", fd); + + device::ScopedUdevDevicePtr device( + udev_monitor_receive_device(monitor_.get())); + if (!device) + return; + + scoped_ptr<DeviceEvent> event = ProcessMessage(device.get()); + if (event) + FOR_EACH_OBSERVER( + DeviceEventObserver, observers_, OnDeviceEvent(*event.get())); +} + +void DeviceManagerUdev::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +scoped_ptr<DeviceEvent> DeviceManagerUdev::ProcessMessage(udev_device* device) { + const char* path = udev_device_get_devnode(device); + const char* action = udev_device_get_action(device); + const char* hotplug = udev_device_get_property_value(device, "HOTPLUG"); + const char* subsystem = udev_device_get_property_value(device, "SUBSYSTEM"); + + if (!path || !subsystem) + return scoped_ptr<DeviceEvent>(); + + DeviceEvent::DeviceType device_type; + if (!strcmp(subsystem, "input") && + StartsWithASCII(path, "/dev/input/event", true)) + device_type = DeviceEvent::INPUT; + else if (!strcmp(subsystem, "drm") && hotplug && !strcmp(hotplug, "1")) + device_type = DeviceEvent::DISPLAY; + else + return scoped_ptr<DeviceEvent>(); + + DeviceEvent::ActionType action_type; + if (!action || !strcmp(action, "add")) + action_type = DeviceEvent::ADD; + else if (!strcmp(action, "remove")) + action_type = DeviceEvent::REMOVE; + else if (!strcmp(action, "change")) + action_type = DeviceEvent::CHANGE; + else + return scoped_ptr<DeviceEvent>(); + + return scoped_ptr<DeviceEvent>( + new DeviceEvent(device_type, action_type, base::FilePath(path))); +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/device/udev/device_manager_udev.h b/chromium/ui/events/ozone/device/udev/device_manager_udev.h new file mode 100644 index 00000000000..8a7537abebe --- /dev/null +++ b/chromium/ui/events/ozone/device/udev/device_manager_udev.h @@ -0,0 +1,51 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_ +#define UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_ + +#include "base/message_loop/message_pump_libevent.h" +#include "base/observer_list.h" +#include "device/udev_linux/udev.h" +#include "ui/events/ozone/device/device_manager.h" + +namespace ui { + +class DeviceEvent; +class DeviceEventObserver; + +class DeviceManagerUdev + : public DeviceManager, base::MessagePumpLibevent::Watcher { + public: + DeviceManagerUdev(); + virtual ~DeviceManagerUdev(); + + private: + scoped_ptr<DeviceEvent> ProcessMessage(udev_device* device); + + // Creates a device-monitor to look for device add/remove/change events. + void CreateMonitor(); + + // DeviceManager overrides: + virtual void ScanDevices(DeviceEventObserver* observer) OVERRIDE; + virtual void AddObserver(DeviceEventObserver* observer) OVERRIDE; + virtual void RemoveObserver(DeviceEventObserver* observer) OVERRIDE; + + // base::MessagePumpLibevent::Watcher overrides: + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + device::ScopedUdevPtr udev_; + device::ScopedUdevMonitorPtr monitor_; + + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + ObserverList<DeviceEventObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(DeviceManagerUdev); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h b/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h new file mode 100644 index 00000000000..7ff01befd9e --- /dev/null +++ b/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h @@ -0,0 +1,31 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_ + +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Vector2dF; +} + +namespace ui { + +class EVENTS_OZONE_EVDEV_EXPORT CursorDelegateEvdev { + public: + // Move the cursor. + virtual void MoveCursor(const gfx::Vector2dF& delta) = 0; + virtual void MoveCursorTo(gfx::AcceleratedWidget widget, + const gfx::PointF& location) = 0; + + // Location in window. + virtual gfx::PointF location() = 0; +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/event_converter_evdev.cc b/chromium/ui/events/ozone/evdev/event_converter_evdev.cc new file mode 100644 index 00000000000..5b1e9a58981 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/event_converter_evdev.cc @@ -0,0 +1,23 @@ +// Copyright 2014 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/ozone/evdev/event_converter_evdev.h" + +#include "ui/events/event.h" +#include "ui/ozone/public/event_factory_ozone.h" + +namespace ui { + +EventConverterEvdev::EventConverterEvdev() {} + +EventConverterEvdev::EventConverterEvdev(const EventDispatchCallback& callback) + : dispatch_callback_(callback) {} + +EventConverterEvdev::~EventConverterEvdev() {} + +void EventConverterEvdev::DispatchEventToCallback(ui::Event* event) { + dispatch_callback_.Run(event); +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/event_converter_evdev.h b/chromium/ui/events/ozone/evdev/event_converter_evdev.h new file mode 100644 index 00000000000..ebc36fcc5a2 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/event_converter_evdev.h @@ -0,0 +1,46 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_ + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" + +namespace ui { + +class Event; +class EventModifiersEvdev; + +typedef base::Callback<void(Event*)> EventDispatchCallback; + +// Base class for device-specific evdev event conversion. +class EVENTS_OZONE_EVDEV_EXPORT EventConverterEvdev { + public: + EventConverterEvdev(); + explicit EventConverterEvdev(const EventDispatchCallback& callback); + virtual ~EventConverterEvdev(); + + // Start converting events. + virtual void Start() = 0; + + // Stop converting events. + virtual void Stop() = 0; + + protected: + // Dispatches an event using the dispatch-callback set using + // |SetDispatchCalback()|. + virtual void DispatchEventToCallback(ui::Event* event); + + private: + EventDispatchCallback dispatch_callback_; + + DISALLOW_COPY_AND_ASSIGN(EventConverterEvdev); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/event_device_info.cc b/chromium/ui/events/ozone/evdev/event_device_info.cc index 9621646d26b..2734b9d2f49 100644 --- a/chromium/ui/events/ozone/evdev/event_device_info.cc +++ b/chromium/ui/events/ozone/evdev/event_device_info.cc @@ -14,8 +14,6 @@ namespace ui { namespace { bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) { - base::ThreadRestrictions::AssertIOAllowed(); - if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) { DLOG(ERROR) << "failed EVIOCGBIT(" << type << ", " << size << ") on fd " << fd; @@ -26,8 +24,6 @@ bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) { } bool GetPropBits(int fd, void* buf, unsigned int size) { - base::ThreadRestrictions::AssertIOAllowed(); - if (ioctl(fd, EVIOCGPROP(size), buf) < 0) { DLOG(ERROR) << "failed EVIOCGPROP(" << size << ") on fd " << fd; return false; @@ -40,6 +36,14 @@ bool BitIsSet(const unsigned long* bits, unsigned int bit) { return (bits[bit / EVDEV_LONG_BITS] & (1UL << (bit % EVDEV_LONG_BITS))); } +bool GetAbsInfo(int fd, int code, struct input_absinfo* absinfo) { + if (ioctl(fd, EVIOCGABS(code), absinfo)) { + DLOG(ERROR) << "failed EVIOCGABS(" << code << ") on fd " << fd; + return false; + } + return true; +} + } // namespace EventDeviceInfo::EventDeviceInfo() { @@ -51,6 +55,7 @@ EventDeviceInfo::EventDeviceInfo() { memset(sw_bits_, 0, sizeof(sw_bits_)); memset(led_bits_, 0, sizeof(led_bits_)); memset(prop_bits_, 0, sizeof(prop_bits_)); + memset(abs_info_, 0, sizeof(abs_info_)); } EventDeviceInfo::~EventDeviceInfo() {} @@ -80,6 +85,11 @@ bool EventDeviceInfo::Initialize(int fd) { if (!GetPropBits(fd, prop_bits_, sizeof(prop_bits_))) return false; + for (unsigned int i = 0; i < ABS_CNT; ++i) + if (HasAbsEvent(i)) + if (!GetAbsInfo(fd, i, &abs_info_[i])) + return false; + return true; } @@ -131,4 +141,49 @@ bool EventDeviceInfo::HasProp(unsigned int code) const { return BitIsSet(prop_bits_, code); } +int32 EventDeviceInfo::GetAbsMinimum(unsigned int code) const { + return abs_info_[code].minimum; +} + +int32 EventDeviceInfo::GetAbsMaximum(unsigned int code) const { + return abs_info_[code].maximum; +} + +bool EventDeviceInfo::HasAbsXY() const { + if (HasAbsEvent(ABS_X) && HasAbsEvent(ABS_Y)) + return true; + + if (HasAbsEvent(ABS_MT_POSITION_X) && HasAbsEvent(ABS_MT_POSITION_Y)) + return true; + + return false; +} + +bool EventDeviceInfo::HasRelXY() const { + return HasRelEvent(REL_X) && HasRelEvent(REL_Y); +} + +bool EventDeviceInfo::IsMappedToScreen() const { + // Device position is mapped directly to the screen. + if (HasProp(INPUT_PROP_DIRECT)) + return true; + + // Device position moves the cursor. + if (HasProp(INPUT_PROP_POINTER)) + return false; + + // Tablets are mapped to the screen. + if (HasKeyEvent(BTN_TOOL_PEN) || HasKeyEvent(BTN_STYLUS) || + HasKeyEvent(BTN_STYLUS2)) + return true; + + // Touchpads are not mapped to the screen. + if (HasKeyEvent(BTN_LEFT) || HasKeyEvent(BTN_MIDDLE) || + HasKeyEvent(BTN_RIGHT) || HasKeyEvent(BTN_TOOL_FINGER)) + return false; + + // Touchscreens are mapped to the screen. + return true; +} + } // namespace ui diff --git a/chromium/ui/events/ozone/evdev/event_device_info.h b/chromium/ui/events/ozone/evdev/event_device_info.h index b4b1c05c233..492539bd83a 100644 --- a/chromium/ui/events/ozone/evdev/event_device_info.h +++ b/chromium/ui/events/ozone/evdev/event_device_info.h @@ -9,6 +9,7 @@ #include <linux/input.h> #include "base/basictypes.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" #define EVDEV_LONG_BITS (CHAR_BIT * sizeof(long)) #define EVDEV_BITS_TO_LONGS(x) (((x) + EVDEV_LONG_BITS - 1) / EVDEV_LONG_BITS) @@ -19,7 +20,7 @@ namespace ui { // // This stores and queries information about input devices; in // particular it knows which events the device can generate. -class EventDeviceInfo { +class EVENTS_OZONE_EVDEV_EXPORT EventDeviceInfo { public: EventDeviceInfo(); ~EventDeviceInfo(); @@ -36,9 +37,23 @@ class EventDeviceInfo { bool HasSwEvent(unsigned int code) const; bool HasLedEvent(unsigned int code) const; + // Properties of absolute axes. + int32 GetAbsMinimum(unsigned int code) const; + int32 GetAbsMaximum(unsigned int code) const; + // Check input device properties. bool HasProp(unsigned int code) const; + // Has absolute X & Y axes. + bool HasAbsXY() const; + + // Has relativeX & Y axes. + bool HasRelXY() const; + + // Determine whether absolute device X/Y coordinates are mapped onto the + // screen. This is the case for touchscreens and tablets but not touchpads. + bool IsMappedToScreen() const; + private: unsigned long ev_bits_[EVDEV_BITS_TO_LONGS(EV_CNT)]; unsigned long key_bits_[EVDEV_BITS_TO_LONGS(KEY_CNT)]; @@ -49,6 +64,8 @@ class EventDeviceInfo { unsigned long led_bits_[EVDEV_BITS_TO_LONGS(LED_CNT)]; unsigned long prop_bits_[EVDEV_BITS_TO_LONGS(INPUT_PROP_CNT)]; + struct input_absinfo abs_info_[ABS_CNT]; + DISALLOW_COPY_AND_ASSIGN(EventDeviceInfo); }; diff --git a/chromium/ui/events/ozone/evdev/event_factory.cc b/chromium/ui/events/ozone/evdev/event_factory.cc deleted file mode 100644 index 9c3f9713730..00000000000 --- a/chromium/ui/events/ozone/evdev/event_factory.cc +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2013 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/ozone/evdev/event_factory.h" - -#include <errno.h> -#include <fcntl.h> -#include <linux/input.h> -#include <poll.h> -#include <unistd.h> - -#include "base/strings/stringprintf.h" -#include "ui/events/ozone/evdev/event_device_info.h" -#include "ui/events/ozone/evdev/key_event_converter.h" -#include "ui/events/ozone/evdev/touch_event_converter.h" -#include "ui/events/ozone/event_factory_ozone.h" - -namespace ui { - -namespace { - -bool IsTouchPad(const EventDeviceInfo& devinfo) { - if (!devinfo.HasEventType(EV_ABS)) - return false; - - return devinfo.HasKeyEvent(BTN_LEFT) || devinfo.HasKeyEvent(BTN_MIDDLE) || - devinfo.HasKeyEvent(BTN_RIGHT) || devinfo.HasKeyEvent(BTN_TOOL_FINGER); -} - -bool IsTouchScreen(const EventDeviceInfo& devinfo) { - return devinfo.HasEventType(EV_ABS) && !IsTouchPad(devinfo); -} - -} // namespace - -EventFactoryEvdev::EventFactoryEvdev() {} - -EventFactoryEvdev::~EventFactoryEvdev() {} - -void EventFactoryEvdev::StartProcessingEvents() { - // The number of devices in the directory is unknown without reading - // the contents of the directory. Further, with hot-plugging, the entries - // might decrease during the execution of this loop. So exciting from the - // loop on the first failure of open below is both cheaper and more - // reliable. - for (int id = 0; true; id++) { - std::string path = base::StringPrintf("/dev/input/event%d", id); - int fd = open(path.c_str(), O_RDONLY | O_NONBLOCK); - if (fd < 0) { - DLOG(ERROR) << "Cannot open '" << path << "': " << strerror(errno); - break; - } - - EventDeviceInfo devinfo; - if (!devinfo.Initialize(fd)) { - DLOG(ERROR) << "failed to get device information for " << path; - close(fd); - continue; - } - - if (IsTouchPad(devinfo)) { - LOG(WARNING) << "touchpad device not supported: " << path; - close(fd); - continue; - } - - scoped_ptr<EventConverterOzone> converter; - // TODO(rjkroege) Add more device types. Support hot-plugging. - if (IsTouchScreen(devinfo)) - converter.reset(new TouchEventConverterEvdev(fd, id)); - else if (devinfo.HasEventType(EV_KEY)) - converter.reset(new KeyEventConverterEvdev(fd, id, &modifiers_)); - - if (converter) { - AddEventConverter(fd, converter.Pass()); - } else { - close(fd); - } - } -} - -} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/event_factory.h b/chromium/ui/events/ozone/evdev/event_factory.h deleted file mode 100644 index b0e7a0a633f..00000000000 --- a/chromium/ui/events/ozone/evdev/event_factory.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2013 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. - -#ifndef UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_ -#define UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_ - -#include "base/compiler_specific.h" -#include "ui/events/events_export.h" -#include "ui/events/ozone/evdev/event_modifiers.h" -#include "ui/events/ozone/event_factory_ozone.h" - -namespace ui { - -// Ozone events implementation for the Linux input subsystem ("evdev"). -class EVENTS_EXPORT EventFactoryEvdev : public EventFactoryOzone { - public: - EventFactoryEvdev(); - virtual ~EventFactoryEvdev(); - - virtual void StartProcessingEvents() OVERRIDE; - - private: - EventModifiersEvdev modifiers_; - - DISALLOW_COPY_AND_ASSIGN(EventFactoryEvdev); -}; - -} // namespace ui - -#endif // UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/event_factory_evdev.cc b/chromium/ui/events/ozone/evdev/event_factory_evdev.cc new file mode 100644 index 00000000000..216488ee5b0 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/event_factory_evdev.cc @@ -0,0 +1,242 @@ +// Copyright 2014 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/ozone/evdev/event_factory_evdev.h" + +#include <fcntl.h> +#include <linux/input.h> + +#include "base/debug/trace_event.h" +#include "base/stl_util.h" +#include "base/task_runner.h" +#include "base/threading/worker_pool.h" +#include "ui/events/ozone/device/device_event.h" +#include "ui/events/ozone/device/device_manager.h" +#include "ui/events/ozone/evdev/cursor_delegate_evdev.h" +#include "ui/events/ozone/evdev/event_device_info.h" +#include "ui/events/ozone/evdev/key_event_converter_evdev.h" +#include "ui/events/ozone/evdev/touch_event_converter_evdev.h" + +#if defined(USE_EVDEV_GESTURES) +#include "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h" +#include "ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h" +#endif + +#ifndef EVIOCSCLOCKID +#define EVIOCSCLOCKID _IOW('E', 0xa0, int) +#endif + +namespace ui { + +namespace { + +#if defined(USE_EVDEV_GESTURES) +bool UseGesturesLibraryForDevice(const EventDeviceInfo& devinfo) { + if (devinfo.HasAbsXY() && !devinfo.IsMappedToScreen()) + return true; // touchpad + + if (devinfo.HasRelXY()) + return true; // mouse + + return false; +} +#endif + +scoped_ptr<EventConverterEvdev> CreateConverter( + int fd, + const base::FilePath& path, + const EventDeviceInfo& devinfo, + const EventDispatchCallback& dispatch, + EventModifiersEvdev* modifiers, + CursorDelegateEvdev* cursor) { +#if defined(USE_EVDEV_GESTURES) + // Touchpad or mouse: use gestures library. + // EventReaderLibevdevCros -> GestureInterpreterLibevdevCros -> DispatchEvent + if (UseGesturesLibraryForDevice(devinfo)) { + scoped_ptr<GestureInterpreterLibevdevCros> gesture_interp = make_scoped_ptr( + new GestureInterpreterLibevdevCros(modifiers, cursor, dispatch)); + scoped_ptr<EventReaderLibevdevCros> libevdev_reader = + make_scoped_ptr(new EventReaderLibevdevCros( + fd, + path, + gesture_interp.PassAs<EventReaderLibevdevCros::Delegate>())); + return libevdev_reader.PassAs<EventConverterEvdev>(); + } +#endif + + // Touchscreen: use TouchEventConverterEvdev. + scoped_ptr<EventConverterEvdev> converter; + if (devinfo.HasAbsXY()) + return make_scoped_ptr<EventConverterEvdev>( + new TouchEventConverterEvdev(fd, path, devinfo, dispatch)); + + // Everything else: use KeyEventConverterEvdev. + return make_scoped_ptr<EventConverterEvdev>( + new KeyEventConverterEvdev(fd, path, modifiers, dispatch)); +} + +// Open an input device. Opening may put the calling thread to sleep, and +// therefore should be run on a thread where latency is not critical. We +// run it on a thread from the worker pool. +// +// This takes a TaskRunner and runs the reply on that thread, so that we +// can hop threads if necessary (back to the UI thread). +void OpenInputDevice( + const base::FilePath& path, + EventModifiersEvdev* modifiers, + CursorDelegateEvdev* cursor, + scoped_refptr<base::TaskRunner> reply_runner, + const EventDispatchCallback& dispatch, + base::Callback<void(scoped_ptr<EventConverterEvdev>)> reply_callback) { + TRACE_EVENT1("ozone", "OpenInputDevice", "path", path.value()); + + int fd = open(path.value().c_str(), O_RDONLY | O_NONBLOCK); + if (fd < 0) { + PLOG(ERROR) << "Cannot open '" << path.value(); + return; + } + + // Use monotonic timestamps for events. The touch code in particular + // expects event timestamps to correlate to the monotonic clock + // (base::TimeTicks). + unsigned int clk = CLOCK_MONOTONIC; + if (ioctl(fd, EVIOCSCLOCKID, &clk)) + PLOG(ERROR) << "failed to set CLOCK_MONOTONIC"; + + EventDeviceInfo devinfo; + if (!devinfo.Initialize(fd)) { + LOG(ERROR) << "failed to get device information for " << path.value(); + close(fd); + return; + } + + scoped_ptr<EventConverterEvdev> converter = + CreateConverter(fd, path, devinfo, dispatch, modifiers, cursor); + + // Reply with the constructed converter. + reply_runner->PostTask(FROM_HERE, + base::Bind(reply_callback, base::Passed(&converter))); +} + +// Close an input device. Closing may put the calling thread to sleep, and +// therefore should be run on a thread where latency is not critical. We +// run it on the FILE thread. +void CloseInputDevice(const base::FilePath& path, + scoped_ptr<EventConverterEvdev> converter) { + TRACE_EVENT1("ozone", "CloseInputDevice", "path", path.value()); + converter.reset(); +} + +} // namespace + +EventFactoryEvdev::EventFactoryEvdev( + CursorDelegateEvdev* cursor, + DeviceManager* device_manager) + : device_manager_(device_manager), + cursor_(cursor), + dispatch_callback_( + base::Bind(base::IgnoreResult(&EventFactoryEvdev::DispatchUiEvent), + base::Unretained(this))), + weak_ptr_factory_(this) { + CHECK(device_manager_); +} + +EventFactoryEvdev::~EventFactoryEvdev() { STLDeleteValues(&converters_); } + +void EventFactoryEvdev::DispatchUiEvent(Event* event) { + DispatchEvent(event); +} + +void EventFactoryEvdev::AttachInputDevice( + const base::FilePath& path, + scoped_ptr<EventConverterEvdev> converter) { + TRACE_EVENT1("ozone", "AttachInputDevice", "path", path.value()); + CHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + // If we have an existing device, detach it. We don't want two + // devices with the same name open at the same time. + if (converters_[path]) + DetachInputDevice(path); + + // Add initialized device to map. + converters_[path] = converter.release(); + converters_[path]->Start(); +} + +void EventFactoryEvdev::OnDeviceEvent(const DeviceEvent& event) { + if (event.device_type() != DeviceEvent::INPUT) + return; + + switch (event.action_type()) { + case DeviceEvent::ADD: + case DeviceEvent::CHANGE: { + TRACE_EVENT1("ozone", "OnDeviceAdded", "path", event.path().value()); + + // Dispatch task to open from the worker pool, since open may block. + base::WorkerPool::PostTask( + FROM_HERE, + base::Bind(&OpenInputDevice, + event.path(), + &modifiers_, + cursor_, + ui_task_runner_, + dispatch_callback_, + base::Bind(&EventFactoryEvdev::AttachInputDevice, + weak_ptr_factory_.GetWeakPtr(), + event.path())), + true); + } + break; + case DeviceEvent::REMOVE: { + TRACE_EVENT1("ozone", "OnDeviceRemoved", "path", event.path().value()); + DetachInputDevice(event.path()); + } + break; + } +} + +void EventFactoryEvdev::OnDispatcherListChanged() { + if (!ui_task_runner_) { + ui_task_runner_ = base::MessageLoopProxy::current(); + // Scan & monitor devices. + device_manager_->AddObserver(this); + device_manager_->ScanDevices(this); + } +} + +void EventFactoryEvdev::DetachInputDevice(const base::FilePath& path) { + TRACE_EVENT1("ozone", "DetachInputDevice", "path", path.value()); + CHECK(ui_task_runner_->RunsTasksOnCurrentThread()); + + // Remove device from map. + scoped_ptr<EventConverterEvdev> converter(converters_[path]); + converters_.erase(path); + + if (converter) { + // Cancel libevent notifications from this converter. This part must be + // on UI since the polling happens on UI. + converter->Stop(); + + // Dispatch task to close from the worker pool, since close may block. + base::WorkerPool::PostTask( + FROM_HERE, + base::Bind(&CloseInputDevice, path, base::Passed(&converter)), + true); + } +} + +void EventFactoryEvdev::WarpCursorTo(gfx::AcceleratedWidget widget, + const gfx::PointF& location) { + if (cursor_) { + cursor_->MoveCursorTo(widget, location); + MouseEvent mouse_event(ET_MOUSE_MOVED, + cursor_->location(), + cursor_->location(), + modifiers_.GetModifierFlags(), + /* changed_button_flags */ 0); + DispatchEvent(&mouse_event); + } +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/event_factory_evdev.h b/chromium/ui/events/ozone/evdev/event_factory_evdev.h new file mode 100644 index 00000000000..3eb11d23822 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/event_factory_evdev.h @@ -0,0 +1,82 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_ + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "ui/events/ozone/device/device_event_observer.h" +#include "ui/events/ozone/evdev/event_converter_evdev.h" +#include "ui/events/ozone/evdev/event_modifiers_evdev.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/ozone/public/event_factory_ozone.h" + +namespace ui { + +class CursorDelegateEvdev; +class DeviceManager; + +// Ozone events implementation for the Linux input subsystem ("evdev"). +class EVENTS_OZONE_EVDEV_EXPORT EventFactoryEvdev : public EventFactoryOzone, + public DeviceEventObserver, + public PlatformEventSource { + public: + EventFactoryEvdev(CursorDelegateEvdev* cursor, + DeviceManager* device_manager); + virtual ~EventFactoryEvdev(); + + void DispatchUiEvent(Event* event); + + // EventFactoryOzone: + virtual void WarpCursorTo(gfx::AcceleratedWidget widget, + const gfx::PointF& location) OVERRIDE; + + private: + // Open device at path & starting processing events (on UI thread). + void AttachInputDevice(const base::FilePath& file_path, + scoped_ptr<EventConverterEvdev> converter); + + // Close device at path (on UI thread). + void DetachInputDevice(const base::FilePath& file_path); + + // DeviceEventObserver overrides: + // + // Callback for device add (on UI thread). + virtual void OnDeviceEvent(const DeviceEvent& event) OVERRIDE; + + // PlatformEventSource: + virtual void OnDispatcherListChanged() OVERRIDE; + + // Owned per-device event converters (by path). + std::map<base::FilePath, EventConverterEvdev*> converters_; + + // Interface for scanning & monitoring input devices. + DeviceManager* device_manager_; // Not owned. + + // Task runner for event dispatch. + scoped_refptr<base::TaskRunner> ui_task_runner_; + + // Modifier key state (shift, ctrl, etc). + EventModifiersEvdev modifiers_; + + // Cursor movement. + CursorDelegateEvdev* cursor_; + + // Dispatch callback for events. + EventDispatchCallback dispatch_callback_; + + // Support weak pointers for attach & detach callbacks. + base::WeakPtrFactory<EventFactoryEvdev> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(EventFactoryEvdev); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/event_modifiers.cc b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.cc index 220ec083c3b..d867b5eef7d 100644 --- a/chromium/ui/events/ozone/evdev/event_modifiers.cc +++ b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.cc @@ -1,8 +1,8 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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/ozone/evdev/event_modifiers.h" +#include "ui/events/ozone/evdev/event_modifiers_evdev.h" #include <linux/input.h> @@ -71,4 +71,9 @@ void EventModifiersEvdev::UpdateFlags(unsigned int modifier) { int EventModifiersEvdev::GetModifierFlags() { return modifier_flags_; } +// static +int EventModifiersEvdev::GetEventFlagFromModifier(unsigned int modifier) { + return kEventFlagFromModifiers[modifier]; +} + } // namespace ui diff --git a/chromium/ui/events/ozone/evdev/event_modifiers.h b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.h index 225af026fbc..d10bb8cc4db 100644 --- a/chromium/ui/events/ozone/evdev/event_modifiers.h +++ b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.h @@ -1,12 +1,12 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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. -#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_H_ -#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_H_ +#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_ -#include "ui/events/events_export.h" -#include "ui/events/ozone/event_converter_ozone.h" +#include "base/basictypes.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" namespace ui { @@ -39,7 +39,7 @@ enum { // currently pressed. However some keys toggle a persistent "lock" for the // modifier instead, such as CapsLock. If a modifier is "locked" then its state // is inverted until it is unlocked. -class EVENTS_EXPORT EventModifiersEvdev { +class EVENTS_OZONE_EVDEV_EXPORT EventModifiersEvdev { public: EventModifiersEvdev(); ~EventModifiersEvdev(); @@ -53,6 +53,9 @@ class EVENTS_EXPORT EventModifiersEvdev { // Return current flags to use for incoming events. int GetModifierFlags(); + // Return the mask for the specified modifier. + static int GetEventFlagFromModifier(unsigned int modifier); + private: // Count of keys pressed for each modifier. int modifiers_down_[EVDEV_NUM_MODIFIERS]; @@ -71,4 +74,4 @@ class EVENTS_EXPORT EventModifiersEvdev { } // namspace ui -#endif // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_H_ +#endif // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h b/chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h new file mode 100644 index 00000000000..4a4cbacee64 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_ +#define UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION) +#define EVENTS_OZONE_EVDEV_EXPORT __declspec(dllexport) +#else +#define EVENTS_OZONE_EVDEV_EXPORT __declspec(dllimport) +#endif // defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION) +#define EVENTS_OZONE_EVDEV_EXPORT __attribute__((visibility("default"))) +#else +#define EVENTS_OZONE_EVDEV_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define EVENTS_OZONE_EVDEV_EXPORT +#endif + +#endif // UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_ diff --git a/chromium/ui/events/ozone/evdev/key_event_converter.h b/chromium/ui/events/ozone/evdev/key_event_converter.h deleted file mode 100644 index a544b02104c..00000000000 --- a/chromium/ui/events/ozone/evdev/key_event_converter.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2013 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. - -#ifndef UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ -#define UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ - -#include "ui/events/event.h" -#include "ui/events/events_export.h" -#include "ui/events/ozone/evdev/event_modifiers.h" -#include "ui/events/ozone/event_converter_ozone.h" - -struct input_event; - -namespace ui { - -class EVENTS_EXPORT KeyEventConverterEvdev : public EventConverterOzone { - public: - KeyEventConverterEvdev(int fd, int id, EventModifiersEvdev* modifiers); - virtual ~KeyEventConverterEvdev(); - - // Overidden from base::MessagePumpLibevent::Watcher. - virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; - virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; - - void ProcessEvents(const struct input_event* inputs, int count); - - private: - // File descriptor for the /dev/input/event* instance. - int fd_; - - // Number corresponding to * in the source evdev device: /dev/input/event* - int id_; - - // Shared modifier state. - EventModifiersEvdev* modifiers_; - - void ConvertKeyEvent(int key, int value); - - DISALLOW_COPY_AND_ASSIGN(KeyEventConverterEvdev); -}; - -} // namspace ui - -#endif // UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ - diff --git a/chromium/ui/events/ozone/evdev/key_event_converter.cc b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.cc index b739b34a53c..2e6f31e052a 100644 --- a/chromium/ui/events/ozone/evdev/key_event_converter.cc +++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.cc @@ -1,14 +1,17 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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/ozone/evdev/key_event_converter.h" +#include "ui/events/ozone/evdev/key_event_converter_evdev.h" +#include <errno.h> #include <linux/input.h> +#include "base/message_loop/message_loop.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" -#include "ui/events/ozone/evdev/event_modifiers.h" +#include "ui/events/ozone/evdev/event_modifiers_evdev.h" +#include "ui/ozone/public/event_factory_ozone.h" namespace ui { @@ -184,23 +187,43 @@ bool IsLockButton(unsigned int code) { return code == KEY_CAPSLOCK; } } // namespace -KeyEventConverterEvdev::KeyEventConverterEvdev(int fd, - int id, - EventModifiersEvdev* modifiers) - : fd_(fd), id_(id), modifiers_(modifiers) { +KeyEventConverterEvdev::KeyEventConverterEvdev( + int fd, + base::FilePath path, + EventModifiersEvdev* modifiers, + const EventDispatchCallback& callback) + : EventConverterEvdev(callback), + fd_(fd), + path_(path), + modifiers_(modifiers) { // TODO(spang): Initialize modifiers using EVIOCGKEY. } KeyEventConverterEvdev::~KeyEventConverterEvdev() { - if (fd_ >= 0 && close(fd_) < 0) - DLOG(WARNING) << "failed close on /dev/input/event" << id_; + Stop(); + close(fd_); +} + +void KeyEventConverterEvdev::Start() { + base::MessageLoopForUI::current()->WatchFileDescriptor( + fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this); +} + +void KeyEventConverterEvdev::Stop() { + controller_.StopWatchingFileDescriptor(); } void KeyEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { input_event inputs[4]; ssize_t read_size = read(fd, inputs, sizeof(inputs)); - if (read_size <= 0) + if (read_size < 0) { + if (errno == EINTR || errno == EAGAIN) + return; + if (errno != ENODEV) + PLOG(ERROR) << "error reading device " << path_.value(); + Stop(); return; + } CHECK_EQ(read_size % sizeof(*inputs), 0u); ProcessEvents(inputs, read_size / sizeof(*inputs)); @@ -240,9 +263,9 @@ void KeyEventConverterEvdev::ConvertKeyEvent(int key, int value) { int flags = modifiers_->GetModifierFlags(); - scoped_ptr<KeyEvent> key_event( - new KeyEvent(down ? ET_KEY_PRESSED : ET_KEY_RELEASED, code, flags, true)); - DispatchEvent(key_event.PassAs<ui::Event>()); + KeyEvent key_event( + down ? ET_KEY_PRESSED : ET_KEY_RELEASED, code, flags, false); + DispatchEventToCallback(&key_event); } } // namespace ui diff --git a/chromium/ui/events/ozone/evdev/key_event_converter_evdev.h b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.h new file mode 100644 index 00000000000..26615d94942 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.h @@ -0,0 +1,60 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ + +#include "base/files/file_path.h" +#include "base/message_loop/message_pump_libevent.h" +#include "ui/events/event.h" +#include "ui/events/ozone/evdev/event_converter_evdev.h" +#include "ui/events/ozone/evdev/event_modifiers_evdev.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" + +struct input_event; + +namespace ui { + +class EVENTS_OZONE_EVDEV_EXPORT KeyEventConverterEvdev + : public EventConverterEvdev, + public base::MessagePumpLibevent::Watcher { + public: + KeyEventConverterEvdev(int fd, + base::FilePath path, + EventModifiersEvdev* modifiers, + const EventDispatchCallback& dispatch); + virtual ~KeyEventConverterEvdev(); + + // Start & stop watching for events. + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + + // Overidden from base::MessagePumpLibevent::Watcher. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + void ProcessEvents(const struct input_event* inputs, int count); + + private: + // File descriptor for the /dev/input/event* instance. + int fd_; + + // Path to input device. + base::FilePath path_; + + // Shared modifier state. + EventModifiersEvdev* modifiers_; + + // Controller for watching the input fd. + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + void ConvertKeyEvent(int key, int value); + + DISALLOW_COPY_AND_ASSIGN(KeyEventConverterEvdev); +}; + +} // namspace ui + +#endif // UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_ + diff --git a/chromium/ui/events/ozone/evdev/key_event_converter_unittest.cc b/chromium/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc index 472139078bd..88ee64609ed 100644 --- a/chromium/ui/events/ozone/evdev/key_event_converter_unittest.cc +++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc @@ -1,4 +1,4 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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. @@ -6,28 +6,34 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" -#include "ui/events/ozone/evdev/key_event_converter.h" +#include "ui/events/ozone/evdev/key_event_converter_evdev.h" namespace ui { -const int kInvalidFileDescriptor = -1; -const int kTestDeviceId = 0; +const char kTestDevicePath[] = "/dev/input/test-device"; class MockKeyEventConverterEvdev : public KeyEventConverterEvdev { public: - MockKeyEventConverterEvdev(EventModifiersEvdev* modifiers) - : KeyEventConverterEvdev(kInvalidFileDescriptor, - kTestDeviceId, - modifiers) {} + MockKeyEventConverterEvdev(int fd, EventModifiersEvdev* modifiers) + : KeyEventConverterEvdev(fd, + base::FilePath(kTestDevicePath), + modifiers, + EventDispatchCallback()) { + Start(); + } virtual ~MockKeyEventConverterEvdev() {}; unsigned size() { return dispatched_events_.size(); } - KeyEvent* event(unsigned index) { return dispatched_events_[index]; } + KeyEvent* event(unsigned index) { + CHECK_GT(dispatched_events_.size(), index); + return dispatched_events_[index]; + } - virtual void DispatchEvent(scoped_ptr<Event> event) OVERRIDE; + virtual void DispatchEventToCallback(Event* event) OVERRIDE; private: ScopedVector<KeyEvent> dispatched_events_; @@ -35,8 +41,8 @@ class MockKeyEventConverterEvdev : public KeyEventConverterEvdev { DISALLOW_COPY_AND_ASSIGN(MockKeyEventConverterEvdev); }; -void MockKeyEventConverterEvdev::DispatchEvent(scoped_ptr<Event> event) { - dispatched_events_.push_back(static_cast<KeyEvent*>(event.release())); +void MockKeyEventConverterEvdev::DispatchEventToCallback(Event* event) { + dispatched_events_.push_back(new KeyEvent(*static_cast<KeyEvent*>(event))); } } // namespace ui @@ -48,20 +54,36 @@ class KeyEventConverterEvdevTest : public testing::Test { // Overridden from testing::Test: virtual void SetUp() OVERRIDE { + + // Set up pipe to satisfy message pump (unused). + int evdev_io[2]; + if (pipe(evdev_io)) + PLOG(FATAL) << "failed pipe"; + events_in_ = evdev_io[0]; + events_out_ = evdev_io[1]; + modifiers_ = new ui::EventModifiersEvdev(); - device_ = new ui::MockKeyEventConverterEvdev(modifiers_); + device_ = new ui::MockKeyEventConverterEvdev(events_in_, modifiers_); } virtual void TearDown() OVERRIDE { delete device_; delete modifiers_; + close(events_in_); + close(events_out_); } ui::MockKeyEventConverterEvdev* device() { return device_; } ui::EventModifiersEvdev* modifiers() { return modifiers_; } private: + base::MessageLoopForUI ui_loop_; + ui::EventModifiersEvdev* modifiers_; ui::MockKeyEventConverterEvdev* device_; + + int events_out_; + int events_in_; + DISALLOW_COPY_AND_ASSIGN(KeyEventConverterEvdevTest); }; diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc new file mode 100644 index 00000000000..86f8834d389 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc @@ -0,0 +1,107 @@ +// Copyright 2014 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/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h" + +#include <errno.h> +#include <libevdev/libevdev.h> +#include <linux/input.h> + +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace ui { + +namespace { + +std::string FormatLog(const char* fmt, va_list args) { + std::string msg = base::StringPrintV(fmt, args); + if (!msg.empty() && msg[msg.size() - 1] == '\n') + msg.erase(msg.end() - 1, msg.end()); + return msg; +} + +} // namespace + +EventReaderLibevdevCros::EventReaderLibevdevCros(int fd, + const base::FilePath& path, + scoped_ptr<Delegate> delegate) + : path_(path), delegate_(delegate.Pass()) { + memset(&evdev_, 0, sizeof(evdev_)); + evdev_.log = OnLogMessage; + evdev_.log_udata = this; + evdev_.syn_report = OnSynReport; + evdev_.syn_report_udata = this; + evdev_.fd = fd; + + memset(&evstate_, 0, sizeof(evstate_)); + evdev_.evstate = &evstate_; + Event_Init(&evdev_); + + Event_Open(&evdev_); + + delegate_->OnLibEvdevCrosOpen(&evdev_, &evstate_); +} + +EventReaderLibevdevCros::~EventReaderLibevdevCros() { + Stop(); + EvdevClose(&evdev_); +} + +EventReaderLibevdevCros::Delegate::~Delegate() {} + +void EventReaderLibevdevCros::Start() { + base::MessageLoopForUI::current()->WatchFileDescriptor( + evdev_.fd, + true, + base::MessagePumpLibevent::WATCH_READ, + &controller_, + this); +} + +void EventReaderLibevdevCros::Stop() { + controller_.StopWatchingFileDescriptor(); +} + +void EventReaderLibevdevCros::OnFileCanReadWithoutBlocking(int fd) { + if (EvdevRead(&evdev_)) { + if (errno == EINTR || errno == EAGAIN) + return; + if (errno != ENODEV) + PLOG(ERROR) << "error reading device " << path_.value(); + Stop(); + return; + } +} + +void EventReaderLibevdevCros::OnFileCanWriteWithoutBlocking(int fd) { + NOTREACHED(); +} + +// static +void EventReaderLibevdevCros::OnSynReport(void* data, + EventStateRec* evstate, + struct timeval* tv) { + EventReaderLibevdevCros* reader = static_cast<EventReaderLibevdevCros*>(data); + reader->delegate_->OnLibEvdevCrosEvent(&reader->evdev_, evstate, *tv); +} + +// static +void EventReaderLibevdevCros::OnLogMessage(void* data, + int level, + const char* fmt, + ...) { + va_list args; + va_start(args, fmt); + if (level >= LOGLEVEL_ERROR) + LOG(ERROR) << "libevdev: " << FormatLog(fmt, args); + else if (level >= LOGLEVEL_WARNING) + LOG(WARNING) << "libevdev: " << FormatLog(fmt, args); + else + VLOG(3) << "libevdev: " << FormatLog(fmt, args); + va_end(args); +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h new file mode 100644 index 00000000000..1631c5b685b --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h @@ -0,0 +1,78 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_ +#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_ + +#include <libevdev/libevdev.h> + +#include "base/files/file_path.h" +#include "base/message_loop/message_loop.h" +#include "ui/events/ozone/evdev/event_converter_evdev.h" + +namespace ui { + +// Basic wrapper for libevdev-cros. +// +// This drives libevdev-cros from a file descriptor and calls delegate +// with the updated event state from libevdev-cros. +// +// The library doesn't support all devices currently. In particular there +// is no support for keyboard events. +class EventReaderLibevdevCros : public base::MessagePumpLibevent::Watcher, + public EventConverterEvdev { + public: + class Delegate { + public: + virtual ~Delegate(); + + // Notifier for open. This is called with the initial event state. + virtual void OnLibEvdevCrosOpen(Evdev* evdev, EventStateRec* evstate) = 0; + + // Notifier for event. This is called with the updated event state. + virtual void OnLibEvdevCrosEvent(Evdev* evdev, + EventStateRec* state, + const timeval& time) = 0; + }; + + EventReaderLibevdevCros(int fd, + const base::FilePath& path, + scoped_ptr<Delegate> delegate); + ~EventReaderLibevdevCros(); + + // Overridden from ui::EventDeviceEvdev. + void Start() OVERRIDE; + void Stop() OVERRIDE; + + // Overidden from MessagePumpLibevent::Watcher. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + static void OnSynReport(void* data, + EventStateRec* evstate, + struct timeval* tv); + static void OnLogMessage(void*, int level, const char*, ...); + + // Libevdev state. + Evdev evdev_; + + // Event state. + EventStateRec evstate_; + + // Path to input device. + base::FilePath path_; + + // Delegate for event processing. + scoped_ptr<Delegate> delegate_; + + // Controller for watching the input fd. + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + DISALLOW_COPY_AND_ASSIGN(EventReaderLibevdevCros); +}; + +} // namspace ui + +#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_ diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc new file mode 100644 index 00000000000..c41a04d2f08 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc @@ -0,0 +1,274 @@ +// Copyright 2014 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/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h" + +#include <gestures/gestures.h> +#include <libevdev/libevdev.h> + +#include "base/strings/stringprintf.h" +#include "base/timer/timer.h" +#include "ui/events/event.h" +#include "ui/events/ozone/evdev/cursor_delegate_evdev.h" +#include "ui/events/ozone/evdev/event_modifiers_evdev.h" +#include "ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h" +#include "ui/gfx/geometry/point_f.h" + +namespace ui { + +namespace { + +// Convert libevdev device class to libgestures device class. +GestureInterpreterDeviceClass GestureDeviceClass(Evdev* evdev) { + switch (evdev->info.evdev_class) { + case EvdevClassMouse: + return GESTURES_DEVCLASS_MOUSE; + case EvdevClassMultitouchMouse: + return GESTURES_DEVCLASS_MULTITOUCH_MOUSE; + case EvdevClassTouchpad: + return GESTURES_DEVCLASS_TOUCHPAD; + case EvdevClassTouchscreen: + return GESTURES_DEVCLASS_TOUCHSCREEN; + default: + return GESTURES_DEVCLASS_UNKNOWN; + } +} + +// Convert libevdev state to libgestures hardware properties. +HardwareProperties GestureHardwareProperties(Evdev* evdev) { + HardwareProperties hwprops; + hwprops.left = Event_Get_Left(evdev); + hwprops.top = Event_Get_Top(evdev); + hwprops.right = Event_Get_Right(evdev); + hwprops.bottom = Event_Get_Bottom(evdev); + hwprops.res_x = Event_Get_Res_X(evdev); + hwprops.res_y = Event_Get_Res_Y(evdev); + hwprops.screen_x_dpi = 133; + hwprops.screen_y_dpi = 133; + hwprops.orientation_minimum = Event_Get_Orientation_Minimum(evdev); + hwprops.orientation_maximum = Event_Get_Orientation_Maximum(evdev); + hwprops.max_finger_cnt = Event_Get_Slot_Count(evdev); + hwprops.max_touch_cnt = Event_Get_Touch_Count_Max(evdev); + hwprops.supports_t5r2 = Event_Get_T5R2(evdev); + hwprops.support_semi_mt = Event_Get_Semi_MT(evdev); + /* buttonpad means a physical button under the touch surface */ + hwprops.is_button_pad = Event_Get_Button_Pad(evdev); + return hwprops; +} + +// Callback from libgestures when a gesture is ready. +void OnGestureReadyHelper(void* client_data, const Gesture* gesture) { + GestureInterpreterLibevdevCros* interpreter = + static_cast<GestureInterpreterLibevdevCros*>(client_data); + interpreter->OnGestureReady(gesture); +} + +// Convert gestures timestamp (stime_t) to ui::Event timestamp. +base::TimeDelta StimeToTimedelta(stime_t timestamp) { + return base::TimeDelta::FromMicroseconds(timestamp * + base::Time::kMicrosecondsPerSecond); +} + +// Number of fingers for scroll gestures. +const int kGestureScrollFingerCount = 2; + +} // namespace + +GestureInterpreterLibevdevCros::GestureInterpreterLibevdevCros( + EventModifiersEvdev* modifiers, + CursorDelegateEvdev* cursor, + const EventDispatchCallback& callback) + : modifiers_(modifiers), + cursor_(cursor), + dispatch_callback_(callback), + interpreter_(NULL) {} + +GestureInterpreterLibevdevCros::~GestureInterpreterLibevdevCros() { + if (interpreter_) { + DeleteGestureInterpreter(interpreter_); + interpreter_ = NULL; + } +} + +void GestureInterpreterLibevdevCros::OnLibEvdevCrosOpen( + Evdev* evdev, + EventStateRec* evstate) { + CHECK(evdev->info.is_monotonic) << "libevdev must use monotonic timestamps"; + VLOG(9) << "HACK DO NOT REMOVE OR LINK WILL FAIL" << (void*)gestures_log; + + HardwareProperties hwprops = GestureHardwareProperties(evdev); + GestureInterpreterDeviceClass devclass = GestureDeviceClass(evdev); + + // Create & initialize GestureInterpreter. + CHECK(!interpreter_); + interpreter_ = NewGestureInterpreter(); + GestureInterpreterInitialize(interpreter_, devclass); + GestureInterpreterSetHardwareProperties(interpreter_, &hwprops); + GestureInterpreterSetTimerProvider( + interpreter_, + const_cast<GesturesTimerProvider*>(&kGestureTimerProvider), + this); + GestureInterpreterSetCallback(interpreter_, OnGestureReadyHelper, this); +} + +void GestureInterpreterLibevdevCros::OnLibEvdevCrosEvent(Evdev* evdev, + EventStateRec* evstate, + const timeval& time) { + HardwareState hwstate; + memset(&hwstate, 0, sizeof(hwstate)); + hwstate.timestamp = StimeFromTimeval(&time); + + // Mouse. + hwstate.rel_x = evstate->rel_x; + hwstate.rel_y = evstate->rel_y; + hwstate.rel_wheel = evstate->rel_wheel; + hwstate.rel_hwheel = evstate->rel_hwheel; + + // Touch. + FingerState fingers[Event_Get_Slot_Count(evdev)]; + memset(&fingers, 0, sizeof(fingers)); + int current_finger = 0; + for (int i = 0; i < evstate->slot_count; i++) { + MtSlotPtr slot = &evstate->slots[i]; + if (slot->tracking_id == -1) + continue; + fingers[current_finger].touch_major = slot->touch_major; + fingers[current_finger].touch_minor = slot->touch_minor; + fingers[current_finger].width_major = slot->width_major; + fingers[current_finger].width_minor = slot->width_minor; + fingers[current_finger].pressure = slot->pressure; + fingers[current_finger].orientation = slot->orientation; + fingers[current_finger].position_x = slot->position_x; + fingers[current_finger].position_y = slot->position_y; + fingers[current_finger].tracking_id = slot->tracking_id; + current_finger++; + } + hwstate.touch_cnt = Event_Get_Touch_Count(evdev); + hwstate.finger_cnt = current_finger; + hwstate.fingers = fingers; + + // Buttons. + if (Event_Get_Button_Left(evdev)) + hwstate.buttons_down |= GESTURES_BUTTON_LEFT; + if (Event_Get_Button_Middle(evdev)) + hwstate.buttons_down |= GESTURES_BUTTON_MIDDLE; + if (Event_Get_Button_Right(evdev)) + hwstate.buttons_down |= GESTURES_BUTTON_RIGHT; + + GestureInterpreterPushHardwareState(interpreter_, &hwstate); +} + +void GestureInterpreterLibevdevCros::OnGestureReady(const Gesture* gesture) { + switch (gesture->type) { + case kGestureTypeMove: + OnGestureMove(gesture, &gesture->details.move); + break; + case kGestureTypeScroll: + OnGestureScroll(gesture, &gesture->details.scroll); + break; + case kGestureTypeButtonsChange: + OnGestureButtonsChange(gesture, &gesture->details.buttons); + break; + case kGestureTypeContactInitiated: + case kGestureTypeFling: + case kGestureTypeSwipe: + case kGestureTypeSwipeLift: + case kGestureTypePinch: + case kGestureTypeMetrics: + // TODO(spang): Support remaining gestures. + NOTIMPLEMENTED(); + break; + default: + LOG(WARNING) << base::StringPrintf("Unrecognized gesture type (%u)", + gesture->type); + break; + } +} + +void GestureInterpreterLibevdevCros::OnGestureMove(const Gesture* gesture, + const GestureMove* move) { + DVLOG(3) << base::StringPrintf("Gesture Move: (%f, %f) [%f, %f]", + move->dx, + move->dy, + move->ordinal_dx, + move->ordinal_dy); + if (!cursor_) + return; // No cursor! + cursor_->MoveCursor(gfx::Vector2dF(move->dx, move->dy)); + // TODO(spang): Use move->ordinal_dx, move->ordinal_dy + // TODO(spang): Use move->start_time, move->end_time + MouseEvent event(ET_MOUSE_MOVED, + cursor_->location(), + cursor_->location(), + modifiers_->GetModifierFlags(), + /* changed_button_flags */ 0); + Dispatch(&event); +} + +void GestureInterpreterLibevdevCros::OnGestureScroll( + const Gesture* gesture, + const GestureScroll* scroll) { + if (!cursor_) + return; // No cursor! + DVLOG(3) << base::StringPrintf("Gesture Scroll: (%f, %f) [%f, %f]", + scroll->dx, + scroll->dy, + scroll->ordinal_dx, + scroll->ordinal_dy); + // TODO(spang): Support SetNaturalScroll + // TODO(spang): Use scroll->start_time + ScrollEvent event(ET_SCROLL, + cursor_->location(), + StimeToTimedelta(gesture->end_time), + modifiers_->GetModifierFlags(), + scroll->dx, + scroll->dy, + scroll->ordinal_dx, + scroll->ordinal_dy, + kGestureScrollFingerCount); + Dispatch(&event); +} + +void GestureInterpreterLibevdevCros::OnGestureButtonsChange( + const Gesture* gesture, + const GestureButtonsChange* buttons) { + DVLOG(3) << base::StringPrintf("Gesture Button Change: down=0x%02x up=0x%02x", + buttons->down, + buttons->up); + // TODO(spang): Use buttons->start_time, buttons->end_time + if (buttons->down & GESTURES_BUTTON_LEFT) + DispatchMouseButton(EVDEV_MODIFIER_LEFT_MOUSE_BUTTON, true); + if (buttons->down & GESTURES_BUTTON_MIDDLE) + DispatchMouseButton(EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON, true); + if (buttons->down & GESTURES_BUTTON_RIGHT) + DispatchMouseButton(EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON, true); + if (buttons->up & GESTURES_BUTTON_LEFT) + DispatchMouseButton(EVDEV_MODIFIER_LEFT_MOUSE_BUTTON, false); + if (buttons->up & GESTURES_BUTTON_MIDDLE) + DispatchMouseButton(EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON, false); + if (buttons->up & GESTURES_BUTTON_RIGHT) + DispatchMouseButton(EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON, false); +} + +void GestureInterpreterLibevdevCros::Dispatch(Event* event) { + dispatch_callback_.Run(event); +} + +void GestureInterpreterLibevdevCros::DispatchMouseButton(unsigned int modifier, + bool down) { + if (!cursor_) + return; // No cursor! + const gfx::PointF& loc = cursor_->location(); + int flag = modifiers_->GetEventFlagFromModifier(modifier); + EventType type = (down ? ET_MOUSE_PRESSED : ET_MOUSE_RELEASED); + modifiers_->UpdateModifier(modifier, down); + MouseEvent event(type, loc, loc, modifiers_->GetModifierFlags() | flag, flag); + + // This hack is necessary to trigger setting the repeat count. + // TODO(spang): Fix it. + MouseEvent event2(&event); + Dispatch(&event2); +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h new file mode 100644 index 00000000000..30d8f62ab90 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h @@ -0,0 +1,82 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_ +#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_ + +#include <gestures/gestures.h> +#include <libevdev/libevdev.h> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/ozone/evdev/cursor_delegate_evdev.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" +#include "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h" + +namespace ui { + +class Event; +class EventDeviceInfo; +class EventModifiersEvdev; +class CursorDelegateEvdev; + +typedef base::Callback<void(Event*)> EventDispatchCallback; + +// Convert libevdev-cros events to ui::Events using libgestures. +// +// This builds a GestureInterpreter for an input device (touchpad or +// mouse). +// +// Raw input events must be preprocessed into a form suitable for +// libgestures. The kernel protocol only emits changes to the device state, +// so changes must be accumulated until a sync event. The full device state +// at sync is then processed by libgestures. +// +// Once we have the state at sync, we convert it to a HardwareState object +// and forward it to libgestures. If any gestures are produced, they are +// converted to ui::Events and dispatched. +class EVENTS_OZONE_EVDEV_EXPORT GestureInterpreterLibevdevCros + : public EventReaderLibevdevCros::Delegate { + public: + GestureInterpreterLibevdevCros(EventModifiersEvdev* modifiers, + CursorDelegateEvdev* cursor, + const EventDispatchCallback& callback); + virtual ~GestureInterpreterLibevdevCros(); + + // Overriden from ui::EventReaderLibevdevCros::Delegate + virtual void OnLibEvdevCrosOpen(Evdev* evdev, + EventStateRec* evstate) OVERRIDE; + virtual void OnLibEvdevCrosEvent(Evdev* evdev, + EventStateRec* evstate, + const timeval& time) OVERRIDE; + + // Handler for gesture events generated from libgestures. + void OnGestureReady(const Gesture* gesture); + + private: + void OnGestureMove(const Gesture* gesture, const GestureMove* move); + void OnGestureScroll(const Gesture* gesture, const GestureScroll* move); + void OnGestureButtonsChange(const Gesture* gesture, + const GestureButtonsChange* move); + void Dispatch(Event* event); + void DispatchMouseButton(unsigned int modifier, bool down); + + // Shared modifier state. + EventModifiersEvdev* modifiers_; + + // Shared cursor state. + CursorDelegateEvdev* cursor_; + + // Callback for dispatching events. + EventDispatchCallback dispatch_callback_; + + // Gestures interpretation state. + gestures::GestureInterpreter* interpreter_; + + DISALLOW_COPY_AND_ASSIGN(GestureInterpreterLibevdevCros); +}; + +} // namspace ui + +#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_ diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc new file mode 100644 index 00000000000..009fc939020 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc @@ -0,0 +1,32 @@ +// Copyright 2014 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/ozone/evdev/libgestures_glue/gesture_logging.h" + +#include <gestures/gestures.h> +#include <stdarg.h> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" + +namespace { + +std::string FormatLog(const char* fmt, va_list args) { + std::string msg = base::StringPrintV(fmt, args); + if (!msg.empty() && msg[msg.size() - 1] == '\n') + msg.erase(msg.end() - 1, msg.end()); + return msg; +} + +} // namespace + +void gestures_log(int verb, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + if (verb <= GESTURES_LOG_ERROR) + LOG(ERROR) << "gestures: " << FormatLog(fmt, args); + else if (verb <= GESTURES_LOG_INFO) + VLOG(3) << "gestures: " << FormatLog(fmt, args); + va_end(args); +} diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h new file mode 100644 index 00000000000..a5f543556ab --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h @@ -0,0 +1,15 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_ +#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_ + +// libgestures.so binds to this function for logging. +// TODO(spang): Fix libgestures to not require this. +extern "C" + __attribute__((visibility("default"))) void gestures_log(int verb, + const char* fmt, + ...); + +#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_ diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc new file mode 100644 index 00000000000..92f15b77825 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc @@ -0,0 +1,72 @@ +// Copyright 2014 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/ozone/evdev/libgestures_glue/gesture_timer_provider.h" + +#include <gestures/gestures.h> + +#include "base/timer/timer.h" + +// libgestures requires that this be in the top level namespace. +class GesturesTimer { + public: + GesturesTimer() : callback_(NULL), callback_data_(NULL) {} + ~GesturesTimer() {} + + void Set(stime_t delay, GesturesTimerCallback callback, void* callback_data) { + callback_ = callback; + callback_data_ = callback_data; + timer_.Start(FROM_HERE, + base::TimeDelta::FromMicroseconds( + delay * base::Time::kMicrosecondsPerSecond), + this, + &GesturesTimer::OnTimerExpired); + } + + void Cancel() { timer_.Stop(); } + + private: + void OnTimerExpired() { + struct timespec ts; + CHECK(!clock_gettime(CLOCK_MONOTONIC, &ts)); + stime_t next_delay = callback_(StimeFromTimespec(&ts), callback_data_); + if (next_delay >= 0) { + timer_.Start(FROM_HERE, + base::TimeDelta::FromMicroseconds( + next_delay * base::Time::kMicrosecondsPerSecond), + this, + &GesturesTimer::OnTimerExpired); + } + } + + GesturesTimerCallback callback_; + void* callback_data_; + base::OneShotTimer<GesturesTimer> timer_; +}; + +namespace ui { + +namespace { + +GesturesTimer* GesturesTimerCreate(void* data) { return new GesturesTimer; } + +void GesturesTimerSet(void* data, + GesturesTimer* timer, + stime_t delay, + GesturesTimerCallback callback, + void* callback_data) { + timer->Set(delay, callback, callback_data); +} + +void GesturesTimerCancel(void* data, GesturesTimer* timer) { timer->Cancel(); } + +void GesturesTimerFree(void* data, GesturesTimer* timer) { delete timer; } + +} // namespace + +const GesturesTimerProvider kGestureTimerProvider = { + GesturesTimerCreate, GesturesTimerSet, GesturesTimerCancel, + GesturesTimerFree}; + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h new file mode 100644 index 00000000000..edc20ba9c07 --- /dev/null +++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h @@ -0,0 +1,16 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_ +#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_ + +#include <gestures/gestures.h> + +namespace ui { + +extern const GesturesTimerProvider kGestureTimerProvider; + +} // namspace ui + +#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_ diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter.cc b/chromium/ui/events/ozone/evdev/touch_event_converter.cc deleted file mode 100644 index dc3c30a43f6..00000000000 --- a/chromium/ui/events/ozone/evdev/touch_event_converter.cc +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2013 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/ozone/evdev/touch_event_converter.h" - -#include <fcntl.h> -#include <linux/input.h> -#include <poll.h> -#include <stdio.h> -#include <unistd.h> - -#include <cmath> -#include <limits> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/logging.h" -#include "base/message_loop/message_loop.h" -#include "base/message_loop/message_pump_ozone.h" -#include "ui/events/event.h" -#include "ui/events/event_constants.h" -#include "ui/gfx/ozone/surface_factory_ozone.h" - -namespace { - -// Number is determined empirically. -// TODO(rjkroege): Configure this per device. -const float kFingerWidth = 25.f; - -} // namespace - -namespace ui { - -TouchEventConverterEvdev::TouchEventConverterEvdev(int fd, int id) - : pressure_min_(0), - pressure_max_(0), - x_scale_(1.), - y_scale_(1.), - x_max_(std::numeric_limits<int>::max()), - y_max_(std::numeric_limits<int>::max()), - current_slot_(0), - fd_(fd), - id_(id) { - Init(); -} - -TouchEventConverterEvdev::~TouchEventConverterEvdev() { - if (fd_ >= 0 && close(fd_) < 0) - DLOG(WARNING) << "failed close on /dev/input/event" << id_; -} - -void TouchEventConverterEvdev::Init() { - input_absinfo abs = {}; - if (ioctl(fd_, EVIOCGABS(ABS_MT_SLOT), &abs) != -1) { - CHECK_GE(abs.maximum, abs.minimum); - CHECK_GE(abs.minimum, 0); - } else { - DLOG(WARNING) << "failed ioctl EVIOCGABS ABS_MT_SLOT event" << id_; - } - if (ioctl(fd_, EVIOCGABS(ABS_MT_PRESSURE), &abs) != -1) { - pressure_min_ = abs.minimum; - pressure_max_ = abs.maximum; - } else { - DLOG(WARNING) << "failed ioctl EVIOCGABS ABS_MT_PRESSURE event" << id_; - } - int x_min = 0, x_max = 0; - if (ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_X), &abs) != -1) { - x_min = abs.minimum; - x_max = abs.maximum; - } else { - LOG(WARNING) << "failed ioctl EVIOCGABS ABS_X event" << id_; - } - int y_min = 0, y_max = 0; - if (ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_Y), &abs) != -1) { - y_min = abs.minimum; - y_max = abs.maximum; - } else { - LOG(WARNING) << "failed ioctl EVIOCGABS ABS_Y event" << id_; - } - if (x_max && y_max && gfx::SurfaceFactoryOzone::GetInstance()) { - const char* display = - gfx::SurfaceFactoryOzone::GetInstance()->DefaultDisplaySpec(); - int screen_width, screen_height; - int sc = sscanf(display, "%dx%d", &screen_width, &screen_height); - if (sc == 2) { - x_scale_ = (double)screen_width / (x_max - x_min); - y_scale_ = (double)screen_height / (y_max - y_min); - x_max_ = screen_width - 1; - y_max_ = screen_height - 1; - LOG(INFO) << "touch input x_scale=" << x_scale_ - << " y_scale=" << y_scale_; - } else { - LOG(WARNING) << "malformed display spec from " - << "SurfaceFactoryOzone::DefaultDisplaySpec"; - } - } -} - -void TouchEventConverterEvdev::OnFileCanWriteWithoutBlocking(int /* fd */) { - // Read-only file-descriptors. - NOTREACHED(); -} - -void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { - input_event inputs[MAX_FINGERS * 6 + 1]; - ssize_t read_size = read(fd, inputs, sizeof(inputs)); - if (read_size <= 0) - return; - - for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) { - const input_event& input = inputs[i]; - if (input.type == EV_ABS) { - switch (input.code) { - case ABS_MT_TOUCH_MAJOR: - altered_slots_.set(current_slot_); - events_[current_slot_].major_ = input.value; - break; - case ABS_X: - case ABS_MT_POSITION_X: - altered_slots_.set(current_slot_); - events_[current_slot_].x_ = roundf(input.value * x_scale_); - break; - case ABS_Y: - case ABS_MT_POSITION_Y: - altered_slots_.set(current_slot_); - events_[current_slot_].y_ = roundf(input.value * y_scale_); - break; - case ABS_MT_TRACKING_ID: - altered_slots_.set(current_slot_); - if (input.value < 0) { - events_[current_slot_].type_ = ET_TOUCH_RELEASED; - } else { - events_[current_slot_].finger_ = input.value; - events_[current_slot_].type_ = ET_TOUCH_PRESSED; - } - break; - case ABS_MT_PRESSURE: - case ABS_PRESSURE: - altered_slots_.set(current_slot_); - events_[current_slot_].pressure_ = input.value - pressure_min_; - events_[current_slot_].pressure_ /= pressure_max_ - pressure_min_; - break; - case ABS_MT_SLOT: - current_slot_ = input.value; - altered_slots_.set(current_slot_); - break; - default: - NOTREACHED() << "invalid code for EV_ABS: " << input.code; - } - } else if (input.type == EV_SYN) { - switch (input.code) { - case SYN_REPORT: - for (int j = 0; j < MAX_FINGERS; j++) { - if (altered_slots_[j]) { - // TODO(rjkroege): Support elliptical finger regions. - scoped_ptr<TouchEvent> tev(new TouchEvent( - events_[j].type_, - gfx::Point(std::min(x_max_, events_[j].x_), - std::min(y_max_, events_[j].y_)), - /* flags */ 0, - /* touch_id */ j, - base::TimeDelta::FromMicroseconds( - input.time.tv_sec * 1000000 + input.time.tv_usec), - events_[j].pressure_ * kFingerWidth, - events_[j].pressure_ * kFingerWidth, - /* angle */ 0., - events_[j].pressure_)); - DispatchEvent(tev.PassAs<ui::Event>()); - - // Subsequent events for this finger will be touch-move until it - // is released. - events_[j].type_ = ET_TOUCH_MOVED; - } - } - altered_slots_.reset(); - break; - case SYN_MT_REPORT: - case SYN_CONFIG: - case SYN_DROPPED: - NOTREACHED() << "invalid code for EV_SYN: " << input.code; - break; - } - } else if (input.type == EV_KEY) { - switch (input.code) { - case BTN_TOUCH: - break; - default: - NOTREACHED() << "invalid code for EV_KEY: " << input.code; - } - } else { - NOTREACHED() << "invalid type: " << input.type; - } - } -} - -} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter.h b/chromium/ui/events/ozone/evdev/touch_event_converter.h deleted file mode 100644 index 7ebac408d8f..00000000000 --- a/chromium/ui/events/ozone/evdev/touch_event_converter.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2013 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. - -#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ -#define UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ - -#include <bitset> - -#include "base/compiler_specific.h" -#include "ui/events/event_constants.h" -#include "ui/events/events_export.h" -#include "ui/events/ozone/event_converter_ozone.h" - -namespace ui { - -class TouchEvent; - -class EVENTS_EXPORT TouchEventConverterEvdev : public EventConverterOzone { - public: - enum { - MAX_FINGERS = 11 - }; - TouchEventConverterEvdev(int fd, int id); - virtual ~TouchEventConverterEvdev(); - - private: - friend class MockTouchEventConverterEvdev; - - // Unsafe part of initialization. - void Init(); - - // Overidden from base::MessagePumpLibevent::Watcher. - virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; - virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; - - // Pressure values. - int pressure_min_; - int pressure_max_; // Used to normalize pressure values. - - // Touch scaling. - float x_scale_; - float y_scale_; - - // Maximum coordinate-values allowed for the events. - int x_max_; - int y_max_; - - // Touch point currently being updated from the /dev/input/event* stream. - int current_slot_; - - // File descriptor for the /dev/input/event* instance. - int fd_; - - // Number corresponding to * in the source evdev device: /dev/input/event* - int id_; - - // Bit field tracking which in-progress touch points have been modified - // without a syn event. - std::bitset<MAX_FINGERS> altered_slots_; - - struct InProgressEvents { - int x_; - int y_; - int id_; // Device reported "unique" touch point id; -1 means not active - int finger_; // "Finger" id starting from 0; -1 means not active - - EventType type_; - int major_; - float pressure_; - }; - - // In-progress touch points. - InProgressEvents events_[MAX_FINGERS]; - - DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdev); -}; - -} // namespace ui - -#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ - diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc new file mode 100644 index 00000000000..9cfab5bf11c --- /dev/null +++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc @@ -0,0 +1,307 @@ +// Copyright 2014 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/ozone/evdev/touch_event_converter_evdev.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <poll.h> +#include <stdio.h> +#include <unistd.h> + +#include <cmath> +#include <limits> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_switches.h" +#include "ui/gfx/screen.h" +#include "ui/ozone/public/event_factory_ozone.h" + +namespace { + +// Number is determined empirically. +// TODO(rjkroege): Configure this per device. +const float kFingerWidth = 25.f; + +struct TouchCalibration { + int bezel_left; + int bezel_right; + int bezel_top; + int bezel_bottom; +}; + +void GetTouchCalibration(TouchCalibration* cal) { + std::vector<std::string> parts; + if (Tokenize(CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kTouchCalibration), + ",", + &parts) >= 4) { + if (!base::StringToInt(parts[0], &cal->bezel_left)) + DLOG(ERROR) << "Incorrect left border calibration value passed."; + if (!base::StringToInt(parts[1], &cal->bezel_right)) + DLOG(ERROR) << "Incorrect right border calibration value passed."; + if (!base::StringToInt(parts[2], &cal->bezel_top)) + DLOG(ERROR) << "Incorrect top border calibration value passed."; + if (!base::StringToInt(parts[3], &cal->bezel_bottom)) + DLOG(ERROR) << "Incorrect bottom border calibration value passed."; + } +} + +float TuxelsToPixels(float val, + float min_tuxels, + float num_tuxels, + float min_pixels, + float num_pixels) { + // Map [min_tuxels, min_tuxels + num_tuxels) to + // [min_pixels, min_pixels + num_pixels). + return min_pixels + (val - min_tuxels) * num_pixels / num_tuxels; +} + +} // namespace + +namespace ui { + +TouchEventConverterEvdev::TouchEventConverterEvdev( + int fd, + base::FilePath path, + const EventDeviceInfo& info, + const EventDispatchCallback& callback) + : EventConverterEvdev(callback), + syn_dropped_(false), + is_type_a_(false), + current_slot_(0), + fd_(fd), + path_(path) { + Init(info); +} + +TouchEventConverterEvdev::~TouchEventConverterEvdev() { + Stop(); + close(fd_); +} + +void TouchEventConverterEvdev::Init(const EventDeviceInfo& info) { + gfx::Screen *screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE); + if (!screen) + return; // No scaling. + gfx::Display display = screen->GetPrimaryDisplay(); + gfx::Size size = display.GetSizeInPixel(); + + pressure_min_ = info.GetAbsMinimum(ABS_MT_PRESSURE), + pressure_max_ = info.GetAbsMaximum(ABS_MT_PRESSURE), + x_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_X), + x_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_X) - x_min_tuxels_ + 1, + y_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_Y), + y_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_Y) - y_min_tuxels_ + 1, + x_min_pixels_ = x_min_tuxels_, + x_num_pixels_ = x_num_tuxels_, + y_min_pixels_ = y_min_tuxels_, + y_num_pixels_ = y_num_tuxels_, + + // Map coordinates onto screen. + x_min_pixels_ = 0; + y_min_pixels_ = 0; + x_num_pixels_ = size.width(); + y_num_pixels_ = size.height(); + + VLOG(1) << "mapping touch coordinates to screen coordinates: " + << base::StringPrintf("%dx%d", size.width(), size.height()); + + // Apply --touch-calibration. + TouchCalibration cal = {}; + GetTouchCalibration(&cal); + x_min_tuxels_ += cal.bezel_left; + x_num_tuxels_ -= cal.bezel_left + cal.bezel_right; + y_min_tuxels_ += cal.bezel_top; + y_num_tuxels_ -= cal.bezel_top + cal.bezel_bottom; + + VLOG(1) << "applying touch calibration: " + << base::StringPrintf("[%d, %d, %d, %d]", + cal.bezel_left, + cal.bezel_right, + cal.bezel_top, + cal.bezel_bottom); +} + +void TouchEventConverterEvdev::Start() { + base::MessageLoopForUI::current()->WatchFileDescriptor( + fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this); +} + +void TouchEventConverterEvdev::Stop() { + controller_.StopWatchingFileDescriptor(); +} + +bool TouchEventConverterEvdev::Reinitialize() { + EventDeviceInfo info; + if (info.Initialize(fd_)) { + Init(info); + return true; + } + return false; +} + +void TouchEventConverterEvdev::OnFileCanWriteWithoutBlocking(int /* fd */) { + // Read-only file-descriptors. + NOTREACHED(); +} + +void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { + input_event inputs[MAX_FINGERS * 6 + 1]; + ssize_t read_size = read(fd, inputs, sizeof(inputs)); + if (read_size < 0) { + if (errno == EINTR || errno == EAGAIN) + return; + if (errno != ENODEV) + PLOG(ERROR) << "error reading device " << path_.value(); + Stop(); + return; + } + + for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) { + ProcessInputEvent(inputs[i]); + } +} + +void TouchEventConverterEvdev::ProcessInputEvent(const input_event& input) { + if (input.type == EV_SYN) { + ProcessSyn(input); + } else if(syn_dropped_) { + // Do nothing. This branch indicates we have lost sync with the driver. + } else if (input.type == EV_ABS) { + if (current_slot_ >= MAX_FINGERS) { + LOG(ERROR) << "too many touch events: " << current_slot_; + return; + } + ProcessAbs(input); + } else if (input.type == EV_KEY) { + switch (input.code) { + case BTN_TOUCH: + break; + default: + NOTIMPLEMENTED() << "invalid code for EV_KEY: " << input.code; + } + } else { + NOTIMPLEMENTED() << "invalid type: " << input.type; + } +} + +void TouchEventConverterEvdev::ProcessAbs(const input_event& input) { + switch (input.code) { + case ABS_MT_TOUCH_MAJOR: + altered_slots_.set(current_slot_); + events_[current_slot_].major_ = input.value; + break; + case ABS_X: + case ABS_MT_POSITION_X: + altered_slots_.set(current_slot_); + events_[current_slot_].x_ = TuxelsToPixels(input.value, + x_min_tuxels_, + x_num_tuxels_, + x_min_pixels_, + x_num_pixels_); + break; + case ABS_Y: + case ABS_MT_POSITION_Y: + altered_slots_.set(current_slot_); + events_[current_slot_].y_ = TuxelsToPixels(input.value, + y_min_tuxels_, + y_num_tuxels_, + y_min_pixels_, + y_num_pixels_); + break; + case ABS_MT_TRACKING_ID: + altered_slots_.set(current_slot_); + if (input.value < 0) { + events_[current_slot_].type_ = ET_TOUCH_RELEASED; + } else { + events_[current_slot_].finger_ = input.value; + events_[current_slot_].type_ = ET_TOUCH_PRESSED; + } + break; + case ABS_MT_PRESSURE: + case ABS_PRESSURE: + altered_slots_.set(current_slot_); + events_[current_slot_].pressure_ = input.value - pressure_min_; + events_[current_slot_].pressure_ /= pressure_max_ - pressure_min_; + break; + case ABS_MT_SLOT: + current_slot_ = input.value; + altered_slots_.set(current_slot_); + break; + default: + NOTIMPLEMENTED() << "invalid code for EV_ABS: " << input.code; + } +} + +void TouchEventConverterEvdev::ProcessSyn(const input_event& input) { + switch (input.code) { + case SYN_REPORT: + if (syn_dropped_) { + // Have to re-initialize. + if (Reinitialize()) { + syn_dropped_ = false; + altered_slots_.reset(); + } else { + LOG(ERROR) << "failed to re-initialize device info"; + } + } else { + ReportEvents(base::TimeDelta::FromMicroseconds( + input.time.tv_sec * 1000000 + input.time.tv_usec)); + } + if (is_type_a_) + current_slot_ = 0; + break; + case SYN_MT_REPORT: + // For type A devices, we just get a stream of all current contacts, + // in some arbitrary order. + events_[current_slot_++].type_ = ET_TOUCH_PRESSED; + is_type_a_ = true; + break; + case SYN_DROPPED: + // Some buffer has overrun. We ignore all events up to and + // including the next SYN_REPORT. + syn_dropped_ = true; + break; + default: + NOTIMPLEMENTED() << "invalid code for EV_SYN: " << input.code; + } +} + +void TouchEventConverterEvdev::ReportEvents(base::TimeDelta delta) { + for (int i = 0; i < MAX_FINGERS; i++) { + if (altered_slots_[i]) { + // TODO(rikroege): Support elliptical finger regions. + TouchEvent evt( + events_[i].type_, + gfx::PointF(events_[i].x_, events_[i].y_), + /* flags */ 0, + /* touch_id */ i, + delta, + events_[i].pressure_ * kFingerWidth, + events_[i].pressure_ * kFingerWidth, + /* angle */ 0., + events_[i].pressure_); + DispatchEventToCallback(&evt); + + // Subsequent events for this finger will be touch-move until it + // is released. + events_[i].type_ = ET_TOUCH_MOVED; + } + } + altered_slots_.reset(); +} + +} // namespace ui diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h new file mode 100644 index 00000000000..668901d5f9d --- /dev/null +++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h @@ -0,0 +1,118 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ + +#include <bitset> + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/message_loop/message_pump_libevent.h" +#include "ui/events/event_constants.h" +#include "ui/events/ozone/evdev/event_converter_evdev.h" +#include "ui/events/ozone/evdev/event_device_info.h" +#include "ui/events/ozone/evdev/events_ozone_evdev_export.h" + +namespace ui { + +class TouchEvent; + +class EVENTS_OZONE_EVDEV_EXPORT TouchEventConverterEvdev + : public EventConverterEvdev, + public base::MessagePumpLibevent::Watcher { + public: + enum { + MAX_FINGERS = 11 + }; + TouchEventConverterEvdev(int fd, + base::FilePath path, + const EventDeviceInfo& info, + const EventDispatchCallback& dispatch); + virtual ~TouchEventConverterEvdev(); + + // Start & stop watching for events. + virtual void Start() OVERRIDE; + virtual void Stop() OVERRIDE; + + private: + friend class MockTouchEventConverterEvdev; + + // Unsafe part of initialization. + void Init(const EventDeviceInfo& info); + + // Overidden from base::MessagePumpLibevent::Watcher. + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + virtual bool Reinitialize(); + + void ProcessInputEvent(const input_event& input); + void ProcessAbs(const input_event& input); + void ProcessSyn(const input_event& input); + + void ReportEvents(base::TimeDelta delta); + + // Set if we have seen a SYN_DROPPED and not yet re-synced with the device. + bool syn_dropped_; + + // Set if this is a type A device (uses SYN_MT_REPORT). + bool is_type_a_; + + // Pressure values. + int pressure_min_; + int pressure_max_; // Used to normalize pressure values. + + // Input range for x-axis. + float x_min_tuxels_; + float x_num_tuxels_; + + // Input range for y-axis. + float y_min_tuxels_; + float y_num_tuxels_; + + // Output range for x-axis. + float x_min_pixels_; + float x_num_pixels_; + + // Output range for y-axis. + float y_min_pixels_; + float y_num_pixels_; + + // Touch point currently being updated from the /dev/input/event* stream. + int current_slot_; + + // File descriptor for the /dev/input/event* instance. + int fd_; + + // Path to input device. + base::FilePath path_; + + // Bit field tracking which in-progress touch points have been modified + // without a syn event. + std::bitset<MAX_FINGERS> altered_slots_; + + struct InProgressEvents { + float x_; + float y_; + int id_; // Device reported "unique" touch point id; -1 means not active + int finger_; // "Finger" id starting from 0; -1 means not active + + EventType type_; + int major_; + float pressure_; + }; + + // In-progress touch points. + InProgressEvents events_[MAX_FINGERS]; + + // Controller for watching the input fd. + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdev); +}; + +} // namespace ui + +#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_ diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter_unittest.cc b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc index d99ab720115..ce2957a1ece 100644 --- a/chromium/ui/events/ozone/evdev/touch_event_converter_unittest.cc +++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc @@ -1,4 +1,4 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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. @@ -11,13 +11,14 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" -#include "base/message_loop/message_loop.h" #include "base/posix/eintr_wrapper.h" #include "base/run_loop.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/event.h" -#include "ui/events/ozone/evdev/touch_event_converter.h" +#include "ui/events/ozone/evdev/touch_event_converter_evdev.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" namespace { @@ -28,14 +29,15 @@ static int SetNonBlocking(int fd) { return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } +const char kTestDevicePath[] = "/dev/input/test-device"; + } // namespace namespace ui { -class MockTouchEventConverterEvdev : public TouchEventConverterEvdev, - public base::MessageLoop::Dispatcher { +class MockTouchEventConverterEvdev : public TouchEventConverterEvdev { public: - MockTouchEventConverterEvdev(int a, int b); + MockTouchEventConverterEvdev(int fd, base::FilePath path); virtual ~MockTouchEventConverterEvdev() {}; void ConfigureReadMock(struct input_event* queue, @@ -51,7 +53,12 @@ class MockTouchEventConverterEvdev : public TouchEventConverterEvdev, base::RunLoop().RunUntilIdle(); } - virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE; + void DispatchCallback(Event* event) { + dispatched_events_.push_back( + new TouchEvent(*static_cast<TouchEvent*>(event))); + } + + virtual bool Reinitialize() OVERRIDE { return true; } private: int read_pipe_; @@ -62,15 +69,27 @@ class MockTouchEventConverterEvdev : public TouchEventConverterEvdev, DISALLOW_COPY_AND_ASSIGN(MockTouchEventConverterEvdev); }; -MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int a, int b) - : TouchEventConverterEvdev(a, b) { +MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int fd, + base::FilePath path) + : TouchEventConverterEvdev( + fd, + path, + EventDeviceInfo(), + base::Bind(&MockTouchEventConverterEvdev::DispatchCallback, + base::Unretained(this))) { pressure_min_ = 30; pressure_max_ = 60; + // TODO(rjkroege): Check test axes. + x_min_pixels_ = x_min_tuxels_ = 0; + x_num_pixels_ = x_num_tuxels_ = std::numeric_limits<int>::max(); + y_min_pixels_ = y_min_tuxels_ = 0; + y_num_pixels_ = y_num_tuxels_ = std::numeric_limits<int>::max(); + int fds[2]; if (pipe(fds)) - NOTREACHED() << "failed pipe(): " << strerror(errno); + PLOG(FATAL) << "failed pipe"; DCHECK(SetNonBlocking(fds[0]) == 0) << "SetNonBlocking for pipe fd[0] failed, errno: " << errno; @@ -80,12 +99,6 @@ MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int a, int b) write_pipe_ = fds[1]; } -bool MockTouchEventConverterEvdev::Dispatch(const base::NativeEvent& event) { - ui::TouchEvent* ev = new ui::TouchEvent(event); - dispatched_events_.push_back(ev); - return true; -} - void MockTouchEventConverterEvdev::ConfigureReadMock(struct input_event* queue, long read_this_many, long queue_index) { @@ -106,10 +119,18 @@ class TouchEventConverterEvdevTest : public testing::Test { // Overridden from testing::Test: virtual void SetUp() OVERRIDE { - loop_ = new base::MessageLoop(base::MessageLoop::TYPE_UI); - device_ = new ui::MockTouchEventConverterEvdev(-1, 2); - base::MessagePumpOzone::Current()->AddDispatcherForRootWindow(device_); + // Set up pipe to satisfy message pump (unused). + int evdev_io[2]; + if (pipe(evdev_io)) + PLOG(FATAL) << "failed pipe"; + events_in_ = evdev_io[0]; + events_out_ = evdev_io[1]; + + loop_ = new base::MessageLoopForUI; + device_ = new ui::MockTouchEventConverterEvdev( + events_in_, base::FilePath(kTestDevicePath)); } + virtual void TearDown() OVERRIDE { delete device_; delete loop_; @@ -120,6 +141,10 @@ class TouchEventConverterEvdevTest : public testing::Test { private: base::MessageLoop* loop_; ui::MockTouchEventConverterEvdev* device_; + + int events_out_; + int events_in_; + DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdevTest); }; @@ -396,3 +421,61 @@ TEST_F(TouchEventConverterEvdevTest, TwoFingerGesture) { EXPECT_FLOAT_EQ(.5f, ev1->force()); EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle()); } + +TEST_F(TouchEventConverterEvdevTest, TypeA) { + ui::MockTouchEventConverterEvdev* dev = device(); + + struct input_event mock_kernel_queue_press0[] = { + {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3}, + {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, + {{0, 0}, EV_SYN, SYN_MT_REPORT, 0}, + {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 61}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 71}, + {{0, 0}, EV_SYN, SYN_MT_REPORT, 0}, + {{0, 0}, EV_SYN, SYN_REPORT, 0} + }; + + // Check that two events are generated. + dev->ConfigureReadMock(mock_kernel_queue_press0, 10, 0); + dev->ReadNow(); + EXPECT_EQ(2u, dev->size()); +} + +TEST_F(TouchEventConverterEvdevTest, Unsync) { + ui::MockTouchEventConverterEvdev* dev = device(); + + struct input_event mock_kernel_queue_press0[] = { + {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684}, + {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3}, + {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0} + }; + + dev->ConfigureReadMock(mock_kernel_queue_press0, 6, 0); + dev->ReadNow(); + EXPECT_EQ(1u, dev->size()); + + // Prepare a move with a drop. + struct input_event mock_kernel_queue_move0[] = { + {{0, 0}, EV_SYN, SYN_DROPPED, 0}, + {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0} + }; + + // Verify that we didn't receive it/ + dev->ConfigureReadMock(mock_kernel_queue_move0, 3, 0); + dev->ReadNow(); + EXPECT_EQ(1u, dev->size()); + + struct input_event mock_kernel_queue_move1[] = { + {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0} + }; + + // Verify that it re-syncs after a SYN_REPORT. + dev->ConfigureReadMock(mock_kernel_queue_move1, 2, 0); + dev->ReadNow(); + EXPECT_EQ(2u, dev->size()); +} diff --git a/chromium/ui/events/ozone/event_converter_ozone.cc b/chromium/ui/events/ozone/event_converter_ozone.cc deleted file mode 100644 index 2bb6164e80b..00000000000 --- a/chromium/ui/events/ozone/event_converter_ozone.cc +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2013 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/ozone/event_converter_ozone.h" - -#include "base/bind.h" -#include "base/message_loop/message_loop.h" -#include "base/message_loop/message_pump_ozone.h" -#include "ui/events/event.h" - -namespace { - -void DispatchEventHelper(scoped_ptr<ui::Event> key) { - base::MessagePumpOzone::Current()->Dispatch(key.get()); -} - -} // namespace - -namespace ui { - -EventConverterOzone::EventConverterOzone() { -} - -EventConverterOzone::~EventConverterOzone() { -} - -void EventConverterOzone::DispatchEvent(scoped_ptr<ui::Event> event) { - base::MessageLoop::current()->PostTask( - FROM_HERE, base::Bind(&DispatchEventHelper, base::Passed(&event))); -} - -} // namespace ui diff --git a/chromium/ui/events/ozone/event_converter_ozone.h b/chromium/ui/events/ozone/event_converter_ozone.h deleted file mode 100644 index 90826c26580..00000000000 --- a/chromium/ui/events/ozone/event_converter_ozone.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2013 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. - -#ifndef UI_EVENTS_OZONE_EVENT_CONVERTER_OZONE_H_ -#define UI_EVENTS_OZONE_EVENT_CONVERTER_OZONE_H_ - -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_pump_libevent.h" -#include "ui/events/events_export.h" - -namespace ui { -class Event; - -// In ozone, Chrome reads events from file descriptors created from Linux device -// drivers. The |MessagePumpLibevent::Watcher| parent class provides the -// functionality to watch a file descriptor for the arrival of new data and -// notify its subclasses. Device-specific event converters turn bytes read from -// the file descriptor into |ui::Event| instances. This class provides the -// functionality needed in common across all converters: dispatching the -// |ui::Event| to aura. -class EVENTS_EXPORT EventConverterOzone - : public base::MessagePumpLibevent::Watcher { - public: - EventConverterOzone(); - virtual ~EventConverterOzone(); - - protected: - // Subclasses should use this method to post a task that will dispatch - // |event| from the UI message loop. This method takes ownership of - // |event|. |event| will be deleted at the end of the posted task. - virtual void DispatchEvent(scoped_ptr<ui::Event> event); - - private: - DISALLOW_COPY_AND_ASSIGN(EventConverterOzone); -}; - -} // namespace ui - -#endif // UI_EVENTS_OZONE_EVENT_CONVERTER_OZONE_H_ diff --git a/chromium/ui/events/ozone/event_factory_ozone.cc b/chromium/ui/events/ozone/event_factory_ozone.cc deleted file mode 100644 index b051e21daee..00000000000 --- a/chromium/ui/events/ozone/event_factory_ozone.cc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2013 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/ozone/event_factory_ozone.h" - -#include "base/command_line.h" -#include "base/message_loop/message_pump_ozone.h" -#include "base/stl_util.h" -#include "base/strings/stringprintf.h" -#include "ui/events/event_switches.h" - -namespace ui { - -// static -EventFactoryOzone* EventFactoryOzone::impl_ = NULL; - -EventFactoryOzone::EventFactoryOzone() {} - -EventFactoryOzone::~EventFactoryOzone() { - // Always delete watchers before converters to prevent a possible race. - STLDeleteValues<std::map<int, FDWatcher> >(&watchers_); - STLDeleteValues<std::map<int, Converter> >(&converters_); -} - -EventFactoryOzone* EventFactoryOzone::GetInstance() { - CHECK(impl_) << "No EventFactoryOzone implementation set."; - return impl_; -} - -void EventFactoryOzone::SetInstance(EventFactoryOzone* impl) { impl_ = impl; } - -void EventFactoryOzone::StartProcessingEvents() {} - -void EventFactoryOzone::AddEventConverter( - int fd, - scoped_ptr<EventConverterOzone> converter) { - CHECK(watchers_.count(fd) == 0 && converters_.count(fd) == 0); - - FDWatcher watcher = new base::MessagePumpLibevent::FileDescriptorWatcher(); - - base::MessagePumpOzone::Current()->WatchFileDescriptor( - fd, - true, - base::MessagePumpLibevent::WATCH_READ, - watcher, - converter.get()); - - converters_[fd] = converter.release(); - watchers_[fd] = watcher; -} - -void EventFactoryOzone::RemoveEventConverter(int fd) { - // Always delete watchers before converters to prevent a possible race. - delete watchers_[fd]; - delete converters_[fd]; - watchers_.erase(fd); - converters_.erase(fd); -} - -} // namespace ui diff --git a/chromium/ui/events/ozone/event_factory_ozone.h b/chromium/ui/events/ozone/event_factory_ozone.h deleted file mode 100644 index 389946ae339..00000000000 --- a/chromium/ui/events/ozone/event_factory_ozone.h +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2013 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. - -#ifndef UI_EVENTS_OZONE_EVENT_FACTORY_OZONE_H_ -#define UI_EVENTS_OZONE_EVENT_FACTORY_OZONE_H_ - -#include <map> - -#include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_pump_libevent.h" -#include "ui/events/events_export.h" -#include "ui/events/ozone/event_converter_ozone.h" - -namespace ui { - -// Creates and dispatches |ui.Event|'s. Ozone assumes that events arrive on file -// descriptors with one |EventConverterOzone| instance for each descriptor. -// Ozone presumes that the set of file desctiprtors can vary at runtime so this -// class supports dynamically adding and removing |EventConverterOzone| -// instances as necessary. -class EVENTS_EXPORT EventFactoryOzone { - public: - EventFactoryOzone(); - virtual ~EventFactoryOzone(); - - // Called from RootWindowHostOzone to initialize and start processing events. - // This should create the initial set of converters, and potentially arrange - // for more converters to be created as new event sources become available. - // No events processing should happen until this is called. All processes have - // an EventFactoryOzone but not all of them should process events. In chrome, - // events are dispatched in the browser process on the UI thread. - virtual void StartProcessingEvents(); - - // Returns the static instance last set using SetInstance(). - static EventFactoryOzone* GetInstance(); - - // Sets the implementation delegate. Ownership is retained by the caller. - static void SetInstance(EventFactoryOzone*); - - // Add an |EventConverterOzone| instances for the given file descriptor. - // Transfers ownership of the |EventConverterOzone| to this class. - void AddEventConverter(int fd, scoped_ptr<EventConverterOzone> converter); - - // Remote the |EventConverterOzone| - void RemoveEventConverter(int fd); - - private: - // |EventConverterOzone| for each file descriptor. - typedef EventConverterOzone* Converter; - typedef base::MessagePumpLibevent::FileDescriptorWatcher* FDWatcher; - std::map<int, Converter> converters_; - std::map<int, FDWatcher> watchers_; - - static EventFactoryOzone* impl_; // not owned - - DISALLOW_COPY_AND_ASSIGN(EventFactoryOzone); -}; - -} // namespace ui - -#endif // UI_EVENTS_OZONE_EVENT_FACTORY_OZONE_H_ diff --git a/chromium/ui/events/ozone/events_ozone.cc b/chromium/ui/events/ozone/events_ozone.cc index 4655d411923..7d3f627d078 100644 --- a/chromium/ui/events/ozone/events_ozone.cc +++ b/chromium/ui/events/ozone/events_ozone.cc @@ -58,9 +58,10 @@ const char* CodeFromNative(const base::NativeEvent& native_event) { return event->code().c_str(); } -bool IsMouseEvent(const base::NativeEvent& native_event) { - const ui::Event* e = static_cast<const ui::Event*>(native_event); - return e->IsMouseEvent(); +uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) { + const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event); + DCHECK(event->IsKeyEvent()); + return event->platform_keycode(); } gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { @@ -70,6 +71,13 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { return event->offset(); } +base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { + return NULL; +} + +void ReleaseCopiedNativeEvent(const base::NativeEvent& event) { +} + void ClearTouchIdIfReleased(const base::NativeEvent& xev) { } @@ -145,16 +153,6 @@ bool IsTouchpadEvent(const base::NativeEvent& event) { return false; } -bool IsNoopEvent(const base::NativeEvent& event) { - NOTIMPLEMENTED(); - return false; -} - -base::NativeEvent CreateNoopEvent() { - NOTIMPLEMENTED(); - return NULL; -} - int GetModifiersFromKeyState() { NOTIMPLEMENTED(); return 0; diff --git a/chromium/ui/events/ozone/events_ozone.gyp b/chromium/ui/events/ozone/events_ozone.gyp new file mode 100644 index 00000000000..e20e75af203 --- /dev/null +++ b/chromium/ui/events/ozone/events_ozone.gyp @@ -0,0 +1,97 @@ +# Copyright 2014 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [{ + 'target_name': 'events_ozone', + 'type': '<(component)', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'defines': [ + 'EVENTS_OZONE_IMPLEMENTATION', + ], + 'sources': [ + 'device/device_event.cc', + 'device/device_event.h', + 'device/device_event_observer.h', + 'device/device_manager.cc', + 'device/device_manager.h', + 'device/device_manager_manual.cc', + 'device/device_manager_manual.h', + 'device/udev/device_manager_udev.cc', + 'device/udev/device_manager_udev.h', + 'events_ozone_export.h', + ], + 'conditions': [ + ['use_udev==0', { + 'sources/': [ + ['exclude', '_udev\\.(h|cc)$'], + ], + }], + ['use_ozone_evdev==1 and use_udev==1', { + 'dependencies': [ + '<(DEPTH)/device/udev_linux/udev.gyp:udev_linux', + ], + }], + ], + }, { + 'target_name': 'events_ozone_evdev', + 'type': '<(component)', + 'dependencies': [ + '../../../base/base.gyp:base', + '../../gfx/gfx.gyp:gfx', + '../../ozone/ozone.gyp:ozone_base', + '../platform/events_platform.gyp:events_platform', + 'events_ozone', + ], + 'defines': [ + 'EVENTS_OZONE_EVDEV_IMPLEMENTATION', + ], + 'sources': [ + 'evdev/libgestures_glue/event_reader_libevdev_cros.cc', + 'evdev/libgestures_glue/event_reader_libevdev_cros.h', + 'evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc', + 'evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h', + 'evdev/libgestures_glue/gesture_logging.cc', + 'evdev/libgestures_glue/gesture_logging.h', + 'evdev/libgestures_glue/gesture_timer_provider.cc', + 'evdev/libgestures_glue/gesture_timer_provider.h', + 'evdev/event_converter_evdev.cc', + 'evdev/event_converter_evdev.h', + 'evdev/event_device_info.cc', + 'evdev/event_device_info.h', + 'evdev/event_factory_evdev.cc', + 'evdev/event_factory_evdev.h', + 'evdev/event_modifiers_evdev.cc', + 'evdev/event_modifiers_evdev.h', + 'evdev/events_ozone_evdev_export.h', + 'evdev/key_event_converter_evdev.cc', + 'evdev/key_event_converter_evdev.h', + 'evdev/touch_event_converter_evdev.cc', + 'evdev/touch_event_converter_evdev.h', + ], + 'conditions': [ + ['use_ozone_evdev==1 and use_evdev_gestures==1', { + 'dependencies': [ + '<(DEPTH)/build/linux/system.gyp:libgestures', + '<(DEPTH)/build/linux/system.gyp:libevdev-cros', + ], + 'defines': [ + 'USE_EVDEV_GESTURES', + ], + }, { + 'sources/': [ + ['exclude', '^evdev/libgestures_glue/'], + ], + }], + ['use_ozone_evdev==1', { + 'defines': ['USE_OZONE_EVDEV=1'], + }], + ], + }] +} diff --git a/chromium/ui/events/ozone/events_ozone_export.h b/chromium/ui/events/ozone/events_ozone_export.h new file mode 100644 index 00000000000..bda8814eda9 --- /dev/null +++ b/chromium/ui/events/ozone/events_ozone_export.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_ +#define UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(EVENTS_OZONE_IMPLEMENTATION) +#define EVENTS_OZONE_EXPORT __declspec(dllexport) +#else +#define EVENTS_OZONE_EXPORT __declspec(dllimport) +#endif // defined(EVENTS_OZONE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(EVENTS_OZONE_IMPLEMENTATION) +#define EVENTS_OZONE_EXPORT __attribute__((visibility("default"))) +#else +#define EVENTS_OZONE_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define EVENTS_OZONE_EXPORT +#endif + +#endif // UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_ diff --git a/chromium/ui/events/platform/BUILD.gn b/chromium/ui/events/platform/BUILD.gn new file mode 100644 index 00000000000..3a20219a33c --- /dev/null +++ b/chromium/ui/events/platform/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright 2014 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. + +import("//build/config/ui.gni") + +component("platform") { + sources = [ + "platform_event_dispatcher.h", + "platform_event_observer.h", + "platform_event_source.cc", + "platform_event_source.h", + "platform_event_source_stub.cc", + "platform_event_types.h", + "scoped_event_dispatcher.cc", + "scoped_event_dispatcher.h", + ] + + defines = [ + "EVENTS_IMPLEMENTATION", + ] + + deps = [ + "//base" + ] + + if (use_x11) { + sources -= [ + "platform_event_source_stub.cc", + ] + } +} diff --git a/chromium/ui/events/platform/events_platform.gyp b/chromium/ui/events/platform/events_platform.gyp new file mode 100644 index 00000000000..d1b3f6bfe3e --- /dev/null +++ b/chromium/ui/events/platform/events_platform.gyp @@ -0,0 +1,36 @@ +# Copyright 2014 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [{ + 'target_name': 'events_platform', + 'type': '<(component)', + 'dependencies': [ + '../../../base/base.gyp:base', + ], + 'defines': [ + 'EVENTS_IMPLEMENTATION', + ], + 'sources': [ + 'platform_event_dispatcher.h', + 'platform_event_observer.h', + 'platform_event_source.cc', + 'platform_event_source.h', + 'platform_event_source_stub.cc', + 'platform_event_types.h', + 'scoped_event_dispatcher.cc', + 'scoped_event_dispatcher.h', + ], + 'conditions': [ + ['use_x11==1', { + 'sources!': [ + 'platform_event_source_stub.cc', + ], + }], + ], + }], +} diff --git a/chromium/ui/events/platform/platform_event_dispatcher.h b/chromium/ui/events/platform/platform_event_dispatcher.h new file mode 100644 index 00000000000..bda035d8f0f --- /dev/null +++ b/chromium/ui/events/platform/platform_event_dispatcher.h @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_ +#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_ + +#include "base/basictypes.h" +#include "ui/events/events_export.h" +#include "ui/events/platform/platform_event_types.h" + +namespace ui { + +// See documentation for |PlatformEventDispatcher::DispatchEvent()| for +// explanation of the meaning of the flags. +enum PostDispatchAction { + POST_DISPATCH_NONE = 0x0, + POST_DISPATCH_PERFORM_DEFAULT = 0x1, + POST_DISPATCH_STOP_PROPAGATION = 0x2, +}; + +// PlatformEventDispatcher receives events from a PlatformEventSource and +// dispatches them. +class EVENTS_EXPORT PlatformEventDispatcher { + public: + // Returns whether this dispatcher wants to dispatch |event|. + virtual bool CanDispatchEvent(const PlatformEvent& event) = 0; + + // Dispatches |event|. If this is not the default dispatcher, then the + // dispatcher can request that the default dispatcher gets a chance to + // dispatch the event by setting POST_DISPATCH_PERFORM_DEFAULT to the return + // value. If the dispatcher has processed the event, and no other dispatcher + // should be allowed to dispatch the event, then the dispatcher should set + // POST_DISPATCH_STOP_PROPAGATION flag on the return value. + virtual uint32_t DispatchEvent(const PlatformEvent& event) = 0; + + protected: + virtual ~PlatformEventDispatcher() {} +}; + +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_ diff --git a/chromium/ui/events/platform/platform_event_observer.h b/chromium/ui/events/platform/platform_event_observer.h new file mode 100644 index 00000000000..37aec638ea5 --- /dev/null +++ b/chromium/ui/events/platform/platform_event_observer.h @@ -0,0 +1,29 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_ +#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_ + +#include "ui/events/events_export.h" +#include "ui/events/platform/platform_event_types.h" + +namespace ui { + +// PlatformEventObserver can be installed on a PlatformEventSource, and it +// receives all events that are dispatched to the dispatchers. +class EVENTS_EXPORT PlatformEventObserver { + public: + // This is called before the dispatcher receives the event. + virtual void WillProcessEvent(const PlatformEvent& event) = 0; + + // This is called after the event has been dispatched to the dispatcher(s). + virtual void DidProcessEvent(const PlatformEvent& event) = 0; + + protected: + virtual ~PlatformEventObserver() {} +}; + +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_ diff --git a/chromium/ui/events/platform/platform_event_source.cc b/chromium/ui/events/platform/platform_event_source.cc new file mode 100644 index 00000000000..9607ab78232 --- /dev/null +++ b/chromium/ui/events/platform/platform_event_source.cc @@ -0,0 +1,111 @@ +// Copyright 2014 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/platform/platform_event_source.h" + +#include <algorithm> + +#include "base/message_loop/message_loop.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_observer.h" +#include "ui/events/platform/scoped_event_dispatcher.h" + +namespace ui { + +// static +PlatformEventSource* PlatformEventSource::instance_ = NULL; + +PlatformEventSource::PlatformEventSource() + : overridden_dispatcher_(NULL), + overridden_dispatcher_restored_(false) { + CHECK(!instance_) << "Only one platform event source can be created."; + instance_ = this; +} + +PlatformEventSource::~PlatformEventSource() { + CHECK_EQ(this, instance_); + instance_ = NULL; +} + +PlatformEventSource* PlatformEventSource::GetInstance() { return instance_; } + +void PlatformEventSource::AddPlatformEventDispatcher( + PlatformEventDispatcher* dispatcher) { + CHECK(dispatcher); + dispatchers_.AddObserver(dispatcher); + OnDispatcherListChanged(); +} + +void PlatformEventSource::RemovePlatformEventDispatcher( + PlatformEventDispatcher* dispatcher) { + dispatchers_.RemoveObserver(dispatcher); + OnDispatcherListChanged(); +} + +scoped_ptr<ScopedEventDispatcher> PlatformEventSource::OverrideDispatcher( + PlatformEventDispatcher* dispatcher) { + CHECK(dispatcher); + overridden_dispatcher_restored_ = false; + return make_scoped_ptr( + new ScopedEventDispatcher(&overridden_dispatcher_, dispatcher)); +} + +void PlatformEventSource::AddPlatformEventObserver( + PlatformEventObserver* observer) { + CHECK(observer); + observers_.AddObserver(observer); +} + +void PlatformEventSource::RemovePlatformEventObserver( + PlatformEventObserver* observer) { + observers_.RemoveObserver(observer); +} + +uint32_t PlatformEventSource::DispatchEvent(PlatformEvent platform_event) { + uint32_t action = POST_DISPATCH_PERFORM_DEFAULT; + + FOR_EACH_OBSERVER(PlatformEventObserver, observers_, + WillProcessEvent(platform_event)); + // Give the overridden dispatcher a chance to dispatch the event first. + if (overridden_dispatcher_) + action = overridden_dispatcher_->DispatchEvent(platform_event); + + if ((action & POST_DISPATCH_PERFORM_DEFAULT) && + dispatchers_.might_have_observers()) { + ObserverList<PlatformEventDispatcher>::Iterator iter(dispatchers_); + while (PlatformEventDispatcher* dispatcher = iter.GetNext()) { + if (dispatcher->CanDispatchEvent(platform_event)) + action = dispatcher->DispatchEvent(platform_event); + if (action & POST_DISPATCH_STOP_PROPAGATION) + break; + } + } + FOR_EACH_OBSERVER(PlatformEventObserver, observers_, + DidProcessEvent(platform_event)); + + // If an overridden dispatcher has been destroyed, then the platform + // event-source should halt dispatching the current stream of events, and wait + // until the next message-loop iteration for dispatching events. This lets any + // nested message-loop to unwind correctly and any new dispatchers to receive + // the correct sequence of events. + if (overridden_dispatcher_restored_) + StopCurrentEventStream(); + + overridden_dispatcher_restored_ = false; + + return action; +} + +void PlatformEventSource::StopCurrentEventStream() { +} + +void PlatformEventSource::OnDispatcherListChanged() { +} + +void PlatformEventSource::OnOverriddenDispatcherRestored() { + CHECK(overridden_dispatcher_); + overridden_dispatcher_restored_ = true; +} + +} // namespace ui diff --git a/chromium/ui/events/platform/platform_event_source.h b/chromium/ui/events/platform/platform_event_source.h new file mode 100644 index 00000000000..87b553197a3 --- /dev/null +++ b/chromium/ui/events/platform/platform_event_source.h @@ -0,0 +1,102 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_ +#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_ + +#include <map> +#include <vector> + +#include "base/auto_reset.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "ui/events/events_export.h" +#include "ui/events/platform/platform_event_types.h" + +namespace ui { + +class Event; +class PlatformEventDispatcher; +class PlatformEventObserver; +class ScopedEventDispatcher; + +// PlatformEventSource receives events from a source and dispatches the events +// to the appropriate dispatchers. +class EVENTS_EXPORT PlatformEventSource { + public: + virtual ~PlatformEventSource(); + + static PlatformEventSource* GetInstance(); + + // Adds a dispatcher to the dispatcher list. If a dispatcher is added during + // dispatching an event, then the newly added dispatcher also receives that + // event. + void AddPlatformEventDispatcher(PlatformEventDispatcher* dispatcher); + + // Removes a dispatcher from the dispatcher list. Dispatchers can safely be + // removed from the dispatcher list during an event is being dispatched, + // without affecting the dispatch of the event to other existing dispatchers. + void RemovePlatformEventDispatcher(PlatformEventDispatcher* dispatcher); + + // Installs a PlatformEventDispatcher that receives all the events. The + // dispatcher can process the event, or request that the default dispatchers + // be invoked by setting |POST_DISPATCH_PERFORM_DEFAULT| flag from the + // |DispatchEvent()| override. + // The returned |ScopedEventDispatcher| object is a handler for the overridden + // dispatcher. When this handler is destroyed, it removes the overridden + // dispatcher, and restores the previous override-dispatcher (or NULL if there + // wasn't any). + scoped_ptr<ScopedEventDispatcher> OverrideDispatcher( + PlatformEventDispatcher* dispatcher); + + void AddPlatformEventObserver(PlatformEventObserver* observer); + void RemovePlatformEventObserver(PlatformEventObserver* observer); + + static scoped_ptr<PlatformEventSource> CreateDefault(); + + protected: + PlatformEventSource(); + + // Dispatches |platform_event| to the dispatchers. If there is an override + // dispatcher installed using |OverrideDispatcher()|, then that dispatcher + // receives the event first. |POST_DISPATCH_QUIT_LOOP| flag is set in the + // returned value if the event-source should stop dispatching events at the + // current message-loop iteration. + virtual uint32_t DispatchEvent(PlatformEvent platform_event); + + private: + friend class ScopedEventDispatcher; + static PlatformEventSource* instance_; + + // Called to indicate that the source should stop dispatching the current + // stream of events and wait until the next iteration of the message-loop to + // dispatch the rest of the events. + virtual void StopCurrentEventStream(); + + // This is invoked when the list of dispatchers changes (i.e. a new dispatcher + // is added, or a dispatcher is removed). + virtual void OnDispatcherListChanged(); + + void OnOverriddenDispatcherRestored(); + + // Use an ObserverList<> instead of an std::vector<> to store the list of + // dispatchers, so that adding/removing dispatchers during an event dispatch + // is well-defined. + typedef ObserverList<PlatformEventDispatcher> PlatformEventDispatcherList; + PlatformEventDispatcherList dispatchers_; + PlatformEventDispatcher* overridden_dispatcher_; + + // Used to keep track of whether the current override-dispatcher has been + // reset and a previous override-dispatcher has been restored. + bool overridden_dispatcher_restored_; + + ObserverList<PlatformEventObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(PlatformEventSource); +}; + +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_ diff --git a/chromium/ui/events/platform/platform_event_source_stub.cc b/chromium/ui/events/platform/platform_event_source_stub.cc new file mode 100644 index 00000000000..57e2a61c3dc --- /dev/null +++ b/chromium/ui/events/platform/platform_event_source_stub.cc @@ -0,0 +1,13 @@ +// Copyright 2014 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/platform/platform_event_source.h" + +namespace ui { + +scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() { + return scoped_ptr<PlatformEventSource>(); +} + +} // namespace ui diff --git a/chromium/ui/events/platform/platform_event_source_unittest.cc b/chromium/ui/events/platform/platform_event_source_unittest.cc new file mode 100644 index 00000000000..cdbd0c4b35c --- /dev/null +++ b/chromium/ui/events/platform/platform_event_source_unittest.cc @@ -0,0 +1,787 @@ +// Copyright 2014 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/platform/platform_event_source.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_observer.h" +#include "ui/events/platform/scoped_event_dispatcher.h" + +namespace ui { + +namespace { + +scoped_ptr<PlatformEvent> CreatePlatformEvent() { + scoped_ptr<PlatformEvent> event(new PlatformEvent()); + memset(event.get(), 0, sizeof(PlatformEvent)); + return event.Pass(); +} + +template <typename T> +void DestroyScopedPtr(scoped_ptr<T> object) {} + +void RemoveDispatcher(PlatformEventDispatcher* dispatcher) { + PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(dispatcher); +} + +void RemoveDispatchers(PlatformEventDispatcher* first, + PlatformEventDispatcher* second) { + PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(first); + PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(second); +} + +void AddDispatcher(PlatformEventDispatcher* dispatcher) { + PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(dispatcher); +} + +} // namespace + +class TestPlatformEventSource : public PlatformEventSource { + public: + TestPlatformEventSource() + : stop_stream_(false) { + } + virtual ~TestPlatformEventSource() {} + + uint32_t Dispatch(const PlatformEvent& event) { return DispatchEvent(event); } + + // Dispatches the stream of events, and returns the number of events that are + // dispatched before it is requested to stop. + size_t DispatchEventStream(const ScopedVector<PlatformEvent>& events) { + stop_stream_ = false; + for (size_t count = 0; count < events.size(); ++count) { + DispatchEvent(*events[count]); + if (stop_stream_) + return count + 1; + } + return events.size(); + } + + // PlatformEventSource: + virtual void StopCurrentEventStream() OVERRIDE { + stop_stream_ = true; + } + + private: + bool stop_stream_; + DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource); +}; + +class TestPlatformEventDispatcher : public PlatformEventDispatcher { + public: + TestPlatformEventDispatcher(int id, std::vector<int>* list) + : id_(id), + list_(list), + post_dispatch_action_(POST_DISPATCH_NONE), + stop_stream_(false) { + PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); + } + virtual ~TestPlatformEventDispatcher() { + PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); + } + + void set_post_dispatch_action(uint32_t action) { + post_dispatch_action_ = action; + } + + protected: + // PlatformEventDispatcher: + virtual bool CanDispatchEvent(const PlatformEvent& event) OVERRIDE { + return true; + } + + virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE { + list_->push_back(id_); + return post_dispatch_action_; + } + + private: + int id_; + std::vector<int>* list_; + uint32_t post_dispatch_action_; + bool stop_stream_; + + DISALLOW_COPY_AND_ASSIGN(TestPlatformEventDispatcher); +}; + +class TestPlatformEventObserver : public PlatformEventObserver { + public: + TestPlatformEventObserver(int id, std::vector<int>* list) + : id_(id), list_(list) { + PlatformEventSource::GetInstance()->AddPlatformEventObserver(this); + } + virtual ~TestPlatformEventObserver() { + PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this); + } + + protected: + // PlatformEventObserver: + virtual void WillProcessEvent(const PlatformEvent& event) OVERRIDE { + list_->push_back(id_); + } + + virtual void DidProcessEvent(const PlatformEvent& event) OVERRIDE {} + + private: + int id_; + std::vector<int>* list_; + + DISALLOW_COPY_AND_ASSIGN(TestPlatformEventObserver); +}; + +class PlatformEventTest : public testing::Test { + public: + PlatformEventTest() {} + virtual ~PlatformEventTest() {} + + TestPlatformEventSource* source() { return source_.get(); } + + protected: + // testing::Test: + virtual void SetUp() OVERRIDE { + source_.reset(new TestPlatformEventSource()); + } + + private: + scoped_ptr<TestPlatformEventSource> source_; + + DISALLOW_COPY_AND_ASSIGN(PlatformEventTest); +}; + +// Tests that a dispatcher receives an event. +TEST_F(PlatformEventTest, DispatcherBasic) { + std::vector<int> list_dispatcher; + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + EXPECT_EQ(0u, list_dispatcher.size()); + { + TestPlatformEventDispatcher dispatcher(1, &list_dispatcher); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(1u, list_dispatcher.size()); + EXPECT_EQ(1, list_dispatcher[0]); + } + + list_dispatcher.clear(); + event = CreatePlatformEvent(); + source()->Dispatch(*event); + EXPECT_EQ(0u, list_dispatcher.size()); +} + +// Tests that dispatchers receive events in the correct order. +TEST_F(PlatformEventTest, DispatcherOrder) { + std::vector<int> list_dispatcher; + int sequence[] = {21, 3, 6, 45}; + ScopedVector<TestPlatformEventDispatcher> dispatchers; + for (size_t i = 0; i < arraysize(sequence); ++i) { + dispatchers.push_back( + new TestPlatformEventDispatcher(sequence[i], &list_dispatcher)); + } + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(arraysize(sequence), list_dispatcher.size()); + EXPECT_EQ(std::vector<int>(sequence, sequence + arraysize(sequence)), + list_dispatcher); +} + +// Tests that if a dispatcher consumes the event, the subsequent dispatchers do +// not receive the event. +TEST_F(PlatformEventTest, DispatcherConsumesEventToStopDispatch) { + std::vector<int> list_dispatcher; + TestPlatformEventDispatcher first(12, &list_dispatcher); + TestPlatformEventDispatcher second(23, &list_dispatcher); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list_dispatcher.size()); + EXPECT_EQ(12, list_dispatcher[0]); + EXPECT_EQ(23, list_dispatcher[1]); + list_dispatcher.clear(); + + first.set_post_dispatch_action(POST_DISPATCH_STOP_PROPAGATION); + event = CreatePlatformEvent(); + source()->Dispatch(*event); + ASSERT_EQ(1u, list_dispatcher.size()); + EXPECT_EQ(12, list_dispatcher[0]); +} + +// Tests that observers receive events. +TEST_F(PlatformEventTest, ObserverBasic) { + std::vector<int> list_observer; + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + EXPECT_EQ(0u, list_observer.size()); + { + TestPlatformEventObserver observer(31, &list_observer); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(1u, list_observer.size()); + EXPECT_EQ(31, list_observer[0]); + } + + list_observer.clear(); + event = CreatePlatformEvent(); + source()->Dispatch(*event); + EXPECT_EQ(0u, list_observer.size()); +} + +// Tests that observers receive events in the correct order. +TEST_F(PlatformEventTest, ObserverOrder) { + std::vector<int> list_observer; + const int sequence[] = {21, 3, 6, 45}; + ScopedVector<TestPlatformEventObserver> observers; + for (size_t i = 0; i < arraysize(sequence); ++i) { + observers.push_back( + new TestPlatformEventObserver(sequence[i], &list_observer)); + } + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(arraysize(sequence), list_observer.size()); + EXPECT_EQ(std::vector<int>(sequence, sequence + arraysize(sequence)), + list_observer); +} + +// Tests that observers and dispatchers receive events in the correct order. +TEST_F(PlatformEventTest, DispatcherAndObserverOrder) { + std::vector<int> list; + TestPlatformEventDispatcher first_d(12, &list); + TestPlatformEventObserver first_o(10, &list); + TestPlatformEventDispatcher second_d(23, &list); + TestPlatformEventObserver second_o(20, &list); + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + const int expected[] = {10, 20, 12, 23}; + EXPECT_EQ(std::vector<int>(expected, expected + arraysize(expected)), list); +} + +// Tests that an overridden dispatcher receives events before the default +// dispatchers. +TEST_F(PlatformEventTest, OverriddenDispatcherBasic) { + std::vector<int> list; + TestPlatformEventDispatcher dispatcher(10, &list); + TestPlatformEventObserver observer(15, &list); + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(10, list[1]); + list.clear(); + + TestPlatformEventDispatcher overriding_dispatcher(20, &list); + source()->RemovePlatformEventDispatcher(&overriding_dispatcher); + scoped_ptr<ScopedEventDispatcher> handle = + source()->OverrideDispatcher(&overriding_dispatcher); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(20, list[1]); +} + +// Tests that an overridden dispatcher can request that the default dispatchers +// can dispatch the events. +TEST_F(PlatformEventTest, OverriddenDispatcherInvokeDefaultDispatcher) { + std::vector<int> list; + TestPlatformEventDispatcher dispatcher(10, &list); + TestPlatformEventObserver observer(15, &list); + TestPlatformEventDispatcher overriding_dispatcher(20, &list); + source()->RemovePlatformEventDispatcher(&overriding_dispatcher); + scoped_ptr<ScopedEventDispatcher> handle = + source()->OverrideDispatcher(&overriding_dispatcher); + overriding_dispatcher.set_post_dispatch_action(POST_DISPATCH_PERFORM_DEFAULT); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // First the observer, then the overriding dispatcher, then the default + // dispatcher. + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(20, list[1]); + EXPECT_EQ(10, list[2]); + list.clear(); + + // Install a second overriding dispatcher. + TestPlatformEventDispatcher second_overriding(50, &list); + source()->RemovePlatformEventDispatcher(&second_overriding); + scoped_ptr<ScopedEventDispatcher> second_override_handle = + source()->OverrideDispatcher(&second_overriding); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(50, list[1]); + list.clear(); + + second_overriding.set_post_dispatch_action(POST_DISPATCH_PERFORM_DEFAULT); + source()->Dispatch(*event); + // First the observer, then the second overriding dispatcher, then the default + // dispatcher. + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(50, list[1]); + EXPECT_EQ(10, list[2]); +} + +// Runs a callback during an event dispatch. +class RunCallbackDuringDispatch : public TestPlatformEventDispatcher { + public: + RunCallbackDuringDispatch(int id, std::vector<int>* list) + : TestPlatformEventDispatcher(id, list) {} + virtual ~RunCallbackDuringDispatch() {} + + void set_callback(const base::Closure& callback) { + callback_ = callback; + } + + protected: + // PlatformEventDispatcher: + virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE { + if (!callback_.is_null()) + callback_.Run(); + return TestPlatformEventDispatcher::DispatchEvent(event); + } + + private: + base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(RunCallbackDuringDispatch); +}; + +// Test that if a dispatcher removes another dispatcher that is later in the +// dispatcher list during dispatching an event, then event dispatching still +// continues correctly. +TEST_F(PlatformEventTest, DispatcherRemovesNextDispatcherDuringDispatch) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + RunCallbackDuringDispatch second(15, &list); + TestPlatformEventDispatcher third(20, &list); + TestPlatformEventDispatcher fourth(30, &list); + + second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&third))); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // |second| removes |third| from the dispatcher list during dispatch. So the + // event should only reach |first|, |second|, and |fourth|. + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + EXPECT_EQ(30, list[2]); +} + +// Tests that if a dispatcher removes itself from the dispatcher list during +// dispatching an event, then event dispatching continues correctly. +TEST_F(PlatformEventTest, DispatcherRemovesSelfDuringDispatch) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + RunCallbackDuringDispatch second(15, &list); + TestPlatformEventDispatcher third(20, &list); + + second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&second))); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // |second| removes itself from the dispatcher list during dispatch. So the + // event should reach all three dispatchers in the list. + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + EXPECT_EQ(20, list[2]); +} + +// Tests that if a dispatcher removes itself from the dispatcher list during +// dispatching an event, and this dispatcher is last in the dispatcher-list, +// then event dispatching ends correctly. +TEST_F(PlatformEventTest, DispatcherRemovesSelfDuringDispatchLast) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + RunCallbackDuringDispatch second(15, &list); + + second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&second))); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // |second| removes itself during dispatch. So both dispatchers will have + // received the event. + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); +} + +// Tests that if a dispatcher removes a single dispatcher that comes before it +// in the dispatcher list, then dispatch continues correctly. +TEST_F(PlatformEventTest, DispatcherRemovesPrevDispatcherDuringDispatch) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + RunCallbackDuringDispatch second(15, &list); + TestPlatformEventDispatcher third(20, &list); + + second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&first))); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // |second| removes |first| from the dispatcher list during dispatch. The + // event should reach all three dispatchers. + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + EXPECT_EQ(20, list[2]); +} + +// Tests that if a dispatcher removes multiple dispatchers that comes before it +// in the dispatcher list, then dispatch continues correctly. +TEST_F(PlatformEventTest, DispatcherRemovesPrevDispatchersDuringDispatch) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + TestPlatformEventDispatcher second(12, &list); + RunCallbackDuringDispatch third(15, &list); + TestPlatformEventDispatcher fourth(20, &list); + + third.set_callback(base::Bind(&RemoveDispatchers, + base::Unretained(&first), + base::Unretained(&second))); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + // |third| removes |first| and |second| from the dispatcher list during + // dispatch. The event should reach all three dispatchers. + ASSERT_EQ(4u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(12, list[1]); + EXPECT_EQ(15, list[2]); + EXPECT_EQ(20, list[3]); +} + +// Tests that adding a dispatcher during dispatching an event receives that +// event. +TEST_F(PlatformEventTest, DispatcherAddedDuringDispatchReceivesEvent) { + std::vector<int> list; + TestPlatformEventDispatcher first(10, &list); + RunCallbackDuringDispatch second(15, &list); + TestPlatformEventDispatcher third(20, &list); + TestPlatformEventDispatcher fourth(30, &list); + RemoveDispatchers(&third, &fourth); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + + second.set_callback(base::Bind(&AddDispatcher, base::Unretained(&third))); + list.clear(); + source()->Dispatch(*event); + ASSERT_EQ(3u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + EXPECT_EQ(20, list[2]); + + second.set_callback(base::Bind(&AddDispatcher, base::Unretained(&fourth))); + list.clear(); + source()->Dispatch(*event); + ASSERT_EQ(4u, list.size()); + EXPECT_EQ(10, list[0]); + EXPECT_EQ(15, list[1]); + EXPECT_EQ(20, list[2]); + EXPECT_EQ(30, list[3]); +} + +// Provides mechanism for running tests from inside an active message-loop. +class PlatformEventTestWithMessageLoop : public PlatformEventTest { + public: + PlatformEventTestWithMessageLoop() {} + virtual ~PlatformEventTestWithMessageLoop() {} + + void Run() { + message_loop_.PostTask( + FROM_HERE, + base::Bind(&PlatformEventTestWithMessageLoop::RunTest, + base::Unretained(this))); + message_loop_.Run(); + } + + protected: + void RunTest() { + RunTestImpl(); + message_loop_.Quit(); + } + + virtual void RunTestImpl() = 0; + + private: + base::MessageLoopForUI message_loop_; + + DISALLOW_COPY_AND_ASSIGN(PlatformEventTestWithMessageLoop); +}; + +#define RUN_TEST_IN_MESSAGE_LOOP(name) \ + TEST_F(name, Run) { Run(); } + +// Tests that a ScopedEventDispatcher restores the previous dispatcher when +// destroyed. +class ScopedDispatcherRestoresAfterDestroy + : public PlatformEventTestWithMessageLoop { + public: + // PlatformEventTestWithMessageLoop: + virtual void RunTestImpl() OVERRIDE { + std::vector<int> list; + TestPlatformEventDispatcher dispatcher(10, &list); + TestPlatformEventObserver observer(15, &list); + + TestPlatformEventDispatcher first_overriding(20, &list); + source()->RemovePlatformEventDispatcher(&first_overriding); + scoped_ptr<ScopedEventDispatcher> first_override_handle = + source()->OverrideDispatcher(&first_overriding); + + // Install a second overriding dispatcher. + TestPlatformEventDispatcher second_overriding(50, &list); + source()->RemovePlatformEventDispatcher(&second_overriding); + scoped_ptr<ScopedEventDispatcher> second_override_handle = + source()->OverrideDispatcher(&second_overriding); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(50, list[1]); + list.clear(); + + second_override_handle.reset(); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(20, list[1]); + } +}; + +RUN_TEST_IN_MESSAGE_LOOP(ScopedDispatcherRestoresAfterDestroy) + +// This dispatcher destroys the handle to the ScopedEventDispatcher when +// dispatching an event. +class DestroyScopedHandleDispatcher : public TestPlatformEventDispatcher { + public: + DestroyScopedHandleDispatcher(int id, std::vector<int>* list) + : TestPlatformEventDispatcher(id, list) {} + virtual ~DestroyScopedHandleDispatcher() {} + + void SetScopedHandle(scoped_ptr<ScopedEventDispatcher> handler) { + handler_ = handler.Pass(); + } + + void set_callback(const base::Closure& callback) { + callback_ = callback; + } + + private: + // PlatformEventDispatcher: + virtual bool CanDispatchEvent(const PlatformEvent& event) OVERRIDE { + return true; + } + + virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE { + handler_.reset(); + uint32_t action = TestPlatformEventDispatcher::DispatchEvent(event); + if (!callback_.is_null()) { + callback_.Run(); + callback_ = base::Closure(); + } + return action; + } + + scoped_ptr<ScopedEventDispatcher> handler_; + base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(DestroyScopedHandleDispatcher); +}; + +// Tests that resetting an overridden dispatcher causes the nested message-loop +// iteration to stop and the rest of the events are dispatched in the next +// iteration. +class DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration + : public PlatformEventTestWithMessageLoop { + public: + void NestedTask(std::vector<int>* list, + TestPlatformEventDispatcher* dispatcher) { + ScopedVector<PlatformEvent> events; + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + events.push_back(event.release()); + event = CreatePlatformEvent(); + events.push_back(event.release()); + + // Attempt to dispatch a couple of events. Dispatching the first event will + // have terminated the ScopedEventDispatcher object, which will terminate + // the current iteration of the message-loop. + size_t count = source()->DispatchEventStream(events); + EXPECT_EQ(1u, count); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(20, (*list)[1]); + list->clear(); + + ASSERT_LT(count, events.size()); + events.erase(events.begin(), events.begin() + count); + + count = source()->DispatchEventStream(events); + EXPECT_EQ(1u, count); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(10, (*list)[1]); + list->clear(); + + // Terminate the message-loop. + base::MessageLoopForUI::current()->QuitNow(); + } + + // PlatformEventTestWithMessageLoop: + virtual void RunTestImpl() OVERRIDE { + std::vector<int> list; + TestPlatformEventDispatcher dispatcher(10, &list); + TestPlatformEventObserver observer(15, &list); + + DestroyScopedHandleDispatcher overriding(20, &list); + source()->RemovePlatformEventDispatcher(&overriding); + scoped_ptr<ScopedEventDispatcher> override_handle = + source()->OverrideDispatcher(&overriding); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(20, list[1]); + list.clear(); + + overriding.SetScopedHandle(override_handle.Pass()); + base::RunLoop run_loop; + base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); + base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop); + loop->PostTask( + FROM_HERE, + base::Bind( + &DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration:: + NestedTask, + base::Unretained(this), + base::Unretained(&list), + base::Unretained(&overriding))); + run_loop.Run(); + + // Dispatching the event should now reach the default dispatcher. + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(10, list[1]); + } +}; + +RUN_TEST_IN_MESSAGE_LOOP( + DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration) + +// Tests that resetting an overridden dispatcher, and installing another +// overridden dispatcher before the nested message-loop completely unwinds +// function correctly. +class ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration + : public PlatformEventTestWithMessageLoop { + public: + void NestedTask(scoped_ptr<ScopedEventDispatcher> dispatch_handle, + std::vector<int>* list) { + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(20, (*list)[1]); + list->clear(); + + // Reset the override dispatcher. This should restore the default + // dispatcher. + dispatch_handle.reset(); + source()->Dispatch(*event); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(10, (*list)[1]); + list->clear(); + + // Install another override-dispatcher. + DestroyScopedHandleDispatcher second_overriding(70, list); + source()->RemovePlatformEventDispatcher(&second_overriding); + scoped_ptr<ScopedEventDispatcher> second_override_handle = + source()->OverrideDispatcher(&second_overriding); + + source()->Dispatch(*event); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(70, (*list)[1]); + list->clear(); + + second_overriding.SetScopedHandle(second_override_handle.Pass()); + second_overriding.set_post_dispatch_action(POST_DISPATCH_NONE); + base::RunLoop run_loop; + second_overriding.set_callback(run_loop.QuitClosure()); + base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); + base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop); + loop->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&TestPlatformEventSource::Dispatch), + base::Unretained(source()), + *event)); + run_loop.Run(); + ASSERT_EQ(2u, list->size()); + EXPECT_EQ(15, (*list)[0]); + EXPECT_EQ(70, (*list)[1]); + list->clear(); + + // Terminate the message-loop. + base::MessageLoopForUI::current()->QuitNow(); + } + + // PlatformEventTestWithMessageLoop: + virtual void RunTestImpl() OVERRIDE { + std::vector<int> list; + TestPlatformEventDispatcher dispatcher(10, &list); + TestPlatformEventObserver observer(15, &list); + + TestPlatformEventDispatcher overriding(20, &list); + source()->RemovePlatformEventDispatcher(&overriding); + scoped_ptr<ScopedEventDispatcher> override_handle = + source()->OverrideDispatcher(&overriding); + + scoped_ptr<PlatformEvent> event(CreatePlatformEvent()); + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(20, list[1]); + list.clear(); + + // Start a nested message-loop, and destroy |override_handle| in the nested + // loop. That should terminate the nested loop, restore the previous + // dispatchers, and return control to this function. + base::RunLoop run_loop; + base::MessageLoopForUI* loop = base::MessageLoopForUI::current(); + base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop); + loop->PostTask( + FROM_HERE, + base::Bind( + &ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration:: + NestedTask, + base::Unretained(this), + base::Passed(&override_handle), + base::Unretained(&list))); + run_loop.Run(); + + // Dispatching the event should now reach the default dispatcher. + source()->Dispatch(*event); + ASSERT_EQ(2u, list.size()); + EXPECT_EQ(15, list[0]); + EXPECT_EQ(10, list[1]); + } +}; + +RUN_TEST_IN_MESSAGE_LOOP( + ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration) + +} // namespace ui diff --git a/chromium/ui/events/platform/platform_event_types.h b/chromium/ui/events/platform/platform_event_types.h new file mode 100644 index 00000000000..dedb38ff063 --- /dev/null +++ b/chromium/ui/events/platform/platform_event_types.h @@ -0,0 +1,14 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_ +#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_ + +#include "base/event_types.h" + +namespace ui { +typedef base::NativeEvent PlatformEvent; +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_ diff --git a/chromium/ui/events/platform/scoped_event_dispatcher.cc b/chromium/ui/events/platform/scoped_event_dispatcher.cc new file mode 100644 index 00000000000..b0941ab8513 --- /dev/null +++ b/chromium/ui/events/platform/scoped_event_dispatcher.cc @@ -0,0 +1,21 @@ +// Copyright 2014 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/platform/scoped_event_dispatcher.h" + +#include "ui/events/platform/platform_event_source.h" + +namespace ui { + +ScopedEventDispatcher::ScopedEventDispatcher( + PlatformEventDispatcher** scoped_dispatcher, + PlatformEventDispatcher* new_dispatcher) + : original_(*scoped_dispatcher), + restore_(scoped_dispatcher, new_dispatcher) {} + +ScopedEventDispatcher::~ScopedEventDispatcher() { + PlatformEventSource::GetInstance()->OnOverriddenDispatcherRestored(); +} + +} // namespace ui diff --git a/chromium/ui/events/platform/scoped_event_dispatcher.h b/chromium/ui/events/platform/scoped_event_dispatcher.h new file mode 100644 index 00000000000..a2f52945d91 --- /dev/null +++ b/chromium/ui/events/platform/scoped_event_dispatcher.h @@ -0,0 +1,40 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_ +#define UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_ + +#include "base/auto_reset.h" +#include "base/basictypes.h" +#include "ui/events/events_export.h" + +namespace ui { + +class PlatformEventDispatcher; + +// A temporary PlatformEventDispatcher can be installed on a +// PlatformEventSource that overrides all installed event dispatchers, and +// always gets a chance to dispatch the event first. The PlatformEventSource +// returns a ScopedEventDispatcher object in such cases. This +// ScopedEventDispatcher object can be used to dispatch the event to any +// previous overridden dispatcher. When this object is destroyed, it removes the +// override-dispatcher, and restores the previous override-dispatcher. +class EVENTS_EXPORT ScopedEventDispatcher { + public: + ScopedEventDispatcher(PlatformEventDispatcher** scoped_dispatcher, + PlatformEventDispatcher* new_dispatcher); + ~ScopedEventDispatcher(); + + operator PlatformEventDispatcher*() const { return original_; } + + private: + PlatformEventDispatcher* original_; + base::AutoReset<PlatformEventDispatcher*> restore_; + + DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatcher); +}; + +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_ diff --git a/chromium/ui/events/platform/x11/BUILD.gn b/chromium/ui/events/platform/x11/BUILD.gn new file mode 100644 index 00000000000..6c8bbe4b1f2 --- /dev/null +++ b/chromium/ui/events/platform/x11/BUILD.gn @@ -0,0 +1,42 @@ +# Copyright 2014 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. + +component("x11") { + output_name = "x11_events_platform" + + sources = [ + "x11_event_source.cc", + "x11_event_source.h", + "x11_event_source_glib.cc", + "x11_event_source_libevent.cc", + ] + + defines = [ + "EVENTS_IMPLEMENTATION", + ] + + configs += [ + "//build/config/linux:x11", + ] + + deps = [ + "//ui/events", + "//ui/events/platform", + "//ui/gfx/x", + ] + + if (is_linux) { + sources -= [ + "x11_event_source_libevent.cc", + ] + + configs += [ + "//build/config/linux:glib", + ] + } else { + sources -= [ + "x11_event_source_glib.cc", + ] + } +} diff --git a/chromium/ui/events/platform/x11/x11_event_source.cc b/chromium/ui/events/platform/x11/x11_event_source.cc new file mode 100644 index 00000000000..25e74098fac --- /dev/null +++ b/chromium/ui/events/platform/x11/x11_event_source.cc @@ -0,0 +1,149 @@ +// Copyright 2014 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/platform/x11/x11_event_source.h" + +#include <X11/extensions/XInput2.h> +#include <X11/X.h> +#include <X11/Xlib.h> +#include <X11/XKBlib.h> + +#include "base/logging.h" +#include "ui/events/event_utils.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/gfx/x/x11_types.h" + +namespace ui { + +namespace { + +int g_xinput_opcode = -1; + +bool InitializeXInput2(XDisplay* display) { + if (!display) + return false; + + int event, err; + + int xiopcode; + if (!XQueryExtension(display, "XInputExtension", &xiopcode, &event, &err)) { + DVLOG(1) << "X Input extension not available."; + return false; + } + g_xinput_opcode = xiopcode; + +#if defined(USE_XI2_MT) + // USE_XI2_MT also defines the required XI2 minor minimum version. + int major = 2, minor = USE_XI2_MT; +#else + int major = 2, minor = 0; +#endif + if (XIQueryVersion(display, &major, &minor) == BadRequest) { + DVLOG(1) << "XInput2 not supported in the server."; + return false; + } +#if defined(USE_XI2_MT) + if (major < 2 || (major == 2 && minor < USE_XI2_MT)) { + DVLOG(1) << "XI version on server is " << major << "." << minor << ". " + << "But 2." << USE_XI2_MT << " is required."; + return false; + } +#endif + + return true; +} + +bool InitializeXkb(XDisplay* display) { + if (!display) + return false; + + int opcode, event, error; + int major = XkbMajorVersion; + int minor = XkbMinorVersion; + if (!XkbQueryExtension(display, &opcode, &event, &error, &major, &minor)) { + DVLOG(1) << "Xkb extension not available."; + return false; + } + + // Ask the server not to send KeyRelease event when the user holds down a key. + // crbug.com/138092 + Bool supported_return; + if (!XkbSetDetectableAutoRepeat(display, True, &supported_return)) { + DVLOG(1) << "XKB not supported in the server."; + return false; + } + + return true; +} + +} // namespace + +X11EventSource::X11EventSource(XDisplay* display) + : display_(display), + continue_stream_(true) { + CHECK(display_); + InitializeXInput2(display_); + InitializeXkb(display_); +} + +X11EventSource::~X11EventSource() { +} + +// static +X11EventSource* X11EventSource::GetInstance() { + return static_cast<X11EventSource*>(PlatformEventSource::GetInstance()); +} + +//////////////////////////////////////////////////////////////////////////////// +// X11EventSource, public + +void X11EventSource::DispatchXEvents() { + DCHECK(display_); + // Handle all pending events. + // It may be useful to eventually align this event dispatch with vsync, but + // not yet. + continue_stream_ = true; + while (XPending(display_) && continue_stream_) { + XEvent xevent; + XNextEvent(display_, &xevent); + DispatchEvent(&xevent); + } +} + +void X11EventSource::BlockUntilWindowMapped(XID window) { + XEvent event; + do { + // Block until there's a message of |event_mask| type on |w|. Then remove + // it from the queue and stuff it in |event|. + XWindowEvent(display_, window, StructureNotifyMask, &event); + DispatchEvent(&event); + } while (event.type != MapNotify); +} + +//////////////////////////////////////////////////////////////////////////////// +// X11EventSource, private + +uint32_t X11EventSource::DispatchEvent(XEvent* xevent) { + bool have_cookie = false; + if (xevent->type == GenericEvent && + XGetEventData(xevent->xgeneric.display, &xevent->xcookie)) { + have_cookie = true; + } + + uint32_t action = PlatformEventSource::DispatchEvent(xevent); + if (xevent->type == GenericEvent && + xevent->xgeneric.evtype == XI_HierarchyChanged) { + ui::UpdateDeviceList(); + } + + if (have_cookie) + XFreeEventData(xevent->xgeneric.display, &xevent->xcookie); + return action; +} + +void X11EventSource::StopCurrentEventStream() { + continue_stream_ = false; +} + +} // namespace ui diff --git a/chromium/ui/events/platform/x11/x11_event_source.h b/chromium/ui/events/platform/x11/x11_event_source.h new file mode 100644 index 00000000000..602eda67dd3 --- /dev/null +++ b/chromium/ui/events/platform/x11/x11_event_source.h @@ -0,0 +1,64 @@ +// Copyright 2014 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. + +#ifndef UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_ +#define UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/events_export.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/gfx/x/x11_types.h" + +typedef struct _GPollFD GPollFD; +typedef struct _GSource GSource; +typedef union _XEvent XEvent; +typedef unsigned long XID; + +namespace ui { + +// A PlatformEventSource implementation for reading events from X11 server and +// dispatching the events to the appropriate dispatcher. +class EVENTS_EXPORT X11EventSource : public PlatformEventSource { + public: + explicit X11EventSource(XDisplay* display); + virtual ~X11EventSource(); + + static X11EventSource* GetInstance(); + + // Called by the glib source dispatch function. Processes all (if any) + // available X events. + void DispatchXEvents(); + + // Blocks on the X11 event queue until we receive notification from the + // xserver that |w| has been mapped; StructureNotifyMask events on |w| are + // pulled out from the queue and dispatched out of order. + // + // For those that know X11, this is really a wrapper around XWindowEvent + // which still makes sure the preempted event is dispatched instead of + // dropped on the floor. This method exists because mapping a window is + // asynchronous (and we receive an XEvent when mapped), while there are also + // functions which require a mapped window. + void BlockUntilWindowMapped(XID window); + + protected: + XDisplay* display() { return display_; } + + private: + // PlatformEventSource: + virtual uint32_t DispatchEvent(XEvent* xevent) OVERRIDE; + virtual void StopCurrentEventStream() OVERRIDE; + + // The connection to the X11 server used to receive the events. + XDisplay* display_; + + // Keeps track of whether this source should continue to dispatch all the + // available events. + bool continue_stream_; + + DISALLOW_COPY_AND_ASSIGN(X11EventSource); +}; + +} // namespace ui + +#endif // UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_ diff --git a/chromium/ui/events/platform/x11/x11_event_source_glib.cc b/chromium/ui/events/platform/x11/x11_event_source_glib.cc new file mode 100644 index 00000000000..95044226b50 --- /dev/null +++ b/chromium/ui/events/platform/x11/x11_event_source_glib.cc @@ -0,0 +1,101 @@ +// Copyright 2014 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/platform/x11/x11_event_source.h" + +#include <glib.h> +#include <X11/Xlib.h> + +namespace ui { + +namespace { + +struct GLibX11Source : public GSource { + // Note: The GLibX11Source is created and destroyed by GLib. So its + // constructor/destructor may or may not get called. + XDisplay* display; + GPollFD* poll_fd; +}; + +gboolean XSourcePrepare(GSource* source, gint* timeout_ms) { + GLibX11Source* gxsource = static_cast<GLibX11Source*>(source); + if (XPending(gxsource->display)) + *timeout_ms = 0; + else + *timeout_ms = -1; + return FALSE; +} + +gboolean XSourceCheck(GSource* source) { + GLibX11Source* gxsource = static_cast<GLibX11Source*>(source); + return XPending(gxsource->display); +} + +gboolean XSourceDispatch(GSource* source, + GSourceFunc unused_func, + gpointer data) { + X11EventSource* x11_source = static_cast<X11EventSource*>(data); + x11_source->DispatchXEvents(); + return TRUE; +} + +GSourceFuncs XSourceFuncs = { + XSourcePrepare, + XSourceCheck, + XSourceDispatch, + NULL +}; + +class X11EventSourceGlib : public X11EventSource { + public: + explicit X11EventSourceGlib(XDisplay* display) + : X11EventSource(display), + x_source_(NULL) { + InitXSource(ConnectionNumber(display)); + } + + virtual ~X11EventSourceGlib() { + g_source_destroy(x_source_); + g_source_unref(x_source_); + } + + private: + void InitXSource(int fd) { + CHECK(!x_source_); + CHECK(display()) << "Unable to get connection to X server"; + + x_poll_.reset(new GPollFD()); + x_poll_->fd = fd; + x_poll_->events = G_IO_IN; + x_poll_->revents = 0; + + GLibX11Source* glib_x_source = static_cast<GLibX11Source*> + (g_source_new(&XSourceFuncs, sizeof(GLibX11Source))); + glib_x_source->display = display(); + glib_x_source->poll_fd = x_poll_.get(); + + x_source_ = glib_x_source; + g_source_add_poll(x_source_, x_poll_.get()); + g_source_set_can_recurse(x_source_, TRUE); + g_source_set_callback(x_source_, NULL, this, NULL); + g_source_attach(x_source_, g_main_context_default()); + } + + // The GLib event source for X events. + GSource* x_source_; + + // The poll attached to |x_source_|. + scoped_ptr<GPollFD> x_poll_; + + DISALLOW_COPY_AND_ASSIGN(X11EventSourceGlib); +}; + +} // namespace + +scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() { + return scoped_ptr<PlatformEventSource>( + new X11EventSourceGlib(gfx::GetXDisplay())); +} + +} // namespace ui diff --git a/chromium/ui/events/platform/x11/x11_event_source_libevent.cc b/chromium/ui/events/platform/x11/x11_event_source_libevent.cc new file mode 100644 index 00000000000..d92e12a5d82 --- /dev/null +++ b/chromium/ui/events/platform/x11/x11_event_source_libevent.cc @@ -0,0 +1,68 @@ +// Copyright 2014 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/platform/x11/x11_event_source.h" + +#include <X11/Xlib.h> + +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_pump_libevent.h" + +namespace ui { + +namespace { + +class X11EventSourceLibevent : public X11EventSource, + public base::MessagePumpLibevent::Watcher { + public: + explicit X11EventSourceLibevent(XDisplay* display) + : X11EventSource(display), + initialized_(false) { + AddEventWatcher(); + } + + virtual ~X11EventSourceLibevent() { + } + + private: + void AddEventWatcher() { + if (initialized_) + return; + if (!base::MessageLoop::current()) + return; + + int fd = ConnectionNumber(display()); + base::MessageLoopForUI::current()->WatchFileDescriptor(fd, true, + base::MessagePumpLibevent::WATCH_READ, &watcher_controller_, this); + initialized_ = true; + } + + // PlatformEventSource: + virtual void OnDispatcherListChanged() OVERRIDE { + AddEventWatcher(); + } + + // base::MessagePumpLibevent::Watcher: + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + DispatchXEvents(); + } + + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE { + NOTREACHED(); + } + + base::MessagePumpLibevent::FileDescriptorWatcher watcher_controller_; + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(X11EventSourceLibevent); +}; + +} // namespace + +scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() { + return scoped_ptr<PlatformEventSource>( + new X11EventSourceLibevent(gfx::GetXDisplay())); +} + +} // namespace ui diff --git a/chromium/ui/events/platform/x11/x11_events_platform.gyp b/chromium/ui/events/platform/x11/x11_events_platform.gyp new file mode 100644 index 00000000000..9070de7cb29 --- /dev/null +++ b/chromium/ui/events/platform/x11/x11_events_platform.gyp @@ -0,0 +1,43 @@ +# Copyright 2014 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [{ + 'target_name': 'x11_events_platform', + 'type': '<(component)', + 'defines': [ + 'EVENTS_IMPLEMENTATION', + ], + 'dependencies': [ + '../../../../build/linux/system.gyp:x11', + '../../../gfx/x/gfx_x11.gyp:gfx_x11', + '../../events.gyp:events', + '../events_platform.gyp:events_platform', + ], + 'sources': [ + 'x11_event_source.cc', + 'x11_event_source.h', + 'x11_event_source_glib.cc', + 'x11_event_source_libevent.cc', + ], + 'conditions': [ + ['use_glib==1', { + 'dependencies': [ + '../../../../build/linux/system.gyp:glib', + ], + 'sources!': [ + 'x11_event_source_libevent.cc', + ], + }, { + # use_glib == 0 + 'sources!': [ + 'x11_event_source_glib.cc', + ], + }], + ], + }], +} diff --git a/chromium/ui/events/win/events_win.cc b/chromium/ui/events/win/events_win.cc index e255fc988eb..9e74d3a4556 100644 --- a/chromium/ui/events/win/events_win.cc +++ b/chromium/ui/events/win/events_win.cc @@ -77,6 +77,11 @@ bool IsNonClientMouseEvent(const base::NativeEvent& native_event) { native_event.message <= WM_NCXBUTTONDBLCLK); } +bool IsMouseEvent(const base::NativeEvent& native_event) { + return IsClientMouseEvent(native_event) || + IsNonClientMouseEvent(native_event); +} + bool IsMouseWheelEvent(const base::NativeEvent& native_event) { return native_event.message == WM_MOUSEWHEEL || native_event.message == WM_MOUSEHWHEEL; @@ -231,7 +236,7 @@ gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) { native_point.y = GET_Y_LPARAM(native_event.lParam); } ScreenToClient(native_event.hwnd, &native_point); - return gfx::win::ScreenToDIPPoint(gfx::Point(native_point)); + return gfx::Point(native_point); } gfx::Point EventSystemLocationFromNative( @@ -250,9 +255,8 @@ const char* CodeFromNative(const base::NativeEvent& native_event) { return CodeForWindowsScanCode(scan_code); } -bool IsMouseEvent(const base::NativeEvent& native_event) { - return IsClientMouseEvent(native_event) || - IsNonClientMouseEvent(native_event); +uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) { + return static_cast<uint32>(native_event.wParam); } int GetChangedMouseButtonFlagsFromNative( @@ -279,6 +283,13 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { return gfx::Vector2d(GET_WHEEL_DELTA_WPARAM(native_event.wParam), 0); } +base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { + return event; +} + +void ReleaseCopiedNativeEvent(const base::NativeEvent& event) { +} + void ClearTouchIdIfReleased(const base::NativeEvent& xev) { NOTIMPLEMENTED(); } @@ -355,16 +366,6 @@ bool IsTouchpadEvent(const base::NativeEvent& event) { return false; } -bool IsNoopEvent(const base::NativeEvent& event) { - return event.message == WM_USER + 310; -} - -base::NativeEvent CreateNoopEvent() { - MSG event = { NULL }; - event.message = WM_USER + 310; - return event; -} - int GetModifiersFromACCEL(const ACCEL& accel) { int modifiers = EF_NONE; if (accel.fVirt & FSHIFT) diff --git a/chromium/ui/events/x/device_data_manager.cc b/chromium/ui/events/x/device_data_manager.cc index a2e02928c4b..4cc0957b4a8 100644 --- a/chromium/ui/events/x/device_data_manager.cc +++ b/chromium/ui/events/x/device_data_manager.cc @@ -10,9 +10,13 @@ #include "base/logging.h" #include "base/memory/singleton.h" +#include "base/sys_info.h" #include "ui/events/event_constants.h" +#include "ui/events/event_switches.h" #include "ui/events/x/device_list_cache_x.h" #include "ui/events/x/touch_factory_x11.h" +#include "ui/gfx/display.h" +#include "ui/gfx/point3_f.h" #include "ui/gfx/x/x11_types.h" // XIScrollClass was introduced in XI 2.1 so we need to define it here @@ -110,8 +114,7 @@ DeviceDataManager* DeviceDataManager::GetInstance() { } DeviceDataManager::DeviceDataManager() - : natural_scroll_enabled_(false), - xi_opcode_(-1), + : xi_opcode_(-1), atom_cache_(gfx::GetXDisplay(), kCachedAtoms), button_map_count_(0) { CHECK(gfx::GetXDisplay()); @@ -121,6 +124,8 @@ DeviceDataManager::DeviceDataManager() CHECK(arraysize(kCachedAtoms) == static_cast<size_t>(DT_LAST_ENTRY) + 1); UpdateDeviceList(gfx::GetXDisplay()); UpdateButtonMap(); + for (int i = 0; i < kMaxDeviceNum; i++) + touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID; } DeviceDataManager::~DeviceDataManager() { @@ -177,14 +182,6 @@ bool DeviceDataManager::IsXInput2Available() const { return xi_opcode_ != -1; } -float DeviceDataManager::GetNaturalScrollFactor(int sourceid) const { - // Natural scroll is touchpad-only. - if (sourceid >= kMaxDeviceNum || !touchpads_[sourceid]) - return -1.0f; - - return natural_scroll_enabled_ ? 1.0f : -1.0f; -} - void DeviceDataManager::UpdateDeviceList(Display* display) { cmt_devices_.reset(); touchpads_.reset(); @@ -460,20 +457,17 @@ void DeviceDataManager::GetScrollOffsets(const base::NativeEvent& native_event, *y_offset_ordinal = 0; *finger_count = 2; - XIDeviceEvent* xiev = - static_cast<XIDeviceEvent*>(native_event->xcookie.data); - const float natural_scroll_factor = GetNaturalScrollFactor(xiev->sourceid); EventData data; GetEventRawData(*native_event, &data); if (data.find(DT_CMT_SCROLL_X) != data.end()) - *x_offset = data[DT_CMT_SCROLL_X] * natural_scroll_factor; + *x_offset = data[DT_CMT_SCROLL_X]; if (data.find(DT_CMT_SCROLL_Y) != data.end()) - *y_offset = data[DT_CMT_SCROLL_Y] * natural_scroll_factor; + *y_offset = data[DT_CMT_SCROLL_Y]; if (data.find(DT_CMT_ORDINAL_X) != data.end()) - *x_offset_ordinal = data[DT_CMT_ORDINAL_X] * natural_scroll_factor; + *x_offset_ordinal = data[DT_CMT_ORDINAL_X]; if (data.find(DT_CMT_ORDINAL_Y) != data.end()) - *y_offset_ordinal = data[DT_CMT_ORDINAL_Y] * natural_scroll_factor; + *y_offset_ordinal = data[DT_CMT_ORDINAL_Y]; if (data.find(DT_CMT_FINGER_COUNT) != data.end()) *finger_count = static_cast<int>(data[DT_CMT_FINGER_COUNT]); } @@ -488,22 +482,19 @@ void DeviceDataManager::GetFlingData(const base::NativeEvent& native_event, *vy_ordinal = 0; *is_cancel = false; - XIDeviceEvent* xiev = - static_cast<XIDeviceEvent*>(native_event->xcookie.data); - const float natural_scroll_factor = GetNaturalScrollFactor(xiev->sourceid); EventData data; GetEventRawData(*native_event, &data); if (data.find(DT_CMT_FLING_X) != data.end()) - *vx = data[DT_CMT_FLING_X] * natural_scroll_factor; + *vx = data[DT_CMT_FLING_X]; if (data.find(DT_CMT_FLING_Y) != data.end()) - *vy = data[DT_CMT_FLING_Y] * natural_scroll_factor; + *vy = data[DT_CMT_FLING_Y]; if (data.find(DT_CMT_FLING_STATE) != data.end()) *is_cancel = !!static_cast<unsigned int>(data[DT_CMT_FLING_STATE]); if (data.find(DT_CMT_ORDINAL_X) != data.end()) - *vx_ordinal = data[DT_CMT_ORDINAL_X] * natural_scroll_factor; + *vx_ordinal = data[DT_CMT_ORDINAL_X]; if (data.find(DT_CMT_ORDINAL_Y) != data.end()) - *vy_ordinal = data[DT_CMT_ORDINAL_Y] * natural_scroll_factor; + *vy_ordinal = data[DT_CMT_ORDINAL_Y]; } void DeviceDataManager::GetMetricsData(const base::NativeEvent& native_event, @@ -649,4 +640,54 @@ void DeviceDataManager::InitializeValuatorsForTest(int deviceid, } } +bool DeviceDataManager::TouchEventNeedsCalibrate(int touch_device_id) const { +#if defined(OS_CHROMEOS) && defined(USE_XI2_MT) + int64 touch_display_id = GetDisplayForTouchDevice(touch_device_id); + if (base::SysInfo::IsRunningOnChromeOS() && + touch_display_id == gfx::Display::InternalDisplayId()) { + return true; + } +#endif // defined(OS_CHROMEOS) && defined(USE_XI2_MT) + return false; +} + +void DeviceDataManager::ClearTouchTransformerRecord() { + for (int i = 0; i < kMaxDeviceNum; i++) { + touch_device_transformer_map_[i] = gfx::Transform(); + touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID; + } +} + +bool DeviceDataManager::IsTouchDeviceIdValid(int touch_device_id) const { + return (touch_device_id > 0 && touch_device_id < kMaxDeviceNum); +} + +void DeviceDataManager::UpdateTouchInfoForDisplay( + int64 display_id, + int touch_device_id, + const gfx::Transform& touch_transformer) { + if (IsTouchDeviceIdValid(touch_device_id)) { + touch_device_to_display_map_[touch_device_id] = display_id; + touch_device_transformer_map_[touch_device_id] = touch_transformer; + } +} + +void DeviceDataManager::ApplyTouchTransformer(int touch_device_id, + float* x, float* y) { + if (IsTouchDeviceIdValid(touch_device_id)) { + gfx::Point3F point(*x, *y, 0.0); + const gfx::Transform& trans = + touch_device_transformer_map_[touch_device_id]; + trans.TransformPoint(&point); + *x = point.x(); + *y = point.y(); + } +} + +int64 DeviceDataManager::GetDisplayForTouchDevice(int touch_device_id) const { + if (IsTouchDeviceIdValid(touch_device_id)) + return touch_device_to_display_map_[touch_device_id]; + return gfx::Display::kInvalidDisplayID; +} + } // namespace ui diff --git a/chromium/ui/events/x/device_data_manager.h b/chromium/ui/events/x/device_data_manager.h index aff06493571..1135ec222b8 100644 --- a/chromium/ui/events/x/device_data_manager.h +++ b/chromium/ui/events/x/device_data_manager.h @@ -20,8 +20,12 @@ #include "base/basictypes.h" #include "base/event_types.h" +#include "base/memory/scoped_ptr.h" +#include "ui/events/event.h" #include "ui/events/event_constants.h" #include "ui/events/events_base_export.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/transform.h" #include "ui/gfx/x/x11_atom_cache.h" template <typename T> struct DefaultSingletonTraits; @@ -104,18 +108,9 @@ class EVENTS_BASE_EXPORT DeviceDataManager { // Returns the DeviceDataManager singleton. static DeviceDataManager* GetInstance(); - // Natural scroll setter/getter. - bool natural_scroll_enabled() const { return natural_scroll_enabled_; } - void set_natural_scroll_enabled(bool enabled) { - natural_scroll_enabled_ = enabled; - } - // Returns if XInput2 is available on the system. bool IsXInput2Available() const; - // Get the natural scroll direction multiplier (1.0f or -1.0f). - float GetNaturalScrollFactor(int sourceid) const; - // Updates the list of devices. void UpdateDeviceList(Display* display); @@ -226,6 +221,14 @@ class EVENTS_BASE_EXPORT DeviceDataManager { DataType type, double value); + void ClearTouchTransformerRecord(); + void UpdateTouchInfoForDisplay(int64 display_id, + int touch_device_id, + const gfx::Transform& touch_transformer); + void ApplyTouchTransformer(int touch_device_id, float* x, float* y); + int64 GetDisplayForTouchDevice(int touch_device_id) const; + bool TouchEventNeedsCalibrate(int touch_device_id) const; + private: // Requirement for Singleton. friend struct DefaultSingletonTraits<DeviceDataManager>; @@ -245,10 +248,11 @@ class EVENTS_BASE_EXPORT DeviceDataManager { double min_value, double max_value); + bool IsTouchDeviceIdValid(int touch_device_id) const; + static const int kMaxDeviceNum = 128; static const int kMaxXIEventType = XI_LASTEVENT + 1; static const int kMaxSlotNum = 10; - bool natural_scroll_enabled_; // Major opcode for the XInput extension. Used to identify XInput events. int xi_opcode_; @@ -292,6 +296,11 @@ class EVENTS_BASE_EXPORT DeviceDataManager { unsigned char button_map_[256]; int button_map_count_; + // Table to keep track of which display id is mapped to which touch device. + int64 touch_device_to_display_map_[kMaxDeviceNum]; + // Index table to find the TouchTransformer for a touch device. + gfx::Transform touch_device_transformer_map_[kMaxDeviceNum]; + DISALLOW_COPY_AND_ASSIGN(DeviceDataManager); }; diff --git a/chromium/ui/events/x/events_x.cc b/chromium/ui/events/x/events_x.cc index 5e3b9dd5462..bd12bd835db 100644 --- a/chromium/ui/events/x/events_x.cc +++ b/chromium/ui/events/x/events_x.cc @@ -9,10 +9,10 @@ #include <X11/extensions/XInput.h> #include <X11/extensions/XInput2.h> #include <X11/Xlib.h> +#include <X11/Xutil.h> #include "base/logging.h" #include "base/memory/singleton.h" -#include "base/message_loop/message_pump_x11.h" #include "ui/events/event_utils.h" #include "ui/events/keycodes/keyboard_code_conversion_x.h" #include "ui/events/x/device_data_manager.h" @@ -137,6 +137,10 @@ int GetEventFlagsFromXState(unsigned int state) { flags |= ui::EF_ALT_DOWN; if (state & LockMask) flags |= ui::EF_CAPS_LOCK_DOWN; + 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) @@ -148,6 +152,36 @@ int GetEventFlagsFromXState(unsigned int state) { return flags; } +int GetEventFlagsFromXKeyEvent(XEvent* xevent) { + DCHECK(xevent->type == KeyPress || xevent->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 = + xevent->xkey.keycode == 0 && + (xevent->xkey.state & ~shift_lock_mask) == 0; + const int ime_fabricated_flag = + fabricated_by_xim ? ui::EF_IME_FABRICATED_KEY : 0; +#endif + + return GetEventFlagsFromXState(xevent->xkey.state) | + (IsKeypadKey(XLookupKeysym(&xevent->xkey, 0)) ? ui::EF_NUMPAD_KEY : 0) | + (IsFunctionKey(XLookupKeysym(&xevent->xkey, 0)) ? + ui::EF_FUNCTION_KEY : 0) | + ime_fabricated_flag; +} + // 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 @@ -225,10 +259,6 @@ double GetTouchParamFromXEvent(XEvent* xev, return default_value; } -Atom GetNoopEventAtom() { - return XInternAtom(gfx::GetXDisplay(), "noop", False); -} - } // namespace namespace ui { @@ -277,10 +307,19 @@ EventType EventTypeFromNative(const base::NativeEvent& native_event) { XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(native_event->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(native_event); 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 = EventButtonFromNative(native_event); if (button >= kMinWheelButton && button <= kMaxWheelButton) @@ -323,7 +362,7 @@ int EventFlagsFromNative(const base::NativeEvent& native_event) { case KeyPress: case KeyRelease: { XModifierStateWatcher::GetInstance()->UpdateStateFromEvent(native_event); - return GetEventFlagsFromXState(native_event->xkey.state); + return GetEventFlagsFromXKeyEvent(native_event); } case ButtonPress: case ButtonRelease: { @@ -333,6 +372,9 @@ int EventFlagsFromNative(const base::NativeEvent& native_event) { flags |= GetEventFlagsForButton(native_event->xbutton.button); return flags; } + case EnterNotify: + case LeaveNotify: + return GetEventFlagsFromXState(native_event->xcrossing.state); case MotionNotify: return GetEventFlagsFromXState(native_event->xmotion.state); case GenericEvent: { @@ -426,8 +468,21 @@ gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) { case GenericEvent: { XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(native_event->xcookie.data); - return gfx::Point(static_cast<int>(xievent->event_x), - static_cast<int>(xievent->event_y)); + 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::DeviceDataManager::GetInstance()->ApplyTouchTransformer( + xievent->deviceid, &x, &y); + break; + default: + break; + } +#endif // defined(OS_CHROMEOS) + return gfx::Point(static_cast<int>(x), static_cast<int>(y)); } } return gfx::Point(); @@ -478,21 +533,10 @@ const char* CodeFromNative(const base::NativeEvent& native_event) { return CodeFromXEvent(native_event); } -bool IsMouseEvent(const base::NativeEvent& native_event) { - if (native_event->type == EnterNotify || - native_event->type == LeaveNotify || - native_event->type == ButtonPress || - native_event->type == ButtonRelease || - native_event->type == MotionNotify) - return true; - if (native_event->type == GenericEvent) { - XIDeviceEvent* xievent = - static_cast<XIDeviceEvent*>(native_event->xcookie.data); - return xievent->evtype == XI_ButtonPress || - xievent->evtype == XI_ButtonRelease || - xievent->evtype == XI_Motion; - } - return false; +uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) { + KeySym keysym; + XLookupString(&native_event->xkey, NULL, 0, &keysym, NULL); + return keysym; } int GetChangedMouseButtonFlagsFromNative( @@ -534,12 +578,27 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) { 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: - // TODO(derat): Do something for horizontal scrolls (buttons 6 and 7)? return gfx::Vector2d(); } } +base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) { + if (!event || event->type == GenericEvent) + return NULL; + XEvent* copy = new XEvent; + *copy = *event; + return copy; +} + +void ReleaseCopiedNativeEvent(const base::NativeEvent& event) { + delete event; +} + void ClearTouchIdIfReleased(const base::NativeEvent& xev) { ui::EventType type = ui::EventTypeFromNative(xev); if (type == ui::ET_TOUCH_CANCELLED || @@ -673,37 +732,8 @@ bool GetGestureTimes(const base::NativeEvent& native_event, return true; } -void SetNaturalScroll(bool enabled) { - DeviceDataManager::GetInstance()->set_natural_scroll_enabled(enabled); -} - -bool IsNaturalScrollEnabled() { - return DeviceDataManager::GetInstance()->natural_scroll_enabled(); -} - bool IsTouchpadEvent(const base::NativeEvent& event) { return DeviceDataManager::GetInstance()->IsTouchpadXInputEvent(event); } -bool IsNoopEvent(const base::NativeEvent& event) { - return (event->type == ClientMessage && - event->xclient.message_type == GetNoopEventAtom()); -} - -base::NativeEvent CreateNoopEvent() { - static XEvent* noop = NULL; - if (!noop) { - noop = new XEvent(); - memset(noop, 0, sizeof(XEvent)); - noop->xclient.type = ClientMessage; - noop->xclient.window = None; - noop->xclient.format = 8; - DCHECK(!noop->xclient.display); - } - // Make sure we use atom from current xdisplay, which may - // change during the test. - noop->xclient.message_type = GetNoopEventAtom(); - return noop; -} - } // namespace ui diff --git a/chromium/ui/events/x/events_x_unittest.cc b/chromium/ui/events/x/events_x_unittest.cc index b6c7f38c0ce..5047c59b19c 100644 --- a/chromium/ui/events/x/events_x_unittest.cc +++ b/chromium/ui/events/x/events_x_unittest.cc @@ -6,6 +6,8 @@ #include <X11/extensions/XInput2.h> #include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> // Generically-named #defines from Xlib that conflict with symbols in GTest. #undef Bool @@ -40,6 +42,37 @@ void InitButtonEvent(XEvent* event, button_event->state = state; } +// Initializes the passed-in Xlib event. +void InitKeyEvent(Display* display, + XEvent* event, + bool is_press, + int keycode, + int state) { + memset(event, 0, sizeof(*event)); + + // We don't bother setting fields that the event code doesn't use, such as + // x_root/y_root and window/root/subwindow. + XKeyEvent* key_event = &(event->xkey); + key_event->display = display; + key_event->type = is_press ? KeyPress : KeyRelease; + key_event->keycode = keycode; + key_event->state = state; +} + +// Returns true if the keysym maps to a KeyEvent with the EF_FUNCTION_KEY +// flag set, or the keysym maps to a zero key code. +bool HasFunctionKeyFlagSetIfSupported(Display* display, int x_keysym) { + XEvent event; + int x_keycode = XKeysymToKeycode(display, x_keysym); + // Exclude keysyms for which the server has no corresponding keycode. + if (x_keycode) { + InitKeyEvent(display, &event, true, x_keycode, 0); + ui::KeyEvent ui_key_event(&event, false); + return (ui_key_event.flags() & ui::EF_FUNCTION_KEY); + } + return true; +} + } // namespace TEST(EventsXTest, ButtonEvents) { @@ -51,7 +84,6 @@ TEST(EventsXTest, ButtonEvents) { EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event)); EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); InitButtonEvent(&event, true, location, 2, Button1Mask | ShiftMask); EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event)); @@ -59,20 +91,17 @@ TEST(EventsXTest, ButtonEvents) { ui::EF_SHIFT_DOWN, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); InitButtonEvent(&event, false, location, 3, 0); EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(&event)); EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); // Scroll up. InitButtonEvent(&event, true, location, 4, 0); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event)); EXPECT_EQ(0, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); offset = ui::GetMouseWheelOffset(&event); EXPECT_GT(offset.y(), 0); EXPECT_EQ(0, offset.x()); @@ -82,30 +111,27 @@ TEST(EventsXTest, ButtonEvents) { EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event)); EXPECT_EQ(0, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); offset = ui::GetMouseWheelOffset(&event); EXPECT_LT(offset.y(), 0); EXPECT_EQ(0, offset.x()); - // Scroll left, typically. + // Scroll left. InitButtonEvent(&event, true, location, 6, 0); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event)); EXPECT_EQ(0, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); offset = ui::GetMouseWheelOffset(&event); EXPECT_EQ(0, offset.y()); - EXPECT_EQ(0, offset.x()); + EXPECT_GT(offset.x(), 0); - // Scroll right, typically. + // Scroll right. InitButtonEvent(&event, true, location, 7, 0); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event)); EXPECT_EQ(0, ui::EventFlagsFromNative(&event)); EXPECT_EQ(location, ui::EventLocationFromNative(&event)); - EXPECT_TRUE(ui::IsMouseEvent(&event)); offset = ui::GetMouseWheelOffset(&event); EXPECT_EQ(0, offset.y()); - EXPECT_EQ(0, offset.x()); + EXPECT_LT(offset.x(), 0); // TODO(derat): Test XInput code. } @@ -245,4 +271,193 @@ TEST(EventsXTest, TouchEventBasic) { EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f); } #endif + +TEST(EventsXTest, NumpadKeyEvents) { + XEvent event; + Display* display = gfx::GetXDisplay(); + + struct { + bool is_numpad_key; + int x_keysym; + } keys[] = { + // XK_KP_Space and XK_KP_Equal are the extrema in the conventional + // keysymdef.h numbering. + { true, XK_KP_Space }, + { true, XK_KP_Equal }, + // Other numpad keysyms. (This is actually exhaustive in the current list.) + { true, XK_KP_Tab }, + { true, XK_KP_Enter }, + { true, XK_KP_F1 }, + { true, XK_KP_F2 }, + { true, XK_KP_F3 }, + { true, XK_KP_F4 }, + { true, XK_KP_Home }, + { true, XK_KP_Left }, + { true, XK_KP_Up }, + { true, XK_KP_Right }, + { true, XK_KP_Down }, + { true, XK_KP_Prior }, + { true, XK_KP_Page_Up }, + { true, XK_KP_Next }, + { true, XK_KP_Page_Down }, + { true, XK_KP_End }, + { true, XK_KP_Begin }, + { true, XK_KP_Insert }, + { true, XK_KP_Delete }, + { true, XK_KP_Multiply }, + { true, XK_KP_Add }, + { true, XK_KP_Separator }, + { true, XK_KP_Subtract }, + { true, XK_KP_Decimal }, + { true, XK_KP_Divide }, + { true, XK_KP_0 }, + { true, XK_KP_1 }, + { true, XK_KP_2 }, + { true, XK_KP_3 }, + { true, XK_KP_4 }, + { true, XK_KP_5 }, + { true, XK_KP_6 }, + { true, XK_KP_7 }, + { true, XK_KP_8 }, + { true, XK_KP_9 }, + // Largest keysym preceding XK_KP_Space. + { false, XK_Num_Lock }, + // Smallest keysym following XK_KP_Equal. + { false, XK_F1 }, + // Non-numpad analogues of numpad keysyms. + { false, XK_Tab }, + { false, XK_Return }, + { false, XK_F1 }, + { false, XK_F2 }, + { false, XK_F3 }, + { false, XK_F4 }, + { false, XK_Home }, + { false, XK_Left }, + { false, XK_Up }, + { false, XK_Right }, + { false, XK_Down }, + { false, XK_Prior }, + { false, XK_Page_Up }, + { false, XK_Next }, + { false, XK_Page_Down }, + { false, XK_End }, + { false, XK_Insert }, + { false, XK_Delete }, + { false, XK_multiply }, + { false, XK_plus }, + { false, XK_minus }, + { false, XK_period }, + { false, XK_slash }, + { false, XK_0 }, + { false, XK_1 }, + { false, XK_2 }, + { false, XK_3 }, + { false, XK_4 }, + { false, XK_5 }, + { false, XK_6 }, + { false, XK_7 }, + { false, XK_8 }, + { false, XK_9 }, + // Miscellaneous other keysyms. + { false, XK_BackSpace }, + { false, XK_Scroll_Lock }, + { false, XK_Multi_key }, + { false, XK_Select }, + { false, XK_Num_Lock }, + { false, XK_Shift_L }, + { false, XK_space }, + { false, XK_A }, + }; + + for (size_t k = 0; k < ARRAYSIZE_UNSAFE(keys); ++k) { + int x_keycode = XKeysymToKeycode(display, keys[k].x_keysym); + // Exclude keysyms for which the server has no corresponding keycode. + if (x_keycode) { + InitKeyEvent(display, &event, true, x_keycode, 0); + // int keysym = XLookupKeysym(&event.xkey, 0); + // if (keysym) { + ui::KeyEvent ui_key_event(&event, false); + EXPECT_EQ(keys[k].is_numpad_key ? ui::EF_NUMPAD_KEY : 0, + ui_key_event.flags() & ui::EF_NUMPAD_KEY); + } + } +} + +TEST(EventsXTest, FunctionKeyEvents) { + Display* display = gfx::GetXDisplay(); + + // Min function key code minus 1. + EXPECT_FALSE(HasFunctionKeyFlagSetIfSupported(display, XK_F1 - 1)); + // All function keys. + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F1)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F2)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F3)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F4)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F5)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F6)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F7)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F8)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F9)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F10)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F11)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F12)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F13)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F14)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F15)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F16)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F17)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F18)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F19)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F20)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F21)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F22)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F23)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F24)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F25)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F26)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F27)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F28)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F29)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F30)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F31)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F32)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F33)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F34)); + EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F35)); + // Max function key code plus 1. + EXPECT_FALSE(HasFunctionKeyFlagSetIfSupported(display, XK_F35 + 1)); +} + +#if !defined(OS_CHROMEOS) +TEST(EventsXTest, ImeFabricatedKeyEvents) { + Display* display = gfx::GetXDisplay(); + + unsigned int state_to_be_fabricated[] = { + 0, ShiftMask, LockMask, ShiftMask | LockMask, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(state_to_be_fabricated); ++i) { + unsigned int state = state_to_be_fabricated[i]; + for (int is_char = 0; is_char < 2; ++is_char) { + XEvent x_event; + InitKeyEvent(display, &x_event, true, 0, state); + ui::KeyEvent key_event(&x_event, is_char); + EXPECT_TRUE(key_event.flags() & ui::EF_IME_FABRICATED_KEY); + } + } + + unsigned int state_to_be_not_fabricated[] = { + ControlMask, Mod1Mask, Mod2Mask, ShiftMask | ControlMask, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(state_to_be_not_fabricated); ++i) { + unsigned int state = state_to_be_not_fabricated[i]; + for (int is_char = 0; is_char < 2; ++is_char) { + XEvent x_event; + InitKeyEvent(display, &x_event, true, 0, state); + ui::KeyEvent key_event(&x_event, is_char); + EXPECT_FALSE(key_event.flags() & ui::EF_IME_FABRICATED_KEY); + } + } +} +#endif + } // namespace ui diff --git a/chromium/ui/events/x/touch_factory_x11.cc b/chromium/ui/events/x/touch_factory_x11.cc index 62f6c89dab7..8176ce221a6 100644 --- a/chromium/ui/events/x/touch_factory_x11.cc +++ b/chromium/ui/events/x/touch_factory_x11.cc @@ -4,6 +4,7 @@ #include "ui/events/x/touch_factory_x11.h" +#include <X11/Xatom.h> #include <X11/cursorfont.h> #include <X11/extensions/XInput.h> #include <X11/extensions/XInput2.h> @@ -81,6 +82,7 @@ void TouchFactory::UpdateDeviceList(Display* display) { touch_device_available_ = false; touch_device_lookup_.reset(); touch_device_list_.clear(); + touchscreen_ids_.clear(); max_touch_points_ = -1; #if !defined(USE_XI2_MT) @@ -128,7 +130,7 @@ void TouchFactory::UpdateDeviceList(Display* display) { XIAnyClassInfo* xiclassinfo = devinfo->classes[k]; if (xiclassinfo->type == XITouchClass) { XITouchClassInfo* tci = - reinterpret_cast<XITouchClassInfo *>(xiclassinfo); + 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; @@ -142,6 +144,21 @@ void TouchFactory::UpdateDeviceList(Display* display) { #endif pointer_device_lookup_[devinfo->deviceid] = true; } + +#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<XITouchClassInfo*>(xiclassinfo); + // Only care direct touch device (such as touch screen) right now + if (tci->mode == XIDirectTouch) + CacheTouchscreenIds(display, devinfo->deviceid); + } + } + } +#endif } } @@ -268,4 +285,43 @@ void TouchFactory::SetPointerDeviceForTest( } } +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<long*>(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 diff --git a/chromium/ui/events/x/touch_factory_x11.h b/chromium/ui/events/x/touch_factory_x11.h index ed62c6d247f..4d8a989fd8f 100644 --- a/chromium/ui/events/x/touch_factory_x11.h +++ b/chromium/ui/events/x/touch_factory_x11.h @@ -7,6 +7,8 @@ #include <bitset> #include <map> +#include <set> +#include <utility> #include <vector> #include "base/timer/timer.h" @@ -72,6 +74,11 @@ class EVENTS_BASE_EXPORT TouchFactory { // Whether any touch device is currently present and enabled. bool IsTouchDevicePresent(); + // Pairs of <vendor id, product id> of external touch screens. + const std::set<std::pair<int, int> >& GetTouchscreenIds() const { + return touchscreen_ids_; + } + // Return maximum simultaneous touch points supported by device. int GetMaxTouchPoints() const; @@ -89,6 +96,8 @@ class EVENTS_BASE_EXPORT TouchFactory { // Requirement for Singleton friend struct DefaultSingletonTraits<TouchFactory>; + void CacheTouchscreenIds(Display* display, int id); + // NOTE: To keep track of touch devices, we currently maintain a lookup table // to quickly decide if a device is a touch device or not. We also maintain a // list of the touch devices. Ideally, there will be only one touch device, @@ -118,6 +127,9 @@ class EVENTS_BASE_EXPORT TouchFactory { // capable. std::map<int, bool> touch_device_list_; + // Touch screen <vid, pid>s. + std::set<std::pair<int, int> > touchscreen_ids_; + // Maximum simultaneous touch points supported by device. In the case of // devices with multiple digitizers (e.g. multiple touchscreens), the value // is the maximum of the set of maximum supported contacts by each individual |