/* * Copyright 2009, Google 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "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 THE COPYRIGHT * OWNER 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. */ // This file implements the platform specific parts of the plugin for // the Macintosh platform. #include "plugin/cross/main.h" #import #include #include #include #include #include "base/logging.h" #include "core/cross/event.h" #include "core/mac/display_window_mac.h" #include "plugin/cross/config.h" #include "plugin/cross/plugin_metrics.h" #include "plugin/mac/plugin_mac.h" #include "plugin/mac/graphics_utils_mac.h" #include "plugin/mac/fullscreen_window_mac.h" #import "plugin/mac/o3d_layer.h" using glue::_o3d::PluginObject; using glue::StreamManager; using o3d::Bitmap; using o3d::DisplayWindowMac; using o3d::Event; using o3d::FullscreenWindowMac; using o3d::Renderer; namespace { #define CFTIMER // #define DEFERRED_DRAW_ON_NULLEVENTS // Helper that extracts the O3DLayer obj c object from the PluginObject // and coerces it to the right type. The code can't live in the PluginObject // since it's c++ code and doesn't know about objective c types, and it saves // lots of casts elsewhere in the code. static O3DLayer* ObjO3DLayer(PluginObject* obj) { return static_cast(obj ? obj->gl_layer_ : nil); } void DrawPlugin(PluginObject* obj, bool send_callback, CGContextRef context) { obj->client()->RenderClient(send_callback); Renderer* renderer = obj->renderer(); if (obj->IsOffscreenRenderingEnabled() && renderer && context) { DCHECK_EQ(obj->drawing_model_, NPDrawingModelCoreGraphics); DCHECK(obj->mac_cgl_pbuffer_); // We need to read back the framebuffer and draw it to the screen using // CoreGraphics. renderer->StartRendering(); Bitmap::Ref bitmap = obj->GetOffscreenBitmap(); obj->GetOffscreenRenderSurface()->GetIntoBitmap(bitmap); bitmap->FlipVertically(); renderer->FinishRendering(); uint8* data = bitmap->GetMipData(0); unsigned width = bitmap->width(); unsigned height = bitmap->height(); int rowBytes = width * 4; CGContextSaveGState(context); CGDataProviderRef dataProvider = CGDataProviderCreateWithData(0, data, rowBytes * height, 0); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); // We need to use kCGImageAlphaNoneSkipFirst to discard the alpha channel. // O3D's output is currently semantically opaque. CGImageRef cgImage = CGImageCreate(width, height, 8, 32, rowBytes, colorSpace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host, dataProvider, 0, false, kCGRenderingIntentDefault); CGRect rect = CGRectMake(0, 0, width, height); // We want to completely overwrite the previous frame's // rendering results. CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextSetInterpolationQuality(context, kCGInterpolationNone); CGContextDrawImage(context, rect, cgImage); CGImageRelease(cgImage); CGColorSpaceRelease(colorSpace); CGDataProviderRelease(dataProvider); CGContextRestoreGState(context); } } unsigned char GetMacEventKeyChar(const EventRecord *the_event) { unsigned char the_char = the_event->message & charCodeMask; return the_char; } unsigned char GetMacEventKeyCode(const EventRecord *the_event) { unsigned char the_key_code = (the_event->message & keyCodeMask) >> 8; return the_key_code; } // Cocoa key events for things like arrow key presses can have strange unicode // values in the 0xF700-0xF747 range, eg NSUpArrowFunctionKey is 0xF700. // These values are defined in NSEvent.h. // Map the ones we care about to the more commonly understood values in // the Mac system header Events.h, eg kUpArrowCharCode is 30. int TranslateMacUnicodeControlChar(int theChar) { switch(theChar) { case NSUpArrowFunctionKey: return kUpArrowCharCode; case NSDownArrowFunctionKey: return kDownArrowCharCode; case NSLeftArrowFunctionKey: return kLeftArrowCharCode; case NSRightArrowFunctionKey: return kRightArrowCharCode; } return theChar; } // The Mac standard char codes for arrow keys are different from the // web standard. // Also in the browser world the enter key gets mapped to be the same as the // return key. int TranslateMacControlCharToWebChar(int theChar) { switch(theChar) { case kUpArrowCharCode: return 38; case kDownArrowCharCode: return 40; case kLeftArrowCharCode: return 37; case kRightArrowCharCode: return 39; case kEnterCharCode: return kReturnCharCode; } return theChar; } // Given an instance, and some event data, calls Javascript methods // placed on the object tag so that the keystrokes can be handled in // Javascript. void DispatchKeyboardEvent(PluginObject* obj, EventKind kind, int theChar, int theKeyCode, EventModifiers mods) { theChar = TranslateMacUnicodeControlChar(theChar); theChar = TranslateMacControlCharToWebChar(theChar); int upperChar = (theChar >= 'a' && theChar <='z') ? theChar - 32 : theChar; Event::Type type = Event::TYPE_KEYDOWN; // Init to something valid. switch (kind) { case keyDown: // We'll also have to send a keypress below. type = Event::TYPE_KEYDOWN; break; case autoKey: type = Event::TYPE_KEYPRESS; break; case keyUp: type = Event::TYPE_KEYUP; break; default: return; break; } Event event(type); switch (kind) { case keyDown: case keyUp: event.set_key_code(upperChar); break; case autoKey: event.set_char_code(theChar); break; default: LOG(FATAL) << "Unknown keyboard event: " << kind; } int modifier_state = 0; if (mods & controlKey) { modifier_state |= Event::MODIFIER_CTRL; } if (mods & shiftKey) { modifier_state |= Event::MODIFIER_SHIFT; } if (mods & optionKey) { modifier_state |= Event::MODIFIER_ALT; } if (mods & cmdKey) { modifier_state |= Event::MODIFIER_META; } event.set_modifier_state(modifier_state); obj->client()->AddEventToQueue(event); if (type == Event::TYPE_KEYDOWN) { event.clear_key_code(); event.set_char_code(theChar); event.set_type(Event::TYPE_KEYPRESS); obj->client()->AddEventToQueue(event); } } // Given an instance, and a MacOS keyboard event, calls Javascript methods // placed on the object tag so that the keystrokes can be handled in // Javascript. void DispatchMacKeyboardEvent(PluginObject* obj, EventRecord* the_event) { DispatchKeyboardEvent(obj, the_event->what, GetMacEventKeyChar(the_event), GetMacEventKeyCode(the_event), the_event->modifiers); } void HandleMouseEvent(PluginObject* obj, EventRecord* the_event) { DCHECK(obj); DCHECK(obj->client()); int screen_x = the_event->where.h; int screen_y = the_event->where.v; static Point last_mouse_loc = {0,0}; o3d::Event::Type type; switch (the_event->what) { case mouseDown: type = o3d::Event::TYPE_MOUSEDOWN; break; case mouseUp: type = o3d::Event::TYPE_MOUSEUP; break; case nullEvent: case osEvt: // can be various things but in this context it's mouse move if (last_mouse_loc.h == screen_x && last_mouse_loc.v == screen_y) return; type = o3d::Event::TYPE_MOUSEMOVE; break; default: LOG(FATAL) << "Unknown mouse event: " << the_event->what; return; } last_mouse_loc = the_event->where; bool in_plugin = false; // Did the event happen within our drawing region? int x, y; // now make x and y plugin relative coords if (obj->GetFullscreenMacWindow()) { Rect wBounds = o3d::CGRect2Rect( obj->GetFullscreenMacWindow()->GetWindowBounds()); x = screen_x - wBounds.left; y = screen_y - wBounds.top; in_plugin = true; } else { Rect wBounds; GetWindowBounds(obj->mac_window_, kWindowGlobalPortRgn, &wBounds); x = screen_x - wBounds.left - obj->last_plugin_loc_.h; y = screen_y - wBounds.top - obj->last_plugin_loc_.v; in_plugin = x >=0 && y >= 0 && x < obj->width() && y < obj->height(); } o3d::Event event(type); int modifier_state = 0; if (the_event->modifiers & controlKey) { modifier_state |= o3d::Event::MODIFIER_CTRL; } if (the_event->modifiers & shiftKey) { modifier_state |= o3d::Event::MODIFIER_SHIFT; } if (the_event->modifiers & optionKey) { modifier_state |= o3d::Event::MODIFIER_ALT; } if (the_event->modifiers & cmdKey) { modifier_state |= o3d::Event::MODIFIER_META; } event.set_modifier_state(modifier_state); // TODO: Figure out how to detect and set buttons properly. if (the_event->modifiers & btnState) { event.set_button(o3d::Event::BUTTON_LEFT); } event.set_position(x, y, screen_x, screen_y, in_plugin); obj->client()->AddEventToQueue(event); if (in_plugin && type == Event::TYPE_MOUSEDOWN && obj->HitFullscreenClickRegion(x, y)) { obj->RequestFullscreenDisplay(); } } // Handle a mouse-related NPCocoaEvent. // These events come from the new Cocoa revision of the NPAPI spec, // currently implemented only in Safari. // See https://wiki.mozilla.org/Mac:NPAPI_Event_Models void HandleCocoaMouseEvent(PluginObject* obj, NPCocoaEvent* the_event) { DCHECK(obj); DCHECK(obj->client()); int screen_x = the_event->data.mouse.pluginX; int screen_y = the_event->data.mouse.pluginY; o3d::Event::Type type; switch (the_event->type) { case NPCocoaEventMouseDown: type = o3d::Event::TYPE_MOUSEDOWN; break; case NPCocoaEventMouseUp: type = o3d::Event::TYPE_MOUSEUP; break; // The Mac makes a distinction between moved and dragged (ie moved with // the button down), but the O3D event model does not. case NPCocoaEventMouseMoved: case NPCocoaEventMouseDragged: type = o3d::Event::TYPE_MOUSEMOVE; break; case NPCocoaEventScrollWheel: type = o3d::Event::TYPE_WHEEL; break; // Don't care about these currently. case NPCocoaEventMouseEntered: case NPCocoaEventMouseExited: default: return; } int x = the_event->data.mouse.pluginX; int y = the_event->data.mouse.pluginY; // Did the event happen within our drawing region? bool in_plugin = x >= 0 && y >= 0 && x < obj->width() && y < obj->height(); o3d::Event event(type); int modifier_state = 0; if (the_event->data.mouse.modifierFlags & NSControlKeyMask) { modifier_state |= o3d::Event::MODIFIER_CTRL; } if (the_event->data.mouse.modifierFlags & (NSAlphaShiftKeyMask | NSShiftKeyMask)) { modifier_state |= o3d::Event::MODIFIER_SHIFT; } if (the_event->data.mouse.modifierFlags & NSAlternateKeyMask) { modifier_state |= o3d::Event::MODIFIER_ALT; } if (the_event->data.mouse.modifierFlags & NSCommandKeyMask) { modifier_state |= o3d::Event::MODIFIER_META; } event.set_modifier_state(modifier_state); if (the_event->type == NPCocoaEventScrollWheel) { if (the_event->data.mouse.deltaX && the_event->data.mouse.deltaY) { if (abs(the_event->data.mouse.deltaX) > abs(the_event->data.mouse.deltaY)) { the_event->data.mouse.deltaY = 0; } else { the_event->data.mouse.deltaX = 0; } } event.set_delta(the_event->data.mouse.deltaX * 10.0, the_event->data.mouse.deltaY * 10.0); } if ((the_event->type == NPCocoaEventMouseDown) || (the_event->type == NPCocoaEventMouseUp)) { event.set_button( o3d::MacOSMouseButtonNumberToO3DButton( the_event->data.mouse.buttonNumber)); } event.set_position(x, y, screen_x, screen_y, in_plugin); obj->client()->AddEventToQueue(event); if (in_plugin && type == Event::TYPE_MOUSEDOWN && obj->HitFullscreenClickRegion(x, y)) { obj->RequestFullscreenDisplay(); } } // Convert an NSEvent style modifiers field to an EventRecord style one. // Not all bits have a representation in the target type, eg NSFunctionKeyMask // but we just need to convert the basic modifiers that need to be passed on // to Javascript. EventModifiers CocoaToEventRecordModifiers(NSUInteger inMods) { EventModifiers outMods = 0; // NSEvent distinuishes between the shift key being down and the capslock key, // but the W3C event spec does not make this distinction. if (inMods & (NSAlphaShiftKeyMask | NSShiftKeyMask)) outMods |= shiftKey; if (inMods & NSControlKeyMask) outMods |= controlKey; if (inMods & NSAlternateKeyMask) outMods |= optionKey; if (inMods & NSCommandKeyMask) outMods |= cmdKey; return outMods; } // List of message types from mozilla's nsplugindefs.h, which is more // complete than the list in NPAPI.h. enum nsPluginEventType { nsPluginEventType_GetFocusEvent = (osEvt + 16), nsPluginEventType_LoseFocusEvent, nsPluginEventType_AdjustCursorEvent, nsPluginEventType_MenuCommandEvent, nsPluginEventType_ClippingChangedEvent, nsPluginEventType_ScrollingBeginsEvent, nsPluginEventType_ScrollingEndsEvent, nsPluginEventType_Idle = 0 }; // When to prefer Core Animation. Safari's support in 10.5 was too // buggy to attempt to use. static bool PreferCoreAnimation() { bool isSafari = o3d::metric_browser_type.value() == o3d::BROWSER_NAME_SAFARI; return (!isSafari || o3d::IsMacOSTenSixOrHigher()); } // Negotiates the best plugin drawing and event model, sets the // browser to use that, and updates the PluginObject so we can // remember which one we chose. We prefer these combinations in the // given order: // - Core Animation drawing model, Cocoa event model // - QuickDraw drawing model, Carbon event model // - Core Graphics drawing model, Cocoa event model // If the browser doesn't even understand the question, we use the // QuickDraw drawing model and Carbon event model for best backward // compatibility. // // This ordering provides the best forward-looking behavior while // still providing compatibility with older browsers. // // Returns NPERR_NO_ERROR (0) if successful, otherwise an NPError code. NPError Mac_SetBestEventAndDrawingModel(NPP instance, PluginObject* obj) { NPError err = NPERR_NO_ERROR; NPBool supportsCocoaEventModel = FALSE; NPBool supportsCarbonEventModel = FALSE; NPBool supportsCoreGraphics = FALSE; NPBool supportsQuickDraw = FALSE; NPBool supportsCoreAnimation = FALSE; // See if browser supports Cocoa event model. err = NPN_GetValue(instance, NPNVsupportsCocoaBool, &supportsCocoaEventModel); if (err != NPERR_NO_ERROR) { supportsCocoaEventModel = FALSE; err = NPERR_NO_ERROR; // browser doesn't support switchable event models } // See if browser supports Carbon event model. err = NPN_GetValue(instance, NPNVsupportsCarbonBool, &supportsCarbonEventModel); if (err != NPERR_NO_ERROR) { supportsCarbonEventModel = FALSE; err = NPERR_NO_ERROR; } // Test for QuickDraw support. err = NPN_GetValue(instance, NPNVsupportsQuickDrawBool, &supportsQuickDraw); if (err != NPERR_NO_ERROR) supportsQuickDraw = FALSE; // Test for Core Graphics support. err = NPN_GetValue(instance, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics); if (err != NPERR_NO_ERROR) supportsCoreGraphics = FALSE; // Test for Core Animation support. err = NPN_GetValue(instance, NPNVsupportsCoreAnimationBool, &supportsCoreAnimation); if (err != NPERR_NO_ERROR) supportsCoreAnimation = FALSE; // Fix up values for older browsers which don't even understand // these questions. if (!supportsCarbonEventModel && !supportsCocoaEventModel) { supportsCarbonEventModel = TRUE; } if (!supportsQuickDraw && !supportsCoreGraphics && !supportsCoreAnimation) { // Must be a very old browser such as FF2. Avoid calling // NPN_SetValue for the event or drawing models at all. obj->drawing_model_ = NPDrawingModelQuickDraw; obj->event_model_ = NPEventModelCarbon; return NPERR_NO_ERROR; } // Now that we have our information, decide on the appropriate combination. NPDrawingModel drawing_model = NPDrawingModelQuickDraw; NPEventModel event_model = NPEventModelCarbon; if (supportsCoreAnimation && supportsCocoaEventModel && PreferCoreAnimation()) { drawing_model = NPDrawingModelCoreAnimation; event_model = NPEventModelCocoa; DLOG(INFO) << "Core Animation drawing model"; } else if (supportsQuickDraw && supportsCarbonEventModel) { drawing_model = NPDrawingModelQuickDraw; event_model = NPEventModelCarbon; DLOG(INFO) << "QuickDraw drawing model"; } else if (supportsCoreGraphics && supportsCocoaEventModel) { drawing_model = NPDrawingModelCoreGraphics; event_model = NPEventModelCocoa; DLOG(INFO) << "Core Graphics drawing model"; } else { // If all of the above tests failed, we are running on a browser // which we don't know how to handle. return NPERR_GENERIC_ERROR; } // Earlier versions of this code did not check the return value of // the call to set the event model. NPN_SetValue(instance, NPPVpluginEventModel, reinterpret_cast(event_model)); err = NPN_SetValue(instance, NPPVpluginDrawingModel, reinterpret_cast(drawing_model)); if (err != NPERR_NO_ERROR) { return err; } obj->drawing_model_ = drawing_model; obj->event_model_ = event_model; return NPERR_NO_ERROR; } } // end anonymous namespace #if defined(O3D_INTERNAL_PLUGIN) namespace o3d { #else extern "C" { #endif #if !defined(O3D_INTERNAL_PLUGIN) // Wrapper that discards the return value to match the expected type of // NPP_ShutdownProcPtr. static void NPP_ShutdownWrapper() { NP_Shutdown(); } // This code is needed to support browsers based on a slightly dated version // of the NPAPI such as Firefox 2, and Camino 1.6. These browsers expect there // to be a main() to call to do basic setup. int main(NPNetscapeFuncs* browserFuncs, NPPluginFuncs* pluginFuncs, NPP_ShutdownProcPtr* shutdownProc) { HANDLE_CRASHES; NPError error = NP_Initialize(browserFuncs); if (error == NPERR_NO_ERROR) error = NP_GetEntryPoints(pluginFuncs); *shutdownProc = NPP_ShutdownWrapper; return error; } #endif // O3D_INTERNAL_PLUGIN } // namespace o3d / extern "C" namespace o3d { NPError PlatformPreNPInitialize() { #if !defined(O3D_INTERNAL_PLUGIN) o3d::InitializeBreakpad(); #ifdef CFTIMER o3d::gRenderTimer.Start(); #endif // CFTIMER #endif // O3D_INTERNAL_PLUGIN return NPERR_NO_ERROR; } NPError PlatformPostNPInitialize() { return NPERR_NO_ERROR; } NPError PlatformPreNPShutdown() { return NPERR_NO_ERROR; } NPError PlatformPostNPShutdown() { #if !defined(O3D_INTERNAL_PLUGIN) #ifdef CFTIMER o3d::gRenderTimer.Stop(); #endif o3d::ShutdownBreakpad(); #endif // O3D_INTERNAL_PLUGIN return NPERR_NO_ERROR; } NPError PlatformNPPGetValue(PluginObject *obj, NPPVariable variable, void *value) { switch (variable) { case NPPVpluginCoreAnimationLayer: if (!ObjO3DLayer(obj)) { // Setup layer O3DLayer* gl_layer = [[[O3DLayer alloc] init] retain]; gl_layer.autoresizingMask = kCALayerWidthSizable + kCALayerHeightSizable; obj->gl_layer_ = gl_layer; [gl_layer setPluginObject:obj]; } // Make sure to return a retained layer *(CALayer**)value = ObjO3DLayer(obj); return NPERR_NO_ERROR; break; default: return NPERR_INVALID_PARAM; } return NPERR_INVALID_PARAM; } bool HandleMacEvent(EventRecord* the_event, NPP instance) { PluginObject* obj = static_cast(instance->pdata); bool handled = false; FullscreenWindowMac* fullscreen_window = obj->GetFullscreenMacWindow(); if (g_logger) g_logger->UpdateLogging(); // Help the plugin keep track of when we last saw an event so the CFTimer can // notice if we get cut off, eg by our tab being hidden by Safari, which there // is no other way for us to detect. obj->MacEventReceived(the_event->what != nsPluginEventType_LoseFocusEvent); switch (the_event->what) { case nullEvent: #ifdef DEFERRED_DRAW_ON_NULLEVENTS GLUE_PROFILE_START(instance, "forceredraw"); NPN_ForceRedraw(instance); // invalidate to cause a redraw GLUE_PROFILE_STOP(instance, "forceredraw"); #elif defined(CFTIMER) #else DrawPlugin(obj, true, (obj->drawing_model_ == NPDrawingModelCoreGraphics) ? reinterpret_cast(obj->mac_2d_context_) : NULL); #endif // Safari tab switching recovery code. if (obj->mac_surface_hidden_) { obj->mac_surface_hidden_ = false; NPN_ForceRedraw(instance); // invalidate to cause a redraw } // Auto-recovery for any situation where another window comes in front // of the fullscreen window and we need to exit fullscreen mode. // This can happen because another browser window has come forward, or // because another app has been called to the front. if (fullscreen_window && !fullscreen_window->IsActive()) { obj->CancelFullscreenDisplay(); } // Send nullEvents to HandleMouseEvent so they can be used to simulate // mouse moved events. Not needed in fullscreen mode, where we really do // get mouse moved events. See the osEvt case below. if (!fullscreen_window) HandleMouseEvent(obj, the_event); handled = true; break; case updateEvt: DrawPlugin(obj, false, (obj->drawing_model_ == NPDrawingModelCoreGraphics) ? reinterpret_cast(obj->mac_2d_context_) : NULL); handled = true; break; case osEvt: // These are mouse moved messages when so tagged in the high byte. if ((the_event->message >> 24) == mouseMovedMessage) { HandleMouseEvent(obj, the_event); handled = true; } break; case mouseDown: HandleMouseEvent(obj, the_event); break; case mouseUp: HandleMouseEvent(obj, the_event); handled = true; break; case keyDown: // Hard-coded trapdoor to get out of fullscreen mode if user hits escape. if ((GetMacEventKeyChar(the_event) == '\e') && fullscreen_window) { obj->CancelFullscreenDisplay(); break; } // otherwise fall through case autoKey: case keyUp: { DispatchMacKeyboardEvent(obj, the_event); handled = true; break; } case nsPluginEventType_ScrollingBeginsEvent: obj->SetScrollIsInProgress(true); break; case nsPluginEventType_ScrollingEndsEvent: obj->SetScrollIsInProgress(false); break; default: break; } return handled; } // Handle an NPCocoaEvent style event. The Cocoa event interface is // a recent addition to the NAPI spec. // See https://wiki.mozilla.org/Mac:NPAPI_Event_Models for further details. // The principle advantages are that we can get scrollwheel messages, // mouse-moved messages, and can tell which mouse button was pressed. // This API will also be required for a carbon-free 64 bit version for 10.6. bool HandleCocoaEvent(NPP instance, NPCocoaEvent* the_event, bool initiated_from_browser) { PluginObject* obj = static_cast(instance->pdata); FullscreenWindowMac* fullscreen_window = obj->GetFullscreenMacWindow(); bool handled = false; if (g_logger) g_logger->UpdateLogging(); bool lostFocus = the_event->type == NPCocoaEventFocusChanged && !the_event->data.focus.hasFocus; obj->MacEventReceived(!lostFocus); switch (the_event->type) { case NPCocoaEventDrawRect: // We need to call the render callback from here if we are rendering // off-screen because it doesn't get called anywhere else. if (obj->drawing_model_ == NPDrawingModelCoreAnimation) { O3DLayer* layer = ObjO3DLayer(obj); if (layer) { [layer setNeedsDisplay]; } } else { DrawPlugin(obj, obj->IsOffscreenRenderingEnabled(), the_event->data.draw.context); } handled = true; break; case NPCocoaEventMouseDown: case NPCocoaEventMouseUp: case NPCocoaEventMouseMoved: case NPCocoaEventMouseDragged: case NPCocoaEventMouseEntered: case NPCocoaEventMouseExited: case NPCocoaEventScrollWheel: if (the_event->type == NPCocoaEventMouseUp && initiated_from_browser && fullscreen_window) { // The mouse-up event associated with the mouse-down that caused // the app to go full-screen is dispatched against the browser // window rather than the full-screen window. Apps that have an // icon which toggles full-screen and windowed mode therefore come // out of full-screen immediately when the icon is clicked, since // the mouse-up occurs at the same location as the mouse-down. Work // around this by squelching this mouse up event. This seems to // work acceptably for known apps. We could do better by // redispatching all mouse events through the full-screen window // until the first mouse up. break; } HandleCocoaMouseEvent(obj, the_event); break; case NPCocoaEventKeyDown: // Hard-coded trapdoor to get out of fullscreen mode if user hits escape. if (fullscreen_window) { NSString *chars = (NSString*) the_event->data.key.charactersIgnoringModifiers; if (chars == NULL || [chars length] == 0) { break; } if ([chars characterAtIndex:0] == '\e') { obj->CancelFullscreenDisplay(); break; } } // otherwise fall through case NPCocoaEventKeyUp: { EventKind eventKind = (the_event->type == NPCocoaEventKeyUp) ? keyUp : (the_event->data.key.isARepeat) ? autoKey : keyDown; EventModifiers modifiers = CocoaToEventRecordModifiers(the_event->data.key.modifierFlags); NSString *chars = (NSString*) the_event->data.key.charactersIgnoringModifiers; if (chars == NULL || [chars length] == 0) { break; } DispatchKeyboardEvent(obj, eventKind, [chars characterAtIndex:0], the_event->data.key.keyCode, modifiers); break; } case NPCocoaEventFlagsChanged: case NPCocoaEventFocusChanged: case NPCocoaEventWindowFocusChanged: // Safari tab switching recovery code. if (obj->mac_surface_hidden_) { obj->mac_surface_hidden_ = false; NPN_ForceRedraw(instance); // invalidate to cause a redraw } // Auto-recovery for any situation where another window comes in front // of the fullscreen window and we need to exit fullscreen mode. // This can happen because another browser window has come forward, or // because another app has been called to the front. // TODO: We'll have problems with this when dealing with e.g. // Japanese text input IME windows. // Note that we ignore focus transfer events coming from the // browser while in full-screen mode, because otherwise we // frequently disable full-screen mode almost immediately after // entering it. if (fullscreen_window && !fullscreen_window->IsActive() && !initiated_from_browser) { obj->CancelFullscreenDisplay(); } break; case NPCocoaEventTextInput: break; } return handled; } NPError PlatformNPPNew(NPP instance, PluginObject *obj) { NPError err = Mac_SetBestEventAndDrawingModel(instance, obj); if (err != NPERR_NO_ERROR) return err; #ifdef CFTIMER if (obj->drawing_model_ == NPDrawingModelCoreAnimation) { o3d::gRenderTimer.AddInstance(instance); } #endif return NPERR_NO_ERROR; } NPError PlatformNPPDestroy(NPP instance, PluginObject *obj) { #if defined(CFTIMER) o3d::gRenderTimer.RemoveInstance(instance); #endif // TODO(maf) / TODO(kbr): are we leaking AGL / CGL contexts? if (obj->drawing_model_ == NPDrawingModelCoreAnimation) { O3DLayer* layer = ObjO3DLayer(obj); if (layer) { // Prevent the layer from rendering any more. [layer setPluginObject:NULL]; } } obj->TearDown(); return NPERR_NO_ERROR; } bool CheckForAGLError() { return aglGetError() != AGL_NO_ERROR; } NPError PlatformNPPSetWindow(NPP instance, PluginObject *obj, NPWindow* window) { WindowRef new_window = NULL; assert(window != NULL); if (window->window == NULL && obj->drawing_model_ != NPDrawingModelCoreGraphics && obj->drawing_model_!= NPDrawingModelCoreAnimation) { return NPERR_NO_ERROR; } obj->last_plugin_loc_.h = window->x; obj->last_plugin_loc_.v = window->y; switch (obj->drawing_model_) { case NPDrawingModelCoreAnimation: { O3DLayer* o3dLayer = ObjO3DLayer(obj); [o3dLayer setWidth: window->width height: window->height]; return NPERR_NO_ERROR; } case NPDrawingModelCoreGraphics: { // Safari 4 sets window->window to NULL when in Cocoa event mode. if (window->window != NULL) { NP_CGContext* np_cg = reinterpret_cast(window->window); if (obj->event_model_ == NPEventModelCocoa) { NSWindow * ns_window = reinterpret_cast(np_cg->window); new_window = reinterpret_cast([ns_window windowRef]); } else { new_window = static_cast(np_cg->window); } obj->mac_2d_context_ = np_cg->context; } break; } case NPDrawingModelQuickDraw: { NP_Port* np_qd = reinterpret_cast(window->window); obj->mac_2d_context_ = np_qd->port; if (np_qd->port) new_window = GetWindowFromPort(np_qd->port); // Safari 4 on Snow Leopard is sending us a spurious // NPP_SetWindow where we can not determine the WindowRef from // the port. Ignore this. if (obj->mac_window_ != NULL && new_window == NULL) return NPERR_NO_ERROR; break; } default: return NPERR_INCOMPATIBLE_VERSION_ERROR; break; } // Whether the target window has changed. bool window_changed = new_window != obj->mac_window_; // Whether we already had a window before this call. bool had_a_window = obj->mac_window_ != NULL; // Whether we already had a pbuffer before this call. bool had_a_pbuffer = obj->mac_cgl_pbuffer_ != NULL; obj->mac_window_ = new_window; if (obj->drawing_model_ == NPDrawingModelCoreAnimation) { if (obj->mac_cgl_context_) { CGLSetCurrentContext(obj->mac_cgl_context_); } } else if (obj->drawing_model_ == NPDrawingModelCoreGraphics) { if (obj->mac_cgl_pbuffer_ == NULL) { // We initialize things with a CGL context rendering to a 1x1 // pbuffer. Later we use the O3D RenderSurface APIs to set up the // framebuffer object which is used for rendering. CGLContextObj share_context = obj->GetFullscreenShareContext(); CGLPixelFormatObj pixel_format = obj->GetFullscreenCGLPixelFormatObj(); DCHECK(share_context); CGLError result; CGLContextObj context; result = CGLCreateContext(pixel_format, share_context, &context); if (result != kCGLNoError) { DLOG(ERROR) << "Error " << result << " creating context."; return NPERR_GENERIC_ERROR; } CGLPBufferObj pbuffer; if ((result = CGLCreatePBuffer(1, 1, GL_TEXTURE_2D, GL_RGBA, 0, &pbuffer)) != kCGLNoError) { CGLDestroyContext(context); DLOG(ERROR) << "Error " << result << " creating pbuffer."; return NPERR_GENERIC_ERROR; } if ((result = CGLSetPBuffer(context, pbuffer, 0, 0, 0)) != kCGLNoError) { CGLDestroyContext(context); CGLDestroyPBuffer(pbuffer); DLOG(ERROR) << "Error " << result << " attaching pbuffer to context."; return NPERR_GENERIC_ERROR; } // Must make the context current for renderer creation to succeed CGLSetCurrentContext(context); obj->mac_cgl_context_ = context; obj->mac_cgl_pbuffer_ = pbuffer; } } else if (!had_a_window && obj->mac_agl_context_ == NULL) { // setup AGL context AGLPixelFormat myAGLPixelFormat = NULL; // We need to spec out a few similar but different sets of renderer // specifications - define some macros to lessen the duplication. #define O3D_DEPTH_SETTINGS AGL_RGBA, AGL_DEPTH_SIZE, 24, #define O3D_STENCIL_SETTINGS AGL_STENCIL_SIZE, 8, #define O3D_HARDWARE_RENDERER AGL_ACCELERATED, AGL_NO_RECOVERY, #define O3D_SOFTWARE_RENDERER AGL_RENDERER_ID, AGL_RENDERER_GENERIC_FLOAT_ID, #define O3D_MULTISAMPLE AGL_MULTISAMPLE, \ AGL_SAMPLE_BUFFERS_ARB, 1, AGL_SAMPLES_ARB, 4, AGL_MULTISAMPLE, #define O3D_END AGL_NONE #ifdef USE_AGL_DOUBLE_BUFFER #define O3D_DOUBLE_BUFFER_SETTINGS AGL_DOUBLEBUFFER, #else #define O3D_DOUBLE_BUFFER_SETTINGS #endif if (!UseSoftwareRenderer()) { // Try to create a hardware context with the following // specification GLint attributes[] = { O3D_DEPTH_SETTINGS O3D_STENCIL_SETTINGS O3D_DOUBLE_BUFFER_SETTINGS O3D_HARDWARE_RENDERER O3D_MULTISAMPLE O3D_END }; myAGLPixelFormat = aglChoosePixelFormat(NULL, 0, attributes); // If this fails, then don't try to be as ambitious // (so don't ask for multi-sampling), but still require hardware if (myAGLPixelFormat == NULL) { GLint low_end_attributes[] = { O3D_DEPTH_SETTINGS O3D_STENCIL_SETTINGS O3D_DOUBLE_BUFFER_SETTINGS O3D_HARDWARE_RENDERER O3D_END }; myAGLPixelFormat = aglChoosePixelFormat(NULL, 0, low_end_attributes); } } if (myAGLPixelFormat == NULL) { // Fallback to software renderer either if the vendorID/gpuID // is known to be problematic, or if the hardware context failed // to get created // // Note that we don't request multisampling since it's too slow. GLint software_renderer_attributes[] = { O3D_DEPTH_SETTINGS O3D_STENCIL_SETTINGS O3D_DOUBLE_BUFFER_SETTINGS O3D_SOFTWARE_RENDERER O3D_END }; myAGLPixelFormat = aglChoosePixelFormat(NULL, 0, software_renderer_attributes); obj->SetRendererIsSoftware(true); } if (myAGLPixelFormat == NULL || CheckForAGLError()) return NPERR_MODULE_LOAD_FAILED_ERROR; obj->mac_agl_context_ = aglCreateContext(myAGLPixelFormat, NULL); if (CheckForAGLError()) return NPERR_MODULE_LOAD_FAILED_ERROR; aglDestroyPixelFormat(myAGLPixelFormat); if (!SetWindowForAGLContext(obj->mac_agl_context_, obj->mac_window_)) return NPERR_MODULE_LOAD_FAILED_ERROR; aglSetCurrentContext(obj->mac_agl_context_); GetOpenGLMetrics(); #ifdef USE_AGL_DOUBLE_BUFFER GLint swapInterval = 1; // request synchronization aglSetInteger(obj->mac_agl_context_, AGL_SWAP_INTERVAL, &swapInterval); #endif } int clipHeight = window->clipRect.bottom - window->clipRect.top; int clipWidth = window->clipRect.right - window->clipRect.left; int x_offset = window->clipRect.left - window->x; int y_offset = window->clipRect.top - window->y; int gl_x_origin = x_offset; int gl_y_origin = window->clipRect.bottom - (window->y + window->height); // Firefox calls us with an empty cliprect all the time, toggling our // cliprect back and forth between empty and the normal value, particularly // during scrolling. // As we need to make our AGL surface track the clip rect, honoring all of // these calls would result in spectacular flashing. // The goal here is to try to spot the calls we can safely ignore. // The bogus empty cliprects always have left and top of the real clip. // A null cliprect should always be honored ({0,0,0,0} means a hidden tab), // as should the first ever call to this function, // or an attempt to change the window. // The call at the end of a scroll step is also honored. // Otherwise, skip this change and do not hide our context. bool is_empty_cliprect = (clipHeight <= 0 || clipWidth <= 0); bool is_null_cliprect = (window->clipRect.bottom == 0 && window->clipRect.top == 0 && window->clipRect.left == 0 && window->clipRect.right == 0); if (is_empty_cliprect && (!is_null_cliprect) && had_a_window && (!window_changed) && !obj->ScrollIsInProgress()) { return NPERR_NO_ERROR; } // Workaround - the Apple software renderer crashes if you set the drawing // area to 0x0, so use 1x1. if (is_empty_cliprect && obj->RendererIsSoftware()) clipWidth = clipHeight = 1; // update size and location of the agl context if (obj->mac_agl_context_) { // We already had a window, and now we've got a different window - // this can happen when the user drags out a tab in Safari into its own // window. if (had_a_window && window_changed) SetWindowForAGLContext(obj->mac_agl_context_, obj->mac_window_); aglSetCurrentContext(obj->mac_agl_context_); Rect windowRect = {0, 0, 0, 0}; if (obj->drawing_model_ == NPDrawingModelQuickDraw) GetWindowBounds(obj->mac_window_, kWindowContentRgn, &windowRect); else GetWindowBounds(obj->mac_window_, kWindowStructureRgn, &windowRect); int windowHeight = windowRect.bottom - windowRect.top; // These are in window coords, the weird bit being that agl wants the // location of the bottom of the GL context in y flipped coords, // eg 100 would mean the bottom of the GL context was 100 up from the // bottom of the window. obj->last_buffer_rect_[0] = window->x + x_offset; obj->last_buffer_rect_[1] = (windowHeight - (window->y + clipHeight)) - y_offset; // this param is y flipped obj->last_buffer_rect_[2] = clipWidth; obj->last_buffer_rect_[3] = clipHeight; obj->mac_surface_hidden_ = false; // If in fullscreen, just remember the desired change in geometry so // we restore to the right rectangle. if (obj->GetFullscreenMacWindow() != NULL) return NPERR_NO_ERROR; aglSetInteger(obj->mac_agl_context_, AGL_BUFFER_RECT, obj->last_buffer_rect_); aglEnable(obj->mac_agl_context_, AGL_BUFFER_RECT); } if (had_a_pbuffer) { // CoreGraphics drawing model when we have no on-screen window (Chrome, // specifically). obj->EnableOffscreenRendering(); obj->Resize(window->width, window->height); return NPERR_NO_ERROR; } // Renderer is already initialized from a previous call to this function, // just update size and position and return. if (had_a_window) { if (obj->renderer()) { obj->renderer()->SetClientOriginOffset(gl_x_origin, gl_y_origin); obj->Resize(window->width, window->height); } return NPERR_NO_ERROR; } if (obj->renderer()) return NPERR_NO_ERROR; // Create and assign the graphics context. o3d::DisplayWindowMac default_display; default_display.set_agl_context(obj->mac_agl_context_); default_display.set_cgl_context(obj->mac_cgl_context_); obj->CreateRenderer(default_display); // if the renderer cannot be created (maybe the features are not supported) // then we can proceed no further if (!obj->renderer()) { if (obj->mac_agl_context_) { ::aglDestroyContext(obj->mac_agl_context_); obj->mac_agl_context_ = NULL; } } obj->client()->Init(); if (obj->renderer()) { if (obj->mac_cgl_pbuffer_) { obj->EnableOffscreenRendering(); } else { obj->renderer()->SetClientOriginOffset(gl_x_origin, gl_y_origin); } obj->Resize(window->width, window->height); #ifdef CFTIMER // now that the grahics context is setup, add this instance to the timer // list so it gets drawn repeatedly gRenderTimer.AddInstance(instance); #endif // CFTIMER } return NPERR_NO_ERROR; } void PlatformNPPStreamAsFile(StreamManager *stream_manager, NPStream *stream, const char *fname) { // Some browsers give us an absolute HFS path in fname, some give us an // absolute Posix path, so convert to Posix if needed. if ((!fname) || (fname[0] == '/') || !fname[0]) { stream_manager->SetStreamFile(stream, fname); } else { const char* converted_fname = CreatePosixFilePathFromHFSFilePath(fname); if (converted_fname == NULL) return; // TODO should also log error if we ever get here stream_manager->SetStreamFile(stream, converted_fname); delete converted_fname; } } int16 PlatformNPPHandleEvent(NPP instance, PluginObject *obj, void *event) { if (obj->event_model_ == NPEventModelCarbon) { EventRecord* theEvent = static_cast(event); return HandleMacEvent(theEvent, instance) ? 1 : 0; } else if (obj->event_model_ == NPEventModelCocoa){ return HandleCocoaEvent(instance, (NPCocoaEvent*)event, true) ? 1 : 0; } return 0; } } // namespace o3d