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