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