1/*
2 * Copyright (C) 2008 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#if ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)
27
28#import "WebNetscapePluginEventHandlerCarbon.h"
29
30#import "WebNetscapePluginView.h"
31#import "WebKitLogging.h"
32#import "WebKitSystemInterface.h"
33
34// Send null events 50 times a second when active, so plug-ins like Flash get high frame rates.
35#define NullEventIntervalActive         0.02
36#define NullEventIntervalNotActive      0.25
37
38WebNetscapePluginEventHandlerCarbon::WebNetscapePluginEventHandlerCarbon(WebNetscapePluginView* pluginView)
39    : WebNetscapePluginEventHandler(pluginView)
40    , m_keyEventHandler(0)
41    , m_suspendKeyUpEvents(false)
42{
43}
44
45static void getCarbonEvent(EventRecord* carbonEvent)
46{
47    carbonEvent->what = nullEvent;
48    carbonEvent->message = 0;
49    carbonEvent->when = TickCount();
50
51    GetGlobalMouse(&carbonEvent->where);
52    carbonEvent->modifiers = GetCurrentKeyModifiers();
53    if (!Button())
54        carbonEvent->modifiers |= btnState;
55}
56
57static EventModifiers modifiersForEvent(NSEvent *event)
58{
59    EventModifiers modifiers;
60    unsigned int modifierFlags = [event modifierFlags];
61    NSEventType eventType = [event type];
62
63    modifiers = 0;
64
65    if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown)
66        modifiers |= btnState;
67
68    if (modifierFlags & NSCommandKeyMask)
69        modifiers |= cmdKey;
70
71    if (modifierFlags & NSShiftKeyMask)
72        modifiers |= shiftKey;
73
74    if (modifierFlags & NSAlphaShiftKeyMask)
75        modifiers |= alphaLock;
76
77    if (modifierFlags & NSAlternateKeyMask)
78        modifiers |= optionKey;
79
80    if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown)
81        modifiers |= controlKey;
82
83    return modifiers;
84}
85
86static void getCarbonEvent(EventRecord *carbonEvent, NSEvent *cocoaEvent)
87{
88    if (WKConvertNSEventToCarbonEvent(carbonEvent, cocoaEvent))
89        return;
90
91    NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]];
92
93    carbonEvent->what = nullEvent;
94    carbonEvent->message = 0;
95    carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks
96    carbonEvent->where.h = (short)where.x;
97    carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y);
98    carbonEvent->modifiers = modifiersForEvent(cocoaEvent);
99}
100
101void WebNetscapePluginEventHandlerCarbon::sendNullEvent()
102{
103    EventRecord event;
104
105    getCarbonEvent(&event);
106
107    // Plug-in should not react to cursor position when not active or when a menu is down.
108    MenuTrackingData trackingData;
109    OSStatus error = GetMenuTrackingData(NULL, &trackingData);
110
111    // Plug-in should not react to cursor position when the actual window is not key.
112    if (![[m_pluginView window] isKeyWindow] || (error == noErr && trackingData.menu)) {
113        // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position?
114        event.where.v = -1;
115        event.where.h = -1;
116    }
117
118    sendEvent(&event);
119}
120
121void WebNetscapePluginEventHandlerCarbon::drawRect(CGContextRef, const NSRect&)
122{
123    EventRecord event;
124
125    getCarbonEvent(&event);
126    event.what = updateEvt;
127    WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];
128    event.message = (unsigned long)windowRef;
129
130    BOOL acceptedEvent;
131    acceptedEvent = sendEvent(&event);
132
133    LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent);
134}
135
136void WebNetscapePluginEventHandlerCarbon::mouseDown(NSEvent* theEvent)
137{
138    EventRecord event;
139
140    getCarbonEvent(&event, theEvent);
141    event.what = ::mouseDown;
142
143    BOOL acceptedEvent;
144    acceptedEvent = sendEvent(&event);
145
146    LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
147}
148
149void WebNetscapePluginEventHandlerCarbon::mouseUp(NSEvent* theEvent)
150{
151    EventRecord event;
152
153    getCarbonEvent(&event, theEvent);
154    event.what = ::mouseUp;
155
156    BOOL acceptedEvent;
157    acceptedEvent = sendEvent(&event);
158
159    LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h);
160}
161
162bool WebNetscapePluginEventHandlerCarbon::scrollWheel(NSEvent* theEvent)
163{
164    return false;
165}
166
167void WebNetscapePluginEventHandlerCarbon::mouseEntered(NSEvent* theEvent)
168{
169    EventRecord event;
170
171    getCarbonEvent(&event, theEvent);
172    event.what = NPEventType_AdjustCursorEvent;
173
174    BOOL acceptedEvent;
175    acceptedEvent = sendEvent(&event);
176
177    LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent);
178}
179
180void WebNetscapePluginEventHandlerCarbon::mouseExited(NSEvent* theEvent)
181{
182    EventRecord event;
183
184    getCarbonEvent(&event, theEvent);
185    event.what = NPEventType_AdjustCursorEvent;
186
187    BOOL acceptedEvent;
188    acceptedEvent = sendEvent(&event);
189
190    LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent);
191}
192
193void WebNetscapePluginEventHandlerCarbon::mouseDragged(NSEvent*)
194{
195}
196
197void WebNetscapePluginEventHandlerCarbon::mouseMoved(NSEvent* theEvent)
198{
199    EventRecord event;
200
201    getCarbonEvent(&event, theEvent);
202    event.what = NPEventType_AdjustCursorEvent;
203
204    BOOL acceptedEvent;
205    acceptedEvent = sendEvent(&event);
206
207    LOG(PluginEvents, "NPP_HandleEvent(mouseMoved): %d", acceptedEvent);
208}
209
210void WebNetscapePluginEventHandlerCarbon::keyDown(NSEvent *theEvent)
211{
212    m_suspendKeyUpEvents = true;
213    WKSendKeyEventToTSM(theEvent);
214}
215
216void WebNetscapePluginEventHandlerCarbon::syntheticKeyDownWithCommandModifier(int keyCode, char character)
217{
218    EventRecord event;
219    getCarbonEvent(&event);
220
221    event.what = ::keyDown;
222    event.modifiers |= cmdKey;
223    event.message = keyCode << 8 | character;
224    sendEvent(&event);
225}
226
227static UInt32 keyMessageForEvent(NSEvent *event)
228{
229    NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())];
230    if (!data)
231        return 0;
232
233    UInt8 characterCode;
234    [data getBytes:&characterCode length:1];
235    UInt16 keyCode = [event keyCode];
236    return keyCode << 8 | characterCode;
237}
238
239void WebNetscapePluginEventHandlerCarbon::keyUp(NSEvent* theEvent)
240{
241    WKSendKeyEventToTSM(theEvent);
242
243    // TSM won't send keyUp events so we have to send them ourselves.
244    // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9.
245    if (!m_suspendKeyUpEvents) {
246        EventRecord event;
247
248        getCarbonEvent(&event, theEvent);
249        event.what = ::keyUp;
250
251        if (event.message == 0)
252            event.message = keyMessageForEvent(theEvent);
253
254        sendEvent(&event);
255    }
256}
257
258void WebNetscapePluginEventHandlerCarbon::flagsChanged(NSEvent*)
259{
260}
261
262void WebNetscapePluginEventHandlerCarbon::focusChanged(bool hasFocus)
263{
264    EventRecord event;
265
266    getCarbonEvent(&event);
267    bool acceptedEvent;
268    if (hasFocus) {
269        event.what = NPEventType_GetFocusEvent;
270        acceptedEvent = sendEvent(&event);
271        LOG(PluginEvents, "NPP_HandleEvent(NPEventType_GetFocusEvent): %d", acceptedEvent);
272        installKeyEventHandler();
273    } else {
274        event.what = NPEventType_LoseFocusEvent;
275        acceptedEvent = sendEvent(&event);
276        LOG(PluginEvents, "NPP_HandleEvent(NPEventType_LoseFocusEvent): %d", acceptedEvent);
277        removeKeyEventHandler();
278    }
279}
280
281void WebNetscapePluginEventHandlerCarbon::windowFocusChanged(bool hasFocus)
282{
283    WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef];
284
285    SetUserFocusWindow(windowRef);
286
287    EventRecord event;
288
289    getCarbonEvent(&event);
290    event.what = activateEvt;
291    event.message = (unsigned long)windowRef;
292    if (hasFocus)
293        event.modifiers |= activeFlag;
294
295    BOOL acceptedEvent;
296    acceptedEvent = sendEvent(&event);
297
298    LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d  isActive: %d", acceptedEvent, hasFocus);
299}
300
301OSStatus WebNetscapePluginEventHandlerCarbon::TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *eventHandler)
302{
303    EventRef rawKeyEventRef;
304    OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef);
305    if (status != noErr) {
306        LOG_ERROR("GetEventParameter failed with error: %d", status);
307        return noErr;
308    }
309
310    // Two-pass read to allocate/extract Mac charCodes
311    ByteCount numBytes;
312    status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL);
313    if (status != noErr) {
314        LOG_ERROR("GetEventParameter failed with error: %d", status);
315        return noErr;
316    }
317    char *buffer = (char *)malloc(numBytes);
318    status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer);
319    if (status != noErr) {
320        LOG_ERROR("GetEventParameter failed with error: %d", status);
321        free(buffer);
322        return noErr;
323    }
324
325    EventRef cloneEvent = CopyEvent(rawKeyEventRef);
326    unsigned i;
327    for (i = 0; i < numBytes; i++) {
328        status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]);
329        if (status != noErr) {
330            LOG_ERROR("SetEventParameter failed with error: %d", status);
331            free(buffer);
332            return noErr;
333        }
334
335        EventRecord eventRec;
336        if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) {
337            BOOL acceptedEvent;
338            acceptedEvent = static_cast<WebNetscapePluginEventHandlerCarbon*>(eventHandler)->sendEvent(&eventRec);
339
340            LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu",
341                acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask));
342
343            // We originally thought that if the plug-in didn't accept this event,
344            // we should pass it along so that keyboard scrolling, for example, will work.
345            // In practice, this is not a good idea, because plug-ins tend to eat the event but return false.
346            // MacIE handles each key event twice because of this, but we will emulate the other browsers instead.
347        }
348    }
349    ReleaseEvent(cloneEvent);
350
351    free(buffer);
352
353    return noErr;
354}
355
356void WebNetscapePluginEventHandlerCarbon::installKeyEventHandler()
357{
358    static const EventTypeSpec sTSMEvents[] =
359    {
360        { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent }
361    };
362
363    if (!m_keyEventHandler) {
364        InstallEventHandler(GetWindowEventTarget((WindowRef)[[m_pluginView window] windowRef]),
365                            NewEventHandlerUPP(TSMEventHandler),
366                            GetEventTypeCount(sTSMEvents),
367                            sTSMEvents,
368                            this,
369                            &m_keyEventHandler);
370    }
371}
372
373void WebNetscapePluginEventHandlerCarbon::removeKeyEventHandler()
374{
375    if (m_keyEventHandler) {
376        RemoveEventHandler(m_keyEventHandler);
377        m_keyEventHandler = 0;
378    }
379}
380
381void WebNetscapePluginEventHandlerCarbon::nullEventTimerFired(CFRunLoopTimerRef timerRef, void *context)
382{
383    static_cast<WebNetscapePluginEventHandlerCarbon*>(context)->sendNullEvent();
384}
385
386void WebNetscapePluginEventHandlerCarbon::startTimers(bool throttleTimers)
387{
388    ASSERT(!m_nullEventTimer);
389
390    CFTimeInterval interval = !throttleTimers ? NullEventIntervalActive : NullEventIntervalNotActive;
391
392    CFRunLoopTimerContext context = { 0, this, NULL, NULL, NULL };
393    m_nullEventTimer.adoptCF(CFRunLoopTimerCreate(0, CFAbsoluteTimeGetCurrent() + interval, interval,
394                                                   0, 0, nullEventTimerFired, &context));
395    CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_nullEventTimer.get(), kCFRunLoopDefaultMode);
396}
397
398void WebNetscapePluginEventHandlerCarbon::stopTimers()
399{
400    if (!m_nullEventTimer)
401        return;
402
403    CFRunLoopTimerInvalidate(m_nullEventTimer.get());
404    m_nullEventTimer = 0;
405}
406
407void* WebNetscapePluginEventHandlerCarbon::platformWindow(NSWindow* window)
408{
409    return [window windowRef];
410}
411
412bool WebNetscapePluginEventHandlerCarbon::sendEvent(EventRecord* event)
413{
414    // If at any point the user clicks or presses a key from within a plugin, set the
415    // currentEventIsUserGesture flag to true. This is important to differentiate legitimate
416    // window.open() calls;  we still want to allow those.  See rdar://problem/4010765
417    if (event->what == ::mouseDown || event->what == ::keyDown || event->what == ::mouseUp || event->what == ::autoKey)
418        m_currentEventIsUserGesture = true;
419
420    m_suspendKeyUpEvents = false;
421
422    bool result = [m_pluginView sendEvent:event isDrawRect:event->what == updateEvt];
423
424    m_currentEventIsUserGesture = false;
425
426    return result;
427}
428
429#endif // ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)
430