| // Copyright 2014 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. |
| |
| #import <Cocoa/Cocoa.h> |
| #include <mach/mach_time.h> |
| #include <stdint.h> |
| |
| #import "ui/events/keycodes/keyboard_code_conversion_mac.h" |
| #include "ui/events/test/cocoa_test_event_utils.h" |
| |
| namespace cocoa_test_event_utils { |
| |
| 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; |
| } |
| |
| } // namespace |
| |
| NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type, |
| NSUInteger modifiers) { |
| if (type == NSOtherMouseUp) { |
| // To synthesize middle clicks we need to create a CGEvent with the |
| // "center" button flags so that our resulting NSEvent will have the |
| // appropriate buttonNumber field. NSEvent provides no way to create a |
| // mouse event with a buttonNumber directly. |
| CGPoint location = { point.x, point.y }; |
| CGEventRef cg_event = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseUp, |
| location, |
| kCGMouseButtonCenter); |
| // Also specify the modifiers for the middle click case. This makes this |
| // test resilient to external modifiers being pressed. |
| CGEventSetFlags(cg_event, static_cast<CGEventFlags>(modifiers)); |
| NSEvent* event = [NSEvent eventWithCGEvent:cg_event]; |
| CFRelease(cg_event); |
| return event; |
| } |
| return [NSEvent mouseEventWithType:type |
| location:point |
| modifierFlags:modifiers |
| timestamp:0 |
| windowNumber:0 |
| context:nil |
| eventNumber:0 |
| clickCount:1 |
| pressure:1.0]; |
| } |
| |
| NSEvent* MouseEventWithType(NSEventType type, NSUInteger modifiers) { |
| return MouseEventAtPoint(NSZeroPoint, type, modifiers); |
| } |
| |
| NSEvent* MouseEventAtPointInWindow(NSPoint point, |
| NSEventType type, |
| NSWindow* window, |
| NSUInteger clickCount) { |
| return [NSEvent mouseEventWithType:type |
| location:point |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:[window windowNumber] |
| context:nil |
| eventNumber:0 |
| clickCount:clickCount |
| pressure:1.0]; |
| } |
| |
| NSEvent* RightMouseDownAtPointInWindow(NSPoint point, NSWindow* window) { |
| return MouseEventAtPointInWindow(point, NSRightMouseDown, window, 1); |
| } |
| |
| NSEvent* RightMouseDownAtPoint(NSPoint point) { |
| return RightMouseDownAtPointInWindow(point, nil); |
| } |
| |
| NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window) { |
| return MouseEventAtPointInWindow(point, NSLeftMouseDown, window, 1); |
| } |
| |
| NSEvent* LeftMouseDownAtPoint(NSPoint point) { |
| return LeftMouseDownAtPointInWindow(point, nil); |
| } |
| |
| std::pair<NSEvent*,NSEvent*> MouseClickInView(NSView* view, |
| NSUInteger clickCount) { |
| const NSRect bounds = [view convertRect:[view bounds] toView:nil]; |
| const NSPoint mid_point = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); |
| NSEvent* down = MouseEventAtPointInWindow(mid_point, NSLeftMouseDown, |
| [view window], clickCount); |
| NSEvent* up = MouseEventAtPointInWindow(mid_point, NSLeftMouseUp, |
| [view window], clickCount); |
| return std::make_pair(down, up); |
| } |
| |
| std::pair<NSEvent*, NSEvent*> RightMouseClickInView(NSView* view, |
| NSUInteger clickCount) { |
| const NSRect bounds = [view convertRect:[view bounds] toView:nil]; |
| const NSPoint mid_point = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); |
| NSEvent* down = MouseEventAtPointInWindow(mid_point, NSRightMouseDown, |
| [view window], clickCount); |
| NSEvent* up = MouseEventAtPointInWindow(mid_point, NSRightMouseUp, |
| [view window], clickCount); |
| return std::make_pair(down, up); |
| } |
| |
| NSEvent* KeyEventWithCharacter(unichar c) { |
| return KeyEventWithKeyCode(0, c, NSKeyDown, 0); |
| } |
| |
| NSEvent* KeyEventWithType(NSEventType event_type, NSUInteger modifiers) { |
| return KeyEventWithKeyCode(0x78, 'x', event_type, modifiers); |
| } |
| |
| NSEvent* KeyEventWithKeyCode(unsigned short key_code, |
| unichar c, |
| NSEventType event_type, |
| NSUInteger modifiers) { |
| NSString* chars = [NSString stringWithCharacters:&c length:1]; |
| return [NSEvent keyEventWithType:event_type |
| location:NSZeroPoint |
| modifierFlags:modifiers |
| timestamp:0 |
| windowNumber:0 |
| context:nil |
| characters:chars |
| charactersIgnoringModifiers:chars |
| isARepeat:NO |
| keyCode:key_code]; |
| } |
| |
| static NSEvent* EnterExitEventWithType(NSEventType event_type) { |
| return [NSEvent enterExitEventWithType:event_type |
| location:NSZeroPoint |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:0 |
| context:nil |
| eventNumber:0 |
| trackingNumber:0 |
| userData:NULL]; |
| } |
| |
| NSEvent* EnterEvent() { |
| return EnterExitEventWithType(NSMouseEntered); |
| } |
| |
| NSEvent* ExitEvent() { |
| return EnterExitEventWithType(NSMouseExited); |
| } |
| |
| NSEvent* OtherEventWithType(NSEventType event_type) { |
| return [NSEvent otherEventWithType:event_type |
| location:NSZeroPoint |
| modifierFlags:0 |
| timestamp:0 |
| windowNumber:0 |
| context:nil |
| subtype:0 |
| data1:0 |
| data2:0]; |
| } |
| |
| NSTimeInterval TimeIntervalSinceSystemStartup() { |
| return UpTimeInNanoseconds() / 1000000000.0; |
| } |
| |
| NSEvent* SynthesizeKeyEvent(NSWindow* window, |
| bool keyDown, |
| ui::KeyboardCode keycode, |
| NSUInteger flags, |
| ui::DomKey dom_key) { |
| // If caps lock is set for an alpha keycode, treat it as if shift was pressed. |
| // Note on Mac (unlike other platforms) shift while caps is down does not go |
| // back to lowercase. |
| if (keycode >= ui::VKEY_A && keycode <= ui::VKEY_Z && |
| (flags & NSAlphaShiftKeyMask)) |
| flags |= NSShiftKeyMask; |
| |
| // Clear caps regardless -- MacKeyCodeForWindowsKeyCode doesn't implement |
| // logic to support it. |
| flags &= ~NSAlphaShiftKeyMask; |
| |
| unichar character; |
| unichar shifted_character; |
| int macKeycode = ui::MacKeyCodeForWindowsKeyCode( |
| keycode, flags, &shifted_character, &character); |
| |
| if (macKeycode < 0) |
| return nil; |
| |
| // If an explicit unicode character is provided, use that instead of the one |
| // derived from the keycode. |
| if (dom_key.IsCharacter()) |
| shifted_character = dom_key.ToCharacter(); |
| |
| // Note that, in line with AppKit's documentation (and tracing "real" events), |
| // -[NSEvent charactersIngoringModifiers]" are "the characters generated by |
| // the receiving key event as if no modifier key (except for Shift)". |
| // So |charactersIgnoringModifiers| uses |shifted_character|. |
| NSString* charactersIgnoringModifiers = |
| [[[NSString alloc] initWithCharacters:&shifted_character |
| length:1] autorelease]; |
| NSString* characters; |
| // The following were determined empirically on OSX 10.9. |
| if (flags & NSControlKeyMask) { |
| // If Ctrl is pressed, Cocoa always puts an empty string into |characters|. |
| characters = [NSString string]; |
| } else if (flags & NSCommandKeyMask) { |
| // If Cmd is pressed, Cocoa puts a lowercase character into |characters|, |
| // regardless of Shift. If, however, Alt is also pressed then shift *is* |
| // preserved, but re-mappings for Alt are not implemented. Although we still |
| // need to support Alt for things like Alt+Left/Right which don't care. |
| characters = |
| [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; |
| } else { |
| // If just Shift or nothing is pressed, |characters| will match |
| // |charactersIgnoringModifiers|. Alt puts a special character into |
| // |characters| (not |charactersIgnoringModifiers|), but they're not mapped |
| // here. |
| characters = charactersIgnoringModifiers; |
| } |
| |
| NSEventType type = (keyDown ? NSKeyDown : NSKeyUp); |
| |
| // Modifier keys generate NSFlagsChanged event rather than |
| // NSKeyDown/NSKeyUp events. |
| if (keycode == ui::VKEY_CONTROL || keycode == ui::VKEY_SHIFT || |
| keycode == ui::VKEY_MENU || keycode == ui::VKEY_COMMAND) |
| type = NSFlagsChanged; |
| |
| // For events other than mouse moved, [event locationInWindow] is |
| // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) |
| // location should be fine. |
| NSEvent* event = [NSEvent keyEventWithType:type |
| location:NSZeroPoint |
| modifierFlags:flags |
| timestamp:TimeIntervalSinceSystemStartup() |
| windowNumber:[window windowNumber] |
| context:nil |
| characters:characters |
| charactersIgnoringModifiers:charactersIgnoringModifiers |
| isARepeat:NO |
| keyCode:(unsigned short)macKeycode]; |
| |
| return event; |
| } |
| |
| } // namespace cocoa_test_event_utils |