// 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 #include #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