1/* 2 * Copyright (C) 2011, 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "PlatformEventFactoryMac.h" 28 29#import "KeyEventCocoa.h" 30#import "Logging.h" 31#import "PlatformScreen.h" 32#import "Scrollbar.h" 33#import "WebCoreSystemInterface.h" 34#import "WindowsKeyboardCodes.h" 35#import <mach/mach_time.h> 36#import <wtf/ASCIICType.h> 37 38namespace WebCore { 39 40IntPoint globalPoint(const NSPoint& windowPoint, NSWindow *window) 41{ 42#pragma clang diagnostic push 43#pragma clang diagnostic ignored "-Wdeprecated-declarations" 44 return IntPoint(flipScreenPoint([window convertBaseToScreen:windowPoint], screenForWindow(window))); 45#pragma clang diagnostic pop 46} 47 48static IntPoint globalPointForEvent(NSEvent *event) 49{ 50 switch ([event type]) { 51 case NSLeftMouseDown: 52 case NSLeftMouseDragged: 53 case NSLeftMouseUp: 54 case NSMouseEntered: 55 case NSMouseExited: 56 case NSMouseMoved: 57 case NSOtherMouseDown: 58 case NSOtherMouseDragged: 59 case NSOtherMouseUp: 60 case NSRightMouseDown: 61 case NSRightMouseDragged: 62 case NSRightMouseUp: 63 case NSScrollWheel: 64 return globalPoint([event locationInWindow], [event window]); 65 default: 66 return IntPoint(); 67 } 68} 69 70static IntPoint pointForEvent(NSEvent *event, NSView *windowView) 71{ 72 switch ([event type]) { 73 case NSLeftMouseDown: 74 case NSLeftMouseDragged: 75 case NSLeftMouseUp: 76 case NSMouseEntered: 77 case NSMouseExited: 78 case NSMouseMoved: 79 case NSOtherMouseDown: 80 case NSOtherMouseDragged: 81 case NSOtherMouseUp: 82 case NSRightMouseDown: 83 case NSRightMouseDragged: 84 case NSRightMouseUp: 85 case NSScrollWheel: { 86 // Note: This will have its origin at the bottom left of the window unless windowView is flipped. 87 // In those cases, the Y coordinate gets flipped by Widget::convertFromContainingWindow. 88 NSPoint location = [event locationInWindow]; 89 if (windowView) 90 location = [windowView convertPoint:location fromView:nil]; 91 return IntPoint(location); 92 } 93 default: 94 return IntPoint(); 95 } 96} 97 98static MouseButton mouseButtonForEvent(NSEvent *event) 99{ 100 switch ([event type]) { 101 case NSLeftMouseDown: 102 case NSLeftMouseUp: 103 case NSLeftMouseDragged: 104 return LeftButton; 105 case NSRightMouseDown: 106 case NSRightMouseUp: 107 case NSRightMouseDragged: 108 return RightButton; 109 case NSOtherMouseDown: 110 case NSOtherMouseUp: 111 case NSOtherMouseDragged: 112 return MiddleButton; 113 default: 114 return NoButton; 115 } 116} 117 118static PlatformEvent::Type mouseEventTypeForEvent(NSEvent* event) 119{ 120 switch ([event type]) { 121 case NSLeftMouseDragged: 122 case NSMouseEntered: 123 case NSMouseExited: 124 case NSMouseMoved: 125 case NSOtherMouseDragged: 126 case NSRightMouseDragged: 127 return PlatformEvent::MouseMoved; 128 case NSLeftMouseDown: 129 case NSRightMouseDown: 130 case NSOtherMouseDown: 131 return PlatformEvent::MousePressed; 132 case NSLeftMouseUp: 133 case NSRightMouseUp: 134 case NSOtherMouseUp: 135 return PlatformEvent::MouseReleased; 136 default: 137 return PlatformEvent::MouseMoved; 138 } 139} 140 141static int clickCountForEvent(NSEvent *event) 142{ 143 switch ([event type]) { 144 case NSLeftMouseDown: 145 case NSLeftMouseUp: 146 case NSLeftMouseDragged: 147 case NSRightMouseDown: 148 case NSRightMouseUp: 149 case NSRightMouseDragged: 150 case NSOtherMouseDown: 151 case NSOtherMouseUp: 152 case NSOtherMouseDragged: 153 return [event clickCount]; 154 default: 155 return 0; 156 } 157} 158 159static PlatformWheelEventPhase momentumPhaseForEvent(NSEvent *event) 160{ 161 uint32_t phase = PlatformWheelEventPhaseNone; 162 163 if ([event momentumPhase] & NSEventPhaseBegan) 164 phase |= PlatformWheelEventPhaseBegan; 165 if ([event momentumPhase] & NSEventPhaseStationary) 166 phase |= PlatformWheelEventPhaseStationary; 167 if ([event momentumPhase] & NSEventPhaseChanged) 168 phase |= PlatformWheelEventPhaseChanged; 169 if ([event momentumPhase] & NSEventPhaseEnded) 170 phase |= PlatformWheelEventPhaseEnded; 171 if ([event momentumPhase] & NSEventPhaseCancelled) 172 phase |= PlatformWheelEventPhaseCancelled; 173 174 return static_cast<PlatformWheelEventPhase>(phase); 175} 176 177static PlatformWheelEventPhase phaseForEvent(NSEvent *event) 178{ 179 uint32_t phase = PlatformWheelEventPhaseNone; 180 if ([event phase] & NSEventPhaseBegan) 181 phase |= PlatformWheelEventPhaseBegan; 182 if ([event phase] & NSEventPhaseStationary) 183 phase |= PlatformWheelEventPhaseStationary; 184 if ([event phase] & NSEventPhaseChanged) 185 phase |= PlatformWheelEventPhaseChanged; 186 if ([event phase] & NSEventPhaseEnded) 187 phase |= PlatformWheelEventPhaseEnded; 188 if ([event phase] & NSEventPhaseCancelled) 189 phase |= PlatformWheelEventPhaseCancelled; 190#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 191 if ([event momentumPhase] & NSEventPhaseMayBegin) 192 phase |= PlatformWheelEventPhaseMayBegin; 193#endif 194 195 return static_cast<PlatformWheelEventPhase>(phase); 196} 197 198static inline String textFromEvent(NSEvent* event) 199{ 200 if ([event type] == NSFlagsChanged) 201 return emptyString(); 202 return String([event characters]); 203} 204 205static inline String unmodifiedTextFromEvent(NSEvent* event) 206{ 207 if ([event type] == NSFlagsChanged) 208 return emptyString(); 209 return String([event charactersIgnoringModifiers]); 210} 211 212String keyIdentifierForKeyEvent(NSEvent* event) 213{ 214 if ([event type] == NSFlagsChanged) 215 switch ([event keyCode]) { 216 case 54: // Right Command 217 case 55: // Left Command 218 return String("Meta"); 219 220 case 57: // Capslock 221 return String("CapsLock"); 222 223 case 56: // Left Shift 224 case 60: // Right Shift 225 return String("Shift"); 226 227 case 58: // Left Alt 228 case 61: // Right Alt 229 return String("Alt"); 230 231 case 59: // Left Ctrl 232 case 62: // Right Ctrl 233 return String("Control"); 234 235 default: 236 ASSERT_NOT_REACHED(); 237 return emptyString(); 238 } 239 240 NSString *s = [event charactersIgnoringModifiers]; 241 if ([s length] != 1) { 242 LOG(Events, "received an unexpected number of characters in key event: %u", [s length]); 243 return "Unidentified"; 244 } 245 return keyIdentifierForCharCode([s characterAtIndex:0]); 246} 247 248static bool isKeypadEvent(NSEvent* event) 249{ 250 // Check that this is the type of event that has a keyCode. 251 switch ([event type]) { 252 case NSKeyDown: 253 case NSKeyUp: 254 case NSFlagsChanged: 255 break; 256 default: 257 return false; 258 } 259 260 if ([event modifierFlags] & NSNumericPadKeyMask) 261 return true; 262 263 switch ([event keyCode]) { 264 case 71: // Clear 265 case 81: // = 266 case 75: // / 267 case 67: // * 268 case 78: // - 269 case 69: // + 270 case 76: // Enter 271 case 65: // . 272 case 82: // 0 273 case 83: // 1 274 case 84: // 2 275 case 85: // 3 276 case 86: // 4 277 case 87: // 5 278 case 88: // 6 279 case 89: // 7 280 case 91: // 8 281 case 92: // 9 282 return true; 283 } 284 285 return false; 286} 287 288int windowsKeyCodeForKeyEvent(NSEvent* event) 289{ 290 int code = 0; 291 // There are several kinds of characters for which we produce key code from char code: 292 // 1. Roman letters. Windows keyboard layouts affect both virtual key codes and character codes for these, 293 // so e.g. 'A' gets the same keyCode on QWERTY, AZERTY or Dvorak layouts. 294 // 2. Keys for which there is no known Mac virtual key codes, like PrintScreen. 295 // 3. Certain punctuation keys. On Windows, these are also remapped depending on current keyboard layout, 296 // but see comment in windowsKeyCodeForCharCode(). 297 if (!isKeypadEvent(event) && ([event type] == NSKeyDown || [event type] == NSKeyUp)) { 298 // Cmd switches Roman letters for Dvorak-QWERTY layout, so try modified characters first. 299 NSString* s = [event characters]; 300 code = [s length] > 0 ? windowsKeyCodeForCharCode([s characterAtIndex:0]) : 0; 301 if (code) 302 return code; 303 304 // Ctrl+A on an AZERTY keyboard would get VK_Q keyCode if we relied on -[NSEvent keyCode] below. 305 s = [event charactersIgnoringModifiers]; 306 code = [s length] > 0 ? windowsKeyCodeForCharCode([s characterAtIndex:0]) : 0; 307 if (code) 308 return code; 309 } 310 311 // Map Mac virtual key code directly to Windows one for any keys not handled above. 312 // E.g. the key next to Caps Lock has the same Event.keyCode on U.S. keyboard ('A') and on Russian keyboard (CYRILLIC LETTER EF). 313 return windowsKeyCodeForKeyCode([event keyCode]); 314} 315 316static CFAbsoluteTime systemStartupTime; 317 318static void updateSystemStartupTimeIntervalSince1970() 319{ 320 // CFAbsoluteTimeGetCurrent() provides the absolute time in seconds since 2001. 321 // mach_absolute_time() provides a relative system time since startup minus the time the computer was suspended. 322 mach_timebase_info_data_t timebase_info; 323 mach_timebase_info(&timebase_info); 324 double elapsedTimeSinceStartup = static_cast<double>(mach_absolute_time()) * timebase_info.numer / timebase_info.denom / 1e9; 325 systemStartupTime = kCFAbsoluteTimeIntervalSince1970 + CFAbsoluteTimeGetCurrent() - elapsedTimeSinceStartup; 326} 327 328static CFTimeInterval cachedStartupTimeIntervalSince1970() 329{ 330 static dispatch_once_t once; 331 dispatch_once(&once, ^{ 332 void (^updateBlock)(NSNotification *) = Block_copy(^(NSNotification *){ updateSystemStartupTimeIntervalSince1970(); }); 333 [[[NSWorkspace sharedWorkspace] notificationCenter] addObserverForName:NSWorkspaceDidWakeNotification 334 object:nil 335 queue:nil 336 usingBlock:updateBlock]; 337 [[NSNotificationCenter defaultCenter] addObserverForName:NSSystemClockDidChangeNotification 338 object:nil 339 queue:nil 340 usingBlock:updateBlock]; 341 Block_release(updateBlock); 342 343 updateSystemStartupTimeIntervalSince1970(); 344 }); 345 return systemStartupTime; 346} 347 348double eventTimeStampSince1970(NSEvent* event) 349{ 350 return static_cast<double>(cachedStartupTimeIntervalSince1970() + [event timestamp]); 351} 352 353static inline bool isKeyUpEvent(NSEvent *event) 354{ 355 if ([event type] != NSFlagsChanged) 356 return [event type] == NSKeyUp; 357 // FIXME: This logic fails if the user presses both Shift keys at once, for example: 358 // we treat releasing one of them as keyDown. 359 switch ([event keyCode]) { 360 case 54: // Right Command 361 case 55: // Left Command 362 return ([event modifierFlags] & NSCommandKeyMask) == 0; 363 364 case 57: // Capslock 365 return ([event modifierFlags] & NSAlphaShiftKeyMask) == 0; 366 367 case 56: // Left Shift 368 case 60: // Right Shift 369 return ([event modifierFlags] & NSShiftKeyMask) == 0; 370 371 case 58: // Left Alt 372 case 61: // Right Alt 373 return ([event modifierFlags] & NSAlternateKeyMask) == 0; 374 375 case 59: // Left Ctrl 376 case 62: // Right Ctrl 377 return ([event modifierFlags] & NSControlKeyMask) == 0; 378 379 case 63: // Function 380 return ([event modifierFlags] & NSFunctionKeyMask) == 0; 381 } 382 return false; 383} 384 385static inline PlatformEvent::Modifiers modifiersForEvent(NSEvent *event) 386{ 387 unsigned modifiers = 0; 388 if ([event modifierFlags] & NSShiftKeyMask) 389 modifiers |= PlatformEvent::ShiftKey; 390 if ([event modifierFlags] & NSControlKeyMask) 391 modifiers |= PlatformEvent::CtrlKey; 392 if ([event modifierFlags] & NSAlternateKeyMask) 393 modifiers |= PlatformEvent::AltKey; 394 if ([event modifierFlags] & NSCommandKeyMask) 395 modifiers |= PlatformEvent::MetaKey; 396 return (PlatformEvent::Modifiers)modifiers; 397} 398 399 400class PlatformMouseEventBuilder : public PlatformMouseEvent { 401public: 402 PlatformMouseEventBuilder(NSEvent *event, NSView *windowView) 403 { 404 // PlatformEvent 405 m_type = mouseEventTypeForEvent(event); 406 m_modifiers = modifiersForEvent(event); 407 m_timestamp = eventTimeStampSince1970(event); 408 409 // PlatformMouseEvent 410 m_position = pointForEvent(event, windowView); 411 m_globalPosition = globalPointForEvent(event); 412 m_button = mouseButtonForEvent(event); 413 m_clickCount = clickCountForEvent(event); 414 415 // Mac specific 416 m_modifierFlags = [event modifierFlags]; 417 m_eventNumber = [event eventNumber]; 418 } 419}; 420 421PlatformMouseEvent PlatformEventFactory::createPlatformMouseEvent(NSEvent *event, NSView *windowView) 422{ 423 return PlatformMouseEventBuilder(event, windowView); 424} 425 426 427class PlatformWheelEventBuilder : public PlatformWheelEvent { 428public: 429 PlatformWheelEventBuilder(NSEvent *event, NSView *windowView) 430 { 431 // PlatformEvent 432 m_type = PlatformEvent::Wheel; 433 m_modifiers = modifiersForEvent(event); 434 m_timestamp = eventTimeStampSince1970(event); 435 436 // PlatformWheelEvent 437 m_position = pointForEvent(event, windowView); 438 m_globalPosition = globalPointForEvent(event); 439 m_granularity = ScrollByPixelWheelEvent; 440 441 BOOL continuous; 442 wkGetWheelEventDeltas(event, &m_deltaX, &m_deltaY, &continuous); 443 if (continuous) { 444 m_wheelTicksX = m_deltaX / static_cast<float>(Scrollbar::pixelsPerLineStep()); 445 m_wheelTicksY = m_deltaY / static_cast<float>(Scrollbar::pixelsPerLineStep()); 446 } else { 447 m_wheelTicksX = m_deltaX; 448 m_wheelTicksY = m_deltaY; 449 m_deltaX *= static_cast<float>(Scrollbar::pixelsPerLineStep()); 450 m_deltaY *= static_cast<float>(Scrollbar::pixelsPerLineStep()); 451 } 452 453 m_phase = phaseForEvent(event); 454 m_momentumPhase = momentumPhaseForEvent(event); 455 m_hasPreciseScrollingDeltas = continuous; 456 m_directionInvertedFromDevice = [event isDirectionInvertedFromDevice]; 457 } 458}; 459 460PlatformWheelEvent PlatformEventFactory::createPlatformWheelEvent(NSEvent *event, NSView *windowView) 461{ 462 return PlatformWheelEventBuilder(event, windowView); 463} 464 465 466class PlatformKeyboardEventBuilder : public PlatformKeyboardEvent { 467public: 468 PlatformKeyboardEventBuilder(NSEvent *event) 469 { 470 // PlatformEvent 471 m_type = isKeyUpEvent(event) ? PlatformEvent::KeyUp : PlatformEvent::KeyDown; 472 m_modifiers = modifiersForEvent(event); 473 m_timestamp = eventTimeStampSince1970(event); 474 475 // PlatformKeyboardEvent 476 m_text = textFromEvent(event); 477 m_unmodifiedText = unmodifiedTextFromEvent(event); 478 m_keyIdentifier = keyIdentifierForKeyEvent(event); 479 m_windowsVirtualKeyCode = windowsKeyCodeForKeyEvent(event); 480 m_nativeVirtualKeyCode = [event keyCode]; 481 m_macCharCode = wkGetNSEventKeyChar(event); 482 m_autoRepeat = ([event type] != NSFlagsChanged) && [event isARepeat]; 483 m_isKeypad = isKeypadEvent(event); 484 m_isSystemKey = false; // SystemKey is always false on the Mac. 485 486 // Always use 13 for Enter/Return -- we don't want to use AppKit's different character for Enter. 487 if (m_windowsVirtualKeyCode == VK_RETURN) { 488 m_text = "\r"; 489 m_unmodifiedText = "\r"; 490 } 491 492 // AppKit sets text to "\x7F" for backspace, but the correct KeyboardEvent character code is 8. 493 if (m_windowsVirtualKeyCode == VK_BACK) { 494 m_text = "\x8"; 495 m_unmodifiedText = "\x8"; 496 } 497 498 // Always use 9 for Tab -- we don't want to use AppKit's different character for shift-tab. 499 if (m_windowsVirtualKeyCode == VK_TAB) { 500 m_text = "\x9"; 501 m_unmodifiedText = "\x9"; 502 } 503 504 // Mac specific. 505 m_macEvent = event; 506 } 507}; 508 509PlatformKeyboardEvent PlatformEventFactory::createPlatformKeyboardEvent(NSEvent *event) 510{ 511 return PlatformKeyboardEventBuilder(event); 512} 513 514} // namespace WebCore 515