/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #if ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__) #import "WebNetscapePluginEventHandlerCarbon.h" #import "WebNetscapePluginView.h" #import "WebKitLogging.h" #import "WebKitSystemInterface.h" // Send null events 50 times a second when active, so plug-ins like Flash get high frame rates. #define NullEventIntervalActive 0.02 #define NullEventIntervalNotActive 0.25 WebNetscapePluginEventHandlerCarbon::WebNetscapePluginEventHandlerCarbon(WebNetscapePluginView* pluginView) : WebNetscapePluginEventHandler(pluginView) , m_keyEventHandler(0) , m_suspendKeyUpEvents(false) { } static void getCarbonEvent(EventRecord* carbonEvent) { carbonEvent->what = nullEvent; carbonEvent->message = 0; carbonEvent->when = TickCount(); GetGlobalMouse(&carbonEvent->where); carbonEvent->modifiers = GetCurrentKeyModifiers(); if (!Button()) carbonEvent->modifiers |= btnState; } static EventModifiers modifiersForEvent(NSEvent *event) { EventModifiers modifiers; unsigned int modifierFlags = [event modifierFlags]; NSEventType eventType = [event type]; modifiers = 0; if (eventType != NSLeftMouseDown && eventType != NSRightMouseDown) modifiers |= btnState; if (modifierFlags & NSCommandKeyMask) modifiers |= cmdKey; if (modifierFlags & NSShiftKeyMask) modifiers |= shiftKey; if (modifierFlags & NSAlphaShiftKeyMask) modifiers |= alphaLock; if (modifierFlags & NSAlternateKeyMask) modifiers |= optionKey; if (modifierFlags & NSControlKeyMask || eventType == NSRightMouseDown) modifiers |= controlKey; return modifiers; } static void getCarbonEvent(EventRecord *carbonEvent, NSEvent *cocoaEvent) { if (WKConvertNSEventToCarbonEvent(carbonEvent, cocoaEvent)) return; NSPoint where = [[cocoaEvent window] convertBaseToScreen:[cocoaEvent locationInWindow]]; carbonEvent->what = nullEvent; carbonEvent->message = 0; carbonEvent->when = (UInt32)([cocoaEvent timestamp] * 60); // seconds to ticks carbonEvent->where.h = (short)where.x; carbonEvent->where.v = (short)(NSMaxY([[[NSScreen screens] objectAtIndex:0] frame]) - where.y); carbonEvent->modifiers = modifiersForEvent(cocoaEvent); } void WebNetscapePluginEventHandlerCarbon::sendNullEvent() { EventRecord event; getCarbonEvent(&event); // Plug-in should not react to cursor position when not active or when a menu is down. MenuTrackingData trackingData; OSStatus error = GetMenuTrackingData(NULL, &trackingData); // Plug-in should not react to cursor position when the actual window is not key. if (![[m_pluginView window] isKeyWindow] || (error == noErr && trackingData.menu)) { // FIXME: Does passing a v and h of -1 really prevent it from reacting to the cursor position? event.where.v = -1; event.where.h = -1; } sendEvent(&event); } void WebNetscapePluginEventHandlerCarbon::drawRect(CGContextRef, const NSRect&) { EventRecord event; getCarbonEvent(&event); event.what = updateEvt; WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef]; event.message = (unsigned long)windowRef; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(updateEvt): %d", acceptedEvent); } void WebNetscapePluginEventHandlerCarbon::mouseDown(NSEvent* theEvent) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = ::mouseDown; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(mouseDown): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h); } void WebNetscapePluginEventHandlerCarbon::mouseUp(NSEvent* theEvent) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = ::mouseUp; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(mouseUp): %d pt.v=%d, pt.h=%d", acceptedEvent, event.where.v, event.where.h); } bool WebNetscapePluginEventHandlerCarbon::scrollWheel(NSEvent* theEvent) { return false; } void WebNetscapePluginEventHandlerCarbon::mouseEntered(NSEvent* theEvent) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = NPEventType_AdjustCursorEvent; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(mouseEntered): %d", acceptedEvent); } void WebNetscapePluginEventHandlerCarbon::mouseExited(NSEvent* theEvent) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = NPEventType_AdjustCursorEvent; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(mouseExited): %d", acceptedEvent); } void WebNetscapePluginEventHandlerCarbon::mouseDragged(NSEvent*) { } void WebNetscapePluginEventHandlerCarbon::mouseMoved(NSEvent* theEvent) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = NPEventType_AdjustCursorEvent; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(mouseMoved): %d", acceptedEvent); } void WebNetscapePluginEventHandlerCarbon::keyDown(NSEvent *theEvent) { m_suspendKeyUpEvents = true; WKSendKeyEventToTSM(theEvent); } void WebNetscapePluginEventHandlerCarbon::syntheticKeyDownWithCommandModifier(int keyCode, char character) { EventRecord event; getCarbonEvent(&event); event.what = ::keyDown; event.modifiers |= cmdKey; event.message = keyCode << 8 | character; sendEvent(&event); } static UInt32 keyMessageForEvent(NSEvent *event) { NSData *data = [[event characters] dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding())]; if (!data) return 0; UInt8 characterCode; [data getBytes:&characterCode length:1]; UInt16 keyCode = [event keyCode]; return keyCode << 8 | characterCode; } void WebNetscapePluginEventHandlerCarbon::keyUp(NSEvent* theEvent) { WKSendKeyEventToTSM(theEvent); // TSM won't send keyUp events so we have to send them ourselves. // Only send keyUp events after we receive the TSM callback because this is what plug-in expect from OS 9. if (!m_suspendKeyUpEvents) { EventRecord event; getCarbonEvent(&event, theEvent); event.what = ::keyUp; if (event.message == 0) event.message = keyMessageForEvent(theEvent); sendEvent(&event); } } void WebNetscapePluginEventHandlerCarbon::flagsChanged(NSEvent*) { } void WebNetscapePluginEventHandlerCarbon::focusChanged(bool hasFocus) { EventRecord event; getCarbonEvent(&event); bool acceptedEvent; if (hasFocus) { event.what = NPEventType_GetFocusEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(NPEventType_GetFocusEvent): %d", acceptedEvent); installKeyEventHandler(); } else { event.what = NPEventType_LoseFocusEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(NPEventType_LoseFocusEvent): %d", acceptedEvent); removeKeyEventHandler(); } } void WebNetscapePluginEventHandlerCarbon::windowFocusChanged(bool hasFocus) { WindowRef windowRef = (WindowRef)[[m_pluginView window] windowRef]; SetUserFocusWindow(windowRef); EventRecord event; getCarbonEvent(&event); event.what = activateEvt; event.message = (unsigned long)windowRef; if (hasFocus) event.modifiers |= activeFlag; BOOL acceptedEvent; acceptedEvent = sendEvent(&event); LOG(PluginEvents, "NPP_HandleEvent(activateEvent): %d isActive: %d", acceptedEvent, hasFocus); } OSStatus WebNetscapePluginEventHandlerCarbon::TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *eventHandler) { EventRef rawKeyEventRef; OSStatus status = GetEventParameter(inEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(EventRef), NULL, &rawKeyEventRef); if (status != noErr) { LOG_ERROR("GetEventParameter failed with error: %d", status); return noErr; } // Two-pass read to allocate/extract Mac charCodes ByteCount numBytes; status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, 0, &numBytes, NULL); if (status != noErr) { LOG_ERROR("GetEventParameter failed with error: %d", status); return noErr; } char *buffer = (char *)malloc(numBytes); status = GetEventParameter(rawKeyEventRef, kEventParamKeyMacCharCodes, typeChar, NULL, numBytes, NULL, buffer); if (status != noErr) { LOG_ERROR("GetEventParameter failed with error: %d", status); free(buffer); return noErr; } EventRef cloneEvent = CopyEvent(rawKeyEventRef); unsigned i; for (i = 0; i < numBytes; i++) { status = SetEventParameter(cloneEvent, kEventParamKeyMacCharCodes, typeChar, 1 /* one char code */, &buffer[i]); if (status != noErr) { LOG_ERROR("SetEventParameter failed with error: %d", status); free(buffer); return noErr; } EventRecord eventRec; if (ConvertEventRefToEventRecord(cloneEvent, &eventRec)) { BOOL acceptedEvent; acceptedEvent = static_cast<WebNetscapePluginEventHandlerCarbon*>(eventHandler)->sendEvent(&eventRec); LOG(PluginEvents, "NPP_HandleEvent(keyDown): %d charCode:%c keyCode:%lu", acceptedEvent, (char) (eventRec.message & charCodeMask), (eventRec.message & keyCodeMask)); // We originally thought that if the plug-in didn't accept this event, // we should pass it along so that keyboard scrolling, for example, will work. // In practice, this is not a good idea, because plug-ins tend to eat the event but return false. // MacIE handles each key event twice because of this, but we will emulate the other browsers instead. } } ReleaseEvent(cloneEvent); free(buffer); return noErr; } void WebNetscapePluginEventHandlerCarbon::installKeyEventHandler() { static const EventTypeSpec sTSMEvents[] = { { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } }; if (!m_keyEventHandler) { InstallEventHandler(GetWindowEventTarget((WindowRef)[[m_pluginView window] windowRef]), NewEventHandlerUPP(TSMEventHandler), GetEventTypeCount(sTSMEvents), sTSMEvents, this, &m_keyEventHandler); } } void WebNetscapePluginEventHandlerCarbon::removeKeyEventHandler() { if (m_keyEventHandler) { RemoveEventHandler(m_keyEventHandler); m_keyEventHandler = 0; } } void WebNetscapePluginEventHandlerCarbon::nullEventTimerFired(CFRunLoopTimerRef timerRef, void *context) { static_cast<WebNetscapePluginEventHandlerCarbon*>(context)->sendNullEvent(); } void WebNetscapePluginEventHandlerCarbon::startTimers(bool throttleTimers) { ASSERT(!m_nullEventTimer); CFTimeInterval interval = !throttleTimers ? NullEventIntervalActive : NullEventIntervalNotActive; CFRunLoopTimerContext context = { 0, this, NULL, NULL, NULL }; m_nullEventTimer.adoptCF(CFRunLoopTimerCreate(0, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0, nullEventTimerFired, &context)); CFRunLoopAddTimer(CFRunLoopGetCurrent(), m_nullEventTimer.get(), kCFRunLoopDefaultMode); } void WebNetscapePluginEventHandlerCarbon::stopTimers() { if (!m_nullEventTimer) return; CFRunLoopTimerInvalidate(m_nullEventTimer.get()); m_nullEventTimer = 0; } void* WebNetscapePluginEventHandlerCarbon::platformWindow(NSWindow* window) { return [window windowRef]; } bool WebNetscapePluginEventHandlerCarbon::sendEvent(EventRecord* event) { // If at any point the user clicks or presses a key from within a plugin, set the // currentEventIsUserGesture flag to true. This is important to differentiate legitimate // window.open() calls; we still want to allow those. See rdar://problem/4010765 if (event->what == ::mouseDown || event->what == ::keyDown || event->what == ::mouseUp || event->what == ::autoKey) m_currentEventIsUserGesture = true; m_suspendKeyUpEvents = false; bool result = [m_pluginView sendEvent:event isDrawRect:event->what == updateEvt]; m_currentEventIsUserGesture = false; return result; } #endif // ENABLE(NETSCAPE_PLUGIN_API) && !defined(__LP64__)