blob: 715135c15e9649d8d2100fa9915dcd92dcf123a9 [file] [log] [blame]
// 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