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