blob: 30a0e86cdb1cead117217ddecfa866529238c396 [file] [log] [blame]
/*
* Copyright (C) 2010 Apple Inc. All rights reserved.
* Copyright (C) 2010 University of Szeged
*
* 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. AND ITS 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 APPLE INC. OR ITS 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.
*/
#include "config.h"
#if PLUGIN_ARCHITECTURE(X11) && ENABLE(NETSCAPE_PLUGIN_API)
#include "NetscapePlugin.h"
#include "PluginController.h"
#include "WebEvent.h"
#include <WebCore/GraphicsContext.h>
#include <WebCore/NotImplemented.h>
#if PLATFORM(QT)
#include <WebCore/QtX11ImageConversion.h>
#elif PLATFORM(GTK)
#include <gtk/gtk.h>
#ifndef GTK_API_VERSION_2
#include <gtk/gtkx.h>
#endif
#include <gdk/gdkx.h>
#include <WebCore/GtkVersioning.h>
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
#include <Ecore_X.h>
#endif
#if USE(CAIRO) && !PLATFORM(WIN_CAIRO)
#include "PlatformContextCairo.h"
#include "RefPtrCairo.h"
#include <cairo/cairo-xlib.h>
#endif
using namespace WebCore;
namespace WebKit {
static Display* getPluginDisplay()
{
#if PLATFORM(QT)
// At the moment, we only support gdk based plugins (like Flash) that use a different X connection.
// The code below has the same effect as this one:
// Display *gdkDisplay = gdk_x11_display_get_xdisplay(gdk_display_get_default());
QLibrary library(QLatin1String("libgdk-x11-2.0"), 0);
if (!library.load())
return 0;
typedef void *(*gdk_init_check_ptr)(void*, void*);
gdk_init_check_ptr gdk_init_check = (gdk_init_check_ptr)library.resolve("gdk_init_check");
if (!gdk_init_check)
return 0;
typedef void *(*gdk_display_get_default_ptr)();
gdk_display_get_default_ptr gdk_display_get_default = (gdk_display_get_default_ptr)library.resolve("gdk_display_get_default");
if (!gdk_display_get_default)
return 0;
typedef void *(*gdk_x11_display_get_xdisplay_ptr)(void *);
gdk_x11_display_get_xdisplay_ptr gdk_x11_display_get_xdisplay = (gdk_x11_display_get_xdisplay_ptr)library.resolve("gdk_x11_display_get_xdisplay");
if (!gdk_x11_display_get_xdisplay)
return 0;
gdk_init_check(0, 0);
return (Display*)gdk_x11_display_get_xdisplay(gdk_display_get_default());
#elif PLATFORM(GTK)
// Since we're a gdk/gtk app, we'll (probably?) have the same X connection as any gdk-based
// plugins, so we can return that. We might want to add other implementations here later.
return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
return static_cast<Display*>(ecore_x_display_get());
#else
return 0;
#endif
}
static inline int x11Screen()
{
#if PLATFORM(QT)
return XDefaultScreen(NetscapePlugin::x11HostDisplay());
#elif PLATFORM(GTK)
return gdk_screen_get_number(gdk_screen_get_default());
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
return ecore_x_screen_index_get(ecore_x_default_screen_get());
#else
return 0;
#endif
}
static inline int displayDepth()
{
#if PLATFORM(QT)
return XDefaultDepth(NetscapePlugin::x11HostDisplay(), x11Screen());
#elif PLATFORM(GTK)
return gdk_visual_get_depth(gdk_screen_get_system_visual(gdk_screen_get_default()));
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
return ecore_x_default_depth_get(NetscapePlugin::x11HostDisplay(), ecore_x_default_screen_get());
#else
return 0;
#endif
}
static inline unsigned long rootWindowID()
{
#if PLATFORM(QT)
return XDefaultRootWindow(NetscapePlugin::x11HostDisplay());
#elif PLATFORM(GTK)
return GDK_ROOT_WINDOW();
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
return ecore_x_window_root_first_get();
#else
return 0;
#endif
}
#if PLATFORM(GTK)
static bool moduleMixesGtkSymbols(Module* module)
{
#ifdef GTK_API_VERSION_2
return module->functionPointer<gpointer>("gtk_application_get_type");
#else
return module->functionPointer<gpointer>("gtk_object_get_type");
#endif
}
#endif
Display* NetscapePlugin::x11HostDisplay()
{
#if PLATFORM(QT)
static Display* dedicatedDisplay = 0;
if (!dedicatedDisplay)
dedicatedDisplay = XOpenDisplay(0);
ASSERT(dedicatedDisplay);
return dedicatedDisplay;
#elif PLATFORM(GTK)
return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
#elif PLATFORM(EFL) && defined(HAVE_ECORE_X)
return static_cast<Display*>(ecore_x_display_get());
#else
return 0;
#endif
}
#if PLATFORM(GTK)
static gboolean socketPlugRemovedCallback(GtkSocket*)
{
// Default action is to destroy the GtkSocket, so we just return TRUE here
// to be able to reuse the socket. For some obscure reason, newer versions
// of flash plugin remove the plug from the socket, probably because the plug
// created by the plugin is re-parented.
return TRUE;
}
#endif
bool NetscapePlugin::platformPostInitializeWindowed(bool needsXEmbed, uint64_t windowID)
{
m_npWindow.type = NPWindowTypeWindow;
if (!needsXEmbed) {
notImplemented();
return false;
}
Display* display = x11HostDisplay();
#if PLATFORM(GTK)
// It seems flash needs the socket to be in the same process,
// I guess it uses gdk_window_lookup(), so we create a new socket here
// containing a plug with the UI process socket embedded.
m_platformPluginWidget = gtk_plug_new(static_cast<Window>(windowID));
GtkWidget* socket = gtk_socket_new();
g_signal_connect(socket, "plug-removed", G_CALLBACK(socketPlugRemovedCallback), 0);
gtk_container_add(GTK_CONTAINER(m_platformPluginWidget), socket);
gtk_widget_show(socket);
gtk_widget_show(m_platformPluginWidget);
m_npWindow.window = GINT_TO_POINTER(gtk_socket_get_id(GTK_SOCKET(socket)));
GdkWindow* window = gtk_widget_get_window(socket);
NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
callbackStruct->display = GDK_WINDOW_XDISPLAY(window);
callbackStruct->visual = GDK_VISUAL_XVISUAL(gdk_window_get_visual(window));
callbackStruct->depth = gdk_visual_get_depth(gdk_window_get_visual(window));
callbackStruct->colormap = XCreateColormap(display, GDK_ROOT_WINDOW(), callbackStruct->visual, AllocNone);
#else
UNUSED_PARAM(windowID);
#endif
XFlush(display);
callSetWindow();
return true;
}
bool NetscapePlugin::platformPostInitializeWindowless()
{
Display* display = x11HostDisplay();
m_npWindow.type = NPWindowTypeDrawable;
m_npWindow.window = 0;
int depth = displayDepth();
#if PLATFORM(QT)
ASSERT(depth == 16 || depth == 24 || depth == 32);
#endif
NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
callbackStruct->display = display;
callbackStruct->depth = depth;
XVisualInfo visualTemplate;
visualTemplate.screen = x11Screen();
visualTemplate.depth = depth;
visualTemplate.c_class = TrueColor;
int numMatching;
XVisualInfo* visualInfo = XGetVisualInfo(display, VisualScreenMask | VisualDepthMask | VisualClassMask,
&visualTemplate, &numMatching);
ASSERT(visualInfo);
Visual* visual = visualInfo[0].visual;
ASSERT(visual);
XFree(visualInfo);
callbackStruct->visual = visual;
callbackStruct->colormap = XCreateColormap(display, rootWindowID(), visual, AllocNone);
callSetWindow();
return true;
}
bool NetscapePlugin::platformPostInitialize()
{
#if PLATFORM(GTK)
if (moduleMixesGtkSymbols(m_pluginModule->module()))
return false;
#endif
uint64_t windowID = 0;
bool needsXEmbed = false;
if (m_isWindowed) {
NPP_GetValue(NPPVpluginNeedsXEmbed, &needsXEmbed);
if (needsXEmbed) {
windowID = controller()->createPluginContainer();
if (!windowID)
return false;
} else {
notImplemented();
return false;
}
}
if (!(m_pluginDisplay = getPluginDisplay()))
return false;
NPSetWindowCallbackStruct* callbackStruct = new NPSetWindowCallbackStruct;
callbackStruct->type = 0;
m_npWindow.ws_info = callbackStruct;
if (m_isWindowed)
return platformPostInitializeWindowed(needsXEmbed, windowID);
return platformPostInitializeWindowless();
}
void NetscapePlugin::platformDestroy()
{
NPSetWindowCallbackStruct* callbackStruct = static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info);
Display* hostDisplay = x11HostDisplay();
XFreeColormap(hostDisplay, callbackStruct->colormap);
delete callbackStruct;
if (m_drawable) {
XFreePixmap(hostDisplay, m_drawable);
m_drawable = 0;
}
}
bool NetscapePlugin::platformInvalidate(const IntRect&)
{
notImplemented();
return false;
}
void NetscapePlugin::platformGeometryDidChange()
{
if (m_isWindowed) {
uint64_t windowID = 0;
#if PLATFORM(GTK)
windowID = static_cast<uint64_t>(GDK_WINDOW_XID(gtk_plug_get_socket_window(GTK_PLUG(m_platformPluginWidget))));
#endif
IntRect clipRect(m_clipRect);
clipRect.move(-m_frameRectInWindowCoordinates.x(), -m_frameRectInWindowCoordinates.y());
controller()->windowedPluginGeometryDidChange(m_frameRectInWindowCoordinates, clipRect, windowID);
return;
}
Display* display = x11HostDisplay();
if (m_drawable)
XFreePixmap(display, m_drawable);
if (m_pluginSize.isEmpty()) {
m_drawable = 0;
return;
}
m_drawable = XCreatePixmap(display, rootWindowID(), m_pluginSize.width(), m_pluginSize.height(), displayDepth());
XSync(display, false); // Make sure that the server knows about the Drawable.
}
void NetscapePlugin::platformVisibilityDidChange()
{
notImplemented();
}
void NetscapePlugin::platformPaint(GraphicsContext* context, const IntRect& dirtyRect, bool /*isSnapshot*/)
{
if (m_isWindowed)
return;
if (!m_isStarted) {
// FIXME: we should paint a missing plugin icon.
return;
}
if (context->paintingDisabled() || !m_drawable)
return;
XEvent xevent;
memset(&xevent, 0, sizeof(XEvent));
XGraphicsExposeEvent& exposeEvent = xevent.xgraphicsexpose;
exposeEvent.type = GraphicsExpose;
exposeEvent.display = x11HostDisplay();
exposeEvent.drawable = m_drawable;
IntRect exposedRect(dirtyRect);
exposeEvent.x = exposedRect.x();
exposeEvent.y = exposedRect.y();
// Note: in transparent mode Flash thinks width is the right and height is the bottom.
// We should take it into account if we want to support transparency.
exposeEvent.width = exposedRect.width();
exposeEvent.height = exposedRect.height();
NPP_HandleEvent(&xevent);
if (m_pluginDisplay != x11HostDisplay())
XSync(m_pluginDisplay, false);
#if PLATFORM(QT)
XImage* xImage = XGetImage(NetscapePlugin::x11HostDisplay(), m_drawable, exposedRect.x(), exposedRect.y(),
exposedRect.width(), exposedRect.height(), ULONG_MAX, ZPixmap);
QPainter* painter = context->platformContext();
painter->drawImage(QPoint(exposedRect.x(), exposedRect.y()), qimageFromXImage(xImage), exposedRect);
XDestroyImage(xImage);
#elif PLATFORM(GTK) || (PLATFORM(EFL) && USE(CAIRO))
RefPtr<cairo_surface_t> drawableSurface = adoptRef(cairo_xlib_surface_create(m_pluginDisplay,
m_drawable,
static_cast<NPSetWindowCallbackStruct*>(m_npWindow.ws_info)->visual,
m_pluginSize.width(),
m_pluginSize.height()));
cairo_t* cr = context->platformContext()->cr();
cairo_save(cr);
cairo_set_source_surface(cr, drawableSurface.get(), 0, 0);
cairo_rectangle(cr, exposedRect.x(), exposedRect.y(), exposedRect.width(), exposedRect.height());
cairo_clip(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_restore(cr);
#else
notImplemented();
#endif
}
static inline void initializeXEvent(XEvent& event)
{
memset(&event, 0, sizeof(XEvent));
event.xany.serial = 0;
event.xany.send_event = false;
event.xany.display = NetscapePlugin::x11HostDisplay();
event.xany.window = 0;
}
static inline uint64_t xTimeStamp(double timestampInSeconds)
{
return timestampInSeconds * 1000;
}
static inline unsigned xKeyModifiers(const WebEvent& event)
{
unsigned xModifiers = 0;
if (event.controlKey())
xModifiers |= ControlMask;
if (event.shiftKey())
xModifiers |= ShiftMask;
if (event.altKey())
xModifiers |= Mod1Mask;
if (event.metaKey())
xModifiers |= Mod4Mask;
return xModifiers;
}
template <typename XEventType, typename WebEventType>
static inline void setCommonMouseEventFields(XEventType& xEvent, const WebEventType& webEvent, const WebCore::IntPoint& pluginLocation)
{
xEvent.root = rootWindowID();
xEvent.subwindow = 0;
xEvent.time = xTimeStamp(webEvent.timestamp());
xEvent.x = webEvent.position().x() - pluginLocation.x();
xEvent.y = webEvent.position().y() - pluginLocation.y();
xEvent.x_root = webEvent.globalPosition().x();
xEvent.y_root = webEvent.globalPosition().y();
xEvent.state = xKeyModifiers(webEvent);
xEvent.same_screen = true;
}
static inline void setXMotionEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
{
XMotionEvent& xMotion = xEvent.xmotion;
setCommonMouseEventFields(xMotion, webEvent, pluginLocation);
xMotion.type = MotionNotify;
}
static inline void setXButtonEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
{
XButtonEvent& xButton = xEvent.xbutton;
setCommonMouseEventFields(xButton, webEvent, pluginLocation);
xButton.type = (webEvent.type() == WebEvent::MouseDown) ? ButtonPress : ButtonRelease;
switch (webEvent.button()) {
case WebMouseEvent::LeftButton:
xButton.button = Button1;
break;
case WebMouseEvent::MiddleButton:
xButton.button = Button2;
break;
case WebMouseEvent::RightButton:
xButton.button = Button3;
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
static inline void setXButtonEventFieldsByWebWheelEvent(XEvent& xEvent, const WebWheelEvent& webEvent, const WebCore::IntPoint& pluginLocation)
{
XButtonEvent& xButton = xEvent.xbutton;
setCommonMouseEventFields(xButton, webEvent, pluginLocation);
xButton.type = ButtonPress;
FloatSize ticks = webEvent.wheelTicks();
if (ticks.height()) {
if (ticks.height() > 0)
xButton.button = 4; // up
else
xButton.button = 5; // down
} else {
if (ticks.width() > 0)
xButton.button = 6; // left
else
xButton.button = 7; // right
}
}
static inline void setXCrossingEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation, int type)
{
XCrossingEvent& xCrossing = xEvent.xcrossing;
setCommonMouseEventFields(xCrossing, webEvent, pluginLocation);
xCrossing.type = type;
xCrossing.mode = NotifyNormal;
xCrossing.detail = NotifyDetailNone;
xCrossing.focus = false;
}
bool NetscapePlugin::platformHandleMouseEvent(const WebMouseEvent& event)
{
if (m_isWindowed)
return false;
if ((event.type() == WebEvent::MouseDown || event.type() == WebEvent::MouseUp)
&& event.button() == WebMouseEvent::RightButton
&& quirks().contains(PluginQuirks::IgnoreRightClickInWindowlessMode))
return false;
XEvent xEvent;
initializeXEvent(xEvent);
switch (event.type()) {
case WebEvent::MouseDown:
case WebEvent::MouseUp:
setXButtonEventFields(xEvent, event, convertToRootView(IntPoint()));
break;
case WebEvent::MouseMove:
setXMotionEventFields(xEvent, event, convertToRootView(IntPoint()));
break;
case WebEvent::NoType:
case WebEvent::Wheel:
case WebEvent::KeyDown:
case WebEvent::KeyUp:
case WebEvent::RawKeyDown:
case WebEvent::Char:
#if ENABLE(GESTURE_EVENTS)
case WebEvent::GestureScrollBegin:
case WebEvent::GestureScrollEnd:
#endif
#if ENABLE(TOUCH_EVENTS)
case WebEvent::TouchStart:
case WebEvent::TouchMove:
case WebEvent::TouchEnd:
case WebEvent::TouchCancel:
#endif
return false;
}
return !NPP_HandleEvent(&xEvent);
}
// We undefine these constants in npruntime_internal.h to avoid collision
// with WebKit and platform headers. Values are defined in X.h.
const int kKeyPressType = 2;
const int kKeyReleaseType = 3;
const int kFocusInType = 9;
const int kFocusOutType = 10;
bool NetscapePlugin::platformHandleWheelEvent(const WebWheelEvent& event)
{
if (m_isWindowed)
return false;
XEvent xEvent;
initializeXEvent(xEvent);
setXButtonEventFieldsByWebWheelEvent(xEvent, event, convertToRootView(IntPoint()));
return !NPP_HandleEvent(&xEvent);
}
void NetscapePlugin::platformSetFocus(bool focusIn)
{
if (m_isWindowed)
return;
XEvent xEvent;
initializeXEvent(xEvent);
XFocusChangeEvent& focusEvent = xEvent.xfocus;
focusEvent.type = focusIn ? kFocusInType : kFocusOutType;
focusEvent.mode = NotifyNormal;
focusEvent.detail = NotifyDetailNone;
NPP_HandleEvent(&xEvent);
}
bool NetscapePlugin::wantsPluginRelativeNPWindowCoordinates()
{
return true;
}
bool NetscapePlugin::platformHandleMouseEnterEvent(const WebMouseEvent& event)
{
if (m_isWindowed)
return false;
XEvent xEvent;
initializeXEvent(xEvent);
setXCrossingEventFields(xEvent, event, convertToRootView(IntPoint()), EnterNotify);
return !NPP_HandleEvent(&xEvent);
}
bool NetscapePlugin::platformHandleMouseLeaveEvent(const WebMouseEvent& event)
{
if (m_isWindowed)
return false;
XEvent xEvent;
initializeXEvent(xEvent);
setXCrossingEventFields(xEvent, event, convertToRootView(IntPoint()), LeaveNotify);
return !NPP_HandleEvent(&xEvent);
}
static inline void setXKeyEventFields(XEvent& xEvent, const WebKeyboardEvent& webEvent)
{
xEvent.xany.type = (webEvent.type() == WebEvent::KeyDown) ? kKeyPressType : kKeyReleaseType;
XKeyEvent& xKey = xEvent.xkey;
xKey.root = rootWindowID();
xKey.subwindow = 0;
xKey.time = xTimeStamp(webEvent.timestamp());
xKey.state = xKeyModifiers(webEvent);
xKey.keycode = webEvent.nativeVirtualKeyCode();
xKey.same_screen = true;
// Key events propagated to the plugin does not need to have position.
// source: https://developer.mozilla.org/en/NPEvent
xKey.x = 0;
xKey.y = 0;
xKey.x_root = 0;
xKey.y_root = 0;
}
bool NetscapePlugin::platformHandleKeyboardEvent(const WebKeyboardEvent& event)
{
// We don't generate other types of keyboard events via WebEventFactory.
ASSERT(event.type() == WebEvent::KeyDown || event.type() == WebEvent::KeyUp);
XEvent xEvent;
initializeXEvent(xEvent);
setXKeyEventFields(xEvent, event);
return !NPP_HandleEvent(&xEvent);
}
} // namespace WebKit
#endif // PLUGIN_ARCHITECTURE(X11) && ENABLE(NETSCAPE_PLUGIN_API)