1/* 2 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2006-2009 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "WebInputEventFactory.h" 29 30#include <ApplicationServices/ApplicationServices.h> 31#import <Cocoa/Cocoa.h> 32 33#import "KeyEventCocoa.h" 34#include "WebInputEvent.h" 35#include <wtf/ASCIICType.h> 36 37namespace WebKit { 38 39// WebKeyboardEvent ----------------------------------------------------------- 40 41// ---------------------------------------------------------------------------- 42// Begin Apple code, copied from KeyEventMac.mm 43// 44// We can share some of this code if we factored it out of KeyEventMac, but 45// the main problem is that it relies on the NSString ctor on String for 46// conversions, and since we're building without PLATFORM(MAC), we don't have 47// that. As a result we have to use NSString here exclusively and thus tweak 48// the code so it's not re-usable as-is. One possiblity would be to make the 49// upstream code only use NSString, but I'm not certain how far that change 50// would propagate. 51 52static inline bool isKeyUpEvent(NSEvent* event) 53{ 54 if ([event type] != NSFlagsChanged) 55 return [event type] == NSKeyUp; 56 // FIXME: This logic fails if the user presses both Shift keys at once, for example: 57 // we treat releasing one of them as keyDown. 58 switch ([event keyCode]) { 59 case 54: // Right Command 60 case 55: // Left Command 61 return ([event modifierFlags] & NSCommandKeyMask) == 0; 62 63 case 57: // Capslock 64 return ([event modifierFlags] & NSAlphaShiftKeyMask) == 0; 65 66 case 56: // Left Shift 67 case 60: // Right Shift 68 return ([event modifierFlags] & NSShiftKeyMask) == 0; 69 70 case 58: // Left Alt 71 case 61: // Right Alt 72 return ([event modifierFlags] & NSAlternateKeyMask) == 0; 73 74 case 59: // Left Ctrl 75 case 62: // Right Ctrl 76 return ([event modifierFlags] & NSControlKeyMask) == 0; 77 78 case 63: // Function 79 return ([event modifierFlags] & NSFunctionKeyMask) == 0; 80 } 81 return false; 82} 83 84static bool isKeypadEvent(NSEvent* event) 85{ 86 // Check that this is the type of event that has a keyCode. 87 switch ([event type]) { 88 case NSKeyDown: 89 case NSKeyUp: 90 case NSFlagsChanged: 91 break; 92 default: 93 return false; 94 } 95 96 if ([event modifierFlags] & NSNumericPadKeyMask) 97 return true; 98 99 switch ([event keyCode]) { 100 case 71: // Clear 101 case 81: // = 102 case 75: // / 103 case 67: // * 104 case 78: // - 105 case 69: // + 106 case 76: // Enter 107 case 65: // . 108 case 82: // 0 109 case 83: // 1 110 case 84: // 2 111 case 85: // 3 112 case 86: // 4 113 case 87: // 5 114 case 88: // 6 115 case 89: // 7 116 case 91: // 8 117 case 92: // 9 118 return true; 119 } 120 121 return false; 122} 123 124static int windowsKeyCodeForKeyEvent(NSEvent* event) 125{ 126 int code = 0; 127 // There are several kinds of characters for which we produce key code from char code: 128 // 1. Roman letters. Windows keyboard layouts affect both virtual key codes and character codes for these, 129 // so e.g. 'A' gets the same keyCode on QWERTY, AZERTY or Dvorak layouts. 130 // 2. Keys for which there is no known Mac virtual key codes, like PrintScreen. 131 // 3. Certain punctuation keys. On Windows, these are also remapped depending on current keyboard layout, 132 // but see comment in windowsKeyCodeForCharCode(). 133 if ([event type] == NSKeyDown || [event type] == NSKeyUp) { 134 // Cmd switches Roman letters for Dvorak-QWERTY layout, so try modified characters first. 135 NSString* s = [event characters]; 136 code = [s length] > 0 ? WebCore::windowsKeyCodeForCharCode([s characterAtIndex:0]) : 0; 137 if (code) 138 return code; 139 140 // Ctrl+A on an AZERTY keyboard would get VK_Q keyCode if we relied on -[NSEvent keyCode] below. 141 s = [event charactersIgnoringModifiers]; 142 code = [s length] > 0 ? WebCore::windowsKeyCodeForCharCode([s characterAtIndex:0]) : 0; 143 if (code) 144 return code; 145 } 146 147 // Map Mac virtual key code directly to Windows one for any keys not handled above. 148 // 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). 149 return WebCore::windowsKeyCodeForKeyCode([event keyCode]); 150} 151 152static inline NSString* textFromEvent(NSEvent* event) 153{ 154 if ([event type] == NSFlagsChanged) 155 return @""; 156 return [event characters]; 157} 158 159static inline NSString* unmodifiedTextFromEvent(NSEvent* event) 160{ 161 if ([event type] == NSFlagsChanged) 162 return @""; 163 return [event charactersIgnoringModifiers]; 164} 165 166static NSString* keyIdentifierForKeyEvent(NSEvent* event) 167{ 168 if ([event type] == NSFlagsChanged) { 169 switch ([event keyCode]) { 170 case 54: // Right Command 171 case 55: // Left Command 172 return @"Meta"; 173 174 case 57: // Capslock 175 return @"CapsLock"; 176 177 case 56: // Left Shift 178 case 60: // Right Shift 179 return @"Shift"; 180 181 case 58: // Left Alt 182 case 61: // Right Alt 183 return @"Alt"; 184 185 case 59: // Left Ctrl 186 case 62: // Right Ctrl 187 return @"Control"; 188 189// Begin non-Apple addition/modification -------------------------------------- 190 case 63: // Function 191 return @"Function"; 192 193 default: // Unknown, but this may be a strange/new keyboard. 194 return @"Unidentified"; 195// End non-Apple addition/modification ---------------------------------------- 196 } 197 } 198 199 NSString* s = [event charactersIgnoringModifiers]; 200 if ([s length] != 1) 201 return @"Unidentified"; 202 203 unichar c = [s characterAtIndex:0]; 204 switch (c) { 205 // Each identifier listed in the DOM spec is listed here. 206 // Many are simply commented out since they do not appear on standard Macintosh keyboards 207 // or are on a key that doesn't have a corresponding character. 208 209 // "Accept" 210 // "AllCandidates" 211 212 // "Alt" 213 case NSMenuFunctionKey: 214 return @"Alt"; 215 216 // "Apps" 217 // "BrowserBack" 218 // "BrowserForward" 219 // "BrowserHome" 220 // "BrowserRefresh" 221 // "BrowserSearch" 222 // "BrowserStop" 223 // "CapsLock" 224 225 // "Clear" 226 case NSClearLineFunctionKey: 227 return @"Clear"; 228 229 // "CodeInput" 230 // "Compose" 231 // "Control" 232 // "Crsel" 233 // "Convert" 234 // "Copy" 235 // "Cut" 236 237 // "Down" 238 case NSDownArrowFunctionKey: 239 return @"Down"; 240 // "End" 241 case NSEndFunctionKey: 242 return @"End"; 243 // "Enter" 244 case 0x3: case 0xA: case 0xD: // Macintosh calls the one on the main keyboard Return, but Windows calls it Enter, so we'll do the same for the DOM 245 return @"Enter"; 246 247 // "EraseEof" 248 249 // "Execute" 250 case NSExecuteFunctionKey: 251 return @"Execute"; 252 253 // "Exsel" 254 255 // "F1" 256 case NSF1FunctionKey: 257 return @"F1"; 258 // "F2" 259 case NSF2FunctionKey: 260 return @"F2"; 261 // "F3" 262 case NSF3FunctionKey: 263 return @"F3"; 264 // "F4" 265 case NSF4FunctionKey: 266 return @"F4"; 267 // "F5" 268 case NSF5FunctionKey: 269 return @"F5"; 270 // "F6" 271 case NSF6FunctionKey: 272 return @"F6"; 273 // "F7" 274 case NSF7FunctionKey: 275 return @"F7"; 276 // "F8" 277 case NSF8FunctionKey: 278 return @"F8"; 279 // "F9" 280 case NSF9FunctionKey: 281 return @"F9"; 282 // "F10" 283 case NSF10FunctionKey: 284 return @"F10"; 285 // "F11" 286 case NSF11FunctionKey: 287 return @"F11"; 288 // "F12" 289 case NSF12FunctionKey: 290 return @"F12"; 291 // "F13" 292 case NSF13FunctionKey: 293 return @"F13"; 294 // "F14" 295 case NSF14FunctionKey: 296 return @"F14"; 297 // "F15" 298 case NSF15FunctionKey: 299 return @"F15"; 300 // "F16" 301 case NSF16FunctionKey: 302 return @"F16"; 303 // "F17" 304 case NSF17FunctionKey: 305 return @"F17"; 306 // "F18" 307 case NSF18FunctionKey: 308 return @"F18"; 309 // "F19" 310 case NSF19FunctionKey: 311 return @"F19"; 312 // "F20" 313 case NSF20FunctionKey: 314 return @"F20"; 315 // "F21" 316 case NSF21FunctionKey: 317 return @"F21"; 318 // "F22" 319 case NSF22FunctionKey: 320 return @"F22"; 321 // "F23" 322 case NSF23FunctionKey: 323 return @"F23"; 324 // "F24" 325 case NSF24FunctionKey: 326 return @"F24"; 327 328 // "FinalMode" 329 330 // "Find" 331 case NSFindFunctionKey: 332 return @"Find"; 333 334 // "FullWidth" 335 // "HalfWidth" 336 // "HangulMode" 337 // "HanjaMode" 338 339 // "Help" 340 case NSHelpFunctionKey: 341 return @"Help"; 342 343 // "Hiragana" 344 345 // "Home" 346 case NSHomeFunctionKey: 347 return @"Home"; 348 // "Insert" 349 case NSInsertFunctionKey: 350 return @"Insert"; 351 352 // "JapaneseHiragana" 353 // "JapaneseKatakana" 354 // "JapaneseRomaji" 355 // "JunjaMode" 356 // "KanaMode" 357 // "KanjiMode" 358 // "Katakana" 359 // "LaunchApplication1" 360 // "LaunchApplication2" 361 // "LaunchMail" 362 363 // "Left" 364 case NSLeftArrowFunctionKey: 365 return @"Left"; 366 367 // "Meta" 368 // "MediaNextTrack" 369 // "MediaPlayPause" 370 // "MediaPreviousTrack" 371 // "MediaStop" 372 373 // "ModeChange" 374 case NSModeSwitchFunctionKey: 375 return @"ModeChange"; 376 377 // "Nonconvert" 378 // "NumLock" 379 380 // "PageDown" 381 case NSPageDownFunctionKey: 382 return @"PageDown"; 383 // "PageUp" 384 case NSPageUpFunctionKey: 385 return @"PageUp"; 386 387 // "Paste" 388 389 // "Pause" 390 case NSPauseFunctionKey: 391 return @"Pause"; 392 393 // "Play" 394 // "PreviousCandidate" 395 396 // "PrintScreen" 397 case NSPrintScreenFunctionKey: 398 return @"PrintScreen"; 399 400 // "Process" 401 // "Props" 402 403 // "Right" 404 case NSRightArrowFunctionKey: 405 return @"Right"; 406 407 // "RomanCharacters" 408 409 // "Scroll" 410 case NSScrollLockFunctionKey: 411 return @"Scroll"; 412 // "Select" 413 case NSSelectFunctionKey: 414 return @"Select"; 415 416 // "SelectMedia" 417 // "Shift" 418 419 // "Stop" 420 case NSStopFunctionKey: 421 return @"Stop"; 422 // "Up" 423 case NSUpArrowFunctionKey: 424 return @"Up"; 425 // "Undo" 426 case NSUndoFunctionKey: 427 return @"Undo"; 428 429 // "VolumeDown" 430 // "VolumeMute" 431 // "VolumeUp" 432 // "Win" 433 // "Zoom" 434 435 // More function keys, not in the key identifier specification. 436 case NSF25FunctionKey: 437 return @"F25"; 438 case NSF26FunctionKey: 439 return @"F26"; 440 case NSF27FunctionKey: 441 return @"F27"; 442 case NSF28FunctionKey: 443 return @"F28"; 444 case NSF29FunctionKey: 445 return @"F29"; 446 case NSF30FunctionKey: 447 return @"F30"; 448 case NSF31FunctionKey: 449 return @"F31"; 450 case NSF32FunctionKey: 451 return @"F32"; 452 case NSF33FunctionKey: 453 return @"F33"; 454 case NSF34FunctionKey: 455 return @"F34"; 456 case NSF35FunctionKey: 457 return @"F35"; 458 459 // Turn 0x7F into 0x08, because backspace needs to always be 0x08. 460 case 0x7F: 461 return @"U+0008"; 462 // Standard says that DEL becomes U+007F. 463 case NSDeleteFunctionKey: 464 return @"U+007F"; 465 466 // Always use 0x09 for tab instead of AppKit's backtab character. 467 case NSBackTabCharacter: 468 return @"U+0009"; 469 470 case NSBeginFunctionKey: 471 case NSBreakFunctionKey: 472 case NSClearDisplayFunctionKey: 473 case NSDeleteCharFunctionKey: 474 case NSDeleteLineFunctionKey: 475 case NSInsertCharFunctionKey: 476 case NSInsertLineFunctionKey: 477 case NSNextFunctionKey: 478 case NSPrevFunctionKey: 479 case NSPrintFunctionKey: 480 case NSRedoFunctionKey: 481 case NSResetFunctionKey: 482 case NSSysReqFunctionKey: 483 case NSSystemFunctionKey: 484 case NSUserFunctionKey: 485 // FIXME: We should use something other than the vendor-area Unicode values for the above keys. 486 // For now, just fall through to the default. 487 default: 488 return [NSString stringWithFormat:@"U+%04X", WTF::toASCIIUpper(c)]; 489 } 490} 491 492// End Apple code. 493// ---------------------------------------------------------------------------- 494 495static inline int modifiersFromEvent(NSEvent* event) { 496 int modifiers = 0; 497 498 if ([event modifierFlags] & NSControlKeyMask) 499 modifiers |= WebInputEvent::ControlKey; 500 if ([event modifierFlags] & NSShiftKeyMask) 501 modifiers |= WebInputEvent::ShiftKey; 502 if ([event modifierFlags] & NSAlternateKeyMask) 503 modifiers |= WebInputEvent::AltKey; 504 if ([event modifierFlags] & NSCommandKeyMask) 505 modifiers |= WebInputEvent::MetaKey; 506 if ([event modifierFlags] & NSAlphaShiftKeyMask) 507 modifiers |= WebInputEvent::CapsLockOn; 508 // TODO(port): Set mouse button states 509 510 return modifiers; 511} 512 513static inline void setWebEventLocationFromEventInView(WebMouseEvent* result, 514 NSEvent* event, 515 NSView* view) { 516 NSPoint windowLocal = [event locationInWindow]; 517 518 NSPoint screenLocal = [[view window] convertBaseToScreen:windowLocal]; 519 result->globalX = screenLocal.x; 520 // Flip y. 521 NSScreen* primaryScreen = ([[NSScreen screens] count] > 0) ? 522 [[NSScreen screens] objectAtIndex:0] : nil; 523 if (primaryScreen) 524 result->globalY = [primaryScreen frame].size.height - screenLocal.y; 525 else 526 result->globalY = screenLocal.y; 527 528 NSPoint contentLocal = [view convertPoint:windowLocal fromView:nil]; 529 result->x = contentLocal.x; 530 result->y = [view frame].size.height - contentLocal.y; // Flip y. 531 532 result->windowX = result->x; 533 result->windowY = result->y; 534} 535 536WebKeyboardEvent WebInputEventFactory::keyboardEvent(NSEvent* event) 537{ 538 WebKeyboardEvent result; 539 540 result.type = 541 isKeyUpEvent(event) ? WebInputEvent::KeyUp : WebInputEvent::RawKeyDown; 542 543 result.modifiers = modifiersFromEvent(event); 544 545 if (isKeypadEvent(event)) 546 result.modifiers |= WebInputEvent::IsKeyPad; 547 548 if (([event type] != NSFlagsChanged) && [event isARepeat]) 549 result.modifiers |= WebInputEvent::IsAutoRepeat; 550 551 result.windowsKeyCode = windowsKeyCodeForKeyEvent(event); 552 result.nativeKeyCode = [event keyCode]; 553 554 NSString* textStr = textFromEvent(event); 555 NSString* unmodifiedStr = unmodifiedTextFromEvent(event); 556 NSString* identifierStr = keyIdentifierForKeyEvent(event); 557 558 // Begin Apple code, copied from KeyEventMac.mm 559 560 // Always use 13 for Enter/Return -- we don't want to use AppKit's 561 // different character for Enter. 562 if (result.windowsKeyCode == '\r') { 563 textStr = @"\r"; 564 unmodifiedStr = @"\r"; 565 } 566 567 // The adjustments below are only needed in backward compatibility mode, 568 // but we cannot tell what mode we are in from here. 569 570 // Turn 0x7F into 8, because backspace needs to always be 8. 571 if ([textStr isEqualToString:@"\x7F"]) 572 textStr = @"\x8"; 573 if ([unmodifiedStr isEqualToString:@"\x7F"]) 574 unmodifiedStr = @"\x8"; 575 // Always use 9 for tab -- we don't want to use AppKit's different character 576 // for shift-tab. 577 if (result.windowsKeyCode == 9) { 578 textStr = @"\x9"; 579 unmodifiedStr = @"\x9"; 580 } 581 582 // End Apple code. 583 584 if ([textStr length] < WebKeyboardEvent::textLengthCap && 585 [unmodifiedStr length] < WebKeyboardEvent::textLengthCap) { 586 [textStr getCharacters:&result.text[0]]; 587 [unmodifiedStr getCharacters:&result.unmodifiedText[0]]; 588 } else 589 ASSERT_NOT_REACHED(); 590 591 [identifierStr getCString:&result.keyIdentifier[0] 592 maxLength:sizeof(result.keyIdentifier) 593 encoding:NSASCIIStringEncoding]; 594 595 result.timeStampSeconds = [event timestamp]; 596 597 // Windows and Linux set |isSystemKey| if alt is down. WebKit looks at this 598 // flag to decide if it should handle a key or not. E.g. alt-left/right 599 // shouldn't be used by WebKit to scroll the current page, because we want 600 // to get that key back for it to do history navigation. Hence, the 601 // corresponding situation on OS X is to set this for cmd key presses. 602 if (result.modifiers & WebInputEvent::MetaKey) 603 result.isSystemKey = true; 604 605 return result; 606} 607 608WebKeyboardEvent WebInputEventFactory::keyboardEvent(wchar_t character, 609 int modifiers, 610 double timeStampSeconds) 611{ 612 // keyboardEvent(NSEvent*) depends on the NSEvent object and 613 // it is hard to use it from methods of the NSTextInput protocol. For 614 // such methods, this function creates a WebInputEvent::Char event without 615 // using a NSEvent object. 616 WebKeyboardEvent result; 617 result.type = WebKit::WebInputEvent::Char; 618 result.timeStampSeconds = timeStampSeconds; 619 result.modifiers = modifiers; 620 result.windowsKeyCode = character; 621 result.nativeKeyCode = character; 622 result.text[0] = character; 623 result.unmodifiedText[0] = character; 624 625 // Windows and Linux set |isSystemKey| if alt is down. WebKit looks at this 626 // flag to decide if it should handle a key or not. E.g. alt-left/right 627 // shouldn't be used by WebKit to scroll the current page, because we want 628 // to get that key back for it to do history navigation. Hence, the 629 // corresponding situation on OS X is to set this for cmd key presses. 630 if (result.modifiers & WebInputEvent::MetaKey) 631 result.isSystemKey = true; 632 633 return result; 634} 635 636// WebMouseEvent -------------------------------------------------------------- 637 638WebMouseEvent WebInputEventFactory::mouseEvent(NSEvent* event, NSView* view) 639{ 640 WebMouseEvent result; 641 642 result.clickCount = 0; 643 644 switch ([event type]) { 645 case NSMouseExited: 646 result.type = WebInputEvent::MouseLeave; 647 result.button = WebMouseEvent::ButtonNone; 648 break; 649 case NSLeftMouseDown: 650 result.type = WebInputEvent::MouseDown; 651 result.clickCount = [event clickCount]; 652 result.button = WebMouseEvent::ButtonLeft; 653 break; 654 case NSOtherMouseDown: 655 result.type = WebInputEvent::MouseDown; 656 result.clickCount = [event clickCount]; 657 result.button = WebMouseEvent::ButtonMiddle; 658 break; 659 case NSRightMouseDown: 660 result.type = WebInputEvent::MouseDown; 661 result.clickCount = [event clickCount]; 662 result.button = WebMouseEvent::ButtonRight; 663 break; 664 case NSLeftMouseUp: 665 result.type = WebInputEvent::MouseUp; 666 result.clickCount = [event clickCount]; 667 result.button = WebMouseEvent::ButtonLeft; 668 break; 669 case NSOtherMouseUp: 670 result.type = WebInputEvent::MouseUp; 671 result.clickCount = [event clickCount]; 672 result.button = WebMouseEvent::ButtonMiddle; 673 break; 674 case NSRightMouseUp: 675 result.type = WebInputEvent::MouseUp; 676 result.clickCount = [event clickCount]; 677 result.button = WebMouseEvent::ButtonRight; 678 break; 679 case NSMouseMoved: 680 case NSMouseEntered: 681 result.type = WebInputEvent::MouseMove; 682 break; 683 case NSLeftMouseDragged: 684 result.type = WebInputEvent::MouseMove; 685 result.button = WebMouseEvent::ButtonLeft; 686 break; 687 case NSOtherMouseDragged: 688 result.type = WebInputEvent::MouseMove; 689 result.button = WebMouseEvent::ButtonMiddle; 690 break; 691 case NSRightMouseDragged: 692 result.type = WebInputEvent::MouseMove; 693 result.button = WebMouseEvent::ButtonRight; 694 break; 695 default: 696 ASSERT_NOT_REACHED(); 697 } 698 699 setWebEventLocationFromEventInView(&result, event, view); 700 701 result.modifiers = modifiersFromEvent(event); 702 703 result.timeStampSeconds = [event timestamp]; 704 705 return result; 706} 707 708// WebMouseWheelEvent --------------------------------------------------------- 709 710WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(NSEvent* event, NSView* view) 711{ 712 WebMouseWheelEvent result; 713 714 result.type = WebInputEvent::MouseWheel; 715 result.button = WebMouseEvent::ButtonNone; 716 717 result.modifiers = modifiersFromEvent(event); 718 719 setWebEventLocationFromEventInView(&result, event, view); 720 721 // Of Mice and Men 722 // --------------- 723 // 724 // There are three types of scroll data available on a scroll wheel CGEvent. 725 // Apple's documentation ([1]) is rather vague in their differences, and not 726 // terribly helpful in deciding which to use. This is what's really going on. 727 // 728 // First, these events behave very differently depending on whether a standard 729 // wheel mouse is used (one that scrolls in discrete units) or a 730 // trackpad/Mighty Mouse is used (which both provide continuous scrolling). 731 // You must check to see which was used for the event by testing the 732 // kCGScrollWheelEventIsContinuous field. 733 // 734 // Second, these events refer to "axes". Axis 1 is the y-axis, and axis 2 is 735 // the x-axis. 736 // 737 // Third, there is a concept of mouse acceleration. Scrolling the same amount 738 // of physical distance will give you different results logically depending on 739 // whether you scrolled a little at a time or in one continuous motion. Some 740 // fields account for this while others do not. 741 // 742 // Fourth, for trackpads there is a concept of chunkiness. When scrolling 743 // continuously, events can be delivered in chunks. That is to say, lots of 744 // scroll events with delta 0 will be delivered, and every so often an event 745 // with a non-zero delta will be delivered, containing the accumulated deltas 746 // from all the intermediate moves. [2] 747 // 748 // For notchy wheel mice (kCGScrollWheelEventIsContinuous == 0) 749 // ------------------------------------------------------------ 750 // 751 // kCGScrollWheelEventDeltaAxis* 752 // This is the rawest of raw events. For each mouse notch you get a value of 753 // +1/-1. This does not take acceleration into account and thus is less 754 // useful for building UIs. 755 // 756 // kCGScrollWheelEventPointDeltaAxis* 757 // This is smarter. In general, for each mouse notch you get a value of 758 // +1/-1, but this _does_ take acceleration into account, so you will get 759 // larger values on longer scrolls. This field would be ideal for building 760 // UIs except for one nasty bug: when the shift key is pressed, this set of 761 // fields fails to move the value into the axis2 field (the other two types 762 // of data do). This wouldn't be so bad except for the fact that while the 763 // number of axes is used in the creation of a CGScrollWheelEvent, there is 764 // no way to get that information out of the event once created. 765 // 766 // kCGScrollWheelEventFixedPtDeltaAxis* 767 // This is a fixed value, and for each mouse notch you get a value of 768 // +0.1/-0.1 (but, like above, scaled appropriately for acceleration). This 769 // value takes acceleration into account, and in fact is identical to the 770 // results you get from -[NSEvent delta*]. (That is, if you linked on Tiger 771 // or greater; see [2] for details.) 772 // 773 // A note about continuous devices 774 // ------------------------------- 775 // 776 // There are two devices that provide continuous scrolling events (trackpads 777 // and Mighty Mouses) and they behave rather differently. The Mighty Mouse 778 // behaves a lot like a regular mouse. There is no chunking, and the 779 // FixedPtDelta values are the PointDelta values multiplied by 0.1. With the 780 // trackpad, though, there is chunking. While the FixedPtDelta values are 781 // reasonable (they occur about every fifth event but have values five times 782 // larger than usual) the Delta values are unreasonable. They don't appear to 783 // accumulate properly. 784 // 785 // For continuous devices (kCGScrollWheelEventIsContinuous != 0) 786 // ------------------------------------------------------------- 787 // 788 // kCGScrollWheelEventDeltaAxis* 789 // This provides values with no acceleration. With a trackpad, these values 790 // are chunked but each non-zero value does not appear to be cumulative. 791 // This seems to be a bug. 792 // 793 // kCGScrollWheelEventPointDeltaAxis* 794 // This provides values with acceleration. With a trackpad, these values are 795 // not chunked and are highly accurate. 796 // 797 // kCGScrollWheelEventFixedPtDeltaAxis* 798 // This provides values with acceleration. With a trackpad, these values are 799 // chunked but unlike Delta events are properly cumulative. 800 // 801 // Summary 802 // ------- 803 // 804 // In general the best approach to take is: determine if the event is 805 // continuous. If it is not, then use the FixedPtDelta events (or just stick 806 // with Cocoa events). They provide both acceleration and proper horizontal 807 // scrolling. If the event is continuous, then doing pixel scrolling with the 808 // PointDelta is the way to go. In general, avoid the Delta events. They're 809 // the oldest (dating back to 10.4, before CGEvents were public) but they lack 810 // acceleration and precision, making them useful only in specific edge cases. 811 // 812 // References 813 // ---------- 814 // 815 // [1] <http://developer.apple.com/documentation/Carbon/Reference/QuartzEventServicesRef/Reference/reference.html> 816 // [2] <http://developer.apple.com/releasenotes/Cocoa/AppKitOlderNotes.html> 817 // Scroll to the section headed "NSScrollWheel events". 818 // 819 // P.S. The "smooth scrolling" option in the system preferences is utterly 820 // unrelated to any of this. 821 822 CGEventRef cgEvent = [event CGEvent]; 823 ASSERT(cgEvent); 824 825 // Wheel ticks are supposed to be raw, unaccelerated values, one per physical 826 // mouse wheel notch. The delta event is perfect for this (being a good 827 // "specific edge case" as mentioned above). Trackpads, unfortunately, do 828 // event chunking, and sending mousewheel events with 0 ticks causes some 829 // websites to malfunction. Therefore, for all continuous input devices we use 830 // the point delta data instead, since we cannot distinguish trackpad data 831 // from data from any other continuous device. 832 833 // Conversion between wheel delta amounts and number of pixels to scroll. 834 static const double scrollbarPixelsPerCocoaTick = 40.0; 835 836 if (CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventIsContinuous)) { 837 result.deltaX = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis2); 838 result.deltaY = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis1); 839 result.wheelTicksX = result.deltaX / scrollbarPixelsPerCocoaTick; 840 result.wheelTicksY = result.deltaY / scrollbarPixelsPerCocoaTick; 841 } else { 842 result.deltaX = [event deltaX] * scrollbarPixelsPerCocoaTick; 843 result.deltaY = [event deltaY] * scrollbarPixelsPerCocoaTick; 844 result.wheelTicksY = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis1); 845 result.wheelTicksX = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventDeltaAxis2); 846 } 847 848 result.timeStampSeconds = [event timestamp]; 849 850 return result; 851} 852 853} // namespace WebKit 854