OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/automation/ui_controls.h" | 5 #include "chrome/browser/automation/ui_controls.h" |
6 | 6 |
7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
8 #include <mach/mach_time.h> | 8 #include <mach/mach_time.h> |
| 9 #include <vector> |
9 | 10 |
| 11 #include "base/keyboard_code_conversion_mac.h" |
10 #include "base/message_loop.h" | 12 #include "base/message_loop.h" |
| 13 #include "chrome/browser/automation/ui_controls_internal.h" |
11 #include "chrome/browser/chrome_thread.h" | 14 #include "chrome/browser/chrome_thread.h" |
12 | 15 |
13 // Implementation details: We use [NSApplication sendEvent:] instead | 16 // Implementation details: We use [NSApplication sendEvent:] instead |
14 // of [NSApplication postEvent:atStart:] so that the event gets sent | 17 // of [NSApplication postEvent:atStart:] so that the event gets sent |
15 // immediately. This lets us run the post-event task right | 18 // immediately. This lets us run the post-event task right |
16 // immediately as well. Unfortunately I cannot subclass NSEvent (it's | 19 // immediately as well. Unfortunately I cannot subclass NSEvent (it's |
17 // probably a class cluster) to allow other easy answers. For | 20 // probably a class cluster) to allow other easy answers. For |
18 // example, if I could subclass NSEvent, I could run the Task in it's | 21 // example, if I could subclass NSEvent, I could run the Task in it's |
19 // dealloc routine (which necessarily happens after the event is | 22 // dealloc routine (which necessarily happens after the event is |
20 // dispatched). Unlike Linux, Mac does not have message loop | 23 // dispatched). Unlike Linux, Mac does not have message loop |
21 // observer/notification. Unlike windows, I cannot post non-events | 24 // observer/notification. Unlike windows, I cannot post non-events |
22 // into the event queue. (I can post other kinds of tasks but can't | 25 // into the event queue. (I can post other kinds of tasks but can't |
23 // guarantee their order with regards to events). | 26 // guarantee their order with regards to events). |
24 | 27 |
| 28 // But [NSApplication sendEvent:] causes a problem when sending mouse click |
| 29 // events. Because in order to handle mouse drag, when processing a mouse |
| 30 // click event, the application may want to retrieve the next event |
| 31 // synchronously by calling NSApplication's nextEventMatchingMask method. |
| 32 // In this case, [NSApplication sendEvent:] causes deadlock. |
| 33 // So we need to use [NSApplication postEvent:atStart:] for mouse click |
| 34 // events. In order to notify the caller correctly after all events has been |
| 35 // processed, we setup a task to watch for the event queue time to time and |
| 36 // notify the caller as soon as there is no event in the queue. |
| 37 // |
| 38 // TODO(suzhe): |
| 39 // 1. Investigate why using [NSApplication postEvent:atStart:] for keyboard |
| 40 // events causes BrowserKeyEventsTest.CommandKeyEvents to fail. |
| 41 // See http://crbug.com/49270 |
| 42 // 2. On OSX 10.6, [NSEvent addLocalMonitorForEventsMatchingMask:handler:] may |
| 43 // be used, so that we don't need to poll the event queue time to time. |
| 44 |
25 namespace { | 45 namespace { |
26 | 46 |
27 // From | 47 // From |
28 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate | 48 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate |
29 // Which credits Apple sample code for this routine. | 49 // Which credits Apple sample code for this routine. |
30 uint64_t UpTimeInNanoseconds(void) { | 50 uint64_t UpTimeInNanoseconds(void) { |
31 uint64_t time; | 51 uint64_t time; |
32 uint64_t timeNano; | 52 uint64_t timeNano; |
33 static mach_timebase_info_data_t sTimebaseInfo; | 53 static mach_timebase_info_data_t sTimebaseInfo; |
34 | 54 |
(...skipping 11 matching lines...) Expand all Loading... |
46 | 66 |
47 // This could overflow; for testing needs we probably don't care. | 67 // This could overflow; for testing needs we probably don't care. |
48 timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; | 68 timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; |
49 return timeNano; | 69 return timeNano; |
50 } | 70 } |
51 | 71 |
52 NSTimeInterval TimeIntervalSinceSystemStartup() { | 72 NSTimeInterval TimeIntervalSinceSystemStartup() { |
53 return UpTimeInNanoseconds() / 1000000000.0; | 73 return UpTimeInNanoseconds() / 1000000000.0; |
54 } | 74 } |
55 | 75 |
| 76 // Creates and returns an autoreleased key event. |
| 77 NSEvent* SynthesizeKeyEvent(NSWindow* window, |
| 78 bool keyDown, |
| 79 base::KeyboardCode keycode, |
| 80 NSUInteger flags) { |
| 81 unichar character; |
| 82 unichar characterIgnoringModifiers; |
| 83 int macKeycode = base::MacKeyCodeForWindowsKeyCode( |
| 84 keycode, flags, &character, &characterIgnoringModifiers); |
| 85 |
| 86 if (macKeycode < 0) |
| 87 return nil; |
| 88 |
| 89 NSString* charactersIgnoringModifiers = |
| 90 [[[NSString alloc] initWithCharacters:&characterIgnoringModifiers |
| 91 length:1] |
| 92 autorelease]; |
| 93 NSString* characters = |
| 94 [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; |
| 95 |
| 96 NSEventType type = (keyDown ? NSKeyDown : NSKeyUp); |
| 97 |
| 98 // Modifier keys generate NSFlagsChanged event rather than |
| 99 // NSKeyDown/NSKeyUp events. |
| 100 if (keycode == base::VKEY_CONTROL || keycode == base::VKEY_SHIFT || |
| 101 keycode == base::VKEY_MENU || keycode == base::VKEY_COMMAND) |
| 102 type = NSFlagsChanged; |
| 103 |
| 104 // For events other than mouse moved, [event locationInWindow] is |
| 105 // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) |
| 106 // location should be fine. |
| 107 NSEvent* event = |
| 108 [NSEvent keyEventWithType:type |
| 109 location:NSMakePoint(0, 0) |
| 110 modifierFlags:flags |
| 111 timestamp:TimeIntervalSinceSystemStartup() |
| 112 windowNumber:[window windowNumber] |
| 113 context:nil |
| 114 characters:characters |
| 115 charactersIgnoringModifiers:charactersIgnoringModifiers |
| 116 isARepeat:NO |
| 117 keyCode:(unsigned short)macKeycode]; |
| 118 |
| 119 return event; |
| 120 } |
| 121 |
| 122 // Creates the proper sequence of autoreleased key events for a key down + up. |
| 123 void SynthesizeKeyEventsSequence(NSWindow* window, |
| 124 base::KeyboardCode keycode, |
| 125 bool control, |
| 126 bool shift, |
| 127 bool alt, |
| 128 bool command, |
| 129 std::vector<NSEvent*>* events) { |
| 130 NSEvent* event = nil; |
| 131 NSUInteger flags = 0; |
| 132 if (control) { |
| 133 flags |= NSControlKeyMask; |
| 134 event = SynthesizeKeyEvent(window, true, base::VKEY_CONTROL, flags); |
| 135 DCHECK(event); |
| 136 events->push_back(event); |
| 137 } |
| 138 if (shift) { |
| 139 flags |= NSShiftKeyMask; |
| 140 event = SynthesizeKeyEvent(window, true, base::VKEY_SHIFT, flags); |
| 141 DCHECK(event); |
| 142 events->push_back(event); |
| 143 } |
| 144 if (alt) { |
| 145 flags |= NSAlternateKeyMask; |
| 146 event = SynthesizeKeyEvent(window, true, base::VKEY_MENU, flags); |
| 147 DCHECK(event); |
| 148 events->push_back(event); |
| 149 } |
| 150 if (command) { |
| 151 flags |= NSCommandKeyMask; |
| 152 event = SynthesizeKeyEvent(window, true, base::VKEY_COMMAND, flags); |
| 153 DCHECK(event); |
| 154 events->push_back(event); |
| 155 } |
| 156 |
| 157 event = SynthesizeKeyEvent(window, true, keycode, flags); |
| 158 DCHECK(event); |
| 159 events->push_back(event); |
| 160 event = SynthesizeKeyEvent(window, false, keycode, flags); |
| 161 DCHECK(event); |
| 162 events->push_back(event); |
| 163 |
| 164 if (command) { |
| 165 flags &= ~NSCommandKeyMask; |
| 166 event = SynthesizeKeyEvent(window, false, base::VKEY_COMMAND, flags); |
| 167 DCHECK(event); |
| 168 events->push_back(event); |
| 169 } |
| 170 if (alt) { |
| 171 flags &= ~NSAlternateKeyMask; |
| 172 event = SynthesizeKeyEvent(window, false, base::VKEY_MENU, flags); |
| 173 DCHECK(event); |
| 174 events->push_back(event); |
| 175 } |
| 176 if (shift) { |
| 177 flags &= ~NSShiftKeyMask; |
| 178 event = SynthesizeKeyEvent(window, false, base::VKEY_SHIFT, flags); |
| 179 DCHECK(event); |
| 180 events->push_back(event); |
| 181 } |
| 182 if (control) { |
| 183 flags &= ~NSControlKeyMask; |
| 184 event = SynthesizeKeyEvent(window, false, base::VKEY_CONTROL, flags); |
| 185 DCHECK(event); |
| 186 events->push_back(event); |
| 187 } |
| 188 } |
| 189 |
| 190 // A task class to watch for the event queue. The specific task will be fired |
| 191 // when there is no more event in the queue. |
| 192 class EventQueueWatcher : public Task { |
| 193 public: |
| 194 EventQueueWatcher(Task* task) : task_(task) {} |
| 195 |
| 196 virtual ~EventQueueWatcher() {} |
| 197 |
| 198 virtual void Run() { |
| 199 NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask |
| 200 untilDate:nil |
| 201 inMode:NSDefaultRunLoopMode |
| 202 dequeue:NO]; |
| 203 // If there is still event in the queue, then we need to check again. |
| 204 if (event) |
| 205 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task_)); |
| 206 else |
| 207 MessageLoop::current()->PostTask(FROM_HERE, task_); |
| 208 } |
| 209 |
| 210 private: |
| 211 Task* task_; |
| 212 }; |
| 213 |
| 214 // Stores the current mouse location on the screen. So that we can use it |
| 215 // when firing keyboard and mouse click events. |
| 216 NSPoint g_mouse_location = { 0, 0 }; |
| 217 |
56 } // anonymous namespace | 218 } // anonymous namespace |
57 | 219 |
58 | 220 |
59 namespace ui_controls { | 221 namespace ui_controls { |
60 | 222 |
61 bool SendKeyPress(gfx::NativeWindow window, | 223 bool SendKeyPress(gfx::NativeWindow window, |
62 base::KeyboardCode key, | 224 base::KeyboardCode key, |
63 bool control, | 225 bool control, |
64 bool shift, | 226 bool shift, |
65 bool alt, | 227 bool alt, |
66 bool command) { | 228 bool command) { |
67 return SendKeyPressNotifyWhenDone(window, key, | 229 return SendKeyPressNotifyWhenDone(window, key, |
68 control, shift, alt, command, | 230 control, shift, alt, command, |
69 NULL); | 231 NULL); |
70 } | 232 } |
71 | 233 |
72 // Win and Linux implement a SendKeyPress() this as a | 234 // Win and Linux implement a SendKeyPress() this as a |
73 // SendKeyPressAndRelease(), so we should as well (despite the name). | 235 // SendKeyPressAndRelease(), so we should as well (despite the name). |
74 // | |
75 // TODO(jrg): handle "characters" better (e.g. apply shift?) | |
76 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, | 236 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, |
77 base::KeyboardCode key, | 237 base::KeyboardCode key, |
78 bool control, | 238 bool control, |
79 bool shift, | 239 bool shift, |
80 bool alt, | 240 bool alt, |
81 bool command, | 241 bool command, |
82 Task* task) { | 242 Task* task) { |
83 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); | 243 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
84 NSUInteger flags = 0; | |
85 if (control) | |
86 flags |= NSControlKeyMask; | |
87 if (shift) | |
88 flags |= NSShiftKeyMask; | |
89 if (alt) | |
90 flags |= NSAlternateKeyMask; | |
91 if (command) | |
92 flags |= NSCommandKeyMask; | |
93 unsigned char keycode = key; | |
94 NSString* charactersIgnoringModifiers = [[[NSString alloc] | |
95 initWithBytes:&keycode | |
96 length:1 | |
97 encoding:NSUTF8StringEncoding] | |
98 autorelease]; | |
99 NSString* characters = charactersIgnoringModifiers; | |
100 | 244 |
101 // For events other than mouse moved, [event locationInWindow] is | 245 std::vector<NSEvent*> events; |
102 // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) | 246 SynthesizeKeyEventsSequence( |
103 // locaiton should be fine. | 247 window, key, control, shift, alt, command, &events); |
104 // First a key down... | 248 |
105 NSEvent* event = | 249 // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes |
106 [NSEvent keyEventWithType:NSKeyDown | 250 // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270 |
107 location:NSMakePoint(0,0) | 251 // But using [NSApplication sendEvent:] should be safe for keyboard events, |
108 modifierFlags:flags | 252 // because until now, no code wants to retrieve the next event when handling |
109 timestamp:TimeIntervalSinceSystemStartup() | 253 // a keyboard event. |
110 windowNumber:[window windowNumber] | 254 for (std::vector<NSEvent*>::iterator iter = events.begin(); |
111 context:nil | 255 iter != events.end(); ++iter) |
112 characters:characters | 256 [[NSApplication sharedApplication] sendEvent:*iter]; |
113 charactersIgnoringModifiers:charactersIgnoringModifiers | |
114 isARepeat:NO | |
115 keyCode:key]; | |
116 [[NSApplication sharedApplication] sendEvent:event]; | |
117 // Then a key up. | |
118 event = | |
119 [NSEvent keyEventWithType:NSKeyUp | |
120 location:NSMakePoint(0,0) | |
121 modifierFlags:flags | |
122 timestamp:TimeIntervalSinceSystemStartup() | |
123 windowNumber:[window windowNumber] | |
124 context:nil | |
125 characters:characters | |
126 charactersIgnoringModifiers:charactersIgnoringModifiers | |
127 isARepeat:NO | |
128 keyCode:key]; | |
129 [[NSApplication sharedApplication] sendEvent:event]; | |
130 | 257 |
131 if (task) | 258 if (task) |
132 MessageLoop::current()->PostTask(FROM_HERE, task); | 259 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
| 260 |
133 return true; | 261 return true; |
134 } | 262 } |
135 | 263 |
136 bool SendMouseMove(long x, long y) { | 264 bool SendMouseMove(long x, long y) { |
137 return SendMouseMoveNotifyWhenDone(x, y, NULL); | 265 return SendMouseMoveNotifyWhenDone(x, y, NULL); |
138 } | 266 } |
139 | 267 |
140 // Input position is in screen coordinates. However, NSMouseMoved | 268 // Input position is in screen coordinates. However, NSMouseMoved |
141 // events require them window-relative, so we adjust. We *DO* flip | 269 // events require them window-relative, so we adjust. We *DO* flip |
142 // the coordinate space, so input events can be the same for all | 270 // the coordinate space, so input events can be the same for all |
143 // platforms. E.g. (0,0) is upper-left. | 271 // platforms. E.g. (0,0) is upper-left. |
144 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { | 272 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { |
145 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; | 273 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
146 CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; | 274 CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; |
147 NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! | 275 g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! |
| 276 NSPoint pointInWindow = g_mouse_location; |
148 if (window) | 277 if (window) |
149 pointInWindow = [window convertScreenToBase:pointInWindow]; | 278 pointInWindow = [window convertScreenToBase:pointInWindow]; |
150 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); | 279 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
151 | 280 |
152 NSEvent* event = | 281 NSEvent* event = |
153 [NSEvent mouseEventWithType:NSMouseMoved | 282 [NSEvent mouseEventWithType:NSMouseMoved |
154 location:pointInWindow | 283 location:pointInWindow |
155 modifierFlags:0 | 284 modifierFlags:0 |
156 timestamp:timestamp | 285 timestamp:timestamp |
157 windowNumber:[window windowNumber] | 286 windowNumber:[window windowNumber] |
158 context:nil | 287 context:nil |
159 eventNumber:0 | 288 eventNumber:0 |
160 clickCount:0 | 289 clickCount:0 |
161 pressure:0.0]; | 290 pressure:0.0]; |
162 [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 291 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
| 292 |
163 if (task) | 293 if (task) |
164 MessageLoop::current()->PostTask(FROM_HERE, task); | 294 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
| 295 |
165 return true; | 296 return true; |
166 } | 297 } |
167 | 298 |
168 bool SendMouseEvents(MouseButton type, int state) { | 299 bool SendMouseEvents(MouseButton type, int state) { |
169 return SendMouseEventsNotifyWhenDone(type, state, NULL); | 300 return SendMouseEventsNotifyWhenDone(type, state, NULL); |
170 } | 301 } |
171 | 302 |
172 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { | 303 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { |
173 // On windows it appears state can be (UP|DOWN). It is unclear if | 304 // On windows it appears state can be (UP|DOWN). It is unclear if |
174 // that'll happen here but prepare for it just in case. | 305 // that'll happen here but prepare for it just in case. |
175 if (state == (UP|DOWN)) { | 306 if (state == (UP|DOWN)) { |
176 return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && | 307 return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && |
177 SendMouseEventsNotifyWhenDone(type, UP, task)); | 308 SendMouseEventsNotifyWhenDone(type, UP, task)); |
178 } | 309 } |
179 | |
180 NSEventType etype = 0; | 310 NSEventType etype = 0; |
181 if (type == LEFT) { | 311 if (type == LEFT) { |
182 if (state == UP) { | 312 if (state == UP) { |
183 etype = NSLeftMouseUp; | 313 etype = NSLeftMouseUp; |
184 } else { | 314 } else { |
185 etype = NSLeftMouseDown; | 315 etype = NSLeftMouseDown; |
186 } | 316 } |
187 } else if (type == MIDDLE) { | 317 } else if (type == MIDDLE) { |
188 if (state == UP) { | 318 if (state == UP) { |
189 etype = NSOtherMouseUp; | 319 etype = NSOtherMouseUp; |
190 } else { | 320 } else { |
191 etype = NSOtherMouseDown; | 321 etype = NSOtherMouseDown; |
192 } | 322 } |
193 } else if (type == RIGHT) { | 323 } else if (type == RIGHT) { |
194 if (state == UP) { | 324 if (state == UP) { |
195 etype = NSRightMouseUp; | 325 etype = NSRightMouseUp; |
196 } else { | 326 } else { |
197 etype = NSRightMouseDown; | 327 etype = NSRightMouseDown; |
198 } | 328 } |
199 } else { | 329 } else { |
200 return false; | 330 return false; |
201 } | 331 } |
202 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; | 332 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
203 NSPoint location = [NSEvent mouseLocation]; | 333 NSPoint pointInWindow = g_mouse_location; |
204 NSPoint pointInWindow = location; | |
205 if (window) | 334 if (window) |
206 pointInWindow = [window convertScreenToBase:pointInWindow]; | 335 pointInWindow = [window convertScreenToBase:pointInWindow]; |
207 | 336 |
208 NSEvent* event = | 337 NSEvent* event = |
209 [NSEvent mouseEventWithType:etype | 338 [NSEvent mouseEventWithType:etype |
210 location:pointInWindow | 339 location:pointInWindow |
211 modifierFlags:0 | 340 modifierFlags:0 |
212 timestamp:TimeIntervalSinceSystemStartup() | 341 timestamp:TimeIntervalSinceSystemStartup() |
213 windowNumber:[window windowNumber] | 342 windowNumber:[window windowNumber] |
214 context:nil | 343 context:nil |
215 eventNumber:0 | 344 eventNumber:0 |
216 clickCount:0 | 345 clickCount:1 |
217 pressure:0.0]; | 346 pressure:(state == DOWN ? 1.0 : 0.0 )]; |
218 [[NSApplication sharedApplication] sendEvent:event]; | 347 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
| 348 |
219 if (task) | 349 if (task) |
220 MessageLoop::current()->PostTask(FROM_HERE, task); | 350 MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
| 351 |
221 return true; | 352 return true; |
222 } | 353 } |
223 | 354 |
224 bool SendMouseClick(MouseButton type) { | 355 bool SendMouseClick(MouseButton type) { |
225 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); | 356 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); |
226 } | 357 } |
227 | 358 |
228 // This appears to only be used by a function in test/ui_test_utils.h: | |
229 // ui_test_utils::ClickOnView(). That is not implemented on Mac, so | |
230 // we don't need to implement MoveMouseToCenterAndPress(). I've | |
231 // suggested an implementation of ClickOnView() which would call Cocoa | |
232 // directly and not need this indirection, so this may not be needed, | |
233 // ever. | |
234 void MoveMouseToCenterAndPress( | 359 void MoveMouseToCenterAndPress( |
235 NSWindow* window, | 360 NSView* view, |
236 MouseButton button, | 361 MouseButton button, |
237 int state, | 362 int state, |
238 Task* task) { | 363 Task* task) { |
239 NOTIMPLEMENTED(); | 364 DCHECK(view); |
| 365 NSWindow* window = [view window]; |
| 366 DCHECK(window); |
| 367 NSScreen* screen = [window screen]; |
| 368 DCHECK(screen); |
| 369 |
| 370 // Converts the center position of the view into the coordinates accepted |
| 371 // by SendMouseMoveNotifyWhenDone() method. |
| 372 NSRect bounds = [view bounds]; |
| 373 NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); |
| 374 center = [view convertPoint:center toView:nil]; |
| 375 center = [window convertBaseToScreen:center]; |
| 376 center = NSMakePoint(center.x, [screen frame].size.height - center.y); |
| 377 |
| 378 SendMouseMoveNotifyWhenDone(center.x, center.y, |
| 379 new ClickTask(button, state, task)); |
240 } | 380 } |
241 | 381 |
242 } // ui_controls | 382 } // ui_controls |
OLD | NEW |