1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 University of Szeged
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24 * THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "NetscapePluginX11.h"
29
30#if PLATFORM(X11) && ENABLE(NETSCAPE_PLUGIN_API)
31
32#include "NetscapePlugin.h"
33#include "PluginController.h"
34#include "WebEvent.h"
35#include <WebCore/GraphicsContext.h>
36#include <WebCore/NotImplemented.h>
37#include <WebCore/PlatformDisplayX11.h>
38#include <WebCore/XUniquePtr.h>
39
40#if PLATFORM(GTK)
41#include <gtk/gtk.h>
42#ifndef GTK_API_VERSION_2
43#include <gtk/gtkx.h>
44#endif
45#include <gdk/gdkx.h>
46#include <WebCore/GtkVersioning.h>
47#endif
48
49#if USE(CAIRO)
50#include <WebCore/PlatformContextCairo.h>
51#include <WebCore/RefPtrCairo.h>
52#include <cairo/cairo-xlib.h>
53#endif
54
55namespace WebKit {
56using namespace WebCore;
57
58static inline Display* x11HostDisplay()
59{
60 return downcast<PlatformDisplayX11>(PlatformDisplay::sharedDisplay()).native();
61}
62
63static Display* getPluginDisplay()
64{
65#if PLATFORM(GTK)
66 // Since we're a gdk/gtk app, we'll (probably?) have the same X connection as any gdk-based
67 // plugins, so we can return that. We might want to add other implementations here later.
68 return GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
69#else
70 return nullptr;
71#endif
72}
73
74static inline int x11Screen()
75{
76#if PLATFORM(GTK)
77 return gdk_screen_get_number(gdk_screen_get_default());
78#else
79 return 0;
80#endif
81}
82
83static inline int displayDepth()
84{
85#if PLATFORM(GTK)
86 return gdk_visual_get_depth(gdk_screen_get_system_visual(gdk_screen_get_default()));
87#else
88 return 0;
89#endif
90}
91
92static inline unsigned long rootWindowID()
93{
94#if PLATFORM(GTK)
95 return GDK_ROOT_WINDOW();
96#else
97 return 0;
98#endif
99}
100
101#if PLATFORM(GTK)
102static gboolean socketPlugRemovedCallback(GtkSocket*)
103{
104 // Default action is to destroy the GtkSocket, so we just return TRUE here
105 // to be able to reuse the socket. For some obscure reason, newer versions
106 // of flash plugin remove the plug from the socket, probably because the plug
107 // created by the plugin is re-parented.
108 return TRUE;
109}
110#endif
111
112std::unique_ptr<NetscapePluginX11> NetscapePluginX11::create(NetscapePlugin& plugin)
113{
114#if PLATFORM(GTK)
115 uint64_t windowID = 0;
116#endif
117 if (plugin.isWindowed()) {
118#if PLATFORM(GTK)
119 // NPPVplugiNeedsXEmbed is a boolean value, but at least the
120 // Flash player plugin is using an 'int' instead.
121 int needsXEmbed = 0;
122 plugin.NPP_GetValue(NPPVpluginNeedsXEmbed, &needsXEmbed);
123 if (needsXEmbed) {
124 windowID = plugin.controller()->createPluginContainer();
125 if (!windowID)
126 return nullptr;
127 } else {
128 notImplemented();
129 return nullptr;
130 }
131#else
132 notImplemented();
133 return nullptr;
134#endif
135 }
136
137 Display* display = getPluginDisplay();
138 if (!display)
139 return nullptr;
140
141#if PLATFORM(GTK)
142 if (plugin.isWindowed())
143 return std::make_unique<NetscapePluginX11>(plugin, display, windowID);
144#endif
145
146 return std::make_unique<NetscapePluginX11>(plugin, display);
147}
148
149NetscapePluginX11::NetscapePluginX11(NetscapePlugin& plugin, Display* display)
150 : m_plugin(plugin)
151 , m_pluginDisplay(display)
152{
153 Display* hostDisplay = x11HostDisplay();
154 int depth = displayDepth();
155 m_setWindowCallbackStruct.display = hostDisplay;
156 m_setWindowCallbackStruct.depth = depth;
157
158 XVisualInfo visualTemplate;
159 visualTemplate.screen = x11Screen();
160 visualTemplate.depth = depth;
161 visualTemplate.c_class = TrueColor;
162 int numMatching;
163 XUniquePtr<XVisualInfo> visualInfo(XGetVisualInfo(hostDisplay, VisualScreenMask | VisualDepthMask | VisualClassMask, &visualTemplate, &numMatching));
164 ASSERT(visualInfo);
165 Visual* visual = visualInfo.get()[0].visual;
166 ASSERT(visual);
167
168 m_setWindowCallbackStruct.type = NP_SETWINDOW;
169 m_setWindowCallbackStruct.visual = visual;
170 m_setWindowCallbackStruct.colormap = XCreateColormap(hostDisplay, rootWindowID(), visual, AllocNone);
171}
172
173#if PLATFORM(GTK)
174NetscapePluginX11::NetscapePluginX11(NetscapePlugin& plugin, Display* display, uint64_t windowID)
175 : m_plugin(plugin)
176 , m_pluginDisplay(display)
177{
178 // It seems flash needs the socket to be in the same process,
179 // I guess it uses gdk_window_lookup(), so we create a new socket here
180 // containing a plug with the UI process socket embedded.
181 m_platformPluginWidget = gtk_plug_new(static_cast<Window>(windowID));
182
183 // Hide the GtkPlug on delete-event since we assume the widget is valid while the plugin is active.
184 // platformDestroy() will be called anyway right after the delete-event.
185 g_signal_connect(m_platformPluginWidget, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
186
187 GtkWidget* socket = gtk_socket_new();
188 // Do not show the plug widget until the socket is connected.
189 g_signal_connect_swapped(socket, "plug-added", G_CALLBACK(gtk_widget_show), m_platformPluginWidget);
190 g_signal_connect(socket, "plug-removed", G_CALLBACK(socketPlugRemovedCallback), nullptr);
191 gtk_container_add(GTK_CONTAINER(m_platformPluginWidget), socket);
192 gtk_widget_show(socket);
193
194 Display* hostDisplay = x11HostDisplay();
195 m_npWindowID = gtk_socket_get_id(GTK_SOCKET(socket));
196 GdkWindow* window = gtk_widget_get_window(socket);
197 m_setWindowCallbackStruct.type = NP_SETWINDOW;
198 m_setWindowCallbackStruct.display = GDK_WINDOW_XDISPLAY(window);
199 m_setWindowCallbackStruct.visual = GDK_VISUAL_XVISUAL(gdk_window_get_visual(window));
200 m_setWindowCallbackStruct.depth = gdk_visual_get_depth(gdk_window_get_visual(window));
201 m_setWindowCallbackStruct.colormap = XCreateColormap(hostDisplay, GDK_ROOT_WINDOW(), m_setWindowCallbackStruct.visual, AllocNone);
202
203 XFlush(hostDisplay);
204}
205#endif
206
207NetscapePluginX11::~NetscapePluginX11()
208{
209 XFreeColormap(x11HostDisplay(), m_setWindowCallbackStruct.colormap);
210
211 m_drawable.reset();
212
213#if PLATFORM(GTK)
214 if (m_platformPluginWidget)
215 gtk_widget_destroy(m_platformPluginWidget);
216#endif
217}
218
219NPWindowType NetscapePluginX11::windowType() const
220{
221 return m_plugin.isWindowed() ? NPWindowTypeWindow : NPWindowTypeDrawable;
222}
223
224void* NetscapePluginX11::window() const
225{
226#if PLATFORM(GTK)
227 return m_plugin.isWindowed() ? GINT_TO_POINTER(m_npWindowID) : nullptr;
228#else
229 return nullptr;
230#endif
231}
232
233void NetscapePluginX11::geometryDidChange()
234{
235 if (m_plugin.isWindowed()) {
236 uint64_t windowID = 0;
237#if PLATFORM(GTK)
238 if (!gtk_plug_get_embedded(GTK_PLUG(m_platformPluginWidget)))
239 return;
240 windowID = static_cast<uint64_t>(GDK_WINDOW_XID(gtk_plug_get_socket_window(GTK_PLUG(m_platformPluginWidget))));
241#endif
242 m_plugin.controller()->windowedPluginGeometryDidChange(m_plugin.frameRectInWindowCoordinates(), m_plugin.clipRect(), windowID);
243 return;
244 }
245
246 m_drawable.reset();
247 if (m_plugin.size().isEmpty())
248 return;
249
250 m_drawable = XCreatePixmap(x11HostDisplay(), rootWindowID(), m_plugin.size().width(), m_plugin.size().height(), displayDepth());
251 XSync(x11HostDisplay(), false); // Make sure that the server knows about the Drawable.
252}
253
254void NetscapePluginX11::visibilityDidChange()
255{
256 ASSERT(m_plugin.isWindowed());
257 uint64_t windowID = 0;
258#if PLATFORM(GTK)
259 if (!gtk_plug_get_embedded(GTK_PLUG(m_platformPluginWidget)))
260 return;
261 windowID = static_cast<uint64_t>(GDK_WINDOW_XID(gtk_plug_get_socket_window(GTK_PLUG(m_platformPluginWidget))));
262#endif
263 m_plugin.controller()->windowedPluginVisibilityDidChange(m_plugin.isVisible(), windowID);
264 m_plugin.controller()->windowedPluginGeometryDidChange(m_plugin.frameRectInWindowCoordinates(), m_plugin.clipRect(), windowID);
265}
266
267void NetscapePluginX11::paint(GraphicsContext& context, const IntRect& dirtyRect)
268{
269 ASSERT(!m_plugin.isWindowed());
270
271 if (context.paintingDisabled() || !m_drawable)
272 return;
273
274 XEvent xevent;
275 memset(&xevent, 0, sizeof(XEvent));
276 XGraphicsExposeEvent& exposeEvent = xevent.xgraphicsexpose;
277 exposeEvent.type = GraphicsExpose;
278 exposeEvent.display = x11HostDisplay();
279 exposeEvent.drawable = m_drawable.get();
280
281 IntRect exposedRect(dirtyRect);
282 exposeEvent.x = exposedRect.x();
283 exposeEvent.y = exposedRect.y();
284
285 // Note: in transparent mode Flash thinks width is the right and height is the bottom.
286 // We should take it into account if we want to support transparency.
287 exposeEvent.width = exposedRect.width();
288 exposeEvent.height = exposedRect.height();
289
290 m_plugin.NPP_HandleEvent(&xevent);
291
292 if (m_pluginDisplay != x11HostDisplay())
293 XSync(m_pluginDisplay, false);
294
295#if PLATFORM(GTK)
296 RefPtr<cairo_surface_t> drawableSurface = adoptRef(cairo_xlib_surface_create(m_pluginDisplay, m_drawable.get(),
297 m_setWindowCallbackStruct.visual, m_plugin.size().width(), m_plugin.size().height()));
298 cairo_t* cr = context.platformContext()->cr();
299 cairo_save(cr);
300
301 cairo_set_source_surface(cr, drawableSurface.get(), 0, 0);
302
303 cairo_rectangle(cr, exposedRect.x(), exposedRect.y(), exposedRect.width(), exposedRect.height());
304 cairo_clip(cr);
305 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
306 cairo_paint(cr);
307
308 cairo_restore(cr);
309#else
310 notImplemented();
311#endif
312}
313
314static inline void initializeXEvent(XEvent& event)
315{
316 memset(&event, 0, sizeof(XEvent));
317 event.xany.serial = 0;
318 event.xany.send_event = false;
319 event.xany.display = x11HostDisplay();
320 event.xany.window = 0;
321}
322
323static inline uint64_t xTimeStamp(WallTime timestamp)
324{
325 return timestamp.secondsSinceEpoch().milliseconds();
326}
327
328static inline unsigned xKeyModifiers(const WebEvent& event)
329{
330 unsigned xModifiers = 0;
331 if (event.controlKey())
332 xModifiers |= ControlMask;
333 if (event.shiftKey())
334 xModifiers |= ShiftMask;
335 if (event.altKey())
336 xModifiers |= Mod1Mask;
337 if (event.metaKey())
338 xModifiers |= Mod4Mask;
339
340 return xModifiers;
341}
342
343template <typename XEventType, typename WebEventType>
344static inline void setCommonMouseEventFields(XEventType& xEvent, const WebEventType& webEvent, const WebCore::IntPoint& pluginLocation)
345{
346 xEvent.root = rootWindowID();
347 xEvent.subwindow = 0;
348 xEvent.time = xTimeStamp(webEvent.timestamp());
349 xEvent.x = webEvent.position().x() - pluginLocation.x();
350 xEvent.y = webEvent.position().y() - pluginLocation.y();
351 xEvent.x_root = webEvent.globalPosition().x();
352 xEvent.y_root = webEvent.globalPosition().y();
353 xEvent.state = xKeyModifiers(webEvent);
354 xEvent.same_screen = true;
355}
356
357static inline void setXMotionEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
358{
359 XMotionEvent& xMotion = xEvent.xmotion;
360 setCommonMouseEventFields(xMotion, webEvent, pluginLocation);
361 xMotion.type = MotionNotify;
362}
363
364static inline void setXButtonEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation)
365{
366 XButtonEvent& xButton = xEvent.xbutton;
367 setCommonMouseEventFields(xButton, webEvent, pluginLocation);
368
369 xButton.type = (webEvent.type() == WebEvent::MouseDown) ? ButtonPress : ButtonRelease;
370 switch (webEvent.button()) {
371 case WebMouseEvent::LeftButton:
372 xButton.button = Button1;
373 break;
374 case WebMouseEvent::MiddleButton:
375 xButton.button = Button2;
376 break;
377 case WebMouseEvent::RightButton:
378 xButton.button = Button3;
379 break;
380 default:
381 ASSERT_NOT_REACHED();
382 break;
383 }
384}
385
386static inline void setXButtonEventFieldsByWebWheelEvent(XEvent& xEvent, const WebWheelEvent& webEvent, const WebCore::IntPoint& pluginLocation)
387{
388 XButtonEvent& xButton = xEvent.xbutton;
389 setCommonMouseEventFields(xButton, webEvent, pluginLocation);
390
391 xButton.type = ButtonPress;
392 FloatSize ticks = webEvent.wheelTicks();
393 if (ticks.height()) {
394 if (ticks.height() > 0)
395 xButton.button = 4; // up
396 else
397 xButton.button = 5; // down
398 } else {
399 if (ticks.width() > 0)
400 xButton.button = 6; // left
401 else
402 xButton.button = 7; // right
403 }
404}
405
406static inline void setXCrossingEventFields(XEvent& xEvent, const WebMouseEvent& webEvent, const WebCore::IntPoint& pluginLocation, int type)
407{
408 XCrossingEvent& xCrossing = xEvent.xcrossing;
409 setCommonMouseEventFields(xCrossing, webEvent, pluginLocation);
410
411 xCrossing.type = type;
412 xCrossing.mode = NotifyNormal;
413 xCrossing.detail = NotifyDetailNone;
414 xCrossing.focus = false;
415}
416
417bool NetscapePluginX11::handleMouseEvent(const WebMouseEvent& event)
418{
419 ASSERT(!m_plugin.isWindowed());
420
421 XEvent xEvent;
422 initializeXEvent(xEvent);
423
424 switch (event.type()) {
425 case WebEvent::MouseDown:
426 case WebEvent::MouseUp:
427 setXButtonEventFields(xEvent, event, m_plugin.convertToRootView(IntPoint()));
428 break;
429 case WebEvent::MouseMove:
430 setXMotionEventFields(xEvent, event, m_plugin.convertToRootView(IntPoint()));
431 break;
432 case WebEvent::MouseForceChanged:
433 case WebEvent::MouseForceDown:
434 case WebEvent::MouseForceUp:
435 case WebEvent::NoType:
436 case WebEvent::Wheel:
437 case WebEvent::KeyDown:
438 case WebEvent::KeyUp:
439 case WebEvent::RawKeyDown:
440 case WebEvent::Char:
441#if ENABLE(TOUCH_EVENTS)
442 case WebEvent::TouchStart:
443 case WebEvent::TouchMove:
444 case WebEvent::TouchEnd:
445 case WebEvent::TouchCancel:
446#endif
447 return false;
448 }
449
450 return !m_plugin.NPP_HandleEvent(&xEvent);
451}
452
453// We undefine these constants in npruntime_internal.h to avoid collision
454// with WebKit and platform headers. Values are defined in X.h.
455const int kKeyPressType = 2;
456const int kKeyReleaseType = 3;
457const int kFocusInType = 9;
458const int kFocusOutType = 10;
459
460bool NetscapePluginX11::handleWheelEvent(const WebWheelEvent& event)
461{
462 ASSERT(!m_plugin.isWindowed());
463
464 XEvent xEvent;
465 initializeXEvent(xEvent);
466 setXButtonEventFieldsByWebWheelEvent(xEvent, event, m_plugin.convertToRootView(IntPoint()));
467
468 return !m_plugin.NPP_HandleEvent(&xEvent);
469}
470
471void NetscapePluginX11::setFocus(bool focusIn)
472{
473 ASSERT(!m_plugin.isWindowed());
474
475 XEvent xEvent;
476 initializeXEvent(xEvent);
477 XFocusChangeEvent& focusEvent = xEvent.xfocus;
478 focusEvent.type = focusIn ? kFocusInType : kFocusOutType;
479 focusEvent.mode = NotifyNormal;
480 focusEvent.detail = NotifyDetailNone;
481
482 m_plugin.NPP_HandleEvent(&xEvent);
483}
484
485bool NetscapePluginX11::handleMouseEnterEvent(const WebMouseEvent& event)
486{
487 ASSERT(!m_plugin.isWindowed());
488
489 XEvent xEvent;
490 initializeXEvent(xEvent);
491 setXCrossingEventFields(xEvent, event, m_plugin.convertToRootView(IntPoint()), EnterNotify);
492
493 return !m_plugin.NPP_HandleEvent(&xEvent);
494}
495
496bool NetscapePluginX11::handleMouseLeaveEvent(const WebMouseEvent& event)
497{
498 ASSERT(!m_plugin.isWindowed());
499
500 XEvent xEvent;
501 initializeXEvent(xEvent);
502 setXCrossingEventFields(xEvent, event, m_plugin.convertToRootView(IntPoint()), LeaveNotify);
503
504 return !m_plugin.NPP_HandleEvent(&xEvent);
505}
506
507static inline void setXKeyEventFields(XEvent& xEvent, const WebKeyboardEvent& webEvent)
508{
509 xEvent.xany.type = (webEvent.type() == WebEvent::KeyDown) ? kKeyPressType : kKeyReleaseType;
510 XKeyEvent& xKey = xEvent.xkey;
511 xKey.root = rootWindowID();
512 xKey.subwindow = 0;
513 xKey.time = xTimeStamp(webEvent.timestamp());
514 xKey.state = xKeyModifiers(webEvent);
515 xKey.keycode = webEvent.nativeVirtualKeyCode();
516
517 xKey.same_screen = true;
518
519 // Key events propagated to the plugin does not need to have position.
520 // source: https://developer.mozilla.org/en/NPEvent
521 xKey.x = 0;
522 xKey.y = 0;
523 xKey.x_root = 0;
524 xKey.y_root = 0;
525}
526
527bool NetscapePluginX11::handleKeyboardEvent(const WebKeyboardEvent& event)
528{
529 ASSERT(!m_plugin.isWindowed());
530 // We don't generate other types of keyboard events via WebEventFactory.
531 ASSERT(event.type() == WebEvent::KeyDown || event.type() == WebEvent::KeyUp);
532
533 XEvent xEvent;
534 initializeXEvent(xEvent);
535 setXKeyEventFields(xEvent, event);
536
537 return !m_plugin.NPP_HandleEvent(&xEvent);
538}
539
540} // namespace WebKit
541
542#endif // PLATFORM(X11) && ENABLE(NETSCAPE_PLUGIN_API)
543