1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
3  */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 
8 #include "MozContainer.h"
9 
10 #include <glib.h>
11 #include <gtk/gtk.h>
12 #include <gdk/gdkx.h>
13 #include "nsWaylandDisplay.h"
14 #include "gfxPlatformGtk.h"
15 #include <wayland-egl.h>
16 #include <stdio.h>
17 #include <dlfcn.h>
18 
19 #undef LOG
20 #ifdef MOZ_LOGGING
21 
22 #  include "mozilla/Logging.h"
23 #  include "nsTArray.h"
24 #  include "Units.h"
25 extern mozilla::LazyLogModule gWidgetWaylandLog;
26 #  define LOGWAYLAND(args) \
27     MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
28 #else
29 #  define LOGWAYLAND(args)
30 #endif /* MOZ_LOGGING */
31 
32 using namespace mozilla;
33 using namespace mozilla::widget;
34 
35 // Declaration from nsWindow, we don't want to include whole nsWindow.h file
36 // here just for it.
37 wl_region* CreateOpaqueRegionWayland(int aX, int aY, int aWidth, int aHeight,
38                                      bool aSubtractCorners);
39 
40 /* init methods */
41 static void moz_container_wayland_destroy(GtkWidget* widget);
42 
43 /* widget class methods */
44 static void moz_container_wayland_map(GtkWidget* widget);
45 static gboolean moz_container_wayland_map_event(GtkWidget* widget,
46                                                 GdkEventAny* event);
47 static void moz_container_wayland_unmap(GtkWidget* widget);
48 static void moz_container_wayland_size_allocate(GtkWidget* widget,
49                                                 GtkAllocation* allocation);
50 
51 // Imlemented in MozContainer.cpp
52 void moz_container_realize(GtkWidget* widget);
53 
moz_container_wayland_move_locked(MozContainer * container,int dx,int dy)54 static void moz_container_wayland_move_locked(MozContainer* container, int dx,
55                                               int dy) {
56   LOGWAYLAND(("moz_container_wayland_move_locked [%p] %d,%d\n",
57               (void*)container, dx, dy));
58 
59   MozContainerWayland* wl_container = &container->wl_container;
60 
61   wl_container->subsurface_dx = dx;
62   wl_container->subsurface_dy = dy;
63   wl_container->surface_position_needs_update = true;
64 
65   // Wayland subsurface is not created yet.
66   if (!wl_container->subsurface) {
67     return;
68   }
69 
70   // wl_subsurface_set_position is actually property of parent surface
71   // which is effective when parent surface is commited.
72   wl_subsurface_set_position(wl_container->subsurface,
73                              wl_container->subsurface_dx,
74                              wl_container->subsurface_dy);
75   wl_container->surface_position_needs_update = false;
76 
77   GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
78   if (window) {
79     GdkRectangle rect = (GdkRectangle){0, 0, gdk_window_get_width(window),
80                                        gdk_window_get_height(window)};
81     gdk_window_invalidate_rect(window, &rect, false);
82   }
83 }
84 
moz_container_wayland_move(MozContainer * container,int dx,int dy)85 static void moz_container_wayland_move(MozContainer* container, int dx,
86                                        int dy) {
87   MutexAutoLock lock(*container->wl_container.container_lock);
88   LOGWAYLAND(
89       ("moz_container_wayland_move [%p] %d,%d\n", (void*)container, dx, dy));
90   moz_container_wayland_move_locked(container, dx, dy);
91 }
92 
93 // This is called from layout/compositor code only with
94 // size equal to GL rendering context. Otherwise there are
95 // rendering artifacts as wl_egl_window size does not match
96 // GL rendering pipeline setup.
moz_container_wayland_egl_window_set_size(MozContainer * container,int width,int height)97 void moz_container_wayland_egl_window_set_size(MozContainer* container,
98                                                int width, int height) {
99   MozContainerWayland* wl_container = &container->wl_container;
100   MutexAutoLock lock(*wl_container->container_lock);
101   if (wl_container->eglwindow) {
102     wl_egl_window_resize(wl_container->eglwindow, width, height, 0, 0);
103   }
104 }
105 
moz_container_wayland_class_init(MozContainerClass * klass)106 void moz_container_wayland_class_init(MozContainerClass* klass) {
107   /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
108     GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
109   GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
110 
111   widget_class->map = moz_container_wayland_map;
112   widget_class->map_event = moz_container_wayland_map_event;
113   widget_class->destroy = moz_container_wayland_destroy;
114   widget_class->unmap = moz_container_wayland_unmap;
115   widget_class->realize = moz_container_realize;
116   widget_class->size_allocate = moz_container_wayland_size_allocate;
117 }
118 
moz_container_wayland_init(MozContainerWayland * container)119 void moz_container_wayland_init(MozContainerWayland* container) {
120   container->surface = nullptr;
121   container->subsurface = nullptr;
122   container->eglwindow = nullptr;
123   container->frame_callback_handler = nullptr;
124   container->frame_callback_handler_surface_id = -1;
125   container->ready_to_draw = false;
126   container->opaque_region_needs_update = false;
127   container->opaque_region_subtract_corners = false;
128   container->opaque_region_fullscreen = false;
129   container->surface_needs_clear = true;
130   container->subsurface_dx = 0;
131   container->subsurface_dy = 0;
132   container->surface_position_needs_update = 0;
133   container->initial_draw_cbs.clear();
134   container->container_lock = new mozilla::Mutex("MozContainer lock");
135 }
136 
moz_container_wayland_destroy(GtkWidget * widget)137 static void moz_container_wayland_destroy(GtkWidget* widget) {
138   MozContainerWayland* container = &MOZ_CONTAINER(widget)->wl_container;
139   delete container->container_lock;
140   container->container_lock = nullptr;
141 }
142 
moz_container_wayland_add_initial_draw_callback(MozContainer * container,const std::function<void (void)> & initial_draw_cb)143 void moz_container_wayland_add_initial_draw_callback(
144     MozContainer* container, const std::function<void(void)>& initial_draw_cb) {
145   container->wl_container.initial_draw_cbs.push_back(initial_draw_cb);
146 }
147 
moz_gtk_widget_get_wl_surface(GtkWidget * aWidget)148 wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget) {
149   static auto sGdkWaylandWindowGetWlSurface = (wl_surface * (*)(GdkWindow*))
150       dlsym(RTLD_DEFAULT, "gdk_wayland_window_get_wl_surface");
151 
152   GdkWindow* window = gtk_widget_get_window(aWidget);
153   wl_surface* surface = sGdkWaylandWindowGetWlSurface(window);
154 
155   LOGWAYLAND(("moz_gtk_widget_get_wl_surface [%p] wl_surface %p ID %d\n",
156               (void*)aWidget, (void*)surface,
157               surface ? wl_proxy_get_id((struct wl_proxy*)surface) : -1));
158 
159   return surface;
160 }
161 
moz_container_wayland_frame_callback_handler(void * data,struct wl_callback * callback,uint32_t time)162 static void moz_container_wayland_frame_callback_handler(
163     void* data, struct wl_callback* callback, uint32_t time) {
164   MozContainerWayland* wl_container = &MOZ_CONTAINER(data)->wl_container;
165 
166   LOGWAYLAND(
167       ("%s [%p] frame_callback_handler %p ready_to_draw %d (set to true)"
168        " initial_draw callback %zd\n",
169        __FUNCTION__, (void*)MOZ_CONTAINER(data),
170        (void*)wl_container->frame_callback_handler, wl_container->ready_to_draw,
171        wl_container->initial_draw_cbs.size()));
172 
173   g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
174   wl_container->frame_callback_handler_surface_id = -1;
175 
176   if (!wl_container->ready_to_draw) {
177     wl_container->ready_to_draw = true;
178     for (auto const& cb : wl_container->initial_draw_cbs) {
179       cb();
180     }
181     wl_container->initial_draw_cbs.clear();
182   }
183 }
184 
185 static const struct wl_callback_listener moz_container_frame_listener = {
186     moz_container_wayland_frame_callback_handler};
187 
moz_container_wayland_request_parent_frame_callback(MozContainer * container)188 static void moz_container_wayland_request_parent_frame_callback(
189     MozContainer* container) {
190   MozContainerWayland* wl_container = &container->wl_container;
191 
192   wl_surface* gtk_container_surface =
193       moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
194   int gtk_container_surface_id =
195       gtk_container_surface
196           ? wl_proxy_get_id((struct wl_proxy*)gtk_container_surface)
197           : -1;
198 
199   LOGWAYLAND(
200       ("%s [%p] frame_callback_handler %p "
201        "frame_callback_handler_surface_id %d\n",
202        __FUNCTION__, (void*)container, wl_container->frame_callback_handler,
203        wl_container->frame_callback_handler_surface_id));
204 
205   if (wl_container->frame_callback_handler &&
206       wl_container->frame_callback_handler_surface_id ==
207           gtk_container_surface_id) {
208     return;
209   }
210 
211   // If there's pending frame callback, delete it.
212   if (wl_container->frame_callback_handler) {
213     g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
214   }
215 
216   if (gtk_container_surface) {
217     wl_container->frame_callback_handler_surface_id = gtk_container_surface_id;
218     wl_container->frame_callback_handler =
219         wl_surface_frame(gtk_container_surface);
220     wl_callback_add_listener(wl_container->frame_callback_handler,
221                              &moz_container_frame_listener, container);
222   } else {
223     wl_container->frame_callback_handler_surface_id = -1;
224   }
225 }
226 
moz_container_wayland_map_event(GtkWidget * widget,GdkEventAny * event)227 static gboolean moz_container_wayland_map_event(GtkWidget* widget,
228                                                 GdkEventAny* event) {
229   MozContainerWayland* wl_container = &MOZ_CONTAINER(widget)->wl_container;
230 
231   LOGWAYLAND(("%s begin [%p] ready_to_draw %d\n", __FUNCTION__,
232               (void*)MOZ_CONTAINER(widget), wl_container->ready_to_draw));
233 
234   if (wl_container->ready_to_draw) {
235     return FALSE;
236   }
237 
238   moz_container_wayland_request_parent_frame_callback(MOZ_CONTAINER(widget));
239   return FALSE;
240 }
241 
moz_container_wayland_unmap_internal(MozContainer * container)242 static void moz_container_wayland_unmap_internal(MozContainer* container) {
243   MozContainerWayland* wl_container = &container->wl_container;
244   MutexAutoLock lock(*wl_container->container_lock);
245 
246   g_clear_pointer(&wl_container->eglwindow, wl_egl_window_destroy);
247   g_clear_pointer(&wl_container->subsurface, wl_subsurface_destroy);
248   g_clear_pointer(&wl_container->surface, wl_surface_destroy);
249   g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
250   wl_container->frame_callback_handler_surface_id = -1;
251 
252   wl_container->surface_needs_clear = true;
253   wl_container->ready_to_draw = false;
254 
255   LOGWAYLAND(("%s [%p]\n", __FUNCTION__, (void*)container));
256 }
257 
moz_container_wayland_map(GtkWidget * widget)258 void moz_container_wayland_map(GtkWidget* widget) {
259   g_return_if_fail(IS_MOZ_CONTAINER(widget));
260   gtk_widget_set_mapped(widget, TRUE);
261 
262   if (gtk_widget_get_has_window(widget)) {
263     gdk_window_show(gtk_widget_get_window(widget));
264     moz_container_wayland_map_event(widget, nullptr);
265   }
266 }
267 
moz_container_wayland_unmap(GtkWidget * widget)268 void moz_container_wayland_unmap(GtkWidget* widget) {
269   g_return_if_fail(IS_MOZ_CONTAINER(widget));
270 
271   gtk_widget_set_mapped(widget, FALSE);
272 
273   if (gtk_widget_get_has_window(widget)) {
274     gdk_window_hide(gtk_widget_get_window(widget));
275     moz_container_wayland_unmap_internal(MOZ_CONTAINER(widget));
276   }
277 }
278 
moz_container_wayland_size_allocate(GtkWidget * widget,GtkAllocation * allocation)279 void moz_container_wayland_size_allocate(GtkWidget* widget,
280                                          GtkAllocation* allocation) {
281   MozContainer* container;
282   GtkAllocation tmp_allocation;
283 
284   g_return_if_fail(IS_MOZ_CONTAINER(widget));
285 
286   LOGWAYLAND(("moz_container_wayland_size_allocate [%p] %d,%d -> %d x %d\n",
287               (void*)widget, allocation->x, allocation->y, allocation->width,
288               allocation->height));
289 
290   /* short circuit if you can */
291   container = MOZ_CONTAINER(widget);
292   gtk_widget_get_allocation(widget, &tmp_allocation);
293   if (!container->children && tmp_allocation.x == allocation->x &&
294       tmp_allocation.y == allocation->y &&
295       tmp_allocation.width == allocation->width &&
296       tmp_allocation.height == allocation->height) {
297     return;
298   }
299 
300   gtk_widget_set_allocation(widget, allocation);
301 
302   if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
303     gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
304                            allocation->y, allocation->width,
305                            allocation->height);
306     // We need to position our subsurface according to GdkWindow
307     // when offset changes (GdkWindow is maximized for instance).
308     // see gtk-clutter-embed.c for reference.
309     if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
310       moz_container_wayland_move(MOZ_CONTAINER(widget), allocation->x,
311                                  allocation->y);
312     }
313   }
314 }
315 
moz_container_wayland_set_opaque_region_locked(MozContainer * container)316 static void moz_container_wayland_set_opaque_region_locked(
317     MozContainer* container) {
318   MozContainerWayland* wl_container = &container->wl_container;
319 
320   if (!wl_container->opaque_region_needs_update || !wl_container->surface) {
321     return;
322   }
323 
324   GtkAllocation allocation;
325   gtk_widget_get_allocation(GTK_WIDGET(container), &allocation);
326 
327   // Set region to mozcontainer for normal state only
328   if (!wl_container->opaque_region_fullscreen) {
329     wl_region* region =
330         CreateOpaqueRegionWayland(0, 0, allocation.width, allocation.height,
331                                   wl_container->opaque_region_subtract_corners);
332     wl_surface_set_opaque_region(wl_container->surface, region);
333     wl_region_destroy(region);
334   } else {
335     wl_surface_set_opaque_region(wl_container->surface, nullptr);
336   }
337 
338   wl_container->opaque_region_needs_update = false;
339 }
340 
moz_container_wayland_set_opaque_region(MozContainer * container)341 static void moz_container_wayland_set_opaque_region(MozContainer* container) {
342   MutexAutoLock lock(*container->wl_container.container_lock);
343   moz_container_wayland_set_opaque_region_locked(container);
344 }
345 
moz_gtk_widget_get_scale_factor(MozContainer * container)346 static int moz_gtk_widget_get_scale_factor(MozContainer* container) {
347   static auto sGtkWidgetGetScaleFactor =
348       (gint(*)(GtkWidget*))dlsym(RTLD_DEFAULT, "gtk_widget_get_scale_factor");
349   return sGtkWidgetGetScaleFactor
350              ? sGtkWidgetGetScaleFactor(GTK_WIDGET(container))
351              : 1;
352 }
353 
moz_container_wayland_set_scale_factor_locked(MozContainer * container)354 static void moz_container_wayland_set_scale_factor_locked(
355     MozContainer* container) {
356   if (!container->wl_container.surface) {
357     return;
358   }
359   wl_surface_set_buffer_scale(container->wl_container.surface,
360                               moz_gtk_widget_get_scale_factor(container));
361 }
362 
moz_container_wayland_set_scale_factor(MozContainer * container)363 void moz_container_wayland_set_scale_factor(MozContainer* container) {
364   MutexAutoLock lock(*container->wl_container.container_lock);
365   moz_container_wayland_set_scale_factor_locked(container);
366 }
367 
moz_container_wayland_get_surface_locked(MozContainer * container,nsWaylandDisplay * aWaylandDisplay)368 static struct wl_surface* moz_container_wayland_get_surface_locked(
369     MozContainer* container, nsWaylandDisplay* aWaylandDisplay) {
370   MozContainerWayland* wl_container = &container->wl_container;
371 
372   LOGWAYLAND(("%s [%p] surface %p ready_to_draw %d\n", __FUNCTION__,
373               (void*)container, (void*)wl_container->surface,
374               wl_container->ready_to_draw));
375 
376   if (!wl_container->surface) {
377     if (!wl_container->ready_to_draw) {
378       moz_container_wayland_request_parent_frame_callback(container);
379       return nullptr;
380     }
381     wl_surface* parent_surface =
382         moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
383     if (!parent_surface) {
384       return nullptr;
385     }
386 
387     // Available as of GTK 3.8+
388     struct wl_compositor* compositor = aWaylandDisplay->GetCompositor();
389     wl_container->surface = wl_compositor_create_surface(compositor);
390     if (!wl_container->surface) {
391       return nullptr;
392     }
393 
394     wl_container->subsurface =
395         wl_subcompositor_get_subsurface(aWaylandDisplay->GetSubcompositor(),
396                                         wl_container->surface, parent_surface);
397     if (!wl_container->subsurface) {
398       g_clear_pointer(&wl_container->surface, wl_surface_destroy);
399       return nullptr;
400     }
401 
402     GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
403     gint x, y;
404     gdk_window_get_position(window, &x, &y);
405     moz_container_wayland_move_locked(container, x, y);
406     wl_subsurface_set_desync(wl_container->subsurface);
407 
408     // Route input to parent wl_surface owned by Gtk+ so we get input
409     // events from Gtk+.
410     wl_region* region = wl_compositor_create_region(compositor);
411     wl_surface_set_input_region(wl_container->surface, region);
412     wl_region_destroy(region);
413 
414     wl_surface_commit(wl_container->surface);
415     wl_display_flush(aWaylandDisplay->GetDisplay());
416 
417     LOGWAYLAND(("%s [%p] created surface %p\n", __FUNCTION__, (void*)container,
418                 (void*)wl_container->surface));
419   }
420 
421   if (wl_container->surface_position_needs_update) {
422     moz_container_wayland_move_locked(container, wl_container->subsurface_dx,
423                                       wl_container->subsurface_dy);
424   }
425 
426   moz_container_wayland_set_opaque_region_locked(container);
427   moz_container_wayland_set_scale_factor_locked(container);
428 
429   return wl_container->surface;
430 }
431 
moz_container_wayland_get_surface(MozContainer * container)432 struct wl_surface* moz_container_wayland_get_surface(MozContainer* container) {
433   GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
434   nsWaylandDisplay* waylandDisplay = WaylandDisplayGet(display);
435 
436   LOGWAYLAND(("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
437               (void*)container->wl_container.surface));
438 
439   MutexAutoLock lock(*container->wl_container.container_lock);
440   return moz_container_wayland_get_surface_locked(container, waylandDisplay);
441 }
442 
moz_container_wayland_get_egl_window(MozContainer * container,int scale)443 struct wl_egl_window* moz_container_wayland_get_egl_window(
444     MozContainer* container, int scale) {
445   GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
446   nsWaylandDisplay* waylandDisplay = WaylandDisplayGet(display);
447   MozContainerWayland* wl_container = &container->wl_container;
448 
449   LOGWAYLAND(("%s [%p] eglwindow %p\n", __FUNCTION__, (void*)container,
450               (void*)wl_container->eglwindow));
451 
452   MutexAutoLock lock(*wl_container->container_lock);
453 
454   // Always call moz_container_get_wl_surface() to ensure underlying
455   // container->surface has correct scale and position.
456   wl_surface* surface =
457       moz_container_wayland_get_surface_locked(container, waylandDisplay);
458   if (!surface) {
459     return nullptr;
460   }
461   if (!wl_container->eglwindow) {
462     GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
463     wl_container->eglwindow =
464         wl_egl_window_create(surface, gdk_window_get_width(window) * scale,
465                              gdk_window_get_height(window) * scale);
466 
467     LOGWAYLAND(("%s [%p] created eglwindow %p\n", __FUNCTION__,
468                 (void*)container, (void*)wl_container->eglwindow));
469   }
470 
471   return wl_container->eglwindow;
472 }
473 
moz_container_wayland_has_egl_window(MozContainer * container)474 gboolean moz_container_wayland_has_egl_window(MozContainer* container) {
475   return container->wl_container.eglwindow ? true : false;
476 }
477 
moz_container_wayland_surface_needs_clear(MozContainer * container)478 gboolean moz_container_wayland_surface_needs_clear(MozContainer* container) {
479   int ret = container->wl_container.surface_needs_clear;
480   container->wl_container.surface_needs_clear = false;
481   return ret;
482 }
483 
moz_container_wayland_update_opaque_region(MozContainer * container,bool aSubtractCorners,bool aFullScreen)484 void moz_container_wayland_update_opaque_region(MozContainer* container,
485                                                 bool aSubtractCorners,
486                                                 bool aFullScreen) {
487   MozContainerWayland* wl_container = &container->wl_container;
488   wl_container->opaque_region_needs_update = true;
489   wl_container->opaque_region_subtract_corners = aSubtractCorners;
490   wl_container->opaque_region_fullscreen = aFullScreen;
491 
492   // When GL compositor / WebRender is used,
493   // moz_container_wayland_get_egl_window() is called only once when window
494   // is created or resized so update opaque region now.
495   if (moz_container_wayland_has_egl_window(container)) {
496     moz_container_wayland_set_opaque_region(container);
497   }
498 }
499 
moz_container_wayland_can_draw(MozContainer * container)500 gboolean moz_container_wayland_can_draw(MozContainer* container) {
501   return container ? container->wl_container.ready_to_draw : false;
502 }
503