diff options
Diffstat (limited to 'chrome/browser/automation/ui_controls_mac.mm')
-rw-r--r-- | chrome/browser/automation/ui_controls_mac.mm | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm new file mode 100644 index 0000000..715135c --- /dev/null +++ b/chrome/browser/automation/ui_controls_mac.mm @@ -0,0 +1,242 @@ +// Copyright (c) 2010 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 "chrome/browser/automation/ui_controls.h" + +#import <Cocoa/Cocoa.h> +#include <mach/mach_time.h> + +#include "base/message_loop.h" +#include "chrome/browser/chrome_thread.h" + +// Implementation details: We use [NSApplication sendEvent:] instead +// of [NSApplication postEvent:atStart:] so that the event gets sent +// immediately. This lets us run the post-event task right +// immediately as well. Unfortunately I cannot subclass NSEvent (it's +// probably a class cluster) to allow other easy answers. For +// example, if I could subclass NSEvent, I could run the Task in it's +// dealloc routine (which necessarily happens after the event is +// dispatched). Unlike Linux, Mac does not have message loop +// observer/notification. Unlike windows, I cannot post non-events +// into the event queue. (I can post other kinds of tasks but can't +// guarantee their order with regards to events). + +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; +} + +NSTimeInterval TimeIntervalSinceSystemStartup() { + return UpTimeInNanoseconds() / 1000000000.0; +} + +} // anonymous namespace + + +namespace ui_controls { + +bool SendKeyPress(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command) { + return SendKeyPressNotifyWhenDone(window, key, + control, shift, alt, command, + NULL); +} + +// Win and Linux implement a SendKeyPress() this as a +// SendKeyPressAndRelease(), so we should as well (despite the name). +// +// TODO(jrg): handle "characters" better (e.g. apply shift?) +bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command, + Task* task) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + NSUInteger flags = 0; + if (control) + flags |= NSControlKeyMask; + if (shift) + flags |= NSShiftKeyMask; + if (alt) + flags |= NSAlternateKeyMask; + if (command) + flags |= NSCommandKeyMask; + unsigned char keycode = key; + NSString* charactersIgnoringModifiers = [[[NSString alloc] + initWithBytes:&keycode + length:1 + encoding:NSUTF8StringEncoding] + autorelease]; + NSString* characters = charactersIgnoringModifiers; + + // For events other than mouse moved, [event locationInWindow] is + // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) + // locaiton should be fine. + // First a key down... + NSEvent* event = + [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:flags + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:NO + keyCode:key]; + [[NSApplication sharedApplication] sendEvent:event]; + // Then a key up. + event = + [NSEvent keyEventWithType:NSKeyUp + location:NSMakePoint(0,0) + modifierFlags:flags + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:NO + keyCode:key]; + [[NSApplication sharedApplication] sendEvent:event]; + + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseMove(long x, long y) { + return SendMouseMoveNotifyWhenDone(x, y, NULL); +} + +// Input position is in screen coordinates. However, NSMouseMoved +// events require them window-relative, so we adjust. We *DO* flip +// the coordinate space, so input events can be the same for all +// platforms. E.g. (0,0) is upper-left. +bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + NSWindow* window = [[NSApplication sharedApplication] keyWindow]; + CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; + NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! + if (window) + pointInWindow = [window convertScreenToBase:pointInWindow]; + NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); + + NSEvent* event = + [NSEvent mouseEventWithType:NSMouseMoved + location:pointInWindow + modifierFlags:0 + timestamp:timestamp + windowNumber:[window windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:0.0]; + [[NSApplication sharedApplication] postEvent:event atStart:NO]; + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseEvents(MouseButton type, int state) { + return SendMouseEventsNotifyWhenDone(type, state, NULL); +} + +bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + // On windows it appears state can be (UP|DOWN). It is unclear if + // that'll happen here but prepare for it just in case. + if (state == (UP|DOWN)) { + return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && + SendMouseEventsNotifyWhenDone(type, UP, task)); + } + + NSEventType etype = 0; + if (type == LEFT) { + if (state == UP) { + etype = NSLeftMouseUp; + } else { + etype = NSLeftMouseDown; + } + } else if (type == MIDDLE) { + if (state == UP) { + etype = NSOtherMouseUp; + } else { + etype = NSOtherMouseDown; + } + } else if (type == RIGHT) { + if (state == UP) { + etype = NSRightMouseUp; + } else { + etype = NSRightMouseDown; + } + } else { + return false; + } + NSWindow* window = [[NSApplication sharedApplication] keyWindow]; + NSPoint location = [NSEvent mouseLocation]; + NSPoint pointInWindow = location; + if (window) + pointInWindow = [window convertScreenToBase:pointInWindow]; + + NSEvent* event = + [NSEvent mouseEventWithType:etype + location:pointInWindow + modifierFlags:0 + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:0.0]; + [[NSApplication sharedApplication] sendEvent:event]; + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); +} + +// This appears to only be used by a function in test/ui_test_utils.h: +// ui_test_utils::ClickOnView(). That is not implemented on Mac, so +// we don't need to implement MoveMouseToCenterAndPress(). I've +// suggested an implementation of ClickOnView() which would call Cocoa +// directly and not need this indirection, so this may not be needed, +// ever. +void MoveMouseToCenterAndPress( + NSWindow* window, + MouseButton button, + int state, + Task* task) { + NOTIMPLEMENTED(); +} + +} // ui_controls |