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