1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import <Cocoa/Cocoa.h>
7 #import "base/mac/scoped_objc_class_swizzler.h"
8 #include "base/memory/singleton.h"
9 #include "ui/events/event_processor.h"
10 #include "ui/events/event_target.h"
11 #include "ui/events/event_target_iterator.h"
12 #include "ui/events/event_targeter.h"
13 #include "ui/events/test/event_generator.h"
14 #include "ui/gfx/mac/coordinate_conversion.h"
18 // Singleton to provide state for swizzled Objective C methods.
19 ui::test::EventGenerator* g_active_generator = NULL;
23 @interface NSEventDonor : NSObject
26 @implementation NSEventDonor
28 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the
30 + (NSUInteger)pressedMouseButtons {
31 if (!g_active_generator)
32 return [NSEventDonor pressedMouseButtons]; // Call original implementation.
34 int flags = g_active_generator->flags();
35 NSUInteger bitmask = 0;
36 if (flags & ui::EF_LEFT_MOUSE_BUTTON)
38 if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
40 if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
49 NSPoint ConvertRootPointToTarget(NSWindow* target,
50 const gfx::Point& point_in_root) {
51 // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can
52 // reposition the window on screen and make things flaky. Initially, just
53 // assume that the contentRect of |target| is at the top-left corner of the
55 NSRect content_rect = [target contentRectForFrameRect:[target frame]];
56 return NSMakePoint(point_in_root.x(),
57 NSHeight(content_rect) - point_in_root.y());
60 // Inverse of ui::EventFlagsFromModifiers().
61 NSUInteger EventFlagsToModifiers(int flags) {
62 NSUInteger modifiers = 0;
63 modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0;
64 modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0;
65 modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0;
66 modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0;
67 modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0;
68 // ui::EF_*_MOUSE_BUTTON not handled here.
69 // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped.
73 // Picks the corresponding mouse event type for the buttons set in |flags|.
74 NSEventType PickMouseEventType(int flags,
78 if (flags & ui::EF_LEFT_MOUSE_BUTTON)
80 if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
85 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set
86 // using the inverse of ui::EventFlagsFromNSEventWithModifiers().
87 NSEventType EventTypeToNative(ui::EventType ui_event_type,
89 NSUInteger* modifiers) {
91 *modifiers = EventFlagsToModifiers(flags);
92 switch (ui_event_type) {
95 case ui::ET_KEY_PRESSED:
97 case ui::ET_KEY_RELEASED:
99 case ui::ET_MOUSE_PRESSED:
100 return PickMouseEventType(flags,
104 case ui::ET_MOUSE_RELEASED:
105 return PickMouseEventType(flags,
109 case ui::ET_MOUSE_DRAGGED:
110 return PickMouseEventType(flags,
113 NSOtherMouseDragged);
114 case ui::ET_MOUSE_MOVED:
116 case ui::ET_MOUSEWHEEL:
117 return NSScrollWheel;
118 case ui::ET_MOUSE_ENTERED:
119 return NSMouseEntered;
120 case ui::ET_MOUSE_EXITED:
121 return NSMouseExited;
122 case ui::ET_SCROLL_FLING_START:
123 return NSEventTypeSwipe;
130 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:].
131 // sendEvent is a black box which (among other things) will try to peek at the
132 // event queue and can block indefinitely.
133 void EmulateSendEvent(NSWindow* window, NSEvent* event) {
134 NSResponder* responder = [window firstResponder];
135 switch ([event type]) {
137 [responder keyDown:event];
140 [responder keyUp:event];
144 // For mouse events, NSWindow will use -[NSView hitTest:] for the initial
145 // mouseDown, and then keep track of the NSView returned. The toolkit-views
146 // RootView does this too. So, for tests, assume tracking will be done there,
147 // and the NSWindow's contentView is wrapping a views::internal::RootView.
148 responder = [window contentView];
149 switch ([event type]) {
150 case NSLeftMouseDown:
151 [responder mouseDown:event];
153 case NSRightMouseDown:
154 [responder rightMouseDown:event];
156 case NSOtherMouseDown:
157 [responder otherMouseDown:event];
160 [responder mouseUp:event];
163 [responder rightMouseUp:event];
166 [responder otherMouseUp:event];
168 case NSLeftMouseDragged:
169 [responder mouseDragged:event];
171 case NSRightMouseDragged:
172 [responder rightMouseDragged:event];
174 case NSOtherMouseDragged:
175 [responder otherMouseDragged:event];
178 // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that
179 // NSTrackingAreas have been appropriately installed on |responder|.
180 [responder mouseMoved:event];
183 [responder scrollWheel:event];
187 // With the assumptions in NSMouseMoved, it doesn't make sense for the
188 // generator to handle entered/exited separately. It's the responsibility
189 // of views::internal::RootView to convert the moved events into entered
190 // and exited events for the individual views.
193 case NSEventTypeSwipe:
194 // NSEventTypeSwipe events can't be generated using public interfaces on
195 // NSEvent, so this will need to be handled at a higher level.
203 NSEvent* CreateMouseEventInWindow(NSWindow* window,
204 ui::EventType event_type,
205 const gfx::Point& point_in_root,
207 NSUInteger click_count = 0;
208 if (event_type == ui::ET_MOUSE_PRESSED ||
209 event_type == ui::ET_MOUSE_RELEASED) {
210 if (flags & ui::EF_IS_TRIPLE_CLICK)
212 else if (flags & ui::EF_IS_DOUBLE_CLICK)
217 NSPoint point = ConvertRootPointToTarget(window, point_in_root);
218 NSUInteger modifiers = 0;
219 NSEventType type = EventTypeToNative(event_type, flags, &modifiers);
220 return [NSEvent mouseEventWithType:type
222 modifierFlags:modifiers
224 windowNumber:[window windowNumber]
227 clickCount:click_count
231 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything
232 // defined inline is just a stub. Interesting overrides are defined below the
234 class EventGeneratorDelegateMac : public ui::EventTarget,
235 public ui::EventSource,
236 public ui::EventProcessor,
237 public ui::EventTargeter,
238 public ui::test::EventGeneratorDelegate {
240 static EventGeneratorDelegateMac* GetInstance() {
241 return Singleton<EventGeneratorDelegateMac>::get();
244 // Overridden from ui::EventTarget:
245 virtual bool CanAcceptEvent(const ui::Event& event) override { return true; }
246 virtual ui::EventTarget* GetParentTarget() override { return NULL; }
247 virtual scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override;
248 virtual ui::EventTargeter* GetEventTargeter() override {
252 // Overridden from ui::EventHandler (via ui::EventTarget):
253 virtual void OnMouseEvent(ui::MouseEvent* event) override;
255 // Overridden from ui::EventSource:
256 virtual ui::EventProcessor* GetEventProcessor() override { return this; }
258 // Overridden from ui::EventProcessor:
259 virtual ui::EventTarget* GetRootTarget() override { return this; }
261 // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor):
262 virtual bool CanDispatchToTarget(EventTarget* target) override {
266 // Overridden from ui::test::EventGeneratorDelegate:
267 virtual void SetContext(ui::test::EventGenerator* owner,
268 gfx::NativeWindow root_window,
269 gfx::NativeWindow window) override;
270 virtual ui::EventTarget* GetTargetAt(const gfx::Point& location) override {
273 virtual ui::EventSource* GetEventSource(ui::EventTarget* target) override {
276 virtual gfx::Point CenterOfTarget(
277 const ui::EventTarget* target) const override;
278 virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const override;
280 virtual void ConvertPointFromTarget(const ui::EventTarget* target,
281 gfx::Point* point) const override {}
282 virtual void ConvertPointToTarget(const ui::EventTarget* target,
283 gfx::Point* point) const override {}
284 virtual void ConvertPointFromHost(const ui::EventTarget* hosted_target,
285 gfx::Point* point) const override {}
288 friend struct DefaultSingletonTraits<EventGeneratorDelegateMac>;
290 EventGeneratorDelegateMac();
291 virtual ~EventGeneratorDelegateMac();
293 ui::test::EventGenerator* owner_;
295 scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_pressed_;
297 DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac);
300 EventGeneratorDelegateMac::EventGeneratorDelegateMac()
303 DCHECK(!ui::test::EventGenerator::default_delegate);
304 ui::test::EventGenerator::default_delegate = this;
307 EventGeneratorDelegateMac::~EventGeneratorDelegateMac() {
308 DCHECK_EQ(this, ui::test::EventGenerator::default_delegate);
309 ui::test::EventGenerator::default_delegate = NULL;
312 scoped_ptr<ui::EventTargetIterator>
313 EventGeneratorDelegateMac::GetChildIterator() const {
314 // Return NULL to dispatch all events to the result of GetRootTarget().
315 return scoped_ptr<ui::EventTargetIterator>();
318 void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
319 // For mouse drag events, ensure the swizzled methods return the right flags.
320 base::AutoReset<ui::test::EventGenerator*> reset(&g_active_generator, owner_);
321 NSEvent* ns_event = CreateMouseEventInWindow(window_,
324 event->changed_button_flags());
325 if (owner_->targeting_application())
326 [NSApp sendEvent:ns_event];
328 EmulateSendEvent(window_, ns_event);
331 void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner,
332 gfx::NativeWindow root_window,
333 gfx::NativeWindow window) {
334 swizzle_pressed_.reset();
338 swizzle_pressed_.reset(new base::mac::ScopedObjCClassSwizzler(
340 [NSEventDonor class],
341 @selector(pressedMouseButtons)));
345 gfx::Point EventGeneratorDelegateMac::CenterOfTarget(
346 const ui::EventTarget* target) const {
347 DCHECK_EQ(target, this);
348 return CenterOfWindow(window_);
351 gfx::Point EventGeneratorDelegateMac::CenterOfWindow(
352 gfx::NativeWindow window) const {
353 DCHECK_EQ(window, window_);
354 return gfx::ScreenRectFromNSRect([window frame]).CenterPoint();
362 void InitializeMacEventGeneratorDelegate() {
363 EventGeneratorDelegateMac::GetInstance();