OLD | NEW |
(Empty) | |
| 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 |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import <Cocoa/Cocoa.h> |
| 6 #include <mach/mach_time.h> |
| 7 |
| 8 #include "chrome/browser/automation/ui_controls.h" |
| 9 #include "chrome/browser/chrome_thread.h" |
| 10 |
| 11 // Implementation details: We use [NSApplication sendEvent:] instead |
| 12 // of [NSApplication postEvent:atStart:] so that the event gets sent |
| 13 // immediately. This lets us run the post-event task right |
| 14 // immediately as well. Unfortunately I cannot subclass NSEvent (it's |
| 15 // probably a class cluster) to allow other easy answers. For |
| 16 // example, if I could subclass NSEvent, I could run the Task in it's |
| 17 // dealloc routine (which necessarily happens after the event is |
| 18 // dispatched). Unlike Linux, Mac does not have message loop |
| 19 // observer/notification. Unlike windows, I cannot post non-events |
| 20 // into the event queue. (I can post other kinds of tasks but can't |
| 21 // guarantee their order with regards to events). |
| 22 |
| 23 namespace { |
| 24 |
| 25 // From |
| 26 // http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate |
| 27 // Which credits Apple sample code for this routine. |
| 28 uint64_t UpTimeInNanoseconds(void) { |
| 29 uint64_t time; |
| 30 uint64_t timeNano; |
| 31 static mach_timebase_info_data_t sTimebaseInfo; |
| 32 |
| 33 time = mach_absolute_time(); |
| 34 |
| 35 // Convert to nanoseconds. |
| 36 |
| 37 // If this is the first time we've run, get the timebase. |
| 38 // We can use denom == 0 to indicate that sTimebaseInfo is |
| 39 // uninitialised because it makes no sense to have a zero |
| 40 // denominator is a fraction. |
| 41 if (sTimebaseInfo.denom == 0) { |
| 42 (void) mach_timebase_info(&sTimebaseInfo); |
| 43 } |
| 44 |
| 45 // This could overflow; for testing needs we probably don't care. |
| 46 timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; |
| 47 return timeNano; |
| 48 } |
| 49 |
| 50 NSTimeInterval TimeIntervalSinceSystemStartup() { |
| 51 return UpTimeInNanoseconds() / 1000000000.0; |
| 52 } |
| 53 |
| 54 } // anonymous namespace |
| 55 |
| 56 |
| 57 namespace ui_controls { |
| 58 |
| 59 bool SendKeyPress(gfx::NativeWindow window, |
| 60 base::KeyboardCode key, |
| 61 bool control, |
| 62 bool shift, |
| 63 bool alt, |
| 64 bool command) { |
| 65 return SendKeyPressNotifyWhenDone(window, key, |
| 66 control, shift, alt, command, |
| 67 NULL); |
| 68 } |
| 69 |
| 70 // Win and Linux implement a SendKeyPress() this as a |
| 71 // SendKeyPressAndRelease(), so we should as well (despite the name). |
| 72 // |
| 73 // TODO(jrg): handle "characters" better (e.g. apply shift?) |
| 74 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, |
| 75 base::KeyboardCode key, |
| 76 bool control, |
| 77 bool shift, |
| 78 bool alt, |
| 79 bool command, |
| 80 Task* task) { |
| 81 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
| 82 NSUInteger flags = 0; |
| 83 if (control) |
| 84 flags |= NSControlKeyMask; |
| 85 if (shift) |
| 86 flags |= NSShiftKeyMask; |
| 87 if (alt) |
| 88 flags |= NSAlternateKeyMask; |
| 89 if (command) |
| 90 flags |= NSCommandKeyMask; |
| 91 unsigned char keycode = key; |
| 92 NSString* charactersIgnoringModifiers = [[[NSString alloc] |
| 93 initWithBytes:&keycode |
| 94 length:1 |
| 95 encoding:NSUTF8StringEncoding] |
| 96 autorelease]; |
| 97 NSString* characters = charactersIgnoringModifiers; |
| 98 |
| 99 // For events other than mouse moved, [event locationInWindow] is |
| 100 // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) |
| 101 // locaiton should be fine. |
| 102 // First a key down... |
| 103 NSEvent* event = |
| 104 [NSEvent keyEventWithType:NSKeyDown |
| 105 location:NSMakePoint(0,0) |
| 106 modifierFlags:flags |
| 107 timestamp:TimeIntervalSinceSystemStartup() |
| 108 windowNumber:[window windowNumber] |
| 109 context:nil |
| 110 characters:characters |
| 111 charactersIgnoringModifiers:charactersIgnoringModifiers |
| 112 isARepeat:NO |
| 113 keyCode:key]; |
| 114 [[NSApplication sharedApplication] sendEvent:event]; |
| 115 // Then a key up. |
| 116 event = |
| 117 [NSEvent keyEventWithType:NSKeyUp |
| 118 location:NSMakePoint(0,0) |
| 119 modifierFlags:flags |
| 120 timestamp:TimeIntervalSinceSystemStartup() |
| 121 windowNumber:[window windowNumber] |
| 122 context:nil |
| 123 characters:characters |
| 124 charactersIgnoringModifiers:charactersIgnoringModifiers |
| 125 isARepeat:NO |
| 126 keyCode:key]; |
| 127 [[NSApplication sharedApplication] sendEvent:event]; |
| 128 |
| 129 if (task) |
| 130 MessageLoop::current()->PostTask(FROM_HERE, task); |
| 131 return true; |
| 132 } |
| 133 |
| 134 bool SendMouseMove(long x, long y) { |
| 135 return SendMouseMoveNotifyWhenDone(x, y, NULL); |
| 136 } |
| 137 |
| 138 // Input position is in screen coordinates. However, NSMouseMoved |
| 139 // events require them window-relative, so we adjust. We *DO* flip |
| 140 // the coordinate space, so input events can be the same for all |
| 141 // platforms. E.g. (0,0) is upper-left. |
| 142 bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { |
| 143 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
| 144 CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; |
| 145 NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! |
| 146 if (window) |
| 147 pointInWindow = [window convertScreenToBase:pointInWindow]; |
| 148 NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
| 149 |
| 150 NSEvent* event = |
| 151 [NSEvent mouseEventWithType:NSMouseMoved |
| 152 location:pointInWindow |
| 153 modifierFlags:0 |
| 154 timestamp:timestamp |
| 155 windowNumber:[window windowNumber] |
| 156 context:nil |
| 157 eventNumber:0 |
| 158 clickCount:0 |
| 159 pressure:0.0]; |
| 160 [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
| 161 if (task) |
| 162 MessageLoop::current()->PostTask(FROM_HERE, task); |
| 163 return true; |
| 164 } |
| 165 |
| 166 bool SendMouseEvents(MouseButton type, int state) { |
| 167 return SendMouseEventsNotifyWhenDone(type, state, NULL); |
| 168 } |
| 169 |
| 170 bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { |
| 171 // On windows it appears state can be (UP|DOWN). It is unclear if |
| 172 // that'll happen here but prepare for it just in case. |
| 173 if (state == (UP|DOWN)) { |
| 174 return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && |
| 175 SendMouseEventsNotifyWhenDone(type, UP, task)); |
| 176 } |
| 177 |
| 178 NSEventType etype = 0; |
| 179 if (type == LEFT) { |
| 180 if (state == UP) { |
| 181 etype = NSLeftMouseUp; |
| 182 } else { |
| 183 etype = NSLeftMouseDown; |
| 184 } |
| 185 } else if (type == MIDDLE) { |
| 186 if (state == UP) { |
| 187 etype = NSOtherMouseUp; |
| 188 } else { |
| 189 etype = NSOtherMouseDown; |
| 190 } |
| 191 } else if (type == RIGHT) { |
| 192 if (state == UP) { |
| 193 etype = NSRightMouseUp; |
| 194 } else { |
| 195 etype = NSRightMouseDown; |
| 196 } |
| 197 } else { |
| 198 return false; |
| 199 } |
| 200 NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
| 201 NSPoint location = [NSEvent mouseLocation]; |
| 202 NSPoint pointInWindow = location; |
| 203 if (window) |
| 204 pointInWindow = [window convertScreenToBase:pointInWindow]; |
| 205 |
| 206 NSEvent* event = |
| 207 [NSEvent mouseEventWithType:etype |
| 208 location:pointInWindow |
| 209 modifierFlags:0 |
| 210 timestamp:TimeIntervalSinceSystemStartup() |
| 211 windowNumber:[window windowNumber] |
| 212 context:nil |
| 213 eventNumber:0 |
| 214 clickCount:0 |
| 215 pressure:0.0]; |
| 216 [[NSApplication sharedApplication] sendEvent:event]; |
| 217 if (task) |
| 218 MessageLoop::current()->PostTask(FROM_HERE, task); |
| 219 return true; |
| 220 } |
| 221 |
| 222 bool SendMouseClick(MouseButton type) { |
| 223 return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); |
| 224 } |
| 225 |
| 226 // This appears to only be used by a function in test/ui_test_utils.h: |
| 227 // ui_test_utils::ClickOnView(). That is not implemented on Mac, so |
| 228 // we don't need to implement MoveMouseToCenterAndPress(). I've |
| 229 // suggested an implementation of ClickOnView() which would call Cocoa |
| 230 // directly and not need this indirection, so this may not be needed, |
| 231 // ever. |
| 232 void MoveMouseToCenterAndPress( |
| 233 NSWindow* window, |
| 234 MouseButton button, |
| 235 int state, |
| 236 Task* task) { |
| 237 NOTIMPLEMENTED(); |
| 238 } |
| 239 |
| 240 } // ui_controls |
OLD | NEW |