Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
new file mode 100644
index 0000000..7277cb3
--- /dev/null
+++ b/ui/events/BUILD.gn
@@ -0,0 +1,330 @@
+# 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 = [
+ "device_data_manager.cc",
+ "device_data_manager.h",
+ "device_hotplug_event_observer.h",
+ "event_constants.h",
+ "event_switches.cc",
+ "event_switches.h",
+ "events_base_export.h",
+ "gesture_event_details.cc",
+ "gesture_event_details.h",
+ "gestures/fling_curve.cc",
+ "gestures/fling_curve.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",
+ "touchscreen_device.cc",
+ "touchscreen_device.h",
+ ]
+
+ defines = [ "EVENTS_BASE_IMPLEMENTATION" ]
+
+ deps = [
+ ":dom4_keycode_converter",
+ "//base/third_party/dynamic_annotations",
+ "//skia",
+ ]
+
+ public_deps = [
+ "//base",
+ "//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_x11.cc",
+ "x/device_data_manager_x11.h",
+ "x/device_list_cache_x.cc",
+ "x/device_list_cache_x.h",
+ "x/hotplug_event_handler_x11.cc",
+ "x/hotplug_event_handler_x11.h",
+ "x/keysym_to_unicode.cc",
+ "x/keysym_to_unicode.h",
+ "x/touch_factory_x11.cc",
+ "x/touch_factory_x11.h",
+ ]
+
+ deps += [ "//ui/gfx/x" ]
+ }
+}
+
+component("events") {
+ sources = [
+ "cocoa/cocoa_event_utils.h",
+ "cocoa/cocoa_event_utils.mm",
+ "cocoa/events_mac.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_recognizer_impl_mac.cc",
+ "gestures/gesture_types.h",
+ "win/events_win.cc",
+ ]
+
+ defines = [ "EVENTS_IMPLEMENTATION" ]
+
+ public_deps = [
+ ":events_base",
+ ]
+ deps = [
+ ":dom4_keycode_converter",
+ ":gesture_detection",
+ "//base/third_party/dynamic_annotations",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ if (use_x11) {
+ sources += [ "x/events_x.cc" ]
+ configs += [
+ "//build/config/linux:glib",
+ "//build/config/linux:x11",
+ ]
+ deps += [ "//ui/gfx/x" ]
+ }
+
+ 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/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",
+ "gestures/gesture_recognizer.h",
+ "gestures/gesture_recognizer_impl.cc",
+ "gestures/gesture_recognizer_impl.h",
+ ]
+ }
+
+ if (is_win || is_mac || 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_config_helper.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_listeners.cc",
+ "gesture_detection/gesture_listeners.h",
+ "gesture_detection/gesture_provider.cc",
+ "gesture_detection/gesture_provider.h",
+ "gesture_detection/gesture_touch_uma_histogram.cc",
+ "gesture_detection/gesture_touch_uma_histogram.h",
+ "gesture_detection/motion_event.cc",
+ "gesture_detection/motion_event.h",
+ "gesture_detection/motion_event_buffer.cc",
+ "gesture_detection/motion_event_buffer.h",
+ "gesture_detection/motion_event_generic.cc",
+ "gesture_detection/motion_event_generic.h",
+ "gesture_detection/scale_gesture_detector.cc",
+ "gesture_detection/scale_gesture_detector.h",
+ "gesture_detection/scale_gesture_listeners.cc",
+ "gesture_detection/scale_gesture_listeners.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("test_support") {
+ sources = [
+ "test/cocoa_test_event_utils.h",
+ "test/cocoa_test_event_utils.mm",
+ "test/event_generator.cc",
+ "test/event_generator.h",
+ "test/events_test_utils.cc",
+ "test/events_test_utils.h",
+ "test/mock_motion_event.cc",
+ "test/mock_motion_event.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",
+ ]
+
+ public_deps = [
+ ":events",
+ ":events_base",
+ ":gesture_detection",
+ ]
+ deps = [
+ "//base",
+ "//skia",
+ "//ui/events/platform",
+ "//ui/gfx/geometry",
+ ]
+
+ if (is_ios) {
+ sources -= [
+ "test/cocoa_test_event_utils.h",
+ "test/cocoa_test_event_utils.mm",
+ ]
+ }
+
+ if (use_x11) {
+ sources += [
+ "test/events_test_utils_x11.cc",
+ "test/events_test_utils_x11.h",
+ ]
+ deps += [ "//ui/gfx/x" ]
+ }
+}
+
+test("events_unittests") {
+ sources = [
+ "cocoa/events_mac_unittest.mm",
+ "event_dispatcher_unittest.cc",
+ "event_processor_unittest.cc",
+ "event_rewriter_unittest.cc",
+ "event_unittest.cc",
+ "gesture_detection/bitset_32_unittest.cc",
+ "gesture_detection/gesture_event_data_packet_unittest.cc",
+ "gesture_detection/gesture_provider_unittest.cc",
+ "gesture_detection/motion_event_buffer_unittest.cc",
+ "gesture_detection/motion_event_generic_unittest.cc",
+ "gesture_detection/touch_disposition_gesture_filter_unittest.cc",
+ "gesture_detection/velocity_tracker_unittest.cc",
+ "gestures/fling_curve_unittest.cc",
+ "keycodes/dom4/keycode_converter_unittest.cc",
+ "latency_info_unittest.cc",
+ "platform/platform_event_source_unittest.cc",
+ "x/events_x_unittest.cc",
+ ]
+
+ deps = [
+ ":dom4_keycode_converter",
+ ":events",
+ ":events_base",
+ ":gesture_detection",
+ ":test_support",
+ "//base",
+ "//base/test:run_all_unittests",
+ "//skia",
+ "//testing/gtest",
+ "//ui/events/platform",
+ "//ui/gfx:test_support",
+ ]
+
+ if (use_x11) {
+ configs += [ "//build/config/linux:x11" ]
+ deps += [ "//ui/gfx/x" ]
+ } else {
+ 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",
+ ]
+ }
+
+ if (use_aura) {
+ sources += [
+ "gestures/gesture_provider_aura_unittest.cc",
+ ]
+ }
+}
diff --git a/ui/events/DEPS b/ui/events/DEPS
new file mode 100644
index 0000000..b273ae3
--- /dev/null
+++ b/ui/events/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/gfx",
+]
diff --git a/ui/events/OWNERS b/ui/events/OWNERS
new file mode 100644
index 0000000..aeec937
--- /dev/null
+++ b/ui/events/OWNERS
@@ -0,0 +1,6 @@
+sadrul@chromium.org
+per-file latency_info*=jbauman@chromium.org
+per-file latency_info*=miletus@chromium.org
+
+# If you're doing structural changes get a review from one of the OWNERS.
+per-file *.gyp*=*
diff --git a/ui/events/PRESUBMIT.py b/ui/events/PRESUBMIT.py
new file mode 100644
index 0000000..bc905a3
--- /dev/null
+++ b/ui/events/PRESUBMIT.py
@@ -0,0 +1,28 @@
+# 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': {
+ 'linux_chromium_rel_swarming': tests,
+ 'linux_chromium_chromeos_rel_swarming': tests,
+ 'linux_chromeos_asan': tests,
+ },
+ 'tryserver.chromium.win': {
+ 'win_chromium_compile_dbg': tests,
+ 'win_chromium_rel_swarming': tests,
+ }
+ }
diff --git a/ui/events/cocoa/cocoa_event_utils.h b/ui/events/cocoa/cocoa_event_utils.h
new file mode 100644
index 0000000..a598435
--- /dev/null
+++ b/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/ui/events/cocoa/cocoa_event_utils.mm b/ui/events/cocoa/cocoa_event_utils.mm
new file mode 100644
index 0000000..ade083c
--- /dev/null
+++ b/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/ui/events/cocoa/events_mac.mm b/ui/events/cocoa/events_mac.mm
new file mode 100644
index 0000000..4772b27
--- /dev/null
+++ b/ui/events/cocoa/events_mac.mm
@@ -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/event_utils.h"
+
+#include <Cocoa/Cocoa.h>
+
+#import "base/mac/mac_util.h"
+#import "base/mac/sdk_forward_declarations.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 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 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 NSEventTypeSwipe:
+ return ET_SCROLL_FLING_START;
+ case NSFlagsChanged:
+ case NSAppKitDefined:
+ case NSSystemDefined:
+ case NSApplicationDefined:
+ case NSPeriodic:
+ case NSCursorUpdate:
+ case NSTabletPoint:
+ case NSTabletProximity:
+ case NSEventTypeGesture:
+ case NSEventTypeMagnify:
+ case NSEventTypeRotate:
+ case NSEventTypeBeginGesture:
+ case NSEventTypeEndGesture:
+ NOTIMPLEMENTED() << type;
+ break;
+ default:
+ NOTIMPLEMENTED() << type;
+ break;
+ }
+ return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& event) {
+ NSUInteger modifiers = [event modifierFlags];
+ return EventFlagsFromNSEventWithModifiers(event, modifiers);
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+ 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];
+ 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) {
+ NOTIMPLEMENTED();
+ return gfx::Point();
+}
+
+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;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& event) {
+ if ([event respondsToSelector:@selector(hasPreciseScrollingDeltas)] &&
+ [event hasPreciseScrollingDeltas]) {
+ // Handle continuous scrolling devices such as a Magic Mouse or a trackpad.
+ // -scrollingDelta{X|Y} have float return types but they return values that
+ // are already rounded to integers.
+ // The values are the same as the values returned from calling
+ // CGEventGetIntegerValueField(kCGScrollWheelEventPointDeltaAxis{1|2}).
+ return gfx::Vector2d([event scrollingDeltaX], [event scrollingDeltaY]);
+ } else {
+ // 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]);
+ }
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+ return [event copy];
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+ [event release];
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+ 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 GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ 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);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ return native_event.keyCode;
+}
+
+uint32 WindowsKeycodeFromNative(const base::NativeEvent& native_event) {
+ return static_cast<uint32>(KeyboardCodeFromNSEvent(native_event));
+}
+
+uint16 TextFromNative(const base::NativeEvent& native_event) {
+ NSString* text = @"";
+ if ([native_event type] != NSFlagsChanged)
+ text = [native_event characters];
+
+ // These exceptions are based on WebInputEventFactoryMac.mm:
+ uint32 windows_keycode = WindowsKeycodeFromNative(native_event);
+ if (windows_keycode == '\r')
+ text = @"\r";
+ if ([text isEqualToString:@"\x7F"])
+ text = @"\x8";
+ if (windows_keycode == 9)
+ text = @"\x9";
+
+ uint16 return_value;
+ [text getCharacters:&return_value];
+ return return_value;
+}
+
+uint16 UnmodifiedTextFromNative(const base::NativeEvent& native_event) {
+ NSString* text = @"";
+ if ([native_event type] != NSFlagsChanged)
+ text = [native_event charactersIgnoringModifiers];
+
+ // These exceptions are based on WebInputEventFactoryMac.mm:
+ uint32 windows_keycode = WindowsKeycodeFromNative(native_event);
+ if (windows_keycode == '\r')
+ text = @"\r";
+ if ([text isEqualToString:@"\x7F"])
+ text = @"\x8";
+ if (windows_keycode == 9)
+ text = @"\x9";
+
+ uint16 return_value;
+ [text getCharacters:&return_value];
+ return return_value;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+ return false;
+}
+
+} // namespace ui
diff --git a/ui/events/cocoa/events_mac_unittest.mm b/ui/events/cocoa/events_mac_unittest.mm
new file mode 100644
index 0000000..f58cfd0
--- /dev/null
+++ b/ui/events/cocoa/events_mac_unittest.mm
@@ -0,0 +1,314 @@
+// 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"
+#import "base/mac/scoped_objc_class_swizzler.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
+- (NSInteger)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 base::mac::ScopedObjCClassSwizzler(
+ [NSEvent class],
+ [MiddleMouseButtonNumberDonor class],
+ @selector(buttonNumber)));
+ }
+
+ void SwizzleTestWindow() {
+ DCHECK(!g_test_window);
+ DCHECK(!swizzler_);
+ g_test_window = test_window();
+ swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
+ [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<base::mac::ScopedObjCClassSwizzler> 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/ui/events/device_data_manager.cc b/ui/events/device_data_manager.cc
new file mode 100644
index 0000000..d255b87
--- /dev/null
+++ b/ui/events/device_data_manager.cc
@@ -0,0 +1,129 @@
+// 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/device_data_manager.h"
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "ui/events/input_device_event_observer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/geometry/point3_f.h"
+
+namespace ui {
+
+// static
+DeviceDataManager* DeviceDataManager::instance_ = NULL;
+
+DeviceDataManager::DeviceDataManager() {
+ CHECK(!instance_) << "Can not create multiple instances of DeviceDataManager";
+ instance_ = this;
+
+ base::AtExitManager::RegisterTask(
+ base::Bind(&base::DeletePointer<DeviceDataManager>, this));
+
+ for (int i = 0; i < kMaxDeviceNum; ++i) {
+ touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID;
+ touch_radius_scale_map_[i] = 1.0;
+ }
+}
+
+DeviceDataManager::~DeviceDataManager() {
+ CHECK_EQ(this, instance_);
+ instance_ = NULL;
+}
+
+// static
+DeviceDataManager* DeviceDataManager::instance() { return instance_; }
+
+// static
+void DeviceDataManager::CreateInstance() {
+ if (instance())
+ return;
+
+ new DeviceDataManager();
+}
+
+// static
+DeviceDataManager* DeviceDataManager::GetInstance() {
+ CHECK(instance_) << "DeviceDataManager was not created.";
+ return instance_;
+}
+
+// static
+bool DeviceDataManager::HasInstance() {
+ return instance_ != NULL;
+}
+
+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;
+ touch_radius_scale_map_[i] = 1.0;
+ }
+}
+
+bool DeviceDataManager::IsTouchDeviceIdValid(int touch_device_id) const {
+ return (touch_device_id > 0 && touch_device_id < kMaxDeviceNum);
+}
+
+void DeviceDataManager::UpdateTouchInfoForDisplay(
+ int64_t 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::UpdateTouchRadiusScale(int touch_device_id,
+ double scale) {
+ if (IsTouchDeviceIdValid(touch_device_id))
+ touch_radius_scale_map_[touch_device_id] = scale;
+}
+
+void DeviceDataManager::ApplyTouchRadiusScale(int touch_device_id,
+ double* radius) {
+ if (IsTouchDeviceIdValid(touch_device_id))
+ *radius = (*radius) * touch_radius_scale_map_[touch_device_id];
+}
+
+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_t 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;
+}
+
+void DeviceDataManager::OnTouchscreenDevicesUpdated(
+ const std::vector<TouchscreenDevice>& devices) {
+ touchscreen_devices_ = devices;
+
+ FOR_EACH_OBSERVER(InputDeviceEventObserver,
+ observers_,
+ OnInputDeviceConfigurationChanged());
+}
+
+void DeviceDataManager::AddObserver(InputDeviceEventObserver* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DeviceDataManager::RemoveObserver(InputDeviceEventObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+} // namespace ui
diff --git a/ui/events/device_data_manager.h b/ui/events/device_data_manager.h
new file mode 100644
index 0000000..aae8272
--- /dev/null
+++ b/ui/events/device_data_manager.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_DEVICE_DATA_MANAGER_H_
+#define UI_EVENTS_DEVICE_DATA_MANAGER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/events/device_hotplug_event_observer.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/touchscreen_device.h"
+#include "ui/gfx/transform.h"
+
+namespace ui {
+
+class InputDeviceEventObserver;
+
+// Keeps track of device mappings and event transformations.
+class EVENTS_BASE_EXPORT DeviceDataManager : public DeviceHotplugEventObserver {
+ public:
+ virtual ~DeviceDataManager();
+
+ static void CreateInstance();
+ static DeviceDataManager* GetInstance();
+ static bool HasInstance();
+
+ void ClearTouchTransformerRecord();
+ void UpdateTouchInfoForDisplay(int64_t display_id,
+ int touch_device_id,
+ const gfx::Transform& touch_transformer);
+ void ApplyTouchTransformer(int touch_device_id, float* x, float* y);
+ int64_t GetDisplayForTouchDevice(int touch_device_id) const;
+
+ void UpdateTouchRadiusScale(int touch_device_id, double scale);
+ void ApplyTouchRadiusScale(int touch_device_id, double* radius);
+
+ const std::vector<TouchscreenDevice>& touchscreen_devices() const {
+ return touchscreen_devices_;
+ }
+
+ void AddObserver(InputDeviceEventObserver* observer);
+ void RemoveObserver(InputDeviceEventObserver* observer);
+
+ protected:
+ DeviceDataManager();
+
+ static DeviceDataManager* instance();
+
+ static const int kMaxDeviceNum = 128;
+
+ private:
+ static DeviceDataManager* instance_;
+
+ bool IsTouchDeviceIdValid(int touch_device_id) const;
+
+ // DeviceHotplugEventObserver:
+ virtual void OnTouchscreenDevicesUpdated(
+ const std::vector<TouchscreenDevice>& devices) OVERRIDE;
+
+ double touch_radius_scale_map_[kMaxDeviceNum];
+
+ // Table to keep track of which display id is mapped to which touch device.
+ int64_t touch_device_to_display_map_[kMaxDeviceNum];
+ // Index table to find the TouchTransformer for a touch device.
+ gfx::Transform touch_device_transformer_map_[kMaxDeviceNum];
+
+ std::vector<TouchscreenDevice> touchscreen_devices_;
+
+ ObserverList<InputDeviceEventObserver> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceDataManager);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_DEVICE_DATA_MANAGER_H_
diff --git a/ui/events/device_hotplug_event_observer.h b/ui/events/device_hotplug_event_observer.h
new file mode 100644
index 0000000..8a763a6
--- /dev/null
+++ b/ui/events/device_hotplug_event_observer.h
@@ -0,0 +1,25 @@
+// 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_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
+#define UI_EVENTS_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/touchscreen_device.h"
+
+namespace ui {
+
+// Listener for specific input device hotplug events.
+class EVENTS_BASE_EXPORT DeviceHotplugEventObserver {
+ public:
+ virtual ~DeviceHotplugEventObserver() {}
+
+ // On a hotplug event this is called with the list of available devices.
+ virtual void OnTouchscreenDevicesUpdated(
+ const std::vector<TouchscreenDevice>& devices) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
diff --git a/ui/events/event.cc b/ui/events/event.cc
new file mode 100644
index 0000000..72e3bd6
--- /dev/null
+++ b/ui/events/event.cc
@@ -0,0 +1,970 @@
+// 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/event.h"
+
+#if defined(USE_X11)
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#endif
+
+#include <cmath>
+#include <cstring>
+
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+#if defined(USE_X11)
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#elif defined(USE_OZONE)
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#endif
+
+namespace {
+
+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
+ switch (type) {
+ CASE_TYPE(ET_UNKNOWN);
+ CASE_TYPE(ET_MOUSE_PRESSED);
+ CASE_TYPE(ET_MOUSE_DRAGGED);
+ CASE_TYPE(ET_MOUSE_RELEASED);
+ CASE_TYPE(ET_MOUSE_MOVED);
+ CASE_TYPE(ET_MOUSE_ENTERED);
+ CASE_TYPE(ET_MOUSE_EXITED);
+ CASE_TYPE(ET_KEY_PRESSED);
+ CASE_TYPE(ET_KEY_RELEASED);
+ CASE_TYPE(ET_MOUSEWHEEL);
+ CASE_TYPE(ET_MOUSE_CAPTURE_CHANGED);
+ CASE_TYPE(ET_TOUCH_RELEASED);
+ CASE_TYPE(ET_TOUCH_PRESSED);
+ CASE_TYPE(ET_TOUCH_MOVED);
+ CASE_TYPE(ET_TOUCH_CANCELLED);
+ CASE_TYPE(ET_DROP_TARGET_EVENT);
+ CASE_TYPE(ET_TRANSLATED_KEY_PRESS);
+ CASE_TYPE(ET_TRANSLATED_KEY_RELEASE);
+ CASE_TYPE(ET_GESTURE_SCROLL_BEGIN);
+ 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);
+ CASE_TYPE(ET_GESTURE_BEGIN);
+ CASE_TYPE(ET_GESTURE_END);
+ CASE_TYPE(ET_GESTURE_TWO_FINGER_TAP);
+ CASE_TYPE(ET_GESTURE_PINCH_BEGIN);
+ CASE_TYPE(ET_GESTURE_PINCH_END);
+ CASE_TYPE(ET_GESTURE_PINCH_UPDATE);
+ CASE_TYPE(ET_GESTURE_LONG_PRESS);
+ CASE_TYPE(ET_GESTURE_LONG_TAP);
+ 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);
+ CASE_TYPE(ET_CANCEL_MODE);
+ CASE_TYPE(ET_UMA_DATA);
+ case ui::ET_LAST: NOTREACHED(); return std::string();
+ // Don't include default, so that we get an error when new type is added.
+ }
+#undef CASE_TYPE
+
+ NOTREACHED();
+ return std::string();
+}
+
+bool IsX11SendEventTrue(const base::NativeEvent& event) {
+#if defined(USE_X11)
+ 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
+
+namespace ui {
+
+////////////////////////////////////////////////////////////////////////////////
+// Event
+
+// static
+scoped_ptr<Event> Event::Clone(const Event& event) {
+ if (event.IsKeyEvent()) {
+ return scoped_ptr<Event>(new KeyEvent(static_cast<const KeyEvent&>(event)));
+ }
+
+ if (event.IsMouseEvent()) {
+ if (event.IsMouseWheelEvent()) {
+ return scoped_ptr<Event>(
+ new MouseWheelEvent(static_cast<const MouseWheelEvent&>(event)));
+ }
+
+ return scoped_ptr<Event>(
+ new MouseEvent(static_cast<const MouseEvent&>(event)));
+ }
+
+ if (event.IsTouchEvent()) {
+ return scoped_ptr<Event>(
+ new TouchEvent(static_cast<const TouchEvent&>(event)));
+ }
+
+ if (event.IsGestureEvent()) {
+ return scoped_ptr<Event>(
+ new GestureEvent(static_cast<const GestureEvent&>(event)));
+ }
+
+ if (event.IsScrollEvent()) {
+ return scoped_ptr<Event>(
+ new ScrollEvent(static_cast<const ScrollEvent&>(event)));
+ }
+
+ return scoped_ptr<Event>(new Event(event));
+}
+
+Event::~Event() {
+ if (delete_native_event_)
+ ReleaseCopiedNativeEvent(native_event_);
+}
+
+GestureEvent* Event::AsGestureEvent() {
+ CHECK(IsGestureEvent());
+ return static_cast<GestureEvent*>(this);
+}
+
+const GestureEvent* Event::AsGestureEvent() const {
+ CHECK(IsGestureEvent());
+ return static_cast<const GestureEvent*>(this);
+}
+
+bool Event::HasNativeEvent() const {
+ base::NativeEvent null_event;
+ std::memset(&null_event, 0, sizeof(null_event));
+ return !!std::memcmp(&native_event_, &null_event, sizeof(null_event));
+}
+
+void Event::StopPropagation() {
+ // TODO(sad): Re-enable these checks once View uses dispatcher to dispatch
+ // events.
+ // CHECK(phase_ != EP_PREDISPATCH && phase_ != EP_POSTDISPATCH);
+ CHECK(cancelable_);
+ result_ = static_cast<EventResult>(result_ | ER_CONSUMED);
+}
+
+void Event::SetHandled() {
+ // TODO(sad): Re-enable these checks once View uses dispatcher to dispatch
+ // events.
+ // CHECK(phase_ != EP_PREDISPATCH && phase_ != EP_POSTDISPATCH);
+ CHECK(cancelable_);
+ result_ = static_cast<EventResult>(result_ | ER_HANDLED);
+}
+
+Event::Event(EventType type, base::TimeDelta time_stamp, int flags)
+ : type_(type),
+ time_stamp_(time_stamp),
+ flags_(flags),
+ native_event_(base::NativeEvent()),
+ delete_native_event_(false),
+ cancelable_(true),
+ target_(NULL),
+ phase_(EP_PREDISPATCH),
+ result_(ER_UNHANDLED),
+ source_device_id_(ED_UNKNOWN_DEVICE) {
+ if (type_ < ET_LAST)
+ name_ = EventTypeName(type_);
+}
+
+Event::Event(const base::NativeEvent& native_event,
+ EventType type,
+ int flags)
+ : type_(type),
+ time_stamp_(EventTimeFromNative(native_event)),
+ flags_(flags),
+ native_event_(native_event),
+ delete_native_event_(false),
+ cancelable_(true),
+ target_(NULL),
+ phase_(EP_PREDISPATCH),
+ result_(ER_UNHANDLED),
+ source_device_id_(ED_UNKNOWN_DEVICE) {
+ base::TimeDelta delta = EventTimeForNow() - time_stamp_;
+ if (type_ < ET_LAST)
+ name_ = EventTypeName(type_);
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser",
+ 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,
+ 1,
+ 1000000,
+ 100,
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ counter_for_type->Add(delta.InMicroseconds());
+
+#if defined(USE_X11)
+ if (native_event->type == GenericEvent) {
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ source_device_id_ = xiev->sourceid;
+ }
+#endif
+}
+
+Event::Event(const Event& copy)
+ : type_(copy.type_),
+ time_stamp_(copy.time_stamp_),
+ latency_(copy.latency_),
+ flags_(copy.flags_),
+ native_event_(CopyNativeEvent(copy.native_event_)),
+ delete_native_event_(true),
+ cancelable_(true),
+ target_(NULL),
+ phase_(EP_PREDISPATCH),
+ result_(ER_UNHANDLED),
+ source_device_id_(copy.source_device_id_) {
+ if (type_ < ET_LAST)
+ name_ = EventTypeName(type_);
+}
+
+void Event::SetType(EventType type) {
+ if (type_ < ET_LAST)
+ name_ = std::string();
+ type_ = type;
+ if (type_ < ET_LAST)
+ name_ = EventTypeName(type_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CancelModeEvent
+
+CancelModeEvent::CancelModeEvent()
+ : Event(ET_CANCEL_MODE, base::TimeDelta(), 0) {
+ set_cancelable(false);
+}
+
+CancelModeEvent::~CancelModeEvent() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocatedEvent
+
+LocatedEvent::~LocatedEvent() {
+}
+
+LocatedEvent::LocatedEvent(const base::NativeEvent& native_event)
+ : Event(native_event,
+ EventTypeFromNative(native_event),
+ EventFlagsFromNative(native_event)),
+ location_(EventLocationFromNative(native_event)),
+ root_location_(location_) {
+}
+
+LocatedEvent::LocatedEvent(EventType type,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
+ base::TimeDelta time_stamp,
+ int flags)
+ : Event(type, time_stamp, flags),
+ location_(location),
+ root_location_(root_location) {
+}
+
+void LocatedEvent::UpdateForRootTransform(
+ const gfx::Transform& reversed_root_transform) {
+ // Transform has to be done at root level.
+ gfx::Point3F p(location_);
+ reversed_root_transform.TransformPoint(&p);
+ location_ = p.AsPointF();
+ root_location_ = location_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseEvent
+
+MouseEvent::MouseEvent(const base::NativeEvent& native_event)
+ : LocatedEvent(native_event),
+ changed_button_flags_(
+ GetChangedMouseButtonFlagsFromNative(native_event)) {
+ if (type() == ET_MOUSE_PRESSED || type() == ET_MOUSE_RELEASED)
+ SetClickCount(GetRepeatCount(*this));
+}
+
+MouseEvent::MouseEvent(EventType type,
+ 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_(changed_button_flags) {
+ if (this->type() == ET_MOUSE_MOVED && IsAnyButton())
+ SetType(ET_MOUSE_DRAGGED);
+}
+
+// static
+bool MouseEvent::IsRepeatedClickEvent(
+ const MouseEvent& event1,
+ const MouseEvent& event2) {
+ // These values match the Windows defaults.
+ static const int kDoubleClickTimeMS = 500;
+ static const int kDoubleClickWidth = 4;
+ static const int kDoubleClickHeight = 4;
+
+ if (event1.type() != ET_MOUSE_PRESSED ||
+ event2.type() != ET_MOUSE_PRESSED)
+ return false;
+
+ // Compare flags, but ignore EF_IS_DOUBLE_CLICK to allow triple clicks.
+ if ((event1.flags() & ~EF_IS_DOUBLE_CLICK) !=
+ (event2.flags() & ~EF_IS_DOUBLE_CLICK))
+ return false;
+
+ base::TimeDelta time_difference = event2.time_stamp() - event1.time_stamp();
+
+ if (time_difference.InMilliseconds() > kDoubleClickTimeMS)
+ return false;
+
+ if (std::abs(event2.x() - event1.x()) > kDoubleClickWidth / 2)
+ return false;
+
+ if (std::abs(event2.y() - event1.y()) > kDoubleClickHeight / 2)
+ return false;
+
+ return true;
+}
+
+// static
+int MouseEvent::GetRepeatCount(const MouseEvent& event) {
+ int click_count = 1;
+ if (last_click_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)) {
+ 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)
+ return 0;
+
+ if (flags() & EF_IS_TRIPLE_CLICK)
+ return 3;
+ else if (flags() & EF_IS_DOUBLE_CLICK)
+ return 2;
+ else
+ return 1;
+}
+
+void MouseEvent::SetClickCount(int click_count) {
+ if (type() != ET_MOUSE_PRESSED && type() != ET_MOUSE_RELEASED)
+ return;
+
+ DCHECK(click_count > 0);
+ DCHECK(click_count <= 3);
+
+ int f = flags();
+ switch (click_count) {
+ case 1:
+ f &= ~EF_IS_DOUBLE_CLICK;
+ f &= ~EF_IS_TRIPLE_CLICK;
+ break;
+ case 2:
+ f |= EF_IS_DOUBLE_CLICK;
+ f &= ~EF_IS_TRIPLE_CLICK;
+ break;
+ case 3:
+ f &= ~EF_IS_DOUBLE_CLICK;
+ f |= EF_IS_TRIPLE_CLICK;
+ break;
+ }
+ set_flags(f);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent
+
+MouseWheelEvent::MouseWheelEvent(const base::NativeEvent& native_event)
+ : MouseEvent(native_event),
+ offset_(GetMouseWheelOffset(native_event)) {
+}
+
+MouseWheelEvent::MouseWheelEvent(const ScrollEvent& scroll_event)
+ : MouseEvent(scroll_event),
+ offset_(scroll_event.x_offset(), scroll_event.y_offset()){
+ SetType(ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const MouseEvent& mouse_event,
+ int x_offset,
+ int y_offset)
+ : MouseEvent(mouse_event), offset_(x_offset, y_offset) {
+ DCHECK(type() == ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const MouseWheelEvent& mouse_wheel_event)
+ : MouseEvent(mouse_wheel_event),
+ offset_(mouse_wheel_event.offset()) {
+ DCHECK(type() == ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const gfx::Vector2d& offset,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
+ int flags,
+ int changed_button_flags)
+ : MouseEvent(ui::ET_MOUSEWHEEL, location, root_location, flags,
+ changed_button_flags),
+ offset_(offset) {
+}
+
+#if defined(OS_WIN)
+// This value matches windows WHEEL_DELTA.
+// static
+const int MouseWheelEvent::kWheelDelta = 120;
+#else
+// This value matches GTK+ wheel scroll amount.
+const int MouseWheelEvent::kWheelDelta = 53;
+#endif
+
+void MouseWheelEvent::UpdateForRootTransform(
+ const gfx::Transform& inverted_root_transform) {
+ LocatedEvent::UpdateForRootTransform(inverted_root_transform);
+ gfx::DecomposedTransform decomp;
+ bool success = gfx::DecomposeTransform(&decomp, inverted_root_transform);
+ DCHECK(success);
+ if (decomp.scale[0])
+ offset_.set_x(offset_.x() * decomp.scale[0]);
+ if (decomp.scale[1])
+ offset_.set_y(offset_.y() * decomp.scale[1]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent
+
+TouchEvent::TouchEvent(const base::NativeEvent& native_event)
+ : LocatedEvent(native_event),
+ touch_id_(GetTouchId(native_event)),
+ radius_x_(GetTouchRadiusX(native_event)),
+ radius_y_(GetTouchRadiusY(native_event)),
+ rotation_angle_(GetTouchAngle(native_event)),
+ force_(GetTouchForce(native_event)) {
+ latency()->AddLatencyNumberWithTimestamp(
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 0,
+ 0,
+ base::TimeTicks::FromInternalValue(time_stamp().ToInternalValue()),
+ 1);
+
+ latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+
+ if (type() == ET_TOUCH_PRESSED)
+ IncrementTouchIdRefCount(native_event);
+}
+
+TouchEvent::TouchEvent(EventType type,
+ const gfx::PointF& location,
+ int touch_id,
+ base::TimeDelta time_stamp)
+ : LocatedEvent(type, location, location, time_stamp, 0),
+ touch_id_(touch_id),
+ radius_x_(0.0f),
+ radius_y_(0.0f),
+ rotation_angle_(0.0f),
+ force_(0.0f) {
+ latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+}
+
+TouchEvent::TouchEvent(EventType type,
+ const gfx::PointF& location,
+ int flags,
+ int touch_id,
+ base::TimeDelta time_stamp,
+ float radius_x,
+ float radius_y,
+ float angle,
+ float force)
+ : LocatedEvent(type, location, location, time_stamp, flags),
+ touch_id_(touch_id),
+ radius_x_(radius_x),
+ radius_y_(radius_y),
+ rotation_angle_(angle),
+ force_(force) {
+ latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+}
+
+TouchEvent::~TouchEvent() {
+ // In ctor TouchEvent(native_event) we call GetTouchId() which in X11
+ // platform setups the tracking_id to slot mapping. So in dtor here,
+ // if this touch event is a release event, we clear the mapping accordingly.
+ if (HasNativeEvent())
+ ClearTouchIdIfReleased(native_event());
+}
+
+void TouchEvent::UpdateForRootTransform(
+ const gfx::Transform& inverted_root_transform) {
+ LocatedEvent::UpdateForRootTransform(inverted_root_transform);
+ gfx::DecomposedTransform decomp;
+ bool success = gfx::DecomposeTransform(&decomp, inverted_root_transform);
+ DCHECK(success);
+ if (decomp.scale[0])
+ radius_x_ *= decomp.scale[0];
+ if (decomp.scale[1])
+ radius_y_ *= decomp.scale[1];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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)
+ : Event(native_event,
+ EventTypeFromNative(native_event),
+ EventFlagsFromNative(native_event)),
+ key_code_(KeyboardCodeFromNative(native_event)),
+ code_(CodeFromNative(native_event)),
+ is_char_(IsCharFromNative(native_event)),
+ platform_keycode_(PlatformKeycodeFromNative(native_event)),
+ character_(0) {
+ if (IsRepeated(*this))
+ set_flags(flags() | ui::EF_IS_REPEAT);
+
+#if defined(USE_X11)
+ NormalizeFlags();
+#endif
+#if defined(OS_WIN)
+ // Only Windows has native character events.
+ if (is_char_)
+ character_ = native_event.wParam;
+#endif
+}
+
+KeyEvent::KeyEvent(EventType type,
+ KeyboardCode key_code,
+ int flags)
+ : Event(type, EventTimeForNow(), flags),
+ key_code_(key_code),
+ is_char_(false),
+ platform_keycode_(0),
+ character_() {
+}
+
+KeyEvent::KeyEvent(EventType type,
+ KeyboardCode key_code,
+ const std::string& code,
+ int flags)
+ : Event(type, EventTimeForNow(), flags),
+ key_code_(key_code),
+ code_(code),
+ is_char_(false),
+ platform_keycode_(0),
+ character_(0) {
+}
+
+KeyEvent::KeyEvent(base::char16 character, KeyboardCode key_code, int flags)
+ : Event(ET_KEY_PRESSED, EventTimeForNow(), flags),
+ key_code_(key_code),
+ code_(""),
+ is_char_(true),
+ platform_keycode_(0),
+ character_(character) {
+}
+
+KeyEvent::KeyEvent(const KeyEvent& rhs)
+ : Event(rhs),
+ key_code_(rhs.key_code_),
+ code_(rhs.code_),
+ is_char_(rhs.is_char_),
+ platform_keycode_(rhs.platform_keycode_),
+ character_(rhs.character_) {
+ if (rhs.extended_key_event_data_)
+ extended_key_event_data_.reset(rhs.extended_key_event_data_->Clone());
+}
+
+KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) {
+ if (this != &rhs) {
+ Event::operator=(rhs);
+ key_code_ = rhs.key_code_;
+ code_ = rhs.code_;
+ is_char_ = rhs.is_char_;
+ platform_keycode_ = rhs.platform_keycode_;
+ character_ = rhs.character_;
+
+ if (rhs.extended_key_event_data_)
+ extended_key_event_data_.reset(rhs.extended_key_event_data_->Clone());
+ }
+ return *this;
+}
+
+KeyEvent::~KeyEvent() {}
+
+void KeyEvent::SetExtendedKeyEventData(scoped_ptr<ExtendedKeyEventData> data) {
+ extended_key_event_data_ = data.Pass();
+}
+
+base::char16 KeyEvent::GetCharacter() const {
+ if (is_char_ || character_)
+ return character_;
+
+ // TODO(kpschoedel): streamline these cases after settling Ozone
+ // positional coding.
+#if defined(OS_WIN)
+ // Native Windows character events always have is_char_ == true,
+ // so this is a synthetic or native keystroke event.
+ character_ = GetCharacterFromKeyCode(key_code_, flags());
+ return character_;
+#elif defined(USE_X11)
+ if (!native_event()) {
+ character_ = GetCharacterFromKeyCode(key_code_, flags());
+ return character_;
+ }
+
+ DCHECK(native_event()->type == KeyPress ||
+ native_event()->type == KeyRelease ||
+ (native_event()->type == GenericEvent &&
+ (native_event()->xgeneric.evtype == XI_KeyPress ||
+ native_event()->xgeneric.evtype == XI_KeyRelease)));
+
+ // 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 ||
+ EventTypeFromNative(native_event()) == ET_KEY_RELEASED);
+ }
+
+ return GetCharacterFromKeyCode(key_code_, flags());
+#endif
+}
+
+base::char16 KeyEvent::GetText() const {
+ if ((flags() & EF_CONTROL_DOWN) != 0) {
+ return GetControlCharacterForKeycode(key_code_,
+ (flags() & EF_SHIFT_DOWN) != 0);
+ }
+ return GetUnmodifiedText();
+}
+
+base::char16 KeyEvent::GetUnmodifiedText() const {
+ if (!is_char_ && (key_code_ == VKEY_RETURN))
+ return '\r';
+ return GetCharacter();
+}
+
+bool KeyEvent::IsUnicodeKeyCode() const {
+#if defined(OS_WIN)
+ if (!IsAltDown())
+ return false;
+ const int key = key_code();
+ if (key >= VKEY_NUMPAD0 && key <= VKEY_NUMPAD9)
+ return true;
+ // Check whether the user is using the numeric keypad with num-lock off.
+ // In that case, EF_EXTENDED will not be set; if it is set, the key event
+ // originated from the relevant non-numpad dedicated key, e.g. [Insert].
+ return (!(flags() & EF_EXTENDED) &&
+ (key == VKEY_INSERT || key == VKEY_END || key == VKEY_DOWN ||
+ 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() {
+ int mask = 0;
+ switch (key_code()) {
+ case VKEY_CONTROL:
+ mask = EF_CONTROL_DOWN;
+ break;
+ case VKEY_SHIFT:
+ mask = EF_SHIFT_DOWN;
+ break;
+ case VKEY_MENU:
+ mask = EF_ALT_DOWN;
+ break;
+ case VKEY_CAPITAL:
+ mask = EF_CAPS_LOCK_DOWN;
+ break;
+ default:
+ return;
+ }
+ if (type() == ET_KEY_PRESSED)
+ set_flags(flags() | mask);
+ else
+ set_flags(flags() & ~mask);
+}
+
+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 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();
+ }
+}
+
+bool KeyEvent::IsRightSideKey() const {
+ switch (key_code_) {
+ case VKEY_CONTROL:
+ case VKEY_SHIFT:
+ case VKEY_MENU:
+ case VKEY_LWIN:
+#if defined(USE_X11)
+ // Under X11, setting code_ requires platform-dependent information, and
+ // currently assumes that X keycodes are based on Linux evdev keycodes.
+ // In certain test environments this is not the case, and code_ is not
+ // set accurately, so we need a different mechanism. Fortunately X11 key
+ // mapping preserves the left-right distinction, so testing keysyms works
+ // if the value is available (as it is for all X11 native-based events).
+ if (platform_keycode_) {
+ return (platform_keycode_ == XK_Shift_R) ||
+ (platform_keycode_ == XK_Control_R) ||
+ (platform_keycode_ == XK_Alt_R) ||
+ (platform_keycode_ == XK_Meta_R) ||
+ (platform_keycode_ == XK_Super_R) ||
+ (platform_keycode_ == XK_Hyper_R);
+ }
+ // Fall through to the generic code if we have no platform_keycode_.
+ // Under X11, this must be a synthetic event, so we can require that
+ // code_ be set correctly.
+#endif
+ return ((code_.size() > 5) &&
+ (code_.compare(code_.size() - 5, 5, "Right", 5)) == 0);
+ default:
+ return false;
+ }
+}
+
+KeyboardCode KeyEvent::GetLocatedWindowsKeyboardCode() const {
+ switch (key_code_) {
+ case VKEY_SHIFT:
+ return IsRightSideKey() ? VKEY_RSHIFT : VKEY_LSHIFT;
+ case VKEY_CONTROL:
+ return IsRightSideKey() ? VKEY_RCONTROL : VKEY_LCONTROL;
+ case VKEY_MENU:
+ return IsRightSideKey() ? VKEY_RMENU : VKEY_LMENU;
+ case VKEY_LWIN:
+ return IsRightSideKey() ? VKEY_RWIN : VKEY_LWIN;
+ // TODO(kpschoedel): EF_NUMPAD_KEY is present only on X11. Currently this
+ // function is only called on X11. Likely the tests here will be replaced
+ // with a DOM-based code enumeration test in the course of Ozone
+ // platform-indpendent key event work.
+ case VKEY_0:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD0 : VKEY_0;
+ case VKEY_1:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD1 : VKEY_1;
+ case VKEY_2:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD2 : VKEY_2;
+ case VKEY_3:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD3 : VKEY_3;
+ case VKEY_4:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD4 : VKEY_4;
+ case VKEY_5:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD5 : VKEY_5;
+ case VKEY_6:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD6 : VKEY_6;
+ case VKEY_7:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD7 : VKEY_7;
+ case VKEY_8:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD8 : VKEY_8;
+ case VKEY_9:
+ return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD9 : VKEY_9;
+ default:
+ return key_code_;
+ }
+}
+
+uint16 KeyEvent::GetConflatedWindowsKeyCode() const {
+ if (is_char_)
+ return character_;
+ return key_code_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScrollEvent
+
+ScrollEvent::ScrollEvent(const base::NativeEvent& native_event)
+ : MouseEvent(native_event) {
+ if (type() == ET_SCROLL) {
+ GetScrollOffsets(native_event,
+ &x_offset_, &y_offset_,
+ &x_offset_ordinal_, &y_offset_ordinal_,
+ &finger_count_);
+ } else if (type() == ET_SCROLL_FLING_START ||
+ type() == ET_SCROLL_FLING_CANCEL) {
+ GetFlingData(native_event,
+ &x_offset_, &y_offset_,
+ &x_offset_ordinal_, &y_offset_ordinal_,
+ NULL);
+ } else {
+ NOTREACHED() << "Unexpected event type " << type()
+ << " when constructing a ScrollEvent.";
+ }
+}
+
+ScrollEvent::ScrollEvent(EventType type,
+ const gfx::PointF& location,
+ base::TimeDelta time_stamp,
+ int flags,
+ float x_offset,
+ float y_offset,
+ float x_offset_ordinal,
+ float y_offset_ordinal,
+ int finger_count)
+ : MouseEvent(type, location, location, flags, 0),
+ x_offset_(x_offset),
+ y_offset_(y_offset),
+ x_offset_ordinal_(x_offset_ordinal),
+ y_offset_ordinal_(y_offset_ordinal),
+ finger_count_(finger_count) {
+ set_time_stamp(time_stamp);
+ CHECK(IsScrollEvent());
+}
+
+void ScrollEvent::Scale(const float factor) {
+ x_offset_ *= factor;
+ y_offset_ *= factor;
+ x_offset_ordinal_ *= factor;
+ y_offset_ordinal_ *= factor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureEvent
+
+GestureEvent::GestureEvent(float x,
+ float y,
+ int flags,
+ base::TimeDelta time_stamp,
+ const GestureEventDetails& details)
+ : LocatedEvent(details.type(),
+ gfx::PointF(x, y),
+ gfx::PointF(x, y),
+ time_stamp,
+ flags | EF_FROM_TOUCH),
+ details_(details) {
+}
+
+GestureEvent::~GestureEvent() {
+}
+
+} // namespace ui
diff --git a/ui/events/event.h b/ui/events/event.h
new file mode 100644
index 0000000..8f2c79a
--- /dev/null
+++ b/ui/events/event.h
@@ -0,0 +1,815 @@
+// 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_EVENT_H_
+#define UI_EVENTS_EVENT_H_
+
+#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/memory/scoped_ptr.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;
+}
+
+namespace ui {
+class EventTarget;
+
+class EVENTS_EXPORT Event {
+ public:
+ static scoped_ptr<Event> Clone(const Event& event);
+
+ virtual ~Event();
+
+ class DispatcherApi {
+ public:
+ explicit DispatcherApi(Event* event) : event_(event) {}
+
+ void set_target(EventTarget* target) {
+ event_->target_ = target;
+ }
+
+ void set_phase(EventPhase phase) { event_->phase_ = phase; }
+ void set_result(int result) {
+ event_->result_ = static_cast<EventResult>(result);
+ }
+
+ private:
+ DispatcherApi();
+ Event* event_;
+
+ DISALLOW_COPY_AND_ASSIGN(DispatcherApi);
+ };
+
+ const base::NativeEvent& native_event() const { return native_event_; }
+ EventType type() const { return type_; }
+ const std::string& name() const { return name_; }
+ // time_stamp represents time since machine was booted.
+ const base::TimeDelta& time_stamp() const { return time_stamp_; }
+ int flags() const { return flags_; }
+
+ // This is only intended to be used externally by classes that are modifying
+ // events in an EventRewriter.
+ void set_flags(int flags) { flags_ = flags; }
+
+ EventTarget* target() const { return target_; }
+ EventPhase phase() const { return phase_; }
+ EventResult result() const { return result_; }
+
+ LatencyInfo* latency() { return &latency_; }
+ const LatencyInfo* latency() const { return &latency_; }
+ void set_latency(const LatencyInfo& latency) { latency_ = latency; }
+
+ int source_device_id() const { return source_device_id_; }
+
+ // By default, events are "cancelable", this means any default processing that
+ // the containing abstraction layer may perform can be prevented by calling
+ // SetHandled(). SetHandled() or StopPropagation() must not be called for
+ // events that are not cancelable.
+ bool cancelable() const { return cancelable_; }
+
+ // The following methods return true if the respective keys were pressed at
+ // the time the event was created.
+ bool IsShiftDown() const { return (flags_ & EF_SHIFT_DOWN) != 0; }
+ bool IsControlDown() const { return (flags_ & EF_CONTROL_DOWN) != 0; }
+ 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 IsCommandDown() const { return (flags_ & EF_COMMAND_DOWN) != 0; }
+ bool IsRepeat() const { return (flags_ & EF_IS_REPEAT) != 0; }
+
+ bool IsKeyEvent() const {
+ return type_ == ET_KEY_PRESSED ||
+ type_ == ET_KEY_RELEASED ||
+ type_ == ET_TRANSLATED_KEY_PRESS ||
+ type_ == ET_TRANSLATED_KEY_RELEASE;
+ }
+
+ bool IsMouseEvent() const {
+ 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 ||
+ type_ == ET_MOUSEWHEEL ||
+ type_ == ET_MOUSE_CAPTURE_CHANGED;
+ }
+
+ bool IsTouchEvent() const {
+ return type_ == ET_TOUCH_RELEASED ||
+ type_ == ET_TOUCH_PRESSED ||
+ type_ == ET_TOUCH_MOVED ||
+ type_ == ET_TOUCH_CANCELLED;
+ }
+
+ bool IsGestureEvent() const {
+ switch (type_) {
+ case ET_GESTURE_SCROLL_BEGIN:
+ case ET_GESTURE_SCROLL_END:
+ case ET_GESTURE_SCROLL_UPDATE:
+ case ET_GESTURE_TAP:
+ case ET_GESTURE_TAP_CANCEL:
+ case ET_GESTURE_TAP_DOWN:
+ case ET_GESTURE_BEGIN:
+ case ET_GESTURE_END:
+ case ET_GESTURE_TWO_FINGER_TAP:
+ case ET_GESTURE_PINCH_BEGIN:
+ case ET_GESTURE_PINCH_END:
+ case ET_GESTURE_PINCH_UPDATE:
+ case ET_GESTURE_LONG_PRESS:
+ case ET_GESTURE_LONG_TAP:
+ 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;
+
+ case ET_SCROLL_FLING_CANCEL:
+ case ET_SCROLL_FLING_START:
+ // These can be ScrollEvents too. EF_FROM_TOUCH determines if they're
+ // Gesture or Scroll events.
+ return (flags_ & EF_FROM_TOUCH) == EF_FROM_TOUCH;
+
+ default:
+ break;
+ }
+ return false;
+ }
+
+ // An ending event is paired with the event which started it. Setting capture
+ // should not prevent ending events from getting to their initial target.
+ bool IsEndingEvent() const {
+ switch(type_) {
+ case ui::ET_TOUCH_CANCELLED:
+ case ui::ET_GESTURE_TAP_CANCEL:
+ case ui::ET_GESTURE_END:
+ case ui::ET_GESTURE_SCROLL_END:
+ case ui::ET_GESTURE_PINCH_END:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool IsScrollEvent() const {
+ // Flings can be GestureEvents too. EF_FROM_TOUCH determins if they're
+ // Gesture or Scroll events.
+ return type_ == ET_SCROLL ||
+ ((type_ == ET_SCROLL_FLING_START ||
+ type_ == ET_SCROLL_FLING_CANCEL) &&
+ !(flags() & EF_FROM_TOUCH));
+ }
+
+ bool IsScrollGestureEvent() const {
+ return type_ == ET_GESTURE_SCROLL_BEGIN ||
+ type_ == ET_GESTURE_SCROLL_UPDATE ||
+ type_ == ET_GESTURE_SCROLL_END;
+ }
+
+ bool IsFlingScrollEvent() const {
+ return type_ == ET_SCROLL_FLING_CANCEL ||
+ type_ == ET_SCROLL_FLING_START;
+ }
+
+ bool IsMouseWheelEvent() const {
+ return type_ == ET_MOUSEWHEEL;
+ }
+
+ bool IsLocatedEvent() const {
+ return IsMouseEvent() || IsScrollEvent() || IsTouchEvent() ||
+ IsGestureEvent();
+ }
+
+ // Convenience methods to cast |this| to a GestureEvent. IsGestureEvent()
+ // must be true as a precondition to calling these methods.
+ GestureEvent* AsGestureEvent();
+ const GestureEvent* AsGestureEvent() const;
+
+ // Returns true if the event has a valid |native_event_|.
+ bool HasNativeEvent() const;
+
+ // Immediately stops the propagation of the event. This must be called only
+ // from an EventHandler during an event-dispatch. Any event handler that may
+ // be in the list will not receive the event after this is called.
+ // Note that StopPropagation() can be called only for cancelable events.
+ void StopPropagation();
+ bool stopped_propagation() const { return !!(result_ & ER_CONSUMED); }
+
+ // Marks the event as having been handled. A handled event does not reach the
+ // next event phase. For example, if an event is handled during the pre-target
+ // phase, then the event is dispatched to all pre-target handlers, but not to
+ // the target or post-target handlers.
+ // Note that SetHandled() can be called only for cancelable events.
+ void SetHandled();
+ bool handled() const { return result_ != ER_UNHANDLED; }
+
+ protected:
+ Event(EventType type, base::TimeDelta time_stamp, int flags);
+ Event(const base::NativeEvent& native_event, EventType type, int flags);
+ Event(const Event& copy);
+ void SetType(EventType type);
+ void set_delete_native_event(bool delete_native_event) {
+ delete_native_event_ = delete_native_event;
+ }
+ void set_cancelable(bool cancelable) { cancelable_ = cancelable; }
+
+ void set_time_stamp(const base::TimeDelta& time_stamp) {
+ time_stamp_ = time_stamp;
+ }
+
+ void set_name(const std::string& name) { name_ = name; }
+
+ private:
+ friend class EventTestApi;
+
+ EventType type_;
+ std::string name_;
+ base::TimeDelta time_stamp_;
+ LatencyInfo latency_;
+ int flags_;
+ base::NativeEvent native_event_;
+ bool delete_native_event_;
+ bool cancelable_;
+ EventTarget* target_;
+ EventPhase phase_;
+ EventResult result_;
+
+ // The device id the event came from, or ED_UNKNOWN_DEVICE if the information
+ // is not available.
+ int source_device_id_;
+};
+
+class EVENTS_EXPORT CancelModeEvent : public Event {
+ public:
+ CancelModeEvent();
+ virtual ~CancelModeEvent();
+};
+
+class EVENTS_EXPORT LocatedEvent : public Event {
+ public:
+ virtual ~LocatedEvent();
+
+ 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 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_|.
+ virtual void UpdateForRootTransform(
+ const gfx::Transform& inverted_root_transform);
+
+ template <class T> void ConvertLocationToTarget(T* source, T* target) {
+ 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:
+ friend class LocatedEventTestApi;
+ explicit LocatedEvent(const base::NativeEvent& native_event);
+
+ // Create a new LocatedEvent which is identical to the provided model.
+ // If source / target windows are provided, the model location will be
+ // converted from |source| coordinate system to |target| coordinate system.
+ template <class T>
+ LocatedEvent(const LocatedEvent& model, T* source, T* target)
+ : Event(model),
+ location_(model.location_),
+ root_location_(model.root_location_) {
+ ConvertLocationToTarget(source, target);
+ }
+
+ // Used for synthetic events in testing.
+ LocatedEvent(EventType type,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
+ base::TimeDelta time_stamp,
+ int flags);
+
+ gfx::PointF location_;
+
+ // |location_| multiplied by an optional transformation matrix for
+ // rotations, animations and skews.
+ gfx::PointF root_location_;
+};
+
+class EVENTS_EXPORT MouseEvent : public LocatedEvent {
+ public:
+ explicit MouseEvent(const base::NativeEvent& native_event);
+
+ // Create a new MouseEvent based on the provided model.
+ // Uses the provided |type| and |flags| for the new event.
+ // If source / target windows are provided, the model location will be
+ // converted from |source| coordinate system to |target| coordinate system.
+ template <class T>
+ MouseEvent(const MouseEvent& model, T* source, T* target)
+ : LocatedEvent(model, source, target),
+ changed_button_flags_(model.changed_button_flags_) {
+ }
+
+ template <class T>
+ MouseEvent(const MouseEvent& model,
+ T* source,
+ T* target,
+ EventType type,
+ int flags)
+ : LocatedEvent(model, source, target),
+ changed_button_flags_(model.changed_button_flags_) {
+ SetType(type);
+ set_flags(flags);
+ }
+
+ // Used for synthetic events in testing and by the gesture recognizer.
+ MouseEvent(EventType type,
+ 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 {
+ return (flags() & EF_LEFT_MOUSE_BUTTON) &&
+ !(flags() & (EF_MIDDLE_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON));
+ }
+
+ bool IsLeftMouseButton() const {
+ return (flags() & EF_LEFT_MOUSE_BUTTON) != 0;
+ }
+
+ bool IsOnlyMiddleMouseButton() const {
+ return (flags() & EF_MIDDLE_MOUSE_BUTTON) &&
+ !(flags() & (EF_LEFT_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON));
+ }
+
+ bool IsMiddleMouseButton() const {
+ return (flags() & EF_MIDDLE_MOUSE_BUTTON) != 0;
+ }
+
+ bool IsOnlyRightMouseButton() const {
+ return (flags() & EF_RIGHT_MOUSE_BUTTON) &&
+ !(flags() & (EF_LEFT_MOUSE_BUTTON | EF_MIDDLE_MOUSE_BUTTON));
+ }
+
+ bool IsRightMouseButton() const {
+ return (flags() & EF_RIGHT_MOUSE_BUTTON) != 0;
+ }
+
+ bool IsAnyButton() const {
+ return (flags() & (EF_LEFT_MOUSE_BUTTON | EF_MIDDLE_MOUSE_BUTTON |
+ EF_RIGHT_MOUSE_BUTTON)) != 0;
+ }
+
+ // Compares two mouse down events and returns true if the second one should
+ // be considered a repeat of the first.
+ static bool IsRepeatedClickEvent(
+ const MouseEvent& event1,
+ const MouseEvent& event2);
+
+ // Get the click count. Can be 1, 2 or 3 for mousedown messages, 0 otherwise.
+ int GetClickCount() const;
+
+ // Set the click count for a mousedown message. Can be 1, 2 or 3.
+ void SetClickCount(int click_count);
+
+ // Identifies the button that changed. During a press this corresponds to the
+ // button that was pressed and during a release this corresponds to the button
+ // that was released.
+ // NOTE: during a press and release flags() contains the complete set of
+ // flags. Use this to determine the button that was pressed or released.
+ int changed_button_flags() const { return changed_button_flags_; }
+
+ // Updates the button that changed.
+ void set_changed_button_flags(int flags) { changed_button_flags_ = 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;
+
+class EVENTS_EXPORT MouseWheelEvent : public MouseEvent {
+ public:
+ // See |offset| for details.
+ static const int kWheelDelta;
+
+ explicit MouseWheelEvent(const base::NativeEvent& native_event);
+ explicit MouseWheelEvent(const ScrollEvent& scroll_event);
+ MouseWheelEvent(const MouseEvent& mouse_event, int x_offset, int y_offset);
+ MouseWheelEvent(const MouseWheelEvent& mouse_wheel_event);
+
+ template <class T>
+ MouseWheelEvent(const MouseWheelEvent& model,
+ T* source,
+ T* target)
+ : MouseEvent(model, source, target, model.type(), model.flags()),
+ offset_(model.x_offset(), model.y_offset()) {
+ }
+
+ // Used for synthetic events in testing and by the gesture recognizer.
+ MouseWheelEvent(const gfx::Vector2d& offset,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
+ int flags,
+ int changed_button_flags);
+
+ // The amount to scroll. This is in multiples of kWheelDelta.
+ // Note: x_offset() > 0/y_offset() > 0 means scroll left/up.
+ int x_offset() const { return offset_.x(); }
+ int y_offset() const { return offset_.y(); }
+ const gfx::Vector2d& offset() const { return offset_; }
+
+ // Overridden from LocatedEvent.
+ virtual void UpdateForRootTransform(
+ const gfx::Transform& inverted_root_transform) OVERRIDE;
+
+ private:
+ gfx::Vector2d offset_;
+};
+
+class EVENTS_EXPORT TouchEvent : public LocatedEvent {
+ public:
+ explicit TouchEvent(const base::NativeEvent& native_event);
+
+ // Create a new TouchEvent which is identical to the provided model.
+ // If source / target windows are provided, the model location will be
+ // converted from |source| coordinate system to |target| coordinate system.
+ template <class T>
+ TouchEvent(const TouchEvent& model, T* source, T* target)
+ : LocatedEvent(model, source, target),
+ touch_id_(model.touch_id_),
+ radius_x_(model.radius_x_),
+ radius_y_(model.radius_y_),
+ rotation_angle_(model.rotation_angle_),
+ force_(model.force_) {
+ }
+
+ TouchEvent(EventType type,
+ const gfx::PointF& location,
+ int touch_id,
+ base::TimeDelta time_stamp);
+
+ TouchEvent(EventType type,
+ const gfx::PointF& location,
+ int flags,
+ int touch_id,
+ base::TimeDelta timestamp,
+ float radius_x,
+ float radius_y,
+ float angle,
+ float force);
+
+ virtual ~TouchEvent();
+
+ int touch_id() const { return touch_id_; }
+ float radius_x() const { return radius_x_; }
+ float radius_y() const { return radius_y_; }
+ float rotation_angle() const { return rotation_angle_; }
+ float force() const { return force_; }
+
+ // Used for unit tests.
+ void set_radius_x(const float r) { radius_x_ = r; }
+ void set_radius_y(const float r) { radius_y_ = r; }
+
+ // Overridden from LocatedEvent.
+ virtual void UpdateForRootTransform(
+ const gfx::Transform& inverted_root_transform) OVERRIDE;
+
+ protected:
+ void set_radius(float radius_x, float radius_y) {
+ radius_x_ = radius_x;
+ radius_y_ = radius_y;
+ }
+
+ void set_rotation_angle(float rotation_angle) {
+ rotation_angle_ = rotation_angle;
+ }
+
+ void set_force(float force) { force_ = force; }
+
+ private:
+ // The identity (typically finger) of the touch starting at 0 and incrementing
+ // for each separable additional touch that the hardware can detect.
+ const int touch_id_;
+
+ // Radius of the X (major) axis of the touch ellipse. 0.0 if unknown.
+ float radius_x_;
+
+ // Radius of the Y (minor) axis of the touch ellipse. 0.0 if unknown.
+ float radius_y_;
+
+ // Angle of the major axis away from the X axis. Default 0.0.
+ float rotation_angle_;
+
+ // Force (pressure) of the touch. Normalized to be [0, 1]. Default to be 0.0.
+ float force_;
+};
+
+// An interface that individual platforms can use to store additional data on
+// KeyEvent.
+//
+// Currently only used in mojo.
+class EVENTS_EXPORT ExtendedKeyEventData {
+ public:
+ virtual ~ExtendedKeyEventData() {}
+
+ virtual ExtendedKeyEventData* Clone() const = 0;
+};
+
+// A KeyEvent is really two distinct classes, melded together due to the
+// DOM legacy of Windows key events: a keystroke event (is_char_ == false),
+// or a character event (is_char_ == true).
+//
+// For a keystroke event,
+// -- is_char_ is false.
+// -- type() can be any one of ET_KEY_PRESSED, ET_KEY_RELEASED,
+// ET_TRANSLATED_KEY_PRESS, or ET_TRANSLATED_KEY_RELEASE.
+// -- character_ functions as a bypass or cache for GetCharacter().
+// -- key_code_ is a VKEY_ value associated with the key. For printable
+// characters, this may or may not be a mapped value, imitating MS Windows:
+// if the mapped key generates a character that has an associated VKEY_
+// code, then key_code_ is that code; if not, then key_code_ is the unmapped
+// VKEY_ code. For example, US, Greek, Cyrillic, Japanese, etc. all use
+// VKEY_Q for the key beside Tab, while French uses VKEY_A.
+// -- code_ is in one-to-one correspondence with a physical keyboard
+// location, and does not vary depending on key layout.
+//
+// For a character event,
+// -- is_char_ is true.
+// -- type() is ET_KEY_PRESSED.
+// -- character_ is a UTF-16 character value.
+// -- key_code_ is conflated with character_ by some code, because both
+// arrive in the wParam field of a Windows event.
+// -- code_ is the empty string.
+//
+class EVENTS_EXPORT KeyEvent : public Event {
+ public:
+ // Create a KeyEvent from a NativeEvent. For Windows this native event can
+ // be either a keystroke message (WM_KEYUP/WM_KEYDOWN) or a character message
+ // (WM_CHAR). Other systems have only keystroke events.
+ explicit KeyEvent(const base::NativeEvent& native_event);
+
+ // Create a keystroke event.
+ KeyEvent(EventType type, KeyboardCode key_code, int flags);
+
+ // Create a character event.
+ KeyEvent(base::char16 character, KeyboardCode key_code, int flags);
+
+ // Used for synthetic events with code of DOM KeyboardEvent (e.g. 'KeyA')
+ // See also: ui/events/keycodes/dom4/keycode_converter_data.h
+ KeyEvent(EventType type,
+ KeyboardCode key_code,
+ const std::string& code,
+ int flags);
+
+ KeyEvent(const KeyEvent& rhs);
+
+ KeyEvent& operator=(const KeyEvent& rhs);
+
+ virtual ~KeyEvent();
+
+ // TODO(erg): While we transition to mojo, we have to hack around a mismatch
+ // in our event types. Our ui::Events don't really have all the data we need
+ // to process key events, and we instead do per-platform conversions with
+ // native HWNDs or XEvents. And we can't reliably send those native data
+ // types across mojo types in a cross-platform way. So instead, we set the
+ // resulting data when read across IPC boundaries.
+ void SetExtendedKeyEventData(scoped_ptr<ExtendedKeyEventData> data);
+ const ExtendedKeyEventData* extended_key_event_data() const {
+ return extended_key_event_data_.get();
+ }
+
+ // This bypasses the normal mapping from keystroke events to characters,
+ // which allows an I18N virtual keyboard to fabricate a keyboard event that
+ // does not have a corresponding KeyboardCode (example: U+00E1 Latin small
+ // letter A with acute, U+0410 Cyrillic capital letter A).
+ void set_character(base::char16 character) { character_ = character; }
+
+ // Gets the character generated by this key event. It only supports Unicode
+ // BMP characters.
+ base::char16 GetCharacter() const;
+
+ // If this is a keystroke event with key_code_ VKEY_RETURN, returns '\r';
+ // otherwise returns the same as GetCharacter().
+ base::char16 GetUnmodifiedText() const;
+
+ // If the Control key is down in the event, returns a layout-independent
+ // character (corresponding to US layout); otherwise returns the same
+ // as GetUnmodifiedText().
+ base::char16 GetText() const;
+
+ // Gets the platform key code. For XKB, this is the xksym value.
+ void set_platform_keycode(uint32 keycode) { platform_keycode_ = keycode; }
+ uint32 platform_keycode() const { return platform_keycode_; }
+
+ // Gets the associated (Windows-based) KeyboardCode for this key event.
+ // Historically, this has also been used to obtain the character associated
+ // with a character event, because both use the Window message 'wParam' field.
+ // This should be avoided; if necessary for backwards compatibility, use
+ // GetConflatedWindowsKeyCode().
+ KeyboardCode key_code() const { return key_code_; }
+
+ // True if this is a character event, false if this is a keystroke event.
+ bool is_char() const { return is_char_; }
+
+ // This is only intended to be used externally by classes that are modifying
+ // events in an EventRewriter.
+ void set_key_code(KeyboardCode key_code) { key_code_ = key_code; }
+
+ // Returns the same value as key_code(), except that located codes are
+ // returned in place of non-located ones (e.g. VKEY_LSHIFT or VKEY_RSHIFT
+ // instead of VKEY_SHIFT). This is a hybrid of semantic and physical
+ // for legacy DOM reasons.
+ KeyboardCode GetLocatedWindowsKeyboardCode() const;
+
+ // For a keystroke event, returns the same value as key_code().
+ // For a character event, returns the same value as GetCharacter().
+ // This exists for backwards compatibility with Windows key events.
+ uint16 GetConflatedWindowsKeyCode() const;
+
+ // Returns true for [Alt]+<num-pad digit> Unicode alt key codes used by Win.
+ // TODO(msw): Additional work may be needed for analogues on other platforms.
+ bool IsUnicodeKeyCode() const;
+
+ std::string code() const { return code_; }
+
+ // Normalizes flags_ so that it describes the state after the event.
+ // (Native X11 event flags describe the state before the event.)
+ 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:
+ friend class KeyEventTestApi;
+
+ // This allows a subclass TranslatedKeyEvent to be a non character event.
+ void set_is_char(bool is_char) { is_char_ = is_char; }
+
+ private:
+ // True if the key press originated from a 'right' key (VKEY_RSHIFT, etc.).
+ bool IsRightSideKey() const;
+
+ KeyboardCode key_code_;
+
+ // String of 'code' defined in DOM KeyboardEvent (e.g. 'KeyA', 'Space')
+ // http://www.w3.org/TR/uievents/#keyboard-key-codes.
+ //
+ // This value represents the physical position in the keyboard and can be
+ // converted from / to keyboard scan code like XKB.
+ std::string code_;
+
+ // True if this is a character event, false if this is a keystroke 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_;
+
+ // 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'.
+ mutable base::char16 character_;
+
+ // Parts of our event handling require raw native events (see both the
+ // windows and linux implementations of web_input_event in content/). Because
+ // mojo instead serializes and deserializes events in potentially different
+ // processes, we need to have a mechanism to keep track of this data.
+ scoped_ptr<ExtendedKeyEventData> extended_key_event_data_;
+
+ static bool IsRepeated(const KeyEvent& event);
+
+ static KeyEvent* last_key_event_;
+};
+
+class EVENTS_EXPORT ScrollEvent : public MouseEvent {
+ public:
+ explicit ScrollEvent(const base::NativeEvent& native_event);
+ template <class T>
+ ScrollEvent(const ScrollEvent& model,
+ T* source,
+ T* target)
+ : MouseEvent(model, source, target),
+ x_offset_(model.x_offset_),
+ y_offset_(model.y_offset_),
+ x_offset_ordinal_(model.x_offset_ordinal_),
+ y_offset_ordinal_(model.y_offset_ordinal_),
+ finger_count_(model.finger_count_){
+ }
+
+ // Used for tests.
+ ScrollEvent(EventType type,
+ const gfx::PointF& location,
+ base::TimeDelta time_stamp,
+ int flags,
+ float x_offset,
+ float y_offset,
+ float x_offset_ordinal,
+ float y_offset_ordinal,
+ int finger_count);
+
+ // Scale the scroll event's offset value.
+ // This is useful in the multi-monitor setup where it needs to be scaled
+ // to provide a consistent user experience.
+ void Scale(const float factor);
+
+ float x_offset() const { return x_offset_; }
+ float y_offset() const { return y_offset_; }
+ float x_offset_ordinal() const { return x_offset_ordinal_; }
+ float y_offset_ordinal() const { return y_offset_ordinal_; }
+ int finger_count() const { return finger_count_; }
+
+ private:
+ // Potential accelerated offsets.
+ float x_offset_;
+ float y_offset_;
+ // Unaccelerated offsets.
+ float x_offset_ordinal_;
+ float y_offset_ordinal_;
+ // Number of fingers on the pad.
+ int finger_count_;
+};
+
+class EVENTS_EXPORT GestureEvent : public LocatedEvent {
+ public:
+ GestureEvent(float x,
+ float y,
+ int flags,
+ base::TimeDelta time_stamp,
+ const GestureEventDetails& details);
+
+ // Create a new GestureEvent which is identical to the provided model.
+ // If source / target windows are provided, the model location will be
+ // converted from |source| coordinate system to |target| coordinate system.
+ template <typename T>
+ GestureEvent(const GestureEvent& model, T* source, T* target)
+ : LocatedEvent(model, source, target),
+ details_(model.details_) {
+ }
+
+ virtual ~GestureEvent();
+
+ const GestureEventDetails& details() const { return details_; }
+
+ private:
+ GestureEventDetails details_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_H_
diff --git a/ui/events/event_constants.h b/ui/events/event_constants.h
new file mode 100644
index 0000000..c3677ad
--- /dev/null
+++ b/ui/events/event_constants.h
@@ -0,0 +1,146 @@
+// 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_EVENT_CONSTANTS_H_
+#define UI_EVENTS_EVENT_CONSTANTS_H_
+
+namespace ui {
+
+// Event types. (prefixed because of a conflict with windows headers)
+enum EventType {
+ ET_UNKNOWN = 0,
+ ET_MOUSE_PRESSED,
+ ET_MOUSE_DRAGGED,
+ ET_MOUSE_RELEASED,
+ ET_MOUSE_MOVED,
+ ET_MOUSE_ENTERED,
+ ET_MOUSE_EXITED,
+ ET_KEY_PRESSED,
+ ET_KEY_RELEASED,
+ ET_MOUSEWHEEL,
+ ET_MOUSE_CAPTURE_CHANGED, // Event has no location.
+ ET_TOUCH_RELEASED,
+ ET_TOUCH_PRESSED,
+ ET_TOUCH_MOVED,
+ ET_TOUCH_CANCELLED,
+ ET_DROP_TARGET_EVENT,
+ ET_TRANSLATED_KEY_PRESS,
+ ET_TRANSLATED_KEY_RELEASE,
+
+ // 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_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 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.
+ ET_CANCEL_MODE,
+
+ // Sent by the CrOS gesture library for interesting patterns that we want
+ // to track with the UMA system.
+ ET_UMA_DATA,
+
+ // Must always be last. User namespace starts above this value.
+ // See ui::RegisterCustomEventType().
+ ET_LAST
+};
+
+// Event flags currently supported
+enum EventFlags {
+ EF_NONE = 0, // Used to denote no flags explicitly
+ EF_CAPS_LOCK_DOWN = 1 << 0,
+ EF_SHIFT_DOWN = 1 << 1,
+ EF_CONTROL_DOWN = 1 << 2,
+ EF_ALT_DOWN = 1 << 3,
+ EF_LEFT_MOUSE_BUTTON = 1 << 4,
+ EF_MIDDLE_MOUSE_BUTTON = 1 << 5,
+ EF_RIGHT_MOUSE_BUTTON = 1 << 6,
+ 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
+ EF_FINAL = 1 << 20, // Do not remap; the event was created with
+ // the desired final values.
+};
+
+// 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_TOUCH_ACCESSIBILITY = 1 << 20, // Indicates this event was generated from
+ // touch accessibility mode.
+};
+
+// Result of dispatching an event.
+enum EventResult {
+ ER_UNHANDLED = 0, // The event hasn't been handled. The event can be
+ // propagated to other handlers.
+ ER_HANDLED = 1 << 0, // The event has already been handled, but it can
+ // still be propagated to other handlers.
+ ER_CONSUMED = 1 << 1, // The event has been handled, and it should not be
+ // propagated to other handlers.
+};
+
+// Phase of the event dispatch.
+enum EventPhase {
+ EP_PREDISPATCH,
+ EP_PRETARGET,
+ EP_TARGET,
+ EP_POSTTARGET,
+ EP_POSTDISPATCH
+};
+
+// Device ID for Touch and Key Events.
+enum EventDeviceId {
+ ED_UNKNOWN_DEVICE = -1
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_CONSTANTS_H_
diff --git a/ui/events/event_dispatcher.cc b/ui/events/event_dispatcher.cc
new file mode 100644
index 0000000..ca24cde
--- /dev/null
+++ b/ui/events/event_dispatcher.cc
@@ -0,0 +1,194 @@
+// 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/event_dispatcher.h"
+
+#include <algorithm>
+
+#include "ui/events/event_target.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+
+namespace {
+
+class ScopedDispatchHelper : public Event::DispatcherApi {
+ public:
+ explicit ScopedDispatchHelper(Event* event)
+ : Event::DispatcherApi(event) {
+ set_result(ui::ER_UNHANDLED);
+ }
+
+ virtual ~ScopedDispatchHelper() {
+ set_phase(EP_POSTDISPATCH);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedDispatchHelper);
+};
+
+} // namespace
+
+EventDispatcherDelegate::EventDispatcherDelegate()
+ : dispatcher_(NULL) {
+}
+
+EventDispatcherDelegate::~EventDispatcherDelegate() {
+ if (dispatcher_)
+ dispatcher_->OnDispatcherDelegateDestroyed();
+}
+
+Event* EventDispatcherDelegate::current_event() {
+ return dispatcher_ ? dispatcher_->current_event() : NULL;
+}
+
+EventDispatchDetails EventDispatcherDelegate::DispatchEvent(EventTarget* target,
+ Event* event) {
+ CHECK(target);
+ Event::DispatcherApi dispatch_helper(event);
+ dispatch_helper.set_phase(EP_PREDISPATCH);
+ dispatch_helper.set_result(ER_UNHANDLED);
+
+ EventDispatchDetails details = PreDispatchEvent(target, event);
+ if (!event->handled() &&
+ !details.dispatcher_destroyed &&
+ !details.target_destroyed) {
+ details = DispatchEventToTarget(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;
+}
+
+EventDispatchDetails EventDispatcherDelegate::PreDispatchEvent(
+ EventTarget* target, Event* event) {
+ return EventDispatchDetails();
+}
+
+EventDispatchDetails EventDispatcherDelegate::PostDispatchEvent(
+ EventTarget* target, const Event& event) {
+ return EventDispatchDetails();
+}
+
+EventDispatchDetails EventDispatcherDelegate::DispatchEventToTarget(
+ EventTarget* target,
+ Event* event) {
+ EventDispatcher* old_dispatcher = dispatcher_;
+ EventDispatcher dispatcher(this);
+ dispatcher_ = &dispatcher;
+ dispatcher.ProcessEvent(target, event);
+ if (!dispatcher.delegate_destroyed())
+ dispatcher_ = old_dispatcher;
+ else if (old_dispatcher)
+ old_dispatcher->OnDispatcherDelegateDestroyed();
+
+ EventDispatchDetails details;
+ details.dispatcher_destroyed = dispatcher.delegate_destroyed();
+ details.target_destroyed =
+ (!details.dispatcher_destroyed && !CanDispatchToTarget(target));
+ return details;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventDispatcher:
+
+EventDispatcher::EventDispatcher(EventDispatcherDelegate* delegate)
+ : delegate_(delegate),
+ current_event_(NULL) {
+}
+
+EventDispatcher::~EventDispatcher() {
+}
+
+void EventDispatcher::OnHandlerDestroyed(EventHandler* handler) {
+ handler_list_.erase(std::find(handler_list_.begin(),
+ handler_list_.end(),
+ handler));
+}
+
+void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) {
+ if (!target || !target->CanAcceptEvent(*event))
+ return;
+
+ ScopedDispatchHelper dispatch_helper(event);
+ dispatch_helper.set_target(target);
+
+ handler_list_.clear();
+ target->GetPreTargetHandlers(&handler_list_);
+
+ dispatch_helper.set_phase(EP_PRETARGET);
+ DispatchEventToEventHandlers(&handler_list_, event);
+ if (event->handled())
+ return;
+
+ // If the event hasn't been consumed, trigger the default handler. Note that
+ // even if the event has already been handled (i.e. return result has
+ // ER_HANDLED set), that means that the event should still be processed at
+ // this layer, however it should not be processed in the next layer of
+ // abstraction.
+ if (delegate_ && delegate_->CanDispatchToTarget(target)) {
+ dispatch_helper.set_phase(EP_TARGET);
+ DispatchEvent(target, event);
+ if (event->handled())
+ return;
+ }
+
+ if (!delegate_ || !delegate_->CanDispatchToTarget(target))
+ return;
+
+ handler_list_.clear();
+ target->GetPostTargetHandlers(&handler_list_);
+ dispatch_helper.set_phase(EP_POSTTARGET);
+ DispatchEventToEventHandlers(&handler_list_, event);
+}
+
+void EventDispatcher::OnDispatcherDelegateDestroyed() {
+ delegate_ = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventDispatcher, private:
+
+void EventDispatcher::DispatchEventToEventHandlers(EventHandlerList* list,
+ Event* event) {
+ for (EventHandlerList::const_iterator it = list->begin(),
+ end = list->end(); it != end; ++it) {
+ (*it)->dispatchers_.push(this);
+ }
+
+ while (!list->empty()) {
+ EventHandler* handler = (*list->begin());
+ if (delegate_ && !event->stopped_propagation())
+ DispatchEvent(handler, event);
+
+ if (!list->empty() && *list->begin() == handler) {
+ // The handler has not been destroyed (because if it were, then it would
+ // have been removed from the list).
+ CHECK(handler->dispatchers_.top() == this);
+ handler->dispatchers_.pop();
+ list->erase(list->begin());
+ }
+ }
+}
+
+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())) {
+ if (event->cancelable())
+ event->StopPropagation();
+ return;
+ }
+
+ base::AutoReset<Event*> event_reset(¤t_event_, event);
+ handler->OnEvent(event);
+ if (!delegate_ && event->cancelable())
+ event->StopPropagation();
+}
+
+} // namespace ui
diff --git a/ui/events/event_dispatcher.h b/ui/events/event_dispatcher.h
new file mode 100644
index 0000000..a8c985e
--- /dev/null
+++ b/ui/events/event_dispatcher.h
@@ -0,0 +1,107 @@
+// 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_EVENT_DISPATCHER_H_
+#define UI_EVENTS_EVENT_DISPATCHER_H_
+
+#include "base/auto_reset.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_handler.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class EventDispatcher;
+class EventTarget;
+class EventTargeter;
+
+struct EventDispatchDetails {
+ EventDispatchDetails()
+ : dispatcher_destroyed(false),
+ target_destroyed(false) {}
+ bool dispatcher_destroyed;
+ bool target_destroyed;
+};
+
+class EVENTS_EXPORT EventDispatcherDelegate {
+ public:
+ EventDispatcherDelegate();
+ virtual ~EventDispatcherDelegate();
+
+ // Returns whether an event can still be dispatched to a target. (e.g. during
+ // event dispatch, one of the handlers may have destroyed the target, in which
+ // case the event can no longer be dispatched to the target).
+ virtual bool CanDispatchToTarget(EventTarget* target) = 0;
+
+ // Returns the event being dispatched (or NULL if no event is being
+ // dispatched).
+ Event* current_event();
+
+ // Dispatches |event| to |target|. This calls |PreDispatchEvent()| before
+ // dispatching the event, and |PostDispatchEvent()| after the event has been
+ // dispatched.
+ EventDispatchDetails DispatchEvent(EventTarget* target, Event* event)
+ WARN_UNUSED_RESULT;
+
+ protected:
+ // This is called once a target has been determined for an event, right before
+ // the event is dispatched to the target. This function may modify |event| to
+ // prepare it for dispatch (e.g. update event flags, location etc.).
+ virtual EventDispatchDetails PreDispatchEvent(
+ EventTarget* target,
+ 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;
+
+ private:
+ // Dispatches the event to the target.
+ EventDispatchDetails DispatchEventToTarget(EventTarget* target,
+ Event* event) WARN_UNUSED_RESULT;
+
+ EventDispatcher* dispatcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventDispatcherDelegate);
+};
+
+// Dispatches events to appropriate targets.
+class EVENTS_EXPORT EventDispatcher {
+ public:
+ explicit EventDispatcher(EventDispatcherDelegate* delegate);
+ virtual ~EventDispatcher();
+
+ void ProcessEvent(EventTarget* target, Event* event);
+
+ const Event* current_event() const { return current_event_; }
+ Event* current_event() { return current_event_; }
+
+ bool delegate_destroyed() const { return !delegate_; }
+
+ void OnHandlerDestroyed(EventHandler* handler);
+ void OnDispatcherDelegateDestroyed();
+
+ private:
+ void DispatchEventToEventHandlers(EventHandlerList* list, Event* event);
+
+ // Dispatches an event, and makes sure it sets ER_CONSUMED on the
+ // event-handling result if the dispatcher itself has been destroyed during
+ // dispatching the event to the event handler.
+ void DispatchEvent(EventHandler* handler, Event* event);
+
+ EventDispatcherDelegate* delegate_;
+
+ Event* current_event_;
+
+ EventHandlerList handler_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventDispatcher);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_DISPATCHER_H_
diff --git a/ui/events/event_dispatcher_unittest.cc b/ui/events/event_dispatcher_unittest.cc
new file mode 100644
index 0000000..248784a
--- /dev/null
+++ b/ui/events/event_dispatcher_unittest.cc
@@ -0,0 +1,609 @@
+// 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/event_dispatcher.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_target.h"
+#include "ui/events/event_target_iterator.h"
+#include "ui/events/event_utils.h"
+
+namespace ui {
+
+namespace {
+
+class TestTarget : public EventTarget {
+ public:
+ TestTarget() : parent_(NULL), valid_(true) {}
+ virtual ~TestTarget() {}
+
+ void set_parent(TestTarget* parent) { parent_ = parent; }
+
+ bool valid() const { return valid_; }
+ void set_valid(bool valid) { valid_ = valid; }
+
+ void AddHandlerId(int id) {
+ handler_list_.push_back(id);
+ }
+
+ const std::vector<int>& handler_list() const { return handler_list_; }
+
+ void Reset() {
+ handler_list_.clear();
+ valid_ = true;
+ }
+
+ private:
+ // Overridden from EventTarget:
+ virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE {
+ return true;
+ }
+
+ virtual EventTarget* GetParentTarget() OVERRIDE {
+ return parent_;
+ }
+
+ virtual scoped_ptr<EventTargetIterator> GetChildIterator() const OVERRIDE {
+ return scoped_ptr<EventTargetIterator>();
+ }
+
+ virtual EventTargeter* GetEventTargeter() OVERRIDE {
+ return NULL;
+ }
+
+ TestTarget* parent_;
+ std::vector<int> handler_list_;
+ bool valid_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTarget);
+};
+
+class TestEventHandler : public EventHandler {
+ public:
+ TestEventHandler(int id)
+ : id_(id),
+ event_result_(ER_UNHANDLED),
+ expect_pre_target_(false),
+ expect_post_target_(false),
+ received_pre_target_(false) {
+ }
+
+ virtual ~TestEventHandler() {}
+
+ virtual void ReceivedEvent(Event* event) {
+ static_cast<TestTarget*>(event->target())->AddHandlerId(id_);
+ if (event->phase() == ui::EP_POSTTARGET) {
+ EXPECT_TRUE(expect_post_target_);
+ if (expect_pre_target_)
+ EXPECT_TRUE(received_pre_target_);
+ } else if (event->phase() == ui::EP_PRETARGET) {
+ EXPECT_TRUE(expect_pre_target_);
+ received_pre_target_ = true;
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ void set_event_result(EventResult result) { event_result_ = result; }
+
+ void set_expect_pre_target(bool expect) { expect_pre_target_ = expect; }
+ void set_expect_post_target(bool expect) { expect_post_target_ = expect; }
+
+ private:
+ // Overridden from EventHandler:
+ virtual void OnEvent(Event* event) OVERRIDE {
+ ui::EventHandler::OnEvent(event);
+ ReceivedEvent(event);
+ SetStatusOnEvent(event);
+ }
+
+ void SetStatusOnEvent(Event* event) {
+ if (event_result_ & ui::ER_CONSUMED)
+ event->StopPropagation();
+ if (event_result_ & ui::ER_HANDLED)
+ event->SetHandled();
+ }
+
+ int id_;
+ EventResult event_result_;
+ bool expect_pre_target_;
+ bool expect_post_target_;
+ bool received_pre_target_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+class NonCancelableEvent : public Event {
+ public:
+ NonCancelableEvent()
+ : Event(ui::ET_CANCEL_MODE, ui::EventTimeForNow(), 0) {
+ set_cancelable(false);
+ }
+
+ virtual ~NonCancelableEvent() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NonCancelableEvent);
+};
+
+// Destroys the dispatcher-delegate when it receives any event.
+class EventHandlerDestroyDispatcherDelegate : public TestEventHandler {
+ public:
+ EventHandlerDestroyDispatcherDelegate(EventDispatcherDelegate* delegate,
+ int id)
+ : TestEventHandler(id),
+ dispatcher_delegate_(delegate) {
+ }
+
+ virtual ~EventHandlerDestroyDispatcherDelegate() {}
+
+ private:
+ virtual void ReceivedEvent(Event* event) OVERRIDE {
+ TestEventHandler::ReceivedEvent(event);
+ delete dispatcher_delegate_;
+ }
+
+ EventDispatcherDelegate* dispatcher_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyDispatcherDelegate);
+};
+
+// Invalidates the target when it receives any event.
+class InvalidateTargetEventHandler : public TestEventHandler {
+ public:
+ explicit InvalidateTargetEventHandler(int id) : TestEventHandler(id) {}
+ virtual ~InvalidateTargetEventHandler() {}
+
+ private:
+ virtual void ReceivedEvent(Event* event) OVERRIDE {
+ TestEventHandler::ReceivedEvent(event);
+ TestTarget* target = static_cast<TestTarget*>(event->target());
+ target->set_valid(false);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(InvalidateTargetEventHandler);
+};
+
+// Destroys a second event handler when this handler gets an event.
+// Optionally also destroys the dispatcher.
+class EventHandlerDestroyer : public TestEventHandler {
+ public:
+ EventHandlerDestroyer(int id, EventHandler* destroy)
+ : TestEventHandler(id),
+ to_destroy_(destroy),
+ dispatcher_delegate_(NULL) {
+ }
+
+ virtual ~EventHandlerDestroyer() {
+ CHECK(!to_destroy_);
+ }
+
+ void set_dispatcher_delegate(EventDispatcherDelegate* dispatcher_delegate) {
+ dispatcher_delegate_ = dispatcher_delegate;
+ }
+
+ private:
+ virtual void ReceivedEvent(Event* event) OVERRIDE {
+ TestEventHandler::ReceivedEvent(event);
+ delete to_destroy_;
+ to_destroy_ = NULL;
+
+ if (dispatcher_delegate_) {
+ delete dispatcher_delegate_;
+ dispatcher_delegate_ = NULL;
+ }
+ }
+
+ EventHandler* to_destroy_;
+ EventDispatcherDelegate* dispatcher_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyer);
+};
+
+class TestEventDispatcher : public EventDispatcherDelegate {
+ public:
+ TestEventDispatcher() {}
+
+ virtual ~TestEventDispatcher() {}
+
+ EventDispatchDetails ProcessEvent(EventTarget* target, Event* event) {
+ return DispatchEvent(target, event);
+ }
+
+ private:
+ // Overridden from EventDispatcherDelegate:
+ virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE {
+ TestTarget* test_target = static_cast<TestTarget*>(target);
+ return test_target->valid();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventDispatcher);
+};
+
+} // namespace
+
+TEST(EventDispatcherTest, EventDispatchOrder) {
+ TestEventDispatcher dispatcher;
+ TestTarget parent, child;
+ TestEventHandler h1(1), h2(2), h3(3), h4(4);
+ TestEventHandler h5(5), h6(6), h7(7), h8(8);
+
+ child.set_parent(&parent);
+
+ parent.AddPreTargetHandler(&h1);
+ parent.AddPreTargetHandler(&h2);
+
+ child.AddPreTargetHandler(&h3);
+ child.AddPreTargetHandler(&h4);
+
+ h1.set_expect_pre_target(true);
+ h2.set_expect_pre_target(true);
+ h3.set_expect_pre_target(true);
+ h4.set_expect_pre_target(true);
+
+ child.AddPostTargetHandler(&h5);
+ child.AddPostTargetHandler(&h6);
+
+ parent.AddPostTargetHandler(&h7);
+ parent.AddPostTargetHandler(&h8);
+
+ h5.set_expect_post_target(true);
+ h6.set_expect_post_target(true);
+ h7.set_expect_post_target(true);
+ h8.set_expect_post_target(true);
+
+ MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+ gfx::Point(3, 4), 0, 0);
+ Event::DispatcherApi event_mod(&mouse);
+ dispatcher.ProcessEvent(&child, &mouse);
+ EXPECT_FALSE(mouse.stopped_propagation());
+ EXPECT_FALSE(mouse.handled());
+
+ {
+ int expected[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+ EXPECT_EQ(
+ std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
+ child.handler_list());
+ }
+
+ child.Reset();
+ event_mod.set_phase(EP_PREDISPATCH);
+ event_mod.set_result(ER_UNHANDLED);
+
+ h1.set_event_result(ER_HANDLED);
+ dispatcher.ProcessEvent(&child, &mouse);
+ EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+ EXPECT_FALSE(mouse.stopped_propagation());
+ EXPECT_TRUE(mouse.handled());
+ {
+ // |h1| marks the event as handled. So only the pre-target handlers should
+ // receive the event.
+ int expected[] = { 1, 2, 3, 4 };
+ EXPECT_EQ(
+ std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
+ child.handler_list());
+ }
+
+ child.Reset();
+ event_mod.set_phase(EP_PREDISPATCH);
+ event_mod.set_result(ER_UNHANDLED);
+
+ int nexpected[] = { 1, 2, 3, 4, 5 };
+ h1.set_event_result(ER_UNHANDLED);
+ h5.set_event_result(ER_CONSUMED);
+ dispatcher.ProcessEvent(&child, &mouse);
+ EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+ EXPECT_TRUE(mouse.stopped_propagation());
+ EXPECT_TRUE(mouse.handled());
+ EXPECT_EQ(
+ std::vector<int>(nexpected, nexpected + sizeof(nexpected) / sizeof(int)),
+ child.handler_list());
+
+ child.Reset();
+ event_mod.set_phase(EP_PREDISPATCH);
+ event_mod.set_result(ER_UNHANDLED);
+
+ int exp[] = { 1 };
+ h1.set_event_result(ER_CONSUMED);
+ dispatcher.ProcessEvent(&child, &mouse);
+ EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+ EXPECT_TRUE(mouse.stopped_propagation());
+ EXPECT_TRUE(mouse.handled());
+ EXPECT_EQ(
+ std::vector<int>(exp, exp + sizeof(exp) / sizeof(int)),
+ child.handler_list());
+}
+
+// Tests that the event-phases are correct.
+TEST(EventDispatcherTest, EventDispatchPhase) {
+ TestEventDispatcher dispatcher;
+ TestTarget target;
+
+ TestEventHandler handler(11);
+
+ target.AddPreTargetHandler(&handler);
+ target.AddPostTargetHandler(&handler);
+ handler.set_expect_pre_target(true);
+ handler.set_expect_post_target(true);
+
+ MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+ gfx::Point(3, 4), 0, 0);
+ Event::DispatcherApi event_mod(&mouse);
+ dispatcher.ProcessEvent(&target, &mouse);
+ EXPECT_EQ(ER_UNHANDLED, mouse.result());
+
+ int handlers[] = { 11, 11 };
+ EXPECT_EQ(
+ std::vector<int>(handlers, handlers + sizeof(handlers) / sizeof(int)),
+ target.handler_list());
+}
+
+// Tests that if the dispatcher is destroyed in the middle of pre or post-target
+// dispatching events, it doesn't cause a crash.
+TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
+ // Test for pre-target first.
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+ TestEventHandler h1(1), h2(2);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&handler);
+ target.AddPreTargetHandler(&h2);
+
+ h1.set_expect_pre_target(true);
+ handler.set_expect_pre_target(true);
+ // |h2| should not receive any events at all since |handler| will have
+ // destroyed the dispatcher.
+ h2.set_expect_pre_target(false);
+
+ 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_EQ(ER_CONSUMED, mouse.result());
+ EXPECT_EQ(2U, target.handler_list().size());
+ EXPECT_EQ(1, target.handler_list()[0]);
+ EXPECT_EQ(5, target.handler_list()[1]);
+ }
+
+ // Test for non-cancelable event.
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+ TestEventHandler h1(1), h2(2);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&handler);
+ target.AddPreTargetHandler(&h2);
+
+ h1.set_expect_pre_target(true);
+ handler.set_expect_pre_target(true);
+ // |h2| should not receive any events at all since |handler| will have
+ // destroyed the dispatcher.
+ h2.set_expect_pre_target(false);
+
+ NonCancelableEvent 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]);
+ }
+
+ // Now test for post-target.
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+ TestEventHandler h1(1), h2(2);
+
+ target.AddPostTargetHandler(&h1);
+ target.AddPostTargetHandler(&handler);
+ target.AddPostTargetHandler(&h2);
+
+ h1.set_expect_post_target(true);
+ handler.set_expect_post_target(true);
+ // |h2| should not receive any events at all since |handler| will have
+ // destroyed the dispatcher.
+ h2.set_expect_post_target(false);
+
+ 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_EQ(ER_CONSUMED, mouse.result());
+ EXPECT_EQ(2U, target.handler_list().size());
+ EXPECT_EQ(1, target.handler_list()[0]);
+ EXPECT_EQ(5, target.handler_list()[1]);
+ }
+
+ // Test for non-cancelable event.
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+ TestEventHandler h1(1), h2(2);
+
+ target.AddPostTargetHandler(&h1);
+ target.AddPostTargetHandler(&handler);
+ target.AddPostTargetHandler(&h2);
+
+ h1.set_expect_post_target(true);
+ handler.set_expect_post_target(true);
+ // |h2| should not receive any events at all since |handler| will have
+ // destroyed the dispatcher.
+ h2.set_expect_post_target(false);
+
+ NonCancelableEvent 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]);
+ }
+}
+
+// Tests that a target becoming invalid in the middle of pre- or post-target
+// event processing aborts processing.
+TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) {
+ TestEventDispatcher dispatcher;
+ TestTarget target;
+ TestEventHandler h1(1);
+ InvalidateTargetEventHandler invalidate_handler(2);
+ TestEventHandler h3(3);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&invalidate_handler);
+ target.AddPreTargetHandler(&h3);
+
+ h1.set_expect_pre_target(true);
+ invalidate_handler.set_expect_pre_target(true);
+ // |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,
+ 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());
+ EXPECT_EQ(1, target.handler_list()[0]);
+ EXPECT_EQ(2, target.handler_list()[1]);
+
+ // Test for non-cancelable event.
+ target.Reset();
+ NonCancelableEvent event;
+ details = dispatcher.ProcessEvent(&target, &event);
+ EXPECT_FALSE(details.dispatcher_destroyed);
+ EXPECT_TRUE(details.target_destroyed);
+ EXPECT_FALSE(target.valid());
+ EXPECT_EQ(2U, target.handler_list().size());
+ EXPECT_EQ(1, target.handler_list()[0]);
+ EXPECT_EQ(2, target.handler_list()[1]);
+}
+
+// Tests that if an event-handler gets destroyed during event-dispatch, it does
+// not cause a crash.
+TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) {
+ {
+ TestEventDispatcher dispatcher;
+ TestTarget target;
+ TestEventHandler h1(1);
+ TestEventHandler* h3 = new TestEventHandler(3);
+ EventHandlerDestroyer handle_destroyer(2, h3);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&handle_destroyer);
+ target.AddPreTargetHandler(h3);
+
+ h1.set_expect_pre_target(true);
+ handle_destroyer.set_expect_pre_target(true);
+ // |h3| should not receive events since |handle_destroyer| will have
+ // destroyed it.
+ h3->set_expect_pre_target(false);
+
+ 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]);
+ EXPECT_EQ(2, target.handler_list()[1]);
+ }
+
+ // Test for non-cancelable events.
+ {
+ TestEventDispatcher dispatcher;
+ TestTarget target;
+ TestEventHandler h1(1);
+ TestEventHandler* h3 = new TestEventHandler(3);
+ EventHandlerDestroyer handle_destroyer(2, h3);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&handle_destroyer);
+ target.AddPreTargetHandler(h3);
+
+ h1.set_expect_pre_target(true);
+ handle_destroyer.set_expect_pre_target(true);
+ h3->set_expect_pre_target(false);
+
+ NonCancelableEvent 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]);
+ }
+}
+
+// Tests that things work correctly if an event-handler destroys both the
+// dispatcher and a handler.
+TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) {
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ TestEventHandler h1(1);
+ TestEventHandler* h3 = new TestEventHandler(3);
+ EventHandlerDestroyer destroyer(2, h3);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&destroyer);
+ target.AddPreTargetHandler(h3);
+
+ h1.set_expect_pre_target(true);
+ destroyer.set_expect_pre_target(true);
+ destroyer.set_dispatcher_delegate(dispatcher);
+ // |h3| should not receive events since |destroyer| will have destroyed
+ // it.
+ h3->set_expect_pre_target(false);
+
+ 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]);
+ EXPECT_EQ(2, target.handler_list()[1]);
+ }
+
+ // Test for non-cancelable events.
+ {
+ TestEventDispatcher* dispatcher = new TestEventDispatcher();
+ TestTarget target;
+ TestEventHandler h1(1);
+ TestEventHandler* h3 = new TestEventHandler(3);
+ EventHandlerDestroyer destroyer(2, h3);
+
+ target.AddPreTargetHandler(&h1);
+ target.AddPreTargetHandler(&destroyer);
+ target.AddPreTargetHandler(h3);
+
+ h1.set_expect_pre_target(true);
+ destroyer.set_expect_pre_target(true);
+ destroyer.set_dispatcher_delegate(dispatcher);
+ // |h3| should not receive events since |destroyer| will have destroyed
+ // it.
+ h3->set_expect_pre_target(false);
+
+ NonCancelableEvent 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]);
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/event_handler.cc b/ui/events/event_handler.cc
new file mode 100644
index 0000000..0f79c35
--- /dev/null
+++ b/ui/events/event_handler.cc
@@ -0,0 +1,58 @@
+// 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/event_handler.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+
+namespace ui {
+
+EventHandler::EventHandler() {
+}
+
+EventHandler::~EventHandler() {
+ while (!dispatchers_.empty()) {
+ EventDispatcher* dispatcher = dispatchers_.top();
+ dispatchers_.pop();
+ dispatcher->OnHandlerDestroyed(this);
+ }
+}
+
+void EventHandler::OnEvent(Event* event) {
+ // TODO(tdanderson): Encapsulate static_casts in ui::Event for all
+ // event types.
+ if (event->IsKeyEvent())
+ OnKeyEvent(static_cast<KeyEvent*>(event));
+ else if (event->IsMouseEvent())
+ OnMouseEvent(static_cast<MouseEvent*>(event));
+ else if (event->IsScrollEvent())
+ OnScrollEvent(static_cast<ScrollEvent*>(event));
+ else if (event->IsTouchEvent())
+ OnTouchEvent(static_cast<TouchEvent*>(event));
+ else if (event->IsGestureEvent())
+ OnGestureEvent(event->AsGestureEvent());
+ else if (event->type() == ET_CANCEL_MODE)
+ OnCancelMode(static_cast<CancelModeEvent*>(event));
+}
+
+void EventHandler::OnKeyEvent(KeyEvent* event) {
+}
+
+void EventHandler::OnMouseEvent(MouseEvent* event) {
+}
+
+void EventHandler::OnScrollEvent(ScrollEvent* event) {
+}
+
+void EventHandler::OnTouchEvent(TouchEvent* event) {
+}
+
+void EventHandler::OnGestureEvent(GestureEvent* event) {
+}
+
+void EventHandler::OnCancelMode(CancelModeEvent* event) {
+}
+
+} // namespace ui
diff --git a/ui/events/event_handler.h b/ui/events/event_handler.h
new file mode 100644
index 0000000..088c3f8
--- /dev/null
+++ b/ui/events/event_handler.h
@@ -0,0 +1,66 @@
+// 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_EVENT_HANDLER_H_
+#define UI_EVENTS_EVENT_HANDLER_H_
+
+#include <stack>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class CancelModeEvent;
+class Event;
+class EventDispatcher;
+class EventTarget;
+class GestureEvent;
+class KeyEvent;
+class MouseEvent;
+class ScrollEvent;
+class TouchEvent;
+
+// Dispatches events to appropriate targets. The default implementations of
+// all of the specific handlers (e.g. OnKeyEvent, OnMouseEvent) do nothing.
+class EVENTS_EXPORT EventHandler {
+ public:
+ EventHandler();
+ virtual ~EventHandler();
+
+ // This is called for all events. The default implementation routes the event
+ // to one of the event-specific callbacks (OnKeyEvent, OnMouseEvent etc.). If
+ // this is overridden, then normally, the override should chain into the
+ // default implementation for un-handled events.
+ virtual void OnEvent(Event* event);
+
+ virtual void OnKeyEvent(KeyEvent* event);
+
+ virtual void OnMouseEvent(MouseEvent* event);
+
+ virtual void OnScrollEvent(ScrollEvent* event);
+
+ virtual void OnTouchEvent(TouchEvent* event);
+
+ virtual void OnGestureEvent(GestureEvent* event);
+
+ virtual void OnCancelMode(CancelModeEvent* event);
+
+ private:
+ friend class EventDispatcher;
+
+ // EventDispatcher pushes itself on the top of this stack while dispatching
+ // events to this then pops itself off when done.
+ std::stack<EventDispatcher*> dispatchers_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventHandler);
+};
+
+typedef std::vector<EventHandler*> EventHandlerList;
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_HANDLER_H_
diff --git a/ui/events/event_processor.cc b/ui/events/event_processor.cc
new file mode 100644
index 0000000..b508508
--- /dev/null
+++ b/ui/events/event_processor.cc
@@ -0,0 +1,63 @@
+// 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/event_processor.h"
+
+#include "ui/events/event_target.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+
+EventDispatchDetails EventProcessor::OnEventFromSource(Event* event) {
+ EventTarget* root = GetRootTarget();
+ CHECK(root);
+ EventTargeter* targeter = root->GetEventTargeter();
+ CHECK(targeter);
+
+ // If |event| is in the process of being dispatched or has already been
+ // dispatched, then dispatch a copy of the event instead.
+ bool dispatch_original_event = event->phase() == EP_PREDISPATCH;
+ Event* event_to_dispatch = event;
+ scoped_ptr<Event> event_copy;
+ if (!dispatch_original_event) {
+ event_copy = Event::Clone(*event);
+ event_to_dispatch = event_copy.get();
+ }
+
+ OnEventProcessingStarted(event_to_dispatch);
+ EventTarget* target = NULL;
+ if (!event_to_dispatch->handled())
+ target = targeter->FindTargetForEvent(root, event_to_dispatch);
+
+ EventDispatchDetails details;
+ while (target) {
+ details = DispatchEvent(target, event_to_dispatch);
+
+ if (!dispatch_original_event) {
+ if (event_to_dispatch->stopped_propagation())
+ event->StopPropagation();
+ else if (event_to_dispatch->handled())
+ event->SetHandled();
+ }
+
+ if (details.dispatcher_destroyed)
+ return details;
+
+ if (details.target_destroyed || event->handled())
+ break;
+
+ target = targeter->FindNextBestTarget(target, event_to_dispatch);
+ }
+
+ OnEventProcessingFinished(event);
+ return details;
+}
+
+void EventProcessor::OnEventProcessingStarted(Event* event) {
+}
+
+void EventProcessor::OnEventProcessingFinished(Event* event) {
+}
+
+} // namespace ui
diff --git a/ui/events/event_processor.h b/ui/events/event_processor.h
new file mode 100644
index 0000000..676d920
--- /dev/null
+++ b/ui/events/event_processor.h
@@ -0,0 +1,49 @@
+// 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_EVENT_PROCESSOR_H_
+#define UI_EVENTS_EVENT_PROCESSOR_H_
+
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_source.h"
+
+namespace ui {
+
+// EventProcessor receives an event from an EventSource and dispatches it to a
+// tree of EventTargets.
+class EVENTS_EXPORT EventProcessor : public EventDispatcherDelegate {
+ public:
+ virtual ~EventProcessor() {}
+
+ // Returns the root of the tree this event processor owns.
+ virtual EventTarget* GetRootTarget() = 0;
+
+ // Dispatches an event received from the EventSource to the tree of
+ // 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.
+ // TODO(tdanderson|sadrul): This is only virtual for testing purposes. It
+ // should not be virtual at all.
+ virtual EventDispatchDetails OnEventFromSource(Event* event)
+ WARN_UNUSED_RESULT;
+
+ protected:
+ // Invoked at the start of processing, before an EventTargeter is used to
+ // find the target of the event. If processing should not take place, marks
+ // |event| as handled. Otherwise updates |event| so that the targeter can
+ // operate correctly (e.g., it can be used to update the location of the
+ // event when dispatching from an EventSource in high-DPI) and updates any
+ // members in the event processor as necessary.
+ virtual void OnEventProcessingStarted(Event* event);
+
+ // Invoked when the processing of |event| has finished (i.e., when no further
+ // dispatching of |event| will be performed by this EventProcessor). Note
+ // that the last target to which |event| was dispatched may have been
+ // destroyed.
+ virtual void OnEventProcessingFinished(Event* event);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_PROCESSOR_H_
diff --git a/ui/events/event_processor_unittest.cc b/ui/events/event_processor_unittest.cc
new file mode 100644
index 0000000..d6720a8
--- /dev/null
+++ b/ui/events/event_processor_unittest.cc
@@ -0,0 +1,525 @@
+// 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 <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 {
+
+class EventProcessorTest : public testing::Test {
+ public:
+ EventProcessorTest() {}
+ virtual ~EventProcessorTest() {}
+
+ // testing::Test:
+ virtual void SetUp() OVERRIDE {
+ processor_.SetRoot(scoped_ptr<EventTarget>(new TestEventTarget()));
+ processor_.Reset();
+ root()->SetEventTargeter(make_scoped_ptr(new EventTargeter()));
+ }
+
+ TestEventTarget* root() {
+ return static_cast<TestEventTarget*>(processor_.GetRootTarget());
+ }
+
+ TestEventProcessor* processor() {
+ return &processor_;
+ }
+
+ void DispatchEvent(Event* event) {
+ processor_.OnEventFromSource(event);
+ }
+
+ protected:
+ TestEventProcessor processor_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventProcessorTest);
+};
+
+TEST_F(EventProcessorTest, Basic) {
+ 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()->RemoveChild(root()->child_at(0));
+ DispatchEvent(&mouse);
+ EXPECT_TRUE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+}
+
+template<typename T>
+class BoundsEventTargeter : public EventTargeter {
+ public:
+ virtual ~BoundsEventTargeter() {}
+
+ protected:
+ virtual bool SubtreeShouldBeExploredForEvent(
+ EventTarget* target, const LocatedEvent& event) OVERRIDE {
+ T* t = static_cast<T*>(target);
+ return (t->bounds().Contains(event.location()));
+ }
+};
+
+class BoundsTestTarget : public TestEventTarget {
+ public:
+ BoundsTestTarget() {}
+ virtual ~BoundsTestTarget() {}
+
+ void set_bounds(gfx::Rect rect) { bounds_ = rect; }
+ gfx::Rect bounds() const { return bounds_; }
+
+ static void ConvertPointToTarget(BoundsTestTarget* source,
+ BoundsTestTarget* target,
+ gfx::Point* location) {
+ gfx::Vector2d vector;
+ if (source->Contains(target)) {
+ for (; target && target != source;
+ target = static_cast<BoundsTestTarget*>(target->parent())) {
+ vector += target->bounds().OffsetFromOrigin();
+ }
+ *location -= vector;
+ } else if (target->Contains(source)) {
+ for (; source && source != target;
+ source = static_cast<BoundsTestTarget*>(source->parent())) {
+ vector += source->bounds().OffsetFromOrigin();
+ }
+ *location += vector;
+ } else {
+ NOTREACHED();
+ }
+ }
+
+ private:
+ // EventTarget:
+ virtual void ConvertEventToTarget(EventTarget* target,
+ LocatedEvent* event) OVERRIDE {
+ event->ConvertLocationToTarget(this,
+ static_cast<BoundsTestTarget*>(target));
+ }
+
+ gfx::Rect bounds_;
+
+ DISALLOW_COPY_AND_ASSIGN(BoundsTestTarget);
+};
+
+TEST_F(EventProcessorTest, Bounds) {
+ scoped_ptr<BoundsTestTarget> parent(new BoundsTestTarget());
+ scoped_ptr<BoundsTestTarget> child(new BoundsTestTarget());
+ scoped_ptr<BoundsTestTarget> grandchild(new BoundsTestTarget());
+
+ parent->set_bounds(gfx::Rect(0, 0, 30, 30));
+ child->set_bounds(gfx::Rect(5, 5, 20, 20));
+ grandchild->set_bounds(gfx::Rect(5, 5, 5, 5));
+
+ child->AddChild(scoped_ptr<TestEventTarget>(grandchild.Pass()));
+ parent->AddChild(scoped_ptr<TestEventTarget>(child.Pass()));
+ root()->AddChild(scoped_ptr<TestEventTarget>(parent.Pass()));
+
+ ASSERT_EQ(1u, root()->child_count());
+ ASSERT_EQ(1u, root()->child_at(0)->child_count());
+ ASSERT_EQ(1u, root()->child_at(0)->child_at(0)->child_count());
+
+ TestEventTarget* parent_r = root()->child_at(0);
+ TestEventTarget* child_r = parent_r->child_at(0);
+ TestEventTarget* grandchild_r = child_r->child_at(0);
+
+ // 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,
+ EF_NONE);
+ DispatchEvent(&mouse);
+ EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ grandchild_r->ResetReceivedEvents();
+
+ // 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(&mouse2);
+ EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ parent_r->ResetReceivedEvents();
+
+ MouseEvent second(ET_MOUSE_MOVED, gfx::Point(12, 12), gfx::Point(12, 12),
+ EF_NONE, EF_NONE);
+ DispatchEvent(&second);
+ EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+}
+
+// ReDispatchEventHandler is used to receive mouse events and forward them
+// to a specified EventProcessor. Verifies that the event has the correct
+// target and phase both before and after the nested event processing. Also
+// verifies that the location of the event remains the same after it has
+// been processed by the second EventProcessor.
+class ReDispatchEventHandler : public TestEventHandler {
+ public:
+ ReDispatchEventHandler(EventProcessor* processor, EventTarget* target)
+ : processor_(processor), expected_target_(target) {}
+ virtual ~ReDispatchEventHandler() {}
+
+ // TestEventHandler:
+ virtual void OnMouseEvent(MouseEvent* event) OVERRIDE {
+ TestEventHandler::OnMouseEvent(event);
+
+ EXPECT_EQ(expected_target_, event->target());
+ EXPECT_EQ(EP_TARGET, event->phase());
+
+ gfx::Point location(event->location());
+ EventDispatchDetails details = processor_->OnEventFromSource(event);
+ EXPECT_FALSE(details.dispatcher_destroyed);
+ EXPECT_FALSE(details.target_destroyed);
+
+ // The nested event-processing should not have mutated the target,
+ // phase, or location of |event|.
+ EXPECT_EQ(expected_target_, event->target());
+ EXPECT_EQ(EP_TARGET, event->phase());
+ EXPECT_EQ(location, event->location());
+ }
+
+ private:
+ EventProcessor* processor_;
+ EventTarget* expected_target_;
+
+ DISALLOW_COPY_AND_ASSIGN(ReDispatchEventHandler);
+};
+
+// Verifies that the phase and target information of an event is not mutated
+// as a result of sending the event to an event processor while it is still
+// being processed by another event processor.
+TEST_F(EventProcessorTest, NestedEventProcessing) {
+ // Add one child to the default event processor used in this test suite.
+ scoped_ptr<TestEventTarget> child(new TestEventTarget());
+ root()->AddChild(child.Pass());
+
+ // Define a second root target and child.
+ scoped_ptr<EventTarget> second_root_scoped(new TestEventTarget());
+ TestEventTarget* second_root =
+ static_cast<TestEventTarget*>(second_root_scoped.get());
+ second_root->SetEventTargeter(make_scoped_ptr(new EventTargeter()));
+ scoped_ptr<TestEventTarget> second_child(new TestEventTarget());
+ second_root->AddChild(second_child.Pass());
+
+ // Define a second event processor which owns the second root.
+ scoped_ptr<TestEventProcessor> second_processor(new TestEventProcessor());
+ second_processor->SetRoot(second_root_scoped.Pass());
+
+ // Indicate that an event which is dispatched to the child target owned by the
+ // first event processor should be handled by |target_handler| instead.
+ scoped_ptr<TestEventHandler> target_handler(
+ new ReDispatchEventHandler(second_processor.get(), root()->child_at(0)));
+ root()->child_at(0)->set_target_handler(target_handler.get());
+
+ // Dispatch a mouse event to the tree of event targets owned by the first
+ // event processor, checking in ReDispatchEventHandler that the phase and
+ // target information of the event is correct.
+ MouseEvent mouse(
+ ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+ DispatchEvent(&mouse);
+
+ // Verify also that |mouse| was seen by the child nodes contained in both
+ // event processors and that the event was not handled.
+ EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(second_root->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_FALSE(mouse.handled());
+ second_root->child_at(0)->ResetReceivedEvents();
+ root()->child_at(0)->ResetReceivedEvents();
+
+ // Indicate that the child of the second root should handle events, and
+ // dispatch another mouse event to verify that it is marked as handled.
+ second_root->child_at(0)->set_mark_events_as_handled(true);
+ MouseEvent mouse2(
+ ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+ DispatchEvent(&mouse2);
+ EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(second_root->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(mouse2.handled());
+}
+
+// Verifies that OnEventProcessingFinished() is called when an event
+// has been handled.
+TEST_F(EventProcessorTest, OnEventProcessingFinished) {
+ scoped_ptr<TestEventTarget> child(new TestEventTarget());
+ child->set_mark_events_as_handled(true);
+ root()->AddChild(child.Pass());
+
+ // Dispatch a mouse event. We expect the event to be seen by the target,
+ // handled, and we expect OnEventProcessingFinished() to be invoked once.
+ 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));
+ EXPECT_TRUE(mouse.handled());
+ EXPECT_EQ(1, processor()->num_times_processing_finished());
+}
+
+// Verifies that OnEventProcessingStarted() has been called when starting to
+// process an event, and that processing does not take place if
+// OnEventProcessingStarted() marks the event as handled. Also verifies that
+// OnEventProcessingFinished() is also called in either case.
+TEST_F(EventProcessorTest, OnEventProcessingStarted) {
+ scoped_ptr<TestEventTarget> child(new TestEventTarget());
+ root()->AddChild(child.Pass());
+
+ // Dispatch a mouse event. We expect the event to be seen by the target,
+ // OnEventProcessingStarted() should be called once, and
+ // OnEventProcessingFinished() should be called once. The event should
+ // remain unhandled.
+ 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));
+ EXPECT_FALSE(mouse.handled());
+ EXPECT_EQ(1, processor()->num_times_processing_started());
+ EXPECT_EQ(1, processor()->num_times_processing_finished());
+ processor()->Reset();
+ root()->ResetReceivedEvents();
+ root()->child_at(0)->ResetReceivedEvents();
+
+ // Dispatch another mouse event, but with OnEventProcessingStarted() marking
+ // the event as handled to prevent processing. We expect the event to not be
+ // seen by the target this time, but OnEventProcessingStarted() and
+ // OnEventProcessingFinished() should both still be called once.
+ processor()->set_should_processing_occur(false);
+ 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_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+ EXPECT_TRUE(mouse2.handled());
+ EXPECT_EQ(1, processor()->num_times_processing_started());
+ EXPECT_EQ(1, processor()->num_times_processing_finished());
+}
+
+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, EF_NONE);
+ 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, EF_NONE);
+ 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, EF_NONE);
+ 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, EF_NONE);
+ 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/ui/events/event_rewriter.h b/ui/events/event_rewriter.h
new file mode 100644
index 0000000..f948725
--- /dev/null
+++ b/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/ui/events/event_rewriter_unittest.cc b/ui/events/event_rewriter_unittest.cc
new file mode 100644
index 0000000..11a05c9
--- /dev/null
+++ b/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/ui/events/event_source.cc b/ui/events/event_source.cc
new file mode 100644
index 0000000..0f3dfb8
--- /dev/null
+++ b/ui/events/event_source.cc
@@ -0,0 +1,76 @@
+// 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/event_source.h"
+
+#include <algorithm>
+
+#include "ui/events/event_processor.h"
+#include "ui/events/event_rewriter.h"
+
+namespace ui {
+
+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);
+ return processor->OnEventFromSource(event);
+}
+
+} // namespace ui
diff --git a/ui/events/event_source.h b/ui/events/event_source.h
new file mode 100644
index 0000000..ca8b00f
--- /dev/null
+++ b/ui/events/event_source.h
@@ -0,0 +1,49 @@
+// 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_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:
+ 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:
+ 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
+
+#endif // UI_EVENTS_EVENT_SOURCE_H_
diff --git a/ui/events/event_switches.cc b/ui/events/event_switches.cc
new file mode 100644
index 0000000..198113f
--- /dev/null
+++ b/ui/events/event_switches.cc
@@ -0,0 +1,42 @@
+// 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/event_switches.h"
+
+namespace switches {
+
+// Enable scroll prediction for scroll update events.
+const char kEnableScrollPrediction[] = "enable-scroll-prediction";
+
+// Enable support for touch events.
+const char kTouchEvents[] = "touch-events";
+
+// The values the kTouchEvents switch may have, as in --touch-events=disabled.
+// auto: enabled at startup when an attached touchscreen is present.
+const char kTouchEventsAuto[] = "auto";
+// enabled: touch events always enabled.
+const char kTouchEventsEnabled[] = "enabled";
+// disabled: touch events are disabled.
+const char kTouchEventsDisabled[] = "disabled";
+
+// Enable compensation for unstable pinch zoom. Some touch screens display
+// significant amount of wobble when moving a finger in a straight line. This
+// makes two finger scroll trigger an oscillating pinch zoom. See
+// crbug.com/394380 for details.
+const char kCompensateForUnstablePinchZoom[] =
+ "compensate-for-unstable-pinch-zoom";
+
+#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
+// devices can be retrieved from 'xinput list'.
+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/ui/events/event_switches.h b/ui/events/event_switches.h
new file mode 100644
index 0000000..ff73d9c
--- /dev/null
+++ b/ui/events/event_switches.h
@@ -0,0 +1,30 @@
+// 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_EVENTS_SWITCHES_H_
+#define UI_EVENTS_EVENTS_SWITCHES_H_
+
+#include "base/compiler_specific.h"
+#include "ui/events/events_base_export.h"
+
+namespace switches {
+
+EVENTS_BASE_EXPORT extern const char kEnableScrollPrediction[];
+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 kCompensateForUnstablePinchZoom[];
+
+#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/ui/events/event_target.cc b/ui/events/event_target.cc
new file mode 100644
index 0000000..9b44f9a
--- /dev/null
+++ b/ui/events/event_target.cc
@@ -0,0 +1,122 @@
+// 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/event_target.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "ui/events/event.h"
+
+namespace ui {
+
+EventTarget::EventTarget()
+ : target_handler_(NULL) {
+}
+
+EventTarget::~EventTarget() {
+}
+
+void EventTarget::ConvertEventToTarget(EventTarget* target,
+ LocatedEvent* event) {
+}
+
+void EventTarget::AddPreTargetHandler(EventHandler* handler) {
+ pre_target_list_.push_back(handler);
+}
+
+void EventTarget::PrependPreTargetHandler(EventHandler* handler) {
+ pre_target_list_.insert(pre_target_list_.begin(), handler);
+}
+
+void EventTarget::RemovePreTargetHandler(EventHandler* handler) {
+ EventHandlerList::iterator find =
+ std::find(pre_target_list_.begin(),
+ pre_target_list_.end(),
+ handler);
+ if (find != pre_target_list_.end())
+ pre_target_list_.erase(find);
+}
+
+void EventTarget::AddPostTargetHandler(EventHandler* handler) {
+ post_target_list_.push_back(handler);
+}
+
+void EventTarget::RemovePostTargetHandler(EventHandler* handler) {
+ EventHandlerList::iterator find =
+ std::find(post_target_list_.begin(),
+ post_target_list_.end(),
+ handler);
+ if (find != post_target_list_.end())
+ post_target_list_.erase(find);
+}
+
+bool EventTarget::IsPreTargetListEmpty() const {
+ return pre_target_list_.empty();
+}
+
+void EventTarget::OnEvent(Event* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnEvent(event);
+ else
+ EventHandler::OnEvent(event);
+}
+
+void EventTarget::OnKeyEvent(KeyEvent* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnKeyEvent(event);
+}
+
+void EventTarget::OnMouseEvent(MouseEvent* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnMouseEvent(event);
+}
+
+void EventTarget::OnScrollEvent(ScrollEvent* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnScrollEvent(event);
+}
+
+void EventTarget::OnTouchEvent(TouchEvent* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnTouchEvent(event);
+}
+
+void EventTarget::OnGestureEvent(GestureEvent* event) {
+ CHECK_EQ(this, event->target());
+ if (target_handler_)
+ target_handler_->OnGestureEvent(event);
+}
+
+void EventTarget::GetPreTargetHandlers(EventHandlerList* list) {
+ EventTarget* target = this;
+ while (target) {
+ EventHandlerList::reverse_iterator it, rend;
+ for (it = target->pre_target_list_.rbegin(),
+ rend = target->pre_target_list_.rend();
+ it != rend;
+ ++it) {
+ list->insert(list->begin(), *it);
+ }
+ target = target->GetParentTarget();
+ }
+}
+
+void EventTarget::GetPostTargetHandlers(EventHandlerList* list) {
+ EventTarget* target = this;
+ while (target) {
+ for (EventHandlerList::iterator it = target->post_target_list_.begin(),
+ end = target->post_target_list_.end(); it != end; ++it) {
+ list->push_back(*it);
+ }
+ target = target->GetParentTarget();
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/event_target.h b/ui/events/event_target.h
new file mode 100644
index 0000000..3ffec55
--- /dev/null
+++ b/ui/events/event_target.h
@@ -0,0 +1,118 @@
+// 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_EVENT_TARGET_H_
+#define UI_EVENTS_EVENT_TARGET_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_handler.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class EventDispatcher;
+class EventTargeter;
+class EventTargetIterator;
+class LocatedEvent;
+
+class EVENTS_EXPORT EventTarget : public EventHandler {
+ public:
+ class DispatcherApi {
+ public:
+ explicit DispatcherApi(EventTarget* target) : target_(target) {}
+
+ const EventHandlerList& pre_target_list() const {
+ return target_->pre_target_list_;
+ }
+
+ private:
+ DispatcherApi();
+ EventTarget* target_;
+
+ DISALLOW_COPY_AND_ASSIGN(DispatcherApi);
+ };
+
+ EventTarget();
+ virtual ~EventTarget();
+
+ virtual bool CanAcceptEvent(const Event& event) = 0;
+
+ // Returns the parent EventTarget in the event-target tree.
+ virtual EventTarget* GetParentTarget() = 0;
+
+ // Returns an iterator an EventTargeter can use to iterate over the list of
+ // child EventTargets.
+ virtual scoped_ptr<EventTargetIterator> GetChildIterator() const = 0;
+
+ // Returns the EventTargeter that should be used to find the target for an
+ // event in the subtree rooted at this EventTarget.
+ virtual EventTargeter* GetEventTargeter() = 0;
+
+ // Updates the states in |event| (e.g. location) to be suitable for |target|,
+ // so that |event| can be dispatched to |target|.
+ virtual void ConvertEventToTarget(EventTarget* target,
+ LocatedEvent* event);
+
+ // Adds a handler to receive events before the target. The handler must be
+ // explicitly removed from the target before the handler is destroyed. The
+ // EventTarget does not take ownership of the handler.
+ void AddPreTargetHandler(EventHandler* handler);
+
+ // Same as AddPreTargetHandler except that the |handler| is added to the front
+ // of the list so it is the first one to receive events.
+ void PrependPreTargetHandler(EventHandler* handler);
+ void RemovePreTargetHandler(EventHandler* handler);
+
+ // Adds a handler to receive events after the target. The handler must be
+ // explicitly removed from the target before the handler is destroyed. The
+ // EventTarget does not take ownership of the handler.
+ void AddPostTargetHandler(EventHandler* handler);
+ void RemovePostTargetHandler(EventHandler* handler);
+
+ // Returns true if the event pre target list is empty.
+ bool IsPreTargetListEmpty() const;
+
+ 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;
+ virtual void OnMouseEvent(MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(GestureEvent* event) OVERRIDE;
+
+ private:
+ friend class EventDispatcher;
+ friend class EventTargetTestApi;
+
+ // Returns the list of handlers that should receive the event before the
+ // target. The handlers from the outermost target are first in the list, and
+ // the handlers on |this| are the last in the list.
+ void GetPreTargetHandlers(EventHandlerList* list);
+
+ // Returns the list of handlers that should receive the event after the
+ // target. The handlers from the outermost target are last in the list, and
+ // the handlers on |this| are the first in the list.
+ void GetPostTargetHandlers(EventHandlerList* list);
+
+ EventHandlerList pre_target_list_;
+ EventHandlerList post_target_list_;
+ EventHandler* target_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventTarget);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_TARGET_H_
diff --git a/ui/events/event_target_iterator.h b/ui/events/event_target_iterator.h
new file mode 100644
index 0000000..3083c83
--- /dev/null
+++ b/ui/events/event_target_iterator.h
@@ -0,0 +1,48 @@
+// 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_EVENT_TARGET_ITERATOR_H_
+#define UI_EVENTS_EVENT_TARGET_ITERATOR_H_
+
+#include <vector>
+
+namespace ui {
+
+class EventTarget;
+
+// An interface that allows iterating over a set of EventTargets.
+class EventTargetIterator {
+ public:
+ virtual ~EventTargetIterator() {}
+ virtual EventTarget* GetNextTarget() = 0;
+};
+
+// Provides an EventTargetIterator implementation for iterating over a list of
+// EventTargets. The list is iterated in the reverse order, since typically the
+// EventTargets are maintained in increasing z-order in the lists.
+template<typename T>
+class EventTargetIteratorImpl : public EventTargetIterator {
+ public:
+ explicit EventTargetIteratorImpl(const std::vector<T*>& children)
+ : begin_(children.rbegin()),
+ end_(children.rend()) {
+ }
+ virtual ~EventTargetIteratorImpl() {}
+
+ virtual EventTarget* GetNextTarget() OVERRIDE {
+ if (begin_ == end_)
+ return NULL;
+ EventTarget* target = *(begin_);
+ ++begin_;
+ return target;
+ }
+
+ private:
+ typename std::vector<T*>::const_reverse_iterator begin_;
+ typename std::vector<T*>::const_reverse_iterator end_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_TARGET_ITERATOR_H_
diff --git a/ui/events/event_targeter.cc b/ui/events/event_targeter.cc
new file mode 100644
index 0000000..3cdc83e
--- /dev/null
+++ b/ui/events/event_targeter.cc
@@ -0,0 +1,74 @@
+// 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/event_targeter.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_target.h"
+#include "ui/events/event_target_iterator.h"
+
+namespace ui {
+
+EventTargeter::~EventTargeter() {
+}
+
+EventTarget* EventTargeter::FindTargetForEvent(EventTarget* root,
+ Event* event) {
+ if (event->IsMouseEvent() ||
+ event->IsScrollEvent() ||
+ event->IsTouchEvent() ||
+ event->IsGestureEvent()) {
+ return FindTargetForLocatedEvent(root,
+ static_cast<LocatedEvent*>(event));
+ }
+ return root;
+}
+
+EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root,
+ LocatedEvent* event) {
+ scoped_ptr<EventTargetIterator> iter = root->GetChildIterator();
+ if (iter) {
+ EventTarget* target = root;
+ for (EventTarget* child = iter->GetNextTarget(); child;
+ child = iter->GetNextTarget()) {
+ EventTargeter* targeter = child->GetEventTargeter();
+ if (!targeter)
+ targeter = this;
+ if (!targeter->SubtreeShouldBeExploredForEvent(child, *event))
+ continue;
+ target->ConvertEventToTarget(child, event);
+ target = child;
+ EventTarget* child_target = targeter ?
+ targeter->FindTargetForLocatedEvent(child, event) :
+ FindTargetForLocatedEvent(child, event);
+ if (child_target)
+ return child_target;
+ }
+ target->ConvertEventToTarget(root, event);
+ }
+ return root->CanAcceptEvent(*event) ? root : NULL;
+}
+
+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;
+}
+
+} // namespace ui
diff --git a/ui/events/event_targeter.h b/ui/events/event_targeter.h
new file mode 100644
index 0000000..23b1261
--- /dev/null
+++ b/ui/events/event_targeter.h
@@ -0,0 +1,77 @@
+// 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_EVENT_TARGETER_H_
+#define UI_EVENTS_EVENT_TARGETER_H_
+
+#include "base/compiler_specific.h"
+#include "ui/events/event.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+class EventTarget;
+class LocatedEvent;
+
+class EVENTS_EXPORT EventTargeter {
+ public:
+ virtual ~EventTargeter();
+
+ // Returns the target |event| should be dispatched to. If there is no such
+ // target, return NULL. If |event| is a located event, the location of |event|
+ // is in the coordinate space of |root|. Furthermore, the targeter can mutate
+ // the event (e.g., by changing the location of the event to be in the
+ // returned target's coordinate space) so that it can be dispatched to the
+ // target without any further modification.
+ virtual EventTarget* FindTargetForEvent(EventTarget* root,
+ Event* event);
+
+ // 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. change the
+ // coordinate to be in the returned target's coordinate system) so that it can
+ // be dispatched to the target without any further modification.
+ // TODO(tdanderson|sadrul): This should not be in the public API of
+ // EventTargeter.
+ virtual EventTarget* FindTargetForLocatedEvent(EventTarget* root,
+ LocatedEvent* event);
+
+ // 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|.
+ // |event| is in the local coordinate space of |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
+
+#endif // UI_EVENTS_EVENT_TARGETER_H_
diff --git a/ui/events/event_unittest.cc b/ui/events/event_unittest.cc
new file mode 100644
index 0000000..46c2f63
--- /dev/null
+++ b/ui/events/event_unittest.cc
@@ -0,0 +1,452 @@
+// 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 "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+#include "ui/events/test/events_test_utils.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/events/test/events_test_utils_x11.h"
+#include "ui/gfx/x/x11_types.h"
+#endif
+
+namespace ui {
+
+TEST(EventTest, NoNativeEvent) {
+ KeyEvent keyev(ET_KEY_PRESSED, VKEY_SPACE, EF_NONE);
+ EXPECT_FALSE(keyev.HasNativeEvent());
+}
+
+TEST(EventTest, NativeEvent) {
+#if defined(OS_WIN)
+ MSG native_event = { NULL, WM_KEYUP, VKEY_A, 0 };
+ KeyEvent keyev(native_event);
+ EXPECT_TRUE(keyev.HasNativeEvent());
+#elif defined(USE_X11)
+ ScopedXI2Event event;
+ event.InitKeyEvent(ET_KEY_RELEASED, VKEY_A, EF_NONE);
+ KeyEvent keyev(event);
+ EXPECT_TRUE(keyev.HasNativeEvent());
+#endif
+}
+
+TEST(EventTest, GetCharacter) {
+ // Check if Control+Enter returns 10.
+ KeyEvent keyev1(ET_KEY_PRESSED, VKEY_RETURN, EF_CONTROL_DOWN);
+ EXPECT_EQ(10, keyev1.GetCharacter());
+ // Check if Enter returns 13.
+ KeyEvent keyev2(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
+ EXPECT_EQ(13, keyev2.GetCharacter());
+
+#if defined(USE_X11)
+ // For X11, test the functions with native_event() as well. crbug.com/107837
+ ScopedXI2Event event;
+ event.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_CONTROL_DOWN);
+ KeyEvent keyev3(event);
+ EXPECT_EQ(10, keyev3.GetCharacter());
+
+ event.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
+ KeyEvent keyev4(event);
+ EXPECT_EQ(13, keyev4.GetCharacter());
+#endif
+}
+
+TEST(EventTest, ClickCount) {
+ const gfx::Point origin(0, 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, RepeatedClick) {
+ const gfx::Point origin(0, 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);
+
+ base::TimeDelta start = base::TimeDelta::FromMilliseconds(0);
+ base::TimeDelta soon = start + base::TimeDelta::FromMilliseconds(1);
+ base::TimeDelta later = start + base::TimeDelta::FromMilliseconds(1000);
+
+ // Close point.
+ test_ev1.set_location(gfx::Point(0, 0));
+ test_ev2.set_location(gfx::Point(1, 0));
+ test_ev1.set_time_stamp(start);
+ test_ev2.set_time_stamp(soon);
+ EXPECT_TRUE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2));
+
+ // Too far.
+ test_ev1.set_location(gfx::Point(0, 0));
+ test_ev2.set_location(gfx::Point(10, 0));
+ test_ev1.set_time_stamp(start);
+ test_ev2.set_time_stamp(soon);
+ EXPECT_FALSE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2));
+
+ // Too long a time between clicks.
+ test_ev1.set_location(gfx::Point(0, 0));
+ test_ev2.set_location(gfx::Point(0, 0));
+ test_ev1.set_time_stamp(start);
+ test_ev2.set_time_stamp(later);
+ 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;
+ int flags;
+ uint16 character;
+ } kTestData[] = {
+ { VKEY_A, 0, 'a' },
+ { VKEY_A, EF_SHIFT_DOWN, 'A' },
+ { VKEY_A, EF_CAPS_LOCK_DOWN, 'A' },
+ { VKEY_A, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, 'a' },
+ { VKEY_A, EF_CONTROL_DOWN, 0x01 },
+ { VKEY_A, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x01' },
+ { VKEY_Z, 0, 'z' },
+ { VKEY_Z, EF_SHIFT_DOWN, 'Z' },
+ { VKEY_Z, EF_CAPS_LOCK_DOWN, 'Z' },
+ { VKEY_Z, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, 'z' },
+ { VKEY_Z, EF_CONTROL_DOWN, '\x1A' },
+ { VKEY_Z, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1A' },
+
+ { VKEY_2, EF_CONTROL_DOWN, '\0' },
+ { VKEY_2, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+ { VKEY_6, EF_CONTROL_DOWN, '\0' },
+ { VKEY_6, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1E' },
+ { VKEY_OEM_MINUS, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_MINUS, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1F' },
+ { VKEY_OEM_4, EF_CONTROL_DOWN, '\x1B' },
+ { VKEY_OEM_4, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_5, EF_CONTROL_DOWN, '\x1C' },
+ { VKEY_OEM_5, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_6, EF_CONTROL_DOWN, '\x1D' },
+ { VKEY_OEM_6, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+ { VKEY_RETURN, EF_CONTROL_DOWN, '\x0A' },
+
+ { VKEY_0, 0, '0' },
+ { VKEY_0, EF_SHIFT_DOWN, ')' },
+ { VKEY_0, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, ')' },
+ { VKEY_0, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+
+ { VKEY_9, 0, '9' },
+ { VKEY_9, EF_SHIFT_DOWN, '(' },
+ { VKEY_9, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, '(' },
+ { VKEY_9, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+
+ { VKEY_NUMPAD0, EF_CONTROL_DOWN, '\0' },
+ { VKEY_NUMPAD0, EF_SHIFT_DOWN, '0' },
+
+ { VKEY_NUMPAD9, EF_CONTROL_DOWN, '\0' },
+ { VKEY_NUMPAD9, EF_SHIFT_DOWN, '9' },
+
+ { VKEY_TAB, EF_CONTROL_DOWN, '\0' },
+ { VKEY_TAB, EF_SHIFT_DOWN, '\t' },
+
+ { VKEY_MULTIPLY, EF_CONTROL_DOWN, '\0' },
+ { VKEY_MULTIPLY, EF_SHIFT_DOWN, '*' },
+ { VKEY_ADD, EF_CONTROL_DOWN, '\0' },
+ { VKEY_ADD, EF_SHIFT_DOWN, '+' },
+ { VKEY_SUBTRACT, EF_CONTROL_DOWN, '\0' },
+ { VKEY_SUBTRACT, EF_SHIFT_DOWN, '-' },
+ { VKEY_DECIMAL, EF_CONTROL_DOWN, '\0' },
+ { VKEY_DECIMAL, EF_SHIFT_DOWN, '.' },
+ { VKEY_DIVIDE, EF_CONTROL_DOWN, '\0' },
+ { VKEY_DIVIDE, EF_SHIFT_DOWN, '/' },
+
+ { VKEY_OEM_1, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_1, EF_SHIFT_DOWN, ':' },
+ { VKEY_OEM_PLUS, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_PLUS, EF_SHIFT_DOWN, '+' },
+ { VKEY_OEM_COMMA, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_COMMA, EF_SHIFT_DOWN, '<' },
+ { VKEY_OEM_PERIOD, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_PERIOD, EF_SHIFT_DOWN, '>' },
+ { VKEY_OEM_3, EF_CONTROL_DOWN, '\0' },
+ { VKEY_OEM_3, EF_SHIFT_DOWN, '~' },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestData); ++i) {
+ KeyEvent key(ET_KEY_PRESSED,
+ kTestData[i].key_code,
+ kTestData[i].flags);
+ EXPECT_EQ(kTestData[i].character, key.GetCharacter())
+ << " Index:" << i << " key_code:" << kTestData[i].key_code;
+ }
+}
+
+TEST(EventTest, KeyEventDirectUnicode) {
+ KeyEvent key(0x1234U, ui::VKEY_UNKNOWN, ui::EF_NONE);
+ EXPECT_EQ(0x1234U, key.GetCharacter());
+ EXPECT_EQ(ET_KEY_PRESSED, key.type());
+ EXPECT_TRUE(key.is_char());
+}
+
+TEST(EventTest, NormalizeKeyEventFlags) {
+#if defined(USE_X11)
+ // Normalize flags when KeyEvent is created from XEvent.
+ ScopedXI2Event event;
+ {
+ event.InitKeyEvent(ET_KEY_PRESSED, VKEY_SHIFT, EF_SHIFT_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+ }
+ {
+ event.InitKeyEvent(ET_KEY_RELEASED, VKEY_SHIFT, EF_SHIFT_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+ {
+ event.InitKeyEvent(ET_KEY_PRESSED, VKEY_CONTROL, EF_CONTROL_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+ }
+ {
+ event.InitKeyEvent(ET_KEY_RELEASED, VKEY_CONTROL, EF_CONTROL_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+ {
+ event.InitKeyEvent(ET_KEY_PRESSED, VKEY_MENU, EF_ALT_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+ }
+ {
+ event.InitKeyEvent(ET_KEY_RELEASED, VKEY_MENU, EF_ALT_DOWN);
+ KeyEvent keyev(event);
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+#endif
+
+ // Do not normalize flags for synthesized events without
+ // KeyEvent::NormalizeFlags called explicitly.
+ {
+ KeyEvent keyev(ET_KEY_PRESSED, VKEY_SHIFT, EF_SHIFT_DOWN);
+ EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+ }
+ {
+ KeyEvent keyev(ET_KEY_RELEASED, VKEY_SHIFT, EF_SHIFT_DOWN);
+ EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+ keyev.NormalizeFlags();
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+ {
+ KeyEvent keyev(ET_KEY_PRESSED, VKEY_CONTROL, EF_CONTROL_DOWN);
+ EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+ }
+ {
+ KeyEvent keyev(ET_KEY_RELEASED, VKEY_CONTROL, EF_CONTROL_DOWN);
+ EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+ keyev.NormalizeFlags();
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+ {
+ KeyEvent keyev(ET_KEY_PRESSED, VKEY_MENU, EF_ALT_DOWN);
+ EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+ }
+ {
+ KeyEvent keyev(ET_KEY_RELEASED, VKEY_MENU, EF_ALT_DOWN);
+ EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+ keyev.NormalizeFlags();
+ EXPECT_EQ(EF_NONE, keyev.flags());
+ }
+}
+
+TEST(EventTest, KeyEventCopy) {
+ KeyEvent key(ET_KEY_PRESSED, VKEY_A, EF_NONE);
+ scoped_ptr<KeyEvent> copied_key(new KeyEvent(key));
+ EXPECT_EQ(copied_key->type(), key.type());
+ EXPECT_EQ(copied_key->key_code(), key.key_code());
+}
+
+TEST(EventTest, KeyEventCode) {
+ const char kCodeForSpace[] = "Space";
+ const uint16 kNativeCodeSpace =
+ ui::KeycodeConverter::CodeToNativeKeycode(kCodeForSpace);
+ ASSERT_NE(ui::KeycodeConverter::InvalidNativeKeycode(), kNativeCodeSpace);
+
+ {
+ KeyEvent key(ET_KEY_PRESSED, VKEY_SPACE, kCodeForSpace, EF_NONE);
+ EXPECT_EQ(kCodeForSpace, key.code());
+ }
+ {
+ // Regardless the KeyEvent.key_code (VKEY_RETURN), code should be
+ // the specified value.
+ KeyEvent key(ET_KEY_PRESSED, VKEY_RETURN, kCodeForSpace, EF_NONE);
+ EXPECT_EQ(kCodeForSpace, key.code());
+ }
+ {
+ // If the synthetic event is initialized without code, it returns
+ // an empty string.
+ // TODO(komatsu): Fill a fallback value assuming the US keyboard layout.
+ KeyEvent key(ET_KEY_PRESSED, VKEY_SPACE, EF_NONE);
+ EXPECT_TRUE(key.code().empty());
+ }
+#if defined(USE_X11)
+ {
+ // KeyEvent converts from the native keycode (XKB) to the code.
+ ScopedXI2Event xevent;
+ xevent.InitKeyEvent(ET_KEY_PRESSED, VKEY_SPACE, kNativeCodeSpace);
+ KeyEvent key(xevent);
+ EXPECT_EQ(kCodeForSpace, key.code());
+ }
+#endif // USE_X11
+#if defined(OS_WIN)
+ {
+ // Test a non extended key.
+ ASSERT_EQ((kNativeCodeSpace & 0xFF), kNativeCodeSpace);
+
+ const LPARAM lParam = GetLParamFromScanCode(kNativeCodeSpace);
+ MSG native_event = { NULL, WM_KEYUP, VKEY_SPACE, lParam };
+ KeyEvent key(native_event);
+
+ // KeyEvent converts from the native keycode (scan code) to the code.
+ EXPECT_EQ(kCodeForSpace, key.code());
+ }
+ {
+ const char kCodeForHome[] = "Home";
+ const uint16 kNativeCodeHome = 0xe047;
+
+ // 'Home' is an extended key with 0xe000 bits.
+ ASSERT_NE((kNativeCodeHome & 0xFF), kNativeCodeHome);
+ const LPARAM lParam = GetLParamFromScanCode(kNativeCodeHome);
+
+ MSG native_event = { NULL, WM_KEYUP, VKEY_HOME, lParam };
+ KeyEvent key(native_event);
+
+ // KeyEvent converts from the native keycode (scan code) to the code.
+ EXPECT_EQ(kCodeForHome, key.code());
+ }
+#endif // OS_WIN
+}
+
+#if defined(USE_X11) || defined(OS_WIN)
+TEST(EventTest, AutoRepeat) {
+ const uint16 kNativeCodeA = ui::KeycodeConverter::CodeToNativeKeycode("KeyA");
+ const uint16 kNativeCodeB = ui::KeycodeConverter::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);
+ EXPECT_FALSE(key_a1.IsRepeat());
+ KeyEvent key_a1_released(native_event_a_released);
+ EXPECT_FALSE(key_a1_released.IsRepeat());
+
+ KeyEvent key_a2(native_event_a_pressed);
+ EXPECT_FALSE(key_a2.IsRepeat());
+ KeyEvent key_a2_repeated(native_event_a_pressed);
+ EXPECT_TRUE(key_a2_repeated.IsRepeat());
+ KeyEvent key_a2_released(native_event_a_released);
+ EXPECT_FALSE(key_a2_released.IsRepeat());
+
+ KeyEvent key_a3(native_event_a_pressed);
+ EXPECT_FALSE(key_a3.IsRepeat());
+ KeyEvent key_b(native_event_b_pressed);
+ EXPECT_FALSE(key_b.IsRepeat());
+ KeyEvent key_a3_again(native_event_a_pressed);
+ EXPECT_FALSE(key_a3_again.IsRepeat());
+ KeyEvent key_a3_repeated(native_event_a_pressed);
+ EXPECT_TRUE(key_a3_repeated.IsRepeat());
+ KeyEvent key_a3_repeated2(native_event_a_pressed);
+ EXPECT_TRUE(key_a3_repeated2.IsRepeat());
+ KeyEvent key_a3_released(native_event_a_released);
+ EXPECT_FALSE(key_a3_released.IsRepeat());
+
+#if defined(USE_X11)
+ KeyEvent key_a4_pressed(native_event_a_pressed);
+ EXPECT_FALSE(key_a4_pressed.IsRepeat());
+
+ KeyEvent key_a4_pressed_nonstandard_state(
+ native_event_a_pressed_nonstandard_state);
+ EXPECT_FALSE(key_a4_pressed_nonstandard_state.IsRepeat());
+#endif
+}
+#endif // USE_X11 || OS_WIN
+
+} // namespace ui
diff --git a/ui/events/event_utils.cc b/ui/events/event_utils.cc
new file mode 100644
index 0000000..2e3517e
--- /dev/null
+++ b/ui/events/event_utils.cc
@@ -0,0 +1,139 @@
+// 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/event_utils.h"
+
+#include <vector>
+
+#include "ui/events/event.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ui {
+
+namespace {
+int g_custom_event_types = ET_LAST;
+} // namespace
+
+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));
+ 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();
+}
+
+// From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp:
+base::char16 GetControlCharacterForKeycode(int windows_key_code, bool shift) {
+ if (windows_key_code >= ui::VKEY_A &&
+ windows_key_code <= ui::VKEY_Z) {
+ // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
+ return windows_key_code - ui::VKEY_A + 1;
+ }
+ if (shift) {
+ // following graphics chars require shift key to input.
+ switch (windows_key_code) {
+ // ctrl-@ maps to \x00 (Null byte)
+ case ui::VKEY_2:
+ return 0;
+ // ctrl-^ maps to \x1E (Record separator, Information separator two)
+ case ui::VKEY_6:
+ return 0x1E;
+ // ctrl-_ maps to \x1F (Unit separator, Information separator one)
+ case ui::VKEY_OEM_MINUS:
+ return 0x1F;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ break;
+ }
+ } else {
+ switch (windows_key_code) {
+ // ctrl-[ maps to \x1B (Escape)
+ case ui::VKEY_OEM_4:
+ return 0x1B;
+ // ctrl-\ maps to \x1C (File separator, Information separator four)
+ case ui::VKEY_OEM_5:
+ return 0x1C;
+ // ctrl-] maps to \x1D (Group separator, Information separator three)
+ case ui::VKEY_OEM_6:
+ return 0x1D;
+ // ctrl-Enter maps to \x0A (Line feed)
+ case ui::VKEY_RETURN:
+ return 0x0A;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+int RegisterCustomEventType() {
+ return ++g_custom_event_types;
+}
+
+base::TimeDelta EventTimeForNow() {
+ return base::TimeDelta::FromInternalValue(
+ base::TimeTicks::Now().ToInternalValue());
+}
+
+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 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) {
+ if (it->IsInternal())
+ return it->touch_support();
+ }
+ return gfx::Display::TOUCH_SUPPORT_UNAVAILABLE;
+}
+
+} // namespace ui
diff --git a/ui/events/event_utils.h b/ui/events/event_utils.h
new file mode 100644
index 0000000..f1f5f34
--- /dev/null
+++ b/ui/events/event_utils.h
@@ -0,0 +1,190 @@
+// 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_EVENT_UTILS_H_
+#define UI_EVENTS_EVENT_UTILS_H_
+
+#include "base/basictypes.h"
+#include "base/event_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.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"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace gfx {
+class Point;
+class Vector2d;
+}
+
+namespace base {
+class TimeDelta;
+}
+
+namespace ui {
+
+class Event;
+class MouseEvent;
+
+// 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);
+
+// Get the EventFlags from a native event.
+EVENTS_EXPORT int EventFlagsFromNative(const base::NativeEvent& native_event);
+
+// Get the timestamp from a native event.
+EVENTS_EXPORT base::TimeDelta EventTimeFromNative(
+ const base::NativeEvent& native_event);
+
+// Create a timestamp based on the current time.
+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/window_tree_host*.
+// TODO(tdresser): Return gfx::PointF here. See crbug.com/337827.
+EVENTS_EXPORT gfx::Point EventLocationFromNative(
+ const base::NativeEvent& native_event);
+
+// Gets the location in native system coordinate space.
+EVENTS_EXPORT gfx::Point EventSystemLocationFromNative(
+ const base::NativeEvent& native_event);
+
+#if defined(USE_X11)
+// Returns the 'real' button for an event. The button reported in slave events
+// does not take into account any remapping (e.g. using xmodmap), while the
+// button reported in master events do. This is a utility function to always
+// return the mapped button.
+EVENTS_EXPORT int EventButtonFromNative(const base::NativeEvent& native_event);
+#endif
+
+// Returns the KeyboardCode from a native event.
+EVENTS_EXPORT KeyboardCode KeyboardCodeFromNative(
+ const base::NativeEvent& native_event);
+
+// Returns the DOM KeyboardEvent code (physical location in the
+// keyboard) from a native event. The ownership of the return value
+// is NOT trasferred to the caller.
+EVENTS_EXPORT const char* CodeFromNative(
+ 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 a control character sequences from a |windows_key_code|.
+EVENTS_EXPORT base::char16 GetControlCharacterForKeycode(int windows_key_code,
+ bool shift);
+
+// Returns true if the keyboard event is a character event rather than
+// a keystroke event.
+EVENTS_EXPORT bool IsCharFromNative(const base::NativeEvent& native_event);
+
+// Returns the flags of the button that changed during a press/release.
+EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative(
+ const base::NativeEvent& native_event);
+
+// Gets the mouse wheel offsets from a native event.
+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);
+
+// Increases the number of times |ClearTouchIdIfReleased| needs to be called on
+// an event with a given touch id before it will actually be cleared.
+EVENTS_EXPORT void IncrementTouchIdRefCount(
+ const base::NativeEvent& native_event);
+
+// Clear the touch id from bookkeeping if it is a release/cancel event.
+EVENTS_EXPORT void ClearTouchIdIfReleased(
+ const base::NativeEvent& native_event);
+
+// Gets the radius along the X/Y axis from a native event. Default is 1.0.
+EVENTS_EXPORT float GetTouchRadiusX(const base::NativeEvent& native_event);
+EVENTS_EXPORT float GetTouchRadiusY(const base::NativeEvent& native_event);
+
+// Gets the angle of the major axis away from the X axis. Default is 0.0.
+EVENTS_EXPORT float GetTouchAngle(const base::NativeEvent& native_event);
+
+// Gets the force from a native_event. Normalized to be [0, 1]. Default is 0.0.
+EVENTS_EXPORT float GetTouchForce(const base::NativeEvent& native_event);
+
+// Gets the fling velocity from a native event. is_cancel is set to true if
+// this was a tap down, intended to stop an ongoing fling.
+EVENTS_EXPORT bool GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel);
+
+// Returns whether this is a scroll event and optionally gets the amount to be
+// scrolled. |x_offset|, |y_offset| and |finger_count| can be NULL.
+EVENTS_EXPORT bool GetScrollOffsets(const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count);
+
+// 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();
+
+#if defined(OS_WIN)
+EVENTS_EXPORT int GetModifiersFromACCEL(const ACCEL& accel);
+EVENTS_EXPORT int GetModifiersFromKeyState();
+
+// Returns true if |message| identifies a mouse event that was generated as the
+// result of a touch event.
+EVENTS_EXPORT bool IsMouseEventFromTouch(UINT message);
+
+// Converts scan code and lParam each other. The scan code
+// representing an extended key contains 0xE000 bits.
+EVENTS_EXPORT uint16 GetScanCodeFromLParam(LPARAM lParam);
+EVENTS_EXPORT LPARAM GetLParamFromScanCode(uint16 scan_code);
+
+#endif
+
+#if defined(USE_X11)
+// Update the native X11 event to correspond to the new flags.
+EVENTS_EXPORT void UpdateX11EventForFlags(Event* event);
+// Update the native X11 event to correspond to the new button flags.
+EVENTS_EXPORT void UpdateX11EventForChangedButtonFlags(MouseEvent* event);
+#endif
+
+// Registers a custom event type.
+EVENTS_EXPORT int RegisterCustomEventType();
+
+} // namespace ui
+
+#endif // UI_EVENTS_EVENT_UTILS_H_
diff --git a/ui/events/events.gyp b/ui/events/events.gyp
new file mode 100644
index 0000000..40ccca6
--- /dev/null
+++ b/ui/events/events.gyp
@@ -0,0 +1,390 @@
+# 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ # GN version: //ui/events:dom4_keycode_converter
+ 'target_name': 'dom4_keycode_converter',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ 'sources': [
+ # Note: sources list duplicated in GN build.
+ 'keycodes/dom4/keycode_converter.cc',
+ 'keycodes/dom4/keycode_converter.h',
+ 'keycodes/dom4/keycode_converter_data.h',
+ ],
+ },
+ {
+ # GN version: //ui/events:events_base
+ 'target_name': 'events_base',
+ 'type': '<(component)',
+ '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': [
+ # Note: sources list duplicated in GN build.
+ 'device_data_manager.cc',
+ 'device_data_manager.h',
+ 'device_hotplug_event_observer.h',
+ 'event_constants.h',
+ 'event_switches.cc',
+ 'event_switches.h',
+ 'events_base_export.h',
+ 'gesture_event_details.cc',
+ 'gesture_event_details.h',
+ 'gestures/fling_curve.cc',
+ 'gestures/fling_curve.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_code_conversion_x.cc',
+ 'keycodes/keyboard_code_conversion_x.h',
+ 'keycodes/keyboard_codes.h',
+ 'latency_info.cc',
+ 'latency_info.h',
+ 'touchscreen_device.cc',
+ 'touchscreen_device.h',
+ 'x/device_data_manager_x11.cc',
+ 'x/device_data_manager_x11.h',
+ 'x/device_list_cache_x.cc',
+ 'x/device_list_cache_x.h',
+ 'x/hotplug_event_handler_x11.cc',
+ 'x/hotplug_event_handler_x11.h',
+ 'x/keysym_to_unicode.cc',
+ 'x/keysym_to_unicode.h',
+ 'x/touch_factory_x11.cc',
+ 'x/touch_factory_x11.h',
+ ],
+ 'export_dependent_settings': [
+ '../../ui/gfx/gfx.gyp:gfx',
+ ],
+ 'conditions': [
+ ['use_x11==1', {
+ 'dependencies': [
+ '../../build/linux/system.gyp:x11',
+ '../gfx/x/gfx_x11.gyp:gfx_x11',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //ui/events
+ 'target_name': 'events',
+ 'type': '<(component)',
+ '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',
+ 'events_base',
+ 'gesture_detection',
+ ],
+ 'defines': [
+ 'EVENTS_IMPLEMENTATION',
+ ],
+ 'sources': [
+ # Note: sources list duplicated in GN build.
+ 'cocoa/cocoa_event_utils.h',
+ 'cocoa/cocoa_event_utils.mm',
+ 'cocoa/events_mac.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_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_types.h',
+ 'gestures/motion_event_aura.cc',
+ 'gestures/motion_event_aura.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_x11==1', {
+ 'dependencies': [
+ '../../build/linux/system.gyp:x11',
+ ],
+ }],
+ ['use_aura==0', {
+ 'sources!': [
+ '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_types.h',
+ 'gestures/motion_event_aura.cc',
+ 'gestures/motion_event_aura.h',
+ ],
+ }],
+ # We explicitly enumerate the platforms we _do_ provide native cracking
+ # for here.
+ ['OS=="win" or OS=="mac" or use_x11==1 or use_ozone==1', {
+ 'sources!': [
+ 'events_stub.cc',
+ ],
+ }],
+ ['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',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //ui/events:gesture_detection
+ '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': [
+ # Note: sources list duplicated in GN build.
+ '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_listeners.cc',
+ 'gesture_detection/gesture_listeners.h',
+ 'gesture_detection/gesture_provider.cc',
+ 'gesture_detection/gesture_provider.h',
+ 'gesture_detection/gesture_touch_uma_histogram.cc',
+ 'gesture_detection/gesture_touch_uma_histogram.h',
+ 'gesture_detection/motion_event.cc',
+ 'gesture_detection/motion_event.h',
+ 'gesture_detection/motion_event_buffer.cc',
+ 'gesture_detection/motion_event_buffer.h',
+ 'gesture_detection/motion_event_generic.cc',
+ 'gesture_detection/motion_event_generic.h',
+ 'gesture_detection/scale_gesture_detector.cc',
+ 'gesture_detection/scale_gesture_detector.h',
+ 'gesture_detection/scale_gesture_listeners.cc',
+ 'gesture_detection/scale_gesture_listeners.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',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //ui/events:test_support
+ 'target_name': 'events_test_support',
+ 'type': 'static_library',
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/skia/skia.gyp:skia',
+ '../gfx/gfx.gyp:gfx_geometry',
+ 'events',
+ 'events_base',
+ 'gesture_detection',
+ 'platform/events_platform.gyp:events_platform',
+ ],
+ 'sources': [
+ # Note: sources list duplicated in GN build.
+ 'test/cocoa_test_event_utils.h',
+ 'test/cocoa_test_event_utils.mm',
+ 'test/event_generator.cc',
+ 'test/event_generator.h',
+ 'test/events_test_utils.cc',
+ 'test/events_test_utils.h',
+ 'test/events_test_utils_x11.cc',
+ 'test/events_test_utils_x11.h',
+ 'test/mock_motion_event.cc',
+ 'test/mock_motion_event.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',
+ ],
+ 'conditions': [
+ ['OS=="ios"', {
+ # The cocoa files don't apply to iOS.
+ 'sources/': [['exclude', 'cocoa']],
+ }],
+ ],
+ },
+ {
+ # GN version: //ui/events:events_unittests
+ 'target_name': 'events_unittests',
+ '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',
+ 'events_base',
+ 'events_test_support',
+ 'gesture_detection',
+ 'platform/events_platform.gyp:events_platform',
+ ],
+ 'sources': [
+ # Note: sources list duplicated in GN build.
+ 'cocoa/events_mac_unittest.mm',
+ 'event_dispatcher_unittest.cc',
+ 'event_processor_unittest.cc',
+ 'event_rewriter_unittest.cc',
+ 'event_unittest.cc',
+ 'gesture_detection/bitset_32_unittest.cc',
+ 'gesture_detection/gesture_event_data_packet_unittest.cc',
+ 'gesture_detection/gesture_provider_unittest.cc',
+ 'gesture_detection/motion_event_buffer_unittest.cc',
+ 'gesture_detection/motion_event_generic_unittest.cc',
+ 'gesture_detection/touch_disposition_gesture_filter_unittest.cc',
+ 'gesture_detection/velocity_tracker_unittest.cc',
+ 'gestures/fling_curve_unittest.cc',
+ 'gestures/gesture_provider_aura_unittest.cc',
+ 'gestures/motion_event_aura_unittest.cc',
+ 'keycodes/dom4/keycode_converter_unittest.cc',
+ 'latency_info_unittest.cc',
+ 'platform/platform_event_source_unittest.cc',
+ 'x/events_x_unittest.cc',
+ ],
+ 'conditions': [
+ ['use_x11==1', {
+ 'dependencies': [
+ '../../build/linux/system.gyp:x11',
+ '../gfx/x/gfx_x11.gyp:gfx_x11',
+ ],
+ }],
+ ['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/gesture_provider_aura_unittest.cc',
+ 'gestures/motion_event_aura_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/ui/events/events_base_export.h b/ui/events/events_base_export.h
new file mode 100644
index 0000000..2da4f78
--- /dev/null
+++ b/ui/events/events_base_export.h
@@ -0,0 +1,29 @@
+// 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_EVENTS_BASE_EXPORT_H_
+#define UI_EVENTS_EVENTS_BASE_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_BASE_IMPLEMENTATION)
+#define EVENTS_BASE_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_BASE_EXPORT __declspec(dllimport)
+#endif // defined(EVENTS_BASE_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(EVENTS_BASE_IMPLEMENTATION)
+#define EVENTS_BASE_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_BASE_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define EVENTS_BASE_EXPORT
+#endif
+
+#endif // UI_EVENTS_EVENTS_BASE_EXPORT_H_
diff --git a/ui/events/events_export.h b/ui/events/events_export.h
new file mode 100644
index 0000000..65ed7a2
--- /dev/null
+++ b/ui/events/events_export.h
@@ -0,0 +1,29 @@
+// 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_EVENTS_EXPORT_H_
+#define UI_EVENTS_EVENTS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_IMPLEMENTATION)
+#define EVENTS_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_EXPORT __declspec(dllimport)
+#endif // defined(EVENTS_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(EVENTS_IMPLEMENTATION)
+#define EVENTS_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_EXPORT
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define EVENTS_EXPORT
+#endif
+
+#endif // UI_EVENTS_EVENTS_EXPORT_H_
diff --git a/ui/events/events_stub.cc b/ui/events/events_stub.cc
new file mode 100644
index 0000000..5c83974
--- /dev/null
+++ b/ui/events/events_stub.cc
@@ -0,0 +1,161 @@
+// 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 "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"
+
+namespace ui {
+
+// Stub implementations of platform-specific methods in events_util.h, built
+// on platforms that currently do not have a complete implementation of events.
+
+void UpdateDeviceList() {
+ NOTIMPLEMENTED();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return base::TimeDelta();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return gfx::Point();
+}
+
+gfx::Point EventSystemLocationFromNative(
+ const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return gfx::Point();
+}
+
+int EventButtonFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+ const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ 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 IncrementTouchIdRefCount(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+ 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 GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return static_cast<KeyboardCode>(0);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return "";
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+uint32 WindowsKeycodeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+uint16 TextFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+uint16 UnmodifiedTextFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/OWNERS b/ui/events/gesture_detection/OWNERS
new file mode 100644
index 0000000..3e9a729
--- /dev/null
+++ b/ui/events/gesture_detection/OWNERS
@@ -0,0 +1,2 @@
+jdduke@chromium.org
+tdresser@chromium.org
diff --git a/ui/events/gesture_detection/bitset_32.h b/ui/events/gesture_detection/bitset_32.h
new file mode 100644
index 0000000..1cc2dad
--- /dev/null
+++ b/ui/events/gesture_detection/bitset_32.h
@@ -0,0 +1,133 @@
+// 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"
+#include "base/logging.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) {
+ DCHECK_LE(n, 31U);
+ 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 {
+ DCHECK_LE(n, 31U);
+ 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/ui/events/gesture_detection/bitset_32_unittest.cc b/ui/events/gesture_detection/bitset_32_unittest.cc
new file mode 100644
index 0000000..2e5d271
--- /dev/null
+++ b/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/ui/events/gesture_detection/filtered_gesture_provider.cc b/ui/events/gesture_detection/filtered_gesture_provider.cc
new file mode 100644
index 0000000..76ee520
--- /dev/null
+++ b/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/ui/events/gesture_detection/filtered_gesture_provider.h b/ui/events/gesture_detection/filtered_gesture_provider.h
new file mode 100644
index 0000000..4d63be6
--- /dev/null
+++ b/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/ui/events/gesture_detection/gesture_config_helper.cc b/ui/events/gesture_detection/gesture_config_helper.cc
new file mode 100644
index 0000000..0039e2c
--- /dev/null
+++ b/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/ui/events/gesture_detection/gesture_config_helper.h b/ui/events/gesture_detection/gesture_config_helper.h
new file mode 100644
index 0000000..0b54c36
--- /dev/null
+++ b/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/ui/events/gesture_detection/gesture_config_helper_android.cc b/ui/events/gesture_detection/gesture_config_helper_android.cc
new file mode 100644
index 0000000..0b23346
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper_android.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/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;
+
+// This value is somewhat arbitrary, but provides a reasonable maximum
+// approximating a large thumb depression.
+const float kMaxGestureBoundsLengthDips = kMinGestureBoundsLengthDips * 4.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;
+
+ const float px_to_dp = 1.f / display.device_scale_factor();
+ config.span_slop = ViewConfiguration::GetTouchSlopInPixels() * 2.f * px_to_dp;
+ config.min_scaling_touch_major =
+ ViewConfiguration::GetMinScalingTouchMajorInPixels() * px_to_dp;
+ config.min_scaling_span =
+ ViewConfiguration::GetMinScalingSpanInPixels() * px_to_dp;
+ config.min_pinch_update_span_delta = 0.f;
+
+ 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;
+ config.max_gesture_bounds_length = kMaxGestureBoundsLengthDips;
+ return config;
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_config_helper_aura.cc b/ui/events/gesture_detection/gesture_config_helper_aura.cc
new file mode 100644
index 0000000..94469c5
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper_aura.cc
@@ -0,0 +1,79 @@
+// 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 <cmath>
+
+#include "base/command_line.h"
+#include "ui/events/event_switches.h"
+#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 =
+ GestureConfiguration::max_swipe_deviation_angle();
+ 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;
+ double min_pinch_update_distance =
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kCompensateForUnstablePinchZoom)
+ ? GestureConfiguration::min_pinch_update_distance_in_pixels()
+ : 0;
+
+ config.span_slop =
+ GestureConfiguration::max_touch_move_in_pixels_for_click() * 2;
+ 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 = min_pinch_update_distance;
+ return config;
+}
+
+} // namespace
+
+GestureProvider::Config DefaultGestureProviderConfig() {
+ GestureProvider::Config config;
+ gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
+ // |screen| is sometimes NULL during tests.
+ if (screen)
+ config.display = screen->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/ui/events/gesture_detection/gesture_detection_export.h b/ui/events/gesture_detection/gesture_detection_export.h
new file mode 100644
index 0000000..edef524
--- /dev/null
+++ b/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/ui/events/gesture_detection/gesture_detector.cc b/ui/events/gesture_detection/gesture_detector.cc
new file mode 100644
index 0000000..c092448
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detector.cc
@@ -0,0 +1,500 @@
+// 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/gesture_listeners.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() {}
+
+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),
+ showpress_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.
+ if (showpress_enabled_)
+ 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/ui/events/gesture_detection/gesture_detector.h b/ui/events/gesture_detection/gesture_detector.h
new file mode 100644
index 0000000..91b2786
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detector.h
@@ -0,0 +1,153 @@
+// 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 DoubleTapListener;
+class GestureListener;
+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 GESTURE_DETECTION_EXPORT 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;
+ };
+
+ 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; }
+ void set_showpress_enabled(bool enabled) { showpress_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 showpress_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/ui/events/gesture_detection/gesture_event_data.cc b/ui/events/gesture_detection/gesture_event_data.cc
new file mode 100644
index 0000000..2102f23
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data.cc
@@ -0,0 +1,62 @@
+// 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,
+ MotionEvent::ToolType primary_tool_type,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box,
+ int flags)
+ : details(details),
+ motion_event_id(motion_event_id),
+ primary_tool_type(primary_tool_type),
+ time(time),
+ x(x),
+ y(y),
+ raw_x(raw_x),
+ raw_y(raw_y),
+ flags(flags) {
+ 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),
+ motion_event_id(other.motion_event_id),
+ primary_tool_type(other.primary_tool_type),
+ time(other.time),
+ x(other.x),
+ y(other.y),
+ raw_x(other.raw_x),
+ raw_y(other.raw_y),
+ flags(other.flags) {
+ details.set_touch_points(other.details.touch_points());
+ details.set_bounding_box(other.details.bounding_box_f());
+}
+
+GestureEventData::GestureEventData()
+ : motion_event_id(0),
+ primary_tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+ x(0),
+ y(0),
+ raw_x(0),
+ raw_y(0),
+ flags(EF_NONE) {
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_event_data.h b/ui/events/gesture_detection/gesture_event_data.h
new file mode 100644
index 0000000..8727c6d
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data.h
@@ -0,0 +1,53 @@
+// 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_detection/motion_event.h"
+#include "ui/events/gesture_event_details.h"
+
+namespace ui {
+
+class GestureEventDataPacket;
+
+struct GESTURE_DETECTION_EXPORT GestureEventData {
+ GestureEventData(const GestureEventDetails&,
+ int motion_event_id,
+ MotionEvent::ToolType primary_tool_type,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box,
+ int flags);
+ GestureEventData(EventType type, const GestureEventData&);
+
+ EventType type() const { return details.type(); }
+
+ GestureEventDetails details;
+ int motion_event_id;
+ MotionEvent::ToolType primary_tool_type;
+ base::TimeTicks time;
+ float x;
+ float y;
+ float raw_x;
+ float raw_y;
+ int flags;
+
+ 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/ui/events/gesture_detection/gesture_event_data_packet.cc b/ui/events/gesture_detection/gesture_event_data_packet.cc
new file mode 100644
index 0000000..265300c
--- /dev/null
+++ b/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/ui/events/gesture_detection/gesture_event_data_packet.h b/ui/events/gesture_detection/gesture_event_data_packet.h
new file mode 100644
index 0000000..9b115de
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data_packet.h
@@ -0,0 +1,67 @@
+// 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 "base/containers/stack_container.h"
+#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);
+
+ enum { kTypicalMaxGesturesPerTouch = 5 };
+ base::TimeTicks timestamp_;
+ base::StackVector<GestureEventData, kTypicalMaxGesturesPerTouch> 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/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
new file mode 100644
index 0000000..f8af11e
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
@@ -0,0 +1,134 @@
+// 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/gesture_event_data_packet.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const float kTouchX = 13.7f;
+const float kTouchY = 14.2f;
+
+GestureEventData CreateGesture(EventType type) {
+ return GestureEventData(GestureEventDetails(type),
+ 0,
+ MotionEvent::TOOL_TYPE_FINGER,
+ base::TimeTicks(),
+ kTouchX,
+ kTouchY,
+ kTouchX + 5.f,
+ kTouchY + 10.f,
+ 1,
+ gfx::RectF(kTouchX - 1.f, kTouchY - 1.f, 2.f, 2.f),
+ EF_NONE);
+}
+
+} // namespace
+
+bool GestureEquals(const GestureEventData& lhs, const GestureEventData& rhs) {
+ return lhs.type() == rhs.type() &&
+ lhs.motion_event_id == rhs.motion_event_id &&
+ lhs.primary_tool_type == rhs.primary_tool_type &&
+ lhs.time == rhs.time && lhs.x == rhs.x && lhs.y == rhs.y &&
+ lhs.raw_x == rhs.raw_x && lhs.raw_y == rhs.raw_y;
+}
+
+bool PacketEquals(const GestureEventDataPacket& lhs,
+ const GestureEventDataPacket& rhs) {
+ if (lhs.timestamp() != rhs.timestamp() ||
+ lhs.gesture_count() != rhs.gesture_count() ||
+ lhs.timestamp() != rhs.timestamp() ||
+ lhs.gesture_source() != rhs.gesture_source() ||
+ lhs.touch_location() != rhs.touch_location() ||
+ lhs.raw_touch_location() != rhs.raw_touch_location())
+ return false;
+
+ for (size_t i = 0; i < lhs.gesture_count(); ++i) {
+ if (!GestureEquals(lhs.gesture(i), rhs.gesture(i)))
+ return false;
+ }
+
+ return true;
+}
+
+class GestureEventDataPacketTest : public testing::Test {};
+
+TEST_F(GestureEventDataPacketTest, Basic) {
+ base::TimeTicks touch_time = base::TimeTicks::Now();
+
+ GestureEventDataPacket packet;
+ EXPECT_EQ(0U, packet.gesture_count());
+ EXPECT_EQ(GestureEventDataPacket::UNDEFINED, packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_DOWN, touch_time, kTouchX, kTouchY));
+ EXPECT_TRUE(touch_time == packet.timestamp());
+ EXPECT_EQ(0U, packet.gesture_count());
+ EXPECT_EQ(gfx::PointF(kTouchX, kTouchY), packet.touch_location());
+
+ for (size_t i = ET_GESTURE_TYPE_START; i < ET_GESTURE_TYPE_END; ++i) {
+ const EventType type = static_cast<EventType>(i);
+ GestureEventData gesture = CreateGesture(type);
+ packet.Push(gesture);
+ const size_t index = (i - ET_GESTURE_TYPE_START);
+ ASSERT_EQ(index + 1U, packet.gesture_count());
+ EXPECT_TRUE(GestureEquals(gesture, packet.gesture(index)));
+ }
+}
+
+TEST_F(GestureEventDataPacketTest, Copy) {
+ GestureEventDataPacket packet0 = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_UP));
+ packet0.Push(CreateGesture(ET_GESTURE_TAP_DOWN));
+ packet0.Push(CreateGesture(ET_GESTURE_SCROLL_BEGIN));
+
+ GestureEventDataPacket packet1 = packet0;
+ EXPECT_TRUE(PacketEquals(packet0, packet1));
+
+ packet0 = packet1;
+ EXPECT_TRUE(PacketEquals(packet1, packet0));
+}
+
+TEST_F(GestureEventDataPacketTest, GestureSource) {
+ GestureEventDataPacket packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_DOWN));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_START,
+ packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_UP));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_END,
+ packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_CANCEL));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL,
+ packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_MOVE));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_MOVE, packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_POINTER_DOWN));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_START, packet.gesture_source());
+
+ packet = GestureEventDataPacket::FromTouch(
+ MockMotionEvent(MotionEvent::ACTION_POINTER_UP));
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_END, packet.gesture_source());
+
+ GestureEventData gesture = CreateGesture(ET_GESTURE_TAP);
+ packet = GestureEventDataPacket::FromTouchTimeout(gesture);
+ EXPECT_EQ(GestureEventDataPacket::TOUCH_TIMEOUT, packet.gesture_source());
+ EXPECT_EQ(1U, packet.gesture_count());
+ EXPECT_EQ(base::TimeTicks(), packet.timestamp());
+ EXPECT_EQ(gfx::PointF(gesture.x, gesture.y), packet.touch_location());
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_listeners.cc b/ui/events/gesture_detection/gesture_listeners.cc
new file mode 100644
index 0000000..8917f26
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_listeners.cc
@@ -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.
+
+#include "ui/events/gesture_detection/gesture_listeners.h"
+
+namespace ui {
+
+bool SimpleGestureListener::OnDown(const MotionEvent& e) {
+ return false;
+}
+
+void SimpleGestureListener::OnShowPress(const MotionEvent& e) {
+}
+
+bool SimpleGestureListener::OnSingleTapUp(const MotionEvent& e) {
+ return false;
+}
+
+void SimpleGestureListener::OnLongPress(const MotionEvent& e) {
+}
+
+bool SimpleGestureListener::OnScroll(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float distance_x,
+ float distance_y) {
+ return false;
+}
+
+bool SimpleGestureListener::OnFling(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float velocity_x,
+ float velocity_y) {
+ return false;
+}
+
+bool SimpleGestureListener::OnSwipe(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float velocity_x,
+ float velocity_y) {
+ return false;
+}
+
+bool SimpleGestureListener::OnTwoFingerTap(const MotionEvent& e1,
+ const MotionEvent& e2) {
+ return false;
+}
+
+bool SimpleGestureListener::OnSingleTapConfirmed(const MotionEvent& e) {
+ return false;
+}
+
+bool SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
+ return false;
+}
+
+bool SimpleGestureListener::OnDoubleTapEvent(const MotionEvent& e) {
+ return false;
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_listeners.h b/ui/events/gesture_detection/gesture_listeners.h
new file mode 100644
index 0000000..1d62347
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_listeners.h
@@ -0,0 +1,83 @@
+// 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_LISTENERS_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_LISTENERS_H_
+
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Client through which |GestureDetector| signals gesture detection.
+class GESTURE_DETECTION_EXPORT 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;
+};
+
+// Client through which |GestureDetector| signals double-tap detection.
+class GESTURE_DETECTION_EXPORT 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 GESTURE_DETECTION_EXPORT 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;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_LISTENERS_H_
diff --git a/ui/events/gesture_detection/gesture_provider.cc b/ui/events/gesture_detection/gesture_provider.cc
new file mode 100644
index 0000000..51d119b
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider.cc
@@ -0,0 +1,822 @@
+// 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/gesture_listeners.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_detection/scale_gesture_listeners.h"
+#include "ui/gfx/geometry/point_f.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 ClampBoundingBox(const gfx::RectF& bounds,
+ float min_length,
+ float max_length) {
+ float width = bounds.width();
+ float height = bounds.height();
+ if (min_length) {
+ width = std::max(min_length, width);
+ height = std::max(min_length, height);
+ }
+ if (max_length) {
+ width = std::min(max_length, width);
+ height = std::min(max_length, height);
+ }
+ const gfx::PointF center = bounds.CenterPoint();
+ return gfx::RectF(
+ center.x() - width / 2.f, center.y() - height / 2.f, width, height);
+}
+
+} // 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),
+ max_gesture_bounds_length(0) {
+}
+
+GestureProvider::Config::~Config() {
+}
+
+// GestureProvider::GestureListener
+
+class GestureProvider::GestureListenerImpl : public ScaleGestureListener,
+ public GestureListener,
+ public DoubleTapListener {
+ public:
+ GestureListenerImpl(const GestureProvider::Config& config,
+ GestureProviderClient* client)
+ : config_(config),
+ client_(client),
+ gesture_detector_(config.gesture_detector_config, this, this),
+ scale_gesture_detector_(config.scale_gesture_detector_config, this),
+ snap_scroll_controller_(config.display),
+ ignore_multitouch_zoom_events_(false),
+ ignore_single_tap_(false),
+ pinch_event_sent_(false),
+ scroll_event_sent_(false),
+ max_diameter_before_show_press_(0),
+ show_press_event_sent_(false) {}
+
+ void OnTouchEvent(const MotionEvent& event) {
+ const bool in_scale_gesture = IsScaleGestureDetectionInProgress();
+ snap_scroll_controller_.SetSnapScrollingMode(event, in_scale_gesture);
+ if (in_scale_gesture)
+ SetIgnoreSingleTap(true);
+
+ const MotionEvent::Action action = event.GetAction();
+ if (action == MotionEvent::ACTION_DOWN) {
+ current_down_time_ = event.GetEventTime();
+ current_longpress_time_ = base::TimeTicks();
+ ignore_single_tap_ = false;
+ scroll_event_sent_ = false;
+ pinch_event_sent_ = false;
+ show_press_event_sent_ = false;
+ gesture_detector_.set_longpress_enabled(true);
+ tap_down_point_ = gfx::PointF(event.GetX(), event.GetY());
+ max_diameter_before_show_press_ = event.GetTouchMajor();
+ }
+
+ gesture_detector_.OnTouchEvent(event);
+ scale_gesture_detector_.OnTouchEvent(event);
+
+ if (action == MotionEvent::ACTION_UP ||
+ action == 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.
+ if (scroll_event_sent_)
+ Send(CreateGesture(ET_GESTURE_SCROLL_END, event));
+ current_down_time_ = base::TimeTicks();
+ } else if (action == MotionEvent::ACTION_MOVE) {
+ if (!show_press_event_sent_ && !scroll_event_sent_) {
+ max_diameter_before_show_press_ =
+ std::max(max_diameter_before_show_press_, event.GetTouchMajor());
+ }
+ }
+ }
+
+ void 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_time_.is_null() || gesture.type() == ET_GESTURE_TAP ||
+ gesture.type() == ET_GESTURE_SHOW_PRESS ||
+ gesture.type() == ET_GESTURE_BEGIN ||
+ gesture.type() == ET_GESTURE_END);
+
+ if (gesture.primary_tool_type == MotionEvent::TOOL_TYPE_UNKNOWN ||
+ gesture.primary_tool_type == MotionEvent::TOOL_TYPE_FINGER) {
+ gesture.details.set_bounding_box(
+ ClampBoundingBox(gesture.details.bounding_box_f(),
+ config_.min_gesture_bounds_length,
+ config_.max_gesture_bounds_length));
+ }
+
+ switch (gesture.type()) {
+ case ET_GESTURE_LONG_PRESS:
+ DCHECK(!IsScaleGestureDetectionInProgress());
+ current_longpress_time_ = gesture.time;
+ break;
+ case ET_GESTURE_LONG_TAP:
+ current_longpress_time_ = base::TimeTicks();
+ break;
+ case ET_GESTURE_SCROLL_BEGIN:
+ DCHECK(!scroll_event_sent_);
+ scroll_event_sent_ = true;
+ break;
+ case ET_GESTURE_SCROLL_END:
+ DCHECK(scroll_event_sent_);
+ if (pinch_event_sent_)
+ Send(GestureEventData(ET_GESTURE_PINCH_END, gesture));
+ scroll_event_sent_ = false;
+ break;
+ case ET_SCROLL_FLING_START:
+ DCHECK(scroll_event_sent_);
+ scroll_event_sent_ = false;
+ break;
+ case ET_GESTURE_PINCH_BEGIN:
+ DCHECK(!pinch_event_sent_);
+ if (!scroll_event_sent_)
+ Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture));
+ pinch_event_sent_ = true;
+ break;
+ case ET_GESTURE_PINCH_END:
+ DCHECK(pinch_event_sent_);
+ pinch_event_sent_ = 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_event_sent_ || scroll_event_sent_)
+ return;
+ default:
+ break;
+ };
+
+ client_->OnGestureEvent(gesture);
+ GestureTouchUMAHistogram::RecordGestureEvent(gesture);
+ }
+
+ // ScaleGestureListener implementation.
+ virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode())
+ return false;
+ return true;
+ }
+
+ virtual void OnScaleEnd(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (!pinch_event_sent_)
+ return;
+ Send(CreateGesture(ET_GESTURE_PINCH_END, e));
+ }
+
+ virtual bool OnScale(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode())
+ return false;
+ if (!pinch_event_sent_) {
+ Send(CreateGesture(ET_GESTURE_PINCH_BEGIN,
+ e.GetId(),
+ e.GetToolType(),
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY(),
+ detector.GetFocusX() + e.GetRawOffsetX(),
+ detector.GetFocusY() + e.GetRawOffsetY(),
+ e.GetPointerCount(),
+ GetBoundingBox(e, ET_GESTURE_PINCH_BEGIN),
+ e.GetFlags()));
+ }
+
+ if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) <
+ config_.scale_gesture_detector_config.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);
+ pinch_details.set_scale(scale);
+ Send(CreateGesture(pinch_details,
+ e.GetId(),
+ e.GetToolType(),
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY(),
+ detector.GetFocusX() + e.GetRawOffsetX(),
+ detector.GetFocusY() + e.GetRawOffsetY(),
+ e.GetPointerCount(),
+ GetBoundingBox(e, pinch_details.type()),
+ e.GetFlags()));
+ return true;
+ }
+
+ // GestureListener implementation.
+ virtual bool OnDown(const MotionEvent& e) OVERRIDE {
+ GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN);
+ 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 (!scroll_event_sent_) {
+ // Remove the touch slop region from the first scroll event to avoid a
+ // jump.
+ 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 - config_.gesture_detector_config.touch_slop) /
+ distance;
+ distance_x *= ratio;
+ distance_y *= ratio;
+ }
+
+ // 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.
+ Send(CreateGesture(scroll_details,
+ e2.GetId(),
+ e2.GetToolType(),
+ e2.GetEventTime(),
+ e1.GetX(),
+ e1.GetY(),
+ e1.GetRawX(),
+ e1.GetRawY(),
+ e2.GetPointerCount(),
+ GetBoundingBox(e2, scroll_details.type()),
+ e2.GetFlags()));
+ DCHECK(scroll_event_sent_);
+ }
+
+ 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 (distance_x || distance_y) {
+ GestureEventDetails scroll_details(
+ ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y);
+ const gfx::RectF bounding_box = GetBoundingBox(e2, scroll_details.type());
+ const gfx::PointF center = bounding_box.CenterPoint();
+ const gfx::PointF raw_center =
+ center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY());
+ Send(CreateGesture(scroll_details,
+ e2.GetId(),
+ e2.GetToolType(),
+ e2.GetEventTime(),
+ center.x(),
+ center.y(),
+ raw_center.x(),
+ raw_center.y(),
+ e2.GetPointerCount(),
+ bounding_box,
+ e2.GetFlags()));
+ }
+
+ 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;
+ }
+ }
+
+ if (!velocity_x && !velocity_y)
+ return true;
+
+ if (!scroll_event_sent_) {
+ // The native side needs a ET_GESTURE_SCROLL_BEGIN before
+ // ET_SCROLL_FLING_START to send the fling to the correct target.
+ // 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, e2));
+ }
+
+ GestureEventDetails fling_details(
+ ET_SCROLL_FLING_START, velocity_x, velocity_y);
+ Send(CreateGesture(fling_details, e2));
+ 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);
+ 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());
+ Send(CreateGesture(two_finger_tap_details,
+ e2.GetId(),
+ e2.GetToolType(),
+ e2.GetEventTime(),
+ e1.GetX(),
+ e1.GetY(),
+ e1.GetRawX(),
+ e1.GetRawY(),
+ e2.GetPointerCount(),
+ GetBoundingBox(e2, two_finger_tap_details.type()),
+ e2.GetFlags()));
+ return true;
+ }
+
+ virtual void OnShowPress(const MotionEvent& e) OVERRIDE {
+ GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS);
+ show_press_event_sent_ = true;
+ 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_ >
+ config_.gesture_detector_config.double_tap_timeout) {
+ return OnSingleTapConfirmed(e);
+ } else if (!IsDoubleTapEnabled() || config_.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.
+ Send(CreateTapGesture(ET_GESTURE_TAP_UNCONFIRMED, e));
+ }
+ }
+
+ if (e.GetAction() == MotionEvent::ACTION_UP &&
+ !current_longpress_time_.is_null() &&
+ !IsScaleGestureDetectionInProgress()) {
+ GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP);
+ Send(CreateGesture(long_tap_details, e));
+ return true;
+ }
+
+ return false;
+ }
+
+ // 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;
+
+ Send(CreateTapGesture(ET_GESTURE_TAP, e));
+ return true;
+ }
+
+ virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE {
+ return scale_gesture_detector_.OnDoubleTap(e);
+ }
+
+ 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 (!IsPinchInProgress() && !IsScrollInProgress()) {
+ Send(CreateTapGesture(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);
+ Send(CreateGesture(long_press_details, e));
+ }
+
+ GestureEventData CreateGesture(const GestureEventDetails& details,
+ int motion_event_id,
+ MotionEvent::ToolType primary_tool_type,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box,
+ int flags) {
+ return GestureEventData(details,
+ motion_event_id,
+ primary_tool_type,
+ time,
+ x,
+ y,
+ raw_x,
+ raw_y,
+ touch_point_count,
+ bounding_box,
+ flags);
+ }
+
+ GestureEventData CreateGesture(EventType type,
+ int motion_event_id,
+ MotionEvent::ToolType primary_tool_type,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box,
+ int flags) {
+ return GestureEventData(GestureEventDetails(type),
+ motion_event_id,
+ primary_tool_type,
+ time,
+ x,
+ y,
+ raw_x,
+ raw_y,
+ touch_point_count,
+ bounding_box,
+ flags);
+ }
+
+ GestureEventData CreateGesture(const GestureEventDetails& details,
+ const MotionEvent& event) {
+ return GestureEventData(details,
+ event.GetId(),
+ event.GetToolType(),
+ event.GetEventTime(),
+ event.GetX(),
+ event.GetY(),
+ event.GetRawX(),
+ event.GetRawY(),
+ event.GetPointerCount(),
+ GetBoundingBox(event, details.type()),
+ event.GetFlags());
+ }
+
+ GestureEventData CreateGesture(EventType type, const MotionEvent& event) {
+ return CreateGesture(GestureEventDetails(type), event);
+ }
+
+ GestureEventData CreateTapGesture(EventType type, const MotionEvent& event) {
+ // 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 details(type);
+ details.set_tap_count(1);
+ return CreateGesture(details, event);
+ }
+
+ gfx::RectF GetBoundingBox(const MotionEvent& event, EventType type) {
+ // Can't use gfx::RectF::Union, as it ignores touches with a radius of 0.
+ 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 (size_t i = 0; i < event.GetPointerCount(); ++i) {
+ float x, y, diameter;
+ // Only for the show press and tap events, the bounding box is calculated
+ // based on the touch start point and the maximum diameter before the
+ // show press event is sent.
+ if (type == ET_GESTURE_SHOW_PRESS || type == ET_GESTURE_TAP ||
+ type == ET_GESTURE_TAP_UNCONFIRMED) {
+ DCHECK_EQ(0U, i);
+ diameter = max_diameter_before_show_press_;
+ x = tap_down_point_.x();
+ y = tap_down_point_.y();
+ } else {
+ diameter = event.GetTouchMajor(i);
+ x = event.GetX(i);
+ y = event.GetY(i);
+ }
+ x = x - diameter / 2;
+ y = y - diameter / 2;
+ left = std::min(left, x);
+ right = std::max(right, x + diameter);
+ top = std::min(top, y);
+ bottom = std::max(bottom, y + diameter);
+ }
+ return gfx::RectF(left, top, right - left, bottom - top);
+ }
+
+ void SetDoubleTapEnabled(bool enabled) {
+ DCHECK(!IsDoubleTapInProgress());
+ gesture_detector_.SetDoubleTapListener(enabled ? this : NULL);
+ }
+
+ void SetMultiTouchZoomEnabled(bool enabled) {
+ // Note that returning false from |OnScaleBegin()| or |OnScale()| prevents
+ // the detector from emitting further scale updates for the current touch
+ // sequence. Thus, if multitouch events are enabled in the middle of a
+ // gesture, it will only take effect with the next gesture.
+ ignore_multitouch_zoom_events_ = !enabled;
+ }
+
+ bool IsDoubleTapInProgress() const {
+ return gesture_detector_.is_double_tapping() ||
+ (IsScaleGestureDetectionInProgress() && InDoubleTapMode());
+ }
+
+ bool IsScrollInProgress() const { return scroll_event_sent_; }
+
+ bool IsPinchInProgress() const { return pinch_event_sent_; }
+
+ private:
+ bool IsScaleGestureDetectionInProgress() const {
+ return scale_gesture_detector_.IsInProgress();
+ }
+
+ bool InDoubleTapMode() const {
+ return scale_gesture_detector_.InDoubleTapMode();
+ }
+
+ bool IsDoubleTapEnabled() const {
+ return gesture_detector_.has_doubletap_listener();
+ }
+
+ void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; }
+
+ const GestureProvider::Config config_;
+ GestureProviderClient* const client_;
+
+ GestureDetector gesture_detector_;
+ ScaleGestureDetector scale_gesture_detector_;
+ SnapScrollController snap_scroll_controller_;
+
+ base::TimeTicks current_down_time_;
+
+ // 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_;
+
+ // Completely silence multi-touch (pinch) scaling events. Used in WebView when
+ // zoom support is turned off.
+ bool ignore_multitouch_zoom_events_;
+
+ // 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_;
+
+ // Tracks whether {PINCH|SCROLL}_BEGIN events have been forwarded for the
+ // current touch sequence.
+ bool pinch_event_sent_;
+ bool scroll_event_sent_;
+
+ // Only track the maximum diameter before the show press event has been
+ // sent and a tap must still be possible for this touch sequence.
+ float max_diameter_before_show_press_;
+
+ gfx::PointF tap_down_point_;
+
+ // Tracks whether an ET_GESTURE_SHOW_PRESS event has been sent for this touch
+ // sequence.
+ bool show_press_event_sent_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl);
+};
+
+// GestureProvider
+
+GestureProvider::GestureProvider(const Config& config,
+ GestureProviderClient* client)
+ : double_tap_support_for_page_(true),
+ double_tap_support_for_platform_(true),
+ gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled) {
+ DCHECK(client);
+ DCHECK(!config.min_gesture_bounds_length ||
+ !config.max_gesture_bounds_length ||
+ config.min_gesture_bounds_length <= config.max_gesture_bounds_length);
+ TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
+ gesture_listener_.reset(new GestureListenerImpl(config, client));
+ UpdateDoubleTapDetectionSupport();
+}
+
+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;
+
+ OnTouchEventHandlingBegin(event);
+ gesture_listener_->OnTouchEvent(event);
+ OnTouchEventHandlingEnd(event);
+ uma_histogram_.RecordTouchEvent(event);
+ return true;
+}
+
+void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) {
+ gesture_listener_->SetMultiTouchZoomEnabled(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 {
+ return gesture_listener_->IsScrollInProgress();
+}
+
+bool GestureProvider::IsPinchInProgress() const {
+ return gesture_listener_->IsPinchInProgress();
+}
+
+bool GestureProvider::IsDoubleTapInProgress() const {
+ return gesture_listener_->IsDoubleTapInProgress();
+}
+
+bool GestureProvider::CanHandle(const MotionEvent& event) const {
+ // Aura requires one cancel event per touch point, whereas Android requires
+ // one cancel event per touch sequence. Thus we need to allow extra cancel
+ // events.
+ return current_down_event_ || event.GetAction() == MotionEvent::ACTION_DOWN ||
+ event.GetAction() == MotionEvent::ACTION_CANCEL;
+}
+
+void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) {
+ switch (event.GetAction()) {
+ case MotionEvent::ACTION_DOWN:
+ current_down_event_ = event.Clone();
+ if (gesture_begin_end_types_enabled_)
+ gesture_listener_->Send(
+ gesture_listener_->CreateGesture(ET_GESTURE_BEGIN, event));
+ break;
+ case MotionEvent::ACTION_POINTER_DOWN:
+ if (gesture_begin_end_types_enabled_) {
+ const int action_index = event.GetActionIndex();
+ gesture_listener_->Send(gesture_listener_->CreateGesture(
+ ET_GESTURE_BEGIN,
+ event.GetId(),
+ event.GetToolType(),
+ event.GetEventTime(),
+ event.GetX(action_index),
+ event.GetY(action_index),
+ event.GetRawX(action_index),
+ event.GetRawY(action_index),
+ event.GetPointerCount(),
+ gesture_listener_->GetBoundingBox(event, ET_GESTURE_BEGIN),
+ event.GetFlags()));
+ }
+ 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: {
+ if (gesture_begin_end_types_enabled_)
+ gesture_listener_->Send(
+ gesture_listener_->CreateGesture(ET_GESTURE_END, event));
+
+ current_down_event_.reset();
+
+ UpdateDoubleTapDetectionSupport();
+ break;
+ }
+ case MotionEvent::ACTION_POINTER_UP:
+ if (gesture_begin_end_types_enabled_)
+ gesture_listener_->Send(
+ gesture_listener_->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);
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_provider.h b/ui/events/gesture_detection/gesture_provider.h
new file mode 100644
index 0000000..7474967
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider.h
@@ -0,0 +1,117 @@
+// 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/gesture_touch_uma_histogram.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. This requires one ACTION_CANCEL event to be sent per
+ // touch point, which only occurs on Aura. Defaults to false.
+ bool gesture_begin_end_types_enabled;
+
+ // The min and max 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 or large touch sizes.
+ // Note that these bounds are only applied for touch or unknown tool types;
+ // mouse and stylus-derived gestures will not be affected.
+ // Both values default to 0 (disabled).
+ float min_gesture_bounds_length;
+ float max_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:
+ bool CanHandle(const MotionEvent& event) const;
+ void OnTouchEventHandlingBegin(const MotionEvent& event);
+ void OnTouchEventHandlingEnd(const MotionEvent& event);
+ void UpdateDoubleTapDetectionSupport();
+
+ class GestureListenerImpl;
+ scoped_ptr<GestureListenerImpl> gesture_listener_;
+
+ scoped_ptr<MotionEvent> current_down_event_;
+
+ // Logs information on touch and gesture events.
+ GestureTouchUMAHistogram uma_histogram_;
+
+ // Whether double-tap gesture detection is currently supported.
+ bool double_tap_support_for_page_;
+ bool double_tap_support_for_platform_;
+
+ const bool gesture_begin_end_types_enabled_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_
diff --git a/ui/events/gesture_detection/gesture_provider_unittest.cc b/ui/events/gesture_detection/gesture_provider_unittest.cc
new file mode 100644
index 0000000..a25a0b5
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider_unittest.cc
@@ -0,0 +1,2502 @@
+// 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/motion_event.h"
+#include "ui/events/test/mock_motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+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;
+ 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 SetMinMaxGestureBoundsLength(float min_gesture_bound_length,
+ float max_gesture_bound_length) {
+ GestureProvider::Config config = GetDefaultConfig();
+ config.min_gesture_bounds_length = min_gesture_bound_length;
+ config.max_gesture_bounds_length = max_gesture_bound_length;
+ SetUpWithConfig(config);
+ }
+
+ void SetShowPressAndLongPressTimeout(base::TimeDelta showpress_timeout,
+ base::TimeDelta longpress_timeout) {
+ GestureProvider::Config config = GetDefaultConfig();
+ config.gesture_detector_config.showpress_timeout = showpress_timeout;
+ config.gesture_detector_config.longpress_timeout = longpress_timeout;
+ 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;
+ int motion_event_flags = EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN;
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+
+ event = ObtainMotionEvent(event_time + kOneSecond,
+ MotionEvent::ACTION_MOVE,
+ scroll_to_x,
+ scroll_to_y);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ 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(motion_event_flags, GetMostRecentGestureEvent().flags);
+ EXPECT_EQ(event.GetToolType(0),
+ GetMostRecentGestureEvent().primary_tool_type);
+ 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.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+ event.set_id(++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(event.GetToolType(0),
+ GetMostRecentGestureEvent().primary_tool_type);
+ 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;
+ int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN;
+
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+ EXPECT_EQ(event.GetToolType(0),
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+ EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+ GetMostRecentGestureEvent().details.bounding_box());
+
+ event = ObtainMotionEvent(event_time + kOneMicrosecond,
+ MotionEvent::ACTION_UP);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ 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(event.GetToolType(0),
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+ 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;
+ int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN | EF_CAPS_LOCK_DOWN;
+
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+ 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.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ 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(motion_event_flags, GetMostRecentGestureEvent().flags);
+ 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;
+ int motion_event_flags = EF_ALT_DOWN;
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+ event = ObtainMotionEvent(event_time + delta_time,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX * 10,
+ kFakeCoordY * 10);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(event_time + delta_time * 2,
+ MotionEvent::ACTION_UP,
+ kFakeCoordX * 10,
+ kFakeCoordY * 10);
+ event.set_id(++motion_event_id);
+ event.set_flags(motion_event_flags);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType());
+ EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+ EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+ 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.set_id(++motion_event_id);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(event_time + delta_time,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX * 5,
+ kFakeCoordY * 5);
+ event.set_id(++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.set_id(++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_FLOAT_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.set_id(++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.set_id(++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.set_id(++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.set_id(++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.set_id(++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.set_id(++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.set_id(++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 when the touch major value is used in scaling.
+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());
+
+ event = ObtainMotionEvent(event_time + kOneSecond * 4,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX + 50.f,
+ kFakeCoordY - 25.f);
+ event.SetTouchMajor(kFatFingerSize * 10.f);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+
+ event = ObtainMotionEvent(event_time + kOneSecond * 4,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX + 100.f,
+ kFakeCoordY - 50.f);
+ event.SetTouchMajor(kFatFingerSize * 5.f);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+}
+
+// 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.set_id(++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.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.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.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.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(3, 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);
+ 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.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(3, 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.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(2, 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.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);
+ 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);
+ 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);
+ 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);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(5U, GetReceivedGestureCount());
+ EXPECT_EQ(3, GetReceivedGesture(4).details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(4).type());
+ EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+ EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+
+ event = ObtainMotionEvent(
+ event_time, MotionEvent::ACTION_CANCEL, 1, 1, 3, 3);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(6U, GetReceivedGestureCount());
+ EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
+ EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+ EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+
+ event = ObtainMotionEvent(
+ event_time, MotionEvent::ACTION_CANCEL, 3, 3);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
+ EXPECT_EQ(3, GetMostRecentGestureEvent().x);
+ EXPECT_EQ(3, GetMostRecentGestureEvent().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;
+ SetMinMaxGestureBoundsLength(kMinGestureBoundsLength, 0.f);
+ 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());
+}
+
+TEST_F(GestureProviderTest, MaxGestureBoundsLength) {
+ const float kMaxGestureBoundsLength = kMockTouchRadius / 10.f;
+ SetMinMaxGestureBoundsLength(0.f, kMaxGestureBoundsLength);
+ 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(kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(kMaxGestureBoundsLength,
+ 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(kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().height());
+}
+
+TEST_F(GestureProviderTest, ZeroRadiusBoundingBox) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 20);
+ event.SetTouchMajor(0);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(gfx::RectF(10, 20, 0, 0),
+ GetMostRecentGestureEvent().details.bounding_box());
+
+ event = ObtainMotionEvent(
+ event_time, MotionEvent::ACTION_POINTER_DOWN, 10, 20, 110, 120);
+ event.SetTouchMajor(0);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(
+ event_time, MotionEvent::ACTION_MOVE, 10, 20, 110, 150);
+ event.SetTouchMajor(0);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ EXPECT_EQ(gfx::RectF(10, 20, 100, 130),
+ GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Verify that the min/max gesture bound settings are not applied to stylus
+// or mouse-derived MotionEvents.
+TEST_F(GestureProviderTest, NoMinOrMaxGestureBoundsLengthWithStylusOrMouse) {
+ const float kMinGestureBoundsLength = 5.f * kMockTouchRadius;
+ const float kMaxGestureBoundsLength = 10.f * kMockTouchRadius;
+ SetMinMaxGestureBoundsLength(kMinGestureBoundsLength,
+ kMaxGestureBoundsLength);
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetTouchMajor(0);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+ event =
+ ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+ event.SetTouchMajor(1);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_STYLUS);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+ EXPECT_EQ(MotionEvent::TOOL_TYPE_STYLUS,
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+ event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+ event =
+ ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+ event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
+ event.SetToolType(0, MotionEvent::TOOL_TYPE_ERASER);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+ EXPECT_EQ(MotionEvent::TOOL_TYPE_ERASER,
+ GetMostRecentGestureEvent().primary_tool_type);
+ EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().width());
+ EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+ GetMostRecentGestureEvent().details.bounding_box_f().height());
+}
+
+// Test the bounding box for show press and tap gestures.
+TEST_F(GestureProviderTest, BoundingBoxForShowPressAndTapGesture) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+ base::TimeDelta showpress_timeout = kOneMicrosecond;
+ base::TimeDelta longpress_timeout = kOneSecond;
+ SetShowPressAndLongPressTimeout(showpress_timeout, longpress_timeout);
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 10);
+ event.SetTouchMajor(10);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+ EXPECT_EQ(gfx::RectF(5, 5, 10, 10),
+ GetMostRecentGestureEvent().details.bounding_box());
+
+ event = ObtainMotionEvent(
+ event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 11, 9);
+ event.SetTouchMajor(20);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ event = ObtainMotionEvent(
+ event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 8, 11);
+ event.SetTouchMajor(10);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ RunTasksAndWait(showpress_timeout + kOneMicrosecond);
+ EXPECT_EQ(ET_GESTURE_SHOW_PRESS, GetMostRecentGestureEventType());
+ EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
+ GetMostRecentGestureEvent().details.bounding_box());
+
+ event =
+ ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+ event.SetTouchMajor(30);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
+ EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+ EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
+ GetMostRecentGestureEvent().details.bounding_box());
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_touch_uma_histogram.cc b/ui/events/gesture_detection/gesture_touch_uma_histogram.cc
new file mode 100644
index 0000000..b8275aa
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_touch_uma_histogram.cc
@@ -0,0 +1,144 @@
+// 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_touch_uma_histogram.h"
+
+#include "base/metrics/histogram.h"
+
+namespace ui {
+
+GestureTouchUMAHistogram::GestureTouchUMAHistogram()
+ : max_distance_from_start_squared_(0), is_single_finger_(true) {
+}
+
+GestureTouchUMAHistogram::~GestureTouchUMAHistogram() {
+}
+
+void GestureTouchUMAHistogram::RecordGestureEvent(
+ const GestureEventData& gesture) {
+ UMA_HISTOGRAM_ENUMERATION(
+ "Event.GestureCreated", UMAEventTypeFromEvent(gesture), UMA_ET_COUNT);
+}
+
+void GestureTouchUMAHistogram::RecordTouchEvent(const MotionEvent& event) {
+ if (event.GetAction() == MotionEvent::ACTION_DOWN) {
+ start_time_ = event.GetEventTime();
+ start_touch_position_ = gfx::Point(event.GetX(), event.GetY());
+ is_single_finger_ = true;
+ max_distance_from_start_squared_ = 0;
+ } else if (event.GetAction() == MotionEvent::ACTION_MOVE &&
+ is_single_finger_) {
+ float cur_dist = (start_touch_position_ -
+ gfx::Point(event.GetX(), event.GetY())).LengthSquared();
+ if (cur_dist > max_distance_from_start_squared_)
+ max_distance_from_start_squared_ = cur_dist;
+ } else {
+ if (event.GetAction() == MotionEvent::ACTION_UP && is_single_finger_) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Event.TouchMaxDistance",
+ static_cast<int>(sqrt(max_distance_from_start_squared_)),
+ 0,
+ 1500,
+ 50);
+
+ base::TimeDelta duration = event.GetEventTime() - start_time_;
+ UMA_HISTOGRAM_TIMES("Event.TouchDuration", duration);
+ }
+ is_single_finger_ = false;
+ }
+}
+
+UMAEventType GestureTouchUMAHistogram::UMAEventTypeFromEvent(
+ const GestureEventData& gesture) {
+ switch (gesture.type()) {
+ case ET_TOUCH_RELEASED:
+ return UMA_ET_TOUCH_RELEASED;
+ case ET_TOUCH_PRESSED:
+ return UMA_ET_TOUCH_PRESSED;
+ case ET_TOUCH_MOVED:
+ return UMA_ET_TOUCH_MOVED;
+ case ET_TOUCH_CANCELLED:
+ return UMA_ET_TOUCH_CANCELLED;
+ case ET_GESTURE_SCROLL_BEGIN:
+ return UMA_ET_GESTURE_SCROLL_BEGIN;
+ case ET_GESTURE_SCROLL_END:
+ return UMA_ET_GESTURE_SCROLL_END;
+ case ET_GESTURE_SCROLL_UPDATE: {
+ int touch_points = gesture.details.touch_points();
+ if (touch_points == 1)
+ return UMA_ET_GESTURE_SCROLL_UPDATE;
+ else if (touch_points == 2)
+ return UMA_ET_GESTURE_SCROLL_UPDATE_2;
+ else if (touch_points == 3)
+ return UMA_ET_GESTURE_SCROLL_UPDATE_3;
+ return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
+ }
+ case ET_GESTURE_TAP: {
+ int tap_count = gesture.details.tap_count();
+ if (tap_count == 1)
+ return UMA_ET_GESTURE_TAP;
+ if (tap_count == 2)
+ return UMA_ET_GESTURE_DOUBLE_TAP;
+ if (tap_count == 3)
+ return UMA_ET_GESTURE_TRIPLE_TAP;
+ NOTREACHED() << "Received tap with tapcount " << tap_count;
+ return UMA_ET_UNKNOWN;
+ }
+ case ET_GESTURE_TAP_DOWN:
+ return UMA_ET_GESTURE_TAP_DOWN;
+ case ET_GESTURE_BEGIN:
+ return UMA_ET_GESTURE_BEGIN;
+ case ET_GESTURE_END:
+ return UMA_ET_GESTURE_END;
+ case ET_GESTURE_TWO_FINGER_TAP:
+ return UMA_ET_GESTURE_TWO_FINGER_TAP;
+ case ET_GESTURE_PINCH_BEGIN:
+ return UMA_ET_GESTURE_PINCH_BEGIN;
+ case ET_GESTURE_PINCH_END:
+ return UMA_ET_GESTURE_PINCH_END;
+ case ET_GESTURE_PINCH_UPDATE: {
+ int touch_points = gesture.details.touch_points();
+ if (touch_points >= 4)
+ return UMA_ET_GESTURE_PINCH_UPDATE_4P;
+ else if (touch_points == 3)
+ return UMA_ET_GESTURE_PINCH_UPDATE_3;
+ return UMA_ET_GESTURE_PINCH_UPDATE;
+ }
+ case ET_GESTURE_LONG_PRESS:
+ return UMA_ET_GESTURE_LONG_PRESS;
+ case ET_GESTURE_LONG_TAP:
+ return UMA_ET_GESTURE_LONG_TAP;
+ case ET_GESTURE_SWIPE: {
+ int touch_points = gesture.details.touch_points();
+ if (touch_points == 1)
+ return UMA_ET_GESTURE_SWIPE_1;
+ else if (touch_points == 2)
+ return UMA_ET_GESTURE_SWIPE_2;
+ else if (touch_points == 3)
+ return UMA_ET_GESTURE_SWIPE_3;
+ return UMA_ET_GESTURE_SWIPE_4P;
+ }
+ case ET_GESTURE_WIN8_EDGE_SWIPE:
+ return UMA_ET_GESTURE_WIN8_EDGE_SWIPE;
+ case ET_GESTURE_TAP_CANCEL:
+ return UMA_ET_GESTURE_TAP_CANCEL;
+ case ET_GESTURE_SHOW_PRESS:
+ return UMA_ET_GESTURE_SHOW_PRESS;
+ case ET_SCROLL:
+ return UMA_ET_SCROLL;
+ case ET_SCROLL_FLING_START:
+ return UMA_ET_SCROLL_FLING_START;
+ case ET_SCROLL_FLING_CANCEL:
+ return UMA_ET_SCROLL_FLING_CANCEL;
+ case ET_GESTURE_TAP_UNCONFIRMED:
+ return UMA_ET_GESTURE_TAP_UNCONFIRMED;
+ case ET_GESTURE_DOUBLE_TAP:
+ return UMA_ET_GESTURE_DOUBLE_TAP;
+ default:
+ NOTREACHED();
+ return UMA_ET_UNKNOWN;
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/gesture_touch_uma_histogram.h b/ui/events/gesture_detection/gesture_touch_uma_histogram.h
new file mode 100644
index 0000000..824a562
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_touch_uma_histogram.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.
+
+#ifndef UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
+
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+enum UMAEventType {
+ // WARNING: Do not change the numerical values of any of these types.
+ // Do not remove deprecated types - just comment them as deprecated.
+ UMA_ET_UNKNOWN = 0,
+ UMA_ET_TOUCH_RELEASED = 1,
+ UMA_ET_TOUCH_PRESSED = 2,
+ UMA_ET_TOUCH_MOVED = 3,
+ UMA_ET_TOUCH_STATIONARY = 4, // Deprecated. Do not remove.
+ UMA_ET_TOUCH_CANCELLED = 5,
+ UMA_ET_GESTURE_SCROLL_BEGIN = 6,
+ UMA_ET_GESTURE_SCROLL_END = 7,
+ UMA_ET_GESTURE_SCROLL_UPDATE = 8,
+ UMA_ET_GESTURE_TAP = 9,
+ UMA_ET_GESTURE_TAP_DOWN = 10,
+ UMA_ET_GESTURE_BEGIN = 11,
+ UMA_ET_GESTURE_END = 12,
+ UMA_ET_GESTURE_DOUBLE_TAP = 13,
+ UMA_ET_GESTURE_TRIPLE_TAP = 14,
+ UMA_ET_GESTURE_TWO_FINGER_TAP = 15,
+ UMA_ET_GESTURE_PINCH_BEGIN = 16,
+ UMA_ET_GESTURE_PINCH_END = 17,
+ UMA_ET_GESTURE_PINCH_UPDATE = 18,
+ UMA_ET_GESTURE_LONG_PRESS = 19,
+ UMA_ET_GESTURE_SWIPE_2 = 20, // Swipe with 2 fingers
+ UMA_ET_SCROLL = 21,
+ UMA_ET_SCROLL_FLING_START = 22,
+ UMA_ET_SCROLL_FLING_CANCEL = 23,
+ UMA_ET_GESTURE_SWIPE_3 = 24, // Swipe with 3 fingers
+ UMA_ET_GESTURE_SWIPE_4P = 25, // Swipe with 4+ fingers
+ UMA_ET_GESTURE_SCROLL_UPDATE_2 = 26,
+ UMA_ET_GESTURE_SCROLL_UPDATE_3 = 27,
+ UMA_ET_GESTURE_SCROLL_UPDATE_4P = 28,
+ UMA_ET_GESTURE_PINCH_UPDATE_3 = 29,
+ UMA_ET_GESTURE_PINCH_UPDATE_4P = 30,
+ UMA_ET_GESTURE_LONG_TAP = 31,
+ UMA_ET_GESTURE_SHOW_PRESS = 32,
+ UMA_ET_GESTURE_TAP_CANCEL = 33,
+ UMA_ET_GESTURE_WIN8_EDGE_SWIPE = 34,
+ UMA_ET_GESTURE_SWIPE_1 = 35, // Swipe with 1 finger
+ UMA_ET_GESTURE_TAP_UNCONFIRMED = 36,
+ // NOTE: Add new event types only immediately above this line. Make sure to
+ // update the UIEventType enum in tools/metrics/histograms/histograms.xml
+ // accordingly.
+ UMA_ET_COUNT
+};
+
+// Records some touch/gesture event specific details (e.g. what gestures are
+// targetted to which components etc.)
+class GESTURE_DETECTION_EXPORT GestureTouchUMAHistogram {
+ public:
+ GestureTouchUMAHistogram();
+ ~GestureTouchUMAHistogram();
+
+ static void RecordGestureEvent(const ui::GestureEventData& gesture);
+ void RecordTouchEvent(const ui::MotionEvent& event);
+
+ private:
+ static UMAEventType UMAEventTypeFromEvent(const GestureEventData& gesture);
+
+ // The first finger's press time.
+ base::TimeTicks start_time_;
+ // The first finger's press location.
+ gfx::Point start_touch_position_;
+ // The maximum distance the first touch point travelled from its starting
+ // location in pixels.
+ float max_distance_from_start_squared_;
+ bool is_single_finger_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
diff --git a/ui/events/gesture_detection/motion_event.cc b/ui/events/gesture_detection/motion_event.cc
new file mode 100644
index 0000000..71a6912
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event.cc
@@ -0,0 +1,93 @@
+// 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/motion_event.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+size_t MotionEvent::GetHistorySize() const {
+ return 0;
+}
+
+base::TimeTicks MotionEvent::GetHistoricalEventTime(
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return base::TimeTicks();
+}
+
+float MotionEvent::GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float MotionEvent::GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+float MotionEvent::GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0.f;
+}
+
+int MotionEvent::FindPointerIndexOfId(int id) const {
+ const size_t pointer_count = GetPointerCount();
+ for (size_t i = 0; i < pointer_count; ++i) {
+ if (GetPointerId(i) == id)
+ return static_cast<int>(i);
+ }
+ return -1;
+}
+
+bool operator==(const MotionEvent& lhs, const MotionEvent& rhs) {
+ if (lhs.GetId() != rhs.GetId() || lhs.GetAction() != rhs.GetAction() ||
+ lhs.GetActionIndex() != rhs.GetActionIndex() ||
+ lhs.GetPointerCount() != rhs.GetPointerCount() ||
+ lhs.GetButtonState() != rhs.GetButtonState() ||
+ lhs.GetEventTime() != rhs.GetEventTime() ||
+ lhs.GetHistorySize() != rhs.GetHistorySize())
+ return false;
+
+ for (size_t i = 0; i < lhs.GetPointerCount(); ++i) {
+ int rhsi = rhs.FindPointerIndexOfId(lhs.GetPointerId(i));
+ if (rhsi == -1)
+ return false;
+
+ if (lhs.GetX(i) != rhs.GetX(rhsi) || lhs.GetY(i) != rhs.GetY(rhsi) ||
+ lhs.GetRawX(i) != rhs.GetRawX(rhsi) ||
+ lhs.GetRawY(i) != rhs.GetRawY(rhsi) ||
+ lhs.GetTouchMajor(i) != rhs.GetTouchMajor(rhsi) ||
+ lhs.GetTouchMinor(i) != rhs.GetTouchMinor(rhsi) ||
+ lhs.GetOrientation(i) != rhs.GetOrientation(rhsi) ||
+ lhs.GetPressure(i) != rhs.GetPressure(rhsi) ||
+ lhs.GetToolType(i) != rhs.GetToolType(rhsi))
+ return false;
+
+ for (size_t h = 0; h < lhs.GetHistorySize(); ++h) {
+ if (lhs.GetHistoricalX(i, h) != rhs.GetHistoricalX(rhsi, h) ||
+ lhs.GetHistoricalY(i, h) != rhs.GetHistoricalY(rhsi, h) ||
+ lhs.GetHistoricalTouchMajor(i, h) !=
+ rhs.GetHistoricalTouchMajor(rhsi, h))
+ return false;
+ }
+ }
+
+ for (size_t h = 0; h < lhs.GetHistorySize(); ++h) {
+ if (lhs.GetHistoricalEventTime(h) != rhs.GetHistoricalEventTime(h))
+ return false;
+ }
+
+ return true;
+}
+
+bool operator!=(const MotionEvent& lhs, const MotionEvent& rhs) {
+ return !(lhs == rhs);
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/motion_event.h b/ui/events/gesture_detection/motion_event.h
new file mode 100644
index 0000000..bb17ef1
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event.h
@@ -0,0 +1,110 @@
+// 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,
+ };
+
+ enum ToolType {
+ TOOL_TYPE_UNKNOWN,
+ TOOL_TYPE_FINGER,
+ TOOL_TYPE_STYLUS,
+ TOOL_TYPE_MOUSE,
+ TOOL_TYPE_ERASER
+ };
+
+ enum ButtonType {
+ BUTTON_PRIMARY = 1 << 0,
+ BUTTON_SECONDARY = 1 << 1,
+ BUTTON_TERTIARY = 1 << 2,
+ BUTTON_BACK = 1 << 3,
+ BUTTON_FORWARD = 1 << 4,
+ };
+
+ // The implementer promises that |GetPointerId()| will never exceed
+ // MAX_POINTER_ID.
+ enum { MAX_POINTER_ID = 31, MAX_TOUCH_POINT_COUNT = 12 };
+
+ 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 GetTouchMinor(size_t pointer_index) const = 0;
+ virtual float GetOrientation(size_t pointer_index) const = 0;
+ virtual float GetPressure(size_t pointer_index) const = 0;
+ virtual ToolType GetToolType(size_t pointer_index) const = 0;
+ virtual int GetButtonState() const = 0;
+ virtual int GetFlags() const = 0;
+ virtual base::TimeTicks GetEventTime() const = 0;
+
+ // Optional historical data, default implementation provides an empty history.
+ virtual size_t GetHistorySize() const;
+ virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const;
+ virtual float GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const;
+ virtual float GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const;
+ virtual float GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const;
+
+ virtual scoped_ptr<MotionEvent> Clone() const = 0;
+ virtual scoped_ptr<MotionEvent> Cancel() const = 0;
+
+ 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 GetTouchMinor() const { return GetTouchMinor(0); }
+
+ // Returns the orientation of the major axis clockwise from vertical, in
+ // radians. The return value lies in [-PI/2, PI/2].
+ float GetOrientation() const { return GetOrientation(0); }
+
+ float GetPressure() const { return GetPressure(0); }
+ ToolType GetToolType() const { return GetToolType(0); }
+
+ // O(N) search of pointers (use sparingly!). Returns -1 if |id| nonexistent.
+ int FindPointerIndexOfId(int id) const;
+};
+
+GESTURE_DETECTION_EXPORT bool operator==(const MotionEvent& lhs,
+ const MotionEvent& rhs);
+GESTURE_DETECTION_EXPORT bool operator!=(const MotionEvent& lhs,
+ const MotionEvent& rhs);
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_
diff --git a/ui/events/gesture_detection/motion_event_buffer.cc b/ui/events/gesture_detection/motion_event_buffer.cc
new file mode 100644
index 0000000..e8e0eb1
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer.cc
@@ -0,0 +1,464 @@
+// 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/motion_event_buffer.h"
+
+#include "base/debug/trace_event.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+
+namespace ui {
+namespace {
+
+// Latency added during resampling. A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+const int kResampleLatencyMs = 5;
+
+// Minimum time difference between consecutive samples before attempting to
+// resample.
+const int kResampleMinDeltaMs = 2;
+
+// Maximum time to predict forward from the last known state, to avoid
+// predicting too far into the future. This time is further bounded by 50% of
+// the last time delta.
+const int kResampleMaxPredictionMs = 8;
+
+typedef ScopedVector<MotionEvent> MotionEventVector;
+
+float Lerp(float a, float b, float alpha) {
+ return a + alpha * (b - a);
+}
+
+bool CanAddSample(const MotionEvent& event0, const MotionEvent& event1) {
+ DCHECK_EQ(event0.GetAction(), MotionEvent::ACTION_MOVE);
+ if (event1.GetAction() != MotionEvent::ACTION_MOVE)
+ return false;
+
+ const size_t pointer_count = event0.GetPointerCount();
+ if (pointer_count != event1.GetPointerCount())
+ return false;
+
+ for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
+ const int id = event0.GetPointerId(event0_i);
+ const int event1_i = event1.FindPointerIndexOfId(id);
+ if (event1_i == -1)
+ return false;
+ if (event0.GetToolType(event0_i) != event1.GetToolType(event1_i))
+ return false;
+ }
+
+ return true;
+}
+
+bool ShouldResampleTool(MotionEvent::ToolType tool) {
+ return tool == MotionEvent::TOOL_TYPE_UNKNOWN ||
+ tool == MotionEvent::TOOL_TYPE_FINGER;
+}
+
+size_t CountSamplesNoLaterThan(const MotionEventVector& batch,
+ base::TimeTicks time) {
+ size_t count = 0;
+ while (count < batch.size() && batch[count]->GetEventTime() <= time)
+ ++count;
+ return count;
+}
+
+MotionEventVector ConsumeSamplesNoLaterThan(MotionEventVector* batch,
+ base::TimeTicks time) {
+ DCHECK(batch);
+ size_t count = CountSamplesNoLaterThan(*batch, time);
+ DCHECK_GE(batch->size(), count);
+ if (count == 0)
+ return MotionEventVector();
+
+ if (count == batch->size())
+ return batch->Pass();
+
+ // TODO(jdduke): Use a ScopedDeque to work around this mess.
+ MotionEventVector unconsumed_batch;
+ unconsumed_batch.insert(
+ unconsumed_batch.begin(), batch->begin() + count, batch->end());
+ batch->weak_erase(batch->begin() + count, batch->end());
+
+ unconsumed_batch.swap(*batch);
+ DCHECK_GE(unconsumed_batch.size(), 1U);
+ return unconsumed_batch.Pass();
+}
+
+PointerProperties PointerFromMotionEvent(const MotionEvent& event,
+ size_t pointer_index) {
+ PointerProperties result;
+ result.id = event.GetPointerId(pointer_index);
+ result.tool_type = event.GetToolType(pointer_index);
+ result.x = event.GetX(pointer_index);
+ result.y = event.GetY(pointer_index);
+ result.raw_x = event.GetRawX(pointer_index);
+ result.raw_y = event.GetRawY(pointer_index);
+ result.pressure = event.GetPressure(pointer_index);
+ result.touch_major = event.GetTouchMajor(pointer_index);
+ result.touch_minor = event.GetTouchMinor(pointer_index);
+ result.orientation = event.GetOrientation(pointer_index);
+ return result;
+}
+
+PointerProperties ResamplePointer(const MotionEvent& event0,
+ const MotionEvent& event1,
+ size_t event0_pointer_index,
+ size_t event1_pointer_index,
+ float alpha) {
+ DCHECK_EQ(event0.GetPointerId(event0_pointer_index),
+ event1.GetPointerId(event1_pointer_index));
+ // If the tool should not be resampled, use the latest event in the valid
+ // horizon (i.e., the event no later than the time interpolated by alpha).
+ if (!ShouldResampleTool(event0.GetToolType(event0_pointer_index))) {
+ if (alpha > 1)
+ return PointerFromMotionEvent(event1, event1_pointer_index);
+ else
+ return PointerFromMotionEvent(event0, event0_pointer_index);
+ }
+
+ PointerProperties p(PointerFromMotionEvent(event0, event0_pointer_index));
+ p.x = Lerp(p.x, event1.GetX(event1_pointer_index), alpha);
+ p.y = Lerp(p.y, event1.GetY(event1_pointer_index), alpha);
+ p.raw_x = Lerp(p.raw_x, event1.GetRawX(event1_pointer_index), alpha);
+ p.raw_y = Lerp(p.raw_y, event1.GetRawY(event1_pointer_index), alpha);
+ return p;
+}
+
+scoped_ptr<MotionEvent> ResampleMotionEvent(const MotionEvent& event0,
+ const MotionEvent& event1,
+ base::TimeTicks resample_time) {
+ DCHECK_EQ(MotionEvent::ACTION_MOVE, event0.GetAction());
+ DCHECK_EQ(event0.GetPointerCount(), event1.GetPointerCount());
+
+ const base::TimeTicks time0 = event0.GetEventTime();
+ const base::TimeTicks time1 = event1.GetEventTime();
+ DCHECK(time0 < time1);
+ DCHECK(time0 <= resample_time);
+
+ const float alpha = (resample_time - time0).InMillisecondsF() /
+ (time1 - time0).InMillisecondsF();
+
+ scoped_ptr<MotionEventGeneric> event;
+ const size_t pointer_count = event0.GetPointerCount();
+ DCHECK_EQ(pointer_count, event1.GetPointerCount());
+ for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
+ int event1_i = event1.FindPointerIndexOfId(event0.GetPointerId(event0_i));
+ DCHECK_NE(event1_i, -1);
+ PointerProperties pointer = ResamplePointer(
+ event0, event1, event0_i, static_cast<size_t>(event1_i), alpha);
+
+ if (event0_i == 0) {
+ event.reset(new MotionEventGeneric(
+ MotionEvent::ACTION_MOVE, resample_time, pointer));
+ } else {
+ event->PushPointer(pointer);
+ }
+ }
+
+ DCHECK(event);
+ event->set_id(event0.GetId());
+ event->set_action_index(event0.GetActionIndex());
+ event->set_button_state(event0.GetButtonState());
+
+ return event.PassAs<MotionEvent>();
+}
+
+// MotionEvent implementation for storing multiple events, with the most
+// recent event used as the base event, and prior events used as the history.
+class CompoundMotionEvent : public ui::MotionEvent {
+ public:
+ explicit CompoundMotionEvent(MotionEventVector events)
+ : events_(events.Pass()) {
+ DCHECK_GE(events_.size(), 1U);
+ }
+ virtual ~CompoundMotionEvent() {}
+
+ virtual int GetId() const OVERRIDE { return latest().GetId(); }
+
+ virtual Action GetAction() const OVERRIDE { return latest().GetAction(); }
+
+ virtual int GetActionIndex() const OVERRIDE {
+ return latest().GetActionIndex();
+ }
+
+ virtual size_t GetPointerCount() const OVERRIDE {
+ return latest().GetPointerCount();
+ }
+
+ virtual int GetPointerId(size_t pointer_index) const OVERRIDE {
+ return latest().GetPointerId(pointer_index);
+ }
+
+ virtual float GetX(size_t pointer_index) const OVERRIDE {
+ return latest().GetX(pointer_index);
+ }
+
+ virtual float GetY(size_t pointer_index) const OVERRIDE {
+ return latest().GetY(pointer_index);
+ }
+
+ virtual float GetRawX(size_t pointer_index) const OVERRIDE {
+ return latest().GetRawX(pointer_index);
+ }
+
+ virtual float GetRawY(size_t pointer_index) const OVERRIDE {
+ return latest().GetRawY(pointer_index);
+ }
+
+ virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE {
+ return latest().GetTouchMajor(pointer_index);
+ }
+
+ virtual float GetTouchMinor(size_t pointer_index) const OVERRIDE {
+ return latest().GetTouchMinor(pointer_index);
+ }
+
+ virtual float GetOrientation(size_t pointer_index) const OVERRIDE {
+ return latest().GetOrientation(pointer_index);
+ }
+
+ virtual float GetPressure(size_t pointer_index) const OVERRIDE {
+ return latest().GetPressure(pointer_index);
+ }
+
+ virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE {
+ return latest().GetToolType(pointer_index);
+ }
+
+ virtual int GetButtonState() const OVERRIDE {
+ return latest().GetButtonState();
+ }
+
+ virtual int GetFlags() const OVERRIDE { return latest().GetFlags(); }
+
+ virtual base::TimeTicks GetEventTime() const OVERRIDE {
+ return latest().GetEventTime();
+ }
+
+ virtual size_t GetHistorySize() const OVERRIDE { return events_.size() - 1; }
+
+ virtual base::TimeTicks GetHistoricalEventTime(
+ size_t historical_index) const OVERRIDE {
+ DCHECK_LT(historical_index, GetHistorySize());
+ return events_[historical_index]->GetEventTime();
+ }
+
+ virtual float GetHistoricalTouchMajor(
+ size_t pointer_index,
+ size_t historical_index) const OVERRIDE {
+ DCHECK_LT(historical_index, GetHistorySize());
+ return events_[historical_index]->GetTouchMajor();
+ }
+
+ virtual float GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const OVERRIDE {
+ DCHECK_LT(historical_index, GetHistorySize());
+ return events_[historical_index]->GetX(pointer_index);
+ }
+
+ virtual float GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const OVERRIDE {
+ DCHECK_LT(historical_index, GetHistorySize());
+ return events_[historical_index]->GetY(pointer_index);
+ }
+
+ virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE {
+ MotionEventVector cloned_events;
+ cloned_events.reserve(events_.size());
+ for (size_t i = 0; i < events_.size(); ++i)
+ cloned_events.push_back(events_[i]->Clone().release());
+ return scoped_ptr<MotionEvent>(
+ new CompoundMotionEvent(cloned_events.Pass()));
+ }
+
+ virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE {
+ return latest().Cancel();
+ }
+
+ // Returns the new, resampled event, or NULL if none was created.
+ // TODO(jdduke): Revisit resampling to handle cases where alternating frames
+ // are resampled or resampling is otherwise inconsistent, e.g., a 90hz input
+ // and 60hz frame signal could phase-align such that even frames yield an
+ // extrapolated event and odd frames are not resampled, crbug.com/399381.
+ const MotionEvent* TryResample(base::TimeTicks resample_time,
+ const ui::MotionEvent* next) {
+ DCHECK_EQ(GetAction(), ACTION_MOVE);
+ const ui::MotionEvent* event0 = NULL;
+ const ui::MotionEvent* event1 = NULL;
+ if (next) {
+ DCHECK(resample_time < next->GetEventTime());
+ // Interpolate between current sample and future sample.
+ event0 = events_.back();
+ event1 = next;
+ } else if (events_.size() >= 2) {
+ // Extrapolate future sample using current sample and past sample.
+ event0 = events_[events_.size() - 2];
+ event1 = events_[events_.size() - 1];
+
+ const base::TimeTicks time1 = event1->GetEventTime();
+ base::TimeTicks max_predict =
+ time1 +
+ std::min((event1->GetEventTime() - event0->GetEventTime()) / 2,
+ base::TimeDelta::FromMilliseconds(kResampleMaxPredictionMs));
+ if (resample_time > max_predict) {
+ TRACE_EVENT_INSTANT2("input",
+ "MotionEventBuffer::TryResample prediction adjust",
+ TRACE_EVENT_SCOPE_THREAD,
+ "original(ms)",
+ (resample_time - time1).InMilliseconds(),
+ "adjusted(ms)",
+ (max_predict - time1).InMilliseconds());
+ resample_time = max_predict;
+ }
+ } else {
+ TRACE_EVENT_INSTANT0("input",
+ "MotionEventBuffer::TryResample insufficient data",
+ TRACE_EVENT_SCOPE_THREAD);
+ return NULL;
+ }
+
+ DCHECK(event0);
+ DCHECK(event1);
+ const base::TimeTicks time0 = event0->GetEventTime();
+ const base::TimeTicks time1 = event1->GetEventTime();
+ base::TimeDelta delta = time1 - time0;
+ if (delta < base::TimeDelta::FromMilliseconds(kResampleMinDeltaMs)) {
+ TRACE_EVENT_INSTANT1("input",
+ "MotionEventBuffer::TryResample failure",
+ TRACE_EVENT_SCOPE_THREAD,
+ "event_delta_too_small(ms)",
+ delta.InMilliseconds());
+ return NULL;
+ }
+
+ events_.push_back(
+ ResampleMotionEvent(*event0, *event1, resample_time).release());
+ return events_.back();
+ }
+
+ size_t samples() const { return events_.size(); }
+
+ private:
+ const MotionEvent& latest() const { return *events_.back(); }
+
+ // Events are in order from oldest to newest.
+ MotionEventVector events_;
+
+ DISALLOW_COPY_AND_ASSIGN(CompoundMotionEvent);
+};
+
+} // namespace
+
+MotionEventBuffer::MotionEventBuffer(MotionEventBufferClient* client,
+ bool enable_resampling)
+ : client_(client), resample_(enable_resampling) {
+}
+
+MotionEventBuffer::~MotionEventBuffer() {
+}
+
+void MotionEventBuffer::OnMotionEvent(const MotionEvent& event) {
+ if (event.GetAction() != MotionEvent::ACTION_MOVE) {
+ last_extrapolated_event_time_ = base::TimeTicks();
+ if (!buffered_events_.empty())
+ FlushWithoutResampling(buffered_events_.Pass());
+ client_->ForwardMotionEvent(event);
+ return;
+ }
+
+ // Guard against events that are *older* than the last one that may have been
+ // artificially synthesized.
+ if (!last_extrapolated_event_time_.is_null()) {
+ DCHECK(buffered_events_.empty());
+ if (event.GetEventTime() < last_extrapolated_event_time_)
+ return;
+ last_extrapolated_event_time_ = base::TimeTicks();
+ }
+
+ scoped_ptr<MotionEvent> clone = event.Clone();
+ if (buffered_events_.empty()) {
+ buffered_events_.push_back(clone.release());
+ client_->SetNeedsFlush();
+ return;
+ }
+
+ if (CanAddSample(*buffered_events_.front(), *clone)) {
+ DCHECK(buffered_events_.back()->GetEventTime() <= clone->GetEventTime());
+ } else {
+ FlushWithoutResampling(buffered_events_.Pass());
+ }
+
+ buffered_events_.push_back(clone.release());
+ // No need to request another flush as the first event will have requested it.
+}
+
+void MotionEventBuffer::Flush(base::TimeTicks frame_time) {
+ if (buffered_events_.empty())
+ return;
+
+ // Shifting the sample time back slightly minimizes the potential for
+ // misprediction when extrapolating events.
+ if (resample_)
+ frame_time -= base::TimeDelta::FromMilliseconds(kResampleLatencyMs);
+
+ // TODO(jdduke): Use a persistent MotionEventVector vector for temporary
+ // storage.
+ MotionEventVector events(
+ ConsumeSamplesNoLaterThan(&buffered_events_, frame_time));
+ if (events.empty()) {
+ DCHECK(!buffered_events_.empty());
+ client_->SetNeedsFlush();
+ return;
+ }
+
+ if (!resample_ || (events.size() == 1 && buffered_events_.empty())) {
+ FlushWithoutResampling(events.Pass());
+ if (!buffered_events_.empty())
+ client_->SetNeedsFlush();
+ return;
+ }
+
+ CompoundMotionEvent resampled_event(events.Pass());
+ base::TimeTicks original_event_time = resampled_event.GetEventTime();
+ const MotionEvent* next_event =
+ !buffered_events_.empty() ? buffered_events_.front() : NULL;
+
+ // Try to interpolate/extrapolate a new event at |frame_time|. Note that
+ // |new_event|, if non-NULL, is owned by |resampled_event_|.
+ const MotionEvent* new_event =
+ resampled_event.TryResample(frame_time, next_event);
+
+ // Log the extrapolated event time, guarding against subsequently queued
+ // events that might have an earlier timestamp.
+ if (!next_event && new_event &&
+ new_event->GetEventTime() > original_event_time) {
+ last_extrapolated_event_time_ = new_event->GetEventTime();
+ } else {
+ last_extrapolated_event_time_ = base::TimeTicks();
+ }
+
+ client_->ForwardMotionEvent(resampled_event);
+ if (!buffered_events_.empty())
+ client_->SetNeedsFlush();
+}
+
+void MotionEventBuffer::FlushWithoutResampling(MotionEventVector events) {
+ last_extrapolated_event_time_ = base::TimeTicks();
+ if (events.empty())
+ return;
+
+ if (events.size() == 1) {
+ // Avoid CompoundEvent creation to prevent unnecessary allocations.
+ scoped_ptr<MotionEvent> event(events.front());
+ events.weak_clear();
+ client_->ForwardMotionEvent(*event);
+ return;
+ }
+
+ CompoundMotionEvent compound_event(events.Pass());
+ client_->ForwardMotionEvent(compound_event);
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_buffer.h b/ui/events/gesture_detection/motion_event_buffer.h
new file mode 100644
index 0000000..498d553
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer.h
@@ -0,0 +1,79 @@
+// 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_BUFFER_H_
+#define UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_BUFFER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Allows event forwarding and flush requests from a |MotionEventBuffer|.
+class MotionEventBufferClient {
+ public:
+ virtual ~MotionEventBufferClient() {}
+ virtual void ForwardMotionEvent(const MotionEvent& event) = 0;
+ virtual void SetNeedsFlush() = 0;
+};
+
+// Utility class for buffering streamed MotionEventVector until a given flush.
+// Events that can be combined will remain buffered, and depending on the flush
+// time and buffered events, a resampled event with history will be synthesized.
+// The primary purpose of this class is to ensure a smooth output motion signal
+// by resampling a discrete input signal that may run on a different frequency
+// or lack alignment with the output display signal.
+// Note that this class is largely based on code from Android's existing touch
+// pipeline (in particular, logic from ImageTransport, http://goo.gl/Ixsb0D).
+// See the design doc at http://goo.gl/MdmpCf for more details.
+class GESTURE_DETECTION_EXPORT MotionEventBuffer {
+ public:
+ // The provided |client| must not be null, and |enable_resampling| determines
+ // resampling behavior (see |resample_|).
+ MotionEventBuffer(MotionEventBufferClient* client, bool enable_resampling);
+ ~MotionEventBuffer();
+
+ // Should be called upon receipt of an event from the platform, prior to event
+ // dispatch to UI or content components. Events that can be coalesced will
+ // remain buffered until the next |Flush()|, while other events will be
+ // forwarded immediately (incidentally flushing currently buffered events).
+ void OnMotionEvent(const MotionEvent& event);
+
+ // Forward any buffered events, resampling if necessary (see |resample_|)
+ // according to the provided |frame_time|. This should be called in response
+ // to |SetNeedsFlush()| calls on the client. If the buffer is empty, no
+ // events will be forwarded, and if another flush is necessary it will be
+ // requested.
+ void Flush(base::TimeTicks frame_time);
+
+ private:
+ typedef ScopedVector<MotionEvent> MotionEventVector;
+
+ void FlushWithoutResampling(MotionEventVector events);
+
+ MotionEventBufferClient* const client_;
+ MotionEventVector buffered_events_;
+
+ // Time of the most recently extrapolated event. This will be 0 if the
+ // last sent event was not extrapolated. Used internally to guard against
+ // conflicts between events received from the platfrom that may have an
+ // earlier timestamp than that synthesized at the latest resample.
+ base::TimeTicks last_extrapolated_event_time_;
+
+ // Whether buffered events should be resampled upon |Flush()|. If true, short
+ // horizon interpolation/extrapolation will be used to synthesize the
+ // forwarded event. Otherwise the most recently buffered event will be
+ // forwarded, with preceding events as historical entries. Defaults to true.
+ bool resample_;
+
+ DISALLOW_COPY_AND_ASSIGN(MotionEventBuffer);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_BUFFER_H_
diff --git a/ui/events/gesture_detection/motion_event_buffer_unittest.cc b/ui/events/gesture_detection/motion_event_buffer_unittest.cc
new file mode 100644
index 0000000..f51897f
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer_unittest.cc
@@ -0,0 +1,871 @@
+// 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/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/motion_event_buffer.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+namespace ui {
+
+const int kSmallDeltaMs = 1;
+const int kLargeDeltaMs = 50;
+const int kResampleDeltaMs = 5;
+const float kVelocityEpsilon = 0.01f;
+const float kDeltaEpsilon = 0.1f;
+
+#define EXPECT_EVENT_EQ(A, B) \
+ { \
+ SCOPED_TRACE(testing::Message()); \
+ ExpectEquals((A), (B)); \
+ }
+#define EXPECT_EVENT_IGNORING_HISTORY_EQ(A, B) \
+ { \
+ SCOPED_TRACE(testing::Message()); \
+ ExpectEqualsIgnoringHistory((A), (B)); \
+ }
+#define EXPECT_EVENT_HISTORY_EQ(A, I, B) \
+ { \
+ SCOPED_TRACE(testing::Message()); \
+ ExpectEqualsHistoryIndex((A), (I), (B)); \
+ }
+
+class MotionEventBufferTest : public testing::Test,
+ public MotionEventBufferClient {
+ public:
+ MotionEventBufferTest() : needs_flush_(false) {}
+ virtual ~MotionEventBufferTest() {}
+
+ // MotionEventBufferClient implementation.
+ virtual void ForwardMotionEvent(const MotionEvent& event) OVERRIDE {
+ forwarded_events_.push_back(event.Clone().release());
+ }
+
+ virtual void SetNeedsFlush() OVERRIDE { needs_flush_ = true; }
+
+ bool GetAndResetNeedsFlush() {
+ bool needs_flush = needs_flush_;
+ needs_flush_ = false;
+ return needs_flush;
+ }
+
+ ScopedVector<MotionEvent> GetAndResetForwardedEvents() {
+ ScopedVector<MotionEvent> forwarded_events;
+ forwarded_events.swap(forwarded_events_);
+ return forwarded_events.Pass();
+ }
+
+ const MotionEvent* GetLastEvent() const {
+ return forwarded_events_.empty() ? NULL : forwarded_events_.back();
+ }
+
+ static base::TimeDelta LargeDelta() {
+ return base::TimeDelta::FromMilliseconds(kLargeDeltaMs);
+ }
+
+ static base::TimeDelta SmallDelta() {
+ return base::TimeDelta::FromMilliseconds(kSmallDeltaMs);
+ }
+
+ static base::TimeDelta ResampleDelta() {
+ return base::TimeDelta::FromMilliseconds(kResampleDeltaMs);
+ }
+
+ static void ExpectEqualsImpl(const MotionEvent& a,
+ const MotionEvent& b,
+ bool ignore_history) {
+ EXPECT_EQ(a.GetId(), b.GetId());
+ EXPECT_EQ(a.GetAction(), b.GetAction());
+ EXPECT_EQ(a.GetActionIndex(), b.GetActionIndex());
+ EXPECT_EQ(a.GetButtonState(), b.GetButtonState());
+ EXPECT_EQ(a.GetEventTime(), b.GetEventTime());
+
+ ASSERT_EQ(a.GetPointerCount(), b.GetPointerCount());
+ for (size_t i = 0; i < a.GetPointerCount(); ++i) {
+ int bi = b.FindPointerIndexOfId(a.GetPointerId(i));
+ ASSERT_NE(bi, -1);
+ EXPECT_EQ(a.GetX(i), b.GetX(bi));
+ EXPECT_EQ(a.GetY(i), b.GetY(bi));
+ EXPECT_EQ(a.GetRawX(i), b.GetRawX(bi));
+ EXPECT_EQ(a.GetRawY(i), b.GetRawY(bi));
+ EXPECT_EQ(a.GetTouchMajor(i), b.GetTouchMajor(bi));
+ EXPECT_EQ(a.GetTouchMinor(i), b.GetTouchMinor(bi));
+ EXPECT_EQ(a.GetOrientation(i), b.GetOrientation(bi));
+ EXPECT_EQ(a.GetPressure(i), b.GetPressure(bi));
+ EXPECT_EQ(a.GetToolType(i), b.GetToolType(bi));
+ }
+
+ if (ignore_history)
+ return;
+
+ ASSERT_EQ(a.GetHistorySize(), b.GetHistorySize());
+ for (size_t h = 0; h < a.GetHistorySize(); ++h)
+ ExpectEqualsHistoryIndex(a, h, b);
+ }
+
+ // Verify that all public data of |a|, excluding history, equals that of |b|.
+ static void ExpectEqualsIgnoringHistory(const MotionEvent& a,
+ const MotionEvent& b) {
+ const bool ignore_history = true;
+ ExpectEqualsImpl(a, b, ignore_history);
+ }
+
+ // Verify that all public data of |a| equals that of |b|.
+ static void ExpectEquals(const MotionEvent& a, const MotionEvent& b) {
+ const bool ignore_history = false;
+ ExpectEqualsImpl(a, b, ignore_history);
+ }
+
+ // Verify that the historical data of |a| given by |historical_index|
+ // corresponds to the *raw* data of |b|.
+ static void ExpectEqualsHistoryIndex(const MotionEvent& a,
+ size_t history_index,
+ const MotionEvent& b) {
+ ASSERT_LT(history_index, a.GetHistorySize());
+ EXPECT_EQ(a.GetPointerCount(), b.GetPointerCount());
+ EXPECT_TRUE(a.GetHistoricalEventTime(history_index) == b.GetEventTime());
+
+ for (size_t i = 0; i < a.GetPointerCount(); ++i) {
+ int bi = b.FindPointerIndexOfId(a.GetPointerId(i));
+ ASSERT_NE(bi, -1);
+ EXPECT_EQ(a.GetHistoricalX(i, history_index), b.GetX(bi));
+ EXPECT_EQ(a.GetHistoricalY(i, history_index), b.GetY(bi));
+ EXPECT_EQ(a.GetHistoricalTouchMajor(i, history_index),
+ b.GetTouchMajor(bi));
+ }
+ }
+
+ protected:
+ void RunResample(base::TimeDelta flush_time_delta,
+ base::TimeDelta event_time_delta) {
+ for (base::TimeDelta offset; offset < event_time_delta;
+ offset += event_time_delta / 3) {
+ SCOPED_TRACE(testing::Message()
+ << "Resample(offset="
+ << static_cast<int>(offset.InMilliseconds()) << "ms)");
+ RunResample(flush_time_delta, event_time_delta, offset);
+ }
+ }
+
+ // Given an event and flush sampling frequency, inject a stream of events,
+ // flushing at appropriate points in the stream. Verify that the continuous
+ // velocity sampled by the *input* stream matches the discrete velocity
+ // as computed from the resampled *output* stream.
+ void RunResample(base::TimeDelta flush_time_delta,
+ base::TimeDelta event_time_delta,
+ base::TimeDelta event_time_offset) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ base::TimeTicks flush_time =
+ event_time + flush_time_delta - event_time_offset;
+ base::TimeTicks max_event_time =
+ event_time + base::TimeDelta::FromSecondsD(0.5f);
+ const size_t min_expected_events =
+ static_cast<size_t>((max_event_time - flush_time) /
+ std::max(event_time_delta, flush_time_delta));
+
+ MotionEventBuffer buffer(this, true);
+
+ gfx::Vector2dF velocity(33.f, -11.f);
+ gfx::PointF position(17.f, 42.f);
+ scoped_ptr<MotionEvent> last_flushed_event;
+ size_t events = 0;
+ float last_dx = 0, last_dy = 0;
+ base::TimeDelta last_dt;
+ while (event_time < max_event_time) {
+ position += gfx::ScaleVector2d(velocity, event_time_delta.InSecondsF());
+ MockMotionEvent move(
+ MotionEvent::ACTION_MOVE, event_time, position.x(), position.y());
+ buffer.OnMotionEvent(move);
+ event_time += event_time_delta;
+
+ while (flush_time < event_time) {
+ buffer.Flush(flush_time);
+ flush_time += flush_time_delta;
+ const MotionEvent* current_flushed_event = GetLastEvent();
+ if (current_flushed_event) {
+ if (!last_flushed_event) {
+ last_flushed_event = current_flushed_event->Clone();
+ continue;
+ }
+
+ base::TimeDelta dt = current_flushed_event->GetEventTime() -
+ last_flushed_event->GetEventTime();
+ EXPECT_GE(dt.ToInternalValue(), 0);
+ // A time delta of 0 is possible if the flush rate is greater than the
+ // event rate, in which case we can simply skip forward.
+ if (dt == base::TimeDelta())
+ continue;
+
+ const float dx =
+ current_flushed_event->GetX() - last_flushed_event->GetX();
+ const float dy =
+ current_flushed_event->GetY() - last_flushed_event->GetY();
+ const float dt_s = (current_flushed_event->GetEventTime() -
+ last_flushed_event->GetEventTime()).InSecondsF();
+
+ // The discrete velocity should mirror the constant velocity.
+ EXPECT_NEAR(velocity.x(), dx / dt_s, kVelocityEpsilon);
+ EXPECT_NEAR(velocity.y(), dy / dt_s, kVelocityEpsilon);
+
+ // The impulse delta for each frame should remain constant.
+ if (last_dy)
+ EXPECT_NEAR(dx, last_dx, kDeltaEpsilon);
+ if (last_dy)
+ EXPECT_NEAR(dy, last_dy, kDeltaEpsilon);
+
+ // The timestamp delta should remain constant.
+ if (last_dt != base::TimeDelta())
+ EXPECT_TRUE((dt - last_dt).InMillisecondsF() < kDeltaEpsilon);
+
+ last_dx = dx;
+ last_dy = dy;
+ last_dt = dt;
+ last_flushed_event = current_flushed_event->Clone();
+ events += GetAndResetForwardedEvents().size();
+ }
+ }
+ }
+ events += GetAndResetForwardedEvents().size();
+ EXPECT_GE(events, min_expected_events);
+ }
+
+ private:
+ ScopedVector<MotionEvent> forwarded_events_;
+ bool needs_flush_;
+};
+
+TEST_F(MotionEventBufferTest, BufferEmpty) {
+ MotionEventBuffer buffer(this, true);
+
+ buffer.Flush(base::TimeTicks::Now());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, BufferWithOneMoveNotResampled) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move(MotionEvent::ACTION_MOVE, event_time, 4.f, 4.f);
+ buffer.OnMotionEvent(move);
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ buffer.Flush(event_time + ResampleDelta());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move, *GetLastEvent());
+ EXPECT_EQ(1U, GetAndResetForwardedEvents().size());
+}
+
+TEST_F(MotionEventBufferTest, BufferFlushedOnNonActionMove) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(move0);
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // The second move should remain buffered.
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+ buffer.OnMotionEvent(move1);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ // The third move should remain buffered.
+ MockMotionEvent move2(MotionEvent::ACTION_MOVE, event_time, 3.f, 3.f);
+ buffer.OnMotionEvent(move2);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ // The up should flush the buffer.
+ MockMotionEvent up(MotionEvent::ACTION_UP, event_time, 4.f, 4.f);
+ buffer.OnMotionEvent(up);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // The flushed events should include the up and the moves, with the latter
+ // combined into a single event with history.
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(2U, events.size());
+ EXPECT_EVENT_EQ(up, *events.back());
+ EXPECT_EQ(2U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move2);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, BufferFlushedOnIncompatibleActionMove) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(move0);
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // The second move has a different pointer count, flushing the first.
+ MockMotionEvent move1(
+ MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f, 3.f, 3.f);
+ buffer.OnMotionEvent(move1);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move0, *GetLastEvent());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // The third move has differing tool types, flushing the second.
+ MockMotionEvent move2(move1);
+ move2.SetToolType(0, MotionEvent::TOOL_TYPE_STYLUS);
+ buffer.OnMotionEvent(move2);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ EXPECT_EVENT_EQ(move1, *GetLastEvent());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // The flushed event should only include the latest move event.
+ buffer.Flush(event_time);
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(3U, events.size());
+ EXPECT_EVENT_EQ(move2, *events.back());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // Events with different pointer ids should not combine.
+ PointerProperties pointer0(5.f, 5.f);
+ pointer0.id = 1;
+ PointerProperties pointer1(10.f, 10.f);
+ pointer1.id = 2;
+ MotionEventGeneric move3(MotionEvent::ACTION_MOVE, event_time, pointer0);
+ move3.PushPointer(pointer1);
+ buffer.OnMotionEvent(move3);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ MotionEventGeneric move4(MotionEvent::ACTION_MOVE, event_time, pointer0);
+ pointer1.id = 7;
+ move4.PushPointer(pointer1);
+ buffer.OnMotionEvent(move2);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move3, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, OnlyActionMoveBuffered) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent down(MotionEvent::ACTION_DOWN, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(down);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(down, *GetLastEvent());
+
+ GetAndResetForwardedEvents();
+
+ MockMotionEvent up(MotionEvent::ACTION_UP, event_time, 2.f, 2.f);
+ buffer.OnMotionEvent(up);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(up, *GetLastEvent());
+
+ GetAndResetForwardedEvents();
+
+ MockMotionEvent cancel(MotionEvent::ACTION_CANCEL, event_time, 3.f, 3.f);
+ buffer.OnMotionEvent(cancel);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(cancel, *GetLastEvent());
+
+ GetAndResetForwardedEvents();
+
+ MockMotionEvent move(MotionEvent::ACTION_MOVE, event_time, 4.f, 4.f);
+ buffer.OnMotionEvent(move);
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ EXPECT_FALSE(GetLastEvent());
+
+ base::TimeTicks flush_time = move.GetEventTime() + ResampleDelta();
+ buffer.Flush(flush_time);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, OutOfOrderPointersBuffered) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ PointerProperties p0(1.f, 2.f);
+ p0.id = 1;
+ PointerProperties p1(2.f, 1.f);
+ p1.id = 2;
+
+ MotionEventGeneric move0(MotionEvent::ACTION_MOVE, event_time, p0);
+ move0.PushPointer(p1);
+ buffer.OnMotionEvent(move0);
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ ASSERT_FALSE(GetLastEvent());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+
+ // The second move should remain buffered even if the logical pointers are
+ // in a different order.
+ MotionEventGeneric move1(MotionEvent::ACTION_MOVE, event_time, p1);
+ move1.PushPointer(p0);
+ buffer.OnMotionEvent(move1);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_FALSE(GetLastEvent());
+
+ // As the two events are logically the same but for ordering and time, the
+ // synthesized event should yield a logically identical event.
+ base::TimeTicks flush_time = move1.GetEventTime() + ResampleDelta();
+ buffer.Flush(flush_time);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(move1, *events.front());
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, FlushedEventsNeverLaterThanFlushTime) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += LargeDelta();
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // A flush occurring too early should not forward any events.
+ base::TimeTicks flush_time = move0.GetEventTime() - ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // With resampling enabled, a flush occurring before the resample
+ // offset should not forward any events.
+ flush_time = move0.GetEventTime();
+ buffer.Flush(flush_time);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // Only the first event should get flushed, as the flush timestamp precedes
+ // the second's timestamp by a sufficient amount (preventing interpolation).
+ flush_time = move0.GetEventTime() + ResampleDelta();
+ buffer.Flush(flush_time);
+
+ // There should only be one flushed event.
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_TRUE(GetLastEvent()->GetEventTime() <= flush_time);
+ GetAndResetForwardedEvents();
+
+ // Flushing again with a similar timestamp should have no effect other than
+ // triggering another flush request.
+ flush_time += base::TimeDelta::FromMilliseconds(1);
+ buffer.Flush(flush_time);
+ EXPECT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // Flushing after the second move's time should trigger forwarding.
+ flush_time = move1.GetEventTime() + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move1, *GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWhenDisabled) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ const bool resampling_enabled = false;
+ MotionEventBuffer buffer(this, resampling_enabled);
+
+ // Queue two events.
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 15.f, 30.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Flush at a time between the first and second events.
+ base::TimeTicks interpolated_time =
+ move0.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+ base::TimeTicks flush_time = interpolated_time;
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the second remaining buffered
+ // and no resampling having occurred.
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EVENT_EQ(move0, *events.front());
+
+ // The second move should be flushed without resampling.
+ flush_time = move1.GetEventTime();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move1, *GetLastEvent());
+ GetAndResetForwardedEvents();
+
+ // Now queue two more events.
+ move0 = MockMotionEvent(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += base::TimeDelta::FromMilliseconds(5);
+ move1 = MockMotionEvent(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Sample at a time beyond the first and second events.
+ flush_time =
+ move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the first event in the history
+ // and the second event as the actual event data (no resampling).
+ events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(1U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithOutOfOrderActionMove) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += base::TimeDelta::FromMilliseconds(10);
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Sample at a time beyond the first and second events.
+ base::TimeTicks extrapolated_time =
+ move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+ base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the event extrapolated from
+ // the two events.
+ base::TimeTicks expected_time =
+ move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+ ScopedVector<MotionEvent> events0 = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events0.size());
+ EXPECT_EQ(2U, events0.front()->GetHistorySize());
+ EXPECT_EQ(expected_time, events0.front()->GetEventTime());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Try enqueuing an event *after* the second event but *before* the
+ // extrapolated event. It should be dropped.
+ event_time = move1.GetEventTime() + base::TimeDelta::FromMilliseconds(1);
+ MockMotionEvent move2(MotionEvent::ACTION_MOVE, event_time, 15.f, 25.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Finally queue an event *after* the extrapolated event.
+ event_time = expected_time + base::TimeDelta::FromMilliseconds(1);
+ MockMotionEvent move3(MotionEvent::ACTION_MOVE, event_time, 15.f, 25.f);
+ buffer.OnMotionEvent(move3);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The flushed event should simply be the latest event.
+ flush_time = event_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ ScopedVector<MotionEvent> events1 = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events1.size());
+ EXPECT_EVENT_EQ(move3, *events1.front());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithSmallTimeDeltaBetweenMoves) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ // The first move should be buffered.
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += SmallDelta();
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ base::TimeTicks flush_time = event_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, and no resampling should have
+ // occured between the first and the second as they were temporally too close.
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(1U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithMismatchBetweenMoves) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ // The first move should be buffered.
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += SmallDelta();
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ base::TimeTicks flush_time = event_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, and no resampling should have
+ // occured between the first and the second as they were temporally too close.
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(1U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, Interpolation) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += base::TimeDelta::FromMilliseconds(5);
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 15.f, 30.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Sample at a time between the first and second events.
+ base::TimeTicks interpolated_time =
+ move0.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 3;
+ base::TimeTicks flush_time = interpolated_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the event interpolated between
+ // the two events. The second event should remain buffered.
+ float alpha = (interpolated_time - move0.GetEventTime()).InMillisecondsF() /
+ (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+ MockMotionEvent interpolated_event(
+ MotionEvent::ACTION_MOVE,
+ interpolated_time,
+ move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * alpha,
+ move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * alpha);
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(1U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), interpolated_event);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+
+ // The second move should be flushed without resampling.
+ flush_time = move1.GetEventTime() + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_EVENT_EQ(move1, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, Extrapolation) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += base::TimeDelta::FromMilliseconds(5);
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Sample at a time beyond the first and second events.
+ base::TimeTicks extrapolated_time =
+ move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+ base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the event extrapolated from
+ // the two events. The first and second events should be in the history.
+ // Note that the maximum extrapolation is limited by *half* of the time delta
+ // between the two events, hence we divide the relative delta by 2 in
+ // determining the extrapolated event.
+ base::TimeTicks expected_time =
+ move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+ float expected_alpha =
+ (expected_time - move0.GetEventTime()).InMillisecondsF() /
+ (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+ MockMotionEvent extrapolated_event(
+ MotionEvent::ACTION_MOVE,
+ expected_time,
+ move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * expected_alpha,
+ move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * expected_alpha);
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(2U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), extrapolated_event);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, ExtrapolationHorizonLimited) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventBuffer buffer(this, true);
+
+ MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+ buffer.OnMotionEvent(move0);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_TRUE(GetAndResetNeedsFlush());
+
+ // The second move should remain buffered.
+ event_time += base::TimeDelta::FromMilliseconds(24);
+ MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+ buffer.OnMotionEvent(move1);
+ ASSERT_FALSE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // Sample at a time beyond the first and second events.
+ base::TimeTicks extrapolated_time =
+ event_time + base::TimeDelta::FromMilliseconds(24);
+ base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+ buffer.Flush(flush_time);
+ ASSERT_TRUE(GetLastEvent());
+ EXPECT_FALSE(GetAndResetNeedsFlush());
+
+ // There should only be one flushed event, with the event extrapolated from
+ // the two events. The first and second events should be in the history.
+ // Note that the maximum extrapolation is limited by 8 ms.
+ base::TimeTicks expected_time =
+ move1.GetEventTime() + base::TimeDelta::FromMilliseconds(8);
+ float expected_alpha =
+ (expected_time - move0.GetEventTime()).InMillisecondsF() /
+ (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+ MockMotionEvent extrapolated_event(
+ MotionEvent::ACTION_MOVE,
+ expected_time,
+ move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * expected_alpha,
+ move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * expected_alpha);
+ ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(2U, events.front()->GetHistorySize());
+ EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), extrapolated_event);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+ EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, ResamplingWithReorderedPointers) {
+
+}
+
+TEST_F(MotionEventBufferTest, Resampling30to60) {
+ base::TimeDelta flush_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+ base::TimeDelta event_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 30.);
+
+ RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling60to60) {
+ base::TimeDelta flush_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+ base::TimeDelta event_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+
+ RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling100to60) {
+ base::TimeDelta flush_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+ base::TimeDelta event_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 100.);
+
+ RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling120to60) {
+ base::TimeDelta flush_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+ base::TimeDelta event_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 120.);
+
+ RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling150to60) {
+ base::TimeDelta flush_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 60.);
+ base::TimeDelta event_time_delta =
+ base::TimeDelta::FromMillisecondsD(1000. / 150.);
+
+ RunResample(flush_time_delta, event_time_delta);
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_generic.cc b/ui/events/gesture_detection/motion_event_generic.cc
new file mode 100644
index 0000000..afda52a
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic.cc
@@ -0,0 +1,168 @@
+// 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/motion_event_generic.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+PointerProperties::PointerProperties()
+ : id(0),
+ tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+ x(0),
+ y(0),
+ raw_x(0),
+ raw_y(0),
+ pressure(0),
+ touch_major(0),
+ touch_minor(0),
+ orientation(0) {
+}
+
+PointerProperties::PointerProperties(float x, float y)
+ : id(0),
+ tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+ x(x),
+ y(y),
+ raw_x(x),
+ raw_y(y),
+ pressure(0),
+ touch_major(0),
+ touch_minor(0),
+ orientation(0) {
+}
+
+MotionEventGeneric::MotionEventGeneric()
+ : action_(ACTION_CANCEL),
+ id_(0),
+ action_index_(0),
+ button_state_(0),
+ flags_(0) {
+}
+
+MotionEventGeneric::MotionEventGeneric(Action action,
+ base::TimeTicks event_time,
+ const PointerProperties& pointer)
+ : action_(action),
+ event_time_(event_time),
+ id_(0),
+ action_index_(0),
+ button_state_(0),
+ flags_(0) {
+ PushPointer(pointer);
+}
+
+MotionEventGeneric::MotionEventGeneric(const MotionEventGeneric& other)
+ : action_(other.action_),
+ event_time_(other.event_time_),
+ id_(other.id_),
+ action_index_(other.action_index_),
+ button_state_(other.button_state_),
+ flags_(other.flags_),
+ pointers_(other.pointers_) {
+}
+
+MotionEventGeneric::~MotionEventGeneric() {
+}
+
+int MotionEventGeneric::GetId() const {
+ return id_;
+}
+
+MotionEvent::Action MotionEventGeneric::GetAction() const {
+ return action_;
+}
+
+int MotionEventGeneric::GetActionIndex() const {
+ return action_index_;
+}
+
+size_t MotionEventGeneric::GetPointerCount() const {
+ return pointers_->size();
+}
+
+int MotionEventGeneric::GetPointerId(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].id;
+}
+
+float MotionEventGeneric::GetX(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].x;
+}
+
+float MotionEventGeneric::GetY(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].y;
+}
+
+float MotionEventGeneric::GetRawX(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].raw_x;
+}
+
+float MotionEventGeneric::GetRawY(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].raw_y;
+}
+
+float MotionEventGeneric::GetTouchMajor(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].touch_major;
+}
+
+float MotionEventGeneric::GetTouchMinor(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].touch_minor;
+}
+
+float MotionEventGeneric::GetOrientation(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].orientation;
+}
+
+float MotionEventGeneric::GetPressure(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].pressure;
+}
+
+MotionEvent::ToolType MotionEventGeneric::GetToolType(
+ size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointers_->size());
+ return pointers_[pointer_index].tool_type;
+}
+
+int MotionEventGeneric::GetButtonState() const {
+ return button_state_;
+}
+
+int MotionEventGeneric::GetFlags() const {
+ return flags_;
+}
+
+base::TimeTicks MotionEventGeneric::GetEventTime() const {
+ return event_time_;
+}
+
+scoped_ptr<MotionEvent> MotionEventGeneric::Clone() const {
+ return scoped_ptr<MotionEvent>(new MotionEventGeneric(*this));
+}
+
+scoped_ptr<MotionEvent> MotionEventGeneric::Cancel() const {
+ scoped_ptr<MotionEventGeneric> event(new MotionEventGeneric(*this));
+ event->set_action(ACTION_CANCEL);
+ return event.PassAs<MotionEvent>();
+}
+
+void MotionEventGeneric::PushPointer(const PointerProperties& pointer) {
+ pointers_->push_back(pointer);
+}
+
+void MotionEventGeneric::PopPointer() {
+ DCHECK_GT(pointers_->size(), 0U);
+ pointers_->pop_back();
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_generic.h b/ui/events/gesture_detection/motion_event_generic.h
new file mode 100644
index 0000000..2750cb2
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic.h
@@ -0,0 +1,95 @@
+// 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_GENERIC_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_GENERIC_H_
+
+#include "base/basictypes.h"
+#include "base/containers/stack_container.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+struct GESTURE_DETECTION_EXPORT PointerProperties {
+ PointerProperties();
+ PointerProperties(float x, float y);
+
+ int id;
+ MotionEvent::ToolType tool_type;
+ float x;
+ float y;
+ float raw_x;
+ float raw_y;
+ float pressure;
+ float touch_major;
+ float touch_minor;
+ float orientation;
+};
+
+// A generic MotionEvent implementation.
+class GESTURE_DETECTION_EXPORT MotionEventGeneric : public MotionEvent {
+ public:
+ MotionEventGeneric(Action action,
+ base::TimeTicks event_time,
+ const PointerProperties& pointer);
+ MotionEventGeneric(const MotionEventGeneric& other);
+
+ virtual ~MotionEventGeneric();
+
+ // 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 GetTouchMinor(size_t pointer_index) const OVERRIDE;
+ virtual float GetOrientation(size_t pointer_index) const OVERRIDE;
+ virtual float GetPressure(size_t pointer_index) const OVERRIDE;
+ virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE;
+ virtual int GetButtonState() const OVERRIDE;
+ virtual int GetFlags() const OVERRIDE;
+ virtual base::TimeTicks GetEventTime() const OVERRIDE;
+ virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+ virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+ void PushPointer(const PointerProperties& pointer);
+
+ void set_action(Action action) { action_ = action; }
+ void set_event_time(base::TimeTicks event_time) { event_time_ = event_time; }
+ void set_id(int id) { id_ = id; }
+ void set_action_index(int action_index) { action_index_ = action_index; }
+ void set_button_state(int button_state) { button_state_ = button_state; }
+ void set_flags(int flags) { flags_ = flags; }
+
+ protected:
+ MotionEventGeneric();
+
+ void PopPointer();
+
+ PointerProperties& pointer(size_t index) { return pointers_[index]; }
+ const PointerProperties& pointer(size_t index) const {
+ return pointers_[index];
+ }
+
+ private:
+ enum { kTypicalMaxPointerCount = 5 };
+
+ Action action_;
+ base::TimeTicks event_time_;
+ int id_;
+ int action_index_;
+ int button_state_;
+ int flags_;
+ base::StackVector<PointerProperties, kTypicalMaxPointerCount> pointers_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_GENERIC_H_
diff --git a/ui/events/gesture_detection/motion_event_generic_unittest.cc b/ui/events/gesture_detection/motion_event_generic_unittest.cc
new file mode 100644
index 0000000..f1aa293
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic_unittest.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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+
+namespace ui {
+
+TEST(MotionEventGenericTest, Basic) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ MotionEventGeneric event(
+ MotionEvent::ACTION_DOWN, event_time, PointerProperties());
+ EXPECT_EQ(1U, event.GetPointerCount());
+ EXPECT_EQ(0U, event.GetHistorySize());
+ EXPECT_EQ(event_time, event.GetEventTime());
+
+ event.PushPointer(PointerProperties(8.3f, 4.7f));
+ ASSERT_EQ(2U, event.GetPointerCount());
+ EXPECT_EQ(8.3f, event.GetX(1));
+ EXPECT_EQ(4.7f, event.GetY(1));
+
+ event.PushPointer(PointerProperties(2.3f, -3.7f));
+ ASSERT_EQ(3U, event.GetPointerCount());
+ EXPECT_EQ(2.3f, event.GetX(2));
+ EXPECT_EQ(-3.7f, event.GetY(2));
+
+ event.set_id(1);
+ EXPECT_EQ(1, event.GetId());
+
+ event.set_action(MotionEvent::ACTION_POINTER_DOWN);
+ EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction());
+
+ event_time += base::TimeDelta::FromMilliseconds(5);
+ event.set_event_time(event_time);
+ EXPECT_EQ(event_time, event.GetEventTime());
+
+ event.set_button_state(MotionEvent::BUTTON_PRIMARY);
+ EXPECT_EQ(MotionEvent::BUTTON_PRIMARY, event.GetButtonState());
+
+ event.set_flags(EF_ALT_DOWN | EF_SHIFT_DOWN);
+ EXPECT_EQ(EF_ALT_DOWN | EF_SHIFT_DOWN, event.GetFlags());
+
+ event.set_action_index(1);
+ EXPECT_EQ(1, event.GetActionIndex());
+}
+
+TEST(MotionEventGenericTest, Clone) {
+ MotionEventGeneric event(MotionEvent::ACTION_DOWN,
+ base::TimeTicks::Now(),
+ PointerProperties(8.3f, 4.7f));
+ event.set_id(1);
+ event.set_button_state(MotionEvent::BUTTON_PRIMARY);
+
+ scoped_ptr<MotionEvent> clone = event.Clone();
+ ASSERT_TRUE(clone);
+ EXPECT_EQ(event, *clone);
+}
+
+TEST(MotionEventGenericTest, Cancel) {
+ MotionEventGeneric event(MotionEvent::ACTION_UP,
+ base::TimeTicks::Now(),
+ PointerProperties(8.7f, 4.3f));
+ event.set_id(2);
+ event.set_button_state(MotionEvent::BUTTON_SECONDARY);
+
+ scoped_ptr<MotionEvent> cancel = event.Cancel();
+ event.set_action(MotionEvent::ACTION_CANCEL);
+ ASSERT_TRUE(cancel);
+ EXPECT_EQ(event, *cancel);
+}
+
+TEST(MotionEventGenericTest, FindPointerIndexOfId) {
+ base::TimeTicks event_time = base::TimeTicks::Now();
+ PointerProperties pointer;
+ pointer.id = 0;
+ MotionEventGeneric event0(MotionEvent::ACTION_DOWN, event_time, pointer);
+ EXPECT_EQ(0, event0.FindPointerIndexOfId(0));
+ EXPECT_EQ(-1, event0.FindPointerIndexOfId(1));
+ EXPECT_EQ(-1, event0.FindPointerIndexOfId(-1));
+
+ MotionEventGeneric event1(event0);
+ pointer.id = 7;
+ event1.PushPointer(pointer);
+ EXPECT_EQ(0, event1.FindPointerIndexOfId(0));
+ EXPECT_EQ(1, event1.FindPointerIndexOfId(7));
+ EXPECT_EQ(-1, event1.FindPointerIndexOfId(6));
+ EXPECT_EQ(-1, event1.FindPointerIndexOfId(1));
+
+ MotionEventGeneric event2(event1);
+ pointer.id = 3;
+ event2.PushPointer(pointer);
+ EXPECT_EQ(0, event2.FindPointerIndexOfId(0));
+ EXPECT_EQ(1, event2.FindPointerIndexOfId(7));
+ EXPECT_EQ(2, event2.FindPointerIndexOfId(3));
+ EXPECT_EQ(-1, event2.FindPointerIndexOfId(1));
+ EXPECT_EQ(-1, event2.FindPointerIndexOfId(2));
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/scale_gesture_detector.cc b/ui/events/gesture_detection/scale_gesture_detector.cc
new file mode 100644
index 0000000..7afe91f
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_detector.cc
@@ -0,0 +1,354 @@
+// 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"
+#include "ui/events/gesture_detection/scale_gesture_listeners.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()
+ : span_slop(16),
+ min_scaling_touch_major(48),
+ min_scaling_span(200),
+ min_pinch_update_span_delta(0) {
+}
+
+ScaleGestureDetector::Config::~Config() {}
+
+ScaleGestureDetector::ScaleGestureDetector(const Config& config,
+ ScaleGestureListener* listener)
+ : listener_(listener),
+ focus_x_(0),
+ focus_y_(0),
+ 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),
+ touch_max_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.span_slop + kSlopEpsilon;
+ touch_min_major_ = config.min_scaling_touch_major;
+ touch_max_major_ = std::min(config.min_scaling_span / std::sqrt(2.f),
+ 2.f * touch_min_major_);
+ min_span_ = config.min_scaling_span + kSlopEpsilon;
+ ResetTouchHistory();
+}
+
+ScaleGestureDetector::~ScaleGestureDetector() {}
+
+bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
+ curr_time_ = event.GetEventTime();
+
+ const int action = event.GetAction();
+
+ 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_radius = touch_history_last_accepted_ / 2;
+
+ const float dev_x = dev_sum_x * inverse_unreleased_point_count + touch_radius;
+ const float dev_y = dev_sum_y * inverse_unreleased_point_count + touch_radius;
+
+ // 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 &&
+ (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;
+}
+
+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_;
+ if (major > touch_max_major_)
+ major = touch_max_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/ui/events/gesture_detection/scale_gesture_detector.h b/ui/events/gesture_detection/scale_gesture_detector.h
new file mode 100644
index 0000000..d45ca4e
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_detector.h
@@ -0,0 +1,125 @@
+// 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/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+class ScaleGestureListener;
+
+// 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 GESTURE_DETECTION_EXPORT ScaleGestureDetector {
+ public:
+ struct GESTURE_DETECTION_EXPORT Config {
+ Config();
+ ~Config();
+
+ // Distance the current span can deviate from the initial span before
+ // scaling will start (in dips). The span is the diameter of the circle with
+ // a radius of average pointer deviation from the focal point.
+ float span_slop;
+
+ // 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;
+
+ // Minimum pinch span change before pinch occurs (in dips). See
+ // crbug.com/373318.
+ float min_pinch_update_span_delta;
+ };
+
+ 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);
+
+ // This method may be called by the owner when a a double-tap event has been
+ // detected *for the same event stream* being fed to this instance of the
+ // ScaleGestureDetector. As call order is important here, the double-tap
+ // detector should always be offered events *before* the ScaleGestureDetector.
+ bool OnDoubleTap(const MotionEvent& event);
+
+ // Set whether the associated |ScaleGestureListener| should receive
+ // OnScale callbacks when the user performs a doubletap followed by a swipe.
+ 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 };
+
+ // 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_;
+
+ float focus_x_;
+ float focus_y_;
+ 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 touch_max_major_;
+ float double_tap_focus_x_;
+ float double_tap_focus_y_;
+ DoubleTapMode double_tap_mode_;
+
+ bool event_before_or_above_starting_gesture_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScaleGestureDetector);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
diff --git a/ui/events/gesture_detection/scale_gesture_listeners.cc b/ui/events/gesture_detection/scale_gesture_listeners.cc
new file mode 100644
index 0000000..98c957a
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_listeners.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/gesture_detection/scale_gesture_listeners.h"
+
+namespace ui {
+
+bool SimpleScaleGestureListener::OnScale(const ScaleGestureDetector&,
+ const MotionEvent&) {
+ return false;
+}
+
+bool SimpleScaleGestureListener::OnScaleBegin(const ScaleGestureDetector&,
+ const MotionEvent&) {
+ return true;
+}
+
+void SimpleScaleGestureListener::OnScaleEnd(const ScaleGestureDetector&,
+ const MotionEvent&) {
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/scale_gesture_listeners.h b/ui/events/gesture_detection/scale_gesture_listeners.h
new file mode 100644
index 0000000..583ef7e
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_listeners.h
@@ -0,0 +1,47 @@
+// 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_LISTENERS_H_
+#define UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_LISTENERS_H_
+
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+class ScaleGestureDetector;
+
+// Client through which |ScaleGestureDetector| signals scale detection.
+class GESTURE_DETECTION_EXPORT 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 GESTURE_DETECTION_EXPORT 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;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_LISTENERS_H_
diff --git a/ui/events/gesture_detection/snap_scroll_controller.cc b/ui/events/gesture_detection/snap_scroll_controller.cc
new file mode 100644
index 0000000..bde5a35
--- /dev/null
+++ b/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/ui/events/gesture_detection/snap_scroll_controller.h b/ui/events/gesture_detection/snap_scroll_controller.h
new file mode 100644
index 0000000..753c2bc
--- /dev/null
+++ b/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/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
new file mode 100644
index 0000000..fcb92d4
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
@@ -0,0 +1,427 @@
+// 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,
+ MotionEvent::ToolType primary_tool_type,
+ const GestureEventDataPacket& packet) {
+ // As the event is purely synthetic, we needn't be strict with event flags.
+ int flags = EF_NONE;
+ return GestureEventData(GestureEventDetails(type),
+ motion_event_id,
+ primary_tool_type,
+ 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()),
+ flags);
+}
+
+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);
+ 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),
+ ending_event_motion_event_id_(0),
+ ending_event_primary_tool_type_(MotionEvent::TOOL_TYPE_UNKNOWN),
+ 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);
+ }
+ int gesture_end_index = -1;
+ 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);
+ // We should not have a timeout gesture and other gestures in the same
+ // packet.
+ DCHECK_EQ(1U, packet.gesture_count());
+ return;
+ }
+ // Occasionally scroll or tap cancel events are synthesized when a touch
+ // sequence has been canceled or terminated, we want to make sure that
+ // ET_GESTURE_END always happens after them.
+ if (gesture.type() == ET_GESTURE_END) {
+ // Make sure there is at most one ET_GESTURE_END event in each packet.
+ DCHECK_EQ(-1, gesture_end_index);
+ gesture_end_index = static_cast<int>(i);
+ continue;
+ }
+ 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);
+ }
+ // Always send the ET_GESTURE_END event as the last one for every touch event.
+ if (gesture_end_index >= 0)
+ SendGesture(packet.gesture(gesture_end_index), 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;
+ ending_event_primary_tool_type_ = event.primary_tool_type;
+ 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;
+ ending_event_primary_tool_type_ = event.primary_tool_type;
+ 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;
+ ending_event_primary_tool_type_ = event.primary_tool_type;
+ 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_,
+ ending_event_primary_tool_type_,
+ 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_,
+ ending_event_primary_tool_type_,
+ 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_,
+ ending_event_primary_tool_type_,
+ 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/ui/events/gesture_detection/touch_disposition_gesture_filter.h b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
new file mode 100644
index 0000000..290afaa
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
@@ -0,0 +1,109 @@
+// 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_;
+ MotionEvent::ToolType ending_event_primary_tool_type_;
+ 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/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
new file mode 100644
index 0000000..7e99c94
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
@@ -0,0 +1,1153 @@
+// 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/touch_disposition_gesture_filter.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const int kDefaultEventFlags = EF_ALT_DOWN | EF_SHIFT_DOWN;
+
+} // namespace
+
+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));
+ touch_event_.set_flags(kDefaultEventFlags);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ queue_.reset();
+ }
+
+ // TouchDispositionGestureFilterClient
+ virtual void ForwardGestureEvent(const GestureEventData& event) OVERRIDE {
+ ++sent_gesture_count_;
+ last_sent_gesture_.reset(new GestureEventData(event));
+ sent_gestures_.push_back(event.type());
+ if (event.type() == ET_GESTURE_SHOW_PRESS)
+ show_press_bounding_box_ = event.details.bounding_box();
+ if (cancel_after_next_gesture_) {
+ cancel_after_next_gesture_ = false;
+ CancelTouchPoint();
+ SendTouchNotConsumedAck();
+ }
+ }
+
+ 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_.set_event_time(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 PushGesture(EventType type, float x, float y, float diameter) {
+ pending_gesture_packet_.Push(CreateGesture(type, x, y, diameter));
+ }
+
+ void PressTouchPoint(int x, int y) {
+ touch_event_.PressPoint(x, y);
+ touch_event_.SetRawOffset(raw_offset_.x(), raw_offset_.y());
+ SendTouchGestures();
+ }
+
+ void MoveTouchPoint(size_t index, int x, int y) {
+ touch_event_.MovePoint(index, x, y);
+ touch_event_.SetRawOffset(raw_offset_.x(), raw_offset_.y());
+ SendTouchGestures();
+ }
+
+ void ReleaseTouchPoint() {
+ touch_event_.ReleasePoint();
+ SendTouchGestures();
+ }
+
+ void CancelTouchPoint() {
+ touch_event_.CancelPoint();
+ SendTouchGestures();
+ }
+
+ void SetRawTouchOffset(const gfx::Vector2dF& raw_offset) {
+ raw_offset_ = raw_offset;
+ }
+
+ void ResetTouchPoints() { touch_event_ = MockMotionEvent(); }
+
+ bool GesturesSent() const { return !sent_gestures_.empty(); }
+
+ base::TimeTicks LastSentGestureTime() const {
+ CHECK(last_sent_gesture_);
+ 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;
+ }
+
+ gfx::PointF LastSentGestureLocation() const {
+ CHECK(last_sent_gesture_);
+ return gfx::PointF(last_sent_gesture_->x, last_sent_gesture_->y);
+ }
+
+ gfx::PointF LastSentGestureRawLocation() const {
+ CHECK(last_sent_gesture_);
+ return gfx::PointF(last_sent_gesture_->raw_x, last_sent_gesture_->raw_y);
+ }
+
+ int LastSentGestureFlags() const {
+ CHECK(last_sent_gesture_);
+ return last_sent_gesture_->flags;
+ }
+
+ const gfx::RectF& ShowPressBoundingBox() const {
+ return show_press_bounding_box_;
+ }
+
+ void SetCancelAfterNextGesture(bool cancel_after_next_gesture) {
+ cancel_after_next_gesture_ = cancel_after_next_gesture;
+ }
+
+ GestureEventData CreateGesture(EventType type) {
+ return CreateGesture(type, 0, 0, 0);
+ }
+
+ GestureEventData CreateGesture(EventType type,
+ float x,
+ float y,
+ float diameter) {
+ return GestureEventData(
+ GestureEventDetails(type),
+ 0,
+ MotionEvent::TOOL_TYPE_FINGER,
+ base::TimeTicks(),
+ touch_event_.GetX(0),
+ touch_event_.GetY(0),
+ touch_event_.GetRawX(0),
+ touch_event_.GetRawY(0),
+ 1,
+ gfx::RectF(x - diameter / 2, y - diameter / 2, diameter, diameter),
+ kDefaultEventFlags);
+ }
+
+ private:
+ scoped_ptr<TouchDispositionGestureFilter> queue_;
+ bool cancel_after_next_gesture_;
+ MockMotionEvent touch_event_;
+ GestureEventDataPacket pending_gesture_packet_;
+ size_t sent_gesture_count_;
+ GestureList sent_gestures_;
+ gfx::Vector2dF raw_offset_;
+ scoped_ptr<GestureEventData> last_sent_gesture_;
+ gfx::RectF show_press_bounding_box_;
+};
+
+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, ET_GESTURE_SCROLL_BEGIN),
+ GetAndResetSentGestures()));
+ EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+}
+
+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_SCROLL_END, ET_GESTURE_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_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);
+ SendTouchConsumedAck();
+ 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()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ShowPressBoundingBox) {
+ PushGesture(ET_GESTURE_TAP_DOWN, 9, 9, 8);
+ PressTouchPoint(9, 9);
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(
+ GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures()));
+
+ PushGesture(ET_GESTURE_TAP, 10, 10, 10);
+ ReleaseTouchPoint();
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP),
+ GetAndResetSentGestures()));
+ EXPECT_EQ(gfx::RectF(5, 5, 10, 10), ShowPressBoundingBox());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelledBeforeGestureEnd) {
+ PushGesture(ET_GESTURE_BEGIN);
+ PushGesture(ET_GESTURE_TAP_DOWN);
+ PressTouchPoint(1, 1);
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, 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_END);
+ CancelTouchPoint();
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, ET_GESTURE_END),
+ GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, EventFlagPropagation) {
+ // Real gestures should propagate flags from their causal touches.
+ PushGesture(ET_GESTURE_TAP_DOWN);
+ PressTouchPoint(1, 1);
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(
+ GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures()));
+ EXPECT_EQ(kDefaultEventFlags, LastSentGestureFlags());
+
+ // Synthetic gestures lack flags.
+ PressTouchPoint(1, 1);
+ SendTouchNotConsumedAck();
+ EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL),
+ GetAndResetSentGestures()));
+ EXPECT_EQ(0, LastSentGestureFlags());
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_detection/velocity_tracker.cc b/ui/events/gesture_detection/velocity_tracker.cc
new file mode 100644
index 0000000..16454e8
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker.cc
@@ -0,0 +1,818 @@
+// 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 between ACTION_MOVE events 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 kAssumePointerMoveStoppedTimeMs = 40;
+
+// Threshold between ACTION_MOVE and ACTION_{UP|POINTER_UP} events for
+// determining that a pointer has stopped moving. This is a larger threshold
+// than |kAssumePointerMoveStoppedTimeMs|, as some devices may delay synthesis
+// of ACTION_{UP|POINTER_UP} to reduce risk of noisy release.
+const int kAssumePointerUpStoppedTimeMs = 80;
+
+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(kAssumePointerMoveStoppedTimeMs)) {
+ // 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;
+ case MotionEvent::ACTION_UP:
+ case MotionEvent::ACTION_POINTER_UP:
+ // 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. However, if the up event itself is delayed because of
+ // (difficult albeit possible) prolonged stationary screen contact, assume
+ // that motion has stopped.
+ if ((event.GetEventTime() - last_event_time_) >=
+ base::TimeDelta::FromMilliseconds(kAssumePointerUpStoppedTimeMs))
+ strategy_->Clear();
+ return;
+ 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.
+ 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/ui/events/gesture_detection/velocity_tracker.h b/ui/events/gesture_detection/velocity_tracker.h
new file mode 100644
index 0000000..8147695
--- /dev/null
+++ b/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/ui/events/gesture_detection/velocity_tracker_state.cc b/ui/events/gesture_detection/velocity_tracker_state.cc
new file mode 100644
index 0000000..1216856
--- /dev/null
+++ b/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/ui/events/gesture_detection/velocity_tracker_state.h b/ui/events/gesture_detection/velocity_tracker_state.h
new file mode 100644
index 0000000..780871c
--- /dev/null
+++ b/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/ui/events/gesture_detection/velocity_tracker_unittest.cc b/ui/events/gesture_detection/velocity_tracker_unittest.cc
new file mode 100644
index 0000000..9b45053
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker_unittest.cc
@@ -0,0 +1,222 @@
+// 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/velocity_tracker_state.h"
+#include "ui/events/test/mock_motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+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);
+ }
+}
+
+TEST_F(VelocityTrackerTest, DelayedActionUp) {
+ const gfx::PointF p0(0, 0);
+ const gfx::Vector2dF v(-50000, 50000);
+ const size_t samples = 10;
+ const base::TimeTicks t0 = base::TimeTicks::Now();
+ const base::TimeDelta dt = kTenMillis * 2;
+
+ VelocityTrackerState state;
+ state.AddMovement(
+ Sample(MotionEvent::ACTION_DOWN, p0, t0, v, base::TimeDelta()));
+
+ // Apply the movement and verify a (non-zero) velocity.
+ ApplyMovement(&state, p0, v, t0, dt, samples);
+ state.ComputeCurrentVelocity(1000, 1000);
+ EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
+ EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
+
+ // Apply the delayed ACTION_UP.
+ const gfx::PointF p1 = p0 + ScaleVector2d(v, dt.InSecondsF());
+ const base::TimeTicks t1 = t0 + dt + kTenMillis * 10;
+ state.AddMovement(Sample(
+ MotionEvent::ACTION_UP, p1, t1, v, base::TimeDelta()));
+
+ // The tracked velocity should have been reset.
+ state.ComputeCurrentVelocity(1000, 1000);
+ EXPECT_EQ(0.f, state.GetXVelocity(0));
+ EXPECT_EQ(0.f, state.GetYVelocity(0));
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_event_details.cc b/ui/events/gesture_event_details.cc
new file mode 100644
index 0000000..7807131
--- /dev/null
+++ b/ui/events/gesture_event_details.cc
@@ -0,0 +1,62 @@
+// 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), touch_points_(0), oldest_touch_id_(-1) {
+}
+
+GestureEventDetails::GestureEventDetails(ui::EventType type)
+ : type_(type), touch_points_(1), oldest_touch_id_(-1) {
+ DCHECK_GE(type, ET_GESTURE_TYPE_START);
+ DCHECK_LE(type, ET_GESTURE_TYPE_END);
+}
+
+GestureEventDetails::GestureEventDetails(ui::EventType type,
+ float delta_x,
+ float delta_y)
+ : type_(type), touch_points_(1), oldest_touch_id_(-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_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;
+
+ default:
+ NOTREACHED() << "Invalid event type for constructor: " << type;
+ }
+}
+
+GestureEventDetails::Details::Details() {
+ memset(this, 0, sizeof(Details));
+}
+
+} // namespace ui
diff --git a/ui/events/gesture_event_details.h b/ui/events/gesture_event_details.h
new file mode 100644
index 0000000..da7a60b
--- /dev/null
+++ b/ui/events/gesture_event_details.h
@@ -0,0 +1,187 @@
+// 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();
+ explicit GestureEventDetails(EventType type);
+ 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;
+ }
+
+ int oldest_touch_id() const { return oldest_touch_id_; }
+ void set_oldest_touch_id(int oldest_touch_id) {
+ DCHECK_GE(oldest_touch_id, 0);
+ oldest_touch_id_ = oldest_touch_id;
+ }
+
+ // 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;
+ }
+
+ void set_scale(float scale) {
+ DCHECK_GE(scale, 0.0f);
+ DCHECK_EQ(type_, ET_GESTURE_PINCH_UPDATE);
+ data.scale = scale;
+ }
+
+ 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_;
+
+ // The touch id of the oldest touch contributing to the gesture.
+ int oldest_touch_id_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_
diff --git a/ui/events/gestures/OWNERS b/ui/events/gestures/OWNERS
new file mode 100644
index 0000000..92704b0
--- /dev/null
+++ b/ui/events/gestures/OWNERS
@@ -0,0 +1,3 @@
+rjkroege@chromium.org
+sadrul@chromium.org
+tdresser@chromium.org
diff --git a/ui/events/gestures/fling_curve.cc b/ui/events/gestures/fling_curve.cc
new file mode 100644
index 0000000..6f63019
--- /dev/null
+++ b/ui/events/gestures/fling_curve.cc
@@ -0,0 +1,80 @@
+// 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/fling_curve.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace {
+
+const float kDefaultAlpha = -5.70762e+03f;
+const float kDefaultBeta = 1.72e+02f;
+const float kDefaultGamma = 3.7e+00f;
+
+inline double GetPositionAtTime(double t) {
+ return kDefaultAlpha * exp(-kDefaultGamma * t) - kDefaultBeta * t -
+ kDefaultAlpha;
+}
+
+inline double GetVelocityAtTime(double t) {
+ return -kDefaultAlpha * kDefaultGamma * exp(-kDefaultGamma * t) -
+ kDefaultBeta;
+}
+
+inline double GetTimeAtVelocity(double v) {
+ return -log((v + kDefaultBeta) / (-kDefaultAlpha * kDefaultGamma)) /
+ kDefaultGamma;
+}
+
+} // namespace
+
+namespace ui {
+
+FlingCurve::FlingCurve(const gfx::Vector2dF& velocity,
+ base::TimeTicks start_timestamp)
+ : curve_duration_(GetTimeAtVelocity(0)),
+ start_timestamp_(start_timestamp),
+ time_offset_(0),
+ position_offset_(0) {
+ float max_start_velocity = std::max(fabs(velocity.x()), fabs(velocity.y()));
+ if (max_start_velocity > GetVelocityAtTime(0))
+ max_start_velocity = GetVelocityAtTime(0);
+ CHECK_GT(max_start_velocity, 0);
+
+ displacement_ratio_ = gfx::Vector2dF(velocity.x() / max_start_velocity,
+ velocity.y() / max_start_velocity);
+ time_offset_ = GetTimeAtVelocity(max_start_velocity);
+ position_offset_ = GetPositionAtTime(time_offset_);
+ last_timestamp_ = start_timestamp_ + base::TimeDelta::FromSecondsD(
+ curve_duration_ - time_offset_);
+}
+
+FlingCurve::~FlingCurve() {
+}
+
+gfx::Vector2dF FlingCurve::GetScrollAmountAtTime(base::TimeTicks current) {
+ if (current < start_timestamp_)
+ return gfx::Vector2dF();
+
+ float displacement = 0;
+ if (current < last_timestamp_) {
+ float time = time_offset_ + (current - start_timestamp_).InSecondsF();
+ CHECK_LT(time, curve_duration_);
+ displacement = GetPositionAtTime(time) - position_offset_;
+ } else {
+ displacement = GetPositionAtTime(curve_duration_) - position_offset_;
+ }
+
+ gfx::Vector2dF scroll(displacement * displacement_ratio_.x(),
+ displacement * displacement_ratio_.y());
+ gfx::Vector2dF scroll_increment(scroll.x() - cumulative_scroll_.x(),
+ scroll.y() - cumulative_scroll_.y());
+ cumulative_scroll_ = scroll;
+ return scroll_increment;
+}
+
+} // namespace ui
diff --git a/ui/events/gestures/fling_curve.h b/ui/events/gestures/fling_curve.h
new file mode 100644
index 0000000..583e172
--- /dev/null
+++ b/ui/events/gestures/fling_curve.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_GESTURES_FLING_CURVE_H_
+#define UI_EVENTS_GESTURES_FLING_CURVE_H_
+
+#include "base/time/time.h"
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace ui {
+
+// FlingCurve can be used to scroll a UI element suitable for touch screen-based
+// flings.
+class EVENTS_BASE_EXPORT FlingCurve {
+ public:
+ FlingCurve(const gfx::Vector2dF& velocity, base::TimeTicks start_timestamp);
+ ~FlingCurve();
+
+ gfx::Vector2dF GetScrollAmountAtTime(base::TimeTicks current_timestamp);
+ base::TimeTicks start_timestamp() const { return start_timestamp_; }
+
+ private:
+ const float curve_duration_;
+ const base::TimeTicks start_timestamp_;
+
+ gfx::Vector2dF displacement_ratio_;
+ gfx::Vector2dF cumulative_scroll_;
+ base::TimeTicks last_timestamp_;
+ float time_offset_;
+ float position_offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(FlingCurve);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_FLING_CURVE_H_
diff --git a/ui/events/gestures/fling_curve_unittest.cc b/ui/events/gestures/fling_curve_unittest.cc
new file mode 100644
index 0000000..653a5ef
--- /dev/null
+++ b/ui/events/gestures/fling_curve_unittest.cc
@@ -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.
+
+#include "ui/events/gestures/fling_curve.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/frame_time.h"
+
+namespace ui {
+
+TEST(FlingCurveTest, Basic) {
+ const gfx::Vector2dF velocity(0, 5000);
+ base::TimeTicks now = gfx::FrameTime::Now();
+ FlingCurve curve(velocity, now);
+
+ gfx::Vector2dF scroll =
+ curve.GetScrollAmountAtTime(now + base::TimeDelta::FromMilliseconds(20));
+ EXPECT_EQ(0, scroll.x());
+ EXPECT_NEAR(scroll.y(), 96, 1);
+
+ scroll =
+ curve.GetScrollAmountAtTime(now + base::TimeDelta::FromMilliseconds(250));
+ EXPECT_EQ(0, scroll.x());
+ EXPECT_NEAR(scroll.y(), 705, 1);
+
+ scroll =
+ curve.GetScrollAmountAtTime(now + base::TimeDelta::FromSeconds(10));
+ EXPECT_EQ(0, scroll.x());
+ EXPECT_NEAR(scroll.y(), 392, 1);
+
+ EXPECT_TRUE(curve.GetScrollAmountAtTime(
+ now + base::TimeDelta::FromSeconds(20)).IsZero());
+}
+
+} // namespace ui
diff --git a/ui/events/gestures/gesture_configuration.cc b/ui/events/gestures/gesture_configuration.cc
new file mode 100644
index 0000000..1ce13d7
--- /dev/null
+++ b/ui/events/gestures/gesture_configuration.cc
@@ -0,0 +1,43 @@
+// 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/gestures/gesture_configuration.h"
+
+namespace ui {
+
+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;
+int GestureConfiguration::tab_scrub_activation_delay_in_ms_ = 200;
+double GestureConfiguration::long_press_time_in_seconds_ = 1.0;
+double GestureConfiguration::semi_long_press_time_in_seconds_ = 0.4;
+double GestureConfiguration::max_distance_for_two_finger_tap_in_pixels_ = 300;
+double GestureConfiguration::max_seconds_between_double_click_ = 0.7;
+double
+ GestureConfiguration::max_separation_for_gesture_touches_in_pixels_ = 150;
+float GestureConfiguration::max_swipe_deviation_angle_ = 20;
+double
+ GestureConfiguration::max_touch_down_duration_in_seconds_for_click_ = 0.8;
+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_pinch_update_distance_in_pixels_ = 5;
+float GestureConfiguration::min_scroll_velocity_ = 30.0f;
+double GestureConfiguration::min_swipe_speed_ = 20;
+
+// 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;
+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
+
+} // namespace ui
diff --git a/ui/events/gestures/gesture_configuration.h b/ui/events/gestures/gesture_configuration.h
new file mode 100644
index 0000000..1537b8e
--- /dev/null
+++ b/ui/events/gestures/gesture_configuration.h
@@ -0,0 +1,194 @@
+// 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_GESTURES_GESTURE_CONFIGURATION_H_
+#define UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+
+namespace ui {
+
+// TODO: Expand this design to support multiple OS configuration
+// approaches (windows, chrome, others). This would turn into an
+// abstract base class.
+
+class EVENTS_BASE_EXPORT GestureConfiguration {
+ public:
+ // Ordered alphabetically ignoring underscores, to align with the
+ // associated list of prefs in gesture_prefs_aura.cc.
+ static int default_radius() {
+ return default_radius_;
+ }
+ static void set_default_radius(int radius) { default_radius_ = radius; }
+ static int fling_max_cancel_to_down_time_in_ms() {
+ return fling_max_cancel_to_down_time_in_ms_;
+ }
+ static void set_fling_max_cancel_to_down_time_in_ms(int val) {
+ fling_max_cancel_to_down_time_in_ms_ = val;
+ }
+ static int fling_max_tap_gap_time_in_ms() {
+ return fling_max_tap_gap_time_in_ms_;
+ }
+ static void set_fling_max_tap_gap_time_in_ms(int val) {
+ fling_max_tap_gap_time_in_ms_ = val;
+ }
+ static double long_press_time_in_seconds() {
+ return long_press_time_in_seconds_;
+ }
+ static double semi_long_press_time_in_seconds() {
+ return semi_long_press_time_in_seconds_;
+ }
+ static double max_distance_for_two_finger_tap_in_pixels() {
+ return max_distance_for_two_finger_tap_in_pixels_;
+ }
+ static void set_max_distance_for_two_finger_tap_in_pixels(double val) {
+ max_distance_for_two_finger_tap_in_pixels_ = val;
+ }
+ static void set_long_press_time_in_seconds(double val) {
+ long_press_time_in_seconds_ = val;
+ }
+ static void set_semi_long_press_time_in_seconds(double val) {
+ semi_long_press_time_in_seconds_ = val;
+ }
+ static double max_seconds_between_double_click() {
+ return max_seconds_between_double_click_;
+ }
+ static void set_max_seconds_between_double_click(double val) {
+ max_seconds_between_double_click_ = val;
+ }
+ static int max_separation_for_gesture_touches_in_pixels() {
+ return max_separation_for_gesture_touches_in_pixels_;
+ }
+ static void set_max_separation_for_gesture_touches_in_pixels(int val) {
+ max_separation_for_gesture_touches_in_pixels_ = val;
+ }
+ static float max_swipe_deviation_angle() {
+ return max_swipe_deviation_angle_;
+ }
+ static void set_max_swipe_deviation_angle(float val) {
+ max_swipe_deviation_angle_ = val;
+ }
+ static double max_touch_down_duration_in_seconds_for_click() {
+ return max_touch_down_duration_in_seconds_for_click_;
+ }
+ static void set_max_touch_down_duration_in_seconds_for_click(double val) {
+ max_touch_down_duration_in_seconds_for_click_ = val;
+ }
+ static double max_touch_move_in_pixels_for_click() {
+ return max_touch_move_in_pixels_for_click_;
+ }
+ static void set_max_touch_move_in_pixels_for_click(double val) {
+ max_touch_move_in_pixels_for_click_ = val;
+ }
+ static double max_distance_between_taps_for_double_tap() {
+ return max_distance_between_taps_for_double_tap_;
+ }
+ static void set_max_distance_between_taps_for_double_tap(double val) {
+ max_distance_between_taps_for_double_tap_ = val;
+ }
+ static double min_distance_for_pinch_scroll_in_pixels() {
+ return min_distance_for_pinch_scroll_in_pixels_;
+ }
+ static void set_min_distance_for_pinch_scroll_in_pixels(double val) {
+ min_distance_for_pinch_scroll_in_pixels_ = val;
+ }
+ static double min_pinch_update_distance_in_pixels() {
+ return min_pinch_update_distance_in_pixels_;
+ }
+ static void set_min_pinch_update_distance_in_pixels(double val) {
+ min_pinch_update_distance_in_pixels_ = val;
+ }
+ static float min_scroll_velocity() {
+ return min_scroll_velocity_;
+ }
+ static void set_min_scroll_velocity(float val) {
+ min_scroll_velocity_ = val;
+ }
+ static double min_swipe_speed() {
+ return min_swipe_speed_;
+ }
+ static void set_min_swipe_speed(double val) {
+ min_swipe_speed_ = 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 show_press_delay_in_ms() {
+ return show_press_delay_in_ms_;
+ }
+ 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 float fling_velocity_cap() {
+ return fling_velocity_cap_;
+ }
+ static void set_fling_velocity_cap(float val) {
+ fling_velocity_cap_ = val;
+ }
+ // TODO(davemoore): Move into chrome/browser/ui.
+ static int tab_scrub_activation_delay_in_ms() {
+ return tab_scrub_activation_delay_in_ms_;
+ }
+ static void set_tab_scrub_activation_delay_in_ms(int val) {
+ tab_scrub_activation_delay_in_ms_ = val;
+ }
+
+ private:
+ // These are listed in alphabetical order ignoring underscores, to
+ // align with the associated list of preferences in
+ // gesture_prefs_aura.cc. These two lists should be kept in sync.
+
+ // The default touch radius length used when the only information given
+ // by the device is the touch center.
+ static int default_radius_;
+
+ // The maximum allowed distance between two fingers for a two finger tap. If
+ // the distance between two fingers is greater than this value, we will not
+ // recognize a two finger tap.
+ static double max_distance_for_two_finger_tap_in_pixels_;
+
+ // Maximum time between a GestureFlingCancel and a mousedown such that the
+ // mousedown is considered associated with the cancel event.
+ static int fling_max_cancel_to_down_time_in_ms_;
+
+ // Maxium time between a mousedown/mouseup pair that is considered to be a
+ // suppressable tap.
+ static int fling_max_tap_gap_time_in_ms_;
+
+ static double long_press_time_in_seconds_;
+ static double semi_long_press_time_in_seconds_;
+ static double max_seconds_between_double_click_;
+ static double max_separation_for_gesture_touches_in_pixels_;
+ static float max_swipe_deviation_angle_;
+ static double max_touch_down_duration_in_seconds_for_click_;
+ static double max_touch_move_in_pixels_for_click_;
+ static double max_distance_between_taps_for_double_tap_;
+ static double min_distance_for_pinch_scroll_in_pixels_;
+ // Only used with --compensate-for-unstable-pinch-zoom.
+ static double min_pinch_update_distance_in_pixels_;
+ static float min_scroll_velocity_;
+ static double min_swipe_speed_;
+ static int min_scaling_span_in_pixels_;
+ static int show_press_delay_in_ms_;
+ static int scroll_debounce_interval_in_ms_;
+ static float fling_velocity_cap_;
+ // TODO(davemoore): Move into chrome/browser/ui.
+ static int tab_scrub_activation_delay_in_ms_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureConfiguration);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
diff --git a/ui/events/gestures/gesture_provider_aura.cc b/ui/events/gestures/gesture_provider_aura.cc
new file mode 100644
index 0000000..a731357
--- /dev/null
+++ b/ui/events/gestures/gesture_provider_aura.cc
@@ -0,0 +1,138 @@
+// 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) {
+ int index = pointer_state_.FindPointerIndexOfId(event.touch_id());
+ bool pointer_id_is_active = index != -1;
+
+ 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;
+ }
+
+ // If this is a touchmove event, and it isn't different from the last
+ // event, ignore it.
+ if (event.type() == ET_TOUCH_MOVED &&
+ event.x() == pointer_state_.GetX(index) &&
+ event.y() == pointer_state_.GetY(index)) {
+ return false;
+ }
+
+ 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;
+ details.set_oldest_touch_id(gesture.motion_event_id);
+
+ 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.x,
+ gesture.y,
+ gesture.flags,
+ gesture.time - base::TimeTicks(),
+ details));
+
+ 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/ui/events/gestures/gesture_provider_aura.h b/ui/events/gestures/gesture_provider_aura.h
new file mode 100644
index 0000000..712b84f
--- /dev/null
+++ b/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 "base/memory/scoped_vector.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_;
+
+ 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/ui/events/gestures/gesture_provider_aura_unittest.cc b/ui/events/gestures/gesture_provider_aura_unittest.cc
new file mode 100644
index 0000000..e2b1523
--- /dev/null
+++ b/ui/events/gestures/gesture_provider_aura_unittest.cc
@@ -0,0 +1,152 @@
+// 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/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/gestures/gesture_provider_aura.h"
+
+namespace ui {
+
+class GestureProviderAuraTest : public testing::Test,
+ public GestureProviderAuraClient {
+ public:
+ GestureProviderAuraTest() {}
+
+ virtual ~GestureProviderAuraTest() {}
+
+ virtual void OnGestureEvent(GestureEvent* event) OVERRIDE {}
+
+ virtual void SetUp() OVERRIDE {
+ provider_.reset(new GestureProviderAura(this));
+ }
+
+ virtual void TearDown() OVERRIDE { provider_.reset(); }
+
+ GestureProviderAura* provider() { return provider_.get(); }
+
+ private:
+ scoped_ptr<GestureProviderAura> provider_;
+ base::MessageLoopForUI message_loop_;
+};
+
+TEST_F(GestureProviderAuraTest, IgnoresExtraPressEvents) {
+ base::TimeDelta time = ui::EventTimeForNow();
+ TouchEvent press1(ET_TOUCH_PRESSED, gfx::PointF(10, 10), 0, time);
+ EXPECT_TRUE(provider()->OnTouchEvent(press1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent press2(ET_TOUCH_PRESSED, gfx::PointF(30, 40), 0, time);
+ // Redundant press with same id is ignored.
+ EXPECT_FALSE(provider()->OnTouchEvent(press2));
+}
+
+TEST_F(GestureProviderAuraTest, IgnoresExtraMoveOrReleaseEvents) {
+ base::TimeDelta time = ui::EventTimeForNow();
+ TouchEvent press1(ET_TOUCH_PRESSED, gfx::PointF(10, 10), 0, time);
+ EXPECT_TRUE(provider()->OnTouchEvent(press1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent release1(ET_TOUCH_RELEASED, gfx::PointF(30, 40), 0, time);
+ EXPECT_TRUE(provider()->OnTouchEvent(release1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent release2(ET_TOUCH_RELEASED, gfx::PointF(30, 45), 0, time);
+ EXPECT_FALSE(provider()->OnTouchEvent(release1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent move1(ET_TOUCH_MOVED, gfx::PointF(70, 75), 0, time);
+ EXPECT_FALSE(provider()->OnTouchEvent(move1));
+}
+
+TEST_F(GestureProviderAuraTest, IgnoresIdenticalMoveEvents) {
+ const float kRadiusX = 20.f;
+ const float kRadiusY = 30.f;
+ const float kAngle = 0.321f;
+ const float kForce = 40.f;
+ const int kTouchId0 = 5;
+ const int kTouchId1 = 3;
+
+ base::TimeDelta time = ui::EventTimeForNow();
+ TouchEvent press0_1(ET_TOUCH_PRESSED, gfx::PointF(9, 10), kTouchId0, time);
+ EXPECT_TRUE(provider()->OnTouchEvent(press0_1));
+
+ TouchEvent press1_1(ET_TOUCH_PRESSED, gfx::PointF(40, 40), kTouchId1, time);
+ EXPECT_TRUE(provider()->OnTouchEvent(press1_1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent move0_1(ET_TOUCH_MOVED,
+ gfx::PointF(10, 10),
+ 0,
+ kTouchId0,
+ time,
+ kRadiusX,
+ kRadiusY,
+ kAngle,
+ kForce);
+ EXPECT_TRUE(provider()->OnTouchEvent(move0_1));
+
+ TouchEvent move1_1(ET_TOUCH_MOVED,
+ gfx::PointF(100, 200),
+ 0,
+ kTouchId1,
+ time,
+ kRadiusX,
+ kRadiusY,
+ kAngle,
+ kForce);
+ EXPECT_TRUE(provider()->OnTouchEvent(move1_1));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent move0_2(ET_TOUCH_MOVED,
+ gfx::PointF(10, 10),
+ 0,
+ kTouchId0,
+ time,
+ kRadiusX,
+ kRadiusY,
+ kAngle,
+ kForce);
+ // Nothing has changed, so ignore the move.
+ EXPECT_FALSE(provider()->OnTouchEvent(move0_2));
+
+ TouchEvent move1_2(ET_TOUCH_MOVED,
+ gfx::PointF(100, 200),
+ 0,
+ kTouchId1,
+ time,
+ kRadiusX,
+ kRadiusY,
+ kAngle,
+ kForce);
+ // Nothing has changed, so ignore the move.
+ EXPECT_FALSE(provider()->OnTouchEvent(move1_2));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent move0_3(ET_TOUCH_MOVED,
+ gfx::PointF(70, 75.1f),
+ 0,
+ kTouchId0,
+ time,
+ kRadiusX,
+ kRadiusY,
+ kAngle,
+ kForce);
+ // Position has changed, so don't ignore the move.
+ EXPECT_TRUE(provider()->OnTouchEvent(move0_3));
+
+ time += base::TimeDelta::FromMilliseconds(10);
+ TouchEvent move0_4(ET_TOUCH_MOVED,
+ gfx::PointF(70, 75.1f),
+ 0,
+ kTouchId0,
+ time,
+ kRadiusX,
+ kRadiusY + 1,
+ kAngle,
+ kForce);
+}
+
+} // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer.h b/ui/events/gestures/gesture_recognizer.h
new file mode 100644
index 0000000..5dc5834
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer.h
@@ -0,0 +1,102 @@
+// 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_RECOGNIZER_H_
+#define UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_H_
+
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#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
+// into gestures.
+class EVENTS_EXPORT GestureRecognizer {
+ public:
+ static GestureRecognizer* Create();
+ static GestureRecognizer* Get();
+ static void Reset();
+
+ // List of GestureEvent*.
+ typedef ScopedVector<GestureEvent> Gestures;
+
+ virtual ~GestureRecognizer() {}
+
+ // Invoked before event dispatch. If the event is invalid given the current
+ // touch sequence, marks it as handled.
+ virtual bool ProcessTouchEventPreDispatch(const TouchEvent& event,
+ GestureConsumer* consumer) = 0;
+ // Returns a list of zero or more GestureEvents. The caller is responsible for
+ // freeing the returned events. Called synchronously after event dispatch.
+ virtual Gestures* ProcessTouchEventPostDispatch(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) = 0;
+ // Returns a list of zero or more GestureEvents. The caller is responsible for
+ // freeing the returned events. Called when a touch event receives an
+ // asynchronous ack.
+ virtual Gestures* ProcessTouchEventOnAsyncAck(const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) = 0;
+
+ // This is called when the consumer is destroyed. So this should cleanup any
+ // 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.
+ // Otherwise, returns null.
+ virtual GestureConsumer* GetTouchLockedTarget(const TouchEvent& event) = 0;
+
+ // Return the window which should handle this GestureEvent.
+ virtual GestureConsumer* GetTargetForGestureEvent(
+ const GestureEvent& event) = 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.
+ // The caller is responsible for updating the state of the consumers to
+ // be aware of this transfer of control (there are no ENTERED/EXITED events).
+ // If |new_consumer| is NULL, all events are canceled.
+ // If |old_consumer| is NULL, all events not already targeting |new_consumer|
+ // are canceled.
+ virtual void TransferEventsTo(GestureConsumer* current_consumer,
+ GestureConsumer* new_consumer) = 0;
+
+ // If a gesture is underway for |consumer| |point| is set to the last touch
+ // 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::PointF* point) = 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
+ // responsibility of the |helper| to call |RemoveGestureEventHelper()| on
+ // destruction.
+ virtual void AddGestureEventHelper(GestureEventHelper* helper) = 0;
+
+ // Unsubscribes |helper| from async gesture dispatch.
+ // Since the GestureRecognizer does not own the |helper|, it is not deleted
+ // and must be cleaned up appropriately by the caller.
+ virtual void RemoveGestureEventHelper(GestureEventHelper* helper) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_H_
diff --git a/ui/events/gestures/gesture_recognizer_impl.cc b/ui/events/gestures/gesture_recognizer_impl.cc
new file mode 100644
index 0000000..9737fd8
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl.cc
@@ -0,0 +1,348 @@
+// 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_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_types.h"
+
+namespace ui {
+
+namespace {
+
+template <typename T>
+void TransferConsumer(GestureConsumer* current_consumer,
+ GestureConsumer* new_consumer,
+ std::map<GestureConsumer*, T>* map) {
+ if (map->count(current_consumer)) {
+ (*map)[new_consumer] = (*map)[current_consumer];
+ map->erase(current_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) {
+ map->erase(i++);
+ consumer_removed = true;
+ } else {
+ ++i;
+ }
+ }
+ return consumer_removed;
+}
+
+void TransferTouchIdToConsumerMap(
+ GestureConsumer* old_consumer,
+ GestureConsumer* new_consumer,
+ GestureRecognizerImpl::TouchIdToConsumerMap* map) {
+ for (GestureRecognizerImpl::TouchIdToConsumerMap::iterator i = map->begin();
+ i != map->end(); ++i) {
+ if (i->second == old_consumer)
+ i->second = new_consumer;
+ }
+}
+
+GestureProviderAura* CreateGestureProvider(GestureProviderAuraClient* client) {
+ return new GestureProviderAura(client);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerImpl, public:
+
+GestureRecognizerImpl::GestureRecognizerImpl() {
+}
+
+GestureRecognizerImpl::~GestureRecognizerImpl() {
+ STLDeleteValues(&consumer_gesture_provider_);
+}
+
+// Checks if this finger is already down, if so, returns the current target.
+// Otherwise, returns NULL.
+GestureConsumer* GestureRecognizerImpl::GetTouchLockedTarget(
+ const TouchEvent& event) {
+ return touch_id_target_[event.touch_id()];
+}
+
+GestureConsumer* GestureRecognizerImpl::GetTargetForGestureEvent(
+ const GestureEvent& event) {
+ GestureConsumer* target = NULL;
+ int touch_id = event.details().oldest_touch_id();
+ target = touch_id_target_for_gestures_[touch_id];
+ return target;
+}
+
+GestureConsumer* GestureRecognizerImpl::GetTargetForLocation(
+ const gfx::PointF& location, int source_device_id) {
+ const int max_distance =
+ GestureConfiguration::max_separation_for_gesture_touches_in_pixels();
+
+ 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)
+ return touch_id_target_[closest_touch_id];
+ return NULL;
+}
+
+void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer,
+ GestureConsumer* new_consumer) {
+ // Send cancel to all those save |new_consumer| and |current_consumer|.
+ // Don't send a cancel to |current_consumer|, unless |new_consumer| is NULL.
+ // Dispatching a touch-cancel event can end up altering |touch_id_target_|
+ // (e.g. when the target of the event is destroyed, causing it to be removed
+ // from |touch_id_target_| in |CleanupStateForConsumer()|). So create a list
+ // of the touch-ids that need to be cancelled, and dispatch the cancel events
+ // for them at the end.
+
+ std::vector<GestureConsumer*> consumers;
+ std::map<GestureConsumer*, GestureProviderAura*>::iterator i;
+ for (i = consumer_gesture_provider_.begin();
+ i != consumer_gesture_provider_.end();
+ ++i) {
+ if (i->first && i->first != new_consumer &&
+ (i->first != current_consumer || new_consumer == NULL)) {
+ consumers.push_back(i->first);
+ }
+ }
+ for (std::vector<GestureConsumer*>::iterator iter = consumers.begin();
+ iter != consumers.end();
+ ++iter) {
+ CancelActiveTouches(*iter);
+ }
+ // Transfer events from |current_consumer| to |new_consumer|.
+ if (current_consumer && new_consumer) {
+ TransferTouchIdToConsumerMap(current_consumer, new_consumer,
+ &touch_id_target_);
+ TransferTouchIdToConsumerMap(current_consumer, new_consumer,
+ &touch_id_target_for_gestures_);
+ TransferConsumer(
+ current_consumer, new_consumer, &consumer_gesture_provider_);
+ }
+}
+
+bool GestureRecognizerImpl::GetLastTouchPointForTarget(
+ GestureConsumer* consumer,
+ gfx::PointF* point) {
+ 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;
+}
+
+bool GestureRecognizerImpl::CancelActiveTouches(GestureConsumer* consumer) {
+ bool cancelled_touch = false;
+ if (consumer_gesture_provider_.count(consumer) == 0)
+ return false;
+ const MotionEventAura& pointer_state =
+ consumer_gesture_provider_[consumer]->pointer_state();
+ if (pointer_state.GetPointerCount() == 0)
+ return false;
+ // Pointer_state is modified every time after DispatchCancelTouchEvent.
+ scoped_ptr<MotionEvent> pointer_state_clone = pointer_state.Clone();
+ for (size_t i = 0; i < pointer_state_clone->GetPointerCount(); ++i) {
+ gfx::PointF point(pointer_state_clone->GetX(i),
+ pointer_state_clone->GetY(i));
+ TouchEvent touch_event(ui::ET_TOUCH_CANCELLED,
+ point,
+ ui::EF_IS_SYNTHESIZED,
+ pointer_state_clone->GetPointerId(i),
+ ui::EventTimeForNow(),
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f);
+ GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+ if (helper)
+ helper->DispatchCancelTouchEvent(&touch_event);
+ cancelled_touch = true;
+ }
+ return cancelled_touch;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerImpl, private:
+
+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 ||
+ event.type() == ui::ET_TOUCH_CANCELLED) {
+ touch_id_target_.erase(event.touch_id());
+ } else if (event.type() == ui::ET_TOUCH_PRESSED) {
+ touch_id_target_[event.touch_id()] = target;
+ if (target)
+ touch_id_target_for_gestures_[event.touch_id()] = target;
+ }
+}
+
+void GestureRecognizerImpl::DispatchGestureEvent(GestureEvent* event) {
+ GestureConsumer* consumer = GetTargetForGestureEvent(*event);
+ if (consumer) {
+ GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+ if (helper)
+ helper->DispatchGestureEvent(event);
+ }
+}
+
+bool GestureRecognizerImpl::ProcessTouchEventPreDispatch(
+ const TouchEvent& event,
+ GestureConsumer* consumer) {
+ SetupTargets(event, consumer);
+
+ if (event.result() & ER_CONSUMED)
+ return false;
+
+ GestureProviderAura* gesture_provider =
+ GetGestureProviderForConsumer(consumer);
+ return gesture_provider->OnTouchEvent(event);
+}
+
+GestureRecognizer::Gestures*
+GestureRecognizerImpl::ProcessTouchEventPostDispatch(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) {
+ GestureProviderAura* gesture_provider =
+ GetGestureProviderForConsumer(consumer);
+ gesture_provider->OnTouchEventAck(result != ER_UNHANDLED);
+ return gesture_provider->GetAndResetPendingGestures();
+}
+
+GestureRecognizer::Gestures* GestureRecognizerImpl::ProcessTouchEventOnAsyncAck(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) {
+ if (result & ui::ER_CONSUMED)
+ return NULL;
+ GestureProviderAura* gesture_provider =
+ GetGestureProviderForConsumer(consumer);
+ gesture_provider->OnTouchEventAck(result != ER_UNHANDLED);
+ return gesture_provider->GetAndResetPendingGestures();
+}
+
+bool GestureRecognizerImpl::CleanupStateForConsumer(
+ GestureConsumer* consumer) {
+ bool state_cleaned_up = false;
+
+ if (consumer_gesture_provider_.count(consumer)) {
+ state_cleaned_up = true;
+ delete consumer_gesture_provider_[consumer];
+ consumer_gesture_provider_.erase(consumer);
+ }
+
+ 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) {
+ helpers_.push_back(helper);
+}
+
+void GestureRecognizerImpl::RemoveGestureEventHelper(
+ GestureEventHelper* helper) {
+ std::vector<GestureEventHelper*>::iterator it = std::find(helpers_.begin(),
+ helpers_.end(), helper);
+ if (it != helpers_.end())
+ helpers_.erase(it);
+}
+
+void GestureRecognizerImpl::OnGestureEvent(GestureEvent* event) {
+ DispatchGestureEvent(event);
+}
+
+GestureEventHelper* GestureRecognizerImpl::FindDispatchHelperForConsumer(
+ GestureConsumer* consumer) {
+ std::vector<GestureEventHelper*>::iterator it;
+ for (it = helpers_.begin(); it != helpers_.end(); ++it) {
+ if ((*it)->CanDispatchToConsumer(consumer))
+ return (*it);
+ }
+ return NULL;
+}
+
+// GestureRecognizer, static
+GestureRecognizer* GestureRecognizer::Create() {
+ return new GestureRecognizerImpl();
+}
+
+static GestureRecognizerImpl* g_gesture_recognizer_instance = NULL;
+
+// GestureRecognizer, static
+GestureRecognizer* GestureRecognizer::Get() {
+ if (!g_gesture_recognizer_instance)
+ g_gesture_recognizer_instance = new GestureRecognizerImpl();
+ 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 =
+ g_gesture_recognizer_instance->helpers();
+ std::vector<GestureEventHelper*>::iterator it;
+ for (it = helpers.begin(); it != helpers.end(); ++it)
+ gesture_recognizer->AddGestureEventHelper(*it);
+
+ helpers.clear();
+ g_gesture_recognizer_instance =
+ static_cast<GestureRecognizerImpl*>(gesture_recognizer);
+}
+
+} // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer_impl.h b/ui/events/gestures/gesture_recognizer_impl.h
new file mode 100644
index 0000000..7ba74f3
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl.h
@@ -0,0 +1,106 @@
+// 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_RECOGNIZER_IMPL_H_
+#define UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
+
+#include <map>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#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/gfx/point.h"
+
+namespace ui {
+class GestureConsumer;
+class GestureEvent;
+class GestureEventHelper;
+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 GestureProviderAuraClient {
+ public:
+ typedef std::map<int, GestureConsumer*> TouchIdToConsumerMap;
+
+ GestureRecognizerImpl();
+ virtual ~GestureRecognizerImpl();
+
+ std::vector<GestureEventHelper*>& helpers() { return helpers_; }
+
+ // Overridden from GestureRecognizer
+ virtual GestureConsumer* GetTouchLockedTarget(
+ const TouchEvent& event) OVERRIDE;
+ virtual GestureConsumer* GetTargetForGestureEvent(
+ const GestureEvent& event) OVERRIDE;
+ virtual GestureConsumer* GetTargetForLocation(
+ 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::PointF* point) OVERRIDE;
+ virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE;
+
+ protected:
+ virtual GestureProviderAura* GetGestureProviderForConsumer(
+ GestureConsumer* c);
+
+ private:
+ // Sets up the target consumer for gestures based on the touch-event.
+ void SetupTargets(const TouchEvent& event, GestureConsumer* consumer);
+
+ void DispatchGestureEvent(GestureEvent* event);
+
+ // Overridden from GestureRecognizer
+ virtual bool ProcessTouchEventPreDispatch(const TouchEvent& event,
+ GestureConsumer* consumer) OVERRIDE;
+
+ virtual Gestures* ProcessTouchEventPostDispatch(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) OVERRIDE;
+
+ virtual Gestures* ProcessTouchEventOnAsyncAck(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) OVERRIDE;
+
+ virtual bool CleanupStateForConsumer(GestureConsumer* consumer)
+ OVERRIDE;
+ virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE;
+ virtual void RemoveGestureEventHelper(GestureEventHelper* helper) 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*, 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
+ // ET_TOUCH_RELEASE and ET_TOUCH_CANCEL. |touch_id_target_for_gestures_| are
+ // removed in ConsumerDestroyed().
+ TouchIdToConsumerMap touch_id_target_;
+ TouchIdToConsumerMap touch_id_target_for_gestures_;
+
+ std::vector<GestureEventHelper*> helpers_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImpl);
+};
+
+// Provided only for testing:
+EVENTS_EXPORT void SetGestureRecognizerForTesting(
+ GestureRecognizer* gesture_recognizer);
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
diff --git a/ui/events/gestures/gesture_recognizer_impl_mac.cc b/ui/events/gestures/gesture_recognizer_impl_mac.cc
new file mode 100644
index 0000000..4dfa311
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl_mac.cc
@@ -0,0 +1,76 @@
+// 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 bool ProcessTouchEventPreDispatch(
+ const TouchEvent& event,
+ GestureConsumer* consumer) OVERRIDE {
+ return false;
+ }
+
+ virtual Gestures* ProcessTouchEventPostDispatch(
+ const TouchEvent& event,
+ ui::EventResult result,
+ GestureConsumer* consumer) OVERRIDE {
+ return NULL;
+ }
+ virtual Gestures* ProcessTouchEventOnAsyncAck(
+ 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/ui/events/gestures/gesture_types.h b/ui/events/gestures/gesture_types.h
new file mode 100644
index 0000000..c15a2fd
--- /dev/null
+++ b/ui/events/gestures/gesture_types.h
@@ -0,0 +1,37 @@
+// 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_TYPES_H_
+#define UI_EVENTS_GESTURES_GESTURE_TYPES_H_
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class GestureEvent;
+class TouchEvent;
+
+// An abstract type for consumers of gesture-events created by the
+// gesture-recognizer.
+class EVENTS_EXPORT GestureConsumer {
+ public:
+ virtual ~GestureConsumer() {}
+};
+
+// GestureEventHelper creates implementation-specific gesture events and
+// can dispatch them.
+class EVENTS_EXPORT GestureEventHelper {
+ public:
+ virtual ~GestureEventHelper() {
+ }
+
+ // Returns true if this helper can dispatch events to |consumer|.
+ virtual bool CanDispatchToConsumer(GestureConsumer* consumer) = 0;
+ virtual void DispatchGestureEvent(GestureEvent* event) = 0;
+ virtual void DispatchCancelTouchEvent(TouchEvent* event) = 0;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_GESTURE_TYPES_H_
diff --git a/ui/events/gestures/motion_event_aura.cc b/ui/events/gestures/motion_event_aura.cc
new file mode 100644
index 0000000..1b7b825
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura.cc
@@ -0,0 +1,287 @@
+// 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/gestures/motion_event_aura.h"
+
+#include <cmath>
+
+#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,
+ int flags,
+ const PointData (&active_touches)[MotionEvent::MAX_TOUCH_POINT_COUNT])
+ : pointer_count_(pointer_count),
+ last_touch_time_(last_touch_time),
+ cached_action_(cached_action),
+ cached_action_index_(cached_action_index),
+ flags_(flags) {
+ 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();
+
+ float radius_x = touch.radius_x();
+ float radius_y = touch.radius_y();
+ float rotation_angle_rad = touch.rotation_angle() * M_PI / 180.f;
+ DCHECK_GE(radius_x, 0) << "Unexpected x-radius < 0";
+ DCHECK_GE(radius_y, 0) << "Unexpected y-radius < 0";
+ DCHECK(0 <= rotation_angle_rad && rotation_angle_rad <= M_PI_2)
+ << "Unexpected touch rotation angle";
+
+ if (radius_x > radius_y) {
+ // The case radius_x == radius_y is omitted from here on purpose: for
+ // circles, we want to pass the angle (which could be any value in such
+ // cases but always seem to be set to zero) unchanged.
+ point_data.touch_major = 2.f * radius_x;
+ point_data.touch_minor = 2.f * radius_y;
+ point_data.orientation = rotation_angle_rad - M_PI_2;
+ } else {
+ point_data.touch_major = 2.f * radius_y;
+ point_data.touch_minor = 2.f * radius_x;
+ point_data.orientation = rotation_angle_rad;
+ }
+
+ if (!point_data.touch_major) {
+ point_data.touch_major = 2.f * GestureConfiguration::default_radius();
+ point_data.touch_minor = 2.f * GestureConfiguration::default_radius();
+ point_data.orientation = 0;
+ }
+
+ 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);
+ flags_ = touch.flags();
+ 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_LT(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_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].touch_id;
+}
+
+float MotionEventAura::GetX(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].x;
+}
+
+float MotionEventAura::GetY(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].y;
+}
+
+float MotionEventAura::GetRawX(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].raw_x;
+}
+
+float MotionEventAura::GetRawY(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].raw_y;
+}
+
+float MotionEventAura::GetTouchMajor(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].touch_major;
+}
+
+float MotionEventAura::GetTouchMinor(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].touch_minor;
+}
+
+float MotionEventAura::GetOrientation(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].orientation;
+}
+
+float MotionEventAura::GetPressure(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].pressure;
+}
+
+MotionEvent::ToolType MotionEventAura::GetToolType(size_t pointer_index) const {
+ // TODO(jdduke): Plumb tool type from the platform, crbug.com/404128.
+ DCHECK_LT(pointer_index, pointer_count_);
+ return MotionEvent::TOOL_TYPE_UNKNOWN;
+}
+
+int MotionEventAura::GetButtonState() const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+int MotionEventAura::GetFlags() const {
+ return flags_;
+}
+
+base::TimeTicks MotionEventAura::GetEventTime() const {
+ return last_touch_time_;
+}
+
+scoped_ptr<MotionEvent> MotionEventAura::Clone() const {
+ return scoped_ptr<MotionEvent>(new MotionEventAura(pointer_count_,
+ last_touch_time_,
+ cached_action_,
+ cached_action_index_,
+ flags_,
+ active_touches_));
+}
+scoped_ptr<MotionEvent> MotionEventAura::Cancel() const {
+ return scoped_ptr<MotionEvent>(new MotionEventAura(
+ pointer_count_, last_touch_time_, ACTION_CANCEL, -1, 0, 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),
+ touch_major(0),
+ touch_minor(0),
+ orientation(0) {
+}
+
+int MotionEventAura::GetSourceDeviceId(size_t pointer_index) const {
+ DCHECK_LT(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].source_device_id;
+}
+
+void MotionEventAura::AddTouch(const TouchEvent& touch) {
+ if (pointer_count_ == MotionEvent::MAX_TOUCH_POINT_COUNT)
+ 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_LT(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/ui/events/gestures/motion_event_aura.h b/ui/events/gestures/motion_event_aura.h
new file mode 100644
index 0000000..ec956fc
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura.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_GESTURE_DETECTION_UI_MOTION_EVENT_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_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/gesture_detection/motion_event.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 GetTouchMinor(size_t pointer_index) const OVERRIDE;
+ virtual float GetOrientation(size_t pointer_index) const OVERRIDE;
+ virtual float GetPressure(size_t pointer_index) const OVERRIDE;
+ virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE;
+ virtual int GetButtonState() const OVERRIDE;
+ virtual int GetFlags() const OVERRIDE;
+ virtual base::TimeTicks GetEventTime() 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 touch_major;
+ float touch_minor;
+ float orientation;
+ };
+
+ MotionEventAura(
+ size_t pointer_count,
+ const base::TimeTicks& last_touch_time,
+ Action cached_action,
+ int cached_action_index,
+ int flags,
+ const PointData (&active_touches)[MotionEvent::MAX_TOUCH_POINT_COUNT]);
+
+ 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_;
+ int flags_;
+
+ // We want constant time indexing by pointer_index, and fast indexing by id.
+ PointData active_touches_[MotionEvent::MAX_TOUCH_POINT_COUNT];
+
+ DISALLOW_COPY_AND_ASSIGN(MotionEventAura);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
diff --git a/ui/events/gestures/motion_event_aura_unittest.cc b/ui/events/gestures/motion_event_aura_unittest.cc
new file mode 100644
index 0000000..6e645dc
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura_unittest.cc
@@ -0,0 +1,423 @@
+// 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 <cmath>
+
+#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) {
+ ui::TouchEvent event(type,
+ gfx::PointF(x, y),
+ 0,
+ id,
+ base::TimeDelta::FromMilliseconds(0),
+ 0,
+ 0,
+ 0,
+ 0);
+ event.set_root_location(gfx::PointF(raw_x, raw_y));
+ return event;
+}
+
+ui::TouchEvent TouchWithTapParams(ui::EventType type,
+ int id,
+ float radius_x,
+ float radius_y,
+ float rotation_angle,
+ float pressure) {
+ ui::TouchEvent event(type,
+ gfx::PointF(1, 1),
+ 0,
+ id,
+ base::TimeDelta::FromMilliseconds(0),
+ radius_x,
+ radius_y,
+ rotation_angle,
+ pressure);
+ event.set_root_location(gfx::PointF(1, 1));
+ 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.
+ // TODO(mustaq): Make a separate clone test
+ 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;
+
+ x = 14.4f;
+ y = 17.3f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ TouchEvent press0 =
+ TouchWithPosition(ET_TOUCH_PRESSED, ids[0], x, y, raw_x, raw_y);
+ 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));
+
+ x = 17.8f;
+ y = 12.1f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ TouchEvent press1 =
+ TouchWithPosition(ET_TOUCH_PRESSED, ids[1], x, y, raw_x, raw_y);
+ 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));
+
+ // Test cloning of pointer location information.
+ scoped_ptr<MotionEvent> clone = event.Clone();
+ {
+ const MotionEventAura* raw_clone_aura =
+ static_cast<MotionEventAura*>(clone.get());
+ EXPECT_EQ(2U, raw_clone_aura->GetPointerCount());
+ EXPECT_FLOAT_EQ(x, raw_clone_aura->GetX(1));
+ EXPECT_FLOAT_EQ(y, raw_clone_aura->GetY(1));
+ EXPECT_FLOAT_EQ(raw_x, raw_clone_aura->GetRawX(1));
+ EXPECT_FLOAT_EQ(raw_y, raw_clone_aura->GetRawY(1));
+ }
+
+ x = 27.9f;
+ y = 22.3f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ TouchEvent move1 =
+ TouchWithPosition(ET_TOUCH_MOVED, ids[1], x, y, raw_x, raw_y);
+ 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));
+
+ x = 34.6f;
+ y = 23.8f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ TouchEvent move0 =
+ TouchWithPosition(ET_TOUCH_MOVED, ids[0], x, y, raw_x, raw_y);
+ 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));
+}
+
+TEST(MotionEventAuraTest, TapParams) {
+ // Test that touch params are stored correctly.
+ MotionEventAura event;
+
+ int ids[] = {15, 13};
+
+ float radius_x;
+ float radius_y;
+ float rotation_angle;
+ float pressure;
+
+ radius_x = 123.45f;
+ radius_y = 67.89f;
+ rotation_angle = 23.f;
+ pressure = 0.123f;
+ TouchEvent press0 = TouchWithTapParams(
+ ET_TOUCH_PRESSED, ids[0], radius_x, radius_y, rotation_angle, pressure);
+ event.OnTouch(press0);
+
+ EXPECT_EQ(1U, event.GetPointerCount());
+ EXPECT_FLOAT_EQ(radius_x, event.GetTouchMajor(0) / 2);
+ EXPECT_FLOAT_EQ(radius_y, event.GetTouchMinor(0) / 2);
+ EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(0) * 180 / M_PI + 90);
+ EXPECT_FLOAT_EQ(pressure, event.GetPressure(0));
+
+ radius_x = 67.89f;
+ radius_y = 123.45f;
+ rotation_angle = 46.f;
+ pressure = 0.456f;
+ TouchEvent press1 = TouchWithTapParams(
+ ET_TOUCH_PRESSED, ids[1], radius_x, radius_y, rotation_angle, pressure);
+ event.OnTouch(press1);
+
+ EXPECT_EQ(2U, event.GetPointerCount());
+ EXPECT_FLOAT_EQ(radius_y, event.GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(radius_x, event.GetTouchMinor(1) / 2);
+ EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(1) * 180 / M_PI);
+ EXPECT_FLOAT_EQ(pressure, event.GetPressure(1));
+
+ // Test cloning of tap params
+ scoped_ptr<MotionEvent> clone = event.Clone();
+ {
+ const MotionEventAura* raw_clone_aura =
+ static_cast<MotionEventAura*>(clone.get());
+ EXPECT_EQ(2U, raw_clone_aura->GetPointerCount());
+ EXPECT_FLOAT_EQ(radius_y, raw_clone_aura->GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(radius_x, raw_clone_aura->GetTouchMinor(1) / 2);
+ EXPECT_FLOAT_EQ(
+ rotation_angle, raw_clone_aura->GetOrientation(1) * 180 / M_PI);
+ EXPECT_FLOAT_EQ(pressure, raw_clone_aura->GetPressure(1));
+ }
+
+ radius_x = 76.98f;
+ radius_y = 321.54f;
+ rotation_angle = 64.f;
+ pressure = 0.654f;
+ TouchEvent move1 = TouchWithTapParams(
+ ET_TOUCH_MOVED, ids[1], radius_x, radius_y, rotation_angle, pressure);
+ event.OnTouch(move1);
+
+ EXPECT_EQ(2U, event.GetPointerCount());
+ EXPECT_FLOAT_EQ(radius_y, event.GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(radius_x, event.GetTouchMinor(1) / 2);
+ EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(1) * 180 / M_PI);
+ EXPECT_FLOAT_EQ(pressure, event.GetPressure(1));
+}
+
+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());
+}
+
+TEST(MotionEventAuraTest, ToolType) {
+ MotionEventAura event;
+
+ // For now, all pointers have an unknown tool type.
+ // TODO(jdduke): Expand this test when ui::TouchEvent identifies the source
+ // touch type, crbug.com/404128.
+ event.OnTouch(TouchWithType(ET_TOUCH_PRESSED, 7));
+ ASSERT_EQ(1U, event.GetPointerCount());
+ EXPECT_EQ(MotionEvent::TOOL_TYPE_UNKNOWN, event.GetToolType(0));
+}
+
+TEST(MotionEventAuraTest, Flags) {
+ int ids[] = {7, 11};
+ MotionEventAura event;
+
+ TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+ press0.set_flags(EF_CONTROL_DOWN);
+ event.OnTouch(press0);
+ EXPECT_EQ(EF_CONTROL_DOWN, event.GetFlags());
+
+ TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+ press1.set_flags(EF_CONTROL_DOWN | EF_CAPS_LOCK_DOWN);
+ event.OnTouch(press1);
+ EXPECT_EQ(EF_CONTROL_DOWN | EF_CAPS_LOCK_DOWN, event.GetFlags());
+}
+
+} // namespace ui
diff --git a/ui/events/input_device_event_observer.h b/ui/events/input_device_event_observer.h
new file mode 100644
index 0000000..679f1f4
--- /dev/null
+++ b/ui/events/input_device_event_observer.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_INPUT_DEVICE_EVENT_OBSERVER_H_
+#define UI_EVENTS_INPUT_DEVICE_EVENT_OBSERVER_H_
+
+namespace ui {
+
+// DeviceDataManager observer used to announce input hotplug events.
+class InputDeviceEventObserver {
+ public:
+ virtual ~InputDeviceEventObserver() {}
+
+ virtual void OnInputDeviceConfigurationChanged() = 0;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_INPUT_DEVICE_EVENT_OBSERVER_H_
diff --git a/ui/events/ipc/BUILD.gn b/ui/events/ipc/BUILD.gn
new file mode 100644
index 0000000..0462410
--- /dev/null
+++ b/ui/events/ipc/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+component("ipc") {
+ output_name = "events_ipc"
+ sources = [
+ "latency_info_param_traits.cc",
+ "latency_info_param_traits.h",
+ ]
+
+ defines = [ "EVENTS_IMPLEMENTATION" ]
+
+ deps = [
+ "//ipc",
+ "//ui/events",
+ ]
+}
+
diff --git a/ui/events/ipc/OWNERS b/ui/events/ipc/OWNERS
new file mode 100644
index 0000000..6732300
--- /dev/null
+++ b/ui/events/ipc/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+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/ui/events/ipc/events_ipc.gyp b/ui/events/ipc/events_ipc.gyp
new file mode 100644
index 0000000..898bf2f
--- /dev/null
+++ b/ui/events/ipc/events_ipc.gyp
@@ -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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ # GN version: //ui/events/ipc
+ '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/ui/events/ipc/latency_info_param_traits.cc b/ui/events/ipc/latency_info_param_traits.cc
new file mode 100644
index 0000000..0b36c31
--- /dev/null
+++ b/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/ui/events/ipc/latency_info_param_traits.h b/ui/events/ipc/latency_info_param_traits.h
new file mode 100644
index 0000000..289ecb4
--- /dev/null
+++ b/ui/events/ipc/latency_info_param_traits.h
@@ -0,0 +1,38 @@
+// 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::InputCoordinate)
+IPC_STRUCT_TRAITS_MEMBER(x)
+IPC_STRUCT_TRAITS_MEMBER(y)
+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_MEMBER(input_coordinates_size)
+ IPC_STRUCT_TRAITS_MEMBER(input_coordinates[0])
+ IPC_STRUCT_TRAITS_MEMBER(input_coordinates[1])
+IPC_STRUCT_TRAITS_END()
+
+#endif // UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
diff --git a/ui/events/keycodes/DEPS b/ui/events/keycodes/DEPS
new file mode 100644
index 0000000..a2ede37
--- /dev/null
+++ b/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/ui/events/keycodes/OWNERS b/ui/events/keycodes/OWNERS
new file mode 100644
index 0000000..a49314c
--- /dev/null
+++ b/ui/events/keycodes/OWNERS
@@ -0,0 +1,2 @@
+garykac@chromium.org
+wez@chromium.org
diff --git a/ui/events/keycodes/dom4/DEPS b/ui/events/keycodes/dom4/DEPS
new file mode 100644
index 0000000..b548956
--- /dev/null
+++ b/ui/events/keycodes/dom4/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/events/keycodes/dom4",
+]
diff --git a/ui/events/keycodes/dom4/keycode_converter.cc b/ui/events/keycodes/dom4/keycode_converter.cc
new file mode 100644
index 0000000..b7958dd
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter.cc
@@ -0,0 +1,134 @@
+// 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/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+namespace {
+
+#if defined(OS_WIN)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, win, code}
+#elif defined(OS_LINUX)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, xkb, code}
+#elif defined(OS_MACOSX)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, mac, code}
+#else
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, 0, code}
+#endif
+#include "ui/events/keycodes/dom4/keycode_converter_data.h"
+
+const size_t kKeycodeMapEntries = arraysize(usb_keycode_map);
+
+} // namespace
+
+// static
+size_t KeycodeConverter::NumKeycodeMapEntriesForTest() {
+ return kKeycodeMapEntries;
+}
+
+// static
+const KeycodeMapEntry* KeycodeConverter::GetKeycodeMapForTest() {
+ return &usb_keycode_map[0];
+}
+
+// static
+uint16_t KeycodeConverter::InvalidNativeKeycode() {
+ return usb_keycode_map[0].native_keycode;
+}
+
+// static
+const char* KeycodeConverter::InvalidKeyboardEventCode() {
+ return "Unidentified";
+}
+
+// static
+const char* KeycodeConverter::NativeKeycodeToCode(uint16_t native_keycode) {
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].native_keycode == native_keycode) {
+ if (usb_keycode_map[i].code != NULL)
+ return usb_keycode_map[i].code;
+ break;
+ }
+ }
+ return InvalidKeyboardEventCode();
+}
+
+// static
+uint16_t KeycodeConverter::CodeToNativeKeycode(const char* code) {
+ if (!code ||
+ strcmp(code, InvalidKeyboardEventCode()) == 0) {
+ return InvalidNativeKeycode();
+ }
+
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].code &&
+ strcmp(usb_keycode_map[i].code, code) == 0) {
+ return usb_keycode_map[i].native_keycode;
+ }
+ }
+ return InvalidNativeKeycode();
+}
+
+// USB keycodes
+// Note that USB keycodes are not part of any web standard.
+// Please don't use USB keycodes in new code.
+
+// static
+uint16_t KeycodeConverter::InvalidUsbKeycode() {
+ return usb_keycode_map[0].usb_keycode;
+}
+
+// static
+uint16_t KeycodeConverter::UsbKeycodeToNativeKeycode(uint32_t usb_keycode) {
+ // Deal with some special-cases that don't fit the 1:1 mapping.
+ if (usb_keycode == 0x070032) // non-US hash.
+ usb_keycode = 0x070031; // US backslash.
+#if defined(OS_MACOSX)
+ if (usb_keycode == 0x070046) // PrintScreen.
+ usb_keycode = 0x070068; // F13.
+#endif
+
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].usb_keycode == usb_keycode)
+ return usb_keycode_map[i].native_keycode;
+ }
+ return InvalidNativeKeycode();
+}
+
+// static
+uint32_t KeycodeConverter::NativeKeycodeToUsbKeycode(uint16_t native_keycode) {
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].native_keycode == native_keycode)
+ return usb_keycode_map[i].usb_keycode;
+ }
+ return InvalidUsbKeycode();
+}
+
+// static
+const char* KeycodeConverter::UsbKeycodeToCode(uint32_t usb_keycode) {
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].usb_keycode == usb_keycode)
+ return usb_keycode_map[i].code;
+ }
+ return InvalidKeyboardEventCode();
+}
+
+// static
+uint32_t KeycodeConverter::CodeToUsbKeycode(const char* code) {
+ if (!code ||
+ strcmp(code, InvalidKeyboardEventCode()) == 0) {
+ return InvalidUsbKeycode();
+ }
+
+ for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+ if (usb_keycode_map[i].code &&
+ strcmp(usb_keycode_map[i].code, code) == 0) {
+ return usb_keycode_map[i].usb_keycode;
+ }
+ }
+ return InvalidUsbKeycode();
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/dom4/keycode_converter.h b/ui/events/keycodes/dom4/keycode_converter.h
new file mode 100644
index 0000000..a52d086
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter.h
@@ -0,0 +1,84 @@
+// 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_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
+#define UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
+
+#include <stdint.h>
+#include "base/basictypes.h"
+
+// For reference, the W3C UI Event spec is located at:
+// http://www.w3.org/TR/uievents/
+
+namespace ui {
+
+// This structure is used to define the keycode mapping table.
+// It is defined here because the unittests need access to it.
+typedef struct {
+ // USB keycode:
+ // Upper 16-bits: USB Usage Page.
+ // Lower 16-bits: USB Usage Id: Assigned ID within this usage page.
+ uint32_t usb_keycode;
+
+ // Contains one of the following:
+ // On Linux: XKB scancode
+ // On Windows: Windows OEM scancode
+ // On Mac: Mac keycode
+ uint16_t native_keycode;
+
+ // The UIEvents (aka: DOM4Events) |code| value as defined in:
+ // https://dvcs.w3.org/hg/d4e/raw-file/tip/source_respec.htm
+ const char* code;
+} KeycodeMapEntry;
+
+// A class to convert between the current platform's native keycode (scancode)
+// and platform-neutral |code| values (as defined in the W3C UI Events
+// spec (http://www.w3.org/TR/uievents/).
+class KeycodeConverter {
+ public:
+ // Return the value that identifies an invalid native keycode.
+ static uint16_t InvalidNativeKeycode();
+
+ // Return the string that indentifies an invalid UI Event |code|.
+ // The returned pointer references a static global string.
+ static const char* InvalidKeyboardEventCode();
+
+ // Convert a native (Mac/Win/Linux) keycode into the |code| string.
+ // The returned pointer references a static global string.
+ static const char* NativeKeycodeToCode(uint16_t native_keycode);
+
+ // Convert a UI Events |code| string value into a native keycode.
+ static uint16_t CodeToNativeKeycode(const char* code);
+
+ // The following methods relate to USB keycodes.
+ // Note that USB keycodes are not part of any web standard.
+ // Please don't use USB keycodes in new code.
+
+ // Return the value that identifies an invalid USB keycode.
+ static uint16_t InvalidUsbKeycode();
+
+ // Convert a USB keycode into an equivalent platform native keycode.
+ static uint16_t UsbKeycodeToNativeKeycode(uint32_t usb_keycode);
+
+ // Convert a platform native keycode into an equivalent USB keycode.
+ static uint32_t NativeKeycodeToUsbKeycode(uint16_t native_keycode);
+
+ // Convert a USB keycode into the string with the DOM3 |code| value.
+ // The returned pointer references a static global string.
+ static const char* UsbKeycodeToCode(uint32_t usb_keycode);
+
+ // Convert a DOM3 Event |code| string into a USB keycode value.
+ static uint32_t CodeToUsbKeycode(const char* code);
+
+ // Static methods to support testing.
+ static size_t NumKeycodeMapEntriesForTest();
+ static const KeycodeMapEntry* GetKeycodeMapForTest();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(KeycodeConverter);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
diff --git a/ui/events/keycodes/dom4/keycode_converter_data.h b/ui/events/keycodes/dom4/keycode_converter_data.h
new file mode 100644
index 0000000..9cdeb5f
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter_data.h
@@ -0,0 +1,379 @@
+// 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_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
+#define UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
+
+// Data in this file was created by referencing:
+// USB HID Usage Tables (v1.11) 27 June 2001
+// HIToolbox/Events.h (Mac)
+
+const KeycodeMapEntry usb_keycode_map[] = {
+
+ // USB XKB Win Mac Code
+ USB_KEYMAP(0x000000, 0x0000, 0x0000, 0xffff, NULL), // Invalid
+
+ // =========================================
+ // USB Usage Page 0x01: Generic Desktop Page
+ // =========================================
+
+ // Sleep could be encoded as USB#0c0032, but there's no corresponding WakeUp
+ // in the 0x0c USB page.
+
+ // USB XKB Win Mac
+ USB_KEYMAP(0x010082, 0x0096, 0x0000, 0xffff, NULL), // SystemSleep
+ USB_KEYMAP(0x010083, 0x0097, 0x0000, 0xffff, NULL), // SystemWakeUp
+
+ // =========================================
+ // USB Usage Page 0x07: Keyboard/Keypad Page
+ // =========================================
+
+ // TODO(garykac):
+ // XKB#005c ISO Level3 Shift (AltGr)
+ // XKB#005e <>||
+ // XKB#006d Linefeed
+ // XKB#008a SunProps cf. USB#0700a3 CrSel/Props
+ // XKB#008e SunOpen
+ // Mac#003f kVK_Function
+ // Mac#000a kVK_ISO_Section (ISO keyboards only)
+ // Mac#0066 kVK_JIS_Eisu (USB#07008a Henkan?)
+
+ // USB XKB Win Mac
+ USB_KEYMAP(0x070000, 0x0000, 0x0000, 0xffff, NULL), // Reserved
+ USB_KEYMAP(0x070001, 0x0000, 0x0000, 0xffff, NULL), // ErrorRollOver
+ USB_KEYMAP(0x070002, 0x0000, 0x0000, 0xffff, NULL), // POSTFail
+ USB_KEYMAP(0x070003, 0x0000, 0x0000, 0xffff, NULL), // ErrorUndefined
+ USB_KEYMAP(0x070004, 0x0026, 0x001e, 0x0000, "KeyA"), // aA
+ USB_KEYMAP(0x070005, 0x0038, 0x0030, 0x000b, "KeyB"), // bB
+ USB_KEYMAP(0x070006, 0x0036, 0x002e, 0x0008, "KeyC"), // cC
+ USB_KEYMAP(0x070007, 0x0028, 0x0020, 0x0002, "KeyD"), // dD
+
+ USB_KEYMAP(0x070008, 0x001a, 0x0012, 0x000e, "KeyE"), // eEo
+ USB_KEYMAP(0x070009, 0x0029, 0x0021, 0x0003, "KeyF"), // fF
+ USB_KEYMAP(0x07000a, 0x002a, 0x0022, 0x0005, "KeyG"), // gG
+ USB_KEYMAP(0x07000b, 0x002b, 0x0023, 0x0004, "KeyH"), // hH
+ USB_KEYMAP(0x07000c, 0x001f, 0x0017, 0x0022, "KeyI"), // iI
+ USB_KEYMAP(0x07000d, 0x002c, 0x0024, 0x0026, "KeyJ"), // jJ
+ USB_KEYMAP(0x07000e, 0x002d, 0x0025, 0x0028, "KeyK"), // kK
+ USB_KEYMAP(0x07000f, 0x002e, 0x0026, 0x0025, "KeyL"), // lL
+
+ USB_KEYMAP(0x070010, 0x003a, 0x0032, 0x002e, "KeyM"), // mM
+ USB_KEYMAP(0x070011, 0x0039, 0x0031, 0x002d, "KeyN"), // nN
+ USB_KEYMAP(0x070012, 0x0020, 0x0018, 0x001f, "KeyO"), // oO
+ USB_KEYMAP(0x070013, 0x0021, 0x0019, 0x0023, "KeyP"), // pP
+ USB_KEYMAP(0x070014, 0x0018, 0x0010, 0x000c, "KeyQ"), // qQ
+ USB_KEYMAP(0x070015, 0x001b, 0x0013, 0x000f, "KeyR"), // rR
+ USB_KEYMAP(0x070016, 0x0027, 0x001f, 0x0001, "KeyS"), // sS
+ USB_KEYMAP(0x070017, 0x001c, 0x0014, 0x0011, "KeyT"), // tT
+
+ USB_KEYMAP(0x070018, 0x001e, 0x0016, 0x0020, "KeyU"), // uU
+ USB_KEYMAP(0x070019, 0x0037, 0x002f, 0x0009, "KeyV"), // vV
+ USB_KEYMAP(0x07001a, 0x0019, 0x0011, 0x000d, "KeyW"), // wW
+ USB_KEYMAP(0x07001b, 0x0035, 0x002d, 0x0007, "KeyX"), // xX
+ USB_KEYMAP(0x07001c, 0x001d, 0x0015, 0x0010, "KeyY"), // yY
+ USB_KEYMAP(0x07001d, 0x0034, 0x002c, 0x0006, "KeyZ"), // zZ
+ USB_KEYMAP(0x07001e, 0x000a, 0x0002, 0x0012, "Digit1"), // 1!
+ USB_KEYMAP(0x07001f, 0x000b, 0x0003, 0x0013, "Digit2"), // 2@
+
+ USB_KEYMAP(0x070020, 0x000c, 0x0004, 0x0014, "Digit3"), // 3#
+ USB_KEYMAP(0x070021, 0x000d, 0x0005, 0x0015, "Digit4"), // 4$
+ USB_KEYMAP(0x070022, 0x000e, 0x0006, 0x0017, "Digit5"), // 5%
+ USB_KEYMAP(0x070023, 0x000f, 0x0007, 0x0016, "Digit6"), // 6^
+ USB_KEYMAP(0x070024, 0x0010, 0x0008, 0x001a, "Digit7"), // 7&
+ USB_KEYMAP(0x070025, 0x0011, 0x0009, 0x001c, "Digit8"), // 8*
+ USB_KEYMAP(0x070026, 0x0012, 0x000a, 0x0019, "Digit9"), // 9(
+ USB_KEYMAP(0x070027, 0x0013, 0x000b, 0x001d, "Digit0"), // 0)
+
+ USB_KEYMAP(0x070028, 0x0024, 0x001c, 0x0024, "Enter"),
+ USB_KEYMAP(0x070029, 0x0009, 0x0001, 0x0035, "Escape"),
+ USB_KEYMAP(0x07002a, 0x0016, 0x000e, 0x0033, "Backspace"),
+ USB_KEYMAP(0x07002b, 0x0017, 0x000f, 0x0030, "Tab"),
+ USB_KEYMAP(0x07002c, 0x0041, 0x0039, 0x0031, "Space"), // Spacebar
+ USB_KEYMAP(0x07002d, 0x0014, 0x000c, 0x001b, "Minus"), // -_
+ USB_KEYMAP(0x07002e, 0x0015, 0x000d, 0x0018, "Equal"), // =+
+ USB_KEYMAP(0x07002f, 0x0022, 0x001a, 0x0021, "BracketLeft"),// [{
+
+ USB_KEYMAP(0x070030, 0x0023, 0x001b, 0x001e, "BracketRight"), // ]}
+ USB_KEYMAP(0x070031, 0x0033, 0x002b, 0x002a, "Backslash"), // \| (US keyboard only)
+ // USB#070032 never appears on keyboards that have USB#070031.
+ // Platforms use the same scancode as for the two keys.
+ // The keycap varies on international keyboards:
+ // Dan: '* Dutch: <> Ger: #' UK: #~
+ // TODO(garykac): Verify Mac intl keyboard.
+ //USB_KEYMAP(0x070032, 0x0033, 0x002b, 0x002a, "IntlHash"), // #~ (Non-US)
+ USB_KEYMAP(0x070033, 0x002f, 0x0027, 0x0029, "Semicolon"), // ;:
+ USB_KEYMAP(0x070034, 0x0030, 0x0028, 0x0027, "Quote"), // '"
+ USB_KEYMAP(0x070035, 0x0031, 0x0029, 0x0032, "Backquote"), // `~
+ USB_KEYMAP(0x070036, 0x003b, 0x0033, 0x002b, "Comma"), // ,<
+ USB_KEYMAP(0x070037, 0x003c, 0x0034, 0x002f, "Period"), // .>
+
+ USB_KEYMAP(0x070038, 0x003d, 0x0035, 0x002c, "Slash"), // /?
+ // TODO(garykac): CapsLock requires special handling for each platform.
+ USB_KEYMAP(0x070039, 0x0042, 0x003a, 0x0039, "CapsLock"),
+ USB_KEYMAP(0x07003a, 0x0043, 0x003b, 0x007a, "F1"),
+ USB_KEYMAP(0x07003b, 0x0044, 0x003c, 0x0078, "F2"),
+ USB_KEYMAP(0x07003c, 0x0045, 0x003d, 0x0063, "F3"),
+ USB_KEYMAP(0x07003d, 0x0046, 0x003e, 0x0076, "F4"),
+ USB_KEYMAP(0x07003e, 0x0047, 0x003f, 0x0060, "F5"),
+ USB_KEYMAP(0x07003f, 0x0048, 0x0040, 0x0061, "F6"),
+
+ USB_KEYMAP(0x070040, 0x0049, 0x0041, 0x0062, "F7"),
+ USB_KEYMAP(0x070041, 0x004a, 0x0042, 0x0064, "F8"),
+ USB_KEYMAP(0x070042, 0x004b, 0x0043, 0x0065, "F9"),
+ USB_KEYMAP(0x070043, 0x004c, 0x0044, 0x006d, "F10"),
+ USB_KEYMAP(0x070044, 0x005f, 0x0057, 0x0067, "F11"),
+ USB_KEYMAP(0x070045, 0x0060, 0x0058, 0x006f, "F12"),
+ // PrintScreen is effectively F13 on Mac OS X.
+ USB_KEYMAP(0x070046, 0x006b, 0xe037, 0xffff, "PrintScreen"),
+ USB_KEYMAP(0x070047, 0x004e, 0x0046, 0xffff, "ScrollLock"),
+
+ USB_KEYMAP(0x070048, 0x007f, 0x0000, 0xffff, "Pause"),
+ // Labeled "Help/Insert" on Mac.
+ USB_KEYMAP(0x070049, 0x0076, 0xe052, 0x0072, "Insert"),
+ USB_KEYMAP(0x07004a, 0x006e, 0xe047, 0x0073, "Home"),
+ USB_KEYMAP(0x07004b, 0x0070, 0xe049, 0x0074, "PageUp"),
+ // Delete (Forward Delete)
+ USB_KEYMAP(0x07004c, 0x0077, 0xe053, 0x0075, "Delete"),
+ USB_KEYMAP(0x07004d, 0x0073, 0xe04f, 0x0077, "End"),
+ USB_KEYMAP(0x07004e, 0x0075, 0xe051, 0x0079, "PageDown"),
+ USB_KEYMAP(0x07004f, 0x0072, 0xe04d, 0x007c, "ArrowRight"),
+
+ USB_KEYMAP(0x070050, 0x0071, 0xe04b, 0x007b, "ArrowLeft"),
+ USB_KEYMAP(0x070051, 0x0074, 0xe050, 0x007d, "ArrowDown"),
+ USB_KEYMAP(0x070052, 0x006f, 0xe048, 0x007e, "ArrowUp"),
+ USB_KEYMAP(0x070053, 0x004d, 0x0045, 0x0047, "NumLock"), // Keypad_NumLock Clear
+ USB_KEYMAP(0x070054, 0x006a, 0xe035, 0x004b, "NumpadDivide"), // Keypad_/
+ USB_KEYMAP(0x070055, 0x003f, 0x0037, 0x0043, "NumpadMultiply"),// Keypad_*
+ USB_KEYMAP(0x070056, 0x0052, 0x004a, 0x004e, "NumpadSubtract"),// Keypad_-
+ USB_KEYMAP(0x070057, 0x0056, 0x004e, 0x0045, "NumpadAdd"), // Keypad_+
+
+ USB_KEYMAP(0x070058, 0x0068, 0xe01c, 0x004c, "NumpadEnter"), // Keypad_Enter
+ USB_KEYMAP(0x070059, 0x0057, 0x004f, 0x0053, "Numpad1"), // Keypad_1 End
+ USB_KEYMAP(0x07005a, 0x0058, 0x0050, 0x0054, "Numpad2"), // Keypad_2 DownArrow
+ USB_KEYMAP(0x07005b, 0x0059, 0x0051, 0x0055, "Numpad3"), // Keypad_3 PageDown
+ USB_KEYMAP(0x07005c, 0x0053, 0x004b, 0x0056, "Numpad4"), // Keypad_4 LeftArrow
+ USB_KEYMAP(0x07005d, 0x0054, 0x004c, 0x0057, "Numpad5"), // Keypad_5
+ USB_KEYMAP(0x07005e, 0x0055, 0x004d, 0x0058, "Numpad6"), // Keypad_6 RightArrow
+ USB_KEYMAP(0x07005f, 0x004f, 0x0047, 0x0059, "Numpad7"), // Keypad_7 Home
+
+ USB_KEYMAP(0x070060, 0x0050, 0x0048, 0x005b, "Numpad8"), // Keypad_8 UpArrow
+ USB_KEYMAP(0x070061, 0x0051, 0x0049, 0x005c, "Numpad9"), // Keypad_9 PageUp
+ USB_KEYMAP(0x070062, 0x005a, 0x0052, 0x0052, "Numpad0"), // Keypad_0 Insert
+ USB_KEYMAP(0x070063, 0x005b, 0x0053, 0x0041, "NumpadDecimal"), // Keypad_. Delete
+ // USB#070064 is not present on US keyboard.
+ // This key is typically located near LeftShift key.
+ // The keycap varies on international keyboards:
+ // Dan: <> Dutch: ][ Ger: <> UK: \|
+ USB_KEYMAP(0x070064, 0x005e, 0x0056, 0x000a, "IntlBackslash"), // Non-US \|
+ // AppMenu (next to RWin key)
+ USB_KEYMAP(0x070065, 0x0087, 0xe05d, 0x006e, "ContextMenu"),
+ USB_KEYMAP(0x070066, 0x007c, 0x0000, 0xffff, "Power"),
+ USB_KEYMAP(0x070067, 0x007d, 0x0000, 0x0051, "NumpadEqual"), // Keypad_=
+
+ USB_KEYMAP(0x070068, 0x0000, 0x005b, 0x0069, "F13"),
+ USB_KEYMAP(0x070069, 0x0000, 0x005c, 0x006b, "F14"),
+ USB_KEYMAP(0x07006a, 0x0000, 0x005d, 0x0071, "F15"),
+ USB_KEYMAP(0x07006b, 0x0000, 0x0063, 0x006a, "F16"),
+ USB_KEYMAP(0x07006c, 0x0000, 0x0064, 0x0040, "F17"),
+ USB_KEYMAP(0x07006d, 0x0000, 0x0065, 0x004f, "F18"),
+ USB_KEYMAP(0x07006e, 0x0000, 0x0066, 0x0050, "F19"),
+ USB_KEYMAP(0x07006f, 0x0000, 0x0067, 0x005a, "F20"),
+
+ USB_KEYMAP(0x070070, 0x0000, 0x0068, 0xffff, "F21"),
+ USB_KEYMAP(0x070071, 0x0000, 0x0069, 0xffff, "F22"),
+ USB_KEYMAP(0x070072, 0x0000, 0x006a, 0xffff, "F23"),
+ USB_KEYMAP(0x070073, 0x0000, 0x006b, 0xffff, "F24"),
+ USB_KEYMAP(0x070074, 0x0000, 0x0000, 0xffff, NULL), // Execute
+ USB_KEYMAP(0x070075, 0x0092, 0xe03b, 0xffff, "Help"),
+ USB_KEYMAP(0x070076, 0x0093, 0x0000, 0xffff, NULL), // Menu
+ //USB_KEYMAP(0x070077, 0x0000, 0x0000, 0xffff, NULL), // Select
+
+ //USB_KEYMAP(0x070078, 0x0000, 0x0000, 0xffff, NULL), // Stop
+ USB_KEYMAP(0x070079, 0x0089, 0x0000, 0xffff, NULL), // Again (Redo)
+ USB_KEYMAP(0x07007a, 0x008b, 0xe008, 0xffff, "Undo"),
+ USB_KEYMAP(0x07007b, 0x0091, 0xe017, 0xffff, "Cut"),
+ USB_KEYMAP(0x07007c, 0x008d, 0xe018, 0xffff, "Copy"),
+ USB_KEYMAP(0x07007d, 0x008f, 0xe00a, 0xffff, "Paste"),
+ USB_KEYMAP(0x07007e, 0x0090, 0x0000, 0xffff, NULL), // Find
+ USB_KEYMAP(0x07007f, 0x0079, 0xe020, 0x004a, "VolumeMute"),
+
+ USB_KEYMAP(0x070080, 0x007b, 0xe030, 0x0048, "VolumeUp"),
+ USB_KEYMAP(0x070081, 0x007a, 0xe02e, 0x0049, "VolumeDown"),
+ //USB_KEYMAP(0x070082, 0x0000, 0x0000, 0xffff, NULL), // LockingCapsLock
+ //USB_KEYMAP(0x070083, 0x0000, 0x0000, 0xffff, NULL), // LockingNumLock
+ //USB_KEYMAP(0x070084, 0x0000, 0x0000, 0xffff, NULL), // LockingScrollLock
+ // USB#070085 is used as Brazilian Keypad_.
+ USB_KEYMAP(0x070085, 0x0000, 0x0000, 0x005f, "NumpadComma"), // Keypad_Comma
+
+ // International1
+ // USB#070086 is used on AS/400 keyboards. Standard Keypad_= is USB#070067.
+ //USB_KEYMAP(0x070086, 0x0000, 0x0000, 0xffff, NULL), // Keypad_=
+ // USB#070087 is used for Brazilian /? and Japanese _ 'ro'.
+ USB_KEYMAP(0x070087, 0x0061, 0x0000, 0x005e, "IntlRo"),
+ // International2
+ // USB#070088 is used as Japanese Hiragana/Katakana key.
+ USB_KEYMAP(0x070088, 0x0065, 0x0000, 0x0068, "KanaMode"),
+ // International3
+ // USB#070089 is used as Japanese Yen key.
+ USB_KEYMAP(0x070089, 0x0084, 0x007d, 0x005d, "IntlYen"),
+ // International4
+ // USB#07008a is used as Japanese Henkan (Convert) key.
+ USB_KEYMAP(0x07008a, 0x0064, 0x0000, 0xffff, "Convert"),
+ // International5
+ // USB#07008b is used as Japanese Muhenkan (No-convert) key.
+ USB_KEYMAP(0x07008b, 0x0066, 0x0000, 0xffff, "NoConvert"),
+ //USB_KEYMAP(0x07008c, 0x0000, 0x0000, 0xffff, NULL), // International6
+ //USB_KEYMAP(0x07008d, 0x0000, 0x0000, 0xffff, NULL), // International7
+ //USB_KEYMAP(0x07008e, 0x0000, 0x0000, 0xffff, NULL), // International8
+ //USB_KEYMAP(0x07008f, 0x0000, 0x0000, 0xffff, NULL), // International9
+
+ // LANG1
+ // USB#070090 is used as Korean Hangul/English toggle key.
+ USB_KEYMAP(0x070090, 0x0082, 0x0000, 0xffff, "HangulMode"),
+ // LANG2
+ // USB#070091 is used as Korean Hanja conversion key.
+ USB_KEYMAP(0x070091, 0x0083, 0x0000, 0xffff, "Hanja"),
+ // LANG3
+ // USB#070092 is used as Japanese Katakana key.
+ USB_KEYMAP(0x070092, 0x0062, 0x0000, 0xffff, NULL),
+ // LANG4
+ // USB#070093 is used as Japanese Hiragana key.
+ USB_KEYMAP(0x070093, 0x0063, 0x0000, 0xffff, NULL),
+ // LANG5
+ // USB#070094 is used as Japanese Zenkaku/Hankaku (Fullwidth/halfwidth) key.
+ //USB_KEYMAP(0x070094, 0x0000, 0x0000, 0xffff, NULL),
+ //USB_KEYMAP(0x070095, 0x0000, 0x0000, 0xffff, NULL), // LANG6
+ //USB_KEYMAP(0x070096, 0x0000, 0x0000, 0xffff, NULL), // LANG7
+ //USB_KEYMAP(0x070097, 0x0000, 0x0000, 0xffff, NULL), // LANG8
+ //USB_KEYMAP(0x070098, 0x0000, 0x0000, 0xffff, NULL), // LANG9
+
+ //USB_KEYMAP(0x070099, 0x0000, 0x0000, 0xffff, NULL), // AlternateErase
+ //USB_KEYMAP(0x07009a, 0x0000, 0x0000, 0xffff, NULL), // SysReq/Attention
+ USB_KEYMAP(0x07009b, 0x0088, 0x0000, 0xffff, "Cancel"),
+ //USB_KEYMAP(0x07009c, 0x0000, 0x0000, 0xffff, NULL), // Clear
+ //USB_KEYMAP(0x07009d, 0x0000, 0x0000, 0xffff, NULL), // Prior
+ //USB_KEYMAP(0x07009e, 0x0000, 0x0000, 0xffff, NULL), // Return
+ //USB_KEYMAP(0x07009f, 0x0000, 0x0000, 0xffff, NULL), // Separator
+
+ //USB_KEYMAP(0x0700a0, 0x0000, 0x0000, 0xffff, NULL), // Out
+ //USB_KEYMAP(0x0700a1, 0x0000, 0x0000, 0xffff, NULL), // Oper
+ //USB_KEYMAP(0x0700a2, 0x0000, 0x0000, 0xffff, NULL), // Clear/Again
+ //USB_KEYMAP(0x0700a3, 0x0000, 0x0000, 0xffff, NULL), // CrSel/Props
+ //USB_KEYMAP(0x0700a4, 0x0000, 0x0000, 0xffff, NULL), // ExSel
+
+ //USB_KEYMAP(0x0700b0, 0x0000, 0x0000, 0xffff, NULL), // Keypad_00
+ //USB_KEYMAP(0x0700b1, 0x0000, 0x0000, 0xffff, NULL), // Keypad_000
+ //USB_KEYMAP(0x0700b2, 0x0000, 0x0000, 0xffff, NULL), // ThousandsSeparator
+ //USB_KEYMAP(0x0700b3, 0x0000, 0x0000, 0xffff, NULL), // DecimalSeparator
+ //USB_KEYMAP(0x0700b4, 0x0000, 0x0000, 0xffff, NULL), // CurrencyUnit
+ //USB_KEYMAP(0x0700b5, 0x0000, 0x0000, 0xffff, NULL), // CurrencySubunit
+ USB_KEYMAP(0x0700b6, 0x00bb, 0x0000, 0xffff, "NumpadParenLeft"), // Keypad_(
+ USB_KEYMAP(0x0700b7, 0x00bc, 0x0000, 0xffff, "NumpadParenRight"), // Keypad_)
+
+ //USB_KEYMAP(0x0700b8, 0x0000, 0x0000, 0xffff, NULL), // Keypad_{
+ //USB_KEYMAP(0x0700b9, 0x0000, 0x0000, 0xffff, NULL), // Keypad_}
+ //USB_KEYMAP(0x0700ba, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Tab
+ //USB_KEYMAP(0x0700bb, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Backspace
+ //USB_KEYMAP(0x0700bc, 0x0000, 0x0000, 0xffff, NULL), // Keypad_A
+ //USB_KEYMAP(0x0700bd, 0x0000, 0x0000, 0xffff, NULL), // Keypad_B
+ //USB_KEYMAP(0x0700be, 0x0000, 0x0000, 0xffff, NULL), // Keypad_C
+ //USB_KEYMAP(0x0700bf, 0x0000, 0x0000, 0xffff, NULL), // Keypad_D
+
+ //USB_KEYMAP(0x0700c0, 0x0000, 0x0000, 0xffff, NULL), // Keypad_E
+ //USB_KEYMAP(0x0700c1, 0x0000, 0x0000, 0xffff, NULL), // Keypad_F
+ //USB_KEYMAP(0x0700c2, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Xor
+ //USB_KEYMAP(0x0700c3, 0x0000, 0x0000, 0xffff, NULL), // Keypad_^
+ //USB_KEYMAP(0x0700c4, 0x0000, 0x0000, 0xffff, NULL), // Keypad_%
+ //USB_KEYMAP(0x0700c5, 0x0000, 0x0000, 0xffff, NULL), // Keypad_<
+ //USB_KEYMAP(0x0700c6, 0x0000, 0x0000, 0xffff, NULL), // Keypad_>
+ //USB_KEYMAP(0x0700c7, 0x0000, 0x0000, 0xffff, NULL), // Keypad_&
+
+ //USB_KEYMAP(0x0700c8, 0x0000, 0x0000, 0xffff, NULL), // Keypad_&&
+ //USB_KEYMAP(0x0700c9, 0x0000, 0x0000, 0xffff, NULL), // Keypad_|
+ //USB_KEYMAP(0x0700ca, 0x0000, 0x0000, 0xffff, NULL), // Keypad_||
+ //USB_KEYMAP(0x0700cb, 0x0000, 0x0000, 0xffff, NULL), // Keypad_:
+ //USB_KEYMAP(0x0700cc, 0x0000, 0x0000, 0xffff, NULL), // Keypad_#
+ //USB_KEYMAP(0x0700cd, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Space
+ //USB_KEYMAP(0x0700ce, 0x0000, 0x0000, 0xffff, NULL), // Keypad_@
+ //USB_KEYMAP(0x0700cf, 0x0000, 0x0000, 0xffff, NULL), // Keypad_!
+
+ //USB_KEYMAP(0x0700d0, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryStore
+ //USB_KEYMAP(0x0700d1, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryRecall
+ //USB_KEYMAP(0x0700d2, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryClear
+ //USB_KEYMAP(0x0700d3, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryAdd
+ //USB_KEYMAP(0x0700d4, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemorySubtract
+ //USB_KEYMAP(0x0700d5, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryMultiply
+ //USB_KEYMAP(0x0700d6, 0x0000, 0x0000, 0xffff, NULL), // Keypad_MemoryDivide
+ USB_KEYMAP(0x0700d7, 0x007e, 0x0000, 0xffff, "NumpadChangeSign"), // Keypad_+/-
+
+ //USB_KEYMAP(0x0700d8, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Clear
+ //USB_KEYMAP(0x0700d9, 0x0000, 0x0000, 0xffff, NULL), // Keypad_ClearEntry
+ //USB_KEYMAP(0x0700da, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Binary
+ //USB_KEYMAP(0x0700db, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Octal
+ USB_KEYMAP(0x0700dc, 0x0081, 0x0000, 0xffff, NULL), // Keypad_Decimal
+ //USB_KEYMAP(0x0700dd, 0x0000, 0x0000, 0xffff, NULL), // Keypad_Hexadecimal
+ // USB#0700de - #0700df are reserved.
+
+ USB_KEYMAP(0x0700e0, 0x0025, 0x001d, 0x003b, "ControlLeft"),
+ USB_KEYMAP(0x0700e1, 0x0032, 0x002a, 0x0038, "ShiftLeft"),
+ USB_KEYMAP(0x0700e2, 0x0040, 0x0038, 0x003a, "AltLeft"), // LeftAlt/Option
+ USB_KEYMAP(0x0700e3, 0x0085, 0xe05b, 0x0037, "OSLeft"), // LeftGUI/Super/Win/Cmd
+ USB_KEYMAP(0x0700e4, 0x0069, 0xe01d, 0x003e, "ControlRight"),
+ USB_KEYMAP(0x0700e5, 0x003e, 0x0036, 0x003c, "ShiftRight"),
+ USB_KEYMAP(0x0700e6, 0x006c, 0xe038, 0x003d, "AltRight"), // RightAlt/Option
+ USB_KEYMAP(0x0700e7, 0x0086, 0xe05c, 0x0036, "OSRight"), // RightGUI/Super/Win/Cmd
+
+ // USB#0700e8 - #07ffff are reserved
+
+ // ==================================
+ // USB Usage Page 0x0c: Consumer Page
+ // ==================================
+ // AL = Application Launch
+ // AC = Application Control
+
+ // TODO(garykac): Many XF86 keys have multiple scancodes mapping to them.
+ // We need to map all of these into a canonical USB scancode without
+ // confusing the reverse-lookup - most likely by simply returning the first
+ // found match.
+
+ // TODO(garykac): Find appropriate mappings for:
+ // Win#e06b LaunchApp1 (My Computer?)
+ // Win#e021 LaunchApp2 (Calculator?)
+ // Win#e03c Music - USB#0c0193 is AL_AVCapturePlayback
+ // Win#e06d Video - USB#0c0193 is AL_AVCapturePlayback
+ // Win#e064 Pictures
+ // XKB#0080 XF86LaunchA
+ // XKB#0097 XF86WakeUp
+ // XKB#0099 XF86Send
+ // XKB#009b XF86Xfer
+ // XKB#009c XF86Launch1
+ // XKB#009d XF86Launch2
+ // XKB... remaining XF86 keys
+
+ // USB XKB Win Mac
+ USB_KEYMAP(0x0c00b5, 0x0000, 0xe019, 0xffff, "MediaTrackNext"),
+ USB_KEYMAP(0x0c00b6, 0x0000, 0xe010, 0xffff, "MediaTrackPrevious"),
+ USB_KEYMAP(0x0c00b7, 0x0000, 0xe024, 0xffff, "MediaStop"),
+ USB_KEYMAP(0x0c00b8, 0x0000, 0xe02c, 0xffff, "Eject"),
+ USB_KEYMAP(0x0c00cd, 0x0000, 0xe022, 0xffff, "MediaPlayPause"),
+ USB_KEYMAP(0x0c018a, 0x0000, 0xe01e, 0xffff, "LaunchMail"), // AL_EmailReader
+ USB_KEYMAP(0x0c0192, 0x0094, 0x0000, 0xffff, NULL), // AL_Calculator
+ // USB#0c0194: My Computer
+ USB_KEYMAP(0x0c0194, 0x00a5, 0x0000, 0xffff, NULL), // AL_LocalMachineBrowser
+ USB_KEYMAP(0x0c01a7, 0x00f3, 0x0000, 0xffff, NULL), // AL_Documents
+ // USB#0c01b4: Home Directory
+ USB_KEYMAP(0x0c01b4, 0x0098, 0x0000, 0xffff, NULL), // AL_FileBrowser (Explorer)
+ USB_KEYMAP(0x0c0221, 0x0000, 0xe065, 0xffff, "BrowserSearch"), // AC_Search
+ USB_KEYMAP(0x0c0223, 0x0000, 0xe032, 0xffff, "BrowserHome"), // AC_Home
+ USB_KEYMAP(0x0c0224, 0x00a6, 0xe06a, 0xffff, "BrowserBack"), // AC_Back
+ USB_KEYMAP(0x0c0225, 0x00a7, 0xe069, 0xffff, "BrowserForward"), // AC_Forward
+ USB_KEYMAP(0x0c0226, 0x0000, 0xe068, 0xffff, "BrowserStop"), // AC_Stop
+ USB_KEYMAP(0x0c0227, 0x00b5, 0xe067, 0xffff, "BrowserRefresh"), // AC_Refresh (Reload)
+ USB_KEYMAP(0x0c022a, 0x00a4, 0xe066, 0xffff, NULL), // AC_Bookmarks (Favorites)
+ USB_KEYMAP(0x0c0289, 0x00f0, 0x0000, 0xffff, NULL), // AC_Reply
+ USB_KEYMAP(0x0c028b, 0x00f1, 0x0000, 0xffff, NULL), // AC_ForwardMsg (MailForward)
+ USB_KEYMAP(0x0c028c, 0x00ef, 0x0000, 0xffff, NULL), // AC_Send
+};
+
+#endif // UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
diff --git a/ui/events/keycodes/dom4/keycode_converter_unittest.cc b/ui/events/keycodes/dom4/keycode_converter_unittest.cc
new file mode 100644
index 0000000..0b96583
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter_unittest.cc
@@ -0,0 +1,112 @@
+// 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/keycodes/dom4/keycode_converter.h"
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ui::KeycodeConverter;
+
+namespace {
+
+#if defined(OS_WIN)
+const size_t kExpectedMappedKeyCount = 138;
+#elif defined(OS_LINUX)
+const size_t kExpectedMappedKeyCount = 145;
+#elif defined(OS_MACOSX)
+const size_t kExpectedMappedKeyCount = 118;
+#else
+const size_t kExpectedMappedKeyCount = 0;
+#endif
+
+const uint32_t kUsbNonExistentKeycode = 0xffffff;
+const uint32_t kUsbUsBackslash = 0x070031;
+const uint32_t kUsbNonUsHash = 0x070032;
+
+TEST(UsbKeycodeMap, Basic) {
+ // Verify that the first element in the table is the "invalid" code.
+ const ui::KeycodeMapEntry* keycode_map =
+ ui::KeycodeConverter::GetKeycodeMapForTest();
+ EXPECT_EQ(ui::KeycodeConverter::InvalidUsbKeycode(),
+ keycode_map[0].usb_keycode);
+ EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+ keycode_map[0].native_keycode);
+ EXPECT_STREQ(ui::KeycodeConverter::InvalidKeyboardEventCode(),
+ "Unidentified");
+ EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+ ui::KeycodeConverter::CodeToNativeKeycode("Unidentified"));
+
+ // Verify that there are no duplicate entries in the mapping.
+ std::map<uint32_t, uint16_t> usb_to_native;
+ std::map<uint16_t, uint32_t> native_to_usb;
+ size_t numEntries = ui::KeycodeConverter::NumKeycodeMapEntriesForTest();
+ for (size_t i = 0; i < numEntries; ++i) {
+ const ui::KeycodeMapEntry* entry = &keycode_map[i];
+ // Don't test keys with no native keycode mapping on this platform.
+ if (entry->native_keycode == ui::KeycodeConverter::InvalidNativeKeycode())
+ continue;
+
+ // Verify UsbKeycodeToNativeKeycode works for this key.
+ EXPECT_EQ(
+ entry->native_keycode,
+ ui::KeycodeConverter::UsbKeycodeToNativeKeycode(entry->usb_keycode));
+
+ // Verify CodeToNativeKeycode and NativeKeycodeToCode work correctly.
+ if (entry->code) {
+ EXPECT_EQ(entry->native_keycode,
+ ui::KeycodeConverter::CodeToNativeKeycode(entry->code));
+ EXPECT_STREQ(
+ entry->code,
+ ui::KeycodeConverter::NativeKeycodeToCode(entry->native_keycode));
+ }
+ else {
+ EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+ ui::KeycodeConverter::CodeToNativeKeycode(entry->code));
+ }
+
+ // Verify that the USB or native codes aren't duplicated.
+ EXPECT_EQ(0U, usb_to_native.count(entry->usb_keycode))
+ << " duplicate of USB code 0x" << std::hex << std::setfill('0')
+ << std::setw(6) << entry->usb_keycode
+ << " to native 0x"
+ << std::setw(4) << entry->native_keycode
+ << " (previous was 0x"
+ << std::setw(4) << usb_to_native[entry->usb_keycode]
+ << ")";
+ usb_to_native[entry->usb_keycode] = entry->native_keycode;
+ EXPECT_EQ(0U, native_to_usb.count(entry->native_keycode))
+ << " duplicate of native code 0x" << std::hex << std::setfill('0')
+ << std::setw(4) << entry->native_keycode
+ << " to USB 0x"
+ << std::setw(6) << entry->usb_keycode
+ << " (previous was 0x"
+ << std::setw(6) << native_to_usb[entry->native_keycode]
+ << ")";
+ native_to_usb[entry->native_keycode] = entry->usb_keycode;
+ }
+ ASSERT_EQ(usb_to_native.size(), native_to_usb.size());
+
+ // Verify that the number of mapped keys is what we expect, i.e. we haven't
+ // lost any, and if we've added some then the expectation has been updated.
+ EXPECT_EQ(kExpectedMappedKeyCount, usb_to_native.size());
+}
+
+TEST(UsbKeycodeMap, NonExistent) {
+ // Verify that UsbKeycodeToNativeKeycode works for a non-existent USB keycode.
+ EXPECT_EQ(
+ ui::KeycodeConverter::InvalidNativeKeycode(),
+ ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbNonExistentKeycode));
+}
+
+TEST(UsbKeycodeMap, UsBackslashIsNonUsHash) {
+ // Verify that UsbKeycodeToNativeKeycode treats the non-US "hash" key
+ // as equivalent to the US "backslash" key.
+ EXPECT_EQ(ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbUsBackslash),
+ ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbNonUsHash));
+}
+
+} // namespace
diff --git a/ui/events/keycodes/keyboard_code_conversion.cc b/ui/events/keycodes/keyboard_code_conversion.cc
new file mode 100644
index 0000000..a812b51
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion.cc
@@ -0,0 +1,115 @@
+// 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.
+
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+
+#include "ui/events/event_constants.h"
+
+namespace ui {
+
+uint16 GetCharacterFromKeyCode(KeyboardCode key_code, int flags) {
+ const bool ctrl = (flags & EF_CONTROL_DOWN) != 0;
+ const bool shift = (flags & EF_SHIFT_DOWN) != 0;
+ const bool upper = shift ^ ((flags & EF_CAPS_LOCK_DOWN) != 0);
+
+ // Following Windows behavior to map ctrl-a ~ ctrl-z to \x01 ~ \x1A.
+ if (key_code >= VKEY_A && key_code <= VKEY_Z)
+ return key_code - VKEY_A + (ctrl ? 1 : (upper ? 'A' : 'a'));
+
+ // Other ctrl characters
+ if (ctrl) {
+ if (shift) {
+ // following graphics chars require shift key to input.
+ switch (key_code) {
+ // ctrl-@ maps to \x00 (Null byte)
+ case VKEY_2:
+ return 0;
+ // ctrl-^ maps to \x1E (Record separator, Information separator two)
+ case VKEY_6:
+ return 0x1E;
+ // ctrl-_ maps to \x1F (Unit separator, Information separator one)
+ case VKEY_OEM_MINUS:
+ return 0x1F;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ return 0;
+ }
+ } else {
+ switch (key_code) {
+ // ctrl-[ maps to \x1B (Escape)
+ case VKEY_OEM_4:
+ return 0x1B;
+ // ctrl-\ maps to \x1C (File separator, Information separator four)
+ case VKEY_OEM_5:
+ return 0x1C;
+ // ctrl-] maps to \x1D (Group separator, Information separator three)
+ case VKEY_OEM_6:
+ return 0x1D;
+ // ctrl-Enter maps to \x0A (Line feed)
+ case VKEY_RETURN:
+ return 0x0A;
+ // Returns 0 for all other keys to avoid inputting unexpected chars.
+ default:
+ return 0;
+ }
+ }
+ }
+
+ // For IME support.
+ if (key_code == ui::VKEY_PROCESSKEY)
+ return 0xE5;
+
+ // Normal characters
+ if (key_code >= VKEY_0 && key_code <= VKEY_9) {
+ return shift ? ")!@#$%^&*("[key_code - VKEY_0] :
+ static_cast<uint16>(key_code);
+ } else if (key_code >= VKEY_NUMPAD0 && key_code <= VKEY_NUMPAD9) {
+ return key_code - VKEY_NUMPAD0 + '0';
+ }
+
+ switch (key_code) {
+ case VKEY_TAB:
+ return '\t';
+ case VKEY_RETURN:
+ return '\r';
+ case VKEY_MULTIPLY:
+ return '*';
+ case VKEY_ADD:
+ return '+';
+ case VKEY_SUBTRACT:
+ return '-';
+ case VKEY_DECIMAL:
+ return '.';
+ case VKEY_DIVIDE:
+ return '/';
+ case VKEY_SPACE:
+ return ' ';
+ case VKEY_OEM_1:
+ return shift ? ':' : ';';
+ case VKEY_OEM_PLUS:
+ return shift ? '+' : '=';
+ case VKEY_OEM_COMMA:
+ return shift ? '<' : ',';
+ case VKEY_OEM_MINUS:
+ return shift ? '_' : '-';
+ case VKEY_OEM_PERIOD:
+ return shift ? '>' : '.';
+ case VKEY_OEM_2:
+ return shift ? '?' : '/';
+ case VKEY_OEM_3:
+ return shift ? '~' : '`';
+ case VKEY_OEM_4:
+ return shift ? '{' : '[';
+ case VKEY_OEM_5:
+ return shift ? '|' : '\\';
+ case VKEY_OEM_6:
+ return shift ? '}' : ']';
+ case VKEY_OEM_7:
+ return shift ? '"' : '\'';
+ default:
+ return 0;
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion.h b/ui/events/keycodes/keyboard_code_conversion.h
new file mode 100644
index 0000000..b7bdd57
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace ui {
+
+// A helper function to get the character generated by a key event in a
+// platform independent way. It supports control characters as well.
+// It assumes a US keyboard layout is used, so it may only be used when there
+// is no native event or no better way to get the character.
+// For example, if a virtual keyboard implementation can only generate key
+// events with key_code and flags information, then there is no way for us to
+// determine the actual character that should be generate by the key. Because
+// a key_code only represents a physical key on the keyboard, it has nothing
+// to do with the actual character printed on that key. In such case, the only
+// thing we can do is to assume that we are using a US keyboard and get the
+// character according to US keyboard layout definition.
+// If a virtual keyboard implementation wants to support other keyboard
+// layouts, that may generate different text for a certain key than on a US
+// keyboard, a special native event object should be introduced to carry extra
+// information to help determine the correct character.
+// Take XKeyEvent as an example, it contains not only keycode and modifier
+// flags but also group and other extra XKB information to help determine the
+// correct character. That's why we can use XLookupString() function to get
+// the correct text generated by a X key event (See how is GetCharacter()
+// implemented in event_x.cc).
+EVENTS_BASE_EXPORT uint16 GetCharacterFromKeyCode(KeyboardCode key_code,
+ int flags);
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_android.cc b/ui/events/keycodes/keyboard_code_conversion_android.cc
new file mode 100644
index 0000000..b7bdc83
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_android.cc
@@ -0,0 +1,307 @@
+// 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/keycodes/keyboard_code_conversion_android.h"
+
+#include <android/keycodes.h>
+
+namespace ui {
+
+namespace {
+
+// The Android NDK does not provide values for these yet:
+enum {
+ AKEYCODE_ESCAPE = 111,
+ AKEYCODE_FORWARD_DEL = 112,
+ AKEYCODE_CTRL_LEFT = 113,
+ AKEYCODE_CTRL_RIGHT = 114,
+ AKEYCODE_CAPS_LOCK = 115,
+ AKEYCODE_SCROLL_LOCK = 116,
+ AKEYCODE_META_LEFT = 117,
+ AKEYCODE_META_RIGHT = 118,
+ AKEYCODE_BREAK = 121,
+ AKEYCODE_MOVE_HOME = 122,
+ AKEYCODE_MOVE_END = 123,
+ AKEYCODE_INSERT = 124,
+ AKEYCODE_MEDIA_PLAY = 126,
+ AKEYCODE_MEDIA_PAUSE = 127,
+ AKEYCODE_F1 = 131,
+ AKEYCODE_F2 = 132,
+ AKEYCODE_F3 = 133,
+ AKEYCODE_F4 = 134,
+ AKEYCODE_F5 = 135,
+ AKEYCODE_F6 = 136,
+ AKEYCODE_F7 = 137,
+ AKEYCODE_F8 = 138,
+ AKEYCODE_F9 = 139,
+ AKEYCODE_F10 = 140,
+ AKEYCODE_F11 = 141,
+ AKEYCODE_F12 = 142,
+ AKEYCODE_NUM_LOCK = 143,
+ AKEYCODE_NUMPAD_0 = 144,
+ AKEYCODE_NUMPAD_1 = 145,
+ AKEYCODE_NUMPAD_2 = 146,
+ AKEYCODE_NUMPAD_3 = 147,
+ AKEYCODE_NUMPAD_4 = 148,
+ AKEYCODE_NUMPAD_5 = 149,
+ AKEYCODE_NUMPAD_6 = 150,
+ AKEYCODE_NUMPAD_7 = 151,
+ AKEYCODE_NUMPAD_8 = 152,
+ AKEYCODE_NUMPAD_9 = 153,
+ AKEYCODE_NUMPAD_DIVIDE = 154,
+ AKEYCODE_NUMPAD_MULTIPLY = 155,
+ AKEYCODE_NUMPAD_SUBTRACT = 156,
+ AKEYCODE_NUMPAD_ADD = 157,
+ AKEYCODE_NUMPAD_DOT = 158,
+ AKEYCODE_VOLUME_MUTE = 164,
+ AKEYCODE_CHANNEL_UP = 166,
+ AKEYCODE_CHANNEL_DOWN = 167,
+};
+
+} // namespace
+
+KeyboardCode KeyboardCodeFromAndroidKeyCode(int keycode) {
+ // Does not provide all key codes, and does not handle all keys.
+ switch (keycode) {
+ case AKEYCODE_DEL:
+ return VKEY_BACK;
+ case AKEYCODE_TAB:
+ return VKEY_TAB;
+ case AKEYCODE_CLEAR:
+ return VKEY_CLEAR;
+ case AKEYCODE_DPAD_CENTER:
+ case AKEYCODE_ENTER:
+ return VKEY_RETURN;
+ case AKEYCODE_SHIFT_LEFT:
+ return VKEY_LSHIFT;
+ case AKEYCODE_SHIFT_RIGHT:
+ return VKEY_RSHIFT;
+ // Back will serve as escape, although we may not have access to it.
+ case AKEYCODE_BACK:
+ return VKEY_ESCAPE;
+ case AKEYCODE_SPACE:
+ return VKEY_SPACE;
+ case AKEYCODE_MOVE_HOME:
+ return VKEY_HOME;
+ case AKEYCODE_DPAD_LEFT:
+ return VKEY_LEFT;
+ case AKEYCODE_DPAD_UP:
+ return VKEY_UP;
+ case AKEYCODE_DPAD_RIGHT:
+ return VKEY_RIGHT;
+ case AKEYCODE_DPAD_DOWN:
+ return VKEY_DOWN;
+ case AKEYCODE_0:
+ return VKEY_0;
+ case AKEYCODE_1:
+ return VKEY_1;
+ case AKEYCODE_2:
+ return VKEY_2;
+ case AKEYCODE_3:
+ return VKEY_3;
+ case AKEYCODE_4:
+ return VKEY_4;
+ case AKEYCODE_5:
+ return VKEY_5;
+ case AKEYCODE_6:
+ return VKEY_6;
+ case AKEYCODE_7:
+ return VKEY_7;
+ case AKEYCODE_8:
+ return VKEY_8;
+ case AKEYCODE_9:
+ return VKEY_9;
+ case AKEYCODE_A:
+ return VKEY_A;
+ case AKEYCODE_B:
+ return VKEY_B;
+ case AKEYCODE_C:
+ return VKEY_C;
+ case AKEYCODE_D:
+ return VKEY_D;
+ case AKEYCODE_E:
+ return VKEY_E;
+ case AKEYCODE_F:
+ return VKEY_F;
+ case AKEYCODE_G:
+ return VKEY_G;
+ case AKEYCODE_H:
+ return VKEY_H;
+ case AKEYCODE_I:
+ return VKEY_I;
+ case AKEYCODE_J:
+ return VKEY_J;
+ case AKEYCODE_K:
+ return VKEY_K;
+ case AKEYCODE_L:
+ return VKEY_L;
+ case AKEYCODE_M:
+ return VKEY_M;
+ case AKEYCODE_N:
+ return VKEY_N;
+ case AKEYCODE_O:
+ return VKEY_O;
+ case AKEYCODE_P:
+ return VKEY_P;
+ case AKEYCODE_Q:
+ return VKEY_Q;
+ case AKEYCODE_R:
+ return VKEY_R;
+ case AKEYCODE_S:
+ return VKEY_S;
+ case AKEYCODE_T:
+ return VKEY_T;
+ case AKEYCODE_U:
+ return VKEY_U;
+ case AKEYCODE_V:
+ return VKEY_V;
+ case AKEYCODE_W:
+ return VKEY_W;
+ case AKEYCODE_X:
+ return VKEY_X;
+ case AKEYCODE_Y:
+ return VKEY_Y;
+ case AKEYCODE_Z:
+ return VKEY_Z;
+ case AKEYCODE_VOLUME_DOWN:
+ return VKEY_VOLUME_DOWN;
+ case AKEYCODE_VOLUME_UP:
+ return VKEY_VOLUME_UP;
+ case AKEYCODE_MEDIA_NEXT:
+ return VKEY_MEDIA_NEXT_TRACK;
+ case AKEYCODE_MEDIA_PREVIOUS:
+ return VKEY_MEDIA_PREV_TRACK;
+ case AKEYCODE_MEDIA_STOP:
+ return VKEY_MEDIA_STOP;
+ case AKEYCODE_MEDIA_PAUSE:
+ return VKEY_MEDIA_PLAY_PAUSE;
+ // Colon key.
+ case AKEYCODE_SEMICOLON:
+ return VKEY_OEM_1;
+ case AKEYCODE_COMMA:
+ return VKEY_OEM_COMMA;
+ case AKEYCODE_MINUS:
+ return VKEY_OEM_MINUS;
+ case AKEYCODE_EQUALS:
+ return VKEY_OEM_PLUS;
+ case AKEYCODE_PERIOD:
+ return VKEY_OEM_PERIOD;
+ case AKEYCODE_SLASH:
+ return VKEY_OEM_2;
+ case AKEYCODE_LEFT_BRACKET:
+ return VKEY_OEM_4;
+ case AKEYCODE_BACKSLASH:
+ return VKEY_OEM_5;
+ case AKEYCODE_RIGHT_BRACKET:
+ return VKEY_OEM_6;
+ case AKEYCODE_MUTE:
+ case AKEYCODE_VOLUME_MUTE:
+ return VKEY_VOLUME_MUTE;
+ case AKEYCODE_ESCAPE:
+ return VKEY_ESCAPE;
+ case AKEYCODE_MEDIA_PLAY:
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ return VKEY_MEDIA_PLAY_PAUSE;
+ case AKEYCODE_MOVE_END:
+ return VKEY_END;
+ case AKEYCODE_ALT_LEFT:
+ return VKEY_LMENU;
+ case AKEYCODE_ALT_RIGHT:
+ return VKEY_RMENU;
+ case AKEYCODE_GRAVE:
+ return VKEY_OEM_3;
+ case AKEYCODE_APOSTROPHE:
+ return VKEY_OEM_3;
+ case AKEYCODE_MEDIA_REWIND:
+ return VKEY_OEM_103;
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ return VKEY_OEM_104;
+ case AKEYCODE_PAGE_UP:
+ return VKEY_PRIOR;
+ case AKEYCODE_PAGE_DOWN:
+ return VKEY_NEXT;
+ case AKEYCODE_FORWARD_DEL:
+ return VKEY_DELETE;
+ case AKEYCODE_CTRL_LEFT:
+ return VKEY_LCONTROL;
+ case AKEYCODE_CTRL_RIGHT:
+ return VKEY_RCONTROL;
+ case AKEYCODE_CAPS_LOCK:
+ return VKEY_CAPITAL;
+ case AKEYCODE_SCROLL_LOCK:
+ return VKEY_SCROLL;
+ case AKEYCODE_META_LEFT:
+ return VKEY_LWIN;
+ case AKEYCODE_META_RIGHT:
+ return VKEY_RWIN;
+ case AKEYCODE_BREAK:
+ return VKEY_PAUSE;
+ case AKEYCODE_INSERT:
+ return VKEY_INSERT;
+ case AKEYCODE_F1:
+ return VKEY_F1;
+ case AKEYCODE_F2:
+ return VKEY_F2;
+ case AKEYCODE_F3:
+ return VKEY_F3;
+ case AKEYCODE_F4:
+ return VKEY_F4;
+ case AKEYCODE_F5:
+ return VKEY_F5;
+ case AKEYCODE_F6:
+ return VKEY_F6;
+ case AKEYCODE_F7:
+ return VKEY_F7;
+ case AKEYCODE_F8:
+ return VKEY_F8;
+ case AKEYCODE_F9:
+ return VKEY_F9;
+ case AKEYCODE_F10:
+ return VKEY_F10;
+ case AKEYCODE_F11:
+ return VKEY_F11;
+ case AKEYCODE_F12:
+ return VKEY_F12;
+ case AKEYCODE_NUM_LOCK:
+ return VKEY_NUMLOCK;
+ case AKEYCODE_NUMPAD_0:
+ return VKEY_NUMPAD0;
+ case AKEYCODE_NUMPAD_1:
+ return VKEY_NUMPAD1;
+ case AKEYCODE_NUMPAD_2:
+ return VKEY_NUMPAD2;
+ case AKEYCODE_NUMPAD_3:
+ return VKEY_NUMPAD3;
+ case AKEYCODE_NUMPAD_4:
+ return VKEY_NUMPAD4;
+ case AKEYCODE_NUMPAD_5:
+ return VKEY_NUMPAD5;
+ case AKEYCODE_NUMPAD_6:
+ return VKEY_NUMPAD6;
+ case AKEYCODE_NUMPAD_7:
+ return VKEY_NUMPAD7;
+ case AKEYCODE_NUMPAD_8:
+ return VKEY_NUMPAD8;
+ case AKEYCODE_NUMPAD_9:
+ return VKEY_NUMPAD9;
+ case AKEYCODE_NUMPAD_DIVIDE:
+ return VKEY_DIVIDE;
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ return VKEY_MULTIPLY;
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ return VKEY_SUBTRACT;
+ case AKEYCODE_NUMPAD_ADD:
+ return VKEY_ADD;
+ case AKEYCODE_NUMPAD_DOT:
+ return VKEY_DECIMAL;
+ case AKEYCODE_CHANNEL_UP:
+ return VKEY_PRIOR;
+ case AKEYCODE_CHANNEL_DOWN:
+ return VKEY_NEXT;
+ default:
+ return VKEY_UNKNOWN;
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_android.h b/ui/events/keycodes/keyboard_code_conversion_android.h
new file mode 100644
index 0000000..23552f5
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_android.h
@@ -0,0 +1,17 @@
+// 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_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ui {
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromAndroidKeyCode(int keycode);
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_mac.h b/ui/events/keycodes/keyboard_code_conversion_mac.h
new file mode 100644
index 0000000..fee3981
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_mac.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+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 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
+// |characterIgnoringModifiers|.
+// -1 will be returned if the keycode can't be converted.
+// This function is mainly for simulating keyboard events in unit tests.
+// See |KeyboardCodeFromNSEvent| for reverse conversion.
+EVENTS_BASE_EXPORT int MacKeyCodeForWindowsKeyCode(
+ KeyboardCode keycode,
+ NSUInteger flags,
+ unichar* character,
+ unichar* characterIgnoringModifiers);
+
+// This implementation cribbed from:
+// third_party/WebKit/Source/web/mac/WebInputEventFactory.mm
+// Converts |event| into a |KeyboardCode|. The mapping is not direct as the Mac
+// has a different notion of key codes.
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromNSEvent(NSEvent* event);
+
+EVENTS_BASE_EXPORT const char* CodeFromNSEvent(NSEvent* event);
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_mac.mm b/ui/events/keycodes/keyboard_code_conversion_mac.mm
new file mode 100644
index 0000000..8f7daae
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_mac.mm
@@ -0,0 +1,551 @@
+// 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.
+
+#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
+
+#include <algorithm>
+
+#import <Carbon/Carbon.h>
+
+#include "base/logging.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+namespace {
+
+// A struct to hold a Windows keycode to Mac virtual keycode mapping.
+struct KeyCodeMap {
+ KeyboardCode keycode;
+ int macKeycode;
+ unichar characterIgnoringModifiers;
+};
+
+// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
+bool operator<(const KeyCodeMap& a, const KeyCodeMap& b) {
+ return a.keycode < b.keycode;
+}
+
+// This array must keep sorted ascending according to the value of |keycode|,
+// so that we can binary search it.
+// TODO(suzhe): This map is not complete, missing entries have macKeycode == -1.
+const KeyCodeMap kKeyCodesMap[] = {
+ { VKEY_BACK /* 0x08 */, kVK_Delete, kBackspaceCharCode },
+ { VKEY_TAB /* 0x09 */, kVK_Tab, kTabCharCode },
+ { VKEY_BACKTAB /* 0x0A */, 0x21E4, '\031' },
+ { VKEY_CLEAR /* 0x0C */, kVK_ANSI_KeypadClear, kClearCharCode },
+ { VKEY_RETURN /* 0x0D */, kVK_Return, kReturnCharCode },
+ { VKEY_SHIFT /* 0x10 */, kVK_Shift, 0 },
+ { VKEY_CONTROL /* 0x11 */, kVK_Control, 0 },
+ { VKEY_MENU /* 0x12 */, kVK_Option, 0 },
+ { VKEY_PAUSE /* 0x13 */, -1, NSPauseFunctionKey },
+ { VKEY_CAPITAL /* 0x14 */, kVK_CapsLock, 0 },
+ { VKEY_KANA /* 0x15 */, kVK_JIS_Kana, 0 },
+ { VKEY_HANGUL /* 0x15 */, -1, 0 },
+ { VKEY_JUNJA /* 0x17 */, -1, 0 },
+ { VKEY_FINAL /* 0x18 */, -1, 0 },
+ { VKEY_HANJA /* 0x19 */, -1, 0 },
+ { VKEY_KANJI /* 0x19 */, -1, 0 },
+ { VKEY_ESCAPE /* 0x1B */, kVK_Escape, kEscapeCharCode },
+ { VKEY_CONVERT /* 0x1C */, -1, 0 },
+ { VKEY_NONCONVERT /* 0x1D */, -1, 0 },
+ { VKEY_ACCEPT /* 0x1E */, -1, 0 },
+ { VKEY_MODECHANGE /* 0x1F */, -1, 0 },
+ { VKEY_SPACE /* 0x20 */, kVK_Space, kSpaceCharCode },
+ { VKEY_PRIOR /* 0x21 */, kVK_PageUp, NSPageUpFunctionKey },
+ { VKEY_NEXT /* 0x22 */, kVK_PageDown, NSPageDownFunctionKey },
+ { VKEY_END /* 0x23 */, kVK_End, NSEndFunctionKey },
+ { VKEY_HOME /* 0x24 */, kVK_Home, NSHomeFunctionKey },
+ { VKEY_LEFT /* 0x25 */, kVK_LeftArrow, NSLeftArrowFunctionKey },
+ { VKEY_UP /* 0x26 */, kVK_UpArrow, NSUpArrowFunctionKey },
+ { VKEY_RIGHT /* 0x27 */, kVK_RightArrow, NSRightArrowFunctionKey },
+ { VKEY_DOWN /* 0x28 */, kVK_DownArrow, NSDownArrowFunctionKey },
+ { VKEY_SELECT /* 0x29 */, -1, 0 },
+ { VKEY_PRINT /* 0x2A */, -1, NSPrintFunctionKey },
+ { VKEY_EXECUTE /* 0x2B */, -1, NSExecuteFunctionKey },
+ { VKEY_SNAPSHOT /* 0x2C */, -1, NSPrintScreenFunctionKey },
+ { VKEY_INSERT /* 0x2D */, -1, NSInsertFunctionKey },
+ { VKEY_DELETE /* 0x2E */, kVK_ForwardDelete, kDeleteCharCode },
+ { VKEY_HELP /* 0x2F */, kVK_Help, kHelpCharCode },
+ { VKEY_0 /* 0x30 */, kVK_ANSI_0, '0' },
+ { VKEY_1 /* 0x31 */, kVK_ANSI_1, '1' },
+ { VKEY_2 /* 0x32 */, kVK_ANSI_2, '2' },
+ { VKEY_3 /* 0x33 */, kVK_ANSI_3, '3' },
+ { VKEY_4 /* 0x34 */, kVK_ANSI_4, '4' },
+ { VKEY_5 /* 0x35 */, kVK_ANSI_5, '5' },
+ { VKEY_6 /* 0x36 */, kVK_ANSI_6, '6' },
+ { VKEY_7 /* 0x37 */, kVK_ANSI_7, '7' },
+ { VKEY_8 /* 0x38 */, kVK_ANSI_8, '8' },
+ { VKEY_9 /* 0x39 */, kVK_ANSI_9, '9' },
+ { VKEY_A /* 0x41 */, kVK_ANSI_A, 'a' },
+ { VKEY_B /* 0x42 */, kVK_ANSI_B, 'b' },
+ { VKEY_C /* 0x43 */, kVK_ANSI_C, 'c' },
+ { VKEY_D /* 0x44 */, kVK_ANSI_D, 'd' },
+ { VKEY_E /* 0x45 */, kVK_ANSI_E, 'e' },
+ { VKEY_F /* 0x46 */, kVK_ANSI_F, 'f' },
+ { VKEY_G /* 0x47 */, kVK_ANSI_G, 'g' },
+ { VKEY_H /* 0x48 */, kVK_ANSI_H, 'h' },
+ { VKEY_I /* 0x49 */, kVK_ANSI_I, 'i' },
+ { VKEY_J /* 0x4A */, kVK_ANSI_J, 'j' },
+ { VKEY_K /* 0x4B */, kVK_ANSI_K, 'k' },
+ { VKEY_L /* 0x4C */, kVK_ANSI_L, 'l' },
+ { VKEY_M /* 0x4D */, kVK_ANSI_M, 'm' },
+ { VKEY_N /* 0x4E */, kVK_ANSI_N, 'n' },
+ { VKEY_O /* 0x4F */, kVK_ANSI_O, 'o' },
+ { VKEY_P /* 0x50 */, kVK_ANSI_P, 'p' },
+ { VKEY_Q /* 0x51 */, kVK_ANSI_Q, 'q' },
+ { VKEY_R /* 0x52 */, kVK_ANSI_R, 'r' },
+ { VKEY_S /* 0x53 */, kVK_ANSI_S, 's' },
+ { VKEY_T /* 0x54 */, kVK_ANSI_T, 't' },
+ { VKEY_U /* 0x55 */, kVK_ANSI_U, 'u' },
+ { VKEY_V /* 0x56 */, kVK_ANSI_V, 'v' },
+ { VKEY_W /* 0x57 */, kVK_ANSI_W, 'w' },
+ { VKEY_X /* 0x58 */, kVK_ANSI_X, 'x' },
+ { VKEY_Y /* 0x59 */, kVK_ANSI_Y, 'y' },
+ { VKEY_Z /* 0x5A */, kVK_ANSI_Z, 'z' },
+ { VKEY_LWIN /* 0x5B */, kVK_Command, 0 },
+ { VKEY_RWIN /* 0x5C */, 0x36, 0 },
+ { VKEY_APPS /* 0x5D */, 0x36, 0 },
+ { VKEY_SLEEP /* 0x5F */, -1, 0 },
+ { VKEY_NUMPAD0 /* 0x60 */, kVK_ANSI_Keypad0, '0' },
+ { VKEY_NUMPAD1 /* 0x61 */, kVK_ANSI_Keypad1, '1' },
+ { VKEY_NUMPAD2 /* 0x62 */, kVK_ANSI_Keypad2, '2' },
+ { VKEY_NUMPAD3 /* 0x63 */, kVK_ANSI_Keypad3, '3' },
+ { VKEY_NUMPAD4 /* 0x64 */, kVK_ANSI_Keypad4, '4' },
+ { VKEY_NUMPAD5 /* 0x65 */, kVK_ANSI_Keypad5, '5' },
+ { VKEY_NUMPAD6 /* 0x66 */, kVK_ANSI_Keypad6, '6' },
+ { VKEY_NUMPAD7 /* 0x67 */, kVK_ANSI_Keypad7, '7' },
+ { VKEY_NUMPAD8 /* 0x68 */, kVK_ANSI_Keypad8, '8' },
+ { VKEY_NUMPAD9 /* 0x69 */, kVK_ANSI_Keypad9, '9' },
+ { VKEY_MULTIPLY /* 0x6A */, kVK_ANSI_KeypadMultiply, '*' },
+ { VKEY_ADD /* 0x6B */, kVK_ANSI_KeypadPlus, '+' },
+ { VKEY_SEPARATOR /* 0x6C */, -1, 0 },
+ { VKEY_SUBTRACT /* 0x6D */, kVK_ANSI_KeypadMinus, '-' },
+ { VKEY_DECIMAL /* 0x6E */, kVK_ANSI_KeypadDecimal, '.' },
+ { VKEY_DIVIDE /* 0x6F */, kVK_ANSI_KeypadDivide, '/' },
+ { VKEY_F1 /* 0x70 */, kVK_F1, NSF1FunctionKey },
+ { VKEY_F2 /* 0x71 */, kVK_F2, NSF2FunctionKey },
+ { VKEY_F3 /* 0x72 */, kVK_F3, NSF3FunctionKey },
+ { VKEY_F4 /* 0x73 */, kVK_F4, NSF4FunctionKey },
+ { VKEY_F5 /* 0x74 */, kVK_F5, NSF5FunctionKey },
+ { VKEY_F6 /* 0x75 */, kVK_F6, NSF6FunctionKey },
+ { VKEY_F7 /* 0x76 */, kVK_F7, NSF7FunctionKey },
+ { VKEY_F8 /* 0x77 */, kVK_F8, NSF8FunctionKey },
+ { VKEY_F9 /* 0x78 */, kVK_F9, NSF9FunctionKey },
+ { VKEY_F10 /* 0x79 */, kVK_F10, NSF10FunctionKey },
+ { VKEY_F11 /* 0x7A */, kVK_F11, NSF11FunctionKey },
+ { VKEY_F12 /* 0x7B */, kVK_F12, NSF12FunctionKey },
+ { VKEY_F13 /* 0x7C */, kVK_F13, NSF13FunctionKey },
+ { VKEY_F14 /* 0x7D */, kVK_F14, NSF14FunctionKey },
+ { VKEY_F15 /* 0x7E */, kVK_F15, NSF15FunctionKey },
+ { VKEY_F16 /* 0x7F */, kVK_F16, NSF16FunctionKey },
+ { VKEY_F17 /* 0x80 */, kVK_F17, NSF17FunctionKey },
+ { VKEY_F18 /* 0x81 */, kVK_F18, NSF18FunctionKey },
+ { VKEY_F19 /* 0x82 */, kVK_F19, NSF19FunctionKey },
+ { VKEY_F20 /* 0x83 */, kVK_F20, NSF20FunctionKey },
+ { VKEY_F21 /* 0x84 */, -1, NSF21FunctionKey },
+ { VKEY_F22 /* 0x85 */, -1, NSF22FunctionKey },
+ { VKEY_F23 /* 0x86 */, -1, NSF23FunctionKey },
+ { VKEY_F24 /* 0x87 */, -1, NSF24FunctionKey },
+ { VKEY_NUMLOCK /* 0x90 */, -1, 0 },
+ { VKEY_SCROLL /* 0x91 */, -1, NSScrollLockFunctionKey },
+ { VKEY_LSHIFT /* 0xA0 */, kVK_Shift, 0 },
+ { VKEY_RSHIFT /* 0xA1 */, kVK_Shift, 0 },
+ { VKEY_LCONTROL /* 0xA2 */, kVK_Control, 0 },
+ { VKEY_RCONTROL /* 0xA3 */, kVK_Control, 0 },
+ { VKEY_LMENU /* 0xA4 */, -1, 0 },
+ { VKEY_RMENU /* 0xA5 */, -1, 0 },
+ { VKEY_BROWSER_BACK /* 0xA6 */, -1, 0 },
+ { VKEY_BROWSER_FORWARD /* 0xA7 */, -1, 0 },
+ { VKEY_BROWSER_REFRESH /* 0xA8 */, -1, 0 },
+ { VKEY_BROWSER_STOP /* 0xA9 */, -1, 0 },
+ { VKEY_BROWSER_SEARCH /* 0xAA */, -1, 0 },
+ { VKEY_BROWSER_FAVORITES /* 0xAB */, -1, 0 },
+ { VKEY_BROWSER_HOME /* 0xAC */, -1, 0 },
+ { VKEY_VOLUME_MUTE /* 0xAD */, -1, 0 },
+ { VKEY_VOLUME_DOWN /* 0xAE */, -1, 0 },
+ { VKEY_VOLUME_UP /* 0xAF */, -1, 0 },
+ { VKEY_MEDIA_NEXT_TRACK /* 0xB0 */, -1, 0 },
+ { VKEY_MEDIA_PREV_TRACK /* 0xB1 */, -1, 0 },
+ { VKEY_MEDIA_STOP /* 0xB2 */, -1, 0 },
+ { VKEY_MEDIA_PLAY_PAUSE /* 0xB3 */, -1, 0 },
+ { VKEY_MEDIA_LAUNCH_MAIL /* 0xB4 */, -1, 0 },
+ { VKEY_MEDIA_LAUNCH_MEDIA_SELECT /* 0xB5 */, -1, 0 },
+ { VKEY_MEDIA_LAUNCH_APP1 /* 0xB6 */, -1, 0 },
+ { VKEY_MEDIA_LAUNCH_APP2 /* 0xB7 */, -1, 0 },
+ { VKEY_OEM_1 /* 0xBA */, kVK_ANSI_Semicolon, ';' },
+ { VKEY_OEM_PLUS /* 0xBB */, kVK_ANSI_Equal, '=' },
+ { VKEY_OEM_COMMA /* 0xBC */, kVK_ANSI_Comma, ',' },
+ { VKEY_OEM_MINUS /* 0xBD */, kVK_ANSI_Minus, '-' },
+ { VKEY_OEM_PERIOD /* 0xBE */, kVK_ANSI_Period, '.' },
+ { VKEY_OEM_2 /* 0xBF */, kVK_ANSI_Slash, '/' },
+ { VKEY_OEM_3 /* 0xC0 */, kVK_ANSI_Grave, '`' },
+ { VKEY_OEM_4 /* 0xDB */, kVK_ANSI_LeftBracket, '[' },
+ { VKEY_OEM_5 /* 0xDC */, kVK_ANSI_Backslash, '\\' },
+ { VKEY_OEM_6 /* 0xDD */, kVK_ANSI_RightBracket, ']' },
+ { VKEY_OEM_7 /* 0xDE */, kVK_ANSI_Quote, '\'' },
+ { VKEY_OEM_8 /* 0xDF */, -1, 0 },
+ { VKEY_OEM_102 /* 0xE2 */, -1, 0 },
+ { VKEY_PROCESSKEY /* 0xE5 */, -1, 0 },
+ { VKEY_PACKET /* 0xE7 */, -1, 0 },
+ { VKEY_ATTN /* 0xF6 */, -1, 0 },
+ { VKEY_CRSEL /* 0xF7 */, -1, 0 },
+ { VKEY_EXSEL /* 0xF8 */, -1, 0 },
+ { VKEY_EREOF /* 0xF9 */, -1, 0 },
+ { VKEY_PLAY /* 0xFA */, -1, 0 },
+ { VKEY_ZOOM /* 0xFB */, -1, 0 },
+ { VKEY_NONAME /* 0xFC */, -1, 0 },
+ { VKEY_PA1 /* 0xFD */, -1, 0 },
+ { VKEY_OEM_CLEAR /* 0xFE */, kVK_ANSI_KeypadClear, kClearCharCode }
+};
+
+// A convenient array for getting symbol characters on the number keys.
+const char kShiftCharsForNumberKeys[] = ")!@#$%^&*(";
+
+// Translates from character code to keyboard code.
+KeyboardCode KeyboardCodeFromCharCode(unichar charCode) {
+ switch (charCode) {
+ case 8: case 0x7F: return VKEY_BACK;
+ case 9: return VKEY_TAB;
+ case 0xD: case 3: return VKEY_RETURN;
+ case 0x1B: return VKEY_ESCAPE;
+ case ' ': return VKEY_SPACE;
+ case NSHomeFunctionKey: return VKEY_HOME;
+ case NSEndFunctionKey: return VKEY_END;
+ case NSPageUpFunctionKey: return VKEY_PRIOR;
+ case NSPageDownFunctionKey: return VKEY_NEXT;
+ case NSUpArrowFunctionKey: return VKEY_UP;
+ case NSDownArrowFunctionKey: return VKEY_DOWN;
+ case NSLeftArrowFunctionKey: return VKEY_LEFT;
+ case NSRightArrowFunctionKey: return VKEY_RIGHT;
+ case NSDeleteFunctionKey: return VKEY_DELETE;
+
+ case '0': case ')': return VKEY_0;
+ case '1': case '!': return VKEY_1;
+ case '2': case '@': return VKEY_2;
+ case '3': case '#': return VKEY_3;
+ case '4': case '$': return VKEY_4;
+ case '5': case '%': return VKEY_5;
+ case '6': case '^': return VKEY_6;
+ case '7': case '&': return VKEY_7;
+ case '8': case '*': return VKEY_8;
+ case '9': case '(': return VKEY_9;
+
+ case 'a': case 'A': return VKEY_A;
+ case 'b': case 'B': return VKEY_B;
+ case 'c': case 'C': return VKEY_C;
+ case 'd': case 'D': return VKEY_D;
+ case 'e': case 'E': return VKEY_E;
+ case 'f': case 'F': return VKEY_F;
+ case 'g': case 'G': return VKEY_G;
+ case 'h': case 'H': return VKEY_H;
+ case 'i': case 'I': return VKEY_I;
+ case 'j': case 'J': return VKEY_J;
+ case 'k': case 'K': return VKEY_K;
+ case 'l': case 'L': return VKEY_L;
+ case 'm': case 'M': return VKEY_M;
+ case 'n': case 'N': return VKEY_N;
+ case 'o': case 'O': return VKEY_O;
+ case 'p': case 'P': return VKEY_P;
+ case 'q': case 'Q': return VKEY_Q;
+ case 'r': case 'R': return VKEY_R;
+ case 's': case 'S': return VKEY_S;
+ case 't': case 'T': return VKEY_T;
+ case 'u': case 'U': return VKEY_U;
+ case 'v': case 'V': return VKEY_V;
+ case 'w': case 'W': return VKEY_W;
+ case 'x': case 'X': return VKEY_X;
+ case 'y': case 'Y': return VKEY_Y;
+ case 'z': case 'Z': return VKEY_Z;
+
+ case NSPauseFunctionKey: return VKEY_PAUSE;
+ case NSSelectFunctionKey: return VKEY_SELECT;
+ case NSPrintFunctionKey: return VKEY_PRINT;
+ case NSExecuteFunctionKey: return VKEY_EXECUTE;
+ case NSPrintScreenFunctionKey: return VKEY_SNAPSHOT;
+ case NSInsertFunctionKey: return VKEY_INSERT;
+ case NSHelpFunctionKey: return VKEY_INSERT;
+
+ case NSF1FunctionKey: return VKEY_F1;
+ case NSF2FunctionKey: return VKEY_F2;
+ case NSF3FunctionKey: return VKEY_F3;
+ case NSF4FunctionKey: return VKEY_F4;
+ case NSF5FunctionKey: return VKEY_F5;
+ case NSF6FunctionKey: return VKEY_F6;
+ case NSF7FunctionKey: return VKEY_F7;
+ case NSF8FunctionKey: return VKEY_F8;
+ case NSF9FunctionKey: return VKEY_F9;
+ case NSF10FunctionKey: return VKEY_F10;
+ case NSF11FunctionKey: return VKEY_F11;
+ case NSF12FunctionKey: return VKEY_F12;
+ case NSF13FunctionKey: return VKEY_F13;
+ case NSF14FunctionKey: return VKEY_F14;
+ case NSF15FunctionKey: return VKEY_F15;
+ case NSF16FunctionKey: return VKEY_F16;
+ case NSF17FunctionKey: return VKEY_F17;
+ case NSF18FunctionKey: return VKEY_F18;
+ case NSF19FunctionKey: return VKEY_F19;
+ case NSF20FunctionKey: return VKEY_F20;
+
+ case NSF21FunctionKey: return VKEY_F21;
+ case NSF22FunctionKey: return VKEY_F22;
+ case NSF23FunctionKey: return VKEY_F23;
+ case NSF24FunctionKey: return VKEY_F24;
+ case NSScrollLockFunctionKey: return VKEY_SCROLL;
+
+ // U.S. Specific mappings. Mileage may vary.
+ case ';': case ':': return VKEY_OEM_1;
+ case '=': case '+': return VKEY_OEM_PLUS;
+ case ',': case '<': return VKEY_OEM_COMMA;
+ case '-': case '_': return VKEY_OEM_MINUS;
+ case '.': case '>': return VKEY_OEM_PERIOD;
+ case '/': case '?': return VKEY_OEM_2;
+ case '`': case '~': return VKEY_OEM_3;
+ case '[': case '{': return VKEY_OEM_4;
+ case '\\': case '|': return VKEY_OEM_5;
+ case ']': case '}': return VKEY_OEM_6;
+ case '\'': case '"': return VKEY_OEM_7;
+ }
+
+ return VKEY_UNKNOWN;
+}
+
+KeyboardCode KeyboardCodeFromKeyCode(unsigned short keyCode) {
+ static const KeyboardCode kKeyboardCodes[] = {
+ /* 0 */ VKEY_A,
+ /* 1 */ VKEY_S,
+ /* 2 */ VKEY_D,
+ /* 3 */ VKEY_F,
+ /* 4 */ VKEY_H,
+ /* 5 */ VKEY_G,
+ /* 6 */ VKEY_Z,
+ /* 7 */ VKEY_X,
+ /* 8 */ VKEY_C,
+ /* 9 */ VKEY_V,
+ /* 0x0A */ VKEY_OEM_3, // Section key.
+ /* 0x0B */ VKEY_B,
+ /* 0x0C */ VKEY_Q,
+ /* 0x0D */ VKEY_W,
+ /* 0x0E */ VKEY_E,
+ /* 0x0F */ VKEY_R,
+ /* 0x10 */ VKEY_Y,
+ /* 0x11 */ VKEY_T,
+ /* 0x12 */ VKEY_1,
+ /* 0x13 */ VKEY_2,
+ /* 0x14 */ VKEY_3,
+ /* 0x15 */ VKEY_4,
+ /* 0x16 */ VKEY_6,
+ /* 0x17 */ VKEY_5,
+ /* 0x18 */ VKEY_OEM_PLUS, // =+
+ /* 0x19 */ VKEY_9,
+ /* 0x1A */ VKEY_7,
+ /* 0x1B */ VKEY_OEM_MINUS, // -_
+ /* 0x1C */ VKEY_8,
+ /* 0x1D */ VKEY_0,
+ /* 0x1E */ VKEY_OEM_6, // ]}
+ /* 0x1F */ VKEY_O,
+ /* 0x20 */ VKEY_U,
+ /* 0x21 */ VKEY_OEM_4, // {[
+ /* 0x22 */ VKEY_I,
+ /* 0x23 */ VKEY_P,
+ /* 0x24 */ VKEY_RETURN, // Return
+ /* 0x25 */ VKEY_L,
+ /* 0x26 */ VKEY_J,
+ /* 0x27 */ VKEY_OEM_7, // '"
+ /* 0x28 */ VKEY_K,
+ /* 0x29 */ VKEY_OEM_1, // ;:
+ /* 0x2A */ VKEY_OEM_5, // \|
+ /* 0x2B */ VKEY_OEM_COMMA, // ,<
+ /* 0x2C */ VKEY_OEM_2, // /?
+ /* 0x2D */ VKEY_N,
+ /* 0x2E */ VKEY_M,
+ /* 0x2F */ VKEY_OEM_PERIOD, // .>
+ /* 0x30 */ VKEY_TAB,
+ /* 0x31 */ VKEY_SPACE,
+ /* 0x32 */ VKEY_OEM_3, // `~
+ /* 0x33 */ VKEY_BACK, // Backspace
+ /* 0x34 */ VKEY_UNKNOWN, // n/a
+ /* 0x35 */ VKEY_ESCAPE,
+ /* 0x36 */ VKEY_APPS, // Right Command
+ /* 0x37 */ VKEY_LWIN, // Left Command
+ /* 0x38 */ VKEY_SHIFT, // Left Shift
+ /* 0x39 */ VKEY_CAPITAL, // Caps Lock
+ /* 0x3A */ VKEY_MENU, // Left Option
+ /* 0x3B */ VKEY_CONTROL, // Left Ctrl
+ /* 0x3C */ VKEY_SHIFT, // Right Shift
+ /* 0x3D */ VKEY_MENU, // Right Option
+ /* 0x3E */ VKEY_CONTROL, // Right Ctrl
+ /* 0x3F */ VKEY_UNKNOWN, // fn
+ /* 0x40 */ VKEY_F17,
+ /* 0x41 */ VKEY_DECIMAL, // Num Pad .
+ /* 0x42 */ VKEY_UNKNOWN, // n/a
+ /* 0x43 */ VKEY_MULTIPLY, // Num Pad *
+ /* 0x44 */ VKEY_UNKNOWN, // n/a
+ /* 0x45 */ VKEY_ADD, // Num Pad +
+ /* 0x46 */ VKEY_UNKNOWN, // n/a
+ /* 0x47 */ VKEY_CLEAR, // Num Pad Clear
+ /* 0x48 */ VKEY_VOLUME_UP,
+ /* 0x49 */ VKEY_VOLUME_DOWN,
+ /* 0x4A */ VKEY_VOLUME_MUTE,
+ /* 0x4B */ VKEY_DIVIDE, // Num Pad /
+ /* 0x4C */ VKEY_RETURN, // Num Pad Enter
+ /* 0x4D */ VKEY_UNKNOWN, // n/a
+ /* 0x4E */ VKEY_SUBTRACT, // Num Pad -
+ /* 0x4F */ VKEY_F18,
+ /* 0x50 */ VKEY_F19,
+ /* 0x51 */ VKEY_OEM_PLUS, // Num Pad =.
+ /* 0x52 */ VKEY_NUMPAD0,
+ /* 0x53 */ VKEY_NUMPAD1,
+ /* 0x54 */ VKEY_NUMPAD2,
+ /* 0x55 */ VKEY_NUMPAD3,
+ /* 0x56 */ VKEY_NUMPAD4,
+ /* 0x57 */ VKEY_NUMPAD5,
+ /* 0x58 */ VKEY_NUMPAD6,
+ /* 0x59 */ VKEY_NUMPAD7,
+ /* 0x5A */ VKEY_F20,
+ /* 0x5B */ VKEY_NUMPAD8,
+ /* 0x5C */ VKEY_NUMPAD9,
+ /* 0x5D */ VKEY_UNKNOWN, // Yen (JIS Keyboard Only)
+ /* 0x5E */ VKEY_UNKNOWN, // Underscore (JIS Keyboard Only)
+ /* 0x5F */ VKEY_UNKNOWN, // KeypadComma (JIS Keyboard Only)
+ /* 0x60 */ VKEY_F5,
+ /* 0x61 */ VKEY_F6,
+ /* 0x62 */ VKEY_F7,
+ /* 0x63 */ VKEY_F3,
+ /* 0x64 */ VKEY_F8,
+ /* 0x65 */ VKEY_F9,
+ /* 0x66 */ VKEY_UNKNOWN, // Eisu (JIS Keyboard Only)
+ /* 0x67 */ VKEY_F11,
+ /* 0x68 */ VKEY_UNKNOWN, // Kana (JIS Keyboard Only)
+ /* 0x69 */ VKEY_F13,
+ /* 0x6A */ VKEY_F16,
+ /* 0x6B */ VKEY_F14,
+ /* 0x6C */ VKEY_UNKNOWN, // n/a
+ /* 0x6D */ VKEY_F10,
+ /* 0x6E */ VKEY_UNKNOWN, // n/a (Windows95 key?)
+ /* 0x6F */ VKEY_F12,
+ /* 0x70 */ VKEY_UNKNOWN, // n/a
+ /* 0x71 */ VKEY_F15,
+ /* 0x72 */ VKEY_INSERT, // Help
+ /* 0x73 */ VKEY_HOME, // Home
+ /* 0x74 */ VKEY_PRIOR, // Page Up
+ /* 0x75 */ VKEY_DELETE, // Forward Delete
+ /* 0x76 */ VKEY_F4,
+ /* 0x77 */ VKEY_END, // End
+ /* 0x78 */ VKEY_F2,
+ /* 0x79 */ VKEY_NEXT, // Page Down
+ /* 0x7A */ VKEY_F1,
+ /* 0x7B */ VKEY_LEFT, // Left Arrow
+ /* 0x7C */ VKEY_RIGHT, // Right Arrow
+ /* 0x7D */ VKEY_DOWN, // Down Arrow
+ /* 0x7E */ VKEY_UP, // Up Arrow
+ /* 0x7F */ VKEY_UNKNOWN // n/a
+ };
+
+ if (keyCode >= 0x80)
+ return VKEY_UNKNOWN;
+
+ return kKeyboardCodes[keyCode];
+}
+
+} // namespace
+
+int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode,
+ NSUInteger flags,
+ unichar* character,
+ unichar* characterIgnoringModifiers) {
+ KeyCodeMap from;
+ from.keycode = keycode;
+
+ const KeyCodeMap* ptr = std::lower_bound(
+ kKeyCodesMap, kKeyCodesMap + arraysize(kKeyCodesMap), from);
+
+ if (ptr >= kKeyCodesMap + arraysize(kKeyCodesMap) ||
+ ptr->keycode != keycode || ptr->macKeycode == -1)
+ return -1;
+
+ int macKeycode = ptr->macKeycode;
+ if (characterIgnoringModifiers)
+ *characterIgnoringModifiers = ptr->characterIgnoringModifiers;
+
+ if (!character)
+ return macKeycode;
+
+ *character = ptr->characterIgnoringModifiers;
+
+ // Fill in |character| according to flags.
+ if (flags & NSShiftKeyMask) {
+ if (keycode >= VKEY_0 && keycode <= VKEY_9) {
+ *character = kShiftCharsForNumberKeys[keycode - VKEY_0];
+ } else if (keycode >= VKEY_A && keycode <= VKEY_Z) {
+ *character = 'A' + (keycode - VKEY_A);
+ } else {
+ switch (macKeycode) {
+ case kVK_ANSI_Grave:
+ *character = '~';
+ break;
+ case kVK_ANSI_Minus:
+ *character = '_';
+ break;
+ case kVK_ANSI_Equal:
+ *character = '+';
+ break;
+ case kVK_ANSI_LeftBracket:
+ *character = '{';
+ break;
+ case kVK_ANSI_RightBracket:
+ *character = '}';
+ break;
+ case kVK_ANSI_Backslash:
+ *character = '|';
+ break;
+ case kVK_ANSI_Semicolon:
+ *character = ':';
+ break;
+ case kVK_ANSI_Quote:
+ *character = '\"';
+ break;
+ case kVK_ANSI_Comma:
+ *character = '<';
+ break;
+ case kVK_ANSI_Period:
+ *character = '>';
+ break;
+ case kVK_ANSI_Slash:
+ *character = '?';
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // TODO(suzhe): Support characters for Option key bindings.
+ return macKeycode;
+}
+
+KeyboardCode KeyboardCodeFromNSEvent(NSEvent* event) {
+ KeyboardCode code = VKEY_UNKNOWN;
+
+ if ([event type] == NSKeyDown || [event type] == NSKeyUp) {
+ NSString* characters = [event characters];
+ if ([characters length] > 0)
+ code = KeyboardCodeFromCharCode([characters characterAtIndex:0]);
+ if (code)
+ return code;
+
+ characters = [event charactersIgnoringModifiers];
+ if ([characters length] > 0)
+ code = KeyboardCodeFromCharCode([characters characterAtIndex:0]);
+ if (code)
+ return code;
+ }
+ return KeyboardCodeFromKeyCode([event keyCode]);
+}
+
+const char* CodeFromNSEvent(NSEvent* event) {
+ return ui::KeycodeConverter::NativeKeycodeToCode([event keyCode]);
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_win.cc b/ui/events/keycodes/keyboard_code_conversion_win.cc
new file mode 100644
index 0000000..262658d
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_win.cc
@@ -0,0 +1,22 @@
+// 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.
+
+#include "ui/events/keycodes/keyboard_code_conversion_win.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+WORD WindowsKeyCodeForKeyboardCode(KeyboardCode keycode) {
+ return static_cast<WORD>(keycode);
+}
+
+KeyboardCode KeyboardCodeForWindowsKeyCode(WORD keycode) {
+ return static_cast<KeyboardCode>(keycode);
+}
+
+const char* CodeForWindowsScanCode(WORD scan_code) {
+ return ui::KeycodeConverter::NativeKeycodeToCode(scan_code);
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_win.h b/ui/events/keycodes/keyboard_code_conversion_win.h
new file mode 100644
index 0000000..4f09a2a
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_win.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace ui {
+
+// Methods to convert ui::KeyboardCode/Windows virtual key type methods.
+EVENTS_BASE_EXPORT WORD WindowsKeyCodeForKeyboardCode(KeyboardCode keycode);
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeForWindowsKeyCode(WORD keycode);
+EVENTS_BASE_EXPORT const char* CodeForWindowsScanCode(WORD scan_code);
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_x.cc b/ui/events/keycodes/keyboard_code_conversion_x.cc
new file mode 100644
index 0000000..4311a96
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_x.cc
@@ -0,0 +1,1356 @@
+// 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/keycodes/keyboard_code_conversion_x.h"
+
+#include <algorithm>
+
+#define XK_3270 // for XK_3270_BackTab
+#include <X11/XF86keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/keysym.h>
+
+#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"
+#include "ui/events/x/keysym_to_unicode.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(const 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;
+ XEvent xkeyevent = {0};
+ if (xev->type == GenericEvent) {
+ // Convert the XI2 key event into a core key event so that we can
+ // continue to use XLookupString() until crbug.com/367732 is complete.
+ InitXKeyEventFromXIDeviceEvent(*xev, &xkeyevent);
+ } else {
+ xkeyevent.xkey = xev->xkey;
+ }
+ XKeyEvent* xkey = &xkeyevent.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.
+ 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;
+}
+
+KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) {
+ // TODO(sad): Have |keysym| go through the X map list?
+
+ switch (keysym) {
+ case XK_BackSpace:
+ return VKEY_BACK;
+ case XK_Delete:
+ case XK_KP_Delete:
+ return VKEY_DELETE;
+ case XK_Tab:
+ case XK_KP_Tab:
+ case XK_ISO_Left_Tab:
+ case XK_3270_BackTab:
+ return VKEY_TAB;
+ case XK_Linefeed:
+ case XK_Return:
+ case XK_KP_Enter:
+ case XK_ISO_Enter:
+ return VKEY_RETURN;
+ case XK_Clear:
+ case XK_KP_Begin: // NumPad 5 without Num Lock, for crosbug.com/29169.
+ return VKEY_CLEAR;
+ case XK_KP_Space:
+ case XK_space:
+ return VKEY_SPACE;
+ case XK_Home:
+ case XK_KP_Home:
+ return VKEY_HOME;
+ case XK_End:
+ case XK_KP_End:
+ return VKEY_END;
+ case XK_Page_Up:
+ case XK_KP_Page_Up: // aka XK_KP_Prior
+ return VKEY_PRIOR;
+ case XK_Page_Down:
+ case XK_KP_Page_Down: // aka XK_KP_Next
+ return VKEY_NEXT;
+ case XK_Left:
+ case XK_KP_Left:
+ return VKEY_LEFT;
+ case XK_Right:
+ case XK_KP_Right:
+ return VKEY_RIGHT;
+ case XK_Down:
+ case XK_KP_Down:
+ return VKEY_DOWN;
+ case XK_Up:
+ case XK_KP_Up:
+ return VKEY_UP;
+ case XK_Escape:
+ return VKEY_ESCAPE;
+ case XK_Kana_Lock:
+ case XK_Kana_Shift:
+ return VKEY_KANA;
+ case XK_Hangul:
+ return VKEY_HANGUL;
+ case XK_Hangul_Hanja:
+ return VKEY_HANJA;
+ case XK_Kanji:
+ return VKEY_KANJI;
+ case XK_Henkan:
+ return VKEY_CONVERT;
+ case XK_Muhenkan:
+ return VKEY_NONCONVERT;
+ case XK_Zenkaku_Hankaku:
+ return VKEY_DBE_DBCSCHAR;
+
+ case XK_KP_0:
+ case XK_KP_1:
+ case XK_KP_2:
+ case XK_KP_3:
+ case XK_KP_4:
+ case XK_KP_5:
+ case XK_KP_6:
+ case XK_KP_7:
+ case XK_KP_8:
+ case XK_KP_9:
+ return static_cast<KeyboardCode>(VKEY_NUMPAD0 + (keysym - XK_KP_0));
+
+ case XK_multiply:
+ case XK_KP_Multiply:
+ return VKEY_MULTIPLY;
+ case XK_KP_Add:
+ return VKEY_ADD;
+ case XK_KP_Separator:
+ return VKEY_SEPARATOR;
+ case XK_KP_Subtract:
+ return VKEY_SUBTRACT;
+ case XK_KP_Decimal:
+ return VKEY_DECIMAL;
+ case XK_KP_Divide:
+ return VKEY_DIVIDE;
+ case XK_KP_Equal:
+ case XK_equal:
+ case XK_plus:
+ return VKEY_OEM_PLUS;
+ case XK_comma:
+ case XK_less:
+ return VKEY_OEM_COMMA;
+ case XK_minus:
+ case XK_underscore:
+ return VKEY_OEM_MINUS;
+ case XK_greater:
+ case XK_period:
+ return VKEY_OEM_PERIOD;
+ case XK_colon:
+ case XK_semicolon:
+ return VKEY_OEM_1;
+ case XK_question:
+ case XK_slash:
+ return VKEY_OEM_2;
+ case XK_asciitilde:
+ case XK_quoteleft:
+ return VKEY_OEM_3;
+ case XK_bracketleft:
+ case XK_braceleft:
+ return VKEY_OEM_4;
+ case XK_backslash:
+ case XK_bar:
+ return VKEY_OEM_5;
+ case XK_bracketright:
+ case XK_braceright:
+ return VKEY_OEM_6;
+ case XK_quoteright:
+ case XK_quotedbl:
+ return VKEY_OEM_7;
+ case XK_ISO_Level5_Shift:
+ return VKEY_OEM_8;
+ case XK_Shift_L:
+ case XK_Shift_R:
+ return VKEY_SHIFT;
+ case XK_Control_L:
+ case XK_Control_R:
+ return VKEY_CONTROL;
+ case XK_Meta_L:
+ case XK_Meta_R:
+ case XK_Alt_L:
+ case XK_Alt_R:
+ return VKEY_MENU;
+ case XK_ISO_Level3_Shift:
+ case XK_Mode_switch:
+ return VKEY_ALTGR;
+ case XK_Multi_key:
+ return VKEY_COMPOSE;
+ case XK_Pause:
+ return VKEY_PAUSE;
+ case XK_Caps_Lock:
+ return VKEY_CAPITAL;
+ case XK_Num_Lock:
+ return VKEY_NUMLOCK;
+ case XK_Scroll_Lock:
+ return VKEY_SCROLL;
+ case XK_Select:
+ return VKEY_SELECT;
+ case XK_Print:
+ return VKEY_PRINT;
+ case XK_Execute:
+ return VKEY_EXECUTE;
+ case XK_Insert:
+ case XK_KP_Insert:
+ return VKEY_INSERT;
+ case XK_Help:
+ return VKEY_HELP;
+ case XK_Super_L:
+ return VKEY_LWIN;
+ case XK_Super_R:
+ return VKEY_RWIN;
+ case XK_Menu:
+ return VKEY_APPS;
+ case XK_F1:
+ case XK_F2:
+ case XK_F3:
+ case XK_F4:
+ case XK_F5:
+ case XK_F6:
+ case XK_F7:
+ case XK_F8:
+ case XK_F9:
+ case XK_F10:
+ case XK_F11:
+ case XK_F12:
+ case XK_F13:
+ case XK_F14:
+ case XK_F15:
+ case XK_F16:
+ case XK_F17:
+ case XK_F18:
+ case XK_F19:
+ case XK_F20:
+ case XK_F21:
+ case XK_F22:
+ case XK_F23:
+ case XK_F24:
+ return static_cast<KeyboardCode>(VKEY_F1 + (keysym - XK_F1));
+ case XK_KP_F1:
+ case XK_KP_F2:
+ case XK_KP_F3:
+ case XK_KP_F4:
+ return static_cast<KeyboardCode>(VKEY_F1 + (keysym - XK_KP_F1));
+
+ case XK_guillemotleft:
+ case XK_guillemotright:
+ case XK_degree:
+ // In the case of canadian multilingual keyboard layout, VKEY_OEM_102 is
+ // assigned to ugrave key.
+ case XK_ugrave:
+ case XK_Ugrave:
+ case XK_brokenbar:
+ return VKEY_OEM_102; // international backslash key in 102 keyboard.
+
+ // When evdev is in use, /usr/share/X11/xkb/symbols/inet maps F13-18 keys
+ // to the special XF86XK symbols to support Microsoft Ergonomic keyboards:
+ // https://bugs.freedesktop.org/show_bug.cgi?id=5783
+ // In Chrome, we map these X key symbols back to F13-18 since we don't have
+ // VKEYs for these XF86XK symbols.
+ case XF86XK_Launch5:
+ return VKEY_F14;
+ case XF86XK_Launch6:
+ return VKEY_F15;
+ case XF86XK_Launch7:
+ return VKEY_F16;
+ case XF86XK_Launch8:
+ return VKEY_F17;
+ case XF86XK_Launch9:
+ return VKEY_F18;
+
+ // For supporting multimedia buttons on a USB keyboard.
+ case XF86XK_Back:
+ return VKEY_BROWSER_BACK;
+ case XF86XK_Forward:
+ return VKEY_BROWSER_FORWARD;
+ case XF86XK_Reload:
+ return VKEY_BROWSER_REFRESH;
+ case XF86XK_Stop:
+ return VKEY_BROWSER_STOP;
+ case XF86XK_Search:
+ return VKEY_BROWSER_SEARCH;
+ case XF86XK_Favorites:
+ return VKEY_BROWSER_FAVORITES;
+ case XF86XK_HomePage:
+ return VKEY_BROWSER_HOME;
+ case XF86XK_AudioMute:
+ return VKEY_VOLUME_MUTE;
+ case XF86XK_AudioLowerVolume:
+ return VKEY_VOLUME_DOWN;
+ case XF86XK_AudioRaiseVolume:
+ return VKEY_VOLUME_UP;
+ case XF86XK_AudioNext:
+ return VKEY_MEDIA_NEXT_TRACK;
+ case XF86XK_AudioPrev:
+ return VKEY_MEDIA_PREV_TRACK;
+ case XF86XK_AudioStop:
+ return VKEY_MEDIA_STOP;
+ case XF86XK_AudioPlay:
+ return VKEY_MEDIA_PLAY_PAUSE;
+ case XF86XK_Mail:
+ return VKEY_MEDIA_LAUNCH_MAIL;
+ case XF86XK_LaunchA: // F3 on an Apple keyboard.
+ return VKEY_MEDIA_LAUNCH_APP1;
+ case XF86XK_LaunchB: // F4 on an Apple keyboard.
+ case XF86XK_Calculator:
+ return VKEY_MEDIA_LAUNCH_APP2;
+
+ // XF86XK_Tools is generated from HID Usage AL_CONSUMER_CONTROL_CONFIG
+ // (Usage 0x0183, Page 0x0C) and most commonly launches the OS default
+ // media player (see crbug.com/398345).
+ case XF86XK_Tools:
+ return VKEY_MEDIA_LAUNCH_MEDIA_SELECT;
+
+ case XF86XK_WLAN:
+ return VKEY_WLAN;
+ case XF86XK_PowerOff:
+ return VKEY_POWER;
+ case XF86XK_MonBrightnessDown:
+ return VKEY_BRIGHTNESS_DOWN;
+ case XF86XK_MonBrightnessUp:
+ return VKEY_BRIGHTNESS_UP;
+ case XF86XK_KbdBrightnessDown:
+ return VKEY_KBD_BRIGHTNESS_DOWN;
+ case XF86XK_KbdBrightnessUp:
+ return VKEY_KBD_BRIGHTNESS_UP;
+
+ // TODO(sad): some keycodes are still missing.
+ }
+ DVLOG(1) << "Unknown keysym: " << base::StringPrintf("0x%x", keysym);
+ return VKEY_UNKNOWN;
+}
+
+const char* CodeFromXEvent(const XEvent* xev) {
+ int keycode = (xev->type == GenericEvent)
+ ? static_cast<XIDeviceEvent*>(xev->xcookie.data)->detail
+ : xev->xkey.keycode;
+ return ui::KeycodeConverter::NativeKeycodeToCode(keycode);
+}
+
+uint16 GetCharacterFromXEvent(const XEvent* xev) {
+ XEvent xkeyevent = {0};
+ const XKeyEvent* xkey = NULL;
+ if (xev->type == GenericEvent) {
+ // Convert the XI2 key event into a core key event so that we can
+ // continue to use XLookupString() until crbug.com/367732 is complete.
+ InitXKeyEventFromXIDeviceEvent(*xev, &xkeyevent);
+ xkey = &xkeyevent.xkey;
+ } else {
+ xkey = &xev->xkey;
+ }
+ KeySym keysym = XK_VoidSymbol;
+ XLookupString(const_cast<XKeyEvent*>(xkey), NULL, 0, &keysym, NULL);
+ return GetUnicodeCharacterFromXKeySym(keysym);
+}
+
+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
+ };
+
+ 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.
+int XKeysymForWindowsKeyCode(KeyboardCode keycode, bool shift) {
+ switch (keycode) {
+ case VKEY_NUMPAD0:
+ return XK_KP_0;
+ case VKEY_NUMPAD1:
+ return XK_KP_1;
+ case VKEY_NUMPAD2:
+ return XK_KP_2;
+ case VKEY_NUMPAD3:
+ return XK_KP_3;
+ case VKEY_NUMPAD4:
+ return XK_KP_4;
+ case VKEY_NUMPAD5:
+ return XK_KP_5;
+ case VKEY_NUMPAD6:
+ return XK_KP_6;
+ case VKEY_NUMPAD7:
+ return XK_KP_7;
+ case VKEY_NUMPAD8:
+ return XK_KP_8;
+ case VKEY_NUMPAD9:
+ return XK_KP_9;
+ case VKEY_MULTIPLY:
+ return XK_KP_Multiply;
+ case VKEY_ADD:
+ return XK_KP_Add;
+ case VKEY_SUBTRACT:
+ return XK_KP_Subtract;
+ case VKEY_DECIMAL:
+ return XK_KP_Decimal;
+ case VKEY_DIVIDE:
+ return XK_KP_Divide;
+
+ case VKEY_BACK:
+ return XK_BackSpace;
+ case VKEY_TAB:
+ return shift ? XK_ISO_Left_Tab : XK_Tab;
+ case VKEY_CLEAR:
+ return XK_Clear;
+ case VKEY_RETURN:
+ return XK_Return;
+ case VKEY_SHIFT:
+ return XK_Shift_L;
+ case VKEY_CONTROL:
+ return XK_Control_L;
+ case VKEY_MENU:
+ return XK_Alt_L;
+ case VKEY_APPS:
+ return XK_Menu;
+ case VKEY_ALTGR:
+ return XK_ISO_Level3_Shift;
+ case VKEY_COMPOSE:
+ return XK_Multi_key;
+
+ case VKEY_PAUSE:
+ return XK_Pause;
+ case VKEY_CAPITAL:
+ return XK_Caps_Lock;
+ case VKEY_KANA:
+ return XK_Kana_Lock;
+ case VKEY_HANJA:
+ return XK_Hangul_Hanja;
+ case VKEY_CONVERT:
+ return XK_Henkan;
+ case VKEY_NONCONVERT:
+ return XK_Muhenkan;
+ case VKEY_DBE_SBCSCHAR:
+ return XK_Zenkaku_Hankaku;
+ case VKEY_DBE_DBCSCHAR:
+ return XK_Zenkaku_Hankaku;
+ case VKEY_ESCAPE:
+ return XK_Escape;
+ case VKEY_SPACE:
+ return XK_space;
+ case VKEY_PRIOR:
+ return XK_Page_Up;
+ case VKEY_NEXT:
+ return XK_Page_Down;
+ case VKEY_END:
+ return XK_End;
+ case VKEY_HOME:
+ return XK_Home;
+ case VKEY_LEFT:
+ return XK_Left;
+ case VKEY_UP:
+ return XK_Up;
+ case VKEY_RIGHT:
+ return XK_Right;
+ case VKEY_DOWN:
+ return XK_Down;
+ case VKEY_SELECT:
+ return XK_Select;
+ case VKEY_PRINT:
+ return XK_Print;
+ case VKEY_EXECUTE:
+ return XK_Execute;
+ case VKEY_INSERT:
+ return XK_Insert;
+ case VKEY_DELETE:
+ return XK_Delete;
+ case VKEY_HELP:
+ return XK_Help;
+ case VKEY_0:
+ return shift ? XK_parenright : XK_0;
+ case VKEY_1:
+ return shift ? XK_exclam : XK_1;
+ case VKEY_2:
+ return shift ? XK_at : XK_2;
+ case VKEY_3:
+ return shift ? XK_numbersign : XK_3;
+ case VKEY_4:
+ return shift ? XK_dollar : XK_4;
+ case VKEY_5:
+ return shift ? XK_percent : XK_5;
+ case VKEY_6:
+ return shift ? XK_asciicircum : XK_6;
+ case VKEY_7:
+ return shift ? XK_ampersand : XK_7;
+ case VKEY_8:
+ return shift ? XK_asterisk : XK_8;
+ case VKEY_9:
+ return shift ? XK_parenleft : XK_9;
+
+ case VKEY_A:
+ case VKEY_B:
+ case VKEY_C:
+ case VKEY_D:
+ case VKEY_E:
+ case VKEY_F:
+ case VKEY_G:
+ case VKEY_H:
+ case VKEY_I:
+ case VKEY_J:
+ case VKEY_K:
+ case VKEY_L:
+ case VKEY_M:
+ case VKEY_N:
+ case VKEY_O:
+ case VKEY_P:
+ case VKEY_Q:
+ case VKEY_R:
+ case VKEY_S:
+ case VKEY_T:
+ case VKEY_U:
+ case VKEY_V:
+ case VKEY_W:
+ case VKEY_X:
+ case VKEY_Y:
+ case VKEY_Z:
+ return (shift ? XK_A : XK_a) + (keycode - VKEY_A);
+
+ case VKEY_LWIN:
+ return XK_Super_L;
+ case VKEY_RWIN:
+ return XK_Super_R;
+
+ case VKEY_NUMLOCK:
+ return XK_Num_Lock;
+
+ case VKEY_SCROLL:
+ return XK_Scroll_Lock;
+
+ case VKEY_OEM_1:
+ return shift ? XK_colon : XK_semicolon;
+ case VKEY_OEM_PLUS:
+ return shift ? XK_plus : XK_equal;
+ case VKEY_OEM_COMMA:
+ return shift ? XK_less : XK_comma;
+ case VKEY_OEM_MINUS:
+ return shift ? XK_underscore : XK_minus;
+ case VKEY_OEM_PERIOD:
+ return shift ? XK_greater : XK_period;
+ case VKEY_OEM_2:
+ return shift ? XK_question : XK_slash;
+ case VKEY_OEM_3:
+ return shift ? XK_asciitilde : XK_quoteleft;
+ case VKEY_OEM_4:
+ return shift ? XK_braceleft : XK_bracketleft;
+ case VKEY_OEM_5:
+ return shift ? XK_bar : XK_backslash;
+ case VKEY_OEM_6:
+ return shift ? XK_braceright : XK_bracketright;
+ case VKEY_OEM_7:
+ return shift ? XK_quotedbl : XK_quoteright;
+ case VKEY_OEM_8:
+ return XK_ISO_Level5_Shift;
+ case VKEY_OEM_102:
+ return shift ? XK_guillemotleft : XK_guillemotright;
+
+ case VKEY_F1:
+ case VKEY_F2:
+ case VKEY_F3:
+ case VKEY_F4:
+ case VKEY_F5:
+ case VKEY_F6:
+ case VKEY_F7:
+ case VKEY_F8:
+ case VKEY_F9:
+ case VKEY_F10:
+ case VKEY_F11:
+ case VKEY_F12:
+ case VKEY_F13:
+ case VKEY_F14:
+ case VKEY_F15:
+ case VKEY_F16:
+ case VKEY_F17:
+ case VKEY_F18:
+ case VKEY_F19:
+ case VKEY_F20:
+ case VKEY_F21:
+ case VKEY_F22:
+ case VKEY_F23:
+ case VKEY_F24:
+ return XK_F1 + (keycode - VKEY_F1);
+
+ case VKEY_BROWSER_BACK:
+ return XF86XK_Back;
+ case VKEY_BROWSER_FORWARD:
+ return XF86XK_Forward;
+ case VKEY_BROWSER_REFRESH:
+ return XF86XK_Reload;
+ case VKEY_BROWSER_STOP:
+ return XF86XK_Stop;
+ case VKEY_BROWSER_SEARCH:
+ return XF86XK_Search;
+ case VKEY_BROWSER_FAVORITES:
+ return XF86XK_Favorites;
+ case VKEY_BROWSER_HOME:
+ return XF86XK_HomePage;
+ case VKEY_VOLUME_MUTE:
+ return XF86XK_AudioMute;
+ case VKEY_VOLUME_DOWN:
+ return XF86XK_AudioLowerVolume;
+ case VKEY_VOLUME_UP:
+ return XF86XK_AudioRaiseVolume;
+ case VKEY_MEDIA_NEXT_TRACK:
+ return XF86XK_AudioNext;
+ case VKEY_MEDIA_PREV_TRACK:
+ return XF86XK_AudioPrev;
+ case VKEY_MEDIA_STOP:
+ return XF86XK_AudioStop;
+ case VKEY_MEDIA_PLAY_PAUSE:
+ return XF86XK_AudioPlay;
+ case VKEY_MEDIA_LAUNCH_MAIL:
+ return XF86XK_Mail;
+ case VKEY_MEDIA_LAUNCH_APP1:
+ return XF86XK_LaunchA;
+ case VKEY_MEDIA_LAUNCH_APP2:
+ return XF86XK_LaunchB;
+ case VKEY_WLAN:
+ return XF86XK_WLAN;
+ case VKEY_POWER:
+ return XF86XK_PowerOff;
+ case VKEY_BRIGHTNESS_DOWN:
+ return XF86XK_MonBrightnessDown;
+ case VKEY_BRIGHTNESS_UP:
+ return XF86XK_MonBrightnessUp;
+ case VKEY_KBD_BRIGHTNESS_DOWN:
+ return XF86XK_KbdBrightnessDown;
+ case VKEY_KBD_BRIGHTNESS_UP:
+ return XF86XK_KbdBrightnessUp;
+
+ default:
+ LOG(WARNING) << "Unknown keycode:" << keycode;
+ return 0;
+ }
+}
+
+void InitXKeyEventFromXIDeviceEvent(const XEvent& src, XEvent* xkeyevent) {
+ DCHECK(src.type == GenericEvent);
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(src.xcookie.data);
+ switch (xievent->evtype) {
+ case XI_KeyPress:
+ xkeyevent->type = KeyPress;
+ break;
+ case XI_KeyRelease:
+ xkeyevent->type = KeyRelease;
+ break;
+ default:
+ NOTREACHED();
+ }
+ xkeyevent->xkey.serial = xievent->serial;
+ xkeyevent->xkey.send_event = xievent->send_event;
+ xkeyevent->xkey.display = xievent->display;
+ xkeyevent->xkey.window = xievent->event;
+ xkeyevent->xkey.root = xievent->root;
+ xkeyevent->xkey.subwindow = xievent->child;
+ xkeyevent->xkey.time = xievent->time;
+ xkeyevent->xkey.x = xievent->event_x;
+ xkeyevent->xkey.y = xievent->event_y;
+ xkeyevent->xkey.x_root = xievent->root_x;
+ xkeyevent->xkey.y_root = xievent->root_y;
+ xkeyevent->xkey.state = xievent->mods.effective;
+ xkeyevent->xkey.keycode = xievent->detail;
+ xkeyevent->xkey.same_screen = 1;
+}
+
+unsigned int XKeyCodeForWindowsKeyCode(ui::KeyboardCode key_code,
+ int flags,
+ XDisplay* display) {
+ // SHIFT state is ignored in the call to XKeysymForWindowsKeyCode() here
+ // because we map the XKeysym back to a keycode, i.e. a physical key position.
+ // Using a SHIFT-modified XKeysym would sometimes yield X keycodes that,
+ // while technically valid, may be surprising in that they do not match
+ // the keycode of the original press, and conflict with assumptions in
+ // other code.
+ //
+ // For example, in a US layout, Shift-9 has the interpretation XK_parenleft,
+ // but the keycode KEY_9 alone does not map to XK_parenleft; instead,
+ // XKeysymToKeycode() returns KEY_KPLEFTPAREN (keypad left parenthesis)
+ // which does map to XK_parenleft -- notwithstanding that keyboards with
+ // dedicated number pad parenthesis keys are currently uncommon.
+ //
+ // Similarly, Shift-Comma has the interpretation XK_less, but KEY_COMMA
+ // alone does not map to XK_less; XKeysymToKeycode() returns KEY_102ND
+ // (the '<>' key between Shift and Z on 105-key keyboards) which does.
+ //
+ // crbug.com/386066 and crbug.com/390263 are examples of problems
+ // associated with this.
+ //
+ return XKeysymToKeycode(display, XKeysymForWindowsKeyCode(key_code, false));
+}
+
+} // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_x.h b/ui/events/keycodes/keyboard_code_conversion_x.h
new file mode 100644
index 0000000..1626691
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_x.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+typedef union _XEvent XEvent;
+typedef struct _XDisplay XDisplay;
+
+namespace ui {
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromXKeyEvent(const XEvent* xev);
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym);
+
+EVENTS_BASE_EXPORT const char* CodeFromXEvent(const XEvent* xev);
+
+// Returns a character on a standard US PC keyboard from an XEvent.
+EVENTS_BASE_EXPORT uint16 GetCharacterFromXEvent(const XEvent* xev);
+
+// Converts a KeyboardCode into an X KeySym.
+EVENTS_BASE_EXPORT int XKeysymForWindowsKeyCode(KeyboardCode keycode,
+ bool shift);
+
+// Returns a XKeyEvent keycode (scancode) for a KeyboardCode. Keyboard layouts
+// are usually not injective, so inverse mapping should be avoided when
+// practical. A round-trip keycode -> KeyboardCode -> keycode will not
+// necessarily return the original keycode.
+EVENTS_BASE_EXPORT unsigned int XKeyCodeForWindowsKeyCode(KeyboardCode key_code,
+ int flags,
+ XDisplay* display);
+
+// Converts an X keycode into ui::KeyboardCode.
+EVENTS_BASE_EXPORT KeyboardCode
+ DefaultKeyboardCodeFromHardwareKeycode(unsigned int hardware_code);
+
+// Initializes a core XKeyEvent from an XI2 key event.
+EVENTS_BASE_EXPORT void InitXKeyEventFromXIDeviceEvent(const XEvent& src,
+ XEvent* dst);
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
diff --git a/ui/events/keycodes/keyboard_codes.h b/ui/events/keycodes/keyboard_codes.h
new file mode 100644
index 0000000..00a7847
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes.h
@@ -0,0 +1,16 @@
+// 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.
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "ui/events/keycodes/keyboard_codes_win.h"
+#elif defined(OS_POSIX)
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#endif
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
diff --git a/ui/events/keycodes/keyboard_codes_posix.h b/ui/events/keycodes/keyboard_codes_posix.h
new file mode 100644
index 0000000..e984839
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes_posix.h
@@ -0,0 +1,233 @@
+// 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.
+
+/*
+ * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com. 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.
+ */
+
+#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
+
+namespace ui {
+
+enum KeyboardCode {
+ VKEY_BACK = 0x08,
+ VKEY_TAB = 0x09,
+ VKEY_BACKTAB = 0x0A,
+ VKEY_CLEAR = 0x0C,
+ VKEY_RETURN = 0x0D,
+ VKEY_SHIFT = 0x10,
+ VKEY_CONTROL = 0x11,
+ VKEY_MENU = 0x12,
+ VKEY_PAUSE = 0x13,
+ VKEY_CAPITAL = 0x14,
+ VKEY_KANA = 0x15,
+ VKEY_HANGUL = 0x15,
+ VKEY_JUNJA = 0x17,
+ VKEY_FINAL = 0x18,
+ VKEY_HANJA = 0x19,
+ VKEY_KANJI = 0x19,
+ VKEY_ESCAPE = 0x1B,
+ VKEY_CONVERT = 0x1C,
+ VKEY_NONCONVERT = 0x1D,
+ VKEY_ACCEPT = 0x1E,
+ VKEY_MODECHANGE = 0x1F,
+ VKEY_SPACE = 0x20,
+ VKEY_PRIOR = 0x21,
+ VKEY_NEXT = 0x22,
+ VKEY_END = 0x23,
+ VKEY_HOME = 0x24,
+ VKEY_LEFT = 0x25,
+ VKEY_UP = 0x26,
+ VKEY_RIGHT = 0x27,
+ VKEY_DOWN = 0x28,
+ VKEY_SELECT = 0x29,
+ VKEY_PRINT = 0x2A,
+ VKEY_EXECUTE = 0x2B,
+ VKEY_SNAPSHOT = 0x2C,
+ VKEY_INSERT = 0x2D,
+ VKEY_DELETE = 0x2E,
+ VKEY_HELP = 0x2F,
+ VKEY_0 = 0x30,
+ VKEY_1 = 0x31,
+ VKEY_2 = 0x32,
+ VKEY_3 = 0x33,
+ VKEY_4 = 0x34,
+ VKEY_5 = 0x35,
+ VKEY_6 = 0x36,
+ VKEY_7 = 0x37,
+ VKEY_8 = 0x38,
+ VKEY_9 = 0x39,
+ VKEY_A = 0x41,
+ VKEY_B = 0x42,
+ VKEY_C = 0x43,
+ VKEY_D = 0x44,
+ VKEY_E = 0x45,
+ VKEY_F = 0x46,
+ VKEY_G = 0x47,
+ VKEY_H = 0x48,
+ VKEY_I = 0x49,
+ VKEY_J = 0x4A,
+ VKEY_K = 0x4B,
+ VKEY_L = 0x4C,
+ VKEY_M = 0x4D,
+ VKEY_N = 0x4E,
+ VKEY_O = 0x4F,
+ VKEY_P = 0x50,
+ VKEY_Q = 0x51,
+ VKEY_R = 0x52,
+ VKEY_S = 0x53,
+ VKEY_T = 0x54,
+ VKEY_U = 0x55,
+ VKEY_V = 0x56,
+ VKEY_W = 0x57,
+ VKEY_X = 0x58,
+ VKEY_Y = 0x59,
+ VKEY_Z = 0x5A,
+ VKEY_LWIN = 0x5B,
+ VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
+ VKEY_RWIN = 0x5C,
+ VKEY_APPS = 0x5D,
+ VKEY_SLEEP = 0x5F,
+ VKEY_NUMPAD0 = 0x60,
+ VKEY_NUMPAD1 = 0x61,
+ VKEY_NUMPAD2 = 0x62,
+ VKEY_NUMPAD3 = 0x63,
+ VKEY_NUMPAD4 = 0x64,
+ VKEY_NUMPAD5 = 0x65,
+ VKEY_NUMPAD6 = 0x66,
+ VKEY_NUMPAD7 = 0x67,
+ VKEY_NUMPAD8 = 0x68,
+ VKEY_NUMPAD9 = 0x69,
+ VKEY_MULTIPLY = 0x6A,
+ VKEY_ADD = 0x6B,
+ VKEY_SEPARATOR = 0x6C,
+ VKEY_SUBTRACT = 0x6D,
+ VKEY_DECIMAL = 0x6E,
+ VKEY_DIVIDE = 0x6F,
+ VKEY_F1 = 0x70,
+ VKEY_F2 = 0x71,
+ VKEY_F3 = 0x72,
+ VKEY_F4 = 0x73,
+ VKEY_F5 = 0x74,
+ VKEY_F6 = 0x75,
+ VKEY_F7 = 0x76,
+ VKEY_F8 = 0x77,
+ VKEY_F9 = 0x78,
+ VKEY_F10 = 0x79,
+ VKEY_F11 = 0x7A,
+ VKEY_F12 = 0x7B,
+ VKEY_F13 = 0x7C,
+ VKEY_F14 = 0x7D,
+ VKEY_F15 = 0x7E,
+ VKEY_F16 = 0x7F,
+ VKEY_F17 = 0x80,
+ VKEY_F18 = 0x81,
+ VKEY_F19 = 0x82,
+ VKEY_F20 = 0x83,
+ VKEY_F21 = 0x84,
+ VKEY_F22 = 0x85,
+ VKEY_F23 = 0x86,
+ VKEY_F24 = 0x87,
+ VKEY_NUMLOCK = 0x90,
+ VKEY_SCROLL = 0x91,
+ VKEY_LSHIFT = 0xA0,
+ VKEY_RSHIFT = 0xA1,
+ VKEY_LCONTROL = 0xA2,
+ VKEY_RCONTROL = 0xA3,
+ VKEY_LMENU = 0xA4,
+ VKEY_RMENU = 0xA5,
+ VKEY_BROWSER_BACK = 0xA6,
+ VKEY_BROWSER_FORWARD = 0xA7,
+ VKEY_BROWSER_REFRESH = 0xA8,
+ VKEY_BROWSER_STOP = 0xA9,
+ VKEY_BROWSER_SEARCH = 0xAA,
+ VKEY_BROWSER_FAVORITES = 0xAB,
+ VKEY_BROWSER_HOME = 0xAC,
+ VKEY_VOLUME_MUTE = 0xAD,
+ VKEY_VOLUME_DOWN = 0xAE,
+ VKEY_VOLUME_UP = 0xAF,
+ VKEY_MEDIA_NEXT_TRACK = 0xB0,
+ VKEY_MEDIA_PREV_TRACK = 0xB1,
+ VKEY_MEDIA_STOP = 0xB2,
+ VKEY_MEDIA_PLAY_PAUSE = 0xB3,
+ VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
+ VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
+ VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
+ VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
+ VKEY_OEM_1 = 0xBA,
+ VKEY_OEM_PLUS = 0xBB,
+ VKEY_OEM_COMMA = 0xBC,
+ VKEY_OEM_MINUS = 0xBD,
+ VKEY_OEM_PERIOD = 0xBE,
+ VKEY_OEM_2 = 0xBF,
+ VKEY_OEM_3 = 0xC0,
+ VKEY_OEM_4 = 0xDB,
+ VKEY_OEM_5 = 0xDC,
+ VKEY_OEM_6 = 0xDD,
+ VKEY_OEM_7 = 0xDE,
+ VKEY_OEM_8 = 0xDF,
+ VKEY_OEM_102 = 0xE2,
+ VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND
+ VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD
+ VKEY_PROCESSKEY = 0xE5,
+ VKEY_PACKET = 0xE7,
+ VKEY_DBE_SBCSCHAR = 0xF3,
+ VKEY_DBE_DBCSCHAR = 0xF4,
+ VKEY_ATTN = 0xF6,
+ VKEY_CRSEL = 0xF7,
+ VKEY_EXSEL = 0xF8,
+ VKEY_EREOF = 0xF9,
+ VKEY_PLAY = 0xFA,
+ VKEY_ZOOM = 0xFB,
+ VKEY_NONAME = 0xFC,
+ VKEY_PA1 = 0xFD,
+ VKEY_OEM_CLEAR = 0xFE,
+ VKEY_UNKNOWN = 0,
+
+ // POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
+ // and 0xE8 are unassigned.
+ VKEY_WLAN = 0x97,
+ VKEY_POWER = 0x98,
+ VKEY_BRIGHTNESS_DOWN = 0xD8,
+ VKEY_BRIGHTNESS_UP = 0xD9,
+ VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
+ VKEY_KBD_BRIGHTNESS_UP = 0xE8,
+
+ // Windows does not have a specific key code for AltGr. We use the unused 0xE1
+ // (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
+ // Linux.
+ VKEY_ALTGR = 0xE1,
+#if defined(USE_X11)
+ // Windows does not have a specific key code for Compose. We use the unused
+ // 0xE6 (VK_ICO_CLEAR) code to represent Compose.
+ VKEY_COMPOSE = 0xE6,
+#endif
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
diff --git a/ui/events/keycodes/keyboard_codes_win.h b/ui/events/keycodes/keyboard_codes_win.h
new file mode 100644
index 0000000..396d38b
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes_win.h
@@ -0,0 +1,192 @@
+// 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_KEYCODES_KEYBOARD_CODES_WIN_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_WIN_H_
+
+#include <windows.h>
+#include <ime.h>
+
+namespace ui {
+
+enum KeyboardCode {
+ VKEY_BACK = VK_BACK,
+ VKEY_TAB = VK_TAB,
+ VKEY_CLEAR = VK_CLEAR,
+ VKEY_RETURN = VK_RETURN,
+ VKEY_SHIFT = VK_SHIFT,
+ VKEY_CONTROL = VK_CONTROL,
+ VKEY_MENU = VK_MENU, // a.k.a. ALT
+ VKEY_PAUSE = VK_PAUSE,
+ VKEY_CAPITAL = VK_CAPITAL,
+ VKEY_KANA = VK_KANA,
+ VKEY_HANGUL = VK_HANGUL,
+ VKEY_JUNJA = VK_JUNJA,
+ VKEY_FINAL = VK_FINAL,
+ VKEY_HANJA = VK_HANJA,
+ VKEY_KANJI = VK_KANJI,
+ VKEY_ESCAPE = VK_ESCAPE,
+ VKEY_CONVERT = VK_CONVERT,
+ VKEY_NONCONVERT = VK_NONCONVERT,
+ VKEY_ACCEPT = VK_ACCEPT,
+ VKEY_MODECHANGE = VK_MODECHANGE,
+ VKEY_SPACE = VK_SPACE,
+ VKEY_PRIOR = VK_PRIOR,
+ VKEY_NEXT = VK_NEXT,
+ VKEY_END = VK_END,
+ VKEY_HOME = VK_HOME,
+ VKEY_LEFT = VK_LEFT,
+ VKEY_UP = VK_UP,
+ VKEY_RIGHT = VK_RIGHT,
+ VKEY_DOWN = VK_DOWN,
+ VKEY_SELECT = VK_SELECT,
+ VKEY_PRINT = VK_PRINT,
+ VKEY_EXECUTE = VK_EXECUTE,
+ VKEY_SNAPSHOT = VK_SNAPSHOT,
+ VKEY_INSERT = VK_INSERT,
+ VKEY_DELETE = VK_DELETE,
+ VKEY_HELP = VK_HELP,
+ VKEY_0 = '0',
+ VKEY_1 = '1',
+ VKEY_2 = '2',
+ VKEY_3 = '3',
+ VKEY_4 = '4',
+ VKEY_5 = '5',
+ VKEY_6 = '6',
+ VKEY_7 = '7',
+ VKEY_8 = '8',
+ VKEY_9 = '9',
+ VKEY_A = 'A',
+ VKEY_B = 'B',
+ VKEY_C = 'C',
+ VKEY_D = 'D',
+ VKEY_E = 'E',
+ VKEY_F = 'F',
+ VKEY_G = 'G',
+ VKEY_H = 'H',
+ VKEY_I = 'I',
+ VKEY_J = 'J',
+ VKEY_K = 'K',
+ VKEY_L = 'L',
+ VKEY_M = 'M',
+ VKEY_N = 'N',
+ VKEY_O = 'O',
+ VKEY_P = 'P',
+ VKEY_Q = 'Q',
+ VKEY_R = 'R',
+ VKEY_S = 'S',
+ VKEY_T = 'T',
+ VKEY_U = 'U',
+ VKEY_V = 'V',
+ VKEY_W = 'W',
+ VKEY_X = 'X',
+ VKEY_Y = 'Y',
+ VKEY_Z = 'Z',
+ VKEY_LWIN = VK_LWIN,
+ VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience.
+ VKEY_RWIN = VK_RWIN,
+ VKEY_APPS = VK_APPS,
+ VKEY_SLEEP = VK_SLEEP,
+ VKEY_NUMPAD0 = VK_NUMPAD0,
+ VKEY_NUMPAD1 = VK_NUMPAD1,
+ VKEY_NUMPAD2 = VK_NUMPAD2,
+ VKEY_NUMPAD3 = VK_NUMPAD3,
+ VKEY_NUMPAD4 = VK_NUMPAD4,
+ VKEY_NUMPAD5 = VK_NUMPAD5,
+ VKEY_NUMPAD6 = VK_NUMPAD6,
+ VKEY_NUMPAD7 = VK_NUMPAD7,
+ VKEY_NUMPAD8 = VK_NUMPAD8,
+ VKEY_NUMPAD9 = VK_NUMPAD9,
+ VKEY_MULTIPLY = VK_MULTIPLY,
+ VKEY_ADD = VK_ADD,
+ VKEY_SEPARATOR = VK_SEPARATOR,
+ VKEY_SUBTRACT = VK_SUBTRACT,
+ VKEY_DECIMAL = VK_DECIMAL,
+ VKEY_DIVIDE = VK_DIVIDE,
+ VKEY_F1 = VK_F1,
+ VKEY_F2 = VK_F2,
+ VKEY_F3 = VK_F3,
+ VKEY_F4 = VK_F4,
+ VKEY_F5 = VK_F5,
+ VKEY_F6 = VK_F6,
+ VKEY_F7 = VK_F7,
+ VKEY_F8 = VK_F8,
+ VKEY_F9 = VK_F9,
+ VKEY_F10 = VK_F10,
+ VKEY_F11 = VK_F11,
+ VKEY_F12 = VK_F12,
+ VKEY_F13 = VK_F13,
+ VKEY_F14 = VK_F14,
+ VKEY_F15 = VK_F15,
+ VKEY_F16 = VK_F16,
+ VKEY_F17 = VK_F17,
+ VKEY_F18 = VK_F18,
+ VKEY_F19 = VK_F19,
+ VKEY_F20 = VK_F20,
+ VKEY_F21 = VK_F21,
+ VKEY_F22 = VK_F22,
+ VKEY_F23 = VK_F23,
+ VKEY_F24 = VK_F24,
+ VKEY_NUMLOCK = VK_NUMLOCK,
+ VKEY_SCROLL = VK_SCROLL,
+ VKEY_LSHIFT = VK_LSHIFT,
+ VKEY_RSHIFT = VK_RSHIFT,
+ VKEY_LCONTROL = VK_LCONTROL,
+ VKEY_RCONTROL = VK_RCONTROL,
+ VKEY_LMENU = VK_LMENU,
+ VKEY_RMENU = VK_RMENU,
+ VKEY_BROWSER_BACK = VK_BROWSER_BACK,
+ VKEY_BROWSER_FORWARD = VK_BROWSER_FORWARD,
+ VKEY_BROWSER_REFRESH = VK_BROWSER_REFRESH,
+ VKEY_BROWSER_STOP = VK_BROWSER_STOP,
+ VKEY_BROWSER_SEARCH = VK_BROWSER_SEARCH,
+ VKEY_BROWSER_FAVORITES = VK_BROWSER_FAVORITES,
+ VKEY_BROWSER_HOME = VK_BROWSER_HOME,
+ VKEY_VOLUME_MUTE = VK_VOLUME_MUTE,
+ VKEY_VOLUME_DOWN = VK_VOLUME_DOWN,
+ VKEY_VOLUME_UP = VK_VOLUME_UP,
+ VKEY_MEDIA_NEXT_TRACK = VK_MEDIA_NEXT_TRACK,
+ VKEY_MEDIA_PREV_TRACK = VK_MEDIA_PREV_TRACK,
+ VKEY_MEDIA_STOP = VK_MEDIA_STOP,
+ VKEY_MEDIA_PLAY_PAUSE = VK_MEDIA_PLAY_PAUSE,
+ VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
+ VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
+ VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
+ VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
+ VKEY_OEM_1 = VK_OEM_1,
+ VKEY_OEM_PLUS = VK_OEM_PLUS,
+ VKEY_OEM_COMMA = VK_OEM_COMMA,
+ VKEY_OEM_MINUS = VK_OEM_MINUS,
+ VKEY_OEM_PERIOD = VK_OEM_PERIOD,
+ VKEY_OEM_2 = VK_OEM_2,
+ VKEY_OEM_3 = VK_OEM_3,
+ VKEY_OEM_4 = VK_OEM_4,
+ VKEY_OEM_5 = VK_OEM_5,
+ VKEY_OEM_6 = VK_OEM_6,
+ VKEY_OEM_7 = VK_OEM_7,
+ VKEY_OEM_8 = VK_OEM_8,
+ VKEY_OEM_102 = VK_OEM_102,
+ VKEY_PROCESSKEY = VK_PROCESSKEY,
+ VKEY_PACKET = VK_PACKET,
+ VKEY_DBE_SBCSCHAR = VK_DBE_SBCSCHAR,
+ VKEY_DBE_DBCSCHAR = VK_DBE_DBCSCHAR,
+ VKEY_ATTN = VK_ATTN,
+ VKEY_CRSEL = VK_CRSEL,
+ VKEY_EXSEL = VK_EXSEL,
+ VKEY_EREOF = VK_EREOF,
+ VKEY_PLAY = VK_PLAY,
+ VKEY_ZOOM = VK_ZOOM,
+ VKEY_NONAME = VK_NONAME,
+ VKEY_PA1 = VK_PA1,
+ VKEY_OEM_CLEAR = VK_OEM_CLEAR,
+ VKEY_UNKNOWN = 0,
+
+ // Windows does not have a specific key code for AltGr. We use the unused
+ // VK_OEM_AX to represent AltGr, matching the behaviour of Firefox on Linux.
+ VKEY_ALTGR = VK_OEM_AX,
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODES_WIN_H_
diff --git a/ui/events/latency_info.cc b/ui/events/latency_info.cc
new file mode 100644
index 0000000..5cd1521
--- /dev/null
+++ b/ui/events/latency_info.cc
@@ -0,0 +1,327 @@
+// 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 "base/debug/trace_event.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/latency_info.h"
+
+#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_BEGIN_SCROLL_UPDATE_MAIN_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);
+ CASE_TYPE(INPUT_EVENT_LATENCY_UI_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT);
+ CASE_TYPE(WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT);
+ CASE_TYPE(WINDOW_OLD_SNAPSHOT_FRAME_NUMBER_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT);
+ default:
+ DLOG(WARNING) << "Unhandled LatencyComponentType.\n";
+ break;
+ }
+#undef CASE_TYPE
+ return "unknown";
+}
+
+bool IsTerminalComponent(ui::LatencyComponentType type) {
+ switch (type) {
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool IsBeginComponent(ui::LatencyComponentType type) {
+ return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT ||
+ type == ui::INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT ||
+ type == ui::INPUT_EVENT_LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT);
+}
+
+// This class is for converting latency info to trace buffer friendly format.
+class LatencyInfoTracedValue : public base::debug::ConvertableToTraceFormat {
+ public:
+ static scoped_refptr<ConvertableToTraceFormat> FromValue(
+ scoped_ptr<base::Value> value);
+
+ virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE;
+
+ private:
+ explicit LatencyInfoTracedValue(base::Value* value);
+ virtual ~LatencyInfoTracedValue();
+
+ scoped_ptr<base::Value> value_;
+
+ DISALLOW_COPY_AND_ASSIGN(LatencyInfoTracedValue);
+};
+
+scoped_refptr<base::debug::ConvertableToTraceFormat>
+LatencyInfoTracedValue::FromValue(scoped_ptr<base::Value> value) {
+ return scoped_refptr<base::debug::ConvertableToTraceFormat>(
+ new LatencyInfoTracedValue(value.release()));
+}
+
+LatencyInfoTracedValue::~LatencyInfoTracedValue() {
+}
+
+void LatencyInfoTracedValue::AppendAsTraceFormat(std::string* out) const {
+ std::string tmp;
+ base::JSONWriter::Write(value_.get(), &tmp);
+ *out += tmp;
+}
+
+LatencyInfoTracedValue::LatencyInfoTracedValue(base::Value* value)
+ : value_(value) {
+}
+
+// Converts latencyinfo into format that can be dumped into trace buffer.
+scoped_refptr<base::debug::ConvertableToTraceFormat> AsTraceableData(
+ const ui::LatencyInfo& latency) {
+ scoped_ptr<base::DictionaryValue> record_data(new base::DictionaryValue());
+ for (ui::LatencyInfo::LatencyMap::const_iterator it =
+ latency.latency_components.begin();
+ it != latency.latency_components.end(); ++it) {
+ base::DictionaryValue* component_info = new base::DictionaryValue();
+ component_info->SetDouble("comp_id", it->first.second);
+ component_info->SetDouble("time", it->second.event_time.ToInternalValue());
+ component_info->SetDouble("count", it->second.event_count);
+ record_data->Set(GetComponentName(it->first.first), component_info);
+ }
+ record_data->SetDouble("trace_id", latency.trace_id);
+
+ scoped_ptr<base::ListValue> coordinates(new base::ListValue());
+ for (size_t i = 0; i < latency.input_coordinates_size; i++) {
+ scoped_ptr<base::DictionaryValue> coordinate_pair(
+ new base::DictionaryValue());
+ coordinate_pair->SetDouble("x", latency.input_coordinates[i].x);
+ coordinate_pair->SetDouble("y", latency.input_coordinates[i].y);
+ coordinates->Append(coordinate_pair.release());
+ }
+ record_data->Set("coordinates", coordinates.release());
+ return LatencyInfoTracedValue::FromValue(record_data.PassAs<base::Value>());
+}
+
+} // namespace
+
+namespace ui {
+
+LatencyInfo::InputCoordinate::InputCoordinate() : x(0), y(0) {
+}
+
+LatencyInfo::InputCoordinate::InputCoordinate(float x, float y) : x(x), y(y) {
+}
+
+LatencyInfo::LatencyInfo()
+ : input_coordinates_size(0), trace_id(-1), terminated(false) {
+}
+
+LatencyInfo::~LatencyInfo() {
+}
+
+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;
+ }
+ for (size_t i = 0; i < latency_info.size(); i++) {
+ if (latency_info[i].input_coordinates_size > kMaxInputCoordinates) {
+ LOG(ERROR) << referring_msg << ", coordinate vector size "
+ << latency_info[i].input_coordinates_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) {
+ if (it->first.first == type) {
+ AddLatencyNumberWithTimestamp(it->first.first,
+ it->first.second,
+ it->second.sequence_number,
+ it->second.event_time,
+ it->second.event_count);
+ }
+ }
+}
+
+void LatencyInfo::AddNewLatencyFrom(const LatencyInfo& other) {
+ for (LatencyMap::const_iterator it = other.latency_components.begin();
+ it != other.latency_components.end();
+ ++it) {
+ if (!FindLatency(it->first.first, it->first.second, NULL)) {
+ AddLatencyNumberWithTimestamp(it->first.first,
+ it->first.second,
+ it->second.sequence_number,
+ it->second.event_time,
+ it->second.event_count);
+ }
+ }
+}
+
+void LatencyInfo::AddLatencyNumber(LatencyComponentType component,
+ int64 id,
+ int64 component_sequence_number) {
+ AddLatencyNumberWithTimestamp(component, id, component_sequence_number,
+ base::TimeTicks::HighResNow(), 1);
+}
+
+void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
+ int64 id,
+ int64 component_sequence_number,
+ base::TimeTicks time,
+ uint32 event_count) {
+
+ static const unsigned char* benchmark_enabled =
+ TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("benchmark");
+
+ if (IsBeginComponent(component)) {
+ // Should only ever add begin component once.
+ CHECK_EQ(-1, trace_id);
+ trace_id = component_sequence_number;
+
+ if (*benchmark_enabled) {
+ // The timestamp for ASYNC_BEGIN trace event is used for drawing the
+ // beginning of the trace event in trace viewer. For better visualization,
+ // for an input event, we want to draw the beginning as when the event is
+ // originally created, e.g. the timestamp of its ORIGINAL/UI_COMPONENT,
+ // not when we actually issue the ASYNC_BEGIN trace event.
+ LatencyComponent component;
+ int64 ts = 0;
+ if (FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 0,
+ &component) ||
+ FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT,
+ 0,
+ &component)) {
+ // The timestamp stored in ORIGINAL/UI_COMPONENT is using clock
+ // CLOCK_MONOTONIC while TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0
+ // expects timestamp using CLOCK_MONOTONIC or CLOCK_SYSTEM_TRACE (on
+ // CrOS). So we need to adjust the diff between in CLOCK_MONOTONIC and
+ // CLOCK_SYSTEM_TRACE. Note that the diff is drifting overtime so we
+ // can't use a static value.
+ int64 diff = base::TimeTicks::HighResNow().ToInternalValue() -
+ base::TimeTicks::NowFromSystemTraceTime().ToInternalValue();
+ ts = component.event_time.ToInternalValue() - diff;
+ } else {
+ ts = base::TimeTicks::NowFromSystemTraceTime().ToInternalValue();
+ }
+ TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0(
+ "benchmark",
+ "InputLatency",
+ TRACE_ID_DONT_MANGLE(trace_id),
+ ts);
+ }
+
+ TRACE_EVENT_FLOW_BEGIN0(
+ "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
+ }
+
+ LatencyMap::key_type key = std::make_pair(component, id);
+ LatencyMap::iterator it = latency_components.find(key);
+ if (it == latency_components.end()) {
+ LatencyComponent info = {component_sequence_number, time, event_count};
+ latency_components[key] = info;
+ } else {
+ it->second.sequence_number = std::max(component_sequence_number,
+ it->second.sequence_number);
+ uint32 new_count = event_count + it->second.event_count;
+ if (event_count > 0 && new_count != 0) {
+ // Do a weighted average, so that the new event_time is the average of
+ // the times of events currently in this structure with the time passed
+ // into this method.
+ it->second.event_time += (time - it->second.event_time) * event_count /
+ new_count;
+ it->second.event_count = new_count;
+ }
+ }
+
+ if (IsTerminalComponent(component) && trace_id != -1) {
+ // Should only ever add terminal component once.
+ CHECK(!terminated);
+ terminated = true;
+
+ if (*benchmark_enabled) {
+ TRACE_EVENT_ASYNC_END1("benchmark",
+ "InputLatency",
+ TRACE_ID_DONT_MANGLE(trace_id),
+ "data", AsTraceableData(*this));
+ }
+
+ TRACE_EVENT_FLOW_END0(
+ "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
+ }
+}
+
+bool LatencyInfo::FindLatency(LatencyComponentType type,
+ int64 id,
+ LatencyComponent* output) const {
+ LatencyMap::const_iterator it = latency_components.find(
+ std::make_pair(type, id));
+ if (it == latency_components.end())
+ return false;
+ if (output)
+ *output = it->second;
+ return true;
+}
+
+void LatencyInfo::RemoveLatency(LatencyComponentType type) {
+ LatencyMap::iterator it = latency_components.begin();
+ while (it != latency_components.end()) {
+ if (it->first.first == type) {
+ LatencyMap::iterator tmp = it;
+ ++it;
+ latency_components.erase(tmp);
+ } else {
+ it++;
+ }
+ }
+}
+
+void LatencyInfo::Clear() {
+ latency_components.clear();
+}
+
+void LatencyInfo::TraceEventType(const char* event_type) {
+ TRACE_EVENT_ASYNC_STEP_INTO0("benchmark",
+ "InputLatency",
+ TRACE_ID_DONT_MANGLE(trace_id),
+ event_type);
+}
+
+} // namespace ui
diff --git a/ui/events/latency_info.h b/ui/events/latency_info.h
new file mode 100644
index 0000000..1a18f9b
--- /dev/null
+++ b/ui/events/latency_info.h
@@ -0,0 +1,186 @@
+// 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_LATENCY_INFO_H_
+#define UI_EVENTS_LATENCY_INFO_H_
+
+#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"
+
+namespace ui {
+
+enum LatencyComponentType {
+ // ---------------------------BEGIN COMPONENT-------------------------------
+ // 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,
+ // Timestamp when a scroll update for the main thread is begun.
+ INPUT_EVENT_LATENCY_BEGIN_SCROLL_UPDATE_MAIN_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
+ // event. So gesture event's INPUT_EVENT_LATENCY_RWH_COMPONENT is the
+ // timestamp when its original touch events is sent from RWH to renderer.
+ // In non-aura platform, INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT
+ // is the same as INPUT_EVENT_LATENCY_RWH_COMPONENT.
+ INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT,
+ // The original timestamp of the touch event which converts to scroll update.
+ INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+ // Original timestamp for input event (e.g. timestamp from kernel).
+ INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ // Timestamp when the UI event is created.
+ INPUT_EVENT_LATENCY_UI_COMPONENT,
+ // This is special component indicating there is rendering scheduled for
+ // the event associated with this LatencyInfo.
+ INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT,
+ // Timestamp when a scroll update is forwarded to the main thread.
+ INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT,
+ // Timestamp when the touch event is acked.
+ INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT,
+ // Frame number when a window snapshot was requested. The snapshot
+ // is taken when the rendering results actually reach the screen.
+ WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+ // Frame number for a snapshot requested via
+ // gpuBenchmarking.beginWindowSnapshotPNG
+ // TODO(vkuzkokov): remove when patch adding this hits Stable
+ WINDOW_OLD_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+ // Timestamp when a tab is requested to be shown.
+ TAB_SHOW_COMPONENT,
+ // Timestamp of when the Browser process began compositing
+ INPUT_EVENT_BROWSER_COMPOSITE_COMPONENT,
+ // Timestamp of when the Browser process began swap buffers
+ INPUT_EVENT_BROWSER_SWAP_BUFFER_COMPONENT,
+ // Timestamp of when the gpu service began swap buffers, unlike
+ // INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT which measure after
+ INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT,
+ // ---------------------------TERMINAL COMPONENT-----------------------------
+ // TERMINAL COMPONENT is when we show the latency end in chrome://tracing.
+ // Timestamp when the mouse event is acked from renderer and it does not
+ // cause any rendering scheduled.
+ INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT,
+ // Timestamp when the touch event is acked from renderer and it does not
+ // cause any rendering schedueld and does not generate any gesture event.
+ INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT,
+ // Timestamp when the gesture event is acked from renderer, and it does not
+ // cause any rendering schedueld.
+ INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT,
+ // Timestamp when the frame is swapped (i.e. when the rendering caused by
+ // input event actually takes effect).
+ INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT,
+ // This component indicates that the input causes a commit to be scheduled
+ // but the commit failed.
+ INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT,
+ // This component indicates that the input causes a commit to be scheduled
+ // but the commit was aborted since it carried no new information.
+ INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT,
+ // This component indicates that the input causes a swap to be scheduled
+ // but the swap failed.
+ INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_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 {
+ struct LatencyComponent {
+ // Nondecreasing number that can be used to determine what events happened
+ // in the component at the time this struct was sent on to the next
+ // component.
+ int64 sequence_number;
+ // Average time of events that happened in this component.
+ base::TimeTicks event_time;
+ // Count of events that happened in this component
+ uint32 event_count;
+ };
+
+ struct EVENTS_BASE_EXPORT InputCoordinate {
+ InputCoordinate();
+ InputCoordinate(float x, float y);
+
+ float x;
+ float y;
+ };
+
+ // Empirically determined constant based on a typical scroll sequence.
+ enum { kTypicalMaxComponentsPerLatencyInfo = 9 };
+
+ enum { kMaxInputCoordinates = 2 };
+
+ // Map a Latency Component (with a component-specific int64 id) to a
+ // component info.
+ typedef base::SmallMap<
+ std::map<std::pair<LatencyComponentType, int64>, LatencyComponent>,
+ kTypicalMaxComponentsPerLatencyInfo> LatencyMap;
+
+ LatencyInfo();
+
+ ~LatencyInfo();
+
+ // 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);
+
+ // Modifies the current sequence number for a component, and adds a new
+ // sequence number with the current timestamp.
+ void AddLatencyNumber(LatencyComponentType component,
+ int64 id,
+ int64 component_sequence_number);
+
+ // Modifies the current sequence number and adds a certain number of events
+ // for a specific component.
+ void AddLatencyNumberWithTimestamp(LatencyComponentType component,
+ int64 id,
+ int64 component_sequence_number,
+ base::TimeTicks time,
+ 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
+ // |output| is not NULL. Returns false if no such component is found.
+ bool FindLatency(LatencyComponentType type,
+ int64 id,
+ LatencyComponent* output) const;
+
+ void RemoveLatency(LatencyComponentType type);
+
+ void Clear();
+
+ // Records the |event_type| in trace buffer as TRACE_EVENT_ASYNC_STEP.
+ void TraceEventType(const char* event_type);
+
+ LatencyMap latency_components;
+
+ // These coordinates represent window coordinates of the original input event.
+ uint32 input_coordinates_size;
+ InputCoordinate input_coordinates[kMaxInputCoordinates];
+
+ // The unique id for matching the ASYNC_BEGIN/END trace event.
+ int64 trace_id;
+ // Whether a terminal component has been added.
+ bool terminated;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_LATENCY_INFO_H_
diff --git a/ui/events/latency_info_nacl.gyp b/ui/events/latency_info_nacl.gyp
new file mode 100644
index 0000000..e42dbf8
--- /dev/null
+++ b/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/ui/events/latency_info_unittest.cc b/ui/events/latency_info_unittest.cc
new file mode 100644
index 0000000..2f9ad05
--- /dev/null
+++ b/ui/events/latency_info_unittest.cc
@@ -0,0 +1,81 @@
+// 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/latency_info.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ui {
+
+TEST(LatencyInfoTest, AddTwoSeparateEvent) {
+ LatencyInfo info;
+ info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+ 0,
+ 1,
+ base::TimeTicks::FromInternalValue(100),
+ 1);
+ info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 1,
+ 5,
+ base::TimeTicks::FromInternalValue(1000),
+ 2);
+
+ EXPECT_EQ(info.latency_components.size(), 2u);
+ LatencyInfo::LatencyComponent component;
+ EXPECT_FALSE(
+ info.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
+ EXPECT_FALSE(
+ info.FindLatency(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 1, &component));
+ EXPECT_TRUE(
+ info.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(
+ info.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, AddTwoSameEvent) {
+ LatencyInfo info;
+ info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 0,
+ 30,
+ base::TimeTicks::FromInternalValue(100),
+ 2);
+ info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+ 0,
+ 13,
+ base::TimeTicks::FromInternalValue(200),
+ 3);
+
+ EXPECT_EQ(info.latency_components.size(), 1u);
+ LatencyInfo::LatencyComponent component;
+ EXPECT_FALSE(
+ info.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
+ EXPECT_FALSE(
+ info.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component));
+ EXPECT_TRUE(
+ info.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);
+
+ EXPECT_EQ(info.latency_components.size(), 1u);
+ info.Clear();
+ EXPECT_EQ(info.latency_components.size(), 0u);
+}
+
+} // namespace ui
diff --git a/ui/events/linux/text_edit_command_auralinux.cc b/ui/events/linux/text_edit_command_auralinux.cc
new file mode 100644
index 0000000..4429225
--- /dev/null
+++ b/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/ui/events/linux/text_edit_command_auralinux.h b/ui/events/linux/text_edit_command_auralinux.h
new file mode 100644
index 0000000..82d3af2
--- /dev/null
+++ b/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/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc
new file mode 100644
index 0000000..e6a941b
--- /dev/null
+++ b/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/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h
new file mode 100644
index 0000000..d291626
--- /dev/null
+++ b/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/ui/events/ozone/BUILD.gn b/ui/events/ozone/BUILD.gn
new file mode 100644
index 0000000..51ad96d
--- /dev/null
+++ b/ui/events/ozone/BUILD.gn
@@ -0,0 +1,99 @@
+# 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",
+ "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/ui/events/ozone/DEPS b/ui/events/ozone/DEPS
new file mode 100644
index 0000000..3f27114
--- /dev/null
+++ b/ui/events/ozone/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+device/udev_linux",
+ "+ui/ozone/public",
+]
diff --git a/ui/events/ozone/OWNERS b/ui/events/ozone/OWNERS
new file mode 100644
index 0000000..479c4d8
--- /dev/null
+++ b/ui/events/ozone/OWNERS
@@ -0,0 +1,2 @@
+rjkroege@chromium.org
+spang@chromium.org
diff --git a/ui/events/ozone/device/device_event.cc b/ui/events/ozone/device/device_event.cc
new file mode 100644
index 0000000..e20079a
--- /dev/null
+++ b/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/ui/events/ozone/device/device_event.h b/ui/events/ozone/device/device_event.h
new file mode 100644
index 0000000..a24394c
--- /dev/null
+++ b/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/ui/events/ozone/device/device_event_observer.h b/ui/events/ozone/device/device_event_observer.h
new file mode 100644
index 0000000..7ad2f49
--- /dev/null
+++ b/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/ui/events/ozone/device/device_manager.cc b/ui/events/ozone/device/device_manager.cc
new file mode 100644
index 0000000..bbd5d80
--- /dev/null
+++ b/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/ui/events/ozone/device/device_manager.h b/ui/events/ozone/device/device_manager.h
new file mode 100644
index 0000000..0551e7e
--- /dev/null
+++ b/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/ui/events/ozone/device/device_manager_manual.cc b/ui/events/ozone/device/device_manager_manual.cc
new file mode 100644
index 0000000..c86ffcc
--- /dev/null
+++ b/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/ui/events/ozone/device/device_manager_manual.h b/ui/events/ozone/device/device_manager_manual.h
new file mode 100644
index 0000000..9d1cf61
--- /dev/null
+++ b/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/ui/events/ozone/device/udev/device_manager_udev.cc b/ui/events/ozone/device/udev/device_manager_udev.cc
new file mode 100644
index 0000000..7f86bee
--- /dev/null
+++ b/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/ui/events/ozone/device/udev/device_manager_udev.h b/ui/events/ozone/device/udev/device_manager_udev.h
new file mode 100644
index 0000000..8a7537a
--- /dev/null
+++ b/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/ui/events/ozone/evdev/cursor_delegate_evdev.h b/ui/events/ozone/evdev/cursor_delegate_evdev.h
new file mode 100644
index 0000000..63947b3
--- /dev/null
+++ b/ui/events/ozone/evdev/cursor_delegate_evdev.h
@@ -0,0 +1,34 @@
+// 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;
+
+ // Cursor visibility.
+ virtual bool IsCursorVisible() = 0;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_
diff --git a/ui/events/ozone/evdev/event_converter_evdev.cc b/ui/events/ozone/evdev/event_converter_evdev.cc
new file mode 100644
index 0000000..518c7f2
--- /dev/null
+++ b/ui/events/ozone/evdev/event_converter_evdev.cc
@@ -0,0 +1,45 @@
+// 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 "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+
+namespace ui {
+
+EventConverterEvdev::EventConverterEvdev(int fd,
+ const base::FilePath& path,
+ int id)
+ : fd_(fd), path_(path), id_(id) {
+}
+
+EventConverterEvdev::~EventConverterEvdev() {
+ Stop();
+}
+
+void EventConverterEvdev::Start() {
+ base::MessageLoopForUI::current()->WatchFileDescriptor(
+ fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
+}
+
+void EventConverterEvdev::Stop() {
+ controller_.StopWatchingFileDescriptor();
+}
+
+void EventConverterEvdev::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+}
+
+bool EventConverterEvdev::HasTouchscreen() const {
+ return false;
+}
+
+gfx::Size EventConverterEvdev::GetTouchscreenSize() const {
+ NOTREACHED();
+ return gfx::Size();
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/evdev/event_converter_evdev.h b/ui/events/ozone/evdev/event_converter_evdev.h
new file mode 100644
index 0000000..b4b2463
--- /dev/null
+++ b/ui/events/ozone/evdev/event_converter_evdev.h
@@ -0,0 +1,63 @@
+// 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/callback.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace ui {
+
+class Event;
+
+typedef base::Callback<void(Event*)> EventDispatchCallback;
+
+class EVENTS_OZONE_EVDEV_EXPORT EventConverterEvdev
+ : public base::MessagePumpLibevent::Watcher {
+ public:
+ EventConverterEvdev(int fd, const base::FilePath& path, int id);
+ virtual ~EventConverterEvdev();
+
+ int id() const { return id_; }
+
+ // Start reading events.
+ void Start();
+
+ // Stop reading events.
+ void Stop();
+
+ // Returns true of the converter is used for a touchscreen device.
+ virtual bool HasTouchscreen() const;
+
+ // Returns the size of the touchscreen device if the converter is used for a
+ // touchscreen device.
+ virtual gfx::Size GetTouchscreenSize() const;
+
+ protected:
+ // base::MessagePumpLibevent::Watcher:
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ // File descriptor to read.
+ int fd_;
+
+ // Path to input device.
+ base::FilePath path_;
+
+ // Uniquely identifies an event converter.
+ int id_;
+
+ // Controller for watching the input fd.
+ base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventConverterEvdev);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_
diff --git a/ui/events/ozone/evdev/event_device_info.cc b/ui/events/ozone/evdev/event_device_info.cc
new file mode 100644
index 0000000..2734b9d
--- /dev/null
+++ b/ui/events/ozone/evdev/event_device_info.cc
@@ -0,0 +1,189 @@
+// 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_device_info.h"
+
+#include <linux/input.h>
+
+#include "base/logging.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace ui {
+
+namespace {
+
+bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) {
+ if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) {
+ DLOG(ERROR) << "failed EVIOCGBIT(" << type << ", " << size << ") on fd "
+ << fd;
+ return false;
+ }
+
+ return true;
+}
+
+bool GetPropBits(int fd, void* buf, unsigned int size) {
+ if (ioctl(fd, EVIOCGPROP(size), buf) < 0) {
+ DLOG(ERROR) << "failed EVIOCGPROP(" << size << ") on fd " << fd;
+ return false;
+ }
+
+ return true;
+}
+
+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() {
+ memset(ev_bits_, 0, sizeof(ev_bits_));
+ memset(key_bits_, 0, sizeof(key_bits_));
+ memset(rel_bits_, 0, sizeof(rel_bits_));
+ memset(abs_bits_, 0, sizeof(abs_bits_));
+ memset(msc_bits_, 0, sizeof(msc_bits_));
+ 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() {}
+
+bool EventDeviceInfo::Initialize(int fd) {
+ if (!GetEventBits(fd, 0, ev_bits_, sizeof(ev_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_KEY, key_bits_, sizeof(key_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_REL, rel_bits_, sizeof(rel_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_ABS, abs_bits_, sizeof(abs_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_MSC, msc_bits_, sizeof(msc_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_SW, sw_bits_, sizeof(sw_bits_)))
+ return false;
+
+ if (!GetEventBits(fd, EV_LED, led_bits_, sizeof(led_bits_)))
+ return false;
+
+ 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;
+}
+
+bool EventDeviceInfo::HasEventType(unsigned int type) const {
+ if (type > EV_MAX)
+ return false;
+ return BitIsSet(ev_bits_, type);
+}
+
+bool EventDeviceInfo::HasKeyEvent(unsigned int code) const {
+ if (code > KEY_MAX)
+ return false;
+ return BitIsSet(key_bits_, code);
+}
+
+bool EventDeviceInfo::HasRelEvent(unsigned int code) const {
+ if (code > REL_MAX)
+ return false;
+ return BitIsSet(rel_bits_, code);
+}
+
+bool EventDeviceInfo::HasAbsEvent(unsigned int code) const {
+ if (code > ABS_MAX)
+ return false;
+ return BitIsSet(abs_bits_, code);
+}
+
+bool EventDeviceInfo::HasMscEvent(unsigned int code) const {
+ if (code > MSC_MAX)
+ return false;
+ return BitIsSet(msc_bits_, code);
+}
+
+bool EventDeviceInfo::HasSwEvent(unsigned int code) const {
+ if (code > SW_MAX)
+ return false;
+ return BitIsSet(sw_bits_, code);
+}
+
+bool EventDeviceInfo::HasLedEvent(unsigned int code) const {
+ if (code > LED_MAX)
+ return false;
+ return BitIsSet(led_bits_, code);
+}
+
+bool EventDeviceInfo::HasProp(unsigned int code) const {
+ if (code > INPUT_PROP_MAX)
+ return false;
+ 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/ui/events/ozone/evdev/event_device_info.h b/ui/events/ozone/evdev/event_device_info.h
new file mode 100644
index 0000000..492539b
--- /dev/null
+++ b/ui/events/ozone/evdev/event_device_info.h
@@ -0,0 +1,74 @@
+// 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_EVENT_DEVICE_INFO_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_INFO_H_
+
+#include <limits.h>
+#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)
+
+namespace ui {
+
+// Device information for Linux input devices
+//
+// This stores and queries information about input devices; in
+// particular it knows which events the device can generate.
+class EVENTS_OZONE_EVDEV_EXPORT EventDeviceInfo {
+ public:
+ EventDeviceInfo();
+ ~EventDeviceInfo();
+
+ // Initialize device information from an open device.
+ bool Initialize(int fd);
+
+ // Check events this device can generate.
+ bool HasEventType(unsigned int type) const;
+ bool HasKeyEvent(unsigned int code) const;
+ bool HasRelEvent(unsigned int code) const;
+ bool HasAbsEvent(unsigned int code) const;
+ bool HasMscEvent(unsigned int code) const;
+ 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)];
+ unsigned long rel_bits_[EVDEV_BITS_TO_LONGS(REL_CNT)];
+ unsigned long abs_bits_[EVDEV_BITS_TO_LONGS(ABS_CNT)];
+ unsigned long msc_bits_[EVDEV_BITS_TO_LONGS(MSC_CNT)];
+ unsigned long sw_bits_[EVDEV_BITS_TO_LONGS(SW_CNT)];
+ 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);
+};
+
+} // namspace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_INFO_H_
diff --git a/ui/events/ozone/evdev/event_factory_evdev.cc b/ui/events/ozone/evdev/event_factory_evdev.cc
new file mode 100644
index 0000000..88ec979
--- /dev/null
+++ b/ui/events/ozone/evdev/event_factory_evdev.cc
@@ -0,0 +1,276 @@
+// 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/thread_task_runner_handle.h"
+#include "base/threading/worker_pool.h"
+#include "ui/events/device_data_manager.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,
+ int id,
+ 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,
+ id,
+ 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, id, devinfo, dispatch));
+
+ // Everything else: use KeyEventConverterEvdev.
+ return make_scoped_ptr<EventConverterEvdev>(
+ new KeyEventConverterEvdev(fd, path, id, 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,
+ int device_id,
+ 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, device_id, 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)
+ : last_device_id_(0),
+ device_manager_(device_manager),
+ cursor_(cursor),
+ dispatch_callback_(
+ base::Bind(base::IgnoreResult(&EventFactoryEvdev::DispatchUiEvent),
+ base::Unretained(this))),
+ weak_ptr_factory_(this) {
+ DCHECK(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());
+ DCHECK(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();
+
+ NotifyHotplugEventObserver(*converters_[path]);
+}
+
+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_,
+ NextDeviceId(),
+ 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_.get()) {
+ ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
+ // 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());
+ DCHECK(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();
+
+ NotifyHotplugEventObserver(*converter);
+
+ // 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);
+ }
+}
+
+void EventFactoryEvdev::NotifyHotplugEventObserver(
+ const EventConverterEvdev& converter) {
+ // For now the only information propagated is related to touchscreens. Ignore
+ // events for everything but touchscreens.
+ if (!converter.HasTouchscreen())
+ return;
+
+ DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
+ std::vector<TouchscreenDevice> touchscreens;
+ for (auto it = converters_.begin(); it != converters_.end(); ++it) {
+ if (it->second->HasTouchscreen()) {
+ touchscreens.push_back(TouchscreenDevice(it->second->id(),
+ it->second->GetTouchscreenSize(),
+ false /* is_internal */));
+ }
+ }
+
+ observer->OnTouchscreenDevicesUpdated(touchscreens);
+}
+
+int EventFactoryEvdev::NextDeviceId() {
+ return ++last_device_id_;
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/evdev/event_factory_evdev.h b/ui/events/ozone/evdev/event_factory_evdev.h
new file mode 100644
index 0000000..15031e4
--- /dev/null
+++ b/ui/events/ozone/evdev/event_factory_evdev.h
@@ -0,0 +1,91 @@
+// 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/gfx/native_widget_types.h"
+
+namespace gfx {
+class PointF;
+} // namespace gfx
+
+namespace ui {
+
+class CursorDelegateEvdev;
+class DeviceManager;
+
+// Ozone events implementation for the Linux input subsystem ("evdev").
+class EVENTS_OZONE_EVDEV_EXPORT EventFactoryEvdev : public DeviceEventObserver,
+ public PlatformEventSource {
+ public:
+ EventFactoryEvdev(CursorDelegateEvdev* cursor,
+ DeviceManager* device_manager);
+ virtual ~EventFactoryEvdev();
+
+ void DispatchUiEvent(Event* event);
+
+ void WarpCursorTo(gfx::AcceleratedWidget widget,
+ const gfx::PointF& location);
+
+ 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);
+
+ void NotifyHotplugEventObserver(const EventConverterEvdev& converter);
+
+ int NextDeviceId();
+
+ // 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_;
+
+ // Used to uniquely identify input devices.
+ int last_device_id_;
+
+ // 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/ui/events/ozone/evdev/event_modifiers_evdev.cc b/ui/events/ozone/evdev/event_modifiers_evdev.cc
new file mode 100644
index 0000000..cee7c1c
--- /dev/null
+++ b/ui/events/ozone/evdev/event_modifiers_evdev.cc
@@ -0,0 +1,79 @@
+// 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_evdev.h"
+
+#include <linux/input.h>
+
+#include "ui/events/event.h"
+
+namespace ui {
+
+namespace {
+
+static const int kEventFlagFromModifiers[] = {
+ EF_NONE, // EVDEV_MODIFIER_NONE,
+ EF_CAPS_LOCK_DOWN, // EVDEV_MODIFIER_CAPS_LOCK
+ EF_SHIFT_DOWN, // EVDEV_MODIFIER_SHIFT
+ EF_CONTROL_DOWN, // EVDEV_MODIFIER_CONTROL
+ EF_ALT_DOWN, // EVDEV_MODIFIER_ALT
+ EF_LEFT_MOUSE_BUTTON, // EVDEV_MODIFIER_LEFT_MOUSE_BUTTON
+ EF_MIDDLE_MOUSE_BUTTON, // EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON
+ EF_RIGHT_MOUSE_BUTTON, // EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON
+ EF_COMMAND_DOWN, // EVDEV_MODIFIER_COMMAND
+ EF_ALTGR_DOWN, // EVDEV_MODIFIER_ALTGR
+};
+
+} // namespace
+
+EventModifiersEvdev::EventModifiersEvdev()
+ : modifier_flags_locked_(0), modifier_flags_(0) {
+ memset(modifiers_down_, 0, sizeof(modifiers_down_));
+}
+EventModifiersEvdev::~EventModifiersEvdev() {}
+
+void EventModifiersEvdev::UpdateModifier(unsigned int modifier, bool down) {
+ DCHECK_LT(modifier, EVDEV_NUM_MODIFIERS);
+
+ if (down) {
+ modifiers_down_[modifier]++;
+ } else {
+ // Ignore spurious modifier "up" events. This might happen if the
+ // button is down during startup.
+ if (modifiers_down_[modifier])
+ modifiers_down_[modifier]--;
+ }
+
+ UpdateFlags(modifier);
+}
+
+void EventModifiersEvdev::UpdateModifierLock(unsigned int modifier, bool down) {
+ DCHECK_LT(modifier, EVDEV_NUM_MODIFIERS);
+
+ if (down)
+ modifier_flags_locked_ ^= kEventFlagFromModifiers[modifier];
+
+ // TODO(spang): Synchronize with the CapsLock LED.
+
+ UpdateFlags(modifier);
+}
+
+void EventModifiersEvdev::UpdateFlags(unsigned int modifier) {
+ int mask = kEventFlagFromModifiers[modifier];
+ bool down = modifiers_down_[modifier];
+ bool locked = (modifier_flags_locked_ & mask);
+ if (down != locked)
+ modifier_flags_ |= mask;
+ else
+ modifier_flags_ &= ~mask;
+}
+
+int EventModifiersEvdev::GetModifierFlags() { return modifier_flags_; }
+
+// static
+int EventModifiersEvdev::GetEventFlagFromModifier(unsigned int modifier) {
+ return kEventFlagFromModifiers[modifier];
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/evdev/event_modifiers_evdev.h b/ui/events/ozone/evdev/event_modifiers_evdev.h
new file mode 100644
index 0000000..d10bb8c
--- /dev/null
+++ b/ui/events/ozone/evdev/event_modifiers_evdev.h
@@ -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.
+
+#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
+
+#include "base/basictypes.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+namespace ui {
+
+enum {
+ EVDEV_MODIFIER_NONE,
+ EVDEV_MODIFIER_CAPS_LOCK,
+ EVDEV_MODIFIER_SHIFT,
+ EVDEV_MODIFIER_CONTROL,
+ EVDEV_MODIFIER_ALT,
+ EVDEV_MODIFIER_LEFT_MOUSE_BUTTON,
+ EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON,
+ EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON,
+ EVDEV_MODIFIER_COMMAND,
+ EVDEV_MODIFIER_ALTGR,
+ EVDEV_NUM_MODIFIERS
+};
+
+// Modifier key state for Evdev.
+//
+// Chrome relies on the underlying OS to interpret modifier keys such as Shift,
+// Ctrl, and Alt. The Linux input subsystem does not assign any special meaning
+// to these keys, so this work must happen at a higher layer (normally X11 or
+// the console driver). When using evdev directly, we must do it ourselves.
+//
+// The modifier state is shared between all input devices connected to the
+// system. This is to support actions such as Shift-Clicking that use multiple
+// devices.
+//
+// Normally a modifier is set if any of the keys or buttons assigned to it are
+// 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_OZONE_EVDEV_EXPORT EventModifiersEvdev {
+ public:
+ EventModifiersEvdev();
+ ~EventModifiersEvdev();
+
+ // Record key press or release for regular modifier key (shift, alt, etc).
+ void UpdateModifier(unsigned int modifier, bool down);
+
+ // Record key press or release for locking modifier key (caps lock).
+ void UpdateModifierLock(unsigned int modifier, bool down);
+
+ // 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];
+
+ // Mask of modifier flags currently "locked".
+ int modifier_flags_locked_;
+
+ // Mask of modifier flags currently active (nonzero keys pressed xor locked).
+ int modifier_flags_;
+
+ // Update modifier_flags_ from modifiers_down_ and modifier_flags_locked_.
+ void UpdateFlags(unsigned int modifier);
+
+ DISALLOW_COPY_AND_ASSIGN(EventModifiersEvdev);
+};
+
+} // namspace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
diff --git a/ui/events/ozone/evdev/events_ozone_evdev_export.h b/ui/events/ozone/evdev/events_ozone_evdev_export.h
new file mode 100644
index 0000000..4a4cbac
--- /dev/null
+++ b/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/ui/events/ozone/evdev/key_event_converter_evdev.cc b/ui/events/ozone/evdev/key_event_converter_evdev.cc
new file mode 100644
index 0000000..195b514
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev.cc
@@ -0,0 +1,263 @@
+// 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_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/dom4/keycode_converter.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+
+namespace ui {
+
+namespace {
+
+const int kXkbKeycodeOffset = 8;
+
+ui::KeyboardCode KeyboardCodeFromButton(unsigned int code) {
+ static const ui::KeyboardCode kLinuxBaseKeyMap[] = {
+ ui::VKEY_UNKNOWN, // KEY_RESERVED
+ ui::VKEY_ESCAPE, // KEY_ESC
+ ui::VKEY_1, // KEY_1
+ ui::VKEY_2, // KEY_2
+ ui::VKEY_3, // KEY_3
+ ui::VKEY_4, // KEY_4
+ ui::VKEY_5, // KEY_5
+ ui::VKEY_6, // KEY_6
+ ui::VKEY_7, // KEY_7
+ ui::VKEY_8, // KEY_8
+ ui::VKEY_9, // KEY_9
+ ui::VKEY_0, // KEY_0
+ ui::VKEY_OEM_MINUS, // KEY_MINUS
+ ui::VKEY_OEM_PLUS, // KEY_EQUAL
+ ui::VKEY_BACK, // KEY_BACKSPACE
+ ui::VKEY_TAB, // KEY_TAB
+ ui::VKEY_Q, // KEY_Q
+ ui::VKEY_W, // KEY_W
+ ui::VKEY_E, // KEY_E
+ ui::VKEY_R, // KEY_R
+ ui::VKEY_T, // KEY_T
+ ui::VKEY_Y, // KEY_Y
+ ui::VKEY_U, // KEY_U
+ ui::VKEY_I, // KEY_I
+ ui::VKEY_O, // KEY_O
+ ui::VKEY_P, // KEY_P
+ ui::VKEY_OEM_4, // KEY_LEFTBRACE
+ ui::VKEY_OEM_6, // KEY_RIGHTBRACE
+ ui::VKEY_RETURN, // KEY_ENTER
+ ui::VKEY_CONTROL, // KEY_LEFTCTRL
+ ui::VKEY_A, // KEY_A
+ ui::VKEY_S, // KEY_S
+ ui::VKEY_D, // KEY_D
+ ui::VKEY_F, // KEY_F
+ ui::VKEY_G, // KEY_G
+ ui::VKEY_H, // KEY_H
+ ui::VKEY_J, // KEY_J
+ ui::VKEY_K, // KEY_K
+ ui::VKEY_L, // KEY_L
+ ui::VKEY_OEM_1, // KEY_SEMICOLON
+ ui::VKEY_OEM_7, // KEY_APOSTROPHE
+ ui::VKEY_OEM_3, // KEY_GRAVE
+ ui::VKEY_SHIFT, // KEY_LEFTSHIFT
+ ui::VKEY_OEM_5, // KEY_BACKSLASH
+ ui::VKEY_Z, // KEY_Z
+ ui::VKEY_X, // KEY_X
+ ui::VKEY_C, // KEY_C
+ ui::VKEY_V, // KEY_V
+ ui::VKEY_B, // KEY_B
+ ui::VKEY_N, // KEY_N
+ ui::VKEY_M, // KEY_M
+ ui::VKEY_OEM_COMMA, // KEY_COMMA
+ ui::VKEY_OEM_PERIOD, // KEY_DOT
+ ui::VKEY_OEM_2, // KEY_SLASH
+ ui::VKEY_SHIFT, // KEY_RIGHTSHIFT
+ ui::VKEY_MULTIPLY, // KEY_KPASTERISK
+ ui::VKEY_MENU, // KEY_LEFTALT
+ ui::VKEY_SPACE, // KEY_SPACE
+ ui::VKEY_CAPITAL, // KEY_CAPSLOCK
+ ui::VKEY_F1, // KEY_F1
+ ui::VKEY_F2, // KEY_F2
+ ui::VKEY_F3, // KEY_F3
+ ui::VKEY_F4, // KEY_F4
+ ui::VKEY_F5, // KEY_F5
+ ui::VKEY_F6, // KEY_F6
+ ui::VKEY_F7, // KEY_F7
+ ui::VKEY_F8, // KEY_F8
+ ui::VKEY_F9, // KEY_F9
+ ui::VKEY_F10, // KEY_F10
+ ui::VKEY_NUMLOCK, // KEY_NUMLOCK
+ ui::VKEY_SCROLL, // KEY_SCROLLLOCK
+ ui::VKEY_NUMPAD7, // KEY_KP7
+ ui::VKEY_NUMPAD8, // KEY_KP8
+ ui::VKEY_NUMPAD9, // KEY_KP9
+ ui::VKEY_SUBTRACT, // KEY_KPMINUS
+ ui::VKEY_NUMPAD4, // KEY_KP4
+ ui::VKEY_NUMPAD5, // KEY_KP5
+ ui::VKEY_NUMPAD6, // KEY_KP6
+ ui::VKEY_ADD, // KEY_KPPLUS
+ ui::VKEY_NUMPAD1, // KEY_KP1
+ ui::VKEY_NUMPAD2, // KEY_KP2
+ ui::VKEY_NUMPAD3, // KEY_KP3
+ ui::VKEY_NUMPAD0, // KEY_KP0
+ ui::VKEY_DECIMAL, // KEY_KPDOT
+ ui::VKEY_UNKNOWN, // (unassigned)
+ ui::VKEY_DBE_DBCSCHAR, // KEY_ZENKAKUHANKAKU
+ ui::VKEY_OEM_102, // KEY_102ND
+ ui::VKEY_F11, // KEY_F11
+ ui::VKEY_F12, // KEY_F12
+ ui::VKEY_UNKNOWN, // KEY_RO
+ ui::VKEY_UNKNOWN, // KEY_KATAKANA
+ ui::VKEY_UNKNOWN, // KEY_HIRAGANA
+ ui::VKEY_CONVERT, // KEY_HENKAN
+ ui::VKEY_UNKNOWN, // KEY_KATAKANAHIRAGANA
+ ui::VKEY_NONCONVERT, // KEY_MUHENKAN
+ ui::VKEY_UNKNOWN, // KEY_KPJPCOMMA
+ ui::VKEY_RETURN, // KEY_KPENTER
+ ui::VKEY_CONTROL, // KEY_RIGHTCTRL
+ ui::VKEY_DIVIDE, // KEY_KPSLASH
+ ui::VKEY_PRINT, // KEY_SYSRQ
+ ui::VKEY_MENU, // KEY_RIGHTALT
+ ui::VKEY_RETURN, // KEY_LINEFEED
+ ui::VKEY_HOME, // KEY_HOME
+ ui::VKEY_UP, // KEY_UP
+ ui::VKEY_PRIOR, // KEY_PAGEUP
+ ui::VKEY_LEFT, // KEY_LEFT
+ ui::VKEY_RIGHT, // KEY_RIGHT
+ ui::VKEY_END, // KEY_END
+ ui::VKEY_DOWN, // KEY_DOWN
+ ui::VKEY_NEXT, // KEY_PAGEDOWN
+ ui::VKEY_INSERT, // KEY_INSERT
+ ui::VKEY_DELETE, // KEY_DELETE
+ ui::VKEY_UNKNOWN, // KEY_MACRO
+ ui::VKEY_VOLUME_MUTE, // KEY_MUTE
+ ui::VKEY_VOLUME_DOWN, // KEY_VOLUMEDOWN
+ ui::VKEY_VOLUME_UP, // KEY_VOLUMEUP
+ ui::VKEY_POWER, // KEY_POWER
+ ui::VKEY_OEM_PLUS, // KEY_KPEQUAL
+ ui::VKEY_UNKNOWN, // KEY_KPPLUSMINUS
+ ui::VKEY_PAUSE, // KEY_PAUSE
+ ui::VKEY_MEDIA_LAUNCH_APP1, // KEY_SCALE
+ ui::VKEY_DECIMAL, // KEY_KPCOMMA
+ ui::VKEY_HANGUL, // KEY_HANGEUL
+ ui::VKEY_HANJA, // KEY_HANJA
+ ui::VKEY_UNKNOWN, // KEY_YEN
+ ui::VKEY_LWIN, // KEY_LEFTMETA
+ ui::VKEY_RWIN, // KEY_RIGHTMETA
+ ui::VKEY_APPS, // KEY_COMPOSE
+ };
+
+ if (code < arraysize(kLinuxBaseKeyMap))
+ return kLinuxBaseKeyMap[code];
+
+ LOG(ERROR) << "Unknown key code: " << code;
+ return ui::VKEY_UNKNOWN;
+}
+
+int ModifierFromButton(unsigned int code) {
+ switch (code) {
+ case KEY_CAPSLOCK:
+ return EVDEV_MODIFIER_CAPS_LOCK;
+ case KEY_LEFTSHIFT:
+ case KEY_RIGHTSHIFT:
+ return EVDEV_MODIFIER_SHIFT;
+ case KEY_LEFTCTRL:
+ case KEY_RIGHTCTRL:
+ return EVDEV_MODIFIER_CONTROL;
+ case KEY_LEFTALT:
+ case KEY_RIGHTALT:
+ return EVDEV_MODIFIER_ALT;
+ case BTN_LEFT:
+ return EVDEV_MODIFIER_LEFT_MOUSE_BUTTON;
+ case BTN_MIDDLE:
+ return EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON;
+ case BTN_RIGHT:
+ return EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON;
+ case KEY_LEFTMETA:
+ case KEY_RIGHTMETA:
+ return EVDEV_MODIFIER_COMMAND;
+ default:
+ return EVDEV_MODIFIER_NONE;
+ }
+}
+
+bool IsLockButton(unsigned int code) { return code == KEY_CAPSLOCK; }
+
+} // namespace
+
+KeyEventConverterEvdev::KeyEventConverterEvdev(
+ int fd,
+ base::FilePath path,
+ int id,
+ EventModifiersEvdev* modifiers,
+ const EventDispatchCallback& callback)
+ : EventConverterEvdev(fd, path, id),
+ callback_(callback),
+ modifiers_(modifiers) {
+ // TODO(spang): Initialize modifiers using EVIOCGKEY.
+}
+
+KeyEventConverterEvdev::~KeyEventConverterEvdev() {
+ Stop();
+ close(fd_);
+}
+
+void KeyEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) {
+ input_event inputs[4];
+ 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;
+ }
+
+ DCHECK_EQ(read_size % sizeof(*inputs), 0u);
+ ProcessEvents(inputs, read_size / sizeof(*inputs));
+}
+
+void KeyEventConverterEvdev::ProcessEvents(const input_event* inputs,
+ int count) {
+ for (int i = 0; i < count; ++i) {
+ const input_event& input = inputs[i];
+ if (input.type == EV_KEY) {
+ ConvertKeyEvent(input.code, input.value);
+ } else if (input.type == EV_SYN) {
+ // TODO(sadrul): Handle this case appropriately.
+ }
+ }
+}
+
+void KeyEventConverterEvdev::ConvertKeyEvent(int key, int value) {
+ int down = (value != 0);
+ int repeat = (value == 2);
+ int modifier = ModifierFromButton(key);
+ ui::KeyboardCode code = KeyboardCodeFromButton(key);
+
+ if (!repeat && (modifier != EVDEV_MODIFIER_NONE)) {
+ if (IsLockButton(key)) {
+ // Locking modifier keys: CapsLock.
+ modifiers_->UpdateModifierLock(modifier, down);
+ } else {
+ // Regular modifier keys: Shift, Ctrl, Alt, etc.
+ modifiers_->UpdateModifier(modifier, down);
+ }
+ }
+
+ int flags = modifiers_->GetModifierFlags();
+
+ KeyEvent key_event(
+ down ? ET_KEY_PRESSED : ET_KEY_RELEASED,
+ code,
+ KeycodeConverter::NativeKeycodeToCode(key + kXkbKeycodeOffset),
+ flags);
+ callback_.Run(&key_event);
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/evdev/key_event_converter_evdev.h b/ui/events/ozone/evdev/key_event_converter_evdev.h
new file mode 100644
index 0000000..d1cab8c
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev.h
@@ -0,0 +1,52 @@
+// 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:
+ KeyEventConverterEvdev(int fd,
+ base::FilePath path,
+ int id,
+ EventModifiersEvdev* modifiers,
+ const EventDispatchCallback& dispatch);
+ virtual ~KeyEventConverterEvdev();
+
+ // EventConverterEvdev:
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+
+ void ProcessEvents(const struct input_event* inputs, int count);
+
+ private:
+ // Callback for dispatching events.
+ EventDispatchCallback callback_;
+
+ // 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/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
new file mode 100644
index 0000000..575e86d
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
@@ -0,0 +1,351 @@
+// 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 <linux/input.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 "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_evdev.h"
+
+namespace ui {
+
+const char kTestDevicePath[] = "/dev/input/test-device";
+
+class MockKeyEventConverterEvdev : public KeyEventConverterEvdev {
+ public:
+ MockKeyEventConverterEvdev(int fd, EventModifiersEvdev* modifiers)
+ : KeyEventConverterEvdev(
+ fd,
+ base::FilePath(kTestDevicePath),
+ 1,
+ modifiers,
+ base::Bind(&MockKeyEventConverterEvdev::DispatchEventForTest,
+ base::Unretained(this))) {
+ Start();
+ }
+ virtual ~MockKeyEventConverterEvdev() {};
+
+ unsigned size() { return dispatched_events_.size(); }
+ KeyEvent* event(unsigned index) {
+ DCHECK_GT(dispatched_events_.size(), index);
+ return dispatched_events_[index];
+ }
+
+ void DispatchEventForTest(Event* event);
+
+ private:
+ ScopedVector<KeyEvent> dispatched_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockKeyEventConverterEvdev);
+};
+
+void MockKeyEventConverterEvdev::DispatchEventForTest(Event* event) {
+ dispatched_events_.push_back(new KeyEvent(*static_cast<KeyEvent*>(event)));
+}
+
+} // namespace ui
+
+// Test fixture.
+class KeyEventConverterEvdevTest : public testing::Test {
+ public:
+ KeyEventConverterEvdevTest() {}
+
+ // 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(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);
+};
+
+TEST_F(KeyEventConverterEvdevTest, KeyPress) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+ EXPECT_EQ(2u, dev->size());
+
+ ui::KeyEvent* event;
+
+ event = dev->event(0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+
+ event = dev->event(1);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyRepeat) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 2},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 2},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+ {{0, 0}, EV_KEY, KEY_BACKSPACE, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+ EXPECT_EQ(4u, dev->size());
+
+ ui::KeyEvent* event;
+
+ event = dev->event(0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+
+ event = dev->event(1);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+
+ event = dev->event(2);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+
+ event = dev->event(3);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+ EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, NoEvents) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+ dev->ProcessEvents(NULL, 0);
+ EXPECT_EQ(0u, dev->size());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithModifier) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+ {{0, 0}, EV_KEY, KEY_LEFTSHIFT, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70004},
+ {{0, 0}, EV_KEY, KEY_A, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70004},
+ {{0, 0}, EV_KEY, KEY_A, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+ {{0, 0}, EV_KEY, KEY_LEFTSHIFT, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+ EXPECT_EQ(4u, dev->size());
+
+ ui::KeyEvent* event;
+
+ event = dev->event(0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_SHIFT, event->key_code());
+ EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+ event = dev->event(1);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_A, event->key_code());
+ EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+ event = dev->event(2);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_A, event->key_code());
+ EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+ event = dev->event(3);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_SHIFT, event->key_code());
+ EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithDuplicateModifier) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+ {{0, 0}, EV_KEY, KEY_LEFTCTRL, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e5},
+ {{0, 0}, EV_KEY, KEY_RIGHTCTRL, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7001d},
+ {{0, 0}, EV_KEY, KEY_Z, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x7001d},
+ {{0, 0}, EV_KEY, KEY_Z, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+ {{0, 0}, EV_KEY, KEY_LEFTCTRL, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x700e5},
+ {{0, 0}, EV_KEY, KEY_RIGHTCTRL, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+ EXPECT_EQ(6u, dev->size());
+
+ ui::KeyEvent* event;
+
+ event = dev->event(0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+ EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+ event = dev->event(1);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+ EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+ event = dev->event(2);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_Z, event->key_code());
+ EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+ event = dev->event(3);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_Z, event->key_code());
+ EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+ event = dev->event(4);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+ EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+ event = dev->event(5);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+ EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithLock) {
+ ui::MockKeyEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+ {{0, 0}, EV_KEY, KEY_CAPSLOCK, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+ {{0, 0}, EV_KEY, KEY_CAPSLOCK, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70014},
+ {{0, 0}, EV_KEY, KEY_Q, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70014},
+ {{0, 0}, EV_KEY, KEY_Q, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+ {{0, 0}, EV_KEY, KEY_CAPSLOCK, 1},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+ {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+ {{0, 0}, EV_KEY, KEY_CAPSLOCK, 0},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+ EXPECT_EQ(6u, dev->size());
+
+ ui::KeyEvent* event;
+
+ event = dev->event(0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+ EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+ event = dev->event(1);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+ EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+ event = dev->event(2);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_Q, event->key_code());
+ EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+ event = dev->event(3);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_Q, event->key_code());
+ EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+ event = dev->event(4);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+ EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+ EXPECT_EQ(0, event->flags());
+
+ event = dev->event(5);
+ EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+ EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+ EXPECT_EQ(0, event->flags());
+}
diff --git a/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
new file mode 100644
index 0000000..f7859cf
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
@@ -0,0 +1,91 @@
+// 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,
+ int id,
+ scoped_ptr<Delegate> delegate)
+ : EventConverterEvdev(fd, path, id), 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::OnFileCanReadWithoutBlocking(int fd) {
+ if (EvdevRead(&evdev_)) {
+ if (errno == EINTR || errno == EAGAIN)
+ return;
+ if (errno != ENODEV)
+ PLOG(ERROR) << "error reading device " << path_.value();
+ Stop();
+ return;
+ }
+}
+
+// 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/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
new file mode 100644
index 0000000..a67d037
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
@@ -0,0 +1,70 @@
+// 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 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,
+ int id,
+ scoped_ptr<Delegate> delegate);
+ ~EventReaderLibevdevCros();
+
+ // EventConverterEvdev:
+ virtual void OnFileCanReadWithoutBlocking(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_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventReaderLibevdevCros);
+};
+
+} // namspace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
new file mode 100644
index 0000000..59d8735
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
@@ -0,0 +1,398 @@
+// 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;
+
+// Number of fingers for swipe gestures.
+const int kGestureSwipeFingerCount = 3;
+
+} // 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) {
+ DCHECK(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.
+ DCHECK(!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:
+ OnGestureContactInitiated(gesture);
+ break;
+ case kGestureTypeFling:
+ OnGestureFling(gesture, &gesture->details.fling);
+ break;
+ case kGestureTypeSwipe:
+ OnGestureSwipe(gesture, &gesture->details.swipe);
+ break;
+ case kGestureTypeSwipeLift:
+ OnGestureSwipeLift(gesture, &gesture->details.swipe_lift);
+ break;
+ case kGestureTypePinch:
+ OnGesturePinch(gesture, &gesture->details.pinch);
+ break;
+ case kGestureTypeMetrics:
+ OnGestureMetrics(gesture, &gesture->details.metrics);
+ 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) {
+ DVLOG(3) << base::StringPrintf("Gesture Scroll: (%f, %f) [%f, %f]",
+ scroll->dx,
+ scroll->dy,
+ scroll->ordinal_dx,
+ scroll->ordinal_dy);
+ if (!cursor_)
+ return; // No cursor!
+
+ // 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);
+
+ if (!cursor_)
+ return; // No cursor!
+
+ // HACK for disabling TTC (actually, all clicks) on hidden cursor.
+ // This is normally plumbed via properties and can be removed soon.
+ // TODO(spang): Remove this.
+ if (buttons->down == GESTURES_BUTTON_LEFT &&
+ buttons->up == GESTURES_BUTTON_LEFT &&
+ !cursor_->IsCursorVisible())
+ return;
+
+ // 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::OnGestureContactInitiated(
+ const Gesture* gesture) {
+ // TODO(spang): handle contact initiated.
+}
+
+void GestureInterpreterLibevdevCros::OnGestureFling(const Gesture* gesture,
+ const GestureFling* fling) {
+ DVLOG(3) << base::StringPrintf(
+ "Gesture Fling: (%f, %f) [%f, %f] fling_state=%d",
+ fling->vx,
+ fling->vy,
+ fling->ordinal_vx,
+ fling->ordinal_vy,
+ fling->fling_state);
+
+ if (!cursor_)
+ return; // No cursor!
+
+ EventType type =
+ (fling->fling_state == GESTURES_FLING_START ? ET_SCROLL_FLING_START
+ : ET_SCROLL_FLING_CANCEL);
+
+ // Fling is like 2-finger scrolling but with velocity instead of displacement.
+ ScrollEvent event(type,
+ cursor_->location(),
+ StimeToTimedelta(gesture->end_time),
+ modifiers_->GetModifierFlags(),
+ fling->vx,
+ fling->vy,
+ fling->ordinal_vx,
+ fling->ordinal_vy,
+ kGestureScrollFingerCount);
+ Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureSwipe(const Gesture* gesture,
+ const GestureSwipe* swipe) {
+ DVLOG(3) << base::StringPrintf("Gesture Swipe: (%f, %f) [%f, %f]",
+ swipe->dx,
+ swipe->dy,
+ swipe->ordinal_dx,
+ swipe->ordinal_dy);
+
+ if (!cursor_)
+ return; // No cursor!
+
+ // Swipe is 3-finger scrolling.
+ ScrollEvent event(ET_SCROLL,
+ cursor_->location(),
+ StimeToTimedelta(gesture->end_time),
+ modifiers_->GetModifierFlags(),
+ swipe->dx,
+ swipe->dy,
+ swipe->ordinal_dx,
+ swipe->ordinal_dy,
+ kGestureSwipeFingerCount);
+ Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureSwipeLift(
+ const Gesture* gesture,
+ const GestureSwipeLift* swipelift) {
+ DVLOG(3) << base::StringPrintf("Gesture Swipe Lift");
+
+ if (!cursor_)
+ return; // No cursor!
+
+ // Turn a swipe lift into a fling start.
+ // TODO(spang): Figure out why and put it in this comment.
+
+ ScrollEvent event(ET_SCROLL_FLING_START,
+ cursor_->location(),
+ StimeToTimedelta(gesture->end_time),
+ modifiers_->GetModifierFlags(),
+ /* x_offset */ 0,
+ /* y_offset */ 0,
+ /* x_offset_ordinal */ 0,
+ /* y_offset_ordinal */ 0,
+ kGestureScrollFingerCount);
+ Dispatch(&event);
+
+}
+
+void GestureInterpreterLibevdevCros::OnGesturePinch(const Gesture* gesture,
+ const GesturePinch* pinch) {
+ DVLOG(3) << base::StringPrintf(
+ "Gesture Pinch: dz=%f [%f]", pinch->dz, pinch->ordinal_dz);
+
+ if (!cursor_)
+ return; // No cursor!
+
+ NOTIMPLEMENTED();
+}
+
+void GestureInterpreterLibevdevCros::OnGestureMetrics(
+ const Gesture* gesture,
+ const GestureMetrics* metrics) {
+ DVLOG(3) << base::StringPrintf("Gesture Metrics: [%f, %f] type=%d",
+ metrics->data[0],
+ metrics->data[1],
+ metrics->type);
+ NOTIMPLEMENTED();
+}
+
+void GestureInterpreterLibevdevCros::Dispatch(Event* event) {
+ dispatch_callback_.Run(event);
+}
+
+void GestureInterpreterLibevdevCros::DispatchMouseButton(unsigned int modifier,
+ bool down) {
+ 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);
+ Dispatch(&event);
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h
new file mode 100644
index 0000000..1044a11
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h
@@ -0,0 +1,90 @@
+// 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 OnGestureContactInitiated(const Gesture* gesture);
+ void OnGestureFling(const Gesture* gesture, const GestureFling* fling);
+ void OnGestureSwipe(const Gesture* gesture, const GestureSwipe* swipe);
+ void OnGestureSwipeLift(const Gesture* gesture,
+ const GestureSwipeLift* swipelift);
+ void OnGesturePinch(const Gesture* gesture, const GesturePinch* pinch);
+ void OnGestureMetrics(const Gesture* gesture, const GestureMetrics* metrics);
+
+ 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/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc
new file mode 100644
index 0000000..009fc93
--- /dev/null
+++ b/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/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h
new file mode 100644
index 0000000..a5f5435
--- /dev/null
+++ b/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/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc
new file mode 100644
index 0000000..6aad161
--- /dev/null
+++ b/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;
+ DCHECK(!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/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h
new file mode 100644
index 0000000..edc20ba
--- /dev/null
+++ b/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/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
new file mode 100644
index 0000000..7b666e0
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -0,0 +1,302 @@
+// 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"
+
+namespace {
+
+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;
+}
+
+float TuxelToPixelSize(float val, float num_tuxels, float num_pixels) {
+ return val * num_pixels / num_tuxels;
+}
+
+} // namespace
+
+namespace ui {
+
+TouchEventConverterEvdev::TouchEventConverterEvdev(
+ int fd,
+ base::FilePath path,
+ int id,
+ const EventDeviceInfo& info,
+ const EventDispatchCallback& callback)
+ : EventConverterEvdev(fd, path, id),
+ callback_(callback),
+ syn_dropped_(false),
+ is_type_a_(false),
+ current_slot_(0) {
+ 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;
+ native_size_ = gfx::Size(x_num_tuxels_, 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);
+}
+
+bool TouchEventConverterEvdev::Reinitialize() {
+ EventDeviceInfo info;
+ if (info.Initialize(fd_)) {
+ Init(info);
+ return true;
+ }
+ return false;
+}
+
+bool TouchEventConverterEvdev::HasTouchscreen() const {
+ return true;
+}
+
+gfx::Size TouchEventConverterEvdev::GetTouchscreenSize() const {
+ return native_size_;
+}
+
+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_);
+ // TODO(spang): If we have all of major, minor, and orientation,
+ // we can scale the ellipse correctly. However on the Pixel we get
+ // neither minor nor orientation, so this is all we can do.
+ events_[current_slot_].radius_x_ =
+ TuxelToPixelSize(input.value, x_num_tuxels_, x_num_pixels_) / 2.0f;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ altered_slots_.set(current_slot_);
+ events_[current_slot_].radius_y_ =
+ TuxelToPixelSize(input.value, y_num_tuxels_, y_num_pixels_) / 2.0f;
+ break;
+ 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_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:
+ 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:
+ DVLOG(5) << "unhandled 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,
+ /* radius_x */ events_[i].radius_x_,
+ /* radius_y */ events_[i].radius_y_,
+ /* angle */ 0.,
+ events_[i].pressure_);
+ callback_.Run(&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/ui/events/ozone/evdev/touch_event_converter_evdev.h b/ui/events/ozone/evdev/touch_event_converter_evdev.h
new file mode 100644
index 0000000..f85e0fe
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.h
@@ -0,0 +1,115 @@
+// 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:
+ enum {
+ MAX_FINGERS = 11
+ };
+ TouchEventConverterEvdev(int fd,
+ base::FilePath path,
+ int id,
+ const EventDeviceInfo& info,
+ const EventDispatchCallback& dispatch);
+ virtual ~TouchEventConverterEvdev();
+
+ // EventConverterEvdev:
+ virtual bool HasTouchscreen() const OVERRIDE;
+ virtual gfx::Size GetTouchscreenSize() const 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 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);
+
+ // Callback for dispatching events.
+ EventDispatchCallback callback_;
+
+ // 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_;
+
+ // Size of the touchscreen as reported by the driver.
+ gfx::Size native_size_;
+
+ // Touch point currently being updated from the /dev/input/event* stream.
+ int current_slot_;
+
+ // 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_;
+ float radius_x_;
+ float radius_y_;
+ 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/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
new file mode 100644
index 0000000..95dc5c4
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -0,0 +1,529 @@
+// 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 <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.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_evdev.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+
+namespace {
+
+static int SetNonBlocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1)
+ flags = 0;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+const char kTestDevicePath[] = "/dev/input/test-device";
+
+} // namespace
+
+namespace ui {
+
+class MockTouchEventConverterEvdev : public TouchEventConverterEvdev {
+ public:
+ MockTouchEventConverterEvdev(int fd, base::FilePath path);
+ virtual ~MockTouchEventConverterEvdev() {};
+
+ void ConfigureReadMock(struct input_event* queue,
+ long read_this_many,
+ long queue_index);
+
+ unsigned size() { return dispatched_events_.size(); }
+ TouchEvent* event(unsigned index) { return dispatched_events_[index]; }
+
+ // Actually dispatch the event reader code.
+ void ReadNow() {
+ OnFileCanReadWithoutBlocking(read_pipe_);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ void DispatchCallback(Event* event) {
+ dispatched_events_.push_back(
+ new TouchEvent(*static_cast<TouchEvent*>(event)));
+ }
+
+ virtual bool Reinitialize() OVERRIDE { return true; }
+
+ private:
+ int read_pipe_;
+ int write_pipe_;
+
+ ScopedVector<TouchEvent> dispatched_events_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockTouchEventConverterEvdev);
+};
+
+MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int fd,
+ base::FilePath path)
+ : TouchEventConverterEvdev(
+ fd,
+ path,
+ 1,
+ 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))
+ PLOG(FATAL) << "failed pipe";
+
+ DCHECK(SetNonBlocking(fds[0]) == 0)
+ << "SetNonBlocking for pipe fd[0] failed, errno: " << errno;
+ DCHECK(SetNonBlocking(fds[1]) == 0)
+ << "SetNonBlocking for pipe fd[0] failed, errno: " << errno;
+ read_pipe_ = fds[0];
+ write_pipe_ = fds[1];
+}
+
+void MockTouchEventConverterEvdev::ConfigureReadMock(struct input_event* queue,
+ long read_this_many,
+ long queue_index) {
+ int nwrite = HANDLE_EINTR(write(write_pipe_,
+ queue + queue_index,
+ sizeof(struct input_event) * read_this_many));
+ DCHECK(nwrite ==
+ static_cast<int>(sizeof(struct input_event) * read_this_many))
+ << "write() failed, errno: " << errno;
+}
+
+} // namespace ui
+
+// Test fixture.
+class TouchEventConverterEvdevTest : public testing::Test {
+ public:
+ TouchEventConverterEvdevTest() {}
+
+ // 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];
+
+ loop_ = new base::MessageLoopForUI;
+ device_ = new ui::MockTouchEventConverterEvdev(
+ events_in_, base::FilePath(kTestDevicePath));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ delete device_;
+ delete loop_;
+ }
+
+ ui::MockTouchEventConverterEvdev* device() { return device_; }
+
+ private:
+ base::MessageLoop* loop_;
+ ui::MockTouchEventConverterEvdev* device_;
+
+ int events_out_;
+ int events_in_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdevTest);
+};
+
+// TODO(rjkroege): Test for valid handling of time stamps.
+TEST_F(TouchEventConverterEvdevTest, TouchDown) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{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, 1, 0);
+ dev->ReadNow();
+ EXPECT_EQ(0u, dev->size());
+
+ dev->ConfigureReadMock(mock_kernel_queue, 2, 1);
+ dev->ReadNow();
+ EXPECT_EQ(0u, dev->size());
+
+ dev->ConfigureReadMock(mock_kernel_queue, 3, 3);
+ dev->ReadNow();
+ EXPECT_EQ(1u, dev->size());
+
+ ui::TouchEvent* event = dev->event(0);
+ EXPECT_FALSE(event == NULL);
+
+ EXPECT_EQ(ui::ET_TOUCH_PRESSED, event->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+ EXPECT_EQ(42, event->x());
+ EXPECT_EQ(51, event->y());
+ EXPECT_EQ(0, event->touch_id());
+ EXPECT_FLOAT_EQ(1.5f, event->radius_x());
+ EXPECT_FLOAT_EQ(.5f, event->force());
+ EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, NoEvents) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+ dev->ConfigureReadMock(NULL, 0, 0);
+ EXPECT_EQ(0u, dev->size());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TouchMove) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue_press[] = {
+ {{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}
+ };
+
+ struct input_event mock_kernel_queue_move1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 50},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 43}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+
+ struct input_event mock_kernel_queue_move2[] = {
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 42}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+
+ // Setup and discard a press.
+ dev->ConfigureReadMock(mock_kernel_queue_press, 6, 0);
+ dev->ReadNow();
+ EXPECT_EQ(1u, dev->size());
+
+ dev->ConfigureReadMock(mock_kernel_queue_move1, 4, 0);
+ dev->ReadNow();
+ EXPECT_EQ(2u, dev->size());
+ ui::TouchEvent* event = dev->event(1);
+ EXPECT_FALSE(event == NULL);
+
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, event->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+ EXPECT_EQ(42, event->x());
+ EXPECT_EQ(43, event->y());
+ EXPECT_EQ(0, event->touch_id());
+ EXPECT_FLOAT_EQ(2.f / 3.f, event->force());
+ EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+
+ dev->ConfigureReadMock(mock_kernel_queue_move2, 2, 0);
+ dev->ReadNow();
+ EXPECT_EQ(3u, dev->size());
+ event = dev->event(2);
+ EXPECT_FALSE(event == NULL);
+
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, event->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+ EXPECT_EQ(42, event->x());
+ EXPECT_EQ(42, event->y());
+ EXPECT_EQ(0, event->touch_id());
+ EXPECT_FLOAT_EQ(2.f / 3.f, event->force());
+ EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TouchRelease) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue_press[] = {
+ {{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}
+ };
+
+ struct input_event mock_kernel_queue_release[] = {
+ {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+
+ // Setup and discard a press.
+ dev->ConfigureReadMock(mock_kernel_queue_press, 6, 0);
+ dev->ReadNow();
+ EXPECT_EQ(1u, dev->size());
+ ui::TouchEvent* event = dev->event(0);
+ EXPECT_FALSE(event == NULL);
+
+ dev->ConfigureReadMock(mock_kernel_queue_release, 2, 0);
+ dev->ReadNow();
+ EXPECT_EQ(2u, dev->size());
+ event = dev->event(1);
+ EXPECT_FALSE(event == NULL);
+
+ EXPECT_EQ(ui::ET_TOUCH_RELEASED, event->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+ EXPECT_EQ(42, event->x());
+ EXPECT_EQ(51, event->y());
+ EXPECT_EQ(0, event->touch_id());
+ EXPECT_FLOAT_EQ(.5f, event->force());
+ EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TwoFingerGesture) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+
+ ui::TouchEvent* ev0;
+ ui::TouchEvent* ev1;
+
+ 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}
+ };
+ // Setup and discard a press.
+ dev->ConfigureReadMock(mock_kernel_queue_press0, 6, 0);
+ dev->ReadNow();
+ EXPECT_EQ(1u, dev->size());
+
+ struct input_event mock_kernel_queue_move0[] = {
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+ // Setup and discard a move.
+ dev->ConfigureReadMock(mock_kernel_queue_move0, 2, 0);
+ dev->ReadNow();
+ EXPECT_EQ(2u, dev->size());
+
+ struct input_event mock_kernel_queue_move0press1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ {{0, 0}, EV_ABS, ABS_MT_SLOT, 1}, {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 686},
+ {{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, 101},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 102}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+ // Move on 0, press on 1.
+ dev->ConfigureReadMock(mock_kernel_queue_move0press1, 9, 0);
+ dev->ReadNow();
+ EXPECT_EQ(4u, dev->size());
+ ev0 = dev->event(2);
+ ev1 = dev->event(3);
+
+ // Move
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, ev0->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+ EXPECT_EQ(40, ev0->x());
+ EXPECT_EQ(51, ev0->y());
+ EXPECT_EQ(0, ev0->touch_id());
+ EXPECT_FLOAT_EQ(.5f, ev0->force());
+ EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+ // Press
+ EXPECT_EQ(ui::ET_TOUCH_PRESSED, ev1->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+ EXPECT_EQ(101, ev1->x());
+ EXPECT_EQ(102, ev1->y());
+ EXPECT_EQ(1, ev1->touch_id());
+ EXPECT_FLOAT_EQ(.5f, ev1->force());
+ EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+ // Stationary 0, Moves 1.
+ struct input_event mock_kernel_queue_stationary0_move1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+ dev->ConfigureReadMock(mock_kernel_queue_stationary0_move1, 2, 0);
+ dev->ReadNow();
+ EXPECT_EQ(5u, dev->size());
+ ev1 = dev->event(4);
+
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, ev1->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+ EXPECT_EQ(40, ev1->x());
+ EXPECT_EQ(102, ev1->y());
+ EXPECT_EQ(1, ev1->touch_id());
+
+ EXPECT_FLOAT_EQ(.5f, ev1->force());
+ EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+ // Move 0, stationary 1.
+ struct input_event mock_kernel_queue_move0_stationary1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_SLOT, 0}, {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 39},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+ dev->ConfigureReadMock(mock_kernel_queue_move0_stationary1, 3, 0);
+ dev->ReadNow();
+ EXPECT_EQ(6u, dev->size());
+ ev0 = dev->event(5);
+
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, ev0->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+ EXPECT_EQ(39, ev0->x());
+ EXPECT_EQ(51, ev0->y());
+ EXPECT_EQ(0, ev0->touch_id());
+ EXPECT_FLOAT_EQ(.5f, ev0->force());
+ EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+ // Release 0, move 1.
+ struct input_event mock_kernel_queue_release0_move1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_ABS, ABS_MT_SLOT, 1},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 38}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+ };
+ dev->ConfigureReadMock(mock_kernel_queue_release0_move1, 4, 0);
+ dev->ReadNow();
+ EXPECT_EQ(8u, dev->size());
+ ev0 = dev->event(6);
+ ev1 = dev->event(7);
+
+ EXPECT_EQ(ui::ET_TOUCH_RELEASED, ev0->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+ EXPECT_EQ(39, ev0->x());
+ EXPECT_EQ(51, ev0->y());
+ EXPECT_EQ(0, ev0->touch_id());
+ EXPECT_FLOAT_EQ(.5f, ev0->force());
+ EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, ev1->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+ EXPECT_EQ(38, ev1->x());
+ EXPECT_EQ(102, ev1->y());
+ EXPECT_EQ(1, ev1->touch_id());
+ EXPECT_FLOAT_EQ(.5f, ev1->force());
+ EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+ // Release 1.
+ struct input_event mock_kernel_queue_release1[] = {
+ {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+ dev->ConfigureReadMock(mock_kernel_queue_release1, 2, 0);
+ dev->ReadNow();
+ EXPECT_EQ(9u, dev->size());
+ ev1 = dev->event(8);
+
+ EXPECT_EQ(ui::ET_TOUCH_RELEASED, ev1->type());
+ EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+ EXPECT_EQ(38, ev1->x());
+ EXPECT_EQ(102, ev1->y());
+ EXPECT_EQ(1, ev1->touch_id());
+ 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());
+}
+
+// crbug.com/407386
+TEST_F(TouchEventConverterEvdevTest,
+ DontChangeMultitouchPositionFromLegacyAxes) {
+ ui::MockTouchEventConverterEvdev* dev = device();
+
+ struct input_event mock_kernel_queue[] = {
+ {{0, 0}, EV_ABS, ABS_MT_SLOT, 0},
+ {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 100},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 999},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 888},
+ {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 55},
+ {{0, 0}, EV_ABS, ABS_MT_SLOT, 1},
+ {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 200},
+ {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 44},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 777},
+ {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 666},
+ {{0, 0}, EV_ABS, ABS_X, 999},
+ {{0, 0}, EV_ABS, ABS_Y, 888},
+ {{0, 0}, EV_ABS, ABS_PRESSURE, 55},
+ {{0, 0}, EV_SYN, SYN_REPORT, 0},
+ };
+
+ // Check that two events are generated.
+ dev->ConfigureReadMock(mock_kernel_queue, arraysize(mock_kernel_queue), 0);
+ dev->ReadNow();
+
+ const unsigned int kExpectedEventCount = 2;
+ EXPECT_EQ(kExpectedEventCount, dev->size());
+ if (kExpectedEventCount != dev->size())
+ return;
+
+ ui::TouchEvent* ev0 = dev->event(0);
+ ui::TouchEvent* ev1 = dev->event(1);
+
+ EXPECT_EQ(0, ev0->touch_id());
+ EXPECT_EQ(999, ev0->x());
+ EXPECT_EQ(888, ev0->y());
+ EXPECT_FLOAT_EQ(0.8333333f, ev0->force());
+
+ EXPECT_EQ(1, ev1->touch_id());
+ EXPECT_EQ(777, ev1->x());
+ EXPECT_EQ(666, ev1->y());
+ EXPECT_FLOAT_EQ(0.4666666f, ev1->force());
+}
diff --git a/ui/events/ozone/events_ozone.cc b/ui/events/ozone/events_ozone.cc
new file mode 100644
index 0000000..d2e1e33
--- /dev/null
+++ b/ui/events/ozone/events_ozone.cc
@@ -0,0 +1,201 @@
+// 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/events_ozone.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+
+namespace ui {
+
+void UpdateDeviceList() { NOTIMPLEMENTED(); }
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+ const ui::Event* event = static_cast<const ui::Event*>(native_event);
+ return event->time_stamp();
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+ const ui::Event* event = static_cast<const ui::Event*>(native_event);
+ return event->flags();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+ const ui::Event* event = static_cast<const ui::Event*>(native_event);
+ return event->type();
+}
+
+gfx::Point EventSystemLocationFromNative(
+ const base::NativeEvent& native_event) {
+ const ui::LocatedEvent* e =
+ static_cast<const ui::LocatedEvent*>(native_event);
+ DCHECK(e->IsMouseEvent() || e->IsTouchEvent() || e->IsGestureEvent() ||
+ e->IsScrollEvent());
+ return e->location();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+ return EventSystemLocationFromNative(native_event);
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+ const base::NativeEvent& native_event) {
+ const ui::MouseEvent* event =
+ static_cast<const ui::MouseEvent*>(native_event);
+ DCHECK(event->IsMouseEvent() || event->IsScrollEvent());
+ return event->changed_button_flags();
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+ const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+ DCHECK(event->IsKeyEvent());
+ return event->key_code();
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+ const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+ DCHECK(event->IsKeyEvent());
+ return event->code().c_str();
+}
+
+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();
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+ const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+ DCHECK(event->IsKeyEvent());
+ return event->is_char();
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+ const ui::MouseWheelEvent* event =
+ static_cast<const ui::MouseWheelEvent*>(native_event);
+ DCHECK(event->type() == ET_MOUSEWHEEL);
+ return event->offset();
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+ return NULL;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& event) {
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+ const ui::TouchEvent* event =
+ static_cast<const ui::TouchEvent*>(native_event);
+ DCHECK(event->IsTouchEvent());
+ return event->touch_id();
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+ const ui::TouchEvent* event =
+ static_cast<const ui::TouchEvent*>(native_event);
+ DCHECK(event->IsTouchEvent());
+ return event->radius_x();
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+ const ui::TouchEvent* event =
+ static_cast<const ui::TouchEvent*>(native_event);
+ DCHECK(event->IsTouchEvent());
+ return event->radius_y();
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+ const ui::TouchEvent* event =
+ static_cast<const ui::TouchEvent*>(native_event);
+ DCHECK(event->IsTouchEvent());
+ return event->rotation_angle();
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+ const ui::TouchEvent* event =
+ static_cast<const ui::TouchEvent*>(native_event);
+ DCHECK(event->IsTouchEvent());
+ return event->force();
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count) {
+ const ui::ScrollEvent* event =
+ static_cast<const ui::ScrollEvent*>(native_event);
+ DCHECK(event->IsScrollEvent());
+ if (x_offset)
+ *x_offset = event->x_offset();
+ if (y_offset)
+ *y_offset = event->y_offset();
+ if (x_offset_ordinal)
+ *x_offset_ordinal = event->x_offset_ordinal();
+ if (y_offset_ordinal)
+ *y_offset_ordinal = event->y_offset_ordinal();
+ if (finger_count)
+ *finger_count = event->finger_count();
+
+ return true;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ const ui::ScrollEvent* event =
+ static_cast<const ui::ScrollEvent*>(native_event);
+ DCHECK(event->IsScrollEvent());
+ if (vx)
+ *vx = event->x_offset();
+ if (vy)
+ *vy = event->y_offset();
+ if (vx_ordinal)
+ *vx_ordinal = event->x_offset_ordinal();
+ if (vy_ordinal)
+ *vy_ordinal = event->y_offset_ordinal();
+ if (is_cancel)
+ *is_cancel = event->type() == ET_SCROLL_FLING_CANCEL;
+
+ return true;
+}
+
+int GetModifiersFromKeyState() {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+void DispatchEventFromNativeUiEvent(const base::NativeEvent& native_event,
+ base::Callback<void(ui::Event*)> callback) {
+ const ui::Event* native_ui_event = static_cast<ui::Event*>(native_event);
+ if (native_ui_event->IsKeyEvent()) {
+ ui::KeyEvent key_event(native_event);
+ callback.Run(&key_event);
+ } else if (native_ui_event->IsMouseEvent()) {
+ ui::MouseEvent mouse_event(native_event);
+ callback.Run(&mouse_event);
+ } else if (native_ui_event->IsTouchEvent()) {
+ ui::TouchEvent touch_event(native_event);
+ callback.Run(&touch_event);
+ } else if (native_ui_event->IsScrollEvent()) {
+ ui::ScrollEvent scroll_event(native_event);
+ callback.Run(&scroll_event);
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/ozone/events_ozone.gyp b/ui/events/ozone/events_ozone.gyp
new file mode 100644
index 0000000..1c88263
--- /dev/null
+++ b/ui/events/ozone/events_ozone.gyp
@@ -0,0 +1,98 @@
+# 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',
+ '../events.gyp:dom4_keycode_converter',
+ '../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/ui/events/ozone/events_ozone.h b/ui/events/ozone/events_ozone.h
new file mode 100644
index 0000000..ad603cd
--- /dev/null
+++ b/ui/events/ozone/events_ozone.h
@@ -0,0 +1,39 @@
+// 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_H_
+#define UI_EVENTS_OZONE_EVENTS_OZONE_H_
+
+#include "base/callback.h"
+#include "base/event_types.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+
+// Wrap a "native" ui::Event in another ui::Event & dispatch it.
+//
+// This is really unfortunate, but exists for two reasons:
+//
+// 1. Some of the ui::Event constructors depend on global state that
+// is only used when building from a "native" event. For example:
+// last_click_event_ is used when constructing MouseEvent from
+// NativeEvent to determine click count.
+//
+// 2. Events contain a reference to a "native event", which some code
+// depends on. The ui::Event might get mutated during dispatch, but
+// the native event won't. Some code depends on the fact that the
+// "native" version of the event is unmodified.
+//
+// We are trying to fix both of these issues, but in the meantime we
+// define NativeEvent == ui::Event.
+//
+EVENTS_EXPORT void DispatchEventFromNativeUiEvent(
+ const base::NativeEvent& native_event,
+ base::Callback<void(ui::Event*)> callback);
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVENTS_OZONE_H_
diff --git a/ui/events/ozone/events_ozone_export.h b/ui/events/ozone/events_ozone_export.h
new file mode 100644
index 0000000..bda8814
--- /dev/null
+++ b/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/ui/events/platform/BUILD.gn b/ui/events/platform/BUILD.gn
new file mode 100644
index 0000000..4008a23
--- /dev/null
+++ b/ui/events/platform/BUILD.gn
@@ -0,0 +1,35 @@
+# 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 = [
+ # Allow this target to include events_export.h without depending on the
+ # events target (which would be circular).
+ "../events_export.h",
+ "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/ui/events/platform/events_platform.gyp b/ui/events/platform/events_platform.gyp
new file mode 100644
index 0000000..d1b3f6b
--- /dev/null
+++ b/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/ui/events/platform/platform_event_dispatcher.h b/ui/events/platform/platform_event_dispatcher.h
new file mode 100644
index 0000000..bda035d
--- /dev/null
+++ b/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/ui/events/platform/platform_event_observer.h b/ui/events/platform/platform_event_observer.h
new file mode 100644
index 0000000..37aec63
--- /dev/null
+++ b/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/ui/events/platform/platform_event_source.cc b/ui/events/platform/platform_event_source.cc
new file mode 100644
index 0000000..9607ab7
--- /dev/null
+++ b/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/ui/events/platform/platform_event_source.h b/ui/events/platform/platform_event_source.h
new file mode 100644
index 0000000..87b5531
--- /dev/null
+++ b/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/ui/events/platform/platform_event_source_stub.cc b/ui/events/platform/platform_event_source_stub.cc
new file mode 100644
index 0000000..57e2a61
--- /dev/null
+++ b/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/ui/events/platform/platform_event_source_unittest.cc b/ui/events/platform/platform_event_source_unittest.cc
new file mode 100644
index 0000000..cdbd0c4
--- /dev/null
+++ b/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/ui/events/platform/platform_event_types.h b/ui/events/platform/platform_event_types.h
new file mode 100644
index 0000000..dedb38f
--- /dev/null
+++ b/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/ui/events/platform/scoped_event_dispatcher.cc b/ui/events/platform/scoped_event_dispatcher.cc
new file mode 100644
index 0000000..b0941ab
--- /dev/null
+++ b/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/ui/events/platform/scoped_event_dispatcher.h b/ui/events/platform/scoped_event_dispatcher.h
new file mode 100644
index 0000000..a2f5294
--- /dev/null
+++ b/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/ui/events/platform/x11/BUILD.gn b/ui/events/platform/x11/BUILD.gn
new file mode 100644
index 0000000..3ed70b9
--- /dev/null
+++ b/ui/events/platform/x11/BUILD.gn
@@ -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.
+
+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" ]
+
+ public_deps = [
+ "//ui/events",
+ "//ui/events:events_base",
+ "//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/ui/events/platform/x11/x11_event_source.cc b/ui/events/platform/x11/x11_event_source.cc
new file mode 100644
index 0000000..b2f5dfa
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source.cc
@@ -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.
+
+#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/events/x/device_data_manager_x11.h"
+#include "ui/events/x/hotplug_event_handler_x11.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_);
+ DeviceDataManagerX11::CreateInstance();
+ hotplug_event_handler_.reset(
+ new HotplugEventHandlerX11(DeviceDataManager::GetInstance()));
+ InitializeXInput2(display_);
+ InitializeXkb(display_);
+
+ // Force the initial device query to have an update list of active devices.
+ hotplug_event_handler_->OnHotplugEvent();
+}
+
+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();
+ hotplug_event_handler_->OnHotplugEvent();
+ }
+
+ if (have_cookie)
+ XFreeEventData(xevent->xgeneric.display, &xevent->xcookie);
+ return action;
+}
+
+void X11EventSource::StopCurrentEventStream() {
+ continue_stream_ = false;
+}
+
+} // namespace ui
diff --git a/ui/events/platform/x11/x11_event_source.h b/ui/events/platform/x11/x11_event_source.h
new file mode 100644
index 0000000..598afa8
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source.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_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 {
+
+class HotplugEventHandlerX11;
+
+// 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_;
+
+ scoped_ptr<HotplugEventHandlerX11> hotplug_event_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(X11EventSource);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_
diff --git a/ui/events/platform/x11/x11_event_source_glib.cc b/ui/events/platform/x11/x11_event_source_glib.cc
new file mode 100644
index 0000000..9504422
--- /dev/null
+++ b/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/ui/events/platform/x11/x11_event_source_libevent.cc b/ui/events/platform/x11/x11_event_source_libevent.cc
new file mode 100644
index 0000000..d92e12a
--- /dev/null
+++ b/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/ui/events/platform/x11/x11_events_platform.gyp b/ui/events/platform/x11/x11_events_platform.gyp
new file mode 100644
index 0000000..0242d05
--- /dev/null
+++ b/ui/events/platform/x11/x11_events_platform.gyp
@@ -0,0 +1,45 @@
+# 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': [{
+ # GN version: //ui/events/platform/x11
+ '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.gyp:events_base',
+ '../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/ui/events/test/cocoa_test_event_utils.h b/ui/events/test/cocoa_test_event_utils.h
new file mode 100644
index 0000000..76c6a32
--- /dev/null
+++ b/ui/events/test/cocoa_test_event_utils.h
@@ -0,0 +1,53 @@
+// 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_TEST_COCOA_TEST_EVENT_UTILS_H_
+#define UI_EVENTS_TEST_COCOA_TEST_EVENT_UTILS_H_
+
+#include <utility>
+
+#import <objc/objc-class.h>
+
+#include "base/basictypes.h"
+
+namespace cocoa_test_event_utils {
+
+// Create synthetic mouse events for testing. Currently these are very
+// basic, flesh out as needed. Points are all in window coordinates;
+// where the window is not specified, coordinate system is undefined
+// (but will be repeated when the event is queried).
+NSEvent* MouseEventWithType(NSEventType type, NSUInteger modifiers);
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+ NSUInteger modifiers);
+NSEvent* LeftMouseDownAtPoint(NSPoint point);
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window);
+NSEvent* RightMouseDownAtPoint(NSPoint point);
+NSEvent* RightMouseDownAtPointInWindow(NSPoint point, NSWindow* window);
+
+// Return a mouse down and an up event with the given |clickCount| at
+// |view|'s midpoint.
+std::pair<NSEvent*, NSEvent*> MouseClickInView(NSView* view,
+ NSUInteger clickCount);
+
+// Returns a key event with the given character.
+NSEvent* KeyEventWithCharacter(unichar c);
+
+// Returns a key event with the given type and modifier flags.
+NSEvent* KeyEventWithType(NSEventType event_type, NSUInteger modifiers);
+
+// Returns a key event with the given key code, type, and modifier flags.
+NSEvent* KeyEventWithKeyCode(unsigned short key_code,
+ unichar c,
+ NSEventType event_type,
+ NSUInteger modifiers);
+
+// Returns a mouse enter/exit event with the given type.
+NSEvent* EnterExitEventWithType(NSEventType event_type);
+
+// Return an "other" event with the given type.
+NSEvent* OtherEventWithType(NSEventType event_type);
+
+} // namespace cocoa_test_event_utils
+
+#endif // UI_EVENTS_TEST_COCOA_TEST_EVENT_UTILS_H_
diff --git a/ui/events/test/cocoa_test_event_utils.mm b/ui/events/test/cocoa_test_event_utils.mm
new file mode 100644
index 0000000..2694b84
--- /dev/null
+++ b/ui/events/test/cocoa_test_event_utils.mm
@@ -0,0 +1,135 @@
+// 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 <Cocoa/Cocoa.h>
+
+#include "ui/events/test/cocoa_test_event_utils.h"
+
+namespace cocoa_test_event_utils {
+
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+ NSUInteger modifiers) {
+ if (type == NSOtherMouseUp) {
+ // To synthesize middle clicks we need to create a CGEvent with the
+ // "center" button flags so that our resulting NSEvent will have the
+ // appropriate buttonNumber field. NSEvent provides no way to create a
+ // mouse event with a buttonNumber directly.
+ CGPoint location = { point.x, point.y };
+ CGEventRef cg_event = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseUp,
+ location,
+ kCGMouseButtonCenter);
+ // Also specify the modifiers for the middle click case. This makes this
+ // test resilient to external modifiers being pressed.
+ CGEventSetFlags(cg_event, modifiers);
+ NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
+ CFRelease(cg_event);
+ return event;
+ }
+ return [NSEvent mouseEventWithType:type
+ location:point
+ modifierFlags:modifiers
+ timestamp:0
+ windowNumber:0
+ context:nil
+ eventNumber:0
+ clickCount:1
+ pressure:1.0];
+}
+
+NSEvent* MouseEventWithType(NSEventType type, NSUInteger modifiers) {
+ return MouseEventAtPoint(NSZeroPoint, type, modifiers);
+}
+
+static NSEvent* MouseEventAtPointInWindow(NSPoint point,
+ NSEventType type,
+ NSWindow* window,
+ NSUInteger clickCount) {
+ return [NSEvent mouseEventWithType:type
+ location:point
+ modifierFlags:0
+ timestamp:0
+ windowNumber:[window windowNumber]
+ context:nil
+ eventNumber:0
+ clickCount:clickCount
+ pressure:1.0];
+}
+
+NSEvent* RightMouseDownAtPointInWindow(NSPoint point, NSWindow* window) {
+ return MouseEventAtPointInWindow(point, NSRightMouseDown, window, 1);
+}
+
+NSEvent* RightMouseDownAtPoint(NSPoint point) {
+ return RightMouseDownAtPointInWindow(point, nil);
+}
+
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window) {
+ return MouseEventAtPointInWindow(point, NSLeftMouseDown, window, 1);
+}
+
+NSEvent* LeftMouseDownAtPoint(NSPoint point) {
+ return LeftMouseDownAtPointInWindow(point, nil);
+}
+
+std::pair<NSEvent*,NSEvent*> MouseClickInView(NSView* view,
+ NSUInteger clickCount) {
+ const NSRect bounds = [view convertRect:[view bounds] toView:nil];
+ const NSPoint mid_point = NSMakePoint(NSMidX(bounds), NSMidY(bounds));
+ NSEvent* down = MouseEventAtPointInWindow(mid_point, NSLeftMouseDown,
+ [view window], clickCount);
+ NSEvent* up = MouseEventAtPointInWindow(mid_point, NSLeftMouseUp,
+ [view window], clickCount);
+ return std::make_pair(down, up);
+}
+
+NSEvent* KeyEventWithCharacter(unichar c) {
+ return KeyEventWithKeyCode(0, c, NSKeyDown, 0);
+}
+
+NSEvent* KeyEventWithType(NSEventType event_type, NSUInteger modifiers) {
+ return KeyEventWithKeyCode(0x78, 'x', event_type, modifiers);
+}
+
+NSEvent* KeyEventWithKeyCode(unsigned short key_code,
+ unichar c,
+ NSEventType event_type,
+ NSUInteger modifiers) {
+ NSString* chars = [NSString stringWithCharacters:&c length:1];
+ return [NSEvent keyEventWithType:event_type
+ location:NSZeroPoint
+ modifierFlags:modifiers
+ timestamp:0
+ windowNumber:0
+ context:nil
+ characters:chars
+ charactersIgnoringModifiers:chars
+ isARepeat:NO
+ keyCode:key_code];
+}
+
+NSEvent* EnterExitEventWithType(NSEventType event_type) {
+ return [NSEvent enterExitEventWithType:event_type
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ eventNumber:0
+ trackingNumber:0
+ userData:NULL];
+}
+
+NSEvent* OtherEventWithType(NSEventType event_type) {
+ return [NSEvent otherEventWithType:event_type
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ subtype:0
+ data1:0
+ data2:0];
+}
+
+} // namespace cocoa_test_event_utils
diff --git a/ui/events/test/event_generator.cc b/ui/events/test/event_generator.cc
new file mode 100644
index 0000000..ad9d597
--- /dev/null
+++ b/ui/events/test/event_generator.cc
@@ -0,0 +1,632 @@
+// 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/test/event_generator.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "ui/events/event.h"
+#include "ui/events/event_source.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/test/events_test_utils.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/events/test/events_test_utils_x11.h"
+#endif
+
+#if defined(OS_WIN)
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#endif
+
+namespace ui {
+namespace test {
+namespace {
+
+void DummyCallback(EventType, const gfx::Vector2dF&) {
+}
+
+class TestKeyEvent : public ui::KeyEvent {
+ public:
+ TestKeyEvent(const base::NativeEvent& native_event, int flags)
+ : KeyEvent(native_event) {
+ set_flags(flags);
+ }
+};
+
+class TestTouchEvent : public ui::TouchEvent {
+ public:
+ TestTouchEvent(ui::EventType type,
+ const gfx::Point& root_location,
+ int touch_id,
+ int flags,
+ base::TimeDelta timestamp)
+ : TouchEvent(type, root_location, flags, touch_id, timestamp,
+ 1.0f, 1.0f, 0.0f, 0.0f) {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestTouchEvent);
+};
+
+const int kAllButtonMask = ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON;
+
+} // namespace
+
+EventGeneratorDelegate* EventGenerator::default_delegate = NULL;
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window)
+ : current_target_(NULL),
+ flags_(0),
+ grab_(false),
+ async_(false),
+ tick_clock_(new base::DefaultTickClock()) {
+ Init(root_window, NULL);
+}
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window,
+ const gfx::Point& point)
+ : current_location_(point),
+ current_target_(NULL),
+ flags_(0),
+ grab_(false),
+ async_(false),
+ tick_clock_(new base::DefaultTickClock()) {
+ Init(root_window, NULL);
+}
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window,
+ gfx::NativeWindow window)
+ : current_target_(NULL),
+ flags_(0),
+ grab_(false),
+ async_(false),
+ tick_clock_(new base::DefaultTickClock()) {
+ Init(root_window, window);
+}
+
+EventGenerator::EventGenerator(EventGeneratorDelegate* delegate)
+ : delegate_(delegate),
+ current_target_(NULL),
+ flags_(0),
+ grab_(false),
+ async_(false),
+ tick_clock_(new base::DefaultTickClock()) {
+ Init(NULL, NULL);
+}
+
+EventGenerator::~EventGenerator() {
+ for (std::list<ui::Event*>::iterator i = pending_events_.begin();
+ i != pending_events_.end(); ++i)
+ delete *i;
+ pending_events_.clear();
+ delegate()->SetContext(NULL, NULL, NULL);
+}
+
+void EventGenerator::PressLeftButton() {
+ PressButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ReleaseLeftButton() {
+ ReleaseButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ClickLeftButton() {
+ PressLeftButton();
+ ReleaseLeftButton();
+}
+
+void EventGenerator::DoubleClickLeftButton() {
+ flags_ |= ui::EF_IS_DOUBLE_CLICK;
+ PressLeftButton();
+ flags_ ^= ui::EF_IS_DOUBLE_CLICK;
+ ReleaseLeftButton();
+}
+
+void EventGenerator::PressRightButton() {
+ PressButton(ui::EF_RIGHT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ReleaseRightButton() {
+ ReleaseButton(ui::EF_RIGHT_MOUSE_BUTTON);
+}
+
+void EventGenerator::MoveMouseWheel(int delta_x, int delta_y) {
+ gfx::Point location = GetLocationInCurrentRoot();
+ ui::MouseEvent mouseev(ui::ET_MOUSEWHEEL, location, location, flags_, 0);
+ ui::MouseWheelEvent wheelev(mouseev, delta_x, delta_y);
+ Dispatch(&wheelev);
+}
+
+void EventGenerator::SendMouseExit() {
+ gfx::Point exit_location(current_location_);
+ delegate()->ConvertPointToTarget(current_target_, &exit_location);
+ ui::MouseEvent mouseev(ui::ET_MOUSE_EXITED, exit_location, exit_location,
+ flags_, 0);
+ Dispatch(&mouseev);
+}
+
+void EventGenerator::MoveMouseToInHost(const gfx::Point& point_in_host) {
+ const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ?
+ ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED;
+ ui::MouseEvent mouseev(event_type, point_in_host, point_in_host, flags_, 0);
+ Dispatch(&mouseev);
+
+ current_location_ = point_in_host;
+ delegate()->ConvertPointFromHost(current_target_, ¤t_location_);
+}
+
+void EventGenerator::MoveMouseTo(const gfx::Point& point_in_screen,
+ int count) {
+ DCHECK_GT(count, 0);
+ const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ?
+ ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED;
+
+ gfx::Vector2dF diff(point_in_screen - current_location_);
+ for (float i = 1; i <= count; i++) {
+ gfx::Vector2dF step(diff);
+ step.Scale(i / count);
+ gfx::Point move_point = current_location_ + gfx::ToRoundedVector2d(step);
+ if (!grab_)
+ UpdateCurrentDispatcher(move_point);
+ delegate()->ConvertPointToTarget(current_target_, &move_point);
+ ui::MouseEvent mouseev(event_type, move_point, move_point, flags_, 0);
+ Dispatch(&mouseev);
+ }
+ current_location_ = point_in_screen;
+}
+
+void EventGenerator::MoveMouseRelativeTo(const EventTarget* window,
+ const gfx::Point& point_in_parent) {
+ gfx::Point point(point_in_parent);
+ delegate()->ConvertPointFromTarget(window, &point);
+ MoveMouseTo(point);
+}
+
+void EventGenerator::DragMouseTo(const gfx::Point& point) {
+ PressLeftButton();
+ MoveMouseTo(point);
+ ReleaseLeftButton();
+}
+
+void EventGenerator::MoveMouseToCenterOf(EventTarget* window) {
+ MoveMouseTo(CenterOfWindow(window));
+}
+
+void EventGenerator::PressTouch() {
+ PressTouchId(0);
+}
+
+void EventGenerator::PressTouchId(int touch_id) {
+ TestTouchEvent touchev(
+ ui::ET_TOUCH_PRESSED, GetLocationInCurrentRoot(), touch_id, flags_,
+ Now());
+ Dispatch(&touchev);
+}
+
+void EventGenerator::MoveTouch(const gfx::Point& point) {
+ MoveTouchId(point, 0);
+}
+
+void EventGenerator::MoveTouchId(const gfx::Point& point, int touch_id) {
+ current_location_ = point;
+ TestTouchEvent touchev(
+ ui::ET_TOUCH_MOVED, GetLocationInCurrentRoot(), touch_id, flags_,
+ Now());
+ Dispatch(&touchev);
+
+ if (!grab_)
+ UpdateCurrentDispatcher(point);
+}
+
+void EventGenerator::ReleaseTouch() {
+ ReleaseTouchId(0);
+}
+
+void EventGenerator::ReleaseTouchId(int touch_id) {
+ TestTouchEvent touchev(
+ ui::ET_TOUCH_RELEASED, GetLocationInCurrentRoot(), touch_id, flags_,
+ Now());
+ Dispatch(&touchev);
+}
+
+void EventGenerator::PressMoveAndReleaseTouchTo(const gfx::Point& point) {
+ PressTouch();
+ MoveTouch(point);
+ ReleaseTouch();
+}
+
+void EventGenerator::PressMoveAndReleaseTouchToCenterOf(EventTarget* window) {
+ PressMoveAndReleaseTouchTo(CenterOfWindow(window));
+}
+
+void EventGenerator::GestureEdgeSwipe() {
+ ui::GestureEvent gesture(
+ 0, 0, 0, Now(), ui::GestureEventDetails(ui::ET_GESTURE_WIN8_EDGE_SWIPE));
+ Dispatch(&gesture);
+}
+
+void EventGenerator::GestureTapAt(const gfx::Point& location) {
+ const int kTouchId = 2;
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ location,
+ kTouchId,
+ Now());
+ Dispatch(&press);
+
+ ui::TouchEvent release(
+ ui::ET_TOUCH_RELEASED, location, kTouchId,
+ press.time_stamp() + base::TimeDelta::FromMilliseconds(50));
+ Dispatch(&release);
+}
+
+void EventGenerator::GestureTapDownAndUp(const gfx::Point& location) {
+ const int kTouchId = 3;
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ location,
+ kTouchId,
+ Now());
+ Dispatch(&press);
+
+ ui::TouchEvent release(
+ ui::ET_TOUCH_RELEASED, location, kTouchId,
+ press.time_stamp() + base::TimeDelta::FromMilliseconds(1000));
+ Dispatch(&release);
+}
+
+void EventGenerator::GestureScrollSequence(const gfx::Point& start,
+ const gfx::Point& end,
+ const base::TimeDelta& step_delay,
+ int steps) {
+ GestureScrollSequenceWithCallback(start, end, step_delay, steps,
+ base::Bind(&DummyCallback));
+}
+
+void EventGenerator::GestureScrollSequenceWithCallback(
+ const gfx::Point& start,
+ const gfx::Point& end,
+ const base::TimeDelta& step_delay,
+ int steps,
+ const ScrollStepCallback& callback) {
+ const int kTouchId = 5;
+ base::TimeDelta timestamp = Now();
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED, start, kTouchId, timestamp);
+ Dispatch(&press);
+
+ callback.Run(ui::ET_GESTURE_SCROLL_BEGIN, gfx::Vector2dF());
+
+ int dx = (end.x() - start.x()) / steps;
+ int dy = (end.y() - start.y()) / steps;
+ gfx::Point location = start;
+ for (int i = 0; i < steps; ++i) {
+ location.Offset(dx, dy);
+ timestamp += step_delay;
+ ui::TouchEvent move(ui::ET_TOUCH_MOVED, location, kTouchId, timestamp);
+ Dispatch(&move);
+ callback.Run(ui::ET_GESTURE_SCROLL_UPDATE, gfx::Vector2dF(dx, dy));
+ }
+
+ ui::TouchEvent release(ui::ET_TOUCH_RELEASED, end, kTouchId, timestamp);
+ Dispatch(&release);
+
+ callback.Run(ui::ET_GESTURE_SCROLL_END, gfx::Vector2dF());
+}
+
+void EventGenerator::GestureMultiFingerScroll(int count,
+ const gfx::Point start[],
+ int event_separation_time_ms,
+ int steps,
+ int move_x,
+ int move_y) {
+ const int kMaxTouchPoints = 10;
+ int delays[kMaxTouchPoints] = { 0 };
+ GestureMultiFingerScrollWithDelays(
+ count, start, delays, event_separation_time_ms, steps, move_x, move_y);
+}
+
+void EventGenerator::GestureMultiFingerScrollWithDelays(
+ int count,
+ const gfx::Point start[],
+ const int delay_adding_finger_ms[],
+ int event_separation_time_ms,
+ int steps,
+ int move_x,
+ int move_y) {
+ const int kMaxTouchPoints = 10;
+ gfx::Point points[kMaxTouchPoints];
+ CHECK_LE(count, kMaxTouchPoints);
+ CHECK_GT(steps, 0);
+
+ int delta_x = move_x / steps;
+ int delta_y = move_y / steps;
+
+ for (int i = 0; i < count; ++i) {
+ points[i] = start[i];
+ }
+
+ base::TimeDelta press_time_first = Now();
+ base::TimeDelta press_time[kMaxTouchPoints];
+ bool pressed[kMaxTouchPoints];
+ for (int i = 0; i < count; ++i) {
+ pressed[i] = false;
+ press_time[i] = press_time_first +
+ base::TimeDelta::FromMilliseconds(delay_adding_finger_ms[i]);
+ }
+
+ int last_id = 0;
+ for (int step = 0; step < steps; ++step) {
+ base::TimeDelta move_time = press_time_first +
+ base::TimeDelta::FromMilliseconds(event_separation_time_ms * step);
+
+ while (last_id < count &&
+ !pressed[last_id] &&
+ move_time >= press_time[last_id]) {
+ ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+ points[last_id],
+ last_id,
+ press_time[last_id]);
+ Dispatch(&press);
+ pressed[last_id] = true;
+ last_id++;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ points[i].Offset(delta_x, delta_y);
+ if (i >= last_id)
+ continue;
+ ui::TouchEvent move(ui::ET_TOUCH_MOVED, points[i], i, move_time);
+ Dispatch(&move);
+ }
+ }
+
+ base::TimeDelta release_time = press_time_first +
+ base::TimeDelta::FromMilliseconds(event_separation_time_ms * steps);
+ for (int i = 0; i < last_id; ++i) {
+ ui::TouchEvent release(
+ ui::ET_TOUCH_RELEASED, points[i], i, release_time);
+ Dispatch(&release);
+ }
+}
+
+void EventGenerator::ScrollSequence(const gfx::Point& start,
+ const base::TimeDelta& step_delay,
+ float x_offset,
+ float y_offset,
+ int steps,
+ int num_fingers) {
+ base::TimeDelta timestamp = Now();
+ ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
+ start,
+ timestamp,
+ 0,
+ 0, 0,
+ 0, 0,
+ num_fingers);
+ Dispatch(&fling_cancel);
+
+ float dx = x_offset / steps;
+ float dy = y_offset / steps;
+ for (int i = 0; i < steps; ++i) {
+ timestamp += step_delay;
+ ui::ScrollEvent move(ui::ET_SCROLL,
+ start,
+ timestamp,
+ 0,
+ dx, dy,
+ dx, dy,
+ num_fingers);
+ Dispatch(&move);
+ }
+
+ ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START,
+ start,
+ timestamp,
+ 0,
+ x_offset, y_offset,
+ x_offset, y_offset,
+ num_fingers);
+ Dispatch(&fling_start);
+}
+
+void EventGenerator::ScrollSequence(const gfx::Point& start,
+ const base::TimeDelta& step_delay,
+ const std::vector<gfx::Point>& offsets,
+ int num_fingers) {
+ size_t steps = offsets.size();
+ base::TimeDelta timestamp = Now();
+ ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
+ start,
+ timestamp,
+ 0,
+ 0, 0,
+ 0, 0,
+ num_fingers);
+ Dispatch(&fling_cancel);
+
+ for (size_t i = 0; i < steps; ++i) {
+ timestamp += step_delay;
+ ui::ScrollEvent scroll(ui::ET_SCROLL,
+ start,
+ timestamp,
+ 0,
+ offsets[i].x(), offsets[i].y(),
+ offsets[i].x(), offsets[i].y(),
+ num_fingers);
+ Dispatch(&scroll);
+ }
+
+ ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START,
+ start,
+ timestamp,
+ 0,
+ offsets[steps - 1].x(), offsets[steps - 1].y(),
+ offsets[steps - 1].x(), offsets[steps - 1].y(),
+ num_fingers);
+ Dispatch(&fling_start);
+}
+
+void EventGenerator::PressKey(ui::KeyboardCode key_code, int flags) {
+ DispatchKeyEvent(true, key_code, flags);
+}
+
+void EventGenerator::ReleaseKey(ui::KeyboardCode key_code, int flags) {
+ DispatchKeyEvent(false, key_code, flags);
+}
+
+void EventGenerator::Dispatch(ui::Event* event) {
+ DoDispatchEvent(event, async_);
+}
+
+void EventGenerator::SetTickClock(scoped_ptr<base::TickClock> tick_clock) {
+ tick_clock_ = tick_clock.Pass();
+}
+
+base::TimeDelta EventGenerator::Now() {
+ // This is the same as what EventTimeForNow() does, but here we do it
+ // with a tick clock that can be replaced with a simulated clock for tests.
+ return base::TimeDelta::FromInternalValue(
+ tick_clock_->NowTicks().ToInternalValue());
+}
+
+void EventGenerator::Init(gfx::NativeWindow root_window,
+ gfx::NativeWindow window_context) {
+ delegate()->SetContext(this, root_window, window_context);
+ if (window_context)
+ current_location_ = delegate()->CenterOfWindow(window_context);
+ current_target_ = delegate()->GetTargetAt(current_location_);
+}
+
+void EventGenerator::DispatchKeyEvent(bool is_press,
+ ui::KeyboardCode key_code,
+ int flags) {
+#if defined(OS_WIN)
+ UINT key_press = WM_KEYDOWN;
+ uint16 character = ui::GetCharacterFromKeyCode(key_code, flags);
+ if (is_press && character) {
+ MSG native_event = { NULL, WM_KEYDOWN, key_code, 0 };
+ TestKeyEvent keyev(native_event, flags);
+ Dispatch(&keyev);
+ // On Windows, WM_KEYDOWN event is followed by WM_CHAR with a character
+ // if the key event cooresponds to a real character.
+ key_press = WM_CHAR;
+ key_code = static_cast<ui::KeyboardCode>(character);
+ }
+ MSG native_event =
+ { NULL, (is_press ? key_press : WM_KEYUP), key_code, 0 };
+ TestKeyEvent keyev(native_event, flags);
+#elif defined(USE_X11)
+ ui::ScopedXI2Event xevent;
+ xevent.InitKeyEvent(is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+ key_code,
+ flags);
+ ui::KeyEvent keyev(xevent);
+#else
+ ui::EventType type = is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED;
+ ui::KeyEvent keyev(type, key_code, flags);
+#endif // OS_WIN
+ Dispatch(&keyev);
+}
+
+void EventGenerator::UpdateCurrentDispatcher(const gfx::Point& point) {
+ current_target_ = delegate()->GetTargetAt(point);
+}
+
+void EventGenerator::PressButton(int flag) {
+ if (!(flags_ & flag)) {
+ flags_ |= flag;
+ grab_ = (flags_ & kAllButtonMask) != 0;
+ gfx::Point location = GetLocationInCurrentRoot();
+ ui::MouseEvent mouseev(ui::ET_MOUSE_PRESSED, location, location, flags_,
+ flag);
+ Dispatch(&mouseev);
+ }
+}
+
+void EventGenerator::ReleaseButton(int flag) {
+ if (flags_ & flag) {
+ gfx::Point location = GetLocationInCurrentRoot();
+ ui::MouseEvent mouseev(ui::ET_MOUSE_RELEASED, location,
+ location, flags_, flag);
+ Dispatch(&mouseev);
+ flags_ ^= flag;
+ }
+ grab_ = (flags_ & kAllButtonMask) != 0;
+}
+
+gfx::Point EventGenerator::GetLocationInCurrentRoot() const {
+ gfx::Point p(current_location_);
+ delegate()->ConvertPointToTarget(current_target_, &p);
+ return p;
+}
+
+gfx::Point EventGenerator::CenterOfWindow(const EventTarget* window) const {
+ return delegate()->CenterOfTarget(window);
+}
+
+void EventGenerator::DoDispatchEvent(ui::Event* event, bool async) {
+ if (async) {
+ ui::Event* pending_event;
+ if (event->IsKeyEvent()) {
+ pending_event = new ui::KeyEvent(*static_cast<ui::KeyEvent*>(event));
+ } else if (event->IsMouseEvent()) {
+ pending_event = new ui::MouseEvent(*static_cast<ui::MouseEvent*>(event));
+ } else if (event->IsTouchEvent()) {
+ pending_event = new ui::TouchEvent(*static_cast<ui::TouchEvent*>(event));
+ } else if (event->IsScrollEvent()) {
+ pending_event =
+ new ui::ScrollEvent(*static_cast<ui::ScrollEvent*>(event));
+ } else {
+ NOTREACHED() << "Invalid event type";
+ return;
+ }
+ if (pending_events_.empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&EventGenerator::DispatchNextPendingEvent,
+ base::Unretained(this)));
+ }
+ pending_events_.push_back(pending_event);
+ } else {
+ ui::EventSource* event_source = delegate()->GetEventSource(current_target_);
+ ui::EventSourceTestApi event_source_test(event_source);
+ ui::EventDispatchDetails details =
+ event_source_test.SendEventToProcessor(event);
+ CHECK(!details.dispatcher_destroyed);
+ }
+}
+
+void EventGenerator::DispatchNextPendingEvent() {
+ DCHECK(!pending_events_.empty());
+ ui::Event* event = pending_events_.front();
+ DoDispatchEvent(event, false);
+ pending_events_.pop_front();
+ delete event;
+ if (!pending_events_.empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&EventGenerator::DispatchNextPendingEvent,
+ base::Unretained(this)));
+ }
+}
+
+const EventGeneratorDelegate* EventGenerator::delegate() const {
+ if (delegate_)
+ return delegate_.get();
+
+ DCHECK(default_delegate);
+ return default_delegate;
+}
+
+EventGeneratorDelegate* EventGenerator::delegate() {
+ return const_cast<EventGeneratorDelegate*>(
+ const_cast<const EventGenerator*>(this)->delegate());
+}
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/event_generator.h b/ui/events/test/event_generator.h
new file mode 100644
index 0000000..bc243b9
--- /dev/null
+++ b/ui/events/test/event_generator.h
@@ -0,0 +1,385 @@
+// 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_TEST_EVENT_GENERATOR_H_
+#define UI_EVENTS_TEST_EVENT_GENERATOR_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+
+namespace base {
+class TickClock;
+}
+
+namespace ui {
+class Event;
+class EventProcessor;
+class EventSource;
+class EventTarget;
+class KeyEvent;
+class MouseEvent;
+class ScrollEvent;
+class TouchEvent;
+
+namespace test {
+
+typedef base::Callback<void(EventType, const gfx::Vector2dF&)>
+ ScrollStepCallback;
+
+class EventGenerator;
+
+// A delegate interface for EventGenerator to abstract platform-specific event
+// targeting and coordinate conversion.
+class EventGeneratorDelegate {
+ public:
+ virtual ~EventGeneratorDelegate() {}
+
+ // Set the context of the delegate, whilst it is being used by an active
+ // EventGenerator.
+ virtual void SetContext(EventGenerator* owner,
+ gfx::NativeWindow root_window,
+ gfx::NativeWindow window) {}
+
+ // The ui::EventTarget at the given |location|.
+ virtual EventTarget* GetTargetAt(const gfx::Point& location) = 0;
+
+ // The ui::EventSource for the given |target|.
+ virtual EventSource* GetEventSource(EventTarget* target) = 0;
+
+ // Helper functions to determine the center point of |target| or |window|.
+ virtual gfx::Point CenterOfTarget(const EventTarget* target) const = 0;
+ virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const = 0;
+
+ // Convert a point between API's coordinates and |target|'s coordinates.
+ virtual void ConvertPointFromTarget(const EventTarget* target,
+ gfx::Point* point) const = 0;
+ virtual void ConvertPointToTarget(const EventTarget* target,
+ gfx::Point* point) const = 0;
+
+ // Convert a point from the coordinate system in the host that contains
+ // |hosted_target| into the root window's coordinate system.
+ virtual void ConvertPointFromHost(const EventTarget* hosted_target,
+ gfx::Point* point) const = 0;
+};
+
+// ui::test::EventGenerator is a tool that generates and dispatches events.
+// Unlike |ui_controls| package in ui/base/test, this does not use platform
+// native message loops. Instead, it sends events to the event dispatcher
+// synchronously.
+//
+// This class is not suited for the following cases:
+//
+// 1) If your test depends on native events (ui::Event::native_event()).
+// This return is empty/NULL event with EventGenerator.
+// 2) If your test involves nested message loop, such as
+// menu or drag & drop. Because this class directly
+// post an event to WindowEventDispatcher, this event will not be
+// handled in the nested message loop.
+// 3) Similarly, |base::MessagePumpObserver| will not be invoked.
+// 4) Any other code that requires native message loops, such as
+// tests for WindowTreeHostWin/WindowTreeHostX11.
+//
+// If one of these applies to your test, please use |ui_controls|
+// package instead.
+//
+// Note: The coordinates of the points in API is determined by the
+// EventGeneratorDelegate.
+class EventGenerator {
+ public:
+ // Creates an EventGenerator with the mouse/touch location (0,0),
+ // which uses the |root_window|'s coordinates and the default delegate for
+ // this platform.
+ explicit EventGenerator(gfx::NativeWindow root_window);
+
+ // Create an EventGenerator with EventGeneratorDelegate,
+ // which uses the coordinates conversions and targeting provided by
+ // |delegate|.
+ explicit EventGenerator(EventGeneratorDelegate* delegate);
+
+ // Creates an EventGenerator with the mouse/touch location
+ // at |initial_location|, which uses the |root_window|'s coordinates.
+ EventGenerator(gfx::NativeWindow root_window,
+ const gfx::Point& initial_location);
+
+ // Creates an EventGenerator with the mouse/touch location centered over
+ // |window|. This is currently the only constructor that works on Mac, since
+ // a specific window is required (and there is no root window).
+ EventGenerator(gfx::NativeWindow root_window, gfx::NativeWindow window);
+
+ virtual ~EventGenerator();
+
+ // Explicitly sets the location used by mouse/touch events. This is set by the
+ // various methods that take a location but can be manipulated directly,
+ // typically for touch.
+ void set_current_location(const gfx::Point& location) {
+ current_location_ = location;
+ }
+ const gfx::Point& current_location() const { return current_location_; }
+
+ void set_async(bool async) { async_ = async; }
+ bool async() const { return async_; }
+
+ // Resets the event flags bitmask.
+ void set_flags(int flags) { flags_ = flags; }
+ int flags() const { return flags_; }
+
+ // Generates a left button press event.
+ void PressLeftButton();
+
+ // Generates a left button release event.
+ void ReleaseLeftButton();
+
+ // Generates events to click (press, release) left button.
+ void ClickLeftButton();
+
+ // Generates a double click event using the left button.
+ void DoubleClickLeftButton();
+
+ // Generates a right button press event.
+ void PressRightButton();
+
+ // Generates a right button release event.
+ void ReleaseRightButton();
+
+ // Moves the mouse wheel by |delta_x|, |delta_y|.
+ void MoveMouseWheel(int delta_x, int delta_y);
+
+ // Generates a mouse exit.
+ void SendMouseExit();
+
+ // Generates events to move mouse to be the given |point| in the
+ // |current_root_window_|'s host window coordinates.
+ void MoveMouseToInHost(const gfx::Point& point_in_host);
+ void MoveMouseToInHost(int x, int y) {
+ MoveMouseToInHost(gfx::Point(x, y));
+ }
+
+ // Generates events to move mouse to be the given |point| in screen
+ // coordinates.
+ void MoveMouseTo(const gfx::Point& point_in_screen, int count);
+ void MoveMouseTo(const gfx::Point& point_in_screen) {
+ MoveMouseTo(point_in_screen, 1);
+ }
+ void MoveMouseTo(int x, int y) {
+ MoveMouseTo(gfx::Point(x, y));
+ }
+
+ // Generates events to move mouse to be the given |point| in |window|'s
+ // coordinates.
+ void MoveMouseRelativeTo(const EventTarget* window, const gfx::Point& point);
+ void MoveMouseRelativeTo(const EventTarget* window, int x, int y) {
+ MoveMouseRelativeTo(window, gfx::Point(x, y));
+ }
+
+ void MoveMouseBy(int x, int y) {
+ MoveMouseTo(current_location_ + gfx::Vector2d(x, y));
+ }
+
+ // Generates events to drag mouse to given |point|.
+ void DragMouseTo(const gfx::Point& point);
+
+ void DragMouseTo(int x, int y) {
+ DragMouseTo(gfx::Point(x, y));
+ }
+
+ void DragMouseBy(int dx, int dy) {
+ DragMouseTo(current_location_ + gfx::Vector2d(dx, dy));
+ }
+
+ // Generates events to move the mouse to the center of the window.
+ void MoveMouseToCenterOf(EventTarget* window);
+
+ // Generates a touch press event.
+ void PressTouch();
+
+ // Generates a touch press event with |touch_id|.
+ void PressTouchId(int touch_id);
+
+ // Generates a ET_TOUCH_MOVED event to |point|.
+ void MoveTouch(const gfx::Point& point);
+
+ // Generates a ET_TOUCH_MOVED event to |point| with |touch_id|.
+ void MoveTouchId(const gfx::Point& point, int touch_id);
+
+ // Generates a touch release event.
+ void ReleaseTouch();
+
+ // Generates a touch release event with |touch_id|.
+ void ReleaseTouchId(int touch_id);
+
+ // Generates press, move and release event to move touch
+ // to be the given |point|.
+ void PressMoveAndReleaseTouchTo(const gfx::Point& point);
+
+ void PressMoveAndReleaseTouchTo(int x, int y) {
+ PressMoveAndReleaseTouchTo(gfx::Point(x, y));
+ }
+
+ void PressMoveAndReleaseTouchBy(int x, int y) {
+ PressMoveAndReleaseTouchTo(current_location_ + gfx::Vector2d(x, y));
+ }
+
+ // Generates press, move and release events to move touch
+ // to the center of the window.
+ void PressMoveAndReleaseTouchToCenterOf(EventTarget* window);
+
+ // Generates and dispatches a Win8 edge-swipe event (swipe up from bottom or
+ // swipe down from top). Note that it is not possible to distinguish between
+ // the two edges with this event.
+ void GestureEdgeSwipe();
+
+ // Generates and dispatches touch-events required to generate a TAP gesture.
+ // Note that this can generate a number of other gesture events at the same
+ // time (e.g. GESTURE_BEGIN, TAP_DOWN, END).
+ void GestureTapAt(const gfx::Point& point);
+
+ // Generates press and release touch-events to generate a TAP_DOWN event, but
+ // without generating any scroll or tap events. This can also generate a few
+ // other gesture events (e.g. GESTURE_BEGIN, END).
+ void GestureTapDownAndUp(const gfx::Point& point);
+
+ // Generates press, move, release touch-events to generate a sequence of
+ // scroll events. |duration| and |steps| affect the velocity of the scroll,
+ // and depending on these values, this may also generate FLING scroll
+ // gestures. If velocity/fling is irrelevant for the test, then any non-zero
+ // values for these should be sufficient.
+ void GestureScrollSequence(const gfx::Point& start,
+ const gfx::Point& end,
+ const base::TimeDelta& duration,
+ int steps);
+
+ // The same as GestureScrollSequence(), with the exception that |callback| is
+ // called at each step of the scroll sequence. |callback| is called at the
+ // start of the sequence with ET_GESTURE_SCROLL_BEGIN, followed by one or more
+ // ET_GESTURE_SCROLL_UPDATE and ends with an ET_GESTURE_SCROLL_END.
+ void GestureScrollSequenceWithCallback(const gfx::Point& start,
+ const gfx::Point& end,
+ const base::TimeDelta& duration,
+ int steps,
+ const ScrollStepCallback& callback);
+
+ // Generates press, move, release touch-events to generate a sequence of
+ // multi-finger scroll events. |count| specifies the number of touch-points
+ // that should generate the scroll events. |start| are the starting positions
+ // of all the touch points. |steps| and |event_separation_time_ms| are
+ // relevant when testing velocity/fling/swipe, otherwise these can be any
+ // non-zero value. |delta_x| and |delta_y| are the amount that each finger
+ // should be moved. Internally calls GestureMultiFingerScrollWithDelays
+ // with zeros as |delay_adding_finger_ms| forcing all touch down events to be
+ // immediate.
+ void GestureMultiFingerScroll(int count,
+ const gfx::Point start[],
+ int event_separation_time_ms,
+ int steps,
+ int move_x,
+ int move_y);
+
+ // Generates press, move, release touch-events to generate a sequence of
+ // multi-finger scroll events. |count| specifies the number of touch-points
+ // that should generate the scroll events. |start| are the starting positions
+ // of all the touch points. |delay_adding_finger_ms| are delays in ms from the
+ // starting time till touching down of each finger. |delay_adding_finger_ms|
+ // is useful when testing complex gestures that start with 1 or 2 fingers and
+ // add fingers with a delay. |steps| and |event_separation_time_ms| are
+ // relevant when testing velocity/fling/swipe, otherwise these can be any
+ // non-zero value. |delta_x| and |delta_y| are the amount that each finger
+ // should be moved.
+ void GestureMultiFingerScrollWithDelays(int count,
+ const gfx::Point start[],
+ const int delay_adding_finger_ms[],
+ int event_separation_time_ms,
+ int steps,
+ int move_x,
+ int move_y);
+
+ // Generates scroll sequences of a FlingCancel, Scrolls, FlingStart, with
+ // constant deltas to |x_offset| and |y_offset| in |steps|.
+ void ScrollSequence(const gfx::Point& start,
+ const base::TimeDelta& step_delay,
+ float x_offset,
+ float y_offset,
+ int steps,
+ int num_fingers);
+
+ // Generates scroll sequences of a FlingCancel, Scrolls, FlingStart, sending
+ // scrolls of each of the values in |offsets|.
+ void ScrollSequence(const gfx::Point& start,
+ const base::TimeDelta& step_delay,
+ const std::vector<gfx::Point>& offsets,
+ int num_fingers);
+
+ // Generates a key press event. On platforms except Windows and X11, a key
+ // event without native_event() is generated. Note that ui::EF_ flags should
+ // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
+ // TODO(yusukes): Support native_event() on all platforms.
+ void PressKey(KeyboardCode key_code, int flags);
+
+ // Generates a key release event. On platforms except Windows and X11, a key
+ // event without native_event() is generated. Note that ui::EF_ flags should
+ // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
+ // TODO(yusukes): Support native_event() on all platforms.
+ void ReleaseKey(KeyboardCode key_code, int flags);
+
+ // Dispatch the event to the WindowEventDispatcher.
+ void Dispatch(Event* event);
+
+ void set_current_target(EventTarget* target) {
+ current_target_ = target;
+ }
+
+ // Specify an alternative tick clock to be used for simulating time in tests.
+ void SetTickClock(scoped_ptr<base::TickClock> tick_clock);
+
+ // Get the current time from the tick clock.
+ base::TimeDelta Now();
+
+ // Default delegate set by a platform-specific GeneratorDelegate singleton.
+ static EventGeneratorDelegate* default_delegate;
+
+ private:
+ // Set up the test context using the delegate.
+ void Init(gfx::NativeWindow root_window, gfx::NativeWindow window_context);
+
+ // Dispatch a key event to the WindowEventDispatcher.
+ void DispatchKeyEvent(bool is_press, KeyboardCode key_code, int flags);
+
+ void UpdateCurrentDispatcher(const gfx::Point& point);
+ void PressButton(int flag);
+ void ReleaseButton(int flag);
+
+ gfx::Point GetLocationInCurrentRoot() const;
+ gfx::Point CenterOfWindow(const EventTarget* window) const;
+
+ void DispatchNextPendingEvent();
+ void DoDispatchEvent(Event* event, bool async);
+
+ const EventGeneratorDelegate* delegate() const;
+ EventGeneratorDelegate* delegate();
+
+ scoped_ptr<EventGeneratorDelegate> delegate_;
+ gfx::Point current_location_;
+ EventTarget* current_target_;
+ int flags_;
+ bool grab_;
+ std::list<Event*> pending_events_;
+ // Set to true to cause events to be posted asynchronously.
+ bool async_;
+ scoped_ptr<base::TickClock> tick_clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventGenerator);
+};
+
+} // namespace test
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_EVENT_GENERATOR_H_
diff --git a/ui/events/test/events_test_utils.cc b/ui/events/test/events_test_utils.cc
new file mode 100644
index 0000000..8c3f1b6
--- /dev/null
+++ b/ui/events/test/events_test_utils.cc
@@ -0,0 +1,36 @@
+// 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/test/events_test_utils.h"
+
+#include "ui/events/event_source.h"
+
+namespace ui {
+
+EventTestApi::EventTestApi(Event* event) : event_(event) {}
+EventTestApi::~EventTestApi() {}
+
+LocatedEventTestApi::LocatedEventTestApi(LocatedEvent* event)
+ : EventTestApi(event),
+ located_event_(event) {}
+LocatedEventTestApi::~LocatedEventTestApi() {}
+
+KeyEventTestApi::KeyEventTestApi(KeyEvent* event)
+ : EventTestApi(event),
+ key_event_(event) {}
+KeyEventTestApi::~KeyEventTestApi() {}
+
+EventTargetTestApi::EventTargetTestApi(EventTarget* target)
+ : target_(target) {}
+
+EventSourceTestApi::EventSourceTestApi(EventSource* event_source)
+ : event_source_(event_source) {
+ DCHECK(event_source);
+}
+
+EventDispatchDetails EventSourceTestApi::SendEventToProcessor(Event* event) {
+ return event_source_->SendEventToProcessor(event);
+}
+
+} // namespace ui
diff --git a/ui/events/test/events_test_utils.h b/ui/events/test/events_test_utils.h
new file mode 100644
index 0000000..3ed6fa3
--- /dev/null
+++ b/ui/events/test/events_test_utils.h
@@ -0,0 +1,103 @@
+// 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_TEST_EVENTS_TEST_UTILS_H_
+#define UI_EVENTS_TEST_EVENTS_TEST_UTILS_H_
+
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_target.h"
+
+namespace ui {
+
+class EventSource;
+
+class EventTestApi {
+ public:
+ explicit EventTestApi(Event* event);
+ virtual ~EventTestApi();
+
+ void set_time_stamp(base::TimeDelta time_stamp) {
+ event_->time_stamp_ = time_stamp;
+ }
+
+ void set_source_device_id(int source_device_id) {
+ event_->source_device_id_ = source_device_id;
+ }
+
+ private:
+ EventTestApi();
+
+ Event* event_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventTestApi);
+};
+
+class LocatedEventTestApi : public EventTestApi {
+ public:
+ explicit LocatedEventTestApi(LocatedEvent* located_event);
+ virtual ~LocatedEventTestApi();
+
+ void set_location(const gfx::Point& location) {
+ located_event_->location_ = location;
+ }
+
+ private:
+ LocatedEventTestApi();
+
+ LocatedEvent* located_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocatedEventTestApi);
+};
+
+class KeyEventTestApi : public EventTestApi {
+ public:
+ explicit KeyEventTestApi(KeyEvent* key_event);
+ virtual ~KeyEventTestApi();
+
+ void set_is_char(bool is_char) {
+ key_event_->set_is_char(is_char);
+ }
+
+ private:
+ KeyEventTestApi();
+
+ KeyEvent* key_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyEventTestApi);
+};
+
+class EventTargetTestApi {
+ public:
+ explicit EventTargetTestApi(EventTarget* target);
+
+ const EventHandlerList& pre_target_handlers() {
+ return target_->pre_target_list_;
+ }
+
+ private:
+ EventTargetTestApi();
+
+ EventTarget* target_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventTargetTestApi);
+};
+
+class EventSourceTestApi {
+ public:
+ explicit EventSourceTestApi(EventSource* event_source);
+
+ EventDispatchDetails SendEventToProcessor(Event* event) WARN_UNUSED_RESULT;
+
+ private:
+ EventSourceTestApi();
+
+ EventSource* event_source_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventSourceTestApi);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_EVENTS_TEST_UTILS_H_
diff --git a/ui/events/test/events_test_utils_x11.cc b/ui/events/test/events_test_utils_x11.cc
new file mode 100644
index 0000000..28558a2
--- /dev/null
+++ b/ui/events/test/events_test_utils_x11.cc
@@ -0,0 +1,304 @@
+// 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/test/events_test_utils_x11.h"
+
+#include <X11/extensions/XI2.h>
+#include <X11/keysym.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#include "base/logging.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/events/x/touch_factory_x11.h"
+
+namespace {
+
+// Converts ui::EventType to state for X*Events.
+unsigned int XEventState(int flags) {
+ return
+ ((flags & ui::EF_SHIFT_DOWN) ? ShiftMask : 0) |
+ ((flags & ui::EF_CONTROL_DOWN) ? ControlMask : 0) |
+ ((flags & ui::EF_ALT_DOWN) ? Mod1Mask : 0) |
+ ((flags & ui::EF_CAPS_LOCK_DOWN) ? LockMask : 0) |
+ ((flags & ui::EF_ALTGR_DOWN) ? Mod5Mask : 0) |
+ ((flags & ui::EF_COMMAND_DOWN) ? Mod4Mask : 0) |
+ ((flags & ui::EF_MOD3_DOWN) ? Mod3Mask : 0) |
+ ((flags & ui::EF_NUMPAD_KEY) ? Mod2Mask : 0) |
+ ((flags & ui::EF_LEFT_MOUSE_BUTTON) ? Button1Mask: 0) |
+ ((flags & ui::EF_MIDDLE_MOUSE_BUTTON) ? Button2Mask: 0) |
+ ((flags & ui::EF_RIGHT_MOUSE_BUTTON) ? Button3Mask: 0);
+}
+
+// Converts EventType to XKeyEvent type.
+int XKeyEventType(ui::EventType type) {
+ switch (type) {
+ case ui::ET_KEY_PRESSED:
+ return KeyPress;
+ case ui::ET_KEY_RELEASED:
+ return KeyRelease;
+ default:
+ return 0;
+ }
+}
+
+// Converts EventType to XI2 event type.
+int XIKeyEventType(ui::EventType type) {
+ switch (type) {
+ case ui::ET_KEY_PRESSED:
+ return XI_KeyPress;
+ case ui::ET_KEY_RELEASED:
+ return XI_KeyRelease;
+ default:
+ return 0;
+ }
+}
+
+int XIButtonEventType(ui::EventType type) {
+ switch (type) {
+ case ui::ET_MOUSEWHEEL:
+ case ui::ET_MOUSE_PRESSED:
+ // The button release X events for mouse wheels are dropped by Aura.
+ return XI_ButtonPress;
+ case ui::ET_MOUSE_RELEASED:
+ return XI_ButtonRelease;
+ default:
+ NOTREACHED();
+ return 0;
+ }
+}
+
+// Converts Aura event type and flag to X button event.
+unsigned int XButtonEventButton(ui::EventType type,
+ int flags) {
+ // Aura events don't keep track of mouse wheel button, so just return
+ // the first mouse wheel button.
+ if (type == ui::ET_MOUSEWHEEL)
+ return Button4;
+
+ if (flags & ui::EF_LEFT_MOUSE_BUTTON)
+ return Button1;
+ if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
+ return Button2;
+ if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
+ return Button3;
+
+ return 0;
+}
+
+void InitValuatorsForXIDeviceEvent(XIDeviceEvent* xiev) {
+ int valuator_count = ui::DeviceDataManagerX11::DT_LAST_ENTRY;
+ xiev->valuators.mask_len = (valuator_count / 8) + 1;
+ xiev->valuators.mask = new unsigned char[xiev->valuators.mask_len];
+ memset(xiev->valuators.mask, 0, xiev->valuators.mask_len);
+ xiev->valuators.values = new double[valuator_count];
+}
+
+XEvent* CreateXInput2Event(int deviceid,
+ int evtype,
+ int tracking_id,
+ const gfx::Point& location) {
+ XEvent* event = new XEvent;
+ memset(event, 0, sizeof(*event));
+ event->type = GenericEvent;
+ event->xcookie.data = new XIDeviceEvent;
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(event->xcookie.data);
+ memset(xiev, 0, sizeof(XIDeviceEvent));
+ xiev->deviceid = deviceid;
+ xiev->sourceid = deviceid;
+ xiev->evtype = evtype;
+ xiev->detail = tracking_id;
+ xiev->event_x = location.x();
+ xiev->event_y = location.y();
+ xiev->event = DefaultRootWindow(gfx::GetXDisplay());
+ if (evtype == XI_ButtonPress || evtype == XI_ButtonRelease) {
+ xiev->buttons.mask_len = 8;
+ xiev->buttons.mask = new unsigned char[xiev->buttons.mask_len];
+ memset(xiev->buttons.mask, 0, xiev->buttons.mask_len);
+ }
+ return event;
+}
+
+} // namespace
+
+namespace ui {
+
+// XInput2 events contain additional data that need to be explicitly freed (see
+// |CreateXInput2Event()|.
+void XEventDeleter::operator()(XEvent* event) {
+ if (event->type == GenericEvent) {
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(event->xcookie.data);
+ if (xiev) {
+ delete[] xiev->valuators.mask;
+ delete[] xiev->valuators.values;
+ delete[] xiev->buttons.mask;
+ delete xiev;
+ }
+ }
+ delete event;
+}
+
+ScopedXI2Event::ScopedXI2Event() {}
+ScopedXI2Event::~ScopedXI2Event() {}
+
+void ScopedXI2Event::InitKeyEvent(EventType type,
+ KeyboardCode key_code,
+ int flags) {
+ XDisplay* display = gfx::GetXDisplay();
+ event_.reset(new XEvent);
+ memset(event_.get(), 0, sizeof(XEvent));
+ event_->type = XKeyEventType(type);
+ CHECK_NE(0, event_->type);
+ event_->xkey.serial = 0;
+ event_->xkey.send_event = 0;
+ event_->xkey.display = display;
+ event_->xkey.time = 0;
+ event_->xkey.window = 0;
+ event_->xkey.root = 0;
+ event_->xkey.subwindow = 0;
+ event_->xkey.x = 0;
+ event_->xkey.y = 0;
+ event_->xkey.x_root = 0;
+ event_->xkey.y_root = 0;
+ event_->xkey.state = XEventState(flags);
+ event_->xkey.keycode = XKeyCodeForWindowsKeyCode(key_code, flags, display);
+ event_->xkey.same_screen = 1;
+}
+
+void ScopedXI2Event::InitGenericKeyEvent(int deviceid,
+ int sourceid,
+ EventType type,
+ KeyboardCode key_code,
+ int flags) {
+ event_.reset(
+ CreateXInput2Event(deviceid, XIKeyEventType(type), 0, gfx::Point()));
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+ CHECK_NE(0, xievent->evtype);
+ XDisplay* display = gfx::GetXDisplay();
+ event_->xgeneric.display = display;
+ xievent->display = display;
+ xievent->mods.effective = XEventState(flags);
+ xievent->detail = XKeyCodeForWindowsKeyCode(key_code, flags, display);
+ xievent->sourceid = sourceid;
+}
+
+void ScopedXI2Event::InitGenericButtonEvent(int deviceid,
+ EventType type,
+ const gfx::Point& location,
+ int flags) {
+ event_.reset(CreateXInput2Event(deviceid,
+ XIButtonEventType(type), 0, gfx::Point()));
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+ xievent->mods.effective = XEventState(flags);
+ xievent->detail = XButtonEventButton(type, flags);
+ xievent->event_x = location.x();
+ xievent->event_y = location.y();
+ XISetMask(xievent->buttons.mask, xievent->detail);
+ // Setup an empty valuator list for generic button events.
+ SetUpValuators(std::vector<Valuator>());
+}
+
+void ScopedXI2Event::InitGenericMouseWheelEvent(int deviceid,
+ int wheel_delta,
+ int flags) {
+ InitGenericButtonEvent(deviceid, ui::ET_MOUSEWHEEL, gfx::Point(), flags);
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+ xievent->detail = wheel_delta > 0 ? Button4 : Button5;
+}
+
+void ScopedXI2Event::InitScrollEvent(int deviceid,
+ int x_offset,
+ int y_offset,
+ int x_offset_ordinal,
+ int y_offset_ordinal,
+ int finger_count) {
+ event_.reset(CreateXInput2Event(deviceid, XI_Motion, 0, gfx::Point()));
+
+ Valuator valuators[] = {
+ Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_X, x_offset),
+ Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_Y, y_offset),
+ Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_offset_ordinal),
+ Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_offset_ordinal),
+ Valuator(DeviceDataManagerX11::DT_CMT_FINGER_COUNT, finger_count)
+ };
+ SetUpValuators(
+ std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
+}
+
+void ScopedXI2Event::InitFlingScrollEvent(int deviceid,
+ int x_velocity,
+ int y_velocity,
+ int x_velocity_ordinal,
+ int y_velocity_ordinal,
+ bool is_cancel) {
+ event_.reset(CreateXInput2Event(deviceid, XI_Motion, deviceid, gfx::Point()));
+
+ Valuator valuators[] = {
+ Valuator(DeviceDataManagerX11::DT_CMT_FLING_STATE, is_cancel ? 1 : 0),
+ Valuator(DeviceDataManagerX11::DT_CMT_FLING_Y, y_velocity),
+ Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_velocity_ordinal),
+ Valuator(DeviceDataManagerX11::DT_CMT_FLING_X, x_velocity),
+ Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_velocity_ordinal)
+ };
+
+ SetUpValuators(
+ std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
+}
+
+void ScopedXI2Event::InitTouchEvent(int deviceid,
+ int evtype,
+ int tracking_id,
+ const gfx::Point& location,
+ const std::vector<Valuator>& valuators) {
+ event_.reset(CreateXInput2Event(deviceid, evtype, tracking_id, location));
+
+ // If a timestamp was specified, setup the event.
+ for (size_t i = 0; i < valuators.size(); ++i) {
+ if (valuators[i].data_type ==
+ DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP) {
+ SetUpValuators(valuators);
+ return;
+ }
+ }
+
+ // No timestamp was specified. Use |ui::EventTimeForNow()|.
+ std::vector<Valuator> valuators_with_time = valuators;
+ valuators_with_time.push_back(
+ Valuator(DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
+ (ui::EventTimeForNow()).InMicroseconds()));
+ SetUpValuators(valuators_with_time);
+}
+
+void ScopedXI2Event::SetUpValuators(const std::vector<Valuator>& valuators) {
+ CHECK(event_.get());
+ CHECK_EQ(GenericEvent, event_->type);
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+ InitValuatorsForXIDeviceEvent(xiev);
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ for (size_t i = 0; i < valuators.size(); ++i) {
+ manager->SetValuatorDataForTest(xiev, valuators[i].data_type,
+ valuators[i].value);
+ }
+}
+
+void SetUpTouchPadForTest(unsigned int deviceid) {
+ std::vector<unsigned int> device_list;
+ device_list.push_back(deviceid);
+
+ TouchFactory::GetInstance()->SetPointerDeviceForTest(device_list);
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ manager->SetDeviceListForTest(std::vector<unsigned int>(), device_list);
+}
+
+void SetUpTouchDevicesForTest(const std::vector<unsigned int>& devices) {
+ TouchFactory::GetInstance()->SetTouchDeviceForTest(devices);
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ manager->SetDeviceListForTest(devices, std::vector<unsigned int>());
+}
+
+} // namespace ui
diff --git a/ui/events/test/events_test_utils_x11.h b/ui/events/test/events_test_utils_x11.h
new file mode 100644
index 0000000..cce83ce
--- /dev/null
+++ b/ui/events/test/events_test_utils_x11.h
@@ -0,0 +1,100 @@
+// 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_TEST_EVENTS_TEST_UTILS_X11_H_
+#define UI_EVENTS_TEST_EVENTS_TEST_UTILS_X11_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/x/x11_types.h"
+
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+struct Valuator {
+ Valuator(DeviceDataManagerX11::DataType type, double v)
+ : data_type(type), value(v) {}
+
+ DeviceDataManagerX11::DataType data_type;
+ double value;
+};
+
+struct XEventDeleter {
+ void operator()(XEvent* event);
+};
+
+class ScopedXI2Event {
+ public:
+ ScopedXI2Event();
+ ~ScopedXI2Event();
+
+ operator XEvent*() { return event_.get(); }
+
+ // Initializes a XEvent with for the appropriate type with the specified data.
+ // Note that ui::EF_ flags should be passed as |flags|, not the native ones in
+ // <X11/X.h>.
+ void InitKeyEvent(EventType type,
+ KeyboardCode key_code,
+ int flags);
+
+ // Initializes an Xinput2 key event.
+ // |deviceid| is the master, and |sourceid| is the slave device.
+ void InitGenericKeyEvent(int deviceid,
+ int sourceid,
+ EventType type,
+ KeyboardCode key_code,
+ int flags);
+
+ void InitGenericButtonEvent(int deviceid,
+ EventType type,
+ const gfx::Point& location,
+ int flags);
+
+ void InitGenericMouseWheelEvent(int deviceid,
+ int wheel_delta,
+ int flags);
+
+ void InitScrollEvent(int deviceid,
+ int x_offset,
+ int y_offset,
+ int x_offset_ordinal,
+ int y_offset_ordinal,
+ int finger_count);
+
+ void InitFlingScrollEvent(int deviceid,
+ int x_velocity,
+ int y_velocity,
+ int x_velocity_ordinal,
+ int y_velocity_ordinal,
+ bool is_cancel);
+
+ void InitTouchEvent(int deviceid,
+ int evtype,
+ int tracking_id,
+ const gfx::Point& location,
+ const std::vector<Valuator>& valuators);
+
+ private:
+ void Cleanup();
+
+ void SetUpValuators(const std::vector<Valuator>& valuators);
+
+ scoped_ptr<XEvent, XEventDeleter> event_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedXI2Event);
+};
+
+// Initializes a test touchpad device for scroll events.
+void SetUpTouchPadForTest(unsigned int deviceid);
+
+// Initializes a list of touchscreen devices for touch events.
+void SetUpTouchDevicesForTest(const std::vector<unsigned int>& devices);
+
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_EVENTS_TEST_UTILS_X11_H_
diff --git a/ui/events/test/mock_motion_event.cc b/ui/events/test/mock_motion_event.cc
new file mode 100644
index 0000000..24d8159
--- /dev/null
+++ b/ui/events/test/mock_motion_event.cc
@@ -0,0 +1,177 @@
+// 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/test/mock_motion_event.h"
+
+#include "base/logging.h"
+
+using base::TimeTicks;
+
+namespace ui {
+namespace test {
+namespace {
+
+PointerProperties CreatePointer() {
+ PointerProperties pointer;
+ pointer.touch_major = MockMotionEvent::TOUCH_MAJOR;
+ return pointer;
+}
+
+PointerProperties CreatePointer(float x, float y, int id) {
+ PointerProperties pointer(x, y);
+ pointer.touch_major = MockMotionEvent::TOUCH_MAJOR;
+ pointer.id = id;
+ return pointer;
+}
+
+
+} // namespace
+
+MockMotionEvent::MockMotionEvent()
+ : MotionEventGeneric(ACTION_CANCEL, base::TimeTicks(), CreatePointer()) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action)
+ : MotionEventGeneric(action, base::TimeTicks(), CreatePointer()) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x0,
+ float y0)
+ : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x0,
+ float y0,
+ float x1,
+ float y1)
+ : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+ PushPointer(x1, y1);
+ if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+ set_action_index(1);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float x2,
+ float y2)
+ : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+ PushPointer(x1, y1);
+ PushPointer(x2, y2);
+ if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+ set_action_index(2);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ base::TimeTicks time,
+ const std::vector<gfx::PointF>& positions) {
+ set_action(action);
+ set_event_time(time);
+ if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+ set_action_index(static_cast<int>(positions.size()) - 1);
+ for (size_t i = 0; i < positions.size(); ++i)
+ PushPointer(positions[i].x(), positions[i].y());
+}
+
+MockMotionEvent::MockMotionEvent(const MockMotionEvent& other)
+ : MotionEventGeneric(other) {
+}
+
+MockMotionEvent::~MockMotionEvent() {}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Clone() const {
+ return scoped_ptr<MotionEvent>(new MockMotionEvent(*this));
+}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Cancel() const {
+ scoped_ptr<MockMotionEvent> event(new MockMotionEvent(*this));
+ event->set_action(MotionEvent::ACTION_CANCEL);
+ return event.PassAs<MotionEvent>();
+}
+
+void MockMotionEvent::PressPoint(float x, float y) {
+ ResolvePointers();
+ PushPointer(x, y);
+ if (GetPointerCount() > 1) {
+ set_action_index(static_cast<int>(GetPointerCount()) - 1);
+ set_action(ACTION_POINTER_DOWN);
+ } else {
+ set_action(ACTION_DOWN);
+ }
+}
+
+void MockMotionEvent::MovePoint(size_t index, float x, float y) {
+ ResolvePointers();
+ DCHECK_LT(index, GetPointerCount());
+ PointerProperties& p = pointer(index);
+ float dx = x - p.x;
+ float dy = x - p.y;
+ p.x = x;
+ p.y = y;
+ p.raw_x += dx;
+ p.raw_y += dy;
+ set_action(ACTION_MOVE);
+}
+
+void MockMotionEvent::ReleasePoint() {
+ ResolvePointers();
+ DCHECK_GT(GetPointerCount(), 0U);
+ if (GetPointerCount() > 1) {
+ set_action_index(static_cast<int>(GetPointerCount()) - 1);
+ set_action(ACTION_POINTER_UP);
+ } else {
+ set_action(ACTION_UP);
+ }
+}
+
+void MockMotionEvent::CancelPoint() {
+ ResolvePointers();
+ DCHECK_GT(GetPointerCount(), 0U);
+ set_action(ACTION_CANCEL);
+}
+
+void MockMotionEvent::SetTouchMajor(float new_touch_major) {
+ for (size_t i = 0; i < GetPointerCount(); ++i)
+ pointer(i).touch_major = new_touch_major;
+}
+
+void MockMotionEvent::SetRawOffset(float raw_offset_x, float raw_offset_y) {
+ for (size_t i = 0; i < GetPointerCount(); ++i) {
+ pointer(i).raw_x = pointer(i).x + raw_offset_x;
+ pointer(i).raw_y = pointer(i).y + raw_offset_y;
+ }
+}
+
+void MockMotionEvent::SetToolType(size_t pointer_index, ToolType tool_type) {
+ DCHECK_LT(pointer_index, GetPointerCount());
+ pointer(pointer_index).tool_type = tool_type;
+}
+
+void MockMotionEvent::PushPointer(float x, float y) {
+ MotionEventGeneric::PushPointer(
+ CreatePointer(x, y, static_cast<int>(GetPointerCount())));
+}
+
+void MockMotionEvent::ResolvePointers() {
+ set_action_index(-1);
+ switch (GetAction()) {
+ case ACTION_UP:
+ case ACTION_POINTER_UP:
+ case ACTION_CANCEL:
+ PopPointer();
+ return;
+ default:
+ break;
+ }
+}
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/mock_motion_event.h b/ui/events/test/mock_motion_event.h
new file mode 100644
index 0000000..9ae9a71
--- /dev/null
+++ b/ui/events/test/mock_motion_event.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.
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+namespace test {
+
+struct MockMotionEvent : public MotionEventGeneric {
+ 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 scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+ virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+ // Utility methods.
+ 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);
+ void SetToolType(size_t index, ToolType tool_type);
+
+ private:
+ void PushPointer(float x, float y);
+ void ResolvePointers();
+};
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/platform_event_waiter.cc b/ui/events/test/platform_event_waiter.cc
new file mode 100644
index 0000000..e024da5
--- /dev/null
+++ b/ui/events/test/platform_event_waiter.cc
@@ -0,0 +1,41 @@
+// 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/test/platform_event_waiter.h"
+
+#include "base/message_loop/message_loop.h"
+#include "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+
+PlatformEventWaiter::PlatformEventWaiter(
+ const base::Closure& success_callback,
+ const PlatformEventMatcher& event_matcher)
+ : success_callback_(success_callback),
+ event_matcher_(event_matcher) {
+ PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+}
+
+PlatformEventWaiter::~PlatformEventWaiter() {
+ PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+}
+
+void PlatformEventWaiter::WillProcessEvent(const PlatformEvent& event) {
+ if (event_matcher_.Run(event)) {
+ base::MessageLoop::current()->PostTask(FROM_HERE, success_callback_);
+ delete this;
+ }
+}
+
+void PlatformEventWaiter::DidProcessEvent(const PlatformEvent& event) {
+}
+
+// static
+PlatformEventWaiter* PlatformEventWaiter::Create(
+ const base::Closure& success_callback,
+ const PlatformEventMatcher& event_matcher) {
+ return new PlatformEventWaiter(success_callback, event_matcher);
+}
+
+} // namespace ui
diff --git a/ui/events/test/platform_event_waiter.h b/ui/events/test/platform_event_waiter.h
new file mode 100644
index 0000000..259662c
--- /dev/null
+++ b/ui/events/test/platform_event_waiter.h
@@ -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.
+
+#ifndef UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
+#define UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
+
+#include "base/callback.h"
+#include "ui/events/platform/platform_event_observer.h"
+
+namespace ui {
+
+class PlatformEventWaiter : public PlatformEventObserver {
+ public:
+ typedef base::Callback<bool(const PlatformEvent&)> PlatformEventMatcher;
+
+ static PlatformEventWaiter* Create(const base::Closure& success_callback,
+ const PlatformEventMatcher& event_matcher);
+
+ private:
+ PlatformEventWaiter(const base::Closure& success_callback,
+ const PlatformEventMatcher& event_matcher);
+ virtual ~PlatformEventWaiter();
+
+ // PlatformEventObserver:
+ virtual void WillProcessEvent(const PlatformEvent& event) OVERRIDE;
+ virtual void DidProcessEvent(const PlatformEvent& event) OVERRIDE;
+
+ base::Closure success_callback_;
+ PlatformEventMatcher event_matcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformEventWaiter);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
diff --git a/ui/events/test/test_event_handler.cc b/ui/events/test/test_event_handler.cc
new file mode 100644
index 0000000..f97aad5
--- /dev/null
+++ b/ui/events/test/test_event_handler.cc
@@ -0,0 +1,64 @@
+// 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/test/test_event_handler.h"
+
+#include "ui/events/event.h"
+
+namespace ui {
+namespace test {
+
+TestEventHandler::TestEventHandler()
+ : num_key_events_(0),
+ num_mouse_events_(0),
+ num_scroll_events_(0),
+ num_touch_events_(0),
+ num_gesture_events_(0),
+ recorder_(NULL),
+ handler_name_("unknown") {
+}
+
+TestEventHandler::~TestEventHandler() {}
+
+void TestEventHandler::Reset() {
+ num_key_events_ = 0;
+ num_mouse_events_ = 0;
+ num_scroll_events_ = 0;
+ num_touch_events_ = 0;
+ num_gesture_events_ = 0;
+}
+
+void TestEventHandler::OnKeyEvent(KeyEvent* event) {
+ if (recorder_)
+ recorder_->push_back(handler_name_);
+ num_key_events_++;
+ event->SetHandled();
+}
+
+void TestEventHandler::OnMouseEvent(MouseEvent* event) {
+ if (recorder_)
+ recorder_->push_back(handler_name_);
+ num_mouse_events_++;
+}
+
+void TestEventHandler::OnScrollEvent(ScrollEvent* event) {
+ if (recorder_)
+ recorder_->push_back(handler_name_);
+ num_scroll_events_++;
+}
+
+void TestEventHandler::OnTouchEvent(TouchEvent* event) {
+ if (recorder_)
+ recorder_->push_back(handler_name_);
+ num_touch_events_++;
+}
+
+void TestEventHandler::OnGestureEvent(GestureEvent* event) {
+ if (recorder_)
+ recorder_->push_back(handler_name_);
+ num_gesture_events_++;
+}
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/test_event_handler.h b/ui/events/test/test_event_handler.h
new file mode 100644
index 0000000..8937b25
--- /dev/null
+++ b/ui/events/test/test_event_handler.h
@@ -0,0 +1,66 @@
+// 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_TEST_TEST_EVENT_HANDLER_H_
+#define UI_EVENTS_TEST_TEST_EVENT_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/events/event_handler.h"
+
+typedef std::vector<std::string> HandlerSequenceRecorder;
+
+namespace ui {
+namespace test {
+
+// A simple EventHandler that keeps track of the number of key events that it's
+// seen.
+class TestEventHandler : public EventHandler {
+ public:
+ TestEventHandler();
+ virtual ~TestEventHandler();
+
+ int num_key_events() const { return num_key_events_; }
+ int num_mouse_events() const { return num_mouse_events_; }
+ int num_scroll_events() const { return num_scroll_events_; }
+ int num_touch_events() const { return num_touch_events_; }
+ int num_gesture_events() const { return num_gesture_events_; }
+
+ void Reset();
+
+ void set_recorder(HandlerSequenceRecorder* recorder) {
+ recorder_ = recorder;
+ }
+ void set_handler_name(const std::string& handler_name) {
+ handler_name_ = handler_name;
+ }
+
+ // EventHandler overrides:
+ virtual void OnKeyEvent(KeyEvent* event) OVERRIDE;
+ virtual void OnMouseEvent(MouseEvent* event) OVERRIDE;
+ virtual void OnScrollEvent(ScrollEvent* event) OVERRIDE;
+ virtual void OnTouchEvent(TouchEvent* event) OVERRIDE;
+ virtual void OnGestureEvent(GestureEvent* event) OVERRIDE;
+
+ private:
+ // How many events have been received of each type?
+ int num_key_events_;
+ int num_mouse_events_;
+ int num_scroll_events_;
+ int num_touch_events_;
+ int num_gesture_events_;
+
+ HandlerSequenceRecorder* recorder_;
+ std::string handler_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+} // namespace test
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_TEST_EVENT_HANDLER_H_
diff --git a/ui/events/test/test_event_processor.cc b/ui/events/test/test_event_processor.cc
new file mode 100644
index 0000000..3be8016
--- /dev/null
+++ b/ui/events/test/test_event_processor.cc
@@ -0,0 +1,53 @@
+// 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/test/test_event_processor.h"
+
+#include "ui/events/event_target.h"
+
+namespace ui {
+namespace test {
+
+TestEventProcessor::TestEventProcessor()
+ : should_processing_occur_(true),
+ num_times_processing_started_(0),
+ num_times_processing_finished_(0) {
+}
+
+TestEventProcessor::~TestEventProcessor() {}
+
+void TestEventProcessor::SetRoot(scoped_ptr<EventTarget> root) {
+ root_ = root.Pass();
+}
+
+void TestEventProcessor::Reset() {
+ should_processing_occur_ = true;
+ num_times_processing_started_ = 0;
+ num_times_processing_finished_ = 0;
+}
+
+bool TestEventProcessor::CanDispatchToTarget(EventTarget* target) {
+ return true;
+}
+
+EventTarget* TestEventProcessor::GetRootTarget() {
+ return root_.get();
+}
+
+EventDispatchDetails TestEventProcessor::OnEventFromSource(Event* event) {
+ return EventProcessor::OnEventFromSource(event);
+}
+
+void TestEventProcessor::OnEventProcessingStarted(Event* event) {
+ num_times_processing_started_++;
+ if (!should_processing_occur_)
+ event->SetHandled();
+}
+
+void TestEventProcessor::OnEventProcessingFinished(Event* event) {
+ num_times_processing_finished_++;
+}
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/test_event_processor.h b/ui/events/test/test_event_processor.h
new file mode 100644
index 0000000..5b9a99b
--- /dev/null
+++ b/ui/events/test/test_event_processor.h
@@ -0,0 +1,60 @@
+// 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_TEST_TEST_EVENT_PROCESSOR_H_
+#define UI_EVENTS_TEST_TEST_EVENT_PROCESSOR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_processor.h"
+
+namespace ui {
+namespace test {
+
+class TestEventProcessor : public EventProcessor {
+ public:
+ TestEventProcessor();
+ virtual ~TestEventProcessor();
+
+ int num_times_processing_started() const {
+ return num_times_processing_started_;
+ }
+
+ int num_times_processing_finished() const {
+ return num_times_processing_finished_;
+ }
+
+ void set_should_processing_occur(bool occur) {
+ should_processing_occur_ = occur;
+ }
+
+ void SetRoot(scoped_ptr<EventTarget> root);
+ void Reset();
+
+ // EventProcessor:
+ virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE;
+ virtual EventTarget* GetRootTarget() OVERRIDE;
+ virtual EventDispatchDetails OnEventFromSource(Event* event) OVERRIDE;
+ virtual void OnEventProcessingStarted(Event* event) OVERRIDE;
+ virtual void OnEventProcessingFinished(Event* event) OVERRIDE;
+
+ private:
+ scoped_ptr<EventTarget> root_;
+
+ // Used in our override of OnEventProcessingStarted(). If this value is
+ // false, mark incoming events as handled.
+ bool should_processing_occur_;
+
+ // Counts the number of times OnEventProcessingStarted() has been called.
+ int num_times_processing_started_;
+
+ // Counts the number of times OnEventProcessingFinished() has been called.
+ int num_times_processing_finished_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventProcessor);
+};
+
+} // namespace test
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_TEST_EVENT_PROCESSOR_H_
diff --git a/ui/events/test/test_event_target.cc b/ui/events/test/test_event_target.cc
new file mode 100644
index 0000000..1274dd5
--- /dev/null
+++ b/ui/events/test/test_event_target.cc
@@ -0,0 +1,94 @@
+// 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/test/test_event_target.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_target_iterator.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+namespace test {
+
+TestEventTarget::TestEventTarget()
+ : parent_(NULL),
+ mark_events_as_handled_(false),
+ recorder_(NULL),
+ target_name_("unknown") {}
+TestEventTarget::~TestEventTarget() {}
+
+void TestEventTarget::AddChild(scoped_ptr<TestEventTarget> child) {
+ TestEventTarget* child_r = child.get();
+ if (child->parent()) {
+ AddChild(child->parent()->RemoveChild(child.release()));
+ } else {
+ children_.push_back(child.release());
+ }
+ child_r->set_parent(this);
+}
+
+scoped_ptr<TestEventTarget> TestEventTarget::RemoveChild(TestEventTarget *c) {
+ ScopedVector<TestEventTarget>::iterator iter = std::find(children_.begin(),
+ children_.end(),
+ c);
+ if (iter != children_.end()) {
+ children_.weak_erase(iter);
+ c->set_parent(NULL);
+ return scoped_ptr<TestEventTarget>(c);
+ }
+ return scoped_ptr<TestEventTarget>();
+}
+
+void TestEventTarget::SetEventTargeter(scoped_ptr<EventTargeter> targeter) {
+ targeter_ = targeter.Pass();
+}
+
+bool TestEventTarget::DidReceiveEvent(ui::EventType type) const {
+ return received_.count(type) > 0;
+}
+
+void TestEventTarget::ResetReceivedEvents() {
+ received_.clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TestEventTarget, protected
+
+bool TestEventTarget::CanAcceptEvent(const ui::Event& event) {
+ return true;
+}
+
+EventTarget* TestEventTarget::GetParentTarget() {
+ return parent_;
+}
+
+scoped_ptr<EventTargetIterator> TestEventTarget::GetChildIterator() const {
+ return scoped_ptr<EventTargetIterator>(
+ new EventTargetIteratorImpl<TestEventTarget>(children_.get()));
+}
+
+EventTargeter* TestEventTarget::GetEventTargeter() {
+ return targeter_.get();
+}
+
+void TestEventTarget::OnEvent(Event* event) {
+ if (recorder_)
+ recorder_->push_back(target_name_);
+ received_.insert(event->type());
+ EventTarget::OnEvent(event);
+ if (!event->handled() && mark_events_as_handled_)
+ event->SetHandled();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TestEventTarget, private
+
+bool TestEventTarget::Contains(TestEventTarget* target) const {
+ while (target && target != this)
+ target = target->parent();
+ return target == this;
+}
+
+} // namespace test
+} // namespace ui
diff --git a/ui/events/test/test_event_target.h b/ui/events/test/test_event_target.h
new file mode 100644
index 0000000..368e295
--- /dev/null
+++ b/ui/events/test/test_event_target.h
@@ -0,0 +1,84 @@
+// 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_TEST_TEST_EVENT_TARGET_H_
+#define UI_EVENTS_TEST_TEST_EVENT_TARGET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "ui/events/event_target.h"
+
+typedef std::vector<std::string> HandlerSequenceRecorder;
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+namespace test {
+
+class TestEventTarget : public EventTarget {
+ public:
+ TestEventTarget();
+ virtual ~TestEventTarget();
+
+ void AddChild(scoped_ptr<TestEventTarget> child);
+ scoped_ptr<TestEventTarget> RemoveChild(TestEventTarget* child);
+
+ TestEventTarget* parent() { return parent_; }
+
+ void set_mark_events_as_handled(bool handle) {
+ mark_events_as_handled_ = handle;
+ }
+
+ TestEventTarget* child_at(int index) { return children_[index]; }
+ size_t child_count() const { return children_.size(); }
+
+ void SetEventTargeter(scoped_ptr<EventTargeter> targeter);
+
+ bool DidReceiveEvent(ui::EventType type) const;
+ void ResetReceivedEvents();
+
+ void set_recorder(HandlerSequenceRecorder* recorder) {
+ recorder_ = recorder;
+ }
+ void set_target_name(const std::string& target_name) {
+ target_name_ = target_name;
+ }
+
+ protected:
+ bool Contains(TestEventTarget* target) const;
+
+ // EventTarget:
+ virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE;
+ virtual EventTarget* GetParentTarget() OVERRIDE;
+ virtual scoped_ptr<EventTargetIterator> GetChildIterator() const OVERRIDE;
+ virtual EventTargeter* GetEventTargeter() OVERRIDE;
+
+ // EventHandler:
+ virtual void OnEvent(Event* event) OVERRIDE;
+
+ private:
+ void set_parent(TestEventTarget* parent) { parent_ = parent; }
+
+ TestEventTarget* parent_;
+ ScopedVector<TestEventTarget> children_;
+ scoped_ptr<EventTargeter> targeter_;
+ bool mark_events_as_handled_;
+
+ std::set<ui::EventType> received_;
+
+ HandlerSequenceRecorder* recorder_;
+ std::string target_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestEventTarget);
+};
+
+} // namespace test
+} // namespace ui
+
+#endif // UI_EVENTS_TEST_TEST_EVENT_TARGET_H_
diff --git a/ui/events/touchscreen_device.cc b/ui/events/touchscreen_device.cc
new file mode 100644
index 0000000..07844a1
--- /dev/null
+++ b/ui/events/touchscreen_device.cc
@@ -0,0 +1,18 @@
+// 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/touchscreen_device.h"
+
+namespace ui {
+
+// static
+const int TouchscreenDevice::kInvalidId = 0;
+
+TouchscreenDevice::TouchscreenDevice(int id,
+ const gfx::Size& size,
+ bool is_internal)
+ : id(id), size(size), is_internal(is_internal) {
+}
+
+} // namespace ui
diff --git a/ui/events/touchscreen_device.h b/ui/events/touchscreen_device.h
new file mode 100644
index 0000000..669f306
--- /dev/null
+++ b/ui/events/touchscreen_device.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_TOUCHSCREEN_DEVICE_H_
+#define UI_EVENTS_TOUCHSCREEN_DEVICE_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace ui {
+
+// Represents a Touchscreen device state.
+struct EVENTS_BASE_EXPORT TouchscreenDevice {
+ static const int kInvalidId;
+
+ TouchscreenDevice(int id, const gfx::Size& size, bool is_internal);
+
+ // ID of the touch screen. This ID must uniquely identify the touch screen.
+ int id;
+
+ // Size of the touch screen area.
+ gfx::Size size;
+
+ // True if this is an internal touchscreen.
+ bool is_internal;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_TOUCHSCREEN_DEVICE_H_
diff --git a/ui/events/win/events_win.cc b/ui/events/win/events_win.cc
new file mode 100644
index 0000000..9434b2b
--- /dev/null
+++ b/ui/events/win/events_win.cc
@@ -0,0 +1,414 @@
+// 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 <windowsx.h>
+
+#include "ui/events/event_constants.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "base/win/win_util.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_win.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/win/dpi.h"
+
+namespace ui {
+
+namespace {
+
+// From MSDN: "Mouse" events are flagged with 0xFF515700 if they come
+// from a touch or stylus device. In Vista or later, they are also flagged
+// with 0x80 if they come from touch.
+#define MOUSEEVENTF_FROMTOUCH (0xFF515700 | 0x80)
+
+// Get the native mouse key state from the native event message type.
+int GetNativeMouseKey(const base::NativeEvent& native_event) {
+ switch (native_event.message) {
+ case WM_LBUTTONDBLCLK:
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_NCLBUTTONDBLCLK:
+ case WM_NCLBUTTONDOWN:
+ case WM_NCLBUTTONUP:
+ return MK_LBUTTON;
+ case WM_MBUTTONDBLCLK:
+ case WM_MBUTTONDOWN:
+ case WM_MBUTTONUP:
+ case WM_NCMBUTTONDBLCLK:
+ case WM_NCMBUTTONDOWN:
+ case WM_NCMBUTTONUP:
+ return MK_MBUTTON;
+ case WM_RBUTTONDBLCLK:
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONUP:
+ case WM_NCRBUTTONDBLCLK:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCRBUTTONUP:
+ return MK_RBUTTON;
+ case WM_NCXBUTTONDBLCLK:
+ case WM_NCXBUTTONDOWN:
+ case WM_NCXBUTTONUP:
+ case WM_XBUTTONDBLCLK:
+ case WM_XBUTTONDOWN:
+ case WM_XBUTTONUP:
+ return MK_XBUTTON1;
+ }
+ return 0;
+}
+
+bool IsButtonDown(const base::NativeEvent& native_event) {
+ return ((MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2) &
+ native_event.wParam) != 0;
+}
+
+bool IsClientMouseEvent(const base::NativeEvent& native_event) {
+ return native_event.message == WM_MOUSELEAVE ||
+ native_event.message == WM_MOUSEHOVER ||
+ (native_event.message >= WM_MOUSEFIRST &&
+ native_event.message <= WM_MOUSELAST);
+}
+
+bool IsNonClientMouseEvent(const base::NativeEvent& native_event) {
+ return native_event.message == WM_NCMOUSELEAVE ||
+ native_event.message == WM_NCMOUSEHOVER ||
+ (native_event.message >= WM_NCMOUSEMOVE &&
+ 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;
+}
+
+bool IsKeyEvent(const base::NativeEvent& native_event) {
+ return native_event.message == WM_KEYDOWN ||
+ native_event.message == WM_SYSKEYDOWN ||
+ native_event.message == WM_CHAR ||
+ native_event.message == WM_KEYUP ||
+ native_event.message == WM_SYSKEYUP;
+}
+
+bool IsScrollEvent(const base::NativeEvent& native_event) {
+ return native_event.message == WM_VSCROLL ||
+ native_event.message == WM_HSCROLL;
+}
+
+// Returns a mask corresponding to the set of pressed modifier keys.
+// Checks the current global state and the state sent by client mouse messages.
+int KeyStateFlagsFromNative(const base::NativeEvent& native_event) {
+ int flags = 0;
+ flags |= base::win::IsAltPressed() ? EF_ALT_DOWN : EF_NONE;
+ flags |= base::win::IsShiftPressed() ? EF_SHIFT_DOWN : EF_NONE;
+ flags |= base::win::IsCtrlPressed() ? EF_CONTROL_DOWN : EF_NONE;
+
+ // Check key messages for the extended key flag.
+ if (IsKeyEvent(native_event))
+ flags |= (HIWORD(native_event.lParam) & KF_EXTENDED) ? EF_EXTENDED : 0;
+
+ // Most client mouse messages include key state information.
+ if (IsClientMouseEvent(native_event)) {
+ int win_flags = GET_KEYSTATE_WPARAM(native_event.wParam);
+ flags |= (win_flags & MK_SHIFT) ? EF_SHIFT_DOWN : 0;
+ flags |= (win_flags & MK_CONTROL) ? EF_CONTROL_DOWN : 0;
+ }
+
+ return flags;
+}
+
+// Returns a mask corresponding to the set of pressed mouse buttons.
+// This includes the button of the given message, even if it is being released.
+int MouseStateFlagsFromNative(const base::NativeEvent& native_event) {
+ int win_flags = GetNativeMouseKey(native_event);
+
+ // Client mouse messages provide key states in their WPARAMs.
+ if (IsClientMouseEvent(native_event))
+ win_flags |= GET_KEYSTATE_WPARAM(native_event.wParam);
+
+ int flags = 0;
+ flags |= (win_flags & MK_LBUTTON) ? EF_LEFT_MOUSE_BUTTON : 0;
+ flags |= (win_flags & MK_MBUTTON) ? EF_MIDDLE_MOUSE_BUTTON : 0;
+ flags |= (win_flags & MK_RBUTTON) ? EF_RIGHT_MOUSE_BUTTON : 0;
+ flags |= IsNonClientMouseEvent(native_event) ? EF_IS_NON_CLIENT : 0;
+ return flags;
+}
+
+} // namespace
+
+void UpdateDeviceList() {
+ NOTIMPLEMENTED();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+ switch (native_event.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ case WM_CHAR:
+ return ET_KEY_PRESSED;
+ // The WM_DEADCHAR message is posted to the window with the keyboard focus
+ // when a WM_KEYUP message is translated. This happens for special keyboard
+ // sequences.
+ case WM_DEADCHAR:
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ return ET_KEY_RELEASED;
+ case WM_LBUTTONDBLCLK:
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDBLCLK:
+ case WM_MBUTTONDOWN:
+ case WM_NCLBUTTONDBLCLK:
+ case WM_NCLBUTTONDOWN:
+ case WM_NCMBUTTONDBLCLK:
+ case WM_NCMBUTTONDOWN:
+ case WM_NCRBUTTONDBLCLK:
+ case WM_NCRBUTTONDOWN:
+ case WM_NCXBUTTONDBLCLK:
+ case WM_NCXBUTTONDOWN:
+ case WM_RBUTTONDBLCLK:
+ case WM_RBUTTONDOWN:
+ case WM_XBUTTONDBLCLK:
+ case WM_XBUTTONDOWN:
+ return ET_MOUSE_PRESSED;
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_NCLBUTTONUP:
+ case WM_NCMBUTTONUP:
+ case WM_NCRBUTTONUP:
+ case WM_NCXBUTTONUP:
+ case WM_RBUTTONUP:
+ case WM_XBUTTONUP:
+ return ET_MOUSE_RELEASED;
+ case WM_MOUSEMOVE:
+ return IsButtonDown(native_event) ? ET_MOUSE_DRAGGED : ET_MOUSE_MOVED;
+ case WM_NCMOUSEMOVE:
+ return ET_MOUSE_MOVED;
+ case WM_MOUSEWHEEL:
+ case WM_MOUSEHWHEEL:
+ return ET_MOUSEWHEEL;
+ case WM_MOUSELEAVE:
+ case WM_NCMOUSELEAVE:
+ return ET_MOUSE_EXITED;
+ case WM_VSCROLL:
+ case WM_HSCROLL:
+ return ET_SCROLL;
+ default:
+ // We can't NOTREACHED() here, since this function can be called for any
+ // message.
+ break;
+ }
+ return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+ int flags = KeyStateFlagsFromNative(native_event);
+ if (IsMouseEvent(native_event))
+ flags |= MouseStateFlagsFromNative(native_event);
+
+ return flags;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+ return base::TimeDelta::FromMilliseconds(native_event.time);
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+ POINT native_point;
+ if ((native_event.message == WM_MOUSELEAVE ||
+ native_event.message == WM_NCMOUSELEAVE) ||
+ IsScrollEvent(native_event)) {
+ // These events have no coordinates. For sanity with rest of events grab
+ // coordinates from the OS.
+ ::GetCursorPos(&native_point);
+ } else if (IsClientMouseEvent(native_event) &&
+ !IsMouseWheelEvent(native_event)) {
+ // Note: Wheel events are considered client, but their position is in screen
+ // coordinates.
+ // Client message. The position is contained in the LPARAM.
+ return gfx::Point(native_event.lParam);
+ } else {
+ DCHECK(IsNonClientMouseEvent(native_event) ||
+ IsMouseWheelEvent(native_event) || IsScrollEvent(native_event));
+ // Non-client message. The position is contained in a POINTS structure in
+ // LPARAM, and is in screen coordinates so we have to convert to client.
+ native_point.x = GET_X_LPARAM(native_event.lParam);
+ native_point.y = GET_Y_LPARAM(native_event.lParam);
+ }
+ ScreenToClient(native_event.hwnd, &native_point);
+ return gfx::Point(native_point);
+}
+
+gfx::Point EventSystemLocationFromNative(
+ const base::NativeEvent& native_event) {
+ POINT global_point = { static_cast<short>(LOWORD(native_event.lParam)),
+ static_cast<short>(HIWORD(native_event.lParam)) };
+ ClientToScreen(native_event.hwnd, &global_point);
+ return gfx::Point(global_point);
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+ return KeyboardCodeForWindowsKeyCode(native_event.wParam);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+ const uint16 scan_code = GetScanCodeFromLParam(native_event.lParam);
+ return CodeForWindowsScanCode(scan_code);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ return static_cast<uint32>(native_event.wParam);
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+ return native_event.message == WM_CHAR;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+ const base::NativeEvent& native_event) {
+ switch (GetNativeMouseKey(native_event)) {
+ case MK_LBUTTON:
+ return EF_LEFT_MOUSE_BUTTON;
+ case MK_MBUTTON:
+ return EF_MIDDLE_MOUSE_BUTTON;
+ case MK_RBUTTON:
+ return EF_RIGHT_MOUSE_BUTTON;
+ // TODO: add support for MK_XBUTTON1.
+ default:
+ break;
+ }
+ return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+ DCHECK(native_event.message == WM_MOUSEWHEEL ||
+ native_event.message == WM_MOUSEHWHEEL);
+ if (native_event.message == WM_MOUSEWHEEL)
+ return gfx::Vector2d(0, GET_WHEEL_DELTA_WPARAM(native_event.wParam));
+ 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 IncrementTouchIdRefCount(const base::NativeEvent& event) {
+ NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+ NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& xev) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 1.0;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 1.0;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.0;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0.0;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count) {
+ // TODO(ananta)
+ // Support retrieving the scroll offsets from the scroll event.
+ if (native_event.message == WM_VSCROLL || native_event.message == WM_HSCROLL)
+ return true;
+ return false;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ // Not supported in Windows.
+ NOTIMPLEMENTED();
+ return false;
+}
+
+int GetModifiersFromACCEL(const ACCEL& accel) {
+ int modifiers = EF_NONE;
+ if (accel.fVirt & FSHIFT)
+ modifiers |= EF_SHIFT_DOWN;
+ if (accel.fVirt & FCONTROL)
+ modifiers |= EF_CONTROL_DOWN;
+ if (accel.fVirt & FALT)
+ modifiers |= EF_ALT_DOWN;
+ return modifiers;
+}
+
+int GetModifiersFromKeyState() {
+ int modifiers = EF_NONE;
+ if (base::win::IsShiftPressed())
+ modifiers |= EF_SHIFT_DOWN;
+ if (base::win::IsCtrlPressed())
+ modifiers |= EF_CONTROL_DOWN;
+ if (base::win::IsAltPressed())
+ modifiers |= EF_ALT_DOWN;
+ if (base::win::IsAltGrPressed())
+ modifiers |= EF_ALTGR_DOWN;
+ return modifiers;
+}
+
+// Windows emulates mouse messages for touch events.
+bool IsMouseEventFromTouch(UINT message) {
+ return (message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST) &&
+ (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) ==
+ MOUSEEVENTF_FROMTOUCH;
+}
+
+// Conversion scan_code and LParam each other.
+// uint16 scan_code:
+// ui/events/keycodes/dom4/keycode_converter_data.h
+// 0 - 15bits: represetns the scan code.
+// 28 - 30 bits (0xE000): represents whether this is an extended key or not.
+//
+// LPARAM lParam:
+// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644984.aspx
+// 16 - 23bits: represetns the scan code.
+// 24bit (0x0100): represents whether this is an extended key or not.
+uint16 GetScanCodeFromLParam(LPARAM l_param) {
+ uint16 scan_code = ((l_param >> 16) & 0x00FF);
+ if (l_param & (1 << 24))
+ scan_code |= 0xE000;
+ return scan_code;
+}
+
+LPARAM GetLParamFromScanCode(uint16 scan_code) {
+ LPARAM l_param = static_cast<LPARAM>(scan_code & 0x00FF) << 16;
+ if ((scan_code & 0xE000) == 0xE000)
+ l_param |= (1 << 24);
+ return l_param;
+}
+
+} // namespace ui
diff --git a/ui/events/x/device_data_manager_x11.cc b/ui/events/x/device_data_manager_x11.cc
new file mode 100644
index 0000000..795cc44
--- /dev/null
+++ b/ui/events/x/device_data_manager_x11.cc
@@ -0,0 +1,710 @@
+// 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/x/device_data_manager_x11.h"
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+
+#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/keycodes/keyboard_code_conversion_x.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
+// for backward-compatibility with older versions of XInput.
+#if !defined(XIScrollClass)
+#define XIScrollClass 3
+#endif
+
+// Multi-touch support was introduced in XI 2.2. Add XI event types here
+// for backward-compatibility with older versions of XInput.
+#if !defined(XI_TouchBegin)
+#define XI_TouchBegin 18
+#define XI_TouchUpdate 19
+#define XI_TouchEnd 20
+#endif
+
+// Copied from xserver-properties.h
+#define AXIS_LABEL_PROP_REL_HWHEEL "Rel Horiz Wheel"
+#define AXIS_LABEL_PROP_REL_WHEEL "Rel Vert Wheel"
+
+// CMT specific timings
+#define AXIS_LABEL_PROP_ABS_DBL_START_TIME "Abs Dbl Start Timestamp"
+#define AXIS_LABEL_PROP_ABS_DBL_END_TIME "Abs Dbl End Timestamp"
+
+// Ordinal values
+#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X "Abs Dbl Ordinal X"
+#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y "Abs Dbl Ordinal Y"
+
+// Fling properties
+#define AXIS_LABEL_PROP_ABS_DBL_FLING_VX "Abs Dbl Fling X Velocity"
+#define AXIS_LABEL_PROP_ABS_DBL_FLING_VY "Abs Dbl Fling Y Velocity"
+#define AXIS_LABEL_PROP_ABS_FLING_STATE "Abs Fling State"
+
+#define AXIS_LABEL_PROP_ABS_FINGER_COUNT "Abs Finger Count"
+
+// Cros metrics gesture from touchpad
+#define AXIS_LABEL_PROP_ABS_METRICS_TYPE "Abs Metrics Type"
+#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1 "Abs Dbl Metrics Data 1"
+#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2 "Abs Dbl Metrics Data 2"
+
+// Touchscreen multi-touch
+#define AXIS_LABEL_ABS_MT_TOUCH_MAJOR "Abs MT Touch Major"
+#define AXIS_LABEL_ABS_MT_TOUCH_MINOR "Abs MT Touch Minor"
+#define AXIS_LABEL_ABS_MT_ORIENTATION "Abs MT Orientation"
+#define AXIS_LABEL_ABS_MT_PRESSURE "Abs MT Pressure"
+#define AXIS_LABEL_ABS_MT_POSITION_X "Abs MT Position X"
+#define AXIS_LABEL_ABS_MT_POSITION_Y "Abs MT Position Y"
+#define AXIS_LABEL_ABS_MT_TRACKING_ID "Abs MT Tracking ID"
+#define AXIS_LABEL_TOUCH_TIMESTAMP "Touch Timestamp"
+
+// When you add new data types, please make sure the order here is aligned
+// with the order in the DataType enum in the header file because we assume
+// they are in sync when updating the device list (see UpdateDeviceList).
+const char* kCachedAtoms[] = {
+ AXIS_LABEL_PROP_REL_HWHEEL,
+ AXIS_LABEL_PROP_REL_WHEEL,
+ AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X,
+ AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y,
+ AXIS_LABEL_PROP_ABS_DBL_START_TIME,
+ AXIS_LABEL_PROP_ABS_DBL_END_TIME,
+ AXIS_LABEL_PROP_ABS_DBL_FLING_VX,
+ AXIS_LABEL_PROP_ABS_DBL_FLING_VY,
+ AXIS_LABEL_PROP_ABS_FLING_STATE,
+ AXIS_LABEL_PROP_ABS_METRICS_TYPE,
+ AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1,
+ AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2,
+ AXIS_LABEL_PROP_ABS_FINGER_COUNT,
+ AXIS_LABEL_ABS_MT_TOUCH_MAJOR,
+ AXIS_LABEL_ABS_MT_TOUCH_MINOR,
+ AXIS_LABEL_ABS_MT_ORIENTATION,
+ AXIS_LABEL_ABS_MT_PRESSURE,
+ AXIS_LABEL_ABS_MT_POSITION_X,
+ AXIS_LABEL_ABS_MT_POSITION_Y,
+ AXIS_LABEL_ABS_MT_TRACKING_ID,
+ AXIS_LABEL_TOUCH_TIMESTAMP,
+
+ NULL
+};
+
+// Constants for checking if a data type lies in the range of CMT/Touch data
+// types.
+const int kCMTDataTypeStart = ui::DeviceDataManagerX11::DT_CMT_SCROLL_X;
+const int kCMTDataTypeEnd = ui::DeviceDataManagerX11::DT_CMT_FINGER_COUNT;
+const int kTouchDataTypeStart = ui::DeviceDataManagerX11::DT_TOUCH_MAJOR;
+const int kTouchDataTypeEnd = ui::DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP;
+
+namespace ui {
+
+bool DeviceDataManagerX11::IsCMTDataType(const int type) {
+ return (type >= kCMTDataTypeStart) && (type <= kCMTDataTypeEnd);
+}
+
+bool DeviceDataManagerX11::IsTouchDataType(const int type) {
+ return (type >= kTouchDataTypeStart) && (type <= kTouchDataTypeEnd);
+}
+
+// static
+void DeviceDataManagerX11::CreateInstance() {
+ if (instance())
+ return;
+
+ new DeviceDataManagerX11();
+}
+
+// static
+DeviceDataManagerX11* DeviceDataManagerX11::GetInstance() {
+ return static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance());
+}
+
+DeviceDataManagerX11::DeviceDataManagerX11()
+ : xi_opcode_(-1),
+ atom_cache_(gfx::GetXDisplay(), kCachedAtoms),
+ button_map_count_(0) {
+ CHECK(gfx::GetXDisplay());
+ InitializeXInputInternal();
+
+ // Make sure the sizes of enum and kCachedAtoms are aligned.
+ CHECK(arraysize(kCachedAtoms) == static_cast<size_t>(DT_LAST_ENTRY) + 1);
+ UpdateDeviceList(gfx::GetXDisplay());
+ UpdateButtonMap();
+}
+
+DeviceDataManagerX11::~DeviceDataManagerX11() {
+}
+
+bool DeviceDataManagerX11::InitializeXInputInternal() {
+ // Check if XInput is available on the system.
+ xi_opcode_ = -1;
+ int opcode, event, error;
+ if (!XQueryExtension(
+ gfx::GetXDisplay(), "XInputExtension", &opcode, &event, &error)) {
+ VLOG(1) << "X Input extension not available: error=" << error;
+ return false;
+ }
+
+ // Check the XInput version.
+#if defined(USE_XI2_MT)
+ int major = 2, minor = USE_XI2_MT;
+#else
+ int major = 2, minor = 0;
+#endif
+ if (XIQueryVersion(gfx::GetXDisplay(), &major, &minor) == BadRequest) {
+ VLOG(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
+
+ xi_opcode_ = opcode;
+ CHECK_NE(-1, xi_opcode_);
+
+ // Possible XI event types for XIDeviceEvent. See the XI2 protocol
+ // specification.
+ xi_device_event_types_[XI_KeyPress] = true;
+ xi_device_event_types_[XI_KeyRelease] = true;
+ xi_device_event_types_[XI_ButtonPress] = true;
+ xi_device_event_types_[XI_ButtonRelease] = true;
+ xi_device_event_types_[XI_Motion] = true;
+ // Multi-touch support was introduced in XI 2.2.
+ if (minor >= 2) {
+ xi_device_event_types_[XI_TouchBegin] = true;
+ xi_device_event_types_[XI_TouchUpdate] = true;
+ xi_device_event_types_[XI_TouchEnd] = true;
+ }
+ return true;
+}
+
+bool DeviceDataManagerX11::IsXInput2Available() const {
+ return xi_opcode_ != -1;
+}
+
+void DeviceDataManagerX11::UpdateDeviceList(Display* display) {
+ cmt_devices_.reset();
+ touchpads_.reset();
+ for (int i = 0; i < kMaxDeviceNum; ++i) {
+ valuator_count_[i] = 0;
+ valuator_lookup_[i].clear();
+ data_type_lookup_[i].clear();
+ valuator_min_[i].clear();
+ valuator_max_[i].clear();
+ for (int j = 0; j < kMaxSlotNum; j++)
+ last_seen_valuator_[i][j].clear();
+ }
+
+ // Find all the touchpad devices.
+ XDeviceList dev_list =
+ ui::DeviceListCacheX::GetInstance()->GetXDeviceList(display);
+ Atom xi_touchpad = XInternAtom(display, XI_TOUCHPAD, false);
+ for (int i = 0; i < dev_list.count; ++i)
+ if (dev_list[i].type == xi_touchpad)
+ touchpads_[dev_list[i].id] = true;
+
+ if (!IsXInput2Available())
+ return;
+
+ // Update the structs with new valuator information
+ XIDeviceList info_list =
+ ui::DeviceListCacheX::GetInstance()->GetXI2DeviceList(display);
+ Atom atoms[DT_LAST_ENTRY];
+ for (int data_type = 0; data_type < DT_LAST_ENTRY; ++data_type)
+ atoms[data_type] = atom_cache_.GetAtom(kCachedAtoms[data_type]);
+
+ for (int i = 0; i < info_list.count; ++i) {
+ XIDeviceInfo* info = info_list.devices + i;
+
+ // We currently handle only slave, non-keyboard devices
+ if (info->use != XISlavePointer && info->use != XIFloatingSlave)
+ continue;
+
+ bool possible_cmt = false;
+ bool not_cmt = false;
+ const int deviceid = info->deviceid;
+
+ for (int j = 0; j < info->num_classes; ++j) {
+ if (info->classes[j]->type == XIValuatorClass)
+ ++valuator_count_[deviceid];
+ else if (info->classes[j]->type == XIScrollClass)
+ not_cmt = true;
+ }
+
+ // Skip devices that don't use any valuator
+ if (!valuator_count_[deviceid])
+ continue;
+
+ valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
+ data_type_lookup_[deviceid].resize(
+ valuator_count_[deviceid], DT_LAST_ENTRY);
+ valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
+ valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
+ for (int j = 0; j < kMaxSlotNum; j++)
+ last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
+ for (int j = 0; j < info->num_classes; ++j) {
+ if (info->classes[j]->type != XIValuatorClass)
+ continue;
+
+ XIValuatorClassInfo* v =
+ reinterpret_cast<XIValuatorClassInfo*>(info->classes[j]);
+ for (int data_type = 0; data_type < DT_LAST_ENTRY; ++data_type) {
+ if (v->label == atoms[data_type]) {
+ valuator_lookup_[deviceid][data_type] = v->number;
+ data_type_lookup_[deviceid][v->number] = data_type;
+ valuator_min_[deviceid][data_type] = v->min;
+ valuator_max_[deviceid][data_type] = v->max;
+ if (IsCMTDataType(data_type))
+ possible_cmt = true;
+ break;
+ }
+ }
+ }
+
+ if (possible_cmt && !not_cmt)
+ cmt_devices_[deviceid] = true;
+ }
+}
+
+bool DeviceDataManagerX11::GetSlotNumber(const XIDeviceEvent* xiev, int* slot) {
+#if defined(USE_XI2_MT)
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ if (!factory->IsMultiTouchDevice(xiev->sourceid)) {
+ *slot = 0;
+ return true;
+ }
+ return factory->QuerySlotForTrackingID(xiev->detail, slot);
+#else
+ *slot = 0;
+ return true;
+#endif
+}
+
+void DeviceDataManagerX11::GetEventRawData(const XEvent& xev, EventData* data) {
+ if (xev.type != GenericEvent)
+ return;
+
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+ if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+ return;
+ data->clear();
+ const int sourceid = xiev->sourceid;
+ double* valuators = xiev->valuators.values;
+ for (int i = 0; i <= valuator_count_[sourceid]; ++i) {
+ if (XIMaskIsSet(xiev->valuators.mask, i)) {
+ int type = data_type_lookup_[sourceid][i];
+ if (type != DT_LAST_ENTRY) {
+ (*data)[type] = *valuators;
+ if (IsTouchDataType(type)) {
+ int slot = -1;
+ if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+ last_seen_valuator_[sourceid][slot][type] = *valuators;
+ }
+ }
+ valuators++;
+ }
+ }
+}
+
+bool DeviceDataManagerX11::GetEventData(const XEvent& xev,
+ const DataType type, double* value) {
+ if (xev.type != GenericEvent)
+ return false;
+
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+ if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+ return false;
+ const int sourceid = xiev->sourceid;
+ if (valuator_lookup_[sourceid].empty())
+ return false;
+
+ if (type == DT_TOUCH_TRACKING_ID) {
+ // With XInput2 MT, Tracking ID is provided in the detail field for touch
+ // events.
+ if (xiev->evtype == XI_TouchBegin ||
+ xiev->evtype == XI_TouchEnd ||
+ xiev->evtype == XI_TouchUpdate) {
+ *value = xiev->detail;
+ } else {
+ *value = 0;
+ }
+ return true;
+ }
+
+ int val_index = valuator_lookup_[sourceid][type];
+ int slot = 0;
+ if (val_index >= 0) {
+ if (XIMaskIsSet(xiev->valuators.mask, val_index)) {
+ double* valuators = xiev->valuators.values;
+ while (val_index--) {
+ if (XIMaskIsSet(xiev->valuators.mask, val_index))
+ ++valuators;
+ }
+ *value = *valuators;
+ if (IsTouchDataType(type)) {
+ if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+ last_seen_valuator_[sourceid][slot][type] = *value;
+ }
+ return true;
+ } else if (IsTouchDataType(type)) {
+ if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+ *value = last_seen_valuator_[sourceid][slot][type];
+ }
+ }
+
+ return false;
+}
+
+bool DeviceDataManagerX11::IsXIDeviceEvent(
+ const base::NativeEvent& native_event) const {
+ if (native_event->type != GenericEvent ||
+ native_event->xcookie.extension != xi_opcode_)
+ return false;
+ return xi_device_event_types_[native_event->xcookie.evtype];
+}
+
+bool DeviceDataManagerX11::IsTouchpadXInputEvent(
+ const base::NativeEvent& native_event) const {
+ if (native_event->type != GenericEvent)
+ return false;
+
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ if (xievent->sourceid >= kMaxDeviceNum)
+ return false;
+ return touchpads_[xievent->sourceid];
+}
+
+bool DeviceDataManagerX11::IsCMTDeviceEvent(
+ const base::NativeEvent& native_event) const {
+ if (native_event->type != GenericEvent)
+ return false;
+
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ if (xievent->sourceid >= kMaxDeviceNum)
+ return false;
+ return cmt_devices_[xievent->sourceid];
+}
+
+bool DeviceDataManagerX11::IsCMTGestureEvent(
+ const base::NativeEvent& native_event) const {
+ return (IsScrollEvent(native_event) ||
+ IsFlingEvent(native_event) ||
+ IsCMTMetricsEvent(native_event));
+}
+
+bool DeviceDataManagerX11::HasEventData(
+ const XIDeviceEvent* xiev, const DataType type) const {
+ const int idx = valuator_lookup_[xiev->sourceid][type];
+ return (idx >= 0) && XIMaskIsSet(xiev->valuators.mask, idx);
+}
+
+bool DeviceDataManagerX11::IsScrollEvent(
+ const base::NativeEvent& native_event) const {
+ if (!IsCMTDeviceEvent(native_event))
+ return false;
+
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return (HasEventData(xiev, DT_CMT_SCROLL_X) ||
+ HasEventData(xiev, DT_CMT_SCROLL_Y));
+}
+
+bool DeviceDataManagerX11::IsFlingEvent(
+ const base::NativeEvent& native_event) const {
+ if (!IsCMTDeviceEvent(native_event))
+ return false;
+
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return (HasEventData(xiev, DT_CMT_FLING_X) &&
+ HasEventData(xiev, DT_CMT_FLING_Y) &&
+ HasEventData(xiev, DT_CMT_FLING_STATE));
+}
+
+bool DeviceDataManagerX11::IsCMTMetricsEvent(
+ const base::NativeEvent& native_event) const {
+ if (!IsCMTDeviceEvent(native_event))
+ return false;
+
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return (HasEventData(xiev, DT_CMT_METRICS_TYPE) &&
+ HasEventData(xiev, DT_CMT_METRICS_DATA1) &&
+ HasEventData(xiev, DT_CMT_METRICS_DATA2));
+}
+
+bool DeviceDataManagerX11::HasGestureTimes(
+ const base::NativeEvent& native_event) const {
+ if (!IsCMTDeviceEvent(native_event))
+ return false;
+
+ XIDeviceEvent* xiev =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return (HasEventData(xiev, DT_CMT_START_TIME) &&
+ HasEventData(xiev, DT_CMT_END_TIME));
+}
+
+void DeviceDataManagerX11::GetScrollOffsets(
+ const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count) {
+ *x_offset = 0;
+ *y_offset = 0;
+ *x_offset_ordinal = 0;
+ *y_offset_ordinal = 0;
+ *finger_count = 2;
+
+ EventData data;
+ GetEventRawData(*native_event, &data);
+
+ if (data.find(DT_CMT_SCROLL_X) != data.end())
+ *x_offset = data[DT_CMT_SCROLL_X];
+ if (data.find(DT_CMT_SCROLL_Y) != data.end())
+ *y_offset = data[DT_CMT_SCROLL_Y];
+ if (data.find(DT_CMT_ORDINAL_X) != data.end())
+ *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];
+ if (data.find(DT_CMT_FINGER_COUNT) != data.end())
+ *finger_count = static_cast<int>(data[DT_CMT_FINGER_COUNT]);
+}
+
+void DeviceDataManagerX11::GetFlingData(
+ const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ *vx = 0;
+ *vy = 0;
+ *vx_ordinal = 0;
+ *vy_ordinal = 0;
+ *is_cancel = false;
+
+ EventData data;
+ GetEventRawData(*native_event, &data);
+
+ if (data.find(DT_CMT_FLING_X) != data.end())
+ *vx = data[DT_CMT_FLING_X];
+ if (data.find(DT_CMT_FLING_Y) != data.end())
+ *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];
+ if (data.find(DT_CMT_ORDINAL_Y) != data.end())
+ *vy_ordinal = data[DT_CMT_ORDINAL_Y];
+}
+
+void DeviceDataManagerX11::GetMetricsData(
+ const base::NativeEvent& native_event,
+ GestureMetricsType* type,
+ float* data1,
+ float* data2) {
+ *type = kGestureMetricsTypeUnknown;
+ *data1 = 0;
+ *data2 = 0;
+
+ EventData data;
+ GetEventRawData(*native_event, &data);
+
+ if (data.find(DT_CMT_METRICS_TYPE) != data.end()) {
+ int val = static_cast<int>(data[DT_CMT_METRICS_TYPE]);
+ if (val == 0)
+ *type = kGestureMetricsTypeNoisyGround;
+ else
+ *type = kGestureMetricsTypeUnknown;
+ }
+ if (data.find(DT_CMT_METRICS_DATA1) != data.end())
+ *data1 = data[DT_CMT_METRICS_DATA1];
+ if (data.find(DT_CMT_METRICS_DATA2) != data.end())
+ *data2 = data[DT_CMT_METRICS_DATA2];
+}
+
+int DeviceDataManagerX11::GetMappedButton(int button) {
+ return button > 0 && button <= button_map_count_ ? button_map_[button - 1] :
+ button;
+}
+
+void DeviceDataManagerX11::UpdateButtonMap() {
+ button_map_count_ = XGetPointerMapping(gfx::GetXDisplay(),
+ button_map_,
+ arraysize(button_map_));
+}
+
+void DeviceDataManagerX11::GetGestureTimes(
+ const base::NativeEvent& native_event,
+ double* start_time,
+ double* end_time) {
+ *start_time = 0;
+ *end_time = 0;
+
+ EventData data;
+ GetEventRawData(*native_event, &data);
+
+ if (data.find(DT_CMT_START_TIME) != data.end())
+ *start_time = data[DT_CMT_START_TIME];
+ if (data.find(DT_CMT_END_TIME) != data.end())
+ *end_time = data[DT_CMT_END_TIME];
+}
+
+bool DeviceDataManagerX11::NormalizeData(unsigned int deviceid,
+ const DataType type,
+ double* value) {
+ double max_value;
+ double min_value;
+ if (GetDataRange(deviceid, type, &min_value, &max_value)) {
+ *value = (*value - min_value) / (max_value - min_value);
+ DCHECK(*value >= 0.0 && *value <= 1.0);
+ return true;
+ }
+ return false;
+}
+
+bool DeviceDataManagerX11::GetDataRange(unsigned int deviceid,
+ const DataType type,
+ double* min,
+ double* max) {
+ if (deviceid >= static_cast<unsigned int>(kMaxDeviceNum))
+ return false;
+ if (valuator_lookup_[deviceid][type] >= 0) {
+ *min = valuator_min_[deviceid][type];
+ *max = valuator_max_[deviceid][type];
+ return true;
+ }
+ return false;
+}
+
+void DeviceDataManagerX11::SetDeviceListForTest(
+ const std::vector<unsigned int>& touchscreen,
+ const std::vector<unsigned int>& cmt_devices) {
+ for (int i = 0; i < kMaxDeviceNum; ++i) {
+ valuator_count_[i] = 0;
+ valuator_lookup_[i].clear();
+ data_type_lookup_[i].clear();
+ valuator_min_[i].clear();
+ valuator_max_[i].clear();
+ for (int j = 0; j < kMaxSlotNum; j++)
+ last_seen_valuator_[i][j].clear();
+ }
+
+ for (size_t i = 0; i < touchscreen.size(); i++) {
+ unsigned int deviceid = touchscreen[i];
+ InitializeValuatorsForTest(deviceid, kTouchDataTypeStart, kTouchDataTypeEnd,
+ 0, 1000);
+ }
+
+ cmt_devices_.reset();
+ for (size_t i = 0; i < cmt_devices.size(); ++i) {
+ unsigned int deviceid = cmt_devices[i];
+ cmt_devices_[deviceid] = true;
+ touchpads_[deviceid] = true;
+ InitializeValuatorsForTest(deviceid, kCMTDataTypeStart, kCMTDataTypeEnd,
+ -1000, 1000);
+ }
+}
+
+void DeviceDataManagerX11::SetValuatorDataForTest(XIDeviceEvent* xievent,
+ DataType type,
+ double value) {
+ int index = valuator_lookup_[xievent->deviceid][type];
+ CHECK(!XIMaskIsSet(xievent->valuators.mask, index));
+ CHECK(index >= 0 && index < valuator_count_[xievent->deviceid]);
+ XISetMask(xievent->valuators.mask, index);
+
+ double* valuators = xievent->valuators.values;
+ for (int i = 0; i < index; ++i) {
+ if (XIMaskIsSet(xievent->valuators.mask, i))
+ valuators++;
+ }
+ for (int i = DT_LAST_ENTRY - 1; i > valuators - xievent->valuators.values;
+ --i)
+ xievent->valuators.values[i] = xievent->valuators.values[i - 1];
+ *valuators = value;
+}
+
+void DeviceDataManagerX11::InitializeValuatorsForTest(int deviceid,
+ int start_valuator,
+ int end_valuator,
+ double min_value,
+ double max_value) {
+ valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
+ data_type_lookup_[deviceid].resize(DT_LAST_ENTRY, DT_LAST_ENTRY);
+ valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
+ valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
+ for (int j = 0; j < kMaxSlotNum; j++)
+ last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
+ for (int j = start_valuator; j <= end_valuator; ++j) {
+ valuator_lookup_[deviceid][j] = valuator_count_[deviceid];
+ data_type_lookup_[deviceid][valuator_count_[deviceid]] = j;
+ valuator_min_[deviceid][j] = min_value;
+ valuator_max_[deviceid][j] = max_value;
+ valuator_count_[deviceid]++;
+ }
+}
+
+bool DeviceDataManagerX11::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 DeviceDataManagerX11::SetDisabledKeyboardAllowedKeys(
+ scoped_ptr<std::set<KeyboardCode> > excepted_keys) {
+ DCHECK(!excepted_keys.get() ||
+ !blocked_keyboard_allowed_keys_.get());
+ blocked_keyboard_allowed_keys_ = excepted_keys.Pass();
+}
+
+void DeviceDataManagerX11::DisableDevice(unsigned int deviceid) {
+ blocked_devices_.set(deviceid, true);
+}
+
+void DeviceDataManagerX11::EnableDevice(unsigned int deviceid) {
+ blocked_devices_.set(deviceid, false);
+}
+
+bool DeviceDataManagerX11::IsEventBlocked(
+ const base::NativeEvent& native_event) {
+ // Only check XI2 events which have a source device id.
+ if (native_event->type != GenericEvent)
+ return false;
+
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ // Allow any key events from blocked_keyboard_allowed_keys_.
+ if (blocked_keyboard_allowed_keys_ &&
+ (xievent->evtype == XI_KeyPress || xievent->evtype == XI_KeyRelease) &&
+ blocked_keyboard_allowed_keys_->find(
+ KeyboardCodeFromXKeyEvent(native_event)) !=
+ blocked_keyboard_allowed_keys_->end()) {
+ return false;
+ }
+
+ return blocked_devices_.test(xievent->sourceid);
+}
+
+} // namespace ui
diff --git a/ui/events/x/device_data_manager_x11.h b/ui/events/x/device_data_manager_x11.h
new file mode 100644
index 0000000..6ceff06
--- /dev/null
+++ b/ui/events/x/device_data_manager_x11.h
@@ -0,0 +1,314 @@
+// 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_DEVICE_DATA_MANAGER_X11_H_
+#define UI_EVENTS_X_DEVICE_DATA_MANAGER_X11_H_
+
+// Generically-named #defines from Xlib is conflicting with symbols in GTest.
+// So many tests .cc file #undef Bool before including device_data_manager.h,
+// which makes Bool unrecognized in XInput2.h.
+#ifndef Bool
+#define Bool int
+#endif
+
+#include <X11/extensions/XInput2.h>
+
+#include <bitset>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/event_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/device_data_manager.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+// CrOS touchpad metrics gesture types
+enum GestureMetricsType {
+ kGestureMetricsTypeNoisyGround = 0,
+ kGestureMetricsTypeUnknown,
+};
+
+// A class that extracts and tracks the input events data. It currently handles
+// mouse, touchpad and touchscreen devices.
+class EVENTS_BASE_EXPORT DeviceDataManagerX11 : public DeviceDataManager {
+ public:
+ // Enumerate additional data that one might be interested on an input event,
+ // which are usually wrapped in X valuators. If you modify any of this,
+ // make sure to update the kCachedAtoms data structure in the source file
+ // and the k*Type[Start/End] constants used by IsCMTDataType and
+ // IsTouchDataType.
+ enum DataType {
+ // Define the valuators used the CrOS CMT driver. Used by mice and CrOS
+ // touchpads.
+ DT_CMT_SCROLL_X = 0, // Scroll amount on the X (horizontal) direction.
+ DT_CMT_SCROLL_Y, // Scroll amount on the Y (vertical) direction.
+ DT_CMT_ORDINAL_X, // Original (unaccelerated) value on the X direction.
+ // Can be used both for scrolls and flings.
+ DT_CMT_ORDINAL_Y, // Original (unaccelerated) value on the Y direction.
+ // Can be used both for scrolls and flings.
+ DT_CMT_START_TIME, // Gesture start time.
+ DT_CMT_END_TIME, // Gesture end time.
+ DT_CMT_FLING_X, // Fling amount on the X (horizontal) direction.
+ DT_CMT_FLING_Y, // Fling amount on the Y (vertical) direction.
+ DT_CMT_FLING_STATE, // The state of fling gesture (whether the user just
+ // start flinging or that he/she taps down).
+ DT_CMT_METRICS_TYPE, // Metrics type of the metrics gesture, which are
+ // used to wrap interesting patterns that we would
+ // like to track via the UMA system.
+ DT_CMT_METRICS_DATA1, // Complementary data 1 of the metrics gesture.
+ DT_CMT_METRICS_DATA2, // Complementary data 2 of the metrics gesture.
+ DT_CMT_FINGER_COUNT, // Finger counts in the current gesture. A same type
+ // of gesture can have very different meanings based
+ // on that (e.g. 2f scroll v.s. 3f swipe).
+
+ // End of CMT data types.
+ // Beginning of touch data types.
+
+ // Define the valuators following the Multi-touch Protocol. Used by
+ // touchscreen devices.
+ DT_TOUCH_MAJOR, // Length of the touch area.
+ DT_TOUCH_MINOR, // Width of the touch area.
+ DT_TOUCH_ORIENTATION, // Angle between the X-axis and the major axis of the
+ // touch area.
+ DT_TOUCH_PRESSURE, // Pressure of the touch contact.
+
+ DT_TOUCH_POSITION_X, // Touch X position.
+ DT_TOUCH_POSITION_Y, // Touch Y position.
+
+ // NOTE for XInput MT: 'Tracking ID' is provided in every touch event to
+ // track individual touch. 'Tracking ID' is an unsigned 32-bit value and
+ // is increased for each new touch. It will wrap back to 0 when reaching
+ // the numerical limit.
+ DT_TOUCH_TRACKING_ID, // ID of the touch point.
+
+ // Kernel timestamp from touch screen (if available).
+ DT_TOUCH_RAW_TIMESTAMP,
+
+ // End of touch data types.
+
+ DT_LAST_ENTRY // This must come last.
+ };
+
+ // Data struct to store extracted data from an input event.
+ typedef std::map<int, double> EventData;
+
+ static void CreateInstance();
+
+ // We use int because enums can be casted to ints but not vice versa.
+ static bool IsCMTDataType(const int type);
+ static bool IsTouchDataType(const int type);
+
+ // Returns the DeviceDataManagerX11 singleton.
+ static DeviceDataManagerX11* GetInstance();
+
+ // Returns if XInput2 is available on the system.
+ bool IsXInput2Available() const;
+
+ // Updates the list of devices.
+ void UpdateDeviceList(Display* display);
+
+ // For multitouch events we use slot number to distinguish touches from
+ // different fingers. This function returns true if the associated slot
+ // for |xiev| can be found and it is saved in |slot|, returns false if
+ // no slot can be found.
+ bool GetSlotNumber(const XIDeviceEvent* xiev, int* slot);
+
+ // Get all event data in one pass. We extract only data types that we know
+ // about (defined in enum DataType). The data is not processed (e.g. not
+ // filled in by cached values) as in GetEventData.
+ void GetEventRawData(const XEvent& xev, EventData* data);
+
+ // Get a datum of the specified type. Return true and the value
+ // is updated if the data is found, false and value unchanged if the data is
+ // not found. In the case of MT-B/XI2.2, the value can come from a previously
+ // cached one (see the comment above last_seen_valuator_).
+ bool GetEventData(const XEvent& xev, const DataType type, double* value);
+
+ // Check if the event is an XI input event in the strict sense
+ // (i.e. XIDeviceEvent). This rules out things like hierarchy changes,
+ /// device changes, property changes and so on.
+ bool IsXIDeviceEvent(const base::NativeEvent& native_event) const;
+
+ // Check if the event comes from touchpad devices.
+ bool IsTouchpadXInputEvent(const base::NativeEvent& native_event) const;
+
+ // Check if the event comes from devices running CMT driver or using
+ // CMT valuators (e.g. mouses). Note that doesn't necessarily mean the event
+ // is a CMT event (e.g. it could be a mouse pointer move).
+ bool IsCMTDeviceEvent(const base::NativeEvent& native_event) const;
+
+ // Check if the event is one of the CMT gesture events (scroll, fling,
+ // metrics etc.).
+ bool IsCMTGestureEvent(const base::NativeEvent& native_event) const;
+
+ // Returns true if the event is of the specific type, false if not.
+ bool IsScrollEvent(const base::NativeEvent& native_event) const;
+ bool IsFlingEvent(const base::NativeEvent& native_event) const;
+ bool IsCMTMetricsEvent(const base::NativeEvent& native_event) const;
+
+ // Returns true if the event has CMT start/end timestamps.
+ bool HasGestureTimes(const base::NativeEvent& native_event) const;
+
+ // Extract data from a scroll event (a motion event with the necessary
+ // valuators). User must first verify the event type with IsScrollEvent.
+ // Pointers shouldn't be NULL.
+ void GetScrollOffsets(const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count);
+
+ // Extract data from a fling event. User must first verify the event type
+ // with IsFlingEvent. Pointers shouldn't be NULL.
+ void GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel);
+
+ // Extract data from a CrOS metrics gesture event. User must first verify
+ // the event type with IsCMTMetricsEvent. Pointers shouldn't be NULL.
+ void GetMetricsData(const base::NativeEvent& native_event,
+ GestureMetricsType* type,
+ float* data1,
+ float* data2);
+
+ // Returns the mapped button.
+ int GetMappedButton(int button);
+
+ // Updates button mapping. This is usually called when a MappingNotify event
+ // is received.
+ void UpdateButtonMap();
+
+ // Extract the start/end timestamps from CMT events. User must first verify
+ // the event with HasGestureTimes. Pointers shouldn't be NULL.
+ void GetGestureTimes(const base::NativeEvent& native_event,
+ double* start_time,
+ double* end_time);
+
+ // Normalize the data value on deviceid to fall into [0, 1].
+ // *value = (*value - min_value_of_tp) / (max_value_of_tp - min_value_of_tp)
+ // Returns true and sets the normalized value in|value| if normalization is
+ // successful. Returns false and |value| is unchanged otherwise.
+ bool NormalizeData(unsigned int deviceid,
+ const DataType type,
+ double* value);
+
+ // Extract the range of the data type. Return true if the range is available
+ // and written into min & max, false if the range is not available.
+ bool GetDataRange(unsigned int deviceid,
+ const DataType type,
+ double* min,
+ double* max);
+
+ // Sets up relevant valuator informations for device ids in the device lists.
+ // This function is only for test purpose. It does not query the X server for
+ // the actual device info, but rather inits the relevant valuator structures
+ // to have safe default values for testing.
+ void SetDeviceListForTest(const std::vector<unsigned int>& touchscreen,
+ const std::vector<unsigned int>& cmt_devices);
+
+ void SetValuatorDataForTest(XIDeviceEvent* xievent,
+ DataType type,
+ double value);
+
+ bool TouchEventNeedsCalibrate(int touch_device_id) const;
+
+ // Sets the keys which are still allowed on a disabled keyboard device.
+ void SetDisabledKeyboardAllowedKeys(
+ scoped_ptr<std::set<KeyboardCode> > excepted_keys);
+
+ // Disables and enables events from devices by device id.
+ void DisableDevice(unsigned int deviceid);
+ void EnableDevice(unsigned int deviceid);
+
+ // Returns true if |native_event| should be blocked.
+ bool IsEventBlocked(const base::NativeEvent& native_event);
+
+ private:
+ DeviceDataManagerX11();
+ virtual ~DeviceDataManagerX11();
+
+ // Initialize the XInput related system information.
+ bool InitializeXInputInternal();
+
+ // Check if an XI event contains data of the specified type.
+ bool HasEventData(const XIDeviceEvent* xiev, const DataType type) const;
+
+ void InitializeValuatorsForTest(int deviceid,
+ int start_valuator,
+ int end_valuator,
+ double min_value,
+ double max_value);
+
+ static const int kMaxXIEventType = XI_LASTEVENT + 1;
+ static const int kMaxSlotNum = 10;
+
+ // Major opcode for the XInput extension. Used to identify XInput events.
+ int xi_opcode_;
+
+ // A quick lookup table for determining if the XI event is an XIDeviceEvent.
+ std::bitset<kMaxXIEventType> xi_device_event_types_;
+
+ // A quick lookup table for determining if events from the pointer device
+ // should be processed.
+ std::bitset<kMaxDeviceNum> cmt_devices_;
+ std::bitset<kMaxDeviceNum> touchpads_;
+
+ // A quick lookup table for determining if events from the XI device
+ // should be blocked.
+ std::bitset<kMaxDeviceNum> blocked_devices_;
+
+ // The set of keys allowed while the keyboard is blocked.
+ scoped_ptr<std::set<KeyboardCode> > blocked_keyboard_allowed_keys_;
+
+ // Number of valuators on the specific device.
+ int valuator_count_[kMaxDeviceNum];
+
+ // Index table to find the valuator for DataType on the specific device
+ // by valuator_lookup_[device_id][data_type].
+ std::vector<int> valuator_lookup_[kMaxDeviceNum];
+
+ // Index table to find the DataType for valuator on the specific device
+ // by data_type_lookup_[device_id][valuator].
+ std::vector<int> data_type_lookup_[kMaxDeviceNum];
+
+ // Index table to find the min & max value of the Valuator on a specific
+ // device.
+ std::vector<double> valuator_min_[kMaxDeviceNum];
+ std::vector<double> valuator_max_[kMaxDeviceNum];
+
+ // Table to keep track of the last seen value for the specified valuator for
+ // a specified slot of a device. Defaults to 0 if the valuator for that slot
+ // was not specified in an earlier event. With MT-B/XI2.2, valuators in an
+ // XEvent are not reported if the values haven't changed from the previous
+ // event. So it is necessary to remember these valuators so that chrome
+ // doesn't think X/device doesn't know about the valuators. We currently
+ // use this only on touchscreen devices.
+ std::vector<double> last_seen_valuator_[kMaxDeviceNum][kMaxSlotNum];
+
+ // X11 atoms cache.
+ X11AtomCache atom_cache_;
+
+ unsigned char button_map_[256];
+ int button_map_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceDataManagerX11);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_X_DEVICE_DATA_MANAGER_X11_H_
diff --git a/ui/events/x/device_list_cache_x.cc b/ui/events/x/device_list_cache_x.cc
new file mode 100644
index 0000000..df930d1
--- /dev/null
+++ b/ui/events/x/device_list_cache_x.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/x/device_list_cache_x.h"
+
+#include <algorithm>
+
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/events/x/device_data_manager_x11.h"
+
+namespace {
+
+bool IsXI2Available() {
+#if defined(USE_AURA)
+ return ui::DeviceDataManagerX11::GetInstance()->IsXInput2Available();
+#else
+ return false;
+#endif
+}
+
+}
+
+namespace ui {
+
+DeviceListCacheX::DeviceListCacheX() {
+}
+
+DeviceListCacheX::~DeviceListCacheX() {
+ std::map<Display*, XDeviceList>::iterator xp;
+ for (xp = x_dev_list_map_.begin(); xp != x_dev_list_map_.end(); xp++) {
+ if (xp->second.devices)
+ XFreeDeviceList(xp->second.devices);
+ }
+ std::map<Display*, XIDeviceList>::iterator xip;
+ for (xip = xi_dev_list_map_.begin(); xip != xi_dev_list_map_.end(); xip++) {
+ if (xip->second.devices)
+ XIFreeDeviceInfo(xip->second.devices);
+ }
+}
+
+DeviceListCacheX* DeviceListCacheX::GetInstance() {
+ return Singleton<DeviceListCacheX>::get();
+}
+
+void DeviceListCacheX::UpdateDeviceList(Display* display) {
+ XDeviceList& new_x_dev_list = x_dev_list_map_[display];
+ if (new_x_dev_list.devices)
+ XFreeDeviceList(new_x_dev_list.devices);
+ new_x_dev_list.devices = XListInputDevices(display, &new_x_dev_list.count);
+
+ XIDeviceList& new_xi_dev_list = xi_dev_list_map_[display];
+ if (new_xi_dev_list.devices)
+ XIFreeDeviceInfo(new_xi_dev_list.devices);
+ new_xi_dev_list.devices = IsXI2Available() ?
+ XIQueryDevice(display, XIAllDevices, &new_xi_dev_list.count) : NULL;
+}
+
+const XDeviceList& DeviceListCacheX::GetXDeviceList(Display* display) {
+ XDeviceList& x_dev_list = x_dev_list_map_[display];
+ // Note that the function can be called before any update has taken place.
+ if (!x_dev_list.devices && !x_dev_list.count)
+ x_dev_list.devices = XListInputDevices(display, &x_dev_list.count);
+ return x_dev_list;
+}
+
+const XIDeviceList& DeviceListCacheX::GetXI2DeviceList(Display* display) {
+ XIDeviceList& xi_dev_list = xi_dev_list_map_[display];
+ if (!xi_dev_list.devices && !xi_dev_list.count) {
+ xi_dev_list.devices = XIQueryDevice(display, XIAllDevices,
+ &xi_dev_list.count);
+ }
+ return xi_dev_list;
+}
+
+} // namespace ui
+
diff --git a/ui/events/x/device_list_cache_x.h b/ui/events/x/device_list_cache_x.h
new file mode 100644
index 0000000..5456a43
--- /dev/null
+++ b/ui/events/x/device_list_cache_x.h
@@ -0,0 +1,71 @@
+// 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_X_DEVICE_LIST_CACHE_X_H_
+#define UI_EVENTS_X_DEVICE_LIST_CACHE_X_H_
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+typedef struct _XDisplay Display;
+
+template <typename T>
+struct DeviceList {
+ DeviceList() : devices(NULL), count(0) {
+ }
+ T& operator[] (int x) {
+ return devices[x];
+ }
+ const T& operator[](int x) const { return devices[x]; }
+ T* devices;
+ int count;
+};
+
+typedef struct DeviceList<XDeviceInfo> XDeviceList;
+typedef struct DeviceList<XIDeviceInfo> XIDeviceList;
+
+namespace ui {
+
+// A class to cache the current XInput device list. This minimized the
+// round-trip time to the X server whenever such a device list is needed. The
+// update function will be called on each incoming XI_HierarchyChanged event.
+class EVENTS_BASE_EXPORT DeviceListCacheX {
+ public:
+ static DeviceListCacheX* GetInstance();
+
+ void UpdateDeviceList(Display* display);
+
+ // Returns the list of devices associated with |display|. Uses the old X11
+ // protocol to get the list of the devices.
+ const XDeviceList& GetXDeviceList(Display* display);
+
+ // Returns the list of devices associated with |display|. Uses the newer
+ // XINPUT2 protocol to get the list of devices. Before making this call, make
+ // sure that XInput2 support is available (e.g. by calling
+ // IsXInput2Available()).
+ const XIDeviceList& GetXI2DeviceList(Display* display);
+
+ private:
+ friend struct DefaultSingletonTraits<DeviceListCacheX>;
+
+ DeviceListCacheX();
+ ~DeviceListCacheX();
+
+ std::map<Display*, XDeviceList> x_dev_list_map_;
+ std::map<Display*, XIDeviceList> xi_dev_list_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeviceListCacheX);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_X_DEVICE_LIST_CACHE_X_H_
+
diff --git a/ui/events/x/events_x.cc b/ui/events/x/events_x.cc
new file mode 100644
index 0000000..371c8f5
--- /dev/null
+++ b/ui/events/x/events_x.cc
@@ -0,0 +1,929 @@
+// 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/event_constants.h"
+
+#include <cmath>
+#include <string.h>
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/events/x/device_data_manager_x11.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/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace {
+
+// Scroll amount for each wheelscroll event. 53 is also the value used for GTK+.
+const int kWheelScrollAmount = 53;
+
+const int kMinWheelButton = 4;
+const int kMaxWheelButton = 7;
+
+// A class to track current modifier state on master device. Only track ctrl,
+// alt, shift and caps lock keys currently. The tracked state can then be used
+// by floating device.
+class XModifierStateWatcher{
+ public:
+ static XModifierStateWatcher* GetInstance() {
+ return Singleton<XModifierStateWatcher>::get();
+ }
+
+ int StateFromKeyboardCode(ui::KeyboardCode keyboard_code) {
+ switch (keyboard_code) {
+ case ui::VKEY_CONTROL:
+ return ControlMask;
+ case ui::VKEY_SHIFT:
+ return ShiftMask;
+ case ui::VKEY_MENU:
+ return Mod1Mask;
+ case ui::VKEY_CAPITAL:
+ return LockMask;
+ default:
+ return 0;
+ }
+ }
+
+ void UpdateStateFromXEvent(const base::NativeEvent& native_event) {
+ ui::KeyboardCode keyboard_code = ui::KeyboardCodeFromNative(native_event);
+ unsigned int mask = StateFromKeyboardCode(keyboard_code);
+ // Floating device can't access the modifer state from master device.
+ // We need to track the states of modifier keys in a singleton for
+ // floating devices such as touch screen. Issue 106426 is one example
+ // of why we need the modifier states for floating device.
+ switch (native_event->type) {
+ case KeyPress:
+ state_ = native_event->xkey.state | mask;
+ break;
+ case KeyRelease:
+ state_ = native_event->xkey.state & ~mask;
+ break;
+ case GenericEvent: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ switch (xievent->evtype) {
+ case XI_KeyPress:
+ state_ = xievent->mods.effective |= mask;
+ break;
+ case XI_KeyRelease:
+ state_ = xievent->mods.effective &= ~mask;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ // Returns the current modifer state in master device. It only contains the
+ // state of ctrl, shift, alt and caps lock keys.
+ unsigned int state() { return state_; }
+
+ private:
+ friend struct DefaultSingletonTraits<XModifierStateWatcher>;
+
+ XModifierStateWatcher() : state_(0) { }
+
+ unsigned int state_;
+
+ DISALLOW_COPY_AND_ASSIGN(XModifierStateWatcher);
+};
+
+#if defined(USE_XI2_MT)
+// Detects if a touch event is a driver-generated 'special event'.
+// A 'special event' is a touch event with maximum radius and pressure at
+// location (0, 0).
+// This needs to be done in a cleaner way: http://crbug.com/169256
+bool TouchEventIsGeneratedHack(const base::NativeEvent& native_event) {
+ XIDeviceEvent* event =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ CHECK(event->evtype == XI_TouchBegin ||
+ event->evtype == XI_TouchUpdate ||
+ event->evtype == XI_TouchEnd);
+
+ // Force is normalized to [0, 1].
+ if (ui::GetTouchForce(native_event) < 1.0f)
+ return false;
+
+ if (ui::EventLocationFromNative(native_event) != gfx::Point())
+ return false;
+
+ // Radius is in pixels, and the valuator is the diameter in pixels.
+ double radius = ui::GetTouchRadiusX(native_event), min, max;
+ unsigned int deviceid =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
+ if (!ui::DeviceDataManagerX11::GetInstance()->GetDataRange(
+ deviceid, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, &min, &max)) {
+ return false;
+ }
+
+ return radius * 2 == max;
+}
+#endif
+
+int GetEventFlagsFromXState(unsigned int state) {
+ int flags = 0;
+ if (state & ControlMask)
+ flags |= ui::EF_CONTROL_DOWN;
+ if (state & ShiftMask)
+ flags |= ui::EF_SHIFT_DOWN;
+ if (state & Mod1Mask)
+ 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)
+ flags |= ui::EF_LEFT_MOUSE_BUTTON;
+ if (state & Button2Mask)
+ flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
+ if (state & Button3Mask)
+ flags |= ui::EF_RIGHT_MOUSE_BUTTON;
+ 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) |
+ (xevent->xkey.send_event ? ui::EF_FINAL : 0) |
+ (IsKeypadKey(XLookupKeysym(&xevent->xkey, 0)) ? ui::EF_NUMPAD_KEY : 0) |
+ (IsFunctionKey(XLookupKeysym(&xevent->xkey, 0)) ?
+ ui::EF_FUNCTION_KEY : 0) |
+ ime_fabricated_flag;
+}
+
+int GetEventFlagsFromXGenericEvent(XEvent* xevent) {
+ DCHECK(xevent->type == GenericEvent);
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xevent->xcookie.data);
+ DCHECK((xievent->evtype == XI_KeyPress) ||
+ (xievent->evtype == XI_KeyRelease));
+ return GetEventFlagsFromXState(xievent->mods.effective) |
+ (xevent->xkey.send_event ? ui::EF_FINAL : 0) |
+ (IsKeypadKey(
+ XkbKeycodeToKeysym(xievent->display, xievent->detail, 0, 0))
+ ? ui::EF_NUMPAD_KEY
+ : 0);
+}
+
+// Get the event flag for the button in XButtonEvent. During a ButtonPress
+// event, |state| in XButtonEvent does not include the button that has just been
+// pressed. Instead |state| contains flags for the buttons (if any) that had
+// already been pressed before the current button, and |button| stores the most
+// current pressed button. So, if you press down left mouse button, and while
+// pressing it down, press down the right mouse button, then for the latter
+// event, |state| would have Button1Mask set but not Button3Mask, and |button|
+// would be 3.
+int GetEventFlagsForButton(int button) {
+ switch (button) {
+ case 1:
+ return ui::EF_LEFT_MOUSE_BUTTON;
+ case 2:
+ return ui::EF_MIDDLE_MOUSE_BUTTON;
+ case 3:
+ return ui::EF_RIGHT_MOUSE_BUTTON;
+ default:
+ return 0;
+ }
+}
+
+int GetButtonMaskForX2Event(XIDeviceEvent* xievent) {
+ int buttonflags = 0;
+ for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) {
+ if (XIMaskIsSet(xievent->buttons.mask, i)) {
+ int button = (xievent->sourceid == xievent->deviceid) ?
+ ui::DeviceDataManagerX11::GetInstance()->GetMappedButton(i) : i;
+ buttonflags |= GetEventFlagsForButton(button);
+ }
+ }
+ return buttonflags;
+}
+
+ui::EventType GetTouchEventType(const base::NativeEvent& native_event) {
+ XIDeviceEvent* event =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+#if defined(USE_XI2_MT)
+ switch(event->evtype) {
+ case XI_TouchBegin:
+ return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
+ ui::ET_TOUCH_PRESSED;
+ case XI_TouchUpdate:
+ return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
+ ui::ET_TOUCH_MOVED;
+ case XI_TouchEnd:
+ return TouchEventIsGeneratedHack(native_event) ? ui::ET_TOUCH_CANCELLED :
+ ui::ET_TOUCH_RELEASED;
+ }
+#endif // defined(USE_XI2_MT)
+
+ DCHECK(ui::TouchFactory::GetInstance()->IsTouchDevice(event->sourceid));
+ switch (event->evtype) {
+ case XI_ButtonPress:
+ return ui::ET_TOUCH_PRESSED;
+ case XI_ButtonRelease:
+ return ui::ET_TOUCH_RELEASED;
+ case XI_Motion:
+ // Should not convert any emulated Motion event from touch device to
+ // touch event.
+ if (!(event->flags & XIPointerEmulated) &&
+ GetButtonMaskForX2Event(event))
+ return ui::ET_TOUCH_MOVED;
+ return ui::ET_UNKNOWN;
+ default:
+ NOTREACHED();
+ }
+ return ui::ET_UNKNOWN;
+}
+
+double GetTouchParamFromXEvent(XEvent* xev,
+ ui::DeviceDataManagerX11::DataType val,
+ double default_value) {
+ ui::DeviceDataManagerX11::GetInstance()->GetEventData(
+ *xev, val, &default_value);
+ return default_value;
+}
+
+void ScaleTouchRadius(XEvent* xev, double* radius) {
+ DCHECK_EQ(GenericEvent, xev->type);
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+ ui::DeviceDataManagerX11::GetInstance()->ApplyTouchRadiusScale(
+ xiev->sourceid, radius);
+}
+
+unsigned int UpdateX11EventFlags(int ui_flags, unsigned int old_x_flags) {
+ static struct {
+ int ui;
+ int x;
+ } flags[] = {
+ {ui::EF_CONTROL_DOWN, ControlMask},
+ {ui::EF_SHIFT_DOWN, ShiftMask},
+ {ui::EF_ALT_DOWN, Mod1Mask},
+ {ui::EF_CAPS_LOCK_DOWN, LockMask},
+ {ui::EF_ALTGR_DOWN, Mod5Mask},
+ {ui::EF_COMMAND_DOWN, Mod4Mask},
+ {ui::EF_MOD3_DOWN, Mod3Mask},
+ {ui::EF_NUMPAD_KEY, Mod2Mask},
+ {ui::EF_LEFT_MOUSE_BUTTON, Button1Mask},
+ {ui::EF_MIDDLE_MOUSE_BUTTON, Button2Mask},
+ {ui::EF_RIGHT_MOUSE_BUTTON, Button3Mask},
+ };
+ unsigned int new_x_flags = old_x_flags;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(flags); ++i) {
+ if (ui_flags & flags[i].ui)
+ new_x_flags |= flags[i].x;
+ else
+ new_x_flags &= ~flags[i].x;
+ }
+ return new_x_flags;
+}
+
+unsigned int UpdateX11EventButton(int ui_flag, unsigned int old_x_button) {
+ switch (ui_flag) {
+ case ui::EF_LEFT_MOUSE_BUTTON:
+ return Button1;
+ case ui::EF_MIDDLE_MOUSE_BUTTON:
+ return Button2;
+ case ui::EF_RIGHT_MOUSE_BUTTON:
+ return Button3;
+ default:
+ return old_x_button;
+ }
+ NOTREACHED();
+}
+
+bool GetGestureTimes(const base::NativeEvent& native_event,
+ double* start_time,
+ double* end_time) {
+ if (!ui::DeviceDataManagerX11::GetInstance()->HasGestureTimes(native_event))
+ return false;
+
+ double start_time_, end_time_;
+ if (!start_time)
+ start_time = &start_time_;
+ if (!end_time)
+ end_time = &end_time_;
+
+ ui::DeviceDataManagerX11::GetInstance()->GetGestureTimes(
+ native_event, start_time, end_time);
+ return true;
+}
+
+} // namespace
+
+namespace ui {
+
+void UpdateDeviceList() {
+ XDisplay* display = gfx::GetXDisplay();
+ DeviceListCacheX::GetInstance()->UpdateDeviceList(display);
+ TouchFactory::GetInstance()->UpdateDeviceList(display);
+ DeviceDataManagerX11::GetInstance()->UpdateDeviceList(display);
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+ // Allow the DeviceDataManager to block the event. If blocked return
+ // ET_UNKNOWN as the type so this event will not be further processed.
+ // NOTE: During some events unittests there is no device data manager.
+ if (DeviceDataManager::HasInstance() &&
+ static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance())->
+ IsEventBlocked(native_event)) {
+ return ET_UNKNOWN;
+ }
+
+ switch (native_event->type) {
+ case KeyPress:
+ return ET_KEY_PRESSED;
+ case KeyRelease:
+ return ET_KEY_RELEASED;
+ case ButtonPress:
+ if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
+ static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
+ return ET_MOUSEWHEEL;
+ return ET_MOUSE_PRESSED;
+ case ButtonRelease:
+ // Drop wheel events; we should've already scrolled on the press.
+ if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
+ static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
+ return ET_UNKNOWN;
+ return ET_MOUSE_RELEASED;
+ case MotionNotify:
+ if (native_event->xmotion.state &
+ (Button1Mask | Button2Mask | Button3Mask))
+ return ET_MOUSE_DRAGGED;
+ return ET_MOUSE_MOVED;
+ case EnterNotify:
+ // The standard on Windows is to send a MouseMove event when the mouse
+ // first enters a window instead of sending a special mouse enter event.
+ // To be consistent we follow the same style.
+ return ET_MOUSE_MOVED;
+ case LeaveNotify:
+ return ET_MOUSE_EXITED;
+ case GenericEvent: {
+ TouchFactory* factory = TouchFactory::GetInstance();
+ if (!factory->ShouldProcessXI2Event(native_event))
+ return ET_UNKNOWN;
+
+ 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)
+ return ET_MOUSEWHEEL;
+ return ET_MOUSE_PRESSED;
+ }
+ case XI_ButtonRelease: {
+ int button = EventButtonFromNative(native_event);
+ // Drop wheel events; we should've already scrolled on the press.
+ if (button >= kMinWheelButton && button <= kMaxWheelButton)
+ return ET_UNKNOWN;
+ return ET_MOUSE_RELEASED;
+ }
+ case XI_Motion: {
+ bool is_cancel;
+ DeviceDataManagerX11* devices = DeviceDataManagerX11::GetInstance();
+ if (GetFlingData(native_event, NULL, NULL, NULL, NULL, &is_cancel))
+ return is_cancel ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START;
+ if (devices->IsScrollEvent(native_event)) {
+ return devices->IsTouchpadXInputEvent(native_event) ? ET_SCROLL
+ : ET_MOUSEWHEEL;
+ }
+ if (devices->IsCMTMetricsEvent(native_event))
+ return ET_UMA_DATA;
+ if (GetButtonMaskForX2Event(xievent))
+ return ET_MOUSE_DRAGGED;
+ return ET_MOUSE_MOVED;
+ }
+ case XI_KeyPress:
+ return ET_KEY_PRESSED;
+ case XI_KeyRelease:
+ return ET_KEY_RELEASED;
+ }
+ }
+ default:
+ break;
+ }
+ return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+ switch (native_event->type) {
+ case KeyPress:
+ case KeyRelease: {
+ XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(native_event);
+ return GetEventFlagsFromXKeyEvent(native_event);
+ }
+ case ButtonPress:
+ case ButtonRelease: {
+ int flags = GetEventFlagsFromXState(native_event->xbutton.state);
+ const EventType type = EventTypeFromNative(native_event);
+ if (type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED)
+ 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: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+
+ switch (xievent->evtype) {
+#if defined(USE_XI2_MT)
+ case XI_TouchBegin:
+ case XI_TouchUpdate:
+ case XI_TouchEnd:
+ return GetButtonMaskForX2Event(xievent) |
+ GetEventFlagsFromXState(xievent->mods.effective) |
+ GetEventFlagsFromXState(
+ XModifierStateWatcher::GetInstance()->state());
+ break;
+#endif
+ case XI_ButtonPress:
+ case XI_ButtonRelease: {
+ const bool touch =
+ TouchFactory::GetInstance()->IsTouchDevice(xievent->sourceid);
+ int flags = GetButtonMaskForX2Event(xievent) |
+ GetEventFlagsFromXState(xievent->mods.effective);
+ if (touch) {
+ flags |= GetEventFlagsFromXState(
+ XModifierStateWatcher::GetInstance()->state());
+ }
+
+ const EventType type = EventTypeFromNative(native_event);
+ int button = EventButtonFromNative(native_event);
+ if ((type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) && !touch)
+ flags |= GetEventFlagsForButton(button);
+ return flags;
+ }
+ case XI_Motion:
+ return GetButtonMaskForX2Event(xievent) |
+ GetEventFlagsFromXState(xievent->mods.effective);
+ case XI_KeyPress:
+ case XI_KeyRelease: {
+ XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(
+ native_event);
+ return GetEventFlagsFromXGenericEvent(native_event);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+ switch(native_event->type) {
+ case KeyPress:
+ case KeyRelease:
+ return base::TimeDelta::FromMilliseconds(native_event->xkey.time);
+ case ButtonPress:
+ case ButtonRelease:
+ return base::TimeDelta::FromMilliseconds(native_event->xbutton.time);
+ break;
+ case MotionNotify:
+ return base::TimeDelta::FromMilliseconds(native_event->xmotion.time);
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ return base::TimeDelta::FromMilliseconds(native_event->xcrossing.time);
+ break;
+ case GenericEvent: {
+ double start, end;
+ double touch_timestamp;
+ if (GetGestureTimes(native_event, &start, &end)) {
+ // If the driver supports gesture times, use them.
+ return base::TimeDelta::FromMicroseconds(end * 1000000);
+ } else if (DeviceDataManagerX11::GetInstance()->GetEventData(
+ *native_event,
+ DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
+ &touch_timestamp)) {
+ return base::TimeDelta::FromMicroseconds(touch_timestamp * 1000000);
+ } else {
+ XIDeviceEvent* xide =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return base::TimeDelta::FromMilliseconds(xide->time);
+ }
+ break;
+ }
+ }
+ NOTREACHED();
+ return base::TimeDelta();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+ switch (native_event->type) {
+ case EnterNotify:
+ case LeaveNotify:
+ return gfx::Point(native_event->xcrossing.x, native_event->xcrossing.y);
+ case ButtonPress:
+ case ButtonRelease:
+ return gfx::Point(native_event->xbutton.x, native_event->xbutton.y);
+ case MotionNotify:
+ return gfx::Point(native_event->xmotion.x, native_event->xmotion.y);
+ case GenericEvent: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ float x = xievent->event_x;
+ float y = xievent->event_y;
+#if defined(OS_CHROMEOS)
+ switch (xievent->evtype) {
+ case XI_TouchBegin:
+ case XI_TouchUpdate:
+ case XI_TouchEnd:
+ ui::DeviceDataManagerX11::GetInstance()->ApplyTouchTransformer(
+ xievent->deviceid, &x, &y);
+ break;
+ default:
+ break;
+ }
+#endif // defined(OS_CHROMEOS)
+ return gfx::Point(static_cast<int>(x), static_cast<int>(y));
+ }
+ }
+ return gfx::Point();
+}
+
+gfx::Point EventSystemLocationFromNative(
+ const base::NativeEvent& native_event) {
+ switch (native_event->type) {
+ case EnterNotify:
+ case LeaveNotify: {
+ return gfx::Point(native_event->xcrossing.x_root,
+ native_event->xcrossing.y_root);
+ }
+ case ButtonPress:
+ case ButtonRelease: {
+ return gfx::Point(native_event->xbutton.x_root,
+ native_event->xbutton.y_root);
+ }
+ case MotionNotify: {
+ return gfx::Point(native_event->xmotion.x_root,
+ native_event->xmotion.y_root);
+ }
+ case GenericEvent: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ return gfx::Point(xievent->root_x, xievent->root_y);
+ }
+ }
+
+ return gfx::Point();
+}
+
+int EventButtonFromNative(const base::NativeEvent& native_event) {
+ CHECK_EQ(GenericEvent, native_event->type);
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ int button = xievent->detail;
+
+ return (xievent->sourceid == xievent->deviceid) ?
+ DeviceDataManagerX11::GetInstance()->GetMappedButton(button) : button;
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+ return KeyboardCodeFromXKeyEvent(native_event);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+ return CodeFromXEvent(native_event);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ XKeyEvent* xkey = NULL;
+ XEvent xkey_from_xi2;
+ switch (native_event->type) {
+ case KeyPress:
+ case KeyRelease:
+ xkey = &native_event->xkey;
+ break;
+ case GenericEvent: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ switch (xievent->evtype) {
+ case XI_KeyPress:
+ case XI_KeyRelease:
+ // Build an XKeyEvent corresponding to the XI2 event,
+ // so that we can call XLookupString on it.
+ InitXKeyEventFromXIDeviceEvent(*native_event, &xkey_from_xi2);
+ xkey = &xkey_from_xi2.xkey;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+ KeySym keysym = XK_VoidSymbol;
+ if (xkey)
+ XLookupString(xkey, NULL, 0, &keysym, NULL);
+ return keysym;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+ return false;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+ const base::NativeEvent& native_event) {
+ switch (native_event->type) {
+ case ButtonPress:
+ case ButtonRelease:
+ return GetEventFlagsFromXState(native_event->xbutton.state);
+ case GenericEvent: {
+ XIDeviceEvent* xievent =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ switch (xievent->evtype) {
+ case XI_ButtonPress:
+ case XI_ButtonRelease:
+ return GetEventFlagsForButton(EventButtonFromNative(native_event));
+ default:
+ break;
+ }
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+ float x_offset, y_offset;
+ if (GetScrollOffsets(
+ native_event, &x_offset, &y_offset, NULL, NULL, NULL)) {
+ return gfx::Vector2d(static_cast<int>(x_offset),
+ static_cast<int>(y_offset));
+ }
+
+ int button = native_event->type == GenericEvent ?
+ EventButtonFromNative(native_event) : native_event->xbutton.button;
+
+ switch (button) {
+ case 4:
+ return gfx::Vector2d(0, kWheelScrollAmount);
+ case 5:
+ return gfx::Vector2d(0, -kWheelScrollAmount);
+ case 6:
+ return gfx::Vector2d(kWheelScrollAmount, 0);
+ case 7:
+ return gfx::Vector2d(-kWheelScrollAmount, 0);
+ default:
+ return gfx::Vector2d();
+ }
+}
+
+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 IncrementTouchIdRefCount(const base::NativeEvent& xev) {
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ double tracking_id;
+ if (!manager->GetEventData(
+ *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+ return;
+ }
+
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ factory->AcquireSlotForTrackingID(tracking_id);
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+ ui::EventType type = ui::EventTypeFromNative(xev);
+ if (type == ui::ET_TOUCH_CANCELLED ||
+ type == ui::ET_TOUCH_RELEASED) {
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ double tracking_id;
+ if (manager->GetEventData(
+ *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+ factory->ReleaseSlotForTrackingID(tracking_id);
+ }
+ }
+}
+
+int GetTouchId(const base::NativeEvent& xev) {
+ double slot = 0;
+ ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+ double tracking_id;
+ if (!manager->GetEventData(
+ *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+ LOG(ERROR) << "Could not get the tracking ID for the event. Using 0.";
+ } else {
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ slot = factory->GetSlotForTrackingID(tracking_id);
+ }
+ return slot;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+ double radius = GetTouchParamFromXEvent(native_event,
+ ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, 0.0) / 2.0;
+ ScaleTouchRadius(native_event, &radius);
+ return radius;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+ double radius = GetTouchParamFromXEvent(native_event,
+ ui::DeviceDataManagerX11::DT_TOUCH_MINOR, 0.0) / 2.0;
+ ScaleTouchRadius(native_event, &radius);
+ return radius;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+ return GetTouchParamFromXEvent(native_event,
+ ui::DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.0) / 2.0;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+ double force = 0.0;
+ force = GetTouchParamFromXEvent(native_event,
+ ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, 0.0);
+ unsigned int deviceid =
+ static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
+ // Force is normalized to fall into [0, 1]
+ if (!ui::DeviceDataManagerX11::GetInstance()->NormalizeData(
+ deviceid, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, &force))
+ force = 0.0;
+ return force;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+ float* x_offset,
+ float* y_offset,
+ float* x_offset_ordinal,
+ float* y_offset_ordinal,
+ int* finger_count) {
+ if (!DeviceDataManagerX11::GetInstance()->IsScrollEvent(native_event))
+ return false;
+
+ // Temp values to prevent passing NULLs to DeviceDataManager.
+ float x_offset_, y_offset_;
+ float x_offset_ordinal_, y_offset_ordinal_;
+ int finger_count_;
+ if (!x_offset)
+ x_offset = &x_offset_;
+ if (!y_offset)
+ y_offset = &y_offset_;
+ if (!x_offset_ordinal)
+ x_offset_ordinal = &x_offset_ordinal_;
+ if (!y_offset_ordinal)
+ y_offset_ordinal = &y_offset_ordinal_;
+ if (!finger_count)
+ finger_count = &finger_count_;
+
+ DeviceDataManagerX11::GetInstance()->GetScrollOffsets(
+ native_event,
+ x_offset, y_offset,
+ x_offset_ordinal, y_offset_ordinal,
+ finger_count);
+ return true;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ if (!DeviceDataManagerX11::GetInstance()->IsFlingEvent(native_event))
+ return false;
+
+ float vx_, vy_;
+ float vx_ordinal_, vy_ordinal_;
+ bool is_cancel_;
+ if (!vx)
+ vx = &vx_;
+ if (!vy)
+ vy = &vy_;
+ if (!vx_ordinal)
+ vx_ordinal = &vx_ordinal_;
+ if (!vy_ordinal)
+ vy_ordinal = &vy_ordinal_;
+ if (!is_cancel)
+ is_cancel = &is_cancel_;
+
+ DeviceDataManagerX11::GetInstance()->GetFlingData(
+ native_event, vx, vy, vx_ordinal, vy_ordinal, is_cancel);
+ return true;
+}
+
+void UpdateX11EventForFlags(Event* event) {
+ XEvent* xev = event->native_event();
+ if (!xev)
+ return;
+ switch (xev->type) {
+ case KeyPress:
+ case KeyRelease:
+ xev->xkey.state = UpdateX11EventFlags(event->flags(), xev->xkey.state);
+ break;
+ case ButtonPress:
+ case ButtonRelease:
+ xev->xbutton.state =
+ UpdateX11EventFlags(event->flags(), xev->xbutton.state);
+ break;
+ case GenericEvent: {
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+ DCHECK(xievent);
+ xievent->mods.effective =
+ UpdateX11EventFlags(event->flags(), xievent->mods.effective);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void UpdateX11EventForChangedButtonFlags(MouseEvent* event) {
+ XEvent* xev = event->native_event();
+ if (!xev)
+ return;
+ switch (xev->type) {
+ case ButtonPress:
+ case ButtonRelease:
+ xev->xbutton.button = UpdateX11EventButton(event->changed_button_flags(),
+ xev->xbutton.button);
+ break;
+ case GenericEvent: {
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+ CHECK(xievent && (xievent->evtype == XI_ButtonPress ||
+ xievent->evtype == XI_ButtonRelease));
+ xievent->detail =
+ UpdateX11EventButton(event->changed_button_flags(), xievent->detail);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+} // namespace ui
diff --git a/ui/events/x/events_x_unittest.cc b/ui/events/x/events_x_unittest.cc
new file mode 100644
index 0000000..d8e2c83
--- /dev/null
+++ b/ui/events/x/events_x_unittest.cc
@@ -0,0 +1,642 @@
+// 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 <cstring>
+#include <set>
+
+#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
+#undef None
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/test/events_test_utils.h"
+#include "ui/events/test/events_test_utils_x11.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/touch_factory_x11.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+
+namespace {
+
+// Initializes the passed-in Xlib event.
+void InitButtonEvent(XEvent* event,
+ bool is_press,
+ const gfx::Point& location,
+ int button,
+ 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.
+ XButtonEvent* button_event = &(event->xbutton);
+ button_event->type = is_press ? ButtonPress : ButtonRelease;
+ button_event->x = location.x();
+ button_event->y = location.y();
+ button_event->button = button;
+ 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);
+ return (ui_key_event.flags() & ui::EF_FUNCTION_KEY);
+ }
+ return true;
+}
+
+} // namespace
+
+class EventsXTest : public testing::Test {
+ public:
+ EventsXTest() {}
+ virtual ~EventsXTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ DeviceDataManagerX11::CreateInstance();
+ ui::TouchFactory::GetInstance()->ResetForTest();
+ }
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventsXTest);
+};
+
+TEST_F(EventsXTest, ButtonEvents) {
+ XEvent event;
+ gfx::Point location(5, 10);
+ gfx::Vector2d offset;
+
+ InitButtonEvent(&event, true, location, 1, 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));
+
+ InitButtonEvent(&event, true, location, 2, Button1Mask | ShiftMask);
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event));
+ EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON |
+ ui::EF_SHIFT_DOWN,
+ ui::EventFlagsFromNative(&event));
+ EXPECT_EQ(location, ui::EventLocationFromNative(&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));
+
+ // 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));
+ offset = ui::GetMouseWheelOffset(&event);
+ EXPECT_GT(offset.y(), 0);
+ EXPECT_EQ(0, offset.x());
+
+ // Scroll down.
+ InitButtonEvent(&event, true, location, 5, 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_LT(offset.y(), 0);
+ EXPECT_EQ(0, offset.x());
+
+ // 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));
+ offset = ui::GetMouseWheelOffset(&event);
+ EXPECT_EQ(0, offset.y());
+ EXPECT_GT(offset.x(), 0);
+
+ // 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));
+ offset = ui::GetMouseWheelOffset(&event);
+ EXPECT_EQ(0, offset.y());
+ EXPECT_LT(offset.x(), 0);
+
+ // TODO(derat): Test XInput code.
+}
+
+TEST_F(EventsXTest, AvoidExtraEventsOnWheelRelease) {
+ XEvent event;
+ gfx::Point location(5, 10);
+
+ InitButtonEvent(&event, true, location, 4, 0);
+ EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+
+ // We should return ET_UNKNOWN for the release event instead of returning
+ // ET_MOUSEWHEEL; otherwise we'll scroll twice for each scrollwheel step.
+ InitButtonEvent(&event, false, location, 4, 0);
+ EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(&event));
+
+ // TODO(derat): Test XInput code.
+}
+
+TEST_F(EventsXTest, EnterLeaveEvent) {
+ XEvent event;
+ event.xcrossing.type = EnterNotify;
+ event.xcrossing.x = 10;
+ event.xcrossing.y = 20;
+ event.xcrossing.x_root = 110;
+ event.xcrossing.y_root = 120;
+
+ // Mouse enter events are converted to mouse move events to be consistent with
+ // the way views handle mouse enter. See comments for EnterNotify case in
+ // ui::EventTypeFromNative for more details.
+ EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(&event));
+ EXPECT_EQ("10,20", ui::EventLocationFromNative(&event).ToString());
+ EXPECT_EQ("110,120", ui::EventSystemLocationFromNative(&event).ToString());
+
+ event.xcrossing.type = LeaveNotify;
+ event.xcrossing.x = 30;
+ event.xcrossing.y = 40;
+ event.xcrossing.x_root = 230;
+ event.xcrossing.y_root = 240;
+ EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(&event));
+ EXPECT_EQ("30,40", ui::EventLocationFromNative(&event).ToString());
+ EXPECT_EQ("230,240", ui::EventSystemLocationFromNative(&event).ToString());
+}
+
+TEST_F(EventsXTest, ClickCount) {
+ XEvent event;
+ gfx::Point location(5, 10);
+
+ for (int i = 1; i <= 3; ++i) {
+ InitButtonEvent(&event, true, location, 1, 0);
+ {
+ MouseEvent mouseev(&event);
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, mouseev.type());
+ EXPECT_EQ(i, mouseev.GetClickCount());
+ }
+
+ InitButtonEvent(&event, false, location, 1, 0);
+ {
+ MouseEvent mouseev(&event);
+ EXPECT_EQ(ui::ET_MOUSE_RELEASED, mouseev.type());
+ EXPECT_EQ(i, mouseev.GetClickCount());
+ }
+ }
+}
+
+#if defined(USE_XI2_MT)
+TEST_F(EventsXTest, TouchEventBasic) {
+ std::vector<unsigned int> devices;
+ devices.push_back(0);
+ ui::SetUpTouchDevicesForTest(devices);
+ std::vector<Valuator> valuators;
+
+ // Init touch begin with tracking id 5, touch id 0.
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 20));
+ valuators.push_back(
+ Valuator(DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.3f));
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 100));
+ ui::ScopedXI2Event scoped_xevent;
+ scoped_xevent.InitTouchEvent(
+ 0, XI_TouchBegin, 5, gfx::Point(10, 10), valuators);
+ EXPECT_EQ(ui::ET_TOUCH_PRESSED, ui::EventTypeFromNative(scoped_xevent));
+ EXPECT_EQ("10,10", ui::EventLocationFromNative(scoped_xevent).ToString());
+ EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+ EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+ EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.15f);
+ EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.1f);
+
+ // Touch update, with new orientation info.
+ valuators.clear();
+ valuators.push_back(
+ Valuator(DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.5f));
+ scoped_xevent.InitTouchEvent(
+ 0, XI_TouchUpdate, 5, gfx::Point(20, 20), valuators);
+ EXPECT_EQ(ui::ET_TOUCH_MOVED, ui::EventTypeFromNative(scoped_xevent));
+ EXPECT_EQ("20,20", ui::EventLocationFromNative(scoped_xevent).ToString());
+ EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+ EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+ EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.25f);
+ EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.1f);
+
+ // Another touch with tracking id 6, touch id 1.
+ valuators.clear();
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 100));
+ valuators.push_back(Valuator(
+ DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.9f));
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 500));
+ scoped_xevent.InitTouchEvent(
+ 0, XI_TouchBegin, 6, gfx::Point(200, 200), valuators);
+ EXPECT_EQ(ui::ET_TOUCH_PRESSED, ui::EventTypeFromNative(scoped_xevent));
+ EXPECT_EQ("200,200", ui::EventLocationFromNative(scoped_xevent).ToString());
+ EXPECT_EQ(GetTouchId(scoped_xevent), 1);
+ EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 50);
+ EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.45f);
+ EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f);
+
+ // Touch with tracking id 5 should have old radius/angle value and new pressue
+ // value.
+ valuators.clear();
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 50));
+ scoped_xevent.InitTouchEvent(
+ 0, XI_TouchEnd, 5, gfx::Point(30, 30), valuators);
+ EXPECT_EQ(ui::ET_TOUCH_RELEASED, ui::EventTypeFromNative(scoped_xevent));
+ EXPECT_EQ("30,30", ui::EventLocationFromNative(scoped_xevent).ToString());
+ EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+ EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+ EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.25f);
+ EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.05f);
+
+ // Touch with tracking id 6 should have old angle/pressure value and new
+ // radius value.
+ valuators.clear();
+ valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 50));
+ scoped_xevent.InitTouchEvent(
+ 0, XI_TouchEnd, 6, gfx::Point(200, 200), valuators);
+ EXPECT_EQ(ui::ET_TOUCH_RELEASED, ui::EventTypeFromNative(scoped_xevent));
+ EXPECT_EQ("200,200", ui::EventLocationFromNative(scoped_xevent).ToString());
+ EXPECT_EQ(GetTouchId(scoped_xevent), 1);
+ EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 25);
+ EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.45f);
+ EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f);
+}
+
+int GetTouchIdForTrackingId(uint32 tracking_id) {
+ int slot = 0;
+ bool success =
+ TouchFactory::GetInstance()->QuerySlotForTrackingID(tracking_id, &slot);
+ if (success)
+ return slot;
+ return -1;
+}
+
+TEST_F(EventsXTest, TouchEventIdRefcounting) {
+ std::vector<unsigned int> devices;
+ devices.push_back(0);
+ ui::SetUpTouchDevicesForTest(devices);
+ std::vector<Valuator> valuators;
+
+ const int kTrackingId0 = 5;
+ const int kTrackingId1 = 7;
+
+ // Increment ref count once for first touch.
+ ui::ScopedXI2Event xpress0;
+ xpress0.InitTouchEvent(
+ 0, XI_TouchBegin, kTrackingId0, gfx::Point(10, 10), valuators);
+ scoped_ptr<ui::TouchEvent> upress0(new ui::TouchEvent(xpress0));
+ EXPECT_EQ(0, GetTouchIdForTrackingId(kTrackingId0));
+
+ // Increment ref count 4 times for second touch.
+ ui::ScopedXI2Event xpress1;
+ xpress1.InitTouchEvent(
+ 0, XI_TouchBegin, kTrackingId1, gfx::Point(20, 20), valuators);
+
+ for (int i = 0; i < 4; ++i) {
+ ui::TouchEvent upress1(xpress1);
+ EXPECT_EQ(1, GetTouchIdForTrackingId(kTrackingId1));
+ }
+
+ ui::ScopedXI2Event xrelease1;
+ xrelease1.InitTouchEvent(
+ 0, XI_TouchEnd, kTrackingId1, gfx::Point(10, 10), valuators);
+
+ // Decrement ref count 3 times for second touch.
+ for (int i = 0; i < 3; ++i) {
+ ui::TouchEvent urelease1(xrelease1);
+ EXPECT_EQ(1, GetTouchIdForTrackingId(kTrackingId1));
+ }
+
+ // This should clear the touch id of the second touch.
+ scoped_ptr<ui::TouchEvent> urelease1(new ui::TouchEvent(xrelease1));
+ urelease1.reset();
+ EXPECT_EQ(-1, GetTouchIdForTrackingId(kTrackingId1));
+
+ // This should clear the touch id of the first touch.
+ ui::ScopedXI2Event xrelease0;
+ xrelease0.InitTouchEvent(
+ 0, XI_TouchEnd, kTrackingId0, gfx::Point(10, 10), valuators);
+ scoped_ptr<ui::TouchEvent> urelease0(new ui::TouchEvent(xrelease0));
+ urelease0.reset();
+ EXPECT_EQ(-1, GetTouchIdForTrackingId(kTrackingId0));
+}
+#endif
+
+TEST_F(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);
+ EXPECT_EQ(keys[k].is_numpad_key ? ui::EF_NUMPAD_KEY : 0,
+ ui_key_event.flags() & ui::EF_NUMPAD_KEY);
+ }
+ }
+}
+
+TEST_F(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(USE_XI2_MT)
+// Verifies that the type of events from a disabled keyboard is ET_UNKNOWN, but
+// that an exception list of keys can still be processed.
+TEST_F(EventsXTest, DisableKeyboard) {
+ DeviceDataManagerX11* device_data_manager =
+ static_cast<DeviceDataManagerX11*>(
+ DeviceDataManager::GetInstance());
+ unsigned int blocked_device_id = 1;
+ unsigned int other_device_id = 2;
+ unsigned int master_device_id = 3;
+ device_data_manager->DisableDevice(blocked_device_id);
+
+ scoped_ptr<std::set<KeyboardCode> > excepted_keys(new std::set<KeyboardCode>);
+ excepted_keys->insert(VKEY_B);
+ device_data_manager->SetDisabledKeyboardAllowedKeys(excepted_keys.Pass());
+
+ ScopedXI2Event xev;
+ // A is not allowed on the blocked keyboard, and should return ET_UNKNOWN.
+ xev.InitGenericKeyEvent(master_device_id,
+ blocked_device_id,
+ ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0);
+ EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(xev));
+
+ // The B key is allowed as an exception, and should return KEY_PRESSED.
+ xev.InitGenericKeyEvent(master_device_id,
+ blocked_device_id,
+ ui::ET_KEY_PRESSED,
+ ui::VKEY_B,
+ 0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+
+ // Both A and B are allowed on an unblocked keyboard device.
+ xev.InitGenericKeyEvent(master_device_id,
+ other_device_id,
+ ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+ xev.InitGenericKeyEvent(master_device_id,
+ other_device_id,
+ ui::ET_KEY_PRESSED,
+ ui::VKEY_B,
+ 0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+
+ device_data_manager->EnableDevice(blocked_device_id);
+ device_data_manager->SetDisabledKeyboardAllowedKeys(
+ scoped_ptr<std::set<KeyboardCode> >());
+
+ // A key returns KEY_PRESSED as per usual now that keyboard was re-enabled.
+ xev.InitGenericKeyEvent(master_device_id,
+ blocked_device_id,
+ ui::ET_KEY_PRESSED,
+ ui::VKEY_A,
+ 0);
+ EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+}
+
+// Verifies that the type of events from a disabled mouse is ET_UNKNOWN.
+TEST_F(EventsXTest, DisableMouse) {
+ DeviceDataManagerX11* device_data_manager =
+ static_cast<DeviceDataManagerX11*>(
+ DeviceDataManager::GetInstance());
+ unsigned int blocked_device_id = 1;
+ unsigned int other_device_id = 2;
+ std::vector<unsigned int> device_list;
+ device_list.push_back(blocked_device_id);
+ device_list.push_back(other_device_id);
+ TouchFactory::GetInstance()->SetPointerDeviceForTest(device_list);
+
+ device_data_manager->DisableDevice(blocked_device_id);
+
+ ScopedXI2Event xev;
+ xev.InitGenericButtonEvent(blocked_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+ EF_LEFT_MOUSE_BUTTON);
+ EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(xev));
+
+ xev.InitGenericButtonEvent(other_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+ EF_LEFT_MOUSE_BUTTON);
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(xev));
+
+ device_data_manager->EnableDevice(blocked_device_id);
+
+ xev.InitGenericButtonEvent(blocked_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+ EF_LEFT_MOUSE_BUTTON);
+ EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(xev));
+}
+#endif // defined(USE_XI2_MT)
+
+#if !defined(OS_CHROMEOS)
+TEST_F(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);
+ if (is_char) {
+ KeyEventTestApi test_event(&key_event);
+ test_event.set_is_char(true);
+ }
+ 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);
+ if (is_char) {
+ KeyEventTestApi test_event(&key_event);
+ test_event.set_is_char(true);
+ }
+ EXPECT_FALSE(key_event.flags() & ui::EF_IME_FABRICATED_KEY);
+ }
+ }
+}
+#endif
+
+} // namespace ui
diff --git a/ui/events/x/hotplug_event_handler_x11.cc b/ui/events/x/hotplug_event_handler_x11.cc
new file mode 100644
index 0000000..16f42f7
--- /dev/null
+++ b/ui/events/x/hotplug_event_handler_x11.cc
@@ -0,0 +1,182 @@
+// 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/x/hotplug_event_handler_x11.h"
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+
+#include <cmath>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/process/launch.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "ui/events/device_hotplug_event_observer.h"
+#include "ui/events/touchscreen_device.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+namespace {
+
+// We consider the touchscreen to be internal if it is an I2c device.
+// With the device id, we can query X to get the device's dev input
+// node eventXXX. Then we search all the dev input nodes registered
+// by I2C devices to see if we can find eventXXX.
+bool IsTouchscreenInternal(XDisplay* dpy, int device_id) {
+ using base::FileEnumerator;
+ using base::FilePath;
+
+#if !defined(CHROMEOS)
+ return false;
+#else
+ if (!base::SysInfo::IsRunningOnChromeOS())
+ return false;
+#endif
+
+ // Input device has a property "Device Node" pointing to its dev input node,
+ // e.g. Device Node (250): "/dev/input/event8"
+ Atom device_node = XInternAtom(dpy, "Device Node", False);
+ if (device_node == None)
+ return false;
+
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, bytes_after;
+ unsigned char* data;
+ XDevice* dev = XOpenDevice(dpy, device_id);
+ if (!dev)
+ return false;
+
+ if (XGetDeviceProperty(dpy,
+ dev,
+ device_node,
+ 0,
+ 1000,
+ False,
+ AnyPropertyType,
+ &actual_type,
+ &actual_format,
+ &nitems,
+ &bytes_after,
+ &data) != Success) {
+ XCloseDevice(dpy, dev);
+ return false;
+ }
+ base::FilePath dev_node_path(reinterpret_cast<char*>(data));
+ XFree(data);
+ XCloseDevice(dpy, dev);
+
+ std::string event_node = dev_node_path.BaseName().value();
+ if (event_node.empty() || !StartsWithASCII(event_node, "event", false))
+ return false;
+
+ // Extract id "XXX" from "eventXXX"
+ std::string event_node_id = event_node.substr(5);
+
+ // I2C input device registers its dev input node at
+ // /sys/bus/i2c/devices/*/input/inputXXX/eventXXX
+ FileEnumerator i2c_enum(FilePath(FILE_PATH_LITERAL("/sys/bus/i2c/devices/")),
+ false,
+ base::FileEnumerator::DIRECTORIES);
+ for (FilePath i2c_name = i2c_enum.Next(); !i2c_name.empty();
+ i2c_name = i2c_enum.Next()) {
+ FileEnumerator input_enum(i2c_name.Append(FILE_PATH_LITERAL("input")),
+ false,
+ base::FileEnumerator::DIRECTORIES,
+ FILE_PATH_LITERAL("input*"));
+ for (base::FilePath input = input_enum.Next(); !input.empty();
+ input = input_enum.Next()) {
+ if (input.BaseName().value().substr(5) == event_node_id)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace
+
+HotplugEventHandlerX11::HotplugEventHandlerX11(
+ DeviceHotplugEventObserver* delegate)
+ : delegate_(delegate) {
+}
+
+HotplugEventHandlerX11::~HotplugEventHandlerX11() {
+}
+
+void HotplugEventHandlerX11::OnHotplugEvent() {
+ const XIDeviceList& device_list =
+ DeviceListCacheX::GetInstance()->GetXI2DeviceList(gfx::GetXDisplay());
+ HandleTouchscreenDevices(device_list);
+}
+
+void HotplugEventHandlerX11::HandleTouchscreenDevices(
+ const XIDeviceList& x11_devices) {
+ std::vector<TouchscreenDevice> devices;
+ Display* display = gfx::GetXDisplay();
+ Atom valuator_x = XInternAtom(display, "Abs MT Position X", False);
+ Atom valuator_y = XInternAtom(display, "Abs MT Position Y", False);
+ if (valuator_x == None || valuator_y == None)
+ return;
+
+ std::set<int> no_match_touchscreen;
+ for (int i = 0; i < x11_devices.count; i++) {
+ if (!x11_devices[i].enabled || x11_devices[i].use != XIFloatingSlave)
+ continue; // Assume all touchscreens are floating slaves
+
+ double width = -1.0;
+ double height = -1.0;
+ bool is_direct_touch = false;
+
+ for (int j = 0; j < x11_devices[i].num_classes; j++) {
+ XIAnyClassInfo* class_info = x11_devices[i].classes[j];
+
+ if (class_info->type == XIValuatorClass) {
+ XIValuatorClassInfo* valuator_info =
+ reinterpret_cast<XIValuatorClassInfo*>(class_info);
+
+ if (valuator_x == valuator_info->label) {
+ // Ignore X axis valuator with unexpected properties
+ if (valuator_info->number == 0 && valuator_info->mode == Absolute &&
+ valuator_info->min == 0.0) {
+ width = valuator_info->max;
+ }
+ } else if (valuator_y == valuator_info->label) {
+ // Ignore Y axis valuator with unexpected properties
+ if (valuator_info->number == 1 && valuator_info->mode == Absolute &&
+ valuator_info->min == 0.0) {
+ height = valuator_info->max;
+ }
+ }
+ }
+#if defined(USE_XI2_MT)
+ if (class_info->type == XITouchClass) {
+ XITouchClassInfo* touch_info =
+ reinterpret_cast<XITouchClassInfo*>(class_info);
+ is_direct_touch = touch_info->mode == XIDirectTouch;
+ }
+#endif
+ }
+
+ // Touchscreens should have absolute X and Y axes, and be direct touch
+ // devices.
+ if (width > 0.0 && height > 0.0 && is_direct_touch) {
+ bool is_internal =
+ IsTouchscreenInternal(display, x11_devices[i].deviceid);
+ devices.push_back(TouchscreenDevice(
+ x11_devices[i].deviceid, gfx::Size(width, height), is_internal));
+ }
+ }
+
+ delegate_->OnTouchscreenDevicesUpdated(devices);
+}
+
+} // namespace ui
diff --git a/ui/events/x/hotplug_event_handler_x11.h b/ui/events/x/hotplug_event_handler_x11.h
new file mode 100644
index 0000000..c99fbf2
--- /dev/null
+++ b/ui/events/x/hotplug_event_handler_x11.h
@@ -0,0 +1,34 @@
+// 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_HOTPLUG_EVENT_HANDLER_H_
+#define UI_EVENTS_X_HOTPLUG_EVENT_HANDLER_H_
+
+#include "ui/events/x/device_list_cache_x.h"
+
+namespace ui {
+
+class DeviceHotplugEventObserver;
+
+// Parses X11 native devices and propagates the list of active devices to an
+// observer.
+class EVENTS_BASE_EXPORT HotplugEventHandlerX11 {
+ public:
+ explicit HotplugEventHandlerX11(DeviceHotplugEventObserver* delegate);
+ ~HotplugEventHandlerX11();
+
+ // Called on an X11 hotplug event.
+ void OnHotplugEvent();
+
+ private:
+ void HandleTouchscreenDevices(const XIDeviceList& device_list);
+
+ DeviceHotplugEventObserver* delegate_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(HotplugEventHandlerX11);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_X_HOTPLUG_EVENT_HANDLER_H_
diff --git a/ui/events/x/keysym_to_unicode.cc b/ui/events/x/keysym_to_unicode.cc
new file mode 100644
index 0000000..9e87bff
--- /dev/null
+++ b/ui/events/x/keysym_to_unicode.cc
@@ -0,0 +1,849 @@
+// 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/x/keysym_to_unicode.h"
+
+// Define XK_xxx before the #include of <X11/keysym.h> so that <X11/keysym.h>
+// defines all KeySyms we need.
+#define XK_MISCELLANY
+#define XK_LATIN1
+#define XK_LATIN2
+#define XK_LATIN3
+#define XK_LATIN4
+#define XK_LATIN8
+#define XK_LATIN9
+#define XK_KATAKANA
+#define XK_ARABIC
+#define XK_CYRILLIC
+#define XK_GREEK
+#define XK_TECHNICAL
+#define XK_SPECIAL
+#define XK_PUBLISHING
+#define XK_APL
+#define XK_HEBREW
+#define XK_THAI
+#define XK_KOREAN
+#define XK_ARMENIAN
+#define XK_GEORGIAN
+#define XK_CAUCASUS
+#define XK_VIETNAMESE
+#define XK_CURRENCY
+#define XK_MATHEMATICAL
+#define XK_BRAILLE
+#define XK_SINHALA
+#include <X11/X.h>
+#include <X11/keysym.h>
+
+#include <unordered_map>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+
+namespace ui {
+
+const struct {
+ KeySym keysym;
+ uint16_t unicode;
+} g_keysym_to_unicode_table[] = {
+ // Control characters
+ {XK_BackSpace, 0x0008},
+ {XK_Tab, 0x0009},
+ {XK_Linefeed, 0x000a},
+ {XK_Clear, 0x000b},
+ {XK_Return, 0x000d},
+ {XK_Escape, 0x001b},
+ {XK_Delete, 0x007f},
+
+ // Numeric keypad
+ {XK_KP_Space, 0x0020},
+ {XK_KP_Tab, 0x0009},
+ {XK_KP_Enter, 0x000d},
+ {XK_KP_Equal, 0x003d},
+ {XK_KP_Multiply, 0x002a},
+ {XK_KP_Add, 0x002b},
+ {XK_KP_Separator, 0x002c},
+ {XK_KP_Subtract, 0x002d},
+ {XK_KP_Decimal, 0x002e},
+ {XK_KP_Divide, 0x002f},
+ {XK_KP_0, 0x0030},
+ {XK_KP_1, 0x0031},
+ {XK_KP_2, 0x0032},
+ {XK_KP_3, 0x0033},
+ {XK_KP_4, 0x0034},
+ {XK_KP_5, 0x0035},
+ {XK_KP_6, 0x0036},
+ {XK_KP_7, 0x0037},
+ {XK_KP_8, 0x0038},
+ {XK_KP_9, 0x0039},
+
+ // Latin 1 KeySyms map 1:1 to Unicode
+
+ // Latin 2
+ {XK_Aogonek, 0x0104}, // LATIN CAPITAL LETTER A WITH OGONEK
+ {XK_breve, 0x02D8}, // BREVE
+ {XK_Lstroke, 0x0141}, // LATIN CAPITAL LETTER L WITH STROKE
+ {XK_Lcaron, 0x013D}, // LATIN CAPITAL LETTER L WITH CARON
+ {XK_Sacute, 0x015A}, // LATIN CAPITAL LETTER S WITH ACUTE
+ {XK_Scaron, 0x0160}, // LATIN CAPITAL LETTER S WITH CARON
+ {XK_Scedilla, 0x015E}, // LATIN CAPITAL LETTER S WITH CEDILLA
+ {XK_Tcaron, 0x0164}, // LATIN CAPITAL LETTER T WITH CARON
+ {XK_Zacute, 0x0179}, // LATIN CAPITAL LETTER Z WITH ACUTE
+ {XK_Zcaron, 0x017D}, // LATIN CAPITAL LETTER Z WITH CARON
+ {XK_Zabovedot, 0x017B}, // LATIN CAPITAL LETTER Z WITH DOT ABOVE
+ {XK_aogonek, 0x0105}, // LATIN SMALL LETTER A WITH OGONEK
+ {XK_ogonek, 0x02DB}, // OGONEK
+ {XK_lstroke, 0x0142}, // LATIN SMALL LETTER L WITH STROKE
+ {XK_lcaron, 0x013E}, // LATIN SMALL LETTER L WITH CARON
+ {XK_sacute, 0x015B}, // LATIN SMALL LETTER S WITH ACUTE
+ {XK_caron, 0x02C7}, // CARON
+ {XK_scaron, 0x0161}, // LATIN SMALL LETTER S WITH CARON
+ {XK_scedilla, 0x015F}, // LATIN SMALL LETTER S WITH CEDILLA
+ {XK_tcaron, 0x0165}, // LATIN SMALL LETTER T WITH CARON
+ {XK_zacute, 0x017A}, // LATIN SMALL LETTER Z WITH ACUTE
+ {XK_doubleacute, 0x02DD}, // DOUBLE ACUTE ACCENT
+ {XK_zcaron, 0x017E}, // LATIN SMALL LETTER Z WITH CARON
+ {XK_zabovedot, 0x017C}, // LATIN SMALL LETTER Z WITH DOT ABOVE
+ {XK_Racute, 0x0154}, // LATIN CAPITAL LETTER R WITH ACUTE
+ {XK_Abreve, 0x0102}, // LATIN CAPITAL LETTER A WITH BREVE
+ {XK_Lacute, 0x0139}, // LATIN CAPITAL LETTER L WITH ACUTE
+ {XK_Cacute, 0x0106}, // LATIN CAPITAL LETTER C WITH ACUTE
+ {XK_Ccaron, 0x010C}, // LATIN CAPITAL LETTER C WITH CARON
+ {XK_Eogonek, 0x0118}, // LATIN CAPITAL LETTER E WITH OGONEK
+ {XK_Ecaron, 0x011A}, // LATIN CAPITAL LETTER E WITH CARON
+ {XK_Dcaron, 0x010E}, // LATIN CAPITAL LETTER D WITH CARON
+ {XK_Dstroke, 0x0110}, // LATIN CAPITAL LETTER D WITH STROKE
+ {XK_Nacute, 0x0143}, // LATIN CAPITAL LETTER N WITH ACUTE
+ {XK_Ncaron, 0x0147}, // LATIN CAPITAL LETTER N WITH CARON
+ {XK_Odoubleacute, 0x0150}, // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+ {XK_Rcaron, 0x0158}, // LATIN CAPITAL LETTER R WITH CARON
+ {XK_Uring, 0x016E}, // LATIN CAPITAL LETTER U WITH RING ABOVE
+ {XK_Udoubleacute, 0x0170}, // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+ {XK_Tcedilla, 0x0162}, // LATIN CAPITAL LETTER T WITH CEDILLA
+ {XK_racute, 0x0155}, // LATIN SMALL LETTER R WITH ACUTE
+ {XK_abreve, 0x0103}, // LATIN SMALL LETTER A WITH BREVE
+ {XK_lacute, 0x013A}, // LATIN SMALL LETTER L WITH ACUTE
+ {XK_cacute, 0x0107}, // LATIN SMALL LETTER C WITH ACUTE
+ {XK_ccaron, 0x010D}, // LATIN SMALL LETTER C WITH CARON
+ {XK_eogonek, 0x0119}, // LATIN SMALL LETTER E WITH OGONEK
+ {XK_ecaron, 0x011B}, // LATIN SMALL LETTER E WITH CARON
+ {XK_dcaron, 0x010F}, // LATIN SMALL LETTER D WITH CARON
+ {XK_dstroke, 0x0111}, // LATIN SMALL LETTER D WITH STROKE
+ {XK_nacute, 0x0144}, // LATIN SMALL LETTER N WITH ACUTE
+ {XK_ncaron, 0x0148}, // LATIN SMALL LETTER N WITH CARON
+ {XK_odoubleacute, 0x0151}, // LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ {XK_rcaron, 0x0159}, // LATIN SMALL LETTER R WITH CARON
+ {XK_uring, 0x016F}, // LATIN SMALL LETTER U WITH RING ABOVE
+ {XK_udoubleacute, 0x0171}, // LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ {XK_tcedilla, 0x0163}, // LATIN SMALL LETTER T WITH CEDILLA
+ {XK_abovedot, 0x02D9}, // DOT ABOVE
+
+ // Latin 3
+ {XK_Hstroke, 0x0126}, // LATIN CAPITAL LETTER H WITH STROKE
+ {XK_Hcircumflex, 0x0124}, // LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+ {XK_Iabovedot, 0x0130}, // LATIN CAPITAL LETTER I WITH DOT ABOVE
+ {XK_Gbreve, 0x011E}, // LATIN CAPITAL LETTER G WITH BREVE
+ {XK_Jcircumflex, 0x0134}, // LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+ {XK_hstroke, 0x0127}, // LATIN SMALL LETTER H WITH STROKE
+ {XK_hcircumflex, 0x0125}, // LATIN SMALL LETTER H WITH CIRCUMFLEX
+ {XK_idotless, 0x0131}, // LATIN SMALL LETTER DOTLESS I
+ {XK_gbreve, 0x011F}, // LATIN SMALL LETTER G WITH BREVE
+ {XK_jcircumflex, 0x0135}, // LATIN SMALL LETTER J WITH CIRCUMFLEX
+ {XK_Cabovedot, 0x010A}, // LATIN CAPITAL LETTER C WITH DOT ABOVE
+ {XK_Ccircumflex, 0x0108}, // LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+ {XK_Gabovedot, 0x0120}, // LATIN CAPITAL LETTER G WITH DOT ABOVE
+ {XK_Gcircumflex, 0x011C}, // LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+ {XK_Ubreve, 0x016C}, // LATIN CAPITAL LETTER U WITH BREVE
+ {XK_Scircumflex, 0x015C}, // LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+ {XK_cabovedot, 0x010B}, // LATIN SMALL LETTER C WITH DOT ABOVE
+ {XK_ccircumflex, 0x0109}, // LATIN SMALL LETTER C WITH CIRCUMFLEX
+ {XK_gabovedot, 0x0121}, // LATIN SMALL LETTER G WITH DOT ABOVE
+ {XK_gcircumflex, 0x011D}, // LATIN SMALL LETTER G WITH CIRCUMFLEX
+ {XK_ubreve, 0x016D}, // LATIN SMALL LETTER U WITH BREVE
+ {XK_scircumflex, 0x015D}, // LATIN SMALL LETTER S WITH CIRCUMFLEX
+
+ // Latin 4
+ {XK_kra, 0x0138}, // LATIN SMALL LETTER KRA
+ {XK_Rcedilla, 0x0156}, // LATIN CAPITAL LETTER R WITH CEDILLA
+ {XK_Itilde, 0x0128}, // LATIN CAPITAL LETTER I WITH TILDE
+ {XK_Lcedilla, 0x013B}, // LATIN CAPITAL LETTER L WITH CEDILLA
+ {XK_Emacron, 0x0112}, // LATIN CAPITAL LETTER E WITH MACRON
+ {XK_Gcedilla, 0x0122}, // LATIN CAPITAL LETTER G WITH CEDILLA
+ {XK_Tslash, 0x0166}, // LATIN CAPITAL LETTER T WITH STROKE
+ {XK_rcedilla, 0x0157}, // LATIN SMALL LETTER R WITH CEDILLA
+ {XK_itilde, 0x0129}, // LATIN SMALL LETTER I WITH TILDE
+ {XK_lcedilla, 0x013C}, // LATIN SMALL LETTER L WITH CEDILLA
+ {XK_emacron, 0x0113}, // LATIN SMALL LETTER E WITH MACRON
+ {XK_gcedilla, 0x0123}, // LATIN SMALL LETTER G WITH CEDILLA
+ {XK_tslash, 0x0167}, // LATIN SMALL LETTER T WITH STROKE
+ {XK_ENG, 0x014A}, // LATIN CAPITAL LETTER ENG
+ {XK_eng, 0x014B}, // LATIN SMALL LETTER ENG
+ {XK_Amacron, 0x0100}, // LATIN CAPITAL LETTER A WITH MACRON
+ {XK_Iogonek, 0x012E}, // LATIN CAPITAL LETTER I WITH OGONEK
+ {XK_Eabovedot, 0x0116}, // LATIN CAPITAL LETTER E WITH DOT ABOVE
+ {XK_Imacron, 0x012A}, // LATIN CAPITAL LETTER I WITH MACRON
+ {XK_Ncedilla, 0x0145}, // LATIN CAPITAL LETTER N WITH CEDILLA
+ {XK_Omacron, 0x014C}, // LATIN CAPITAL LETTER O WITH MACRON
+ {XK_Kcedilla, 0x0136}, // LATIN CAPITAL LETTER K WITH CEDILLA
+ {XK_Uogonek, 0x0172}, // LATIN CAPITAL LETTER U WITH OGONEK
+ {XK_Utilde, 0x0168}, // LATIN CAPITAL LETTER U WITH TILDE
+ {XK_Umacron, 0x016A}, // LATIN CAPITAL LETTER U WITH MACRON
+ {XK_amacron, 0x0101}, // LATIN SMALL LETTER A WITH MACRON
+ {XK_iogonek, 0x012F}, // LATIN SMALL LETTER I WITH OGONEK
+ {XK_eabovedot, 0x0117}, // LATIN SMALL LETTER E WITH DOT ABOVE
+ {XK_imacron, 0x012B}, // LATIN SMALL LETTER I WITH MACRON
+ {XK_ncedilla, 0x0146}, // LATIN SMALL LETTER N WITH CEDILLA
+ {XK_omacron, 0x014D}, // LATIN SMALL LETTER O WITH MACRON
+ {XK_kcedilla, 0x0137}, // LATIN SMALL LETTER K WITH CEDILLA
+ {XK_uogonek, 0x0173}, // LATIN SMALL LETTER U WITH OGONEK
+ {XK_utilde, 0x0169}, // LATIN SMALL LETTER U WITH TILDE
+ {XK_umacron, 0x016B}, // LATIN SMALL LETTER U WITH MACRON
+
+ // Latin 8 KeySyms map 1:1 to Unicode
+
+ // Latin 9
+ {XK_OE, 0x0152}, // LATIN CAPITAL LIGATURE OE
+ {XK_oe, 0x0153}, // LATIN SMALL LIGATURE OE
+ {XK_Ydiaeresis, 0x0178}, // LATIN CAPITAL LETTER Y WITH DIAERESIS
+
+ // Katakana
+ {XK_overline, 0x203E}, // OVERLINE
+ {XK_kana_fullstop, 0x3002}, // IDEOGRAPHIC FULL STOP
+ {XK_kana_openingbracket, 0x300C}, // LEFT CORNER BRACKET
+ {XK_kana_closingbracket, 0x300D}, // RIGHT CORNER BRACKET
+ {XK_kana_comma, 0x3001}, // IDEOGRAPHIC COMMA
+ {XK_kana_conjunctive, 0x30FB}, // KATAKANA MIDDLE DOT
+ {XK_kana_WO, 0x30F2}, // KATAKANA LETTER WO
+ {XK_kana_a, 0x30A1}, // KATAKANA LETTER SMALL A
+ {XK_kana_i, 0x30A3}, // KATAKANA LETTER SMALL I
+ {XK_kana_u, 0x30A5}, // KATAKANA LETTER SMALL U
+ {XK_kana_e, 0x30A7}, // KATAKANA LETTER SMALL E
+ {XK_kana_o, 0x30A9}, // KATAKANA LETTER SMALL O
+ {XK_kana_ya, 0x30E3}, // KATAKANA LETTER SMALL YA
+ {XK_kana_yu, 0x30E5}, // KATAKANA LETTER SMALL YU
+ {XK_kana_yo, 0x30E7}, // KATAKANA LETTER SMALL YO
+ {XK_kana_tsu, 0x30C3}, // KATAKANA LETTER SMALL TU
+ {XK_prolongedsound, 0x30FC}, // KATAKANA-HIRAGANA PROLONGED SOUND MARK
+ {XK_kana_A, 0x30A2}, // KATAKANA LETTER A
+ {XK_kana_I, 0x30A4}, // KATAKANA LETTER I
+ {XK_kana_U, 0x30A6}, // KATAKANA LETTER U
+ {XK_kana_E, 0x30A8}, // KATAKANA LETTER E
+ {XK_kana_O, 0x30AA}, // KATAKANA LETTER O
+ {XK_kana_KA, 0x30AB}, // KATAKANA LETTER KA
+ {XK_kana_KI, 0x30AD}, // KATAKANA LETTER KI
+ {XK_kana_KU, 0x30AF}, // KATAKANA LETTER KU
+ {XK_kana_KE, 0x30B1}, // KATAKANA LETTER KE
+ {XK_kana_KO, 0x30B3}, // KATAKANA LETTER KO
+ {XK_kana_SA, 0x30B5}, // KATAKANA LETTER SA
+ {XK_kana_SHI, 0x30B7}, // KATAKANA LETTER SI
+ {XK_kana_SU, 0x30B9}, // KATAKANA LETTER SU
+ {XK_kana_SE, 0x30BB}, // KATAKANA LETTER SE
+ {XK_kana_SO, 0x30BD}, // KATAKANA LETTER SO
+ {XK_kana_TA, 0x30BF}, // KATAKANA LETTER TA
+ {XK_kana_CHI, 0x30C1}, // KATAKANA LETTER TI
+ {XK_kana_TSU, 0x30C4}, // KATAKANA LETTER TU
+ {XK_kana_TE, 0x30C6}, // KATAKANA LETTER TE
+ {XK_kana_TO, 0x30C8}, // KATAKANA LETTER TO
+ {XK_kana_NA, 0x30CA}, // KATAKANA LETTER NA
+ {XK_kana_NI, 0x30CB}, // KATAKANA LETTER NI
+ {XK_kana_NU, 0x30CC}, // KATAKANA LETTER NU
+ {XK_kana_NE, 0x30CD}, // KATAKANA LETTER NE
+ {XK_kana_NO, 0x30CE}, // KATAKANA LETTER NO
+ {XK_kana_HA, 0x30CF}, // KATAKANA LETTER HA
+ {XK_kana_HI, 0x30D2}, // KATAKANA LETTER HI
+ {XK_kana_FU, 0x30D5}, // KATAKANA LETTER HU
+ {XK_kana_HE, 0x30D8}, // KATAKANA LETTER HE
+ {XK_kana_HO, 0x30DB}, // KATAKANA LETTER HO
+ {XK_kana_MA, 0x30DE}, // KATAKANA LETTER MA
+ {XK_kana_MI, 0x30DF}, // KATAKANA LETTER MI
+ {XK_kana_MU, 0x30E0}, // KATAKANA LETTER MU
+ {XK_kana_ME, 0x30E1}, // KATAKANA LETTER ME
+ {XK_kana_MO, 0x30E2}, // KATAKANA LETTER MO
+ {XK_kana_YA, 0x30E4}, // KATAKANA LETTER YA
+ {XK_kana_YU, 0x30E6}, // KATAKANA LETTER YU
+ {XK_kana_YO, 0x30E8}, // KATAKANA LETTER YO
+ {XK_kana_RA, 0x30E9}, // KATAKANA LETTER RA
+ {XK_kana_RI, 0x30EA}, // KATAKANA LETTER RI
+ {XK_kana_RU, 0x30EB}, // KATAKANA LETTER RU
+ {XK_kana_RE, 0x30EC}, // KATAKANA LETTER RE
+ {XK_kana_RO, 0x30ED}, // KATAKANA LETTER RO
+ {XK_kana_WA, 0x30EF}, // KATAKANA LETTER WA
+ {XK_kana_N, 0x30F3}, // KATAKANA LETTER N
+ {XK_voicedsound, 0x309B}, // KATAKANA-HIRAGANA VOICED SOUND MARK
+ {XK_semivoicedsound, 0x309C}, // KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+
+ // Arabic KeySyms partially map 1:1 to Unicode
+ {XK_Arabic_comma, 0x060C}, // ARABIC COMMA
+ {XK_Arabic_semicolon, 0x061B}, // ARABIC SEMICOLON
+ {XK_Arabic_question_mark, 0x061F}, // ARABIC QUESTION MARK
+ {XK_Arabic_hamza, 0x0621}, // ARABIC LETTER HAMZA
+ {XK_Arabic_maddaonalef, 0x0622}, // ARABIC LETTER ALEF WITH MADDA ABOVE
+ {XK_Arabic_hamzaonalef, 0x0623}, // ARABIC LETTER ALEF WITH HAMZA ABOVE
+ {XK_Arabic_hamzaonwaw, 0x0624}, // ARABIC LETTER WAW WITH HAMZA ABOVE
+ {XK_Arabic_hamzaunderalef, 0x0625}, // ARABIC LETTER ALEF WITH HAMZA BELOW
+ {XK_Arabic_hamzaonyeh, 0x0626}, // ARABIC LETTER YEH WITH HAMZA ABOVE
+ {XK_Arabic_alef, 0x0627}, // ARABIC LETTER ALEF
+ {XK_Arabic_beh, 0x0628}, // ARABIC LETTER BEH
+ {XK_Arabic_tehmarbuta, 0x0629}, // ARABIC LETTER TEH MARBUTA
+ {XK_Arabic_teh, 0x062A}, // ARABIC LETTER TEH
+ {XK_Arabic_theh, 0x062B}, // ARABIC LETTER THEH
+ {XK_Arabic_jeem, 0x062C}, // ARABIC LETTER JEEM
+ {XK_Arabic_hah, 0x062D}, // ARABIC LETTER HAH
+ {XK_Arabic_khah, 0x062E}, // ARABIC LETTER KHAH
+ {XK_Arabic_dal, 0x062F}, // ARABIC LETTER DAL
+ {XK_Arabic_thal, 0x0630}, // ARABIC LETTER THAL
+ {XK_Arabic_ra, 0x0631}, // ARABIC LETTER REH
+ {XK_Arabic_zain, 0x0632}, // ARABIC LETTER ZAIN
+ {XK_Arabic_seen, 0x0633}, // ARABIC LETTER SEEN
+ {XK_Arabic_sheen, 0x0634}, // ARABIC LETTER SHEEN
+ {XK_Arabic_sad, 0x0635}, // ARABIC LETTER SAD
+ {XK_Arabic_dad, 0x0636}, // ARABIC LETTER DAD
+ {XK_Arabic_tah, 0x0637}, // ARABIC LETTER TAH
+ {XK_Arabic_zah, 0x0638}, // ARABIC LETTER ZAH
+ {XK_Arabic_ain, 0x0639}, // ARABIC LETTER AIN
+ {XK_Arabic_ghain, 0x063A}, // ARABIC LETTER GHAIN
+ {XK_Arabic_tatweel, 0x0640}, // ARABIC TATWEEL
+ {XK_Arabic_feh, 0x0641}, // ARABIC LETTER FEH
+ {XK_Arabic_qaf, 0x0642}, // ARABIC LETTER QAF
+ {XK_Arabic_kaf, 0x0643}, // ARABIC LETTER KAF
+ {XK_Arabic_lam, 0x0644}, // ARABIC LETTER LAM
+ {XK_Arabic_meem, 0x0645}, // ARABIC LETTER MEEM
+ {XK_Arabic_noon, 0x0646}, // ARABIC LETTER NOON
+ {XK_Arabic_ha, 0x0647}, // ARABIC LETTER HEH
+ {XK_Arabic_waw, 0x0648}, // ARABIC LETTER WAW
+ {XK_Arabic_alefmaksura, 0x0649}, // ARABIC LETTER ALEF MAKSURA
+ {XK_Arabic_yeh, 0x064A}, // ARABIC LETTER YEH
+ {XK_Arabic_fathatan, 0x064B}, // ARABIC FATHATAN
+ {XK_Arabic_dammatan, 0x064C}, // ARABIC DAMMATAN
+ {XK_Arabic_kasratan, 0x064D}, // ARABIC KASRATAN
+ {XK_Arabic_fatha, 0x064E}, // ARABIC FATHA
+ {XK_Arabic_damma, 0x064F}, // ARABIC DAMMA
+ {XK_Arabic_kasra, 0x0650}, // ARABIC KASRA
+ {XK_Arabic_shadda, 0x0651}, // ARABIC SHADDA
+ {XK_Arabic_sukun, 0x0652}, // ARABIC SUKUN
+
+ // Cyrillic KeySyms partially map 1:1 to Unicode
+ {XK_Serbian_dje, 0x0452}, // CYRILLIC SMALL LETTER DJE
+ {XK_Macedonia_gje, 0x0453}, // CYRILLIC SMALL LETTER GJE
+ {XK_Cyrillic_io, 0x0451}, // CYRILLIC SMALL LETTER IO
+ {XK_Ukrainian_ie, 0x0454}, // CYRILLIC SMALL LETTER UKRAINIAN IE
+ {XK_Macedonia_dse, 0x0455}, // CYRILLIC SMALL LETTER DZE
+ {XK_Ukrainian_i, 0x0456}, // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ {XK_Ukrainian_yi, 0x0457}, // CYRILLIC SMALL LETTER YI
+ {XK_Cyrillic_je, 0x0458}, // CYRILLIC SMALL LETTER JE
+ {XK_Cyrillic_lje, 0x0459}, // CYRILLIC SMALL LETTER LJE
+ {XK_Cyrillic_nje, 0x045A}, // CYRILLIC SMALL LETTER NJE
+ {XK_Serbian_tshe, 0x045B}, // CYRILLIC SMALL LETTER TSHE
+ {XK_Macedonia_kje, 0x045C}, // CYRILLIC SMALL LETTER KJE
+ {XK_Ukrainian_ghe_with_upturn, 0x0491}, // CYRILLIC SMALL LETTER GHE WITH UPTURN
+ {XK_Byelorussian_shortu, 0x045E}, // CYRILLIC SMALL LETTER SHORT U
+ {XK_Cyrillic_dzhe, 0x045F}, // CYRILLIC SMALL LETTER DZHE
+ {XK_numerosign, 0x2116}, // NUMERO SIGN
+ {XK_Serbian_DJE, 0x0402}, // CYRILLIC CAPITAL LETTER DJE
+ {XK_Macedonia_GJE, 0x0403}, // CYRILLIC CAPITAL LETTER GJE
+ {XK_Cyrillic_IO, 0x0401}, // CYRILLIC CAPITAL LETTER IO
+ {XK_Ukrainian_IE, 0x0404}, // CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ {XK_Macedonia_DSE, 0x0405}, // CYRILLIC CAPITAL LETTER DZE
+ {XK_Ukrainian_I, 0x0406}, // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ {XK_Ukrainian_YI, 0x0407}, // CYRILLIC CAPITAL LETTER YI
+ {XK_Cyrillic_JE, 0x0408}, // CYRILLIC CAPITAL LETTER JE
+ {XK_Cyrillic_LJE, 0x0409}, // CYRILLIC CAPITAL LETTER LJE
+ {XK_Cyrillic_NJE, 0x040A}, // CYRILLIC CAPITAL LETTER NJE
+ {XK_Serbian_TSHE, 0x040B}, // CYRILLIC CAPITAL LETTER TSHE
+ {XK_Macedonia_KJE, 0x040C}, // CYRILLIC CAPITAL LETTER KJE
+ {XK_Ukrainian_GHE_WITH_UPTURN, 0x0490}, // CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ {XK_Byelorussian_SHORTU, 0x040E}, // CYRILLIC CAPITAL LETTER SHORT U
+ {XK_Cyrillic_DZHE, 0x040F}, // CYRILLIC CAPITAL LETTER DZHE
+ {XK_Cyrillic_yu, 0x044E}, // CYRILLIC SMALL LETTER YU
+ {XK_Cyrillic_a, 0x0430}, // CYRILLIC SMALL LETTER A
+ {XK_Cyrillic_be, 0x0431}, // CYRILLIC SMALL LETTER BE
+ {XK_Cyrillic_tse, 0x0446}, // CYRILLIC SMALL LETTER TSE
+ {XK_Cyrillic_de, 0x0434}, // CYRILLIC SMALL LETTER DE
+ {XK_Cyrillic_ie, 0x0435}, // CYRILLIC SMALL LETTER IE
+ {XK_Cyrillic_ef, 0x0444}, // CYRILLIC SMALL LETTER EF
+ {XK_Cyrillic_ghe, 0x0433}, // CYRILLIC SMALL LETTER GHE
+ {XK_Cyrillic_ha, 0x0445}, // CYRILLIC SMALL LETTER HA
+ {XK_Cyrillic_i, 0x0438}, // CYRILLIC SMALL LETTER I
+ {XK_Cyrillic_shorti, 0x0439}, // CYRILLIC SMALL LETTER SHORT I
+ {XK_Cyrillic_ka, 0x043A}, // CYRILLIC SMALL LETTER KA
+ {XK_Cyrillic_el, 0x043B}, // CYRILLIC SMALL LETTER EL
+ {XK_Cyrillic_em, 0x043C}, // CYRILLIC SMALL LETTER EM
+ {XK_Cyrillic_en, 0x043D}, // CYRILLIC SMALL LETTER EN
+ {XK_Cyrillic_o, 0x043E}, // CYRILLIC SMALL LETTER O
+ {XK_Cyrillic_pe, 0x043F}, // CYRILLIC SMALL LETTER PE
+ {XK_Cyrillic_ya, 0x044F}, // CYRILLIC SMALL LETTER YA
+ {XK_Cyrillic_er, 0x0440}, // CYRILLIC SMALL LETTER ER
+ {XK_Cyrillic_es, 0x0441}, // CYRILLIC SMALL LETTER ES
+ {XK_Cyrillic_te, 0x0442}, // CYRILLIC SMALL LETTER TE
+ {XK_Cyrillic_u, 0x0443}, // CYRILLIC SMALL LETTER U
+ {XK_Cyrillic_zhe, 0x0436}, // CYRILLIC SMALL LETTER ZHE
+ {XK_Cyrillic_ve, 0x0432}, // CYRILLIC SMALL LETTER VE
+ {XK_Cyrillic_softsign, 0x044C}, // CYRILLIC SMALL LETTER SOFT SIGN
+ {XK_Cyrillic_yeru, 0x044B}, // CYRILLIC SMALL LETTER YERU
+ {XK_Cyrillic_ze, 0x0437}, // CYRILLIC SMALL LETTER ZE
+ {XK_Cyrillic_sha, 0x0448}, // CYRILLIC SMALL LETTER SHA
+ {XK_Cyrillic_e, 0x044D}, // CYRILLIC SMALL LETTER E
+ {XK_Cyrillic_shcha, 0x0449}, // CYRILLIC SMALL LETTER SHCHA
+ {XK_Cyrillic_che, 0x0447}, // CYRILLIC SMALL LETTER CHE
+ {XK_Cyrillic_hardsign, 0x044A}, // CYRILLIC SMALL LETTER HARD SIGN
+ {XK_Cyrillic_YU, 0x042E}, // CYRILLIC CAPITAL LETTER YU
+ {XK_Cyrillic_A, 0x0410}, // CYRILLIC CAPITAL LETTER A
+ {XK_Cyrillic_BE, 0x0411}, // CYRILLIC CAPITAL LETTER BE
+ {XK_Cyrillic_TSE, 0x0426}, // CYRILLIC CAPITAL LETTER TSE
+ {XK_Cyrillic_DE, 0x0414}, // CYRILLIC CAPITAL LETTER DE
+ {XK_Cyrillic_IE, 0x0415}, // CYRILLIC CAPITAL LETTER IE
+ {XK_Cyrillic_EF, 0x0424}, // CYRILLIC CAPITAL LETTER EF
+ {XK_Cyrillic_GHE, 0x0413}, // CYRILLIC CAPITAL LETTER GHE
+ {XK_Cyrillic_HA, 0x0425}, // CYRILLIC CAPITAL LETTER HA
+ {XK_Cyrillic_I, 0x0418}, // CYRILLIC CAPITAL LETTER I
+ {XK_Cyrillic_SHORTI, 0x0419}, // CYRILLIC CAPITAL LETTER SHORT I
+ {XK_Cyrillic_KA, 0x041A}, // CYRILLIC CAPITAL LETTER KA
+ {XK_Cyrillic_EL, 0x041B}, // CYRILLIC CAPITAL LETTER EL
+ {XK_Cyrillic_EM, 0x041C}, // CYRILLIC CAPITAL LETTER EM
+ {XK_Cyrillic_EN, 0x041D}, // CYRILLIC CAPITAL LETTER EN
+ {XK_Cyrillic_O, 0x041E}, // CYRILLIC CAPITAL LETTER O
+ {XK_Cyrillic_PE, 0x041F}, // CYRILLIC CAPITAL LETTER PE
+ {XK_Cyrillic_YA, 0x042F}, // CYRILLIC CAPITAL LETTER YA
+ {XK_Cyrillic_ER, 0x0420}, // CYRILLIC CAPITAL LETTER ER
+ {XK_Cyrillic_ES, 0x0421}, // CYRILLIC CAPITAL LETTER ES
+ {XK_Cyrillic_TE, 0x0422}, // CYRILLIC CAPITAL LETTER TE
+ {XK_Cyrillic_U, 0x0423}, // CYRILLIC CAPITAL LETTER U
+ {XK_Cyrillic_ZHE, 0x0416}, // CYRILLIC CAPITAL LETTER ZHE
+ {XK_Cyrillic_VE, 0x0412}, // CYRILLIC CAPITAL LETTER VE
+ {XK_Cyrillic_SOFTSIGN, 0x042C}, // CYRILLIC CAPITAL LETTER SOFT SIGN
+ {XK_Cyrillic_YERU, 0x042B}, // CYRILLIC CAPITAL LETTER YERU
+ {XK_Cyrillic_ZE, 0x0417}, // CYRILLIC CAPITAL LETTER ZE
+ {XK_Cyrillic_SHA, 0x0428}, // CYRILLIC CAPITAL LETTER SHA
+ {XK_Cyrillic_E, 0x042D}, // CYRILLIC CAPITAL LETTER E
+ {XK_Cyrillic_SHCHA, 0x0429}, // CYRILLIC CAPITAL LETTER SHCHA
+ {XK_Cyrillic_CHE, 0x0427}, // CYRILLIC CAPITAL LETTER CHE
+ {XK_Cyrillic_HARDSIGN, 0x042A}, // CYRILLIC CAPITAL LETTER HARD SIGN
+
+ // Greek
+ {XK_Greek_ALPHAaccent, 0x0386}, // GREEK CAPITAL LETTER ALPHA WITH TONOS
+ {XK_Greek_EPSILONaccent, 0x0388}, // GREEK CAPITAL LETTER EPSILON WITH TONOS
+ {XK_Greek_ETAaccent, 0x0389}, // GREEK CAPITAL LETTER ETA WITH TONOS
+ {XK_Greek_IOTAaccent, 0x038A}, // GREEK CAPITAL LETTER IOTA WITH TONOS
+ {XK_Greek_IOTAdieresis, 0x03AA}, // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+ {XK_Greek_OMICRONaccent, 0x038C}, // GREEK CAPITAL LETTER OMICRON WITH TONOS
+ {XK_Greek_UPSILONaccent, 0x038E}, // GREEK CAPITAL LETTER UPSILON WITH TONOS
+ {XK_Greek_UPSILONdieresis, 0x03AB}, // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+ {XK_Greek_OMEGAaccent, 0x038F}, // GREEK CAPITAL LETTER OMEGA WITH TONOS
+ {XK_Greek_accentdieresis, 0x0385}, // GREEK DIALYTIKA TONOS
+ {XK_Greek_horizbar, 0x2015}, // HORIZONTAL BAR
+ {XK_Greek_alphaaccent, 0x03AC}, // GREEK SMALL LETTER ALPHA WITH TONOS
+ {XK_Greek_epsilonaccent, 0x03AD}, // GREEK SMALL LETTER EPSILON WITH TONOS
+ {XK_Greek_etaaccent, 0x03AE}, // GREEK SMALL LETTER ETA WITH TONOS
+ {XK_Greek_iotaaccent, 0x03AF}, // GREEK SMALL LETTER IOTA WITH TONOS
+ {XK_Greek_iotadieresis, 0x03CA}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA
+ {XK_Greek_iotaaccentdieresis, 0x0390}, // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+ {XK_Greek_omicronaccent, 0x03CC}, // GREEK SMALL LETTER OMICRON WITH TONOS
+ {XK_Greek_upsilonaccent, 0x03CD}, // GREEK SMALL LETTER UPSILON WITH TONOS
+ {XK_Greek_upsilondieresis, 0x03CB}, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+ {XK_Greek_upsilonaccentdieresis, 0x03B0}, // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+ {XK_Greek_omegaaccent, 0x03CE}, // GREEK SMALL LETTER OMEGA WITH TONOS
+ {XK_Greek_ALPHA, 0x0391}, // GREEK CAPITAL LETTER ALPHA
+ {XK_Greek_BETA, 0x0392}, // GREEK CAPITAL LETTER BETA
+ {XK_Greek_GAMMA, 0x0393}, // GREEK CAPITAL LETTER GAMMA
+ {XK_Greek_DELTA, 0x0394}, // GREEK CAPITAL LETTER DELTA
+ {XK_Greek_EPSILON, 0x0395}, // GREEK CAPITAL LETTER EPSILON
+ {XK_Greek_ZETA, 0x0396}, // GREEK CAPITAL LETTER ZETA
+ {XK_Greek_ETA, 0x0397}, // GREEK CAPITAL LETTER ETA
+ {XK_Greek_THETA, 0x0398}, // GREEK CAPITAL LETTER THETA
+ {XK_Greek_IOTA, 0x0399}, // GREEK CAPITAL LETTER IOTA
+ {XK_Greek_KAPPA, 0x039A}, // GREEK CAPITAL LETTER KAPPA
+ {XK_Greek_LAMDA, 0x039B}, // GREEK CAPITAL LETTER LAMDA
+ {XK_Greek_LAMBDA, 0x039B}, // GREEK CAPITAL LETTER LAMDA
+ {XK_Greek_MU, 0x039C}, // GREEK CAPITAL LETTER MU
+ {XK_Greek_NU, 0x039D}, // GREEK CAPITAL LETTER NU
+ {XK_Greek_XI, 0x039E}, // GREEK CAPITAL LETTER XI
+ {XK_Greek_OMICRON, 0x039F}, // GREEK CAPITAL LETTER OMICRON
+ {XK_Greek_PI, 0x03A0}, // GREEK CAPITAL LETTER PI
+ {XK_Greek_RHO, 0x03A1}, // GREEK CAPITAL LETTER RHO
+ {XK_Greek_SIGMA, 0x03A3}, // GREEK CAPITAL LETTER SIGMA
+ {XK_Greek_TAU, 0x03A4}, // GREEK CAPITAL LETTER TAU
+ {XK_Greek_UPSILON, 0x03A5}, // GREEK CAPITAL LETTER UPSILON
+ {XK_Greek_PHI, 0x03A6}, // GREEK CAPITAL LETTER PHI
+ {XK_Greek_CHI, 0x03A7}, // GREEK CAPITAL LETTER CHI
+ {XK_Greek_PSI, 0x03A8}, // GREEK CAPITAL LETTER PSI
+ {XK_Greek_OMEGA, 0x03A9}, // GREEK CAPITAL LETTER OMEGA
+ {XK_Greek_alpha, 0x03B1}, // GREEK SMALL LETTER ALPHA
+ {XK_Greek_beta, 0x03B2}, // GREEK SMALL LETTER BETA
+ {XK_Greek_gamma, 0x03B3}, // GREEK SMALL LETTER GAMMA
+ {XK_Greek_delta, 0x03B4}, // GREEK SMALL LETTER DELTA
+ {XK_Greek_epsilon, 0x03B5}, // GREEK SMALL LETTER EPSILON
+ {XK_Greek_zeta, 0x03B6}, // GREEK SMALL LETTER ZETA
+ {XK_Greek_eta, 0x03B7}, // GREEK SMALL LETTER ETA
+ {XK_Greek_theta, 0x03B8}, // GREEK SMALL LETTER THETA
+ {XK_Greek_iota, 0x03B9}, // GREEK SMALL LETTER IOTA
+ {XK_Greek_kappa, 0x03BA}, // GREEK SMALL LETTER KAPPA
+ {XK_Greek_lamda, 0x03BB}, // GREEK SMALL LETTER LAMDA
+ {XK_Greek_lambda, 0x03BB}, // GREEK SMALL LETTER LAMDA
+ {XK_Greek_mu, 0x03BC}, // GREEK SMALL LETTER MU
+ {XK_Greek_nu, 0x03BD}, // GREEK SMALL LETTER NU
+ {XK_Greek_xi, 0x03BE}, // GREEK SMALL LETTER XI
+ {XK_Greek_omicron, 0x03BF}, // GREEK SMALL LETTER OMICRON
+ {XK_Greek_pi, 0x03C0}, // GREEK SMALL LETTER PI
+ {XK_Greek_rho, 0x03C1}, // GREEK SMALL LETTER RHO
+ {XK_Greek_sigma, 0x03C3}, // GREEK SMALL LETTER SIGMA
+ {XK_Greek_finalsmallsigma, 0x03C2}, // GREEK SMALL LETTER FINAL SIGMA
+ {XK_Greek_tau, 0x03C4}, // GREEK SMALL LETTER TAU
+ {XK_Greek_upsilon, 0x03C5}, // GREEK SMALL LETTER UPSILON
+ {XK_Greek_phi, 0x03C6}, // GREEK SMALL LETTER PHI
+ {XK_Greek_chi, 0x03C7}, // GREEK SMALL LETTER CHI
+ {XK_Greek_psi, 0x03C8}, // GREEK SMALL LETTER PSI
+ {XK_Greek_omega, 0x03C9}, // GREEK SMALL LETTER OMEGA
+
+ // Technical
+ {XK_leftradical, 0x23B7}, // RADICAL SYMBOL BOTTOM
+ {XK_topleftradical, 0x250C}, // BOX DRAWINGS LIGHT DOWN AND RIGHT
+ {XK_horizconnector, 0x2500}, // BOX DRAWINGS LIGHT HORIZONTAL
+ {XK_topintegral, 0x2320}, // TOP HALF INTEGRAL
+ {XK_botintegral, 0x2321}, // BOTTOM HALF INTEGRAL
+ {XK_vertconnector, 0x2502}, // BOX DRAWINGS LIGHT VERTICAL
+ {XK_topleftsqbracket, 0x23A1}, // LEFT SQUARE BRACKET UPPER CORNER
+ {XK_botleftsqbracket, 0x23A3}, // LEFT SQUARE BRACKET LOWER CORNER
+ {XK_toprightsqbracket, 0x23A4}, // RIGHT SQUARE BRACKET UPPER CORNER
+ {XK_botrightsqbracket, 0x23A6}, // RIGHT SQUARE BRACKET LOWER CORNER
+ {XK_topleftparens, 0x239B}, // LEFT PARENTHESIS UPPER HOOK
+ {XK_botleftparens, 0x239D}, // LEFT PARENTHESIS LOWER HOOK
+ {XK_toprightparens, 0x239E}, // RIGHT PARENTHESIS UPPER HOOK
+ {XK_botrightparens, 0x23A0}, // RIGHT PARENTHESIS LOWER HOOK
+ {XK_leftmiddlecurlybrace, 0x23A8}, // LEFT CURLY BRACKET MIDDLE PIECE
+ {XK_rightmiddlecurlybrace, 0x23AC}, // RIGHT CURLY BRACKET MIDDLE PIECE
+ {XK_lessthanequal, 0x2264}, // LESS-THAN OR EQUAL TO
+ {XK_notequal, 0x2260}, // NOT EQUAL TO
+ {XK_greaterthanequal, 0x2265}, // GREATER-THAN OR EQUAL TO
+ {XK_integral, 0x222B}, // INTEGRAL
+ {XK_therefore, 0x2234}, // THEREFORE
+ {XK_variation, 0x221D}, // PROPORTIONAL TO
+ {XK_infinity, 0x221E}, // INFINITY
+ {XK_nabla, 0x2207}, // NABLA
+ {XK_approximate, 0x223C}, // TILDE OPERATOR
+ {XK_similarequal, 0x2243}, // ASYMPTOTICALLY EQUAL TO
+ {XK_ifonlyif, 0x21D4}, // LEFT RIGHT DOUBLE ARROW
+ {XK_implies, 0x21D2}, // RIGHTWARDS DOUBLE ARROW
+ {XK_identical, 0x2261}, // IDENTICAL TO
+ {XK_radical, 0x221A}, // SQUARE ROOT
+ {XK_includedin, 0x2282}, // SUBSET OF
+ {XK_includes, 0x2283}, // SUPERSET OF
+ {XK_intersection, 0x2229}, // INTERSECTION
+ {XK_union, 0x222A}, // UNION
+ {XK_logicaland, 0x2227}, // LOGICAL AND
+ {XK_logicalor, 0x2228}, // LOGICAL OR
+ {XK_partialderivative, 0x2202}, // PARTIAL DIFFERENTIAL
+ {XK_function, 0x0192}, // LATIN SMALL LETTER F WITH HOOK
+ {XK_leftarrow, 0x2190}, // LEFTWARDS ARROW
+ {XK_uparrow, 0x2191}, // UPWARDS ARROW
+ {XK_rightarrow, 0x2192}, // RIGHTWARDS ARROW
+ {XK_downarrow, 0x2193}, // DOWNWARDS ARROW
+
+ // Special
+ {XK_soliddiamond, 0x25C6}, // BLACK DIAMOND
+ {XK_checkerboard, 0x2592}, // MEDIUM SHADE
+ {XK_ht, 0x2409}, // SYMBOL FOR HORIZONTAL TABULATION
+ {XK_ff, 0x240C}, // SYMBOL FOR FORM FEED
+ {XK_cr, 0x240D}, // SYMBOL FOR CARRIAGE RETURN
+ {XK_lf, 0x240A}, // SYMBOL FOR LINE FEED
+ {XK_nl, 0x2424}, // SYMBOL FOR NEWLINE
+ {XK_vt, 0x240B}, // SYMBOL FOR VERTICAL TABULATION
+ {XK_lowrightcorner, 0x2518}, // BOX DRAWINGS LIGHT UP AND LEFT
+ {XK_uprightcorner, 0x2510}, // BOX DRAWINGS LIGHT DOWN AND LEFT
+ {XK_upleftcorner, 0x250C}, // BOX DRAWINGS LIGHT DOWN AND RIGHT
+ {XK_lowleftcorner, 0x2514}, // BOX DRAWINGS LIGHT UP AND RIGHT
+ {XK_crossinglines, 0x253C}, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ {XK_horizlinescan1, 0x23BA}, // HORIZONTAL SCAN LINE-1
+ {XK_horizlinescan3, 0x23BB}, // HORIZONTAL SCAN LINE-3
+ {XK_horizlinescan5, 0x2500}, // BOX DRAWINGS LIGHT HORIZONTAL
+ {XK_horizlinescan7, 0x23BC}, // HORIZONTAL SCAN LINE-7
+ {XK_horizlinescan9, 0x23BD}, // HORIZONTAL SCAN LINE-9
+ {XK_leftt, 0x251C}, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ {XK_rightt, 0x2524}, // BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ {XK_bott, 0x2534}, // BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ {XK_topt, 0x252C}, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ {XK_vertbar, 0x2502}, // BOX DRAWINGS LIGHT VERTICAL
+
+ // Publishing
+ {XK_emspace, 0x2003}, // EM SPACE
+ {XK_enspace, 0x2002}, // EN SPACE
+ {XK_em3space, 0x2004}, // THREE-PER-EM SPACE
+ {XK_em4space, 0x2005}, // FOUR-PER-EM SPACE
+ {XK_digitspace, 0x2007}, // FIGURE SPACE
+ {XK_punctspace, 0x2008}, // PUNCTUATION SPACE
+ {XK_thinspace, 0x2009}, // THIN SPACE
+ {XK_hairspace, 0x200A}, // HAIR SPACE
+ {XK_emdash, 0x2014}, // EM DASH
+ {XK_endash, 0x2013}, // EN DASH
+ {XK_signifblank, 0x2423}, // OPEN BOX
+ {XK_ellipsis, 0x2026}, // HORIZONTAL ELLIPSIS
+ {XK_doubbaselinedot, 0x2025}, // TWO DOT LEADER
+ {XK_onethird, 0x2153}, // VULGAR FRACTION ONE THIRD
+ {XK_twothirds, 0x2154}, // VULGAR FRACTION TWO THIRDS
+ {XK_onefifth, 0x2155}, // VULGAR FRACTION ONE FIFTH
+ {XK_twofifths, 0x2156}, // VULGAR FRACTION TWO FIFTHS
+ {XK_threefifths, 0x2157}, // VULGAR FRACTION THREE FIFTHS
+ {XK_fourfifths, 0x2158}, // VULGAR FRACTION FOUR FIFTHS
+ {XK_onesixth, 0x2159}, // VULGAR FRACTION ONE SIXTH
+ {XK_fivesixths, 0x215A}, // VULGAR FRACTION FIVE SIXTHS
+ {XK_careof, 0x2105}, // CARE OF
+ {XK_figdash, 0x2012}, // FIGURE DASH
+ {XK_leftanglebracket, 0x27E8}, // MATHEMATICAL LEFT ANGLE BRACKET
+ {XK_decimalpoint, 0x002E}, // FULL STOP
+ {XK_rightanglebracket, 0x27E9}, // MATHEMATICAL RIGHT ANGLE BRACKET
+ {XK_oneeighth, 0x215B}, // VULGAR FRACTION ONE EIGHTH
+ {XK_threeeighths, 0x215C}, // VULGAR FRACTION THREE EIGHTHS
+ {XK_fiveeighths, 0x215D}, // VULGAR FRACTION FIVE EIGHTHS
+ {XK_seveneighths, 0x215E}, // VULGAR FRACTION SEVEN EIGHTHS
+ {XK_trademark, 0x2122}, // TRADE MARK SIGN
+ {XK_signaturemark, 0x2613}, // SALTIRE
+ {XK_leftopentriangle, 0x25C1}, // WHITE LEFT-POINTING TRIANGLE
+ {XK_rightopentriangle, 0x25B7}, // WHITE RIGHT-POINTING TRIANGLE
+ {XK_emopencircle, 0x25CB}, // WHITE CIRCLE
+ {XK_emopenrectangle, 0x25AF}, // WHITE VERTICAL RECTANGLE
+ {XK_leftsinglequotemark, 0x2018}, // LEFT SINGLE QUOTATION MARK
+ {XK_rightsinglequotemark, 0x2019}, // RIGHT SINGLE QUOTATION MARK
+ {XK_leftdoublequotemark, 0x201C}, // LEFT DOUBLE QUOTATION MARK
+ {XK_rightdoublequotemark, 0x201D}, // RIGHT DOUBLE QUOTATION MARK
+ {XK_prescription, 0x211E}, // PRESCRIPTION TAKE
+ {XK_minutes, 0x2032}, // PRIME
+ {XK_seconds, 0x2033}, // DOUBLE PRIME
+ {XK_latincross, 0x271D}, // LATIN CROSS
+ {XK_filledrectbullet, 0x25AC}, // BLACK RECTANGLE
+ {XK_filledlefttribullet, 0x25C0}, // BLACK LEFT-POINTING TRIANGLE
+ {XK_filledrighttribullet, 0x25B6}, // BLACK RIGHT-POINTING TRIANGLE
+ {XK_emfilledcircle, 0x25CF}, // BLACK CIRCLE
+ {XK_emfilledrect, 0x25AE}, // BLACK VERTICAL RECTANGLE
+ {XK_enopencircbullet, 0x25E6}, // WHITE BULLET
+ {XK_enopensquarebullet, 0x25AB}, // WHITE SMALL SQUARE
+ {XK_openrectbullet, 0x25AD}, // WHITE RECTANGLE
+ {XK_opentribulletup, 0x25B3}, // WHITE UP-POINTING TRIANGLE
+ {XK_opentribulletdown, 0x25BD}, // WHITE DOWN-POINTING TRIANGLE
+ {XK_openstar, 0x2606}, // WHITE STAR
+ {XK_enfilledcircbullet, 0x2022}, // BULLET
+ {XK_enfilledsqbullet, 0x25AA}, // BLACK SMALL SQUARE
+ {XK_filledtribulletup, 0x25B2}, // BLACK UP-POINTING TRIANGLE
+ {XK_filledtribulletdown, 0x25BC}, // BLACK DOWN-POINTING TRIANGLE
+ {XK_leftpointer, 0x261C}, // WHITE LEFT POINTING INDEX
+ {XK_rightpointer, 0x261E}, // WHITE RIGHT POINTING INDEX
+ {XK_club, 0x2663}, // BLACK CLUB SUIT
+ {XK_diamond, 0x2666}, // BLACK DIAMOND SUIT
+ {XK_heart, 0x2665}, // BLACK HEART SUIT
+ {XK_maltesecross, 0x2720}, // MALTESE CROSS
+ {XK_dagger, 0x2020}, // DAGGER
+ {XK_doubledagger, 0x2021}, // DOUBLE DAGGER
+ {XK_checkmark, 0x2713}, // CHECK MARK
+ {XK_ballotcross, 0x2717}, // BALLOT X
+ {XK_musicalsharp, 0x266F}, // MUSIC SHARP SIGN
+ {XK_musicalflat, 0x266D}, // MUSIC FLAT SIGN
+ {XK_malesymbol, 0x2642}, // MALE SIGN
+ {XK_femalesymbol, 0x2640}, // FEMALE SIGN
+ {XK_telephone, 0x260E}, // BLACK TELEPHONE
+ {XK_telephonerecorder, 0x2315}, // TELEPHONE RECORDER
+ {XK_phonographcopyright, 0x2117}, // SOUND RECORDING COPYRIGHT
+ {XK_caret, 0x2038}, // CARET
+ {XK_singlelowquotemark, 0x201A}, // SINGLE LOW-9 QUOTATION MARK
+ {XK_doublelowquotemark, 0x201E}, // DOUBLE LOW-9 QUOTATION MARK
+
+ // APL
+ {XK_leftcaret, 0x003C}, // LESS-THAN SIGN
+ {XK_rightcaret, 0x003E}, // GREATER-THAN SIGN
+ {XK_downcaret, 0x2228}, // LOGICAL OR
+ {XK_upcaret, 0x2227}, // LOGICAL AND
+ {XK_overbar, 0x00AF}, // MACRON
+ {XK_downtack, 0x22A4}, // DOWN TACK
+ {XK_upshoe, 0x2229}, // INTERSECTION
+ {XK_downstile, 0x230A}, // LEFT FLOOR
+ {XK_underbar, 0x005F}, // LOW LINE
+ {XK_jot, 0x2218}, // RING OPERATOR
+ {XK_quad, 0x2395}, // APL FUNCTIONAL SYMBOL QUAD
+ {XK_uptack, 0x22A5}, // UP TACK
+ {XK_circle, 0x25CB}, // WHITE CIRCLE
+ {XK_upstile, 0x2308}, // LEFT CEILING
+ {XK_downshoe, 0x222A}, // UNION
+ {XK_rightshoe, 0x2283}, // SUPERSET OF
+ {XK_leftshoe, 0x2282}, // SUBSET OF
+ {XK_lefttack, 0x22A3}, // LEFT TACK
+ {XK_righttack, 0x22A2}, // RIGHT TACK
+
+ // Hebrew
+ {XK_hebrew_doublelowline, 0x2017}, // DOUBLE LOW LINE
+ {XK_hebrew_aleph, 0x05D0}, // HEBREW LETTER ALEF
+ {XK_hebrew_bet, 0x05D1}, // HEBREW LETTER BET
+ {XK_hebrew_gimel, 0x05D2}, // HEBREW LETTER GIMEL
+ {XK_hebrew_dalet, 0x05D3}, // HEBREW LETTER DALET
+ {XK_hebrew_he, 0x05D4}, // HEBREW LETTER HE
+ {XK_hebrew_waw, 0x05D5}, // HEBREW LETTER VAV
+ {XK_hebrew_zain, 0x05D6}, // HEBREW LETTER ZAYIN
+ {XK_hebrew_chet, 0x05D7}, // HEBREW LETTER HET
+ {XK_hebrew_tet, 0x05D8}, // HEBREW LETTER TET
+ {XK_hebrew_yod, 0x05D9}, // HEBREW LETTER YOD
+ {XK_hebrew_finalkaph, 0x05DA}, // HEBREW LETTER FINAL KAF
+ {XK_hebrew_kaph, 0x05DB}, // HEBREW LETTER KAF
+ {XK_hebrew_lamed, 0x05DC}, // HEBREW LETTER LAMED
+ {XK_hebrew_finalmem, 0x05DD}, // HEBREW LETTER FINAL MEM
+ {XK_hebrew_mem, 0x05DE}, // HEBREW LETTER MEM
+ {XK_hebrew_finalnun, 0x05DF}, // HEBREW LETTER FINAL NUN
+ {XK_hebrew_nun, 0x05E0}, // HEBREW LETTER NUN
+ {XK_hebrew_samech, 0x05E1}, // HEBREW LETTER SAMEKH
+ {XK_hebrew_ayin, 0x05E2}, // HEBREW LETTER AYIN
+ {XK_hebrew_finalpe, 0x05E3}, // HEBREW LETTER FINAL PE
+ {XK_hebrew_pe, 0x05E4}, // HEBREW LETTER PE
+ {XK_hebrew_finalzade, 0x05E5}, // HEBREW LETTER FINAL TSADI
+ {XK_hebrew_zade, 0x05E6}, // HEBREW LETTER TSADI
+ {XK_hebrew_qoph, 0x05E7}, // HEBREW LETTER QOF
+ {XK_hebrew_resh, 0x05E8}, // HEBREW LETTER RESH
+ {XK_hebrew_shin, 0x05E9}, // HEBREW LETTER SHIN
+ {XK_hebrew_taw, 0x05EA}, // HEBREW LETTER TAV
+
+ // Thai
+ {XK_Thai_kokai, 0x0E01}, // THAI CHARACTER KO KAI
+ {XK_Thai_khokhai, 0x0E02}, // THAI CHARACTER KHO KHAI
+ {XK_Thai_khokhuat, 0x0E03}, // THAI CHARACTER KHO KHUAT
+ {XK_Thai_khokhwai, 0x0E04}, // THAI CHARACTER KHO KHWAI
+ {XK_Thai_khokhon, 0x0E05}, // THAI CHARACTER KHO KHON
+ {XK_Thai_khorakhang, 0x0E06}, // THAI CHARACTER KHO RAKHANG
+ {XK_Thai_ngongu, 0x0E07}, // THAI CHARACTER NGO NGU
+ {XK_Thai_chochan, 0x0E08}, // THAI CHARACTER CHO CHAN
+ {XK_Thai_choching, 0x0E09}, // THAI CHARACTER CHO CHING
+ {XK_Thai_chochang, 0x0E0A}, // THAI CHARACTER CHO CHANG
+ {XK_Thai_soso, 0x0E0B}, // THAI CHARACTER SO SO
+ {XK_Thai_chochoe, 0x0E0C}, // THAI CHARACTER CHO CHOE
+ {XK_Thai_yoying, 0x0E0D}, // THAI CHARACTER YO YING
+ {XK_Thai_dochada, 0x0E0E}, // THAI CHARACTER DO CHADA
+ {XK_Thai_topatak, 0x0E0F}, // THAI CHARACTER TO PATAK
+ {XK_Thai_thothan, 0x0E10}, // THAI CHARACTER THO THAN
+ {XK_Thai_thonangmontho, 0x0E11}, // THAI CHARACTER THO NANGMONTHO
+ {XK_Thai_thophuthao, 0x0E12}, // THAI CHARACTER THO PHUTHAO
+ {XK_Thai_nonen, 0x0E13}, // THAI CHARACTER NO NEN
+ {XK_Thai_dodek, 0x0E14}, // THAI CHARACTER DO DEK
+ {XK_Thai_totao, 0x0E15}, // THAI CHARACTER TO TAO
+ {XK_Thai_thothung, 0x0E16}, // THAI CHARACTER THO THUNG
+ {XK_Thai_thothahan, 0x0E17}, // THAI CHARACTER THO THAHAN
+ {XK_Thai_thothong, 0x0E18}, // THAI CHARACTER THO THONG
+ {XK_Thai_nonu, 0x0E19}, // THAI CHARACTER NO NU
+ {XK_Thai_bobaimai, 0x0E1A}, // THAI CHARACTER BO BAIMAI
+ {XK_Thai_popla, 0x0E1B}, // THAI CHARACTER PO PLA
+ {XK_Thai_phophung, 0x0E1C}, // THAI CHARACTER PHO PHUNG
+ {XK_Thai_fofa, 0x0E1D}, // THAI CHARACTER FO FA
+ {XK_Thai_phophan, 0x0E1E}, // THAI CHARACTER PHO PHAN
+ {XK_Thai_fofan, 0x0E1F}, // THAI CHARACTER FO FAN
+ {XK_Thai_phosamphao, 0x0E20}, // THAI CHARACTER PHO SAMPHAO
+ {XK_Thai_moma, 0x0E21}, // THAI CHARACTER MO MA
+ {XK_Thai_yoyak, 0x0E22}, // THAI CHARACTER YO YAK
+ {XK_Thai_rorua, 0x0E23}, // THAI CHARACTER RO RUA
+ {XK_Thai_ru, 0x0E24}, // THAI CHARACTER RU
+ {XK_Thai_loling, 0x0E25}, // THAI CHARACTER LO LING
+ {XK_Thai_lu, 0x0E26}, // THAI CHARACTER LU
+ {XK_Thai_wowaen, 0x0E27}, // THAI CHARACTER WO WAEN
+ {XK_Thai_sosala, 0x0E28}, // THAI CHARACTER SO SALA
+ {XK_Thai_sorusi, 0x0E29}, // THAI CHARACTER SO RUSI
+ {XK_Thai_sosua, 0x0E2A}, // THAI CHARACTER SO SUA
+ {XK_Thai_hohip, 0x0E2B}, // THAI CHARACTER HO HIP
+ {XK_Thai_lochula, 0x0E2C}, // THAI CHARACTER LO CHULA
+ {XK_Thai_oang, 0x0E2D}, // THAI CHARACTER O ANG
+ {XK_Thai_honokhuk, 0x0E2E}, // THAI CHARACTER HO NOKHUK
+ {XK_Thai_paiyannoi, 0x0E2F}, // THAI CHARACTER PAIYANNOI
+ {XK_Thai_saraa, 0x0E30}, // THAI CHARACTER SARA A
+ {XK_Thai_maihanakat, 0x0E31}, // THAI CHARACTER MAI HAN-AKAT
+ {XK_Thai_saraaa, 0x0E32}, // THAI CHARACTER SARA AA
+ {XK_Thai_saraam, 0x0E33}, // THAI CHARACTER SARA AM
+ {XK_Thai_sarai, 0x0E34}, // THAI CHARACTER SARA I
+ {XK_Thai_saraii, 0x0E35}, // THAI CHARACTER SARA II
+ {XK_Thai_saraue, 0x0E36}, // THAI CHARACTER SARA UE
+ {XK_Thai_sarauee, 0x0E37}, // THAI CHARACTER SARA UEE
+ {XK_Thai_sarau, 0x0E38}, // THAI CHARACTER SARA U
+ {XK_Thai_sarauu, 0x0E39}, // THAI CHARACTER SARA UU
+ {XK_Thai_phinthu, 0x0E3A}, // THAI CHARACTER PHINTHU
+ {XK_Thai_baht, 0x0E3F}, // THAI CURRENCY SYMBOL BAHT
+ {XK_Thai_sarae, 0x0E40}, // THAI CHARACTER SARA E
+ {XK_Thai_saraae, 0x0E41}, // THAI CHARACTER SARA AE
+ {XK_Thai_sarao, 0x0E42}, // THAI CHARACTER SARA O
+ {XK_Thai_saraaimaimuan, 0x0E43}, // THAI CHARACTER SARA AI MAIMUAN
+ {XK_Thai_saraaimaimalai, 0x0E44}, // THAI CHARACTER SARA AI MAIMALAI
+ {XK_Thai_lakkhangyao, 0x0E45}, // THAI CHARACTER LAKKHANGYAO
+ {XK_Thai_maiyamok, 0x0E46}, // THAI CHARACTER MAIYAMOK
+ {XK_Thai_maitaikhu, 0x0E47}, // THAI CHARACTER MAITAIKHU
+ {XK_Thai_maiek, 0x0E48}, // THAI CHARACTER MAI EK
+ {XK_Thai_maitho, 0x0E49}, // THAI CHARACTER MAI THO
+ {XK_Thai_maitri, 0x0E4A}, // THAI CHARACTER MAI TRI
+ {XK_Thai_maichattawa, 0x0E4B}, // THAI CHARACTER MAI CHATTAWA
+ {XK_Thai_thanthakhat, 0x0E4C}, // THAI CHARACTER THANTHAKHAT
+ {XK_Thai_nikhahit, 0x0E4D}, // THAI CHARACTER NIKHAHIT
+ {XK_Thai_leksun, 0x0E50}, // THAI DIGIT ZERO
+ {XK_Thai_leknung, 0x0E51}, // THAI DIGIT ONE
+ {XK_Thai_leksong, 0x0E52}, // THAI DIGIT TWO
+ {XK_Thai_leksam, 0x0E53}, // THAI DIGIT THREE
+ {XK_Thai_leksi, 0x0E54}, // THAI DIGIT FOUR
+ {XK_Thai_lekha, 0x0E55}, // THAI DIGIT FIVE
+ {XK_Thai_lekhok, 0x0E56}, // THAI DIGIT SIX
+ {XK_Thai_lekchet, 0x0E57}, // THAI DIGIT SEVEN
+ {XK_Thai_lekpaet, 0x0E58}, // THAI DIGIT EIGHT
+ {XK_Thai_lekkao, 0x0E59}, // THAI DIGIT NINE
+
+ // Korean
+ {XK_Korean_Won, 0x20A9}, // WON SIGN
+
+ // Armenian KeySyms map 1:1 to Unicode
+
+ // Georgian KeySyms map 1:1 to Unicode
+
+ // Azeri KeySyms map 1:1 to Unicode
+
+ // Vietnamese KeySyms map 1:1 to Unicode
+
+ // Currency KeySyms partially map 1:1 to Unicode
+ {XK_EuroSign, 0x20AC}, // EURO SIGN
+
+ // Mathematical KeySyms map 1:1 to Unicode
+
+ // Braille KeySyms map 1:1 to Unicode
+
+ // Sinhala KeySyms map 1:1 to Unicode
+};
+
+class KeySymToUnicode {
+ public:
+ KeySymToUnicode()
+ : keysym_to_unicode_map_(arraysize(g_keysym_to_unicode_table)) {
+ for (size_t i = 0; i < arraysize(g_keysym_to_unicode_table); ++i) {
+ keysym_to_unicode_map_[g_keysym_to_unicode_table[i].keysym] =
+ g_keysym_to_unicode_table[i].unicode;
+ }
+ }
+
+ uint16_t UnicodeFromKeySym(KeySym keysym) const {
+ // Latin-1 characters have the same representation.
+ if ((0x0020 <= keysym && keysym <= 0x007e) ||
+ (0x00a0 <= keysym && keysym <= 0x00ff))
+ return static_cast<uint16_t>(keysym);
+
+ // Unicode-style KeySyms.
+ if ((keysym & 0xffe00000) == 0x01000000) {
+ uint32_t unicode = static_cast<uint32_t>(keysym & 0x1fffff);
+ if (unicode & ~0xffff)
+ return 0; // We don't support characters outside the Basic Plane.
+ return static_cast<uint16_t>(unicode);
+ }
+
+ // Other KeySyms which are not Unicode-style.
+ KeySymToUnicodeMap::const_iterator i =
+ keysym_to_unicode_map_.find(keysym);
+ return i != keysym_to_unicode_map_.end() ? i->second : 0;
+ }
+
+ private:
+ typedef std::unordered_map<KeySym, uint16_t> KeySymToUnicodeMap;
+ KeySymToUnicodeMap keysym_to_unicode_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeySymToUnicode);
+};
+
+static base::LazyInstance<KeySymToUnicode>::Leaky g_keysym_to_unicode =
+ LAZY_INSTANCE_INITIALIZER;
+
+uint16_t GetUnicodeCharacterFromXKeySym(unsigned long keysym) {
+ return g_keysym_to_unicode.Get().UnicodeFromKeySym(
+ static_cast<KeySym>(keysym));
+}
+
+} // namespace ui
diff --git a/ui/events/x/keysym_to_unicode.h b/ui/events/x/keysym_to_unicode.h
new file mode 100644
index 0000000..ba530b6
--- /dev/null
+++ b/ui/events/x/keysym_to_unicode.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_X_KEYSYM_TO_UNICODE_H_
+#define UI_EVENTS_X_KEYSYM_TO_UNICODE_H_
+
+#include <cstdint>
+
+namespace ui {
+
+// Returns a Unicode character corresponding to the given |keysym|. If the
+// |keysym| doesn't represent a printable character, returns zero. We don't
+// support characters outside the Basic Plane, and this function returns zero
+// in that case.
+uint16_t GetUnicodeCharacterFromXKeySym(unsigned long keysym);
+
+} // namespace ui
+
+#endif // UI_EVENTS_X_KEYSYM_TO_UNICODE_H_
diff --git a/ui/events/x/touch_factory_x11.cc b/ui/events/x/touch_factory_x11.cc
new file mode 100644
index 0000000..10cb890
--- /dev/null
+++ b/ui/events/x/touch_factory_x11.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/events/x/touch_factory_x11.h"
+
+#include <X11/Xatom.h>
+#include <X11/cursorfont.h>
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XIproto.h>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/sys_info.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/device_list_cache_x.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+TouchFactory::TouchFactory()
+ : pointer_device_lookup_(),
+ touch_events_disabled_(false),
+ touch_device_list_(),
+ max_touch_points_(-1),
+ virtual_core_keyboard_device_(-1),
+ id_generator_(0) {
+ if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
+ return;
+
+ XDisplay* display = gfx::GetXDisplay();
+ UpdateDeviceList(display);
+
+ CommandLine* cmdline = CommandLine::ForCurrentProcess();
+ touch_events_disabled_ = cmdline->HasSwitch(switches::kTouchEvents) &&
+ cmdline->GetSwitchValueASCII(switches::kTouchEvents) ==
+ switches::kTouchEventsDisabled;
+}
+
+TouchFactory::~TouchFactory() {
+}
+
+// static
+TouchFactory* TouchFactory::GetInstance() {
+ return Singleton<TouchFactory>::get();
+}
+
+// static
+void TouchFactory::SetTouchDeviceListFromCommandLine() {
+ // Get a list of pointer-devices that should be treated as touch-devices.
+ // This is primarily used for testing/debugging touch-event processing when a
+ // touch-device isn't available.
+ std::string touch_devices =
+ CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kTouchDevices);
+
+ if (!touch_devices.empty()) {
+ std::vector<std::string> devs;
+ std::vector<unsigned int> device_ids;
+ unsigned int devid;
+ base::SplitString(touch_devices, ',', &devs);
+ for (std::vector<std::string>::iterator iter = devs.begin();
+ iter != devs.end(); ++iter) {
+ if (base::StringToInt(*iter, reinterpret_cast<int*>(&devid)))
+ device_ids.push_back(devid);
+ else
+ DLOG(WARNING) << "Invalid touch-device id: " << *iter;
+ }
+ ui::TouchFactory::GetInstance()->SetTouchDeviceList(device_ids);
+ }
+}
+
+void TouchFactory::UpdateDeviceList(Display* display) {
+ // Detect touch devices.
+ touch_device_lookup_.reset();
+ touch_device_list_.clear();
+ touchscreen_ids_.clear();
+ max_touch_points_ = -1;
+
+#if !defined(USE_XI2_MT)
+ // NOTE: The new API for retrieving the list of devices (XIQueryDevice) does
+ // not provide enough information to detect a touch device. As a result, the
+ // old version of query function (XListInputDevices) is used instead.
+ // If XInput2 is not supported, this will return null (with count of -1) so
+ // we assume there cannot be any touch devices.
+ // With XI2.1 or older, we allow only single touch devices.
+ XDeviceList dev_list =
+ DeviceListCacheX::GetInstance()->GetXDeviceList(display);
+ Atom xi_touchscreen = XInternAtom(display, XI_TOUCHSCREEN, false);
+ for (int i = 0; i < dev_list.count; i++) {
+ if (dev_list[i].type == xi_touchscreen) {
+ touch_device_lookup_[dev_list[i].id] = true;
+ touch_device_list_[dev_list[i].id] = false;
+ }
+ }
+#endif
+
+ if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
+ return;
+
+ // Instead of asking X for the list of devices all the time, let's maintain a
+ // list of pointer devices we care about.
+ // It should not be necessary to select for slave devices. XInput2 provides
+ // enough information to the event callback to decide which slave device
+ // triggered the event, thus decide whether the 'pointer event' is a
+ // 'mouse event' or a 'touch event'.
+ // However, on some desktops, some events from a master pointer are
+ // not delivered to the client. So we select for slave devices instead.
+ // If the touch device has 'GrabDevice' set and 'SendCoreEvents' unset (which
+ // is possible), then the device is detected as a floating device, and a
+ // floating device is not connected to a master device. So it is necessary to
+ // also select on the floating devices.
+ pointer_device_lookup_.reset();
+ XIDeviceList xi_dev_list =
+ DeviceListCacheX::GetInstance()->GetXI2DeviceList(display);
+ for (int i = 0; i < xi_dev_list.count; i++) {
+ XIDeviceInfo* devinfo = xi_dev_list.devices + i;
+ if (devinfo->use == XIFloatingSlave || devinfo->use == XIMasterPointer) {
+#if defined(USE_XI2_MT)
+ for (int k = 0; k < devinfo->num_classes; ++k) {
+ XIAnyClassInfo* xiclassinfo = devinfo->classes[k];
+ if (xiclassinfo->type == XITouchClass) {
+ XITouchClassInfo* tci =
+ reinterpret_cast<XITouchClassInfo*>(xiclassinfo);
+ // Only care direct touch device (such as touch screen) right now
+ if (tci->mode == XIDirectTouch) {
+ touch_device_lookup_[devinfo->deviceid] = true;
+ touch_device_list_[devinfo->deviceid] = true;
+ if (tci->num_touches > 0 && tci->num_touches > max_touch_points_)
+ max_touch_points_ = tci->num_touches;
+ }
+ }
+ }
+#endif
+ pointer_device_lookup_[devinfo->deviceid] = true;
+ } else if (devinfo->use == XIMasterKeyboard) {
+ virtual_core_keyboard_device_ = devinfo->deviceid;
+ }
+
+#if defined(USE_XI2_MT)
+ if (devinfo->use == XIFloatingSlave || devinfo->use == XISlavePointer) {
+ for (int k = 0; k < devinfo->num_classes; ++k) {
+ XIAnyClassInfo* xiclassinfo = devinfo->classes[k];
+ if (xiclassinfo->type == XITouchClass) {
+ XITouchClassInfo* tci =
+ reinterpret_cast<XITouchClassInfo*>(xiclassinfo);
+ // Only care direct touch device (such as touch screen) right now
+ if (tci->mode == XIDirectTouch)
+ CacheTouchscreenIds(display, devinfo->deviceid);
+ }
+ }
+ }
+#endif
+ }
+}
+
+bool TouchFactory::ShouldProcessXI2Event(XEvent* xev) {
+ DCHECK_EQ(GenericEvent, xev->type);
+ XIEvent* event = static_cast<XIEvent*>(xev->xcookie.data);
+ XIDeviceEvent* xiev = reinterpret_cast<XIDeviceEvent*>(event);
+
+#if defined(USE_XI2_MT)
+ if (event->evtype == XI_TouchBegin ||
+ event->evtype == XI_TouchUpdate ||
+ event->evtype == XI_TouchEnd) {
+ return !touch_events_disabled_ && IsTouchDevice(xiev->deviceid);
+ }
+#endif
+ // Make sure only key-events from the virtual core keyboard are processed.
+ if (event->evtype == XI_KeyPress || event->evtype == XI_KeyRelease) {
+ return (virtual_core_keyboard_device_ < 0) ||
+ (virtual_core_keyboard_device_ == xiev->deviceid);
+ }
+
+ if (event->evtype != XI_ButtonPress &&
+ event->evtype != XI_ButtonRelease &&
+ event->evtype != XI_Motion)
+ return true;
+
+ if (!pointer_device_lookup_[xiev->deviceid])
+ return false;
+
+ return IsTouchDevice(xiev->deviceid) ? !touch_events_disabled_ : true;
+}
+
+void TouchFactory::SetupXI2ForXWindow(Window window) {
+ // Setup mask for mouse events. It is possible that a device is loaded/plugged
+ // in after we have setup XInput2 on a window. In such cases, we need to
+ // either resetup XInput2 for the window, so that we get events from the new
+ // device, or we need to listen to events from all devices, and then filter
+ // the events from uninteresting devices. We do the latter because that's
+ // simpler.
+
+ XDisplay* display = gfx::GetXDisplay();
+
+ unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+ memset(mask, 0, sizeof(mask));
+
+#if defined(USE_XI2_MT)
+ XISetMask(mask, XI_TouchBegin);
+ XISetMask(mask, XI_TouchUpdate);
+ XISetMask(mask, XI_TouchEnd);
+#endif
+ XISetMask(mask, XI_ButtonPress);
+ XISetMask(mask, XI_ButtonRelease);
+ XISetMask(mask, XI_Motion);
+#if defined(OS_CHROMEOS)
+ if (base::SysInfo::IsRunningOnChromeOS()) {
+ XISetMask(mask, XI_KeyPress);
+ XISetMask(mask, XI_KeyRelease);
+ }
+#endif
+
+ XIEventMask evmask;
+ evmask.deviceid = XIAllDevices;
+ evmask.mask_len = sizeof(mask);
+ evmask.mask = mask;
+ XISelectEvents(display, window, &evmask, 1);
+ XFlush(display);
+}
+
+void TouchFactory::SetTouchDeviceList(
+ const std::vector<unsigned int>& devices) {
+ touch_device_lookup_.reset();
+ touch_device_list_.clear();
+ for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+ iter != devices.end(); ++iter) {
+ DCHECK(*iter < touch_device_lookup_.size());
+ touch_device_lookup_[*iter] = true;
+ touch_device_list_[*iter] = false;
+ }
+}
+
+bool TouchFactory::IsTouchDevice(unsigned deviceid) const {
+ return deviceid < touch_device_lookup_.size() ?
+ touch_device_lookup_[deviceid] : false;
+}
+
+bool TouchFactory::IsMultiTouchDevice(unsigned int deviceid) const {
+ return (deviceid < touch_device_lookup_.size() &&
+ touch_device_lookup_[deviceid]) ?
+ touch_device_list_.find(deviceid)->second :
+ false;
+}
+
+bool TouchFactory::QuerySlotForTrackingID(uint32 tracking_id, int* slot) {
+ if (!id_generator_.HasGeneratedIDFor(tracking_id))
+ return false;
+ *slot = static_cast<int>(id_generator_.GetGeneratedID(tracking_id));
+ return true;
+}
+
+int TouchFactory::GetSlotForTrackingID(uint32 tracking_id) {
+ return id_generator_.GetGeneratedID(tracking_id);
+}
+
+void TouchFactory::AcquireSlotForTrackingID(uint32 tracking_id) {
+ tracking_id_refcounts_[tracking_id]++;
+}
+
+void TouchFactory::ReleaseSlotForTrackingID(uint32 tracking_id) {
+ tracking_id_refcounts_[tracking_id]--;
+ if (tracking_id_refcounts_[tracking_id] == 0)
+ id_generator_.ReleaseNumber(tracking_id);
+}
+
+bool TouchFactory::IsTouchDevicePresent() {
+ return !touch_events_disabled_ && touch_device_lookup_.any();
+}
+
+int TouchFactory::GetMaxTouchPoints() const {
+ return max_touch_points_;
+}
+
+void TouchFactory::ResetForTest() {
+ pointer_device_lookup_.reset();
+ touch_device_lookup_.reset();
+ touch_events_disabled_ = false;
+ touch_device_list_.clear();
+ touchscreen_ids_.clear();
+ tracking_id_refcounts_.clear();
+ max_touch_points_ = -1;
+ id_generator_.ResetForTest();
+}
+
+void TouchFactory::SetTouchDeviceForTest(
+ const std::vector<unsigned int>& devices) {
+ touch_device_lookup_.reset();
+ touch_device_list_.clear();
+ for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+ iter != devices.end(); ++iter) {
+ DCHECK(*iter < touch_device_lookup_.size());
+ touch_device_lookup_[*iter] = true;
+ touch_device_list_[*iter] = true;
+ }
+ touch_events_disabled_ = false;
+}
+
+void TouchFactory::SetPointerDeviceForTest(
+ const std::vector<unsigned int>& devices) {
+ pointer_device_lookup_.reset();
+ for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+ iter != devices.end(); ++iter) {
+ pointer_device_lookup_[*iter] = true;
+ }
+}
+
+void TouchFactory::CacheTouchscreenIds(Display* display, int device_id) {
+ XDevice* device = XOpenDevice(display, device_id);
+ if (!device)
+ return;
+
+ Atom actual_type_return;
+ int actual_format_return;
+ unsigned long nitems_return;
+ unsigned long bytes_after_return;
+ unsigned char *prop_return;
+
+ const char kDeviceProductIdString[] = "Device Product ID";
+ Atom device_product_id_atom =
+ XInternAtom(display, kDeviceProductIdString, false);
+
+ if (device_product_id_atom != None &&
+ XGetDeviceProperty(display, device, device_product_id_atom, 0, 2,
+ False, XA_INTEGER, &actual_type_return,
+ &actual_format_return, &nitems_return,
+ &bytes_after_return, &prop_return) == Success) {
+ if (actual_type_return == XA_INTEGER &&
+ actual_format_return == 32 &&
+ nitems_return == 2) {
+ // An actual_format_return of 32 implies that the returned data is an
+ // array of longs. See the description of |prop_return| in `man
+ // XGetDeviceProperty` for details.
+ long* ptr = reinterpret_cast<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/ui/events/x/touch_factory_x11.h b/ui/events/x/touch_factory_x11.h
new file mode 100644
index 0000000..183dc09
--- /dev/null
+++ b/ui/events/x/touch_factory_x11.h
@@ -0,0 +1,157 @@
+// 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_X_TOUCH_FACTORY_X11_H_
+#define UI_EVENTS_X_TOUCH_FACTORY_X11_H_
+
+#include <bitset>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/timer/timer.h"
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/sequential_id_generator.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+typedef unsigned long Cursor;
+typedef unsigned long Window;
+typedef struct _XDisplay Display;
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+// Functions related to determining touch devices.
+class EVENTS_BASE_EXPORT TouchFactory {
+ private:
+ TouchFactory();
+ ~TouchFactory();
+
+ public:
+ // Returns the TouchFactory singleton.
+ static TouchFactory* GetInstance();
+
+ // Sets the touch devices from the command line.
+ static void SetTouchDeviceListFromCommandLine();
+
+ // Updates the list of devices.
+ void UpdateDeviceList(Display* display);
+
+ // Checks whether an XI2 event should be processed or not (i.e. if the event
+ // originated from a device we are interested in).
+ bool ShouldProcessXI2Event(XEvent* xevent);
+
+ // Setup an X Window for XInput2 events.
+ void SetupXI2ForXWindow(::Window xid);
+
+ // Keeps a list of touch devices so that it is possible to determine if a
+ // pointer event is a touch-event or a mouse-event. The list is reset each
+ // time this is called.
+ void SetTouchDeviceList(const std::vector<unsigned int>& devices);
+
+ // Is the device a touch-device?
+ bool IsTouchDevice(unsigned int deviceid) const;
+
+ // Is the device a real multi-touch-device? (see doc. for |touch_device_list_|
+ // below for more explanation.)
+ bool IsMultiTouchDevice(unsigned int deviceid) const;
+
+ // Tries to find an existing slot ID mapping to tracking ID. Returns true
+ // if the slot is found and it is saved in |slot|, false if no such slot
+ // can be found.
+ bool QuerySlotForTrackingID(uint32 tracking_id, int* slot);
+
+ // Tries to find an existing slot ID mapping to tracking ID. If there
+ // isn't one already, allocates a new slot ID and sets up the mapping.
+ int GetSlotForTrackingID(uint32 tracking_id);
+
+ // Increases the number of times |ReleaseSlotForTrackingID| needs to be called
+ // on a given tracking id before it will actually be released.
+ void AcquireSlotForTrackingID(uint32 tracking_id);
+
+ // Releases the slot ID mapping to tracking ID.
+ void ReleaseSlotForTrackingID(uint32 tracking_id);
+
+ // 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;
+
+ // Resets the TouchFactory singleton.
+ void ResetForTest();
+
+ // Sets up the device id in the list |devices| as multi-touch capable
+ // devices and enables touch events processing. This function is only
+ // for test purpose, and it does not query from X server.
+ void SetTouchDeviceForTest(const std::vector<unsigned int>& devices);
+
+ // Sets up the device id in the list |devices| as pointer devices.
+ // This function is only for test purpose, and it does not query from
+ // X server.
+ void SetPointerDeviceForTest(const std::vector<unsigned int>& devices);
+
+ private:
+ // 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,
+ // and instead of having the lookup table and the list, there will be a single
+ // identifier for the touch device. This can be completed after enough testing
+ // on real touch devices.
+
+ static const int kMaxDeviceNum = 128;
+
+ // A quick lookup table for determining if events from the pointer device
+ // should be processed.
+ std::bitset<kMaxDeviceNum> pointer_device_lookup_;
+
+ // A quick lookup table for determining if a device is a touch device.
+ std::bitset<kMaxDeviceNum> touch_device_lookup_;
+
+ // Indicates whether touch events are explicitly disabled.
+ bool touch_events_disabled_;
+
+ // The list of touch devices. For testing/debugging purposes, a single-pointer
+ // device (mouse or touch screen without sufficient X/driver support for MT)
+ // can sometimes be treated as a touch device. The key in the map represents
+ // the device id, and the value represents if the device is multi-touch
+ // capable.
+ std::map<int, bool> touch_device_list_;
+
+ // Touch screen <vid, pid>s.
+ std::set<std::pair<int, int> > touchscreen_ids_;
+
+ // Maps from a tracking id to the number of times |ReleaseSlotForTrackingID|
+ // must be called before the tracking id is released.
+ std::map<uint32, int> tracking_id_refcounts_;
+
+ // 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
+ // digitizer.
+ int max_touch_points_;
+
+ // Device ID of the virtual core keyboard.
+ int virtual_core_keyboard_device_;
+
+ SequentialIDGenerator id_generator_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchFactory);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_X_TOUCH_FACTORY_X11_H_