summaryrefslogtreecommitdiffstats
path: root/ui/events/test/cocoa_test_event_utils.mm
blob: 9810d589419b6f6a1f2ebd1b6d01313e80cef5c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// 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 <mach/mach_time.h>
#include <stdint.h>

#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
#include "ui/events/test/cocoa_test_event_utils.h"

namespace cocoa_test_event_utils {

namespace {

// From
// http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate
// Which credits Apple sample code for this routine.
uint64_t UpTimeInNanoseconds(void) {
  uint64_t time;
  uint64_t timeNano;
  static mach_timebase_info_data_t sTimebaseInfo;

  time = mach_absolute_time();

  // Convert to nanoseconds.

  // If this is the first time we've run, get the timebase.
  // We can use denom == 0 to indicate that sTimebaseInfo is
  // uninitialised because it makes no sense to have a zero
  // denominator is a fraction.
  if (sTimebaseInfo.denom == 0) {
    (void) mach_timebase_info(&sTimebaseInfo);
  }

  // This could overflow; for testing needs we probably don't care.
  timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom;
  return timeNano;
}

}  // namespace

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, static_cast<CGEventFlags>(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);
}

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);
}

std::pair<NSEvent*, NSEvent*> RightMouseClickInView(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, NSRightMouseDown,
                                            [view window], clickCount);
  NSEvent* up = MouseEventAtPointInWindow(mid_point, NSRightMouseUp,
                                          [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];
}

static 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* EnterEvent() {
  return EnterExitEventWithType(NSMouseEntered);
}

NSEvent* ExitEvent() {
  return EnterExitEventWithType(NSMouseExited);
}

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];
}

NSTimeInterval TimeIntervalSinceSystemStartup() {
  return UpTimeInNanoseconds() / 1000000000.0;
}

NSEvent* SynthesizeKeyEvent(NSWindow* window,
                            bool keyDown,
                            ui::KeyboardCode keycode,
                            NSUInteger flags,
                            ui::DomKey dom_key) {
  // If caps lock is set for an alpha keycode, treat it as if shift was pressed.
  // Note on Mac (unlike other platforms) shift while caps is down does not go
  // back to lowercase.
  if (keycode >= ui::VKEY_A && keycode <= ui::VKEY_Z &&
      (flags & NSAlphaShiftKeyMask))
    flags |= NSShiftKeyMask;

  // Clear caps regardless -- MacKeyCodeForWindowsKeyCode doesn't implement
  // logic to support it.
  flags &= ~NSAlphaShiftKeyMask;

  unichar character;
  unichar shifted_character;
  int macKeycode = ui::MacKeyCodeForWindowsKeyCode(
      keycode, flags, &shifted_character, &character);

  if (macKeycode < 0)
    return nil;

  // If an explicit unicode character is provided, use that instead of the one
  // derived from the keycode.
  if (dom_key.IsCharacter())
    shifted_character = dom_key.ToCharacter();

  // Note that, in line with AppKit's documentation (and tracing "real" events),
  // -[NSEvent charactersIngoringModifiers]" are "the characters generated by
  // the receiving key event as if no modifier key (except for Shift)".
  // So |charactersIgnoringModifiers| uses |shifted_character|.
  NSString* charactersIgnoringModifiers =
      [[[NSString alloc] initWithCharacters:&shifted_character
                                     length:1] autorelease];
  NSString* characters;
  // The following were determined empirically on OSX 10.9.
  if (flags & NSControlKeyMask) {
    // If Ctrl is pressed, Cocoa always puts an empty string into |characters|.
    characters = [NSString string];
  } else if (flags & NSCommandKeyMask) {
    // If Cmd is pressed, Cocoa puts a lowercase character into |characters|,
    // regardless of Shift. If, however, Alt is also pressed then shift *is*
    // preserved, but re-mappings for Alt are not implemented. Although we still
    // need to support Alt for things like Alt+Left/Right which don't care.
    characters =
        [[[NSString alloc] initWithCharacters:&character length:1] autorelease];
  } else {
    // If just Shift or nothing is pressed, |characters| will match
    // |charactersIgnoringModifiers|. Alt puts a special character into
    // |characters| (not |charactersIgnoringModifiers|), but they're not mapped
    // here.
    characters = charactersIgnoringModifiers;
  }

  NSEventType type = (keyDown ? NSKeyDown : NSKeyUp);

  // Modifier keys generate NSFlagsChanged event rather than
  // NSKeyDown/NSKeyUp events.
  if (keycode == ui::VKEY_CONTROL || keycode == ui::VKEY_SHIFT ||
      keycode == ui::VKEY_MENU || keycode == ui::VKEY_COMMAND)
    type = NSFlagsChanged;

  // For events other than mouse moved, [event locationInWindow] is
  // UNDEFINED if the event is not NSMouseMoved.  Thus, the (0,0)
  // location should be fine.
  NSEvent* event = [NSEvent keyEventWithType:type
                                    location:NSZeroPoint
                               modifierFlags:flags
                                   timestamp:TimeIntervalSinceSystemStartup()
                                windowNumber:[window windowNumber]
                                     context:nil
                                  characters:characters
                 charactersIgnoringModifiers:charactersIgnoringModifiers
                                   isARepeat:NO
                                     keyCode:(unsigned short)macKeycode];

  return event;
}

}  // namespace cocoa_test_event_utils