1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 #include <gtk/gtk.h>
10 #ifdef MOZ_WAYLAND
11 #include <gdk/gdkx.h>
12 #include <gdk/gdkwayland.h>
13 #endif
14 #include <stdio.h>
15 #include <dlfcn.h>
16 
17 #ifdef ACCESSIBILITY
18 #include <atk/atk.h>
19 #include "maiRedundantObjectFactory.h"
20 #endif
21 
22 /* init methods */
23 static void moz_container_class_init(MozContainerClass *klass);
24 static void moz_container_init(MozContainer *container);
25 
26 /* widget class methods */
27 static void moz_container_map(GtkWidget *widget);
28 static void moz_container_unmap(GtkWidget *widget);
29 static void moz_container_realize(GtkWidget *widget);
30 static void moz_container_size_allocate(GtkWidget *widget,
31                                         GtkAllocation *allocation);
32 
33 /* container class methods */
34 static void moz_container_remove(GtkContainer *container,
35                                  GtkWidget *child_widget);
36 static void moz_container_forall(GtkContainer *container,
37                                  gboolean include_internals,
38                                  GtkCallback callback, gpointer callback_data);
39 static void moz_container_add(GtkContainer *container, GtkWidget *widget);
40 
41 typedef struct _MozContainerChild MozContainerChild;
42 
43 struct _MozContainerChild {
44   GtkWidget *widget;
45   gint x;
46   gint y;
47 };
48 
49 static void moz_container_allocate_child(MozContainer *container,
50                                          MozContainerChild *child);
51 static MozContainerChild *moz_container_get_child(MozContainer *container,
52                                                   GtkWidget *child);
53 
54 /* public methods */
55 
moz_container_get_type(void)56 GType moz_container_get_type(void) {
57   static GType moz_container_type = 0;
58 
59   if (!moz_container_type) {
60     static GTypeInfo moz_container_info = {
61         sizeof(MozContainerClass),                /* class_size */
62         NULL,                                     /* base_init */
63         NULL,                                     /* base_finalize */
64         (GClassInitFunc)moz_container_class_init, /* class_init */
65         NULL,                                     /* class_destroy */
66         NULL,                                     /* class_data */
67         sizeof(MozContainer),                     /* instance_size */
68         0,                                        /* n_preallocs */
69         (GInstanceInitFunc)moz_container_init,    /* instance_init */
70         NULL,                                     /* value_table */
71     };
72 
73     moz_container_type =
74         g_type_register_static(GTK_TYPE_CONTAINER, "MozContainer",
75                                &moz_container_info, static_cast<GTypeFlags>(0));
76 #ifdef ACCESSIBILITY
77     /* Set a factory to return accessible object with ROLE_REDUNDANT for
78      * MozContainer, so that gail won't send focus notification for it */
79     atk_registry_set_factory_type(atk_get_default_registry(),
80                                   moz_container_type,
81                                   mai_redundant_object_factory_get_type());
82 #endif
83   }
84 
85   return moz_container_type;
86 }
87 
moz_container_new(void)88 GtkWidget *moz_container_new(void) {
89   MozContainer *container;
90 
91   container =
92       static_cast<MozContainer *>(g_object_new(MOZ_CONTAINER_TYPE, nullptr));
93 
94   return GTK_WIDGET(container);
95 }
96 
moz_container_put(MozContainer * container,GtkWidget * child_widget,gint x,gint y)97 void moz_container_put(MozContainer *container, GtkWidget *child_widget, gint x,
98                        gint y) {
99   MozContainerChild *child;
100 
101   child = g_new(MozContainerChild, 1);
102 
103   child->widget = child_widget;
104   child->x = x;
105   child->y = y;
106 
107   /*  printf("moz_container_put %p %p %d %d\n", (void *)container,
108       (void *)child_widget, x, y); */
109 
110   container->children = g_list_append(container->children, child);
111 
112   /* we assume that the caller of this function will have already set
113      the parent GdkWindow because we can have many anonymous children. */
114   gtk_widget_set_parent(child_widget, GTK_WIDGET(container));
115 }
116 
moz_container_move(MozContainer * container,GtkWidget * child_widget,gint x,gint y,gint width,gint height)117 void moz_container_move(MozContainer *container, GtkWidget *child_widget,
118                         gint x, gint y, gint width, gint height) {
119   MozContainerChild *child;
120   GtkAllocation new_allocation;
121 
122   child = moz_container_get_child(container, child_widget);
123 
124   child->x = x;
125   child->y = y;
126 
127   new_allocation.x = x;
128   new_allocation.y = y;
129   new_allocation.width = width;
130   new_allocation.height = height;
131 
132   /* printf("moz_container_move %p %p will allocate to %d %d %d %d\n",
133      (void *)container, (void *)child_widget,
134      new_allocation.x, new_allocation.y,
135      new_allocation.width, new_allocation.height); */
136 
137   gtk_widget_size_allocate(child_widget, &new_allocation);
138 }
139 
140 /* static methods */
141 
moz_container_class_init(MozContainerClass * klass)142 void moz_container_class_init(MozContainerClass *klass) {
143   /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
144     GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
145   GtkContainerClass *container_class = GTK_CONTAINER_CLASS(klass);
146   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
147 
148   widget_class->map = moz_container_map;
149   widget_class->unmap = moz_container_unmap;
150   widget_class->realize = moz_container_realize;
151   widget_class->size_allocate = moz_container_size_allocate;
152 
153   container_class->remove = moz_container_remove;
154   container_class->forall = moz_container_forall;
155   container_class->add = moz_container_add;
156 }
157 
158 #if defined(MOZ_WAYLAND)
registry_handle_global(void * data,struct wl_registry * registry,uint32_t name,const char * interface,uint32_t version)159 static void registry_handle_global(void *data, struct wl_registry *registry,
160                                    uint32_t name, const char *interface,
161                                    uint32_t version) {
162   MozContainer *container = MOZ_CONTAINER(data);
163   if (strcmp(interface, "wl_subcompositor") == 0) {
164     container->subcompositor = static_cast<wl_subcompositor *>(
165         wl_registry_bind(registry, name, &wl_subcompositor_interface, 1));
166   }
167 }
168 
registry_handle_global_remove(void * data,struct wl_registry * registry,uint32_t name)169 static void registry_handle_global_remove(void *data,
170                                           struct wl_registry *registry,
171                                           uint32_t name) {}
172 
173 static const struct wl_registry_listener registry_listener = {
174     registry_handle_global, registry_handle_global_remove};
175 #endif
176 
moz_container_init(MozContainer * container)177 void moz_container_init(MozContainer *container) {
178   gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE);
179   gtk_container_set_resize_mode(GTK_CONTAINER(container), GTK_RESIZE_IMMEDIATE);
180   gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE);
181 
182 #if defined(MOZ_WAYLAND)
183   {
184     GdkDisplay *gdk_display = gtk_widget_get_display(GTK_WIDGET(container));
185     if (GDK_IS_WAYLAND_DISPLAY(gdk_display)) {
186       // Available as of GTK 3.8+
187       static auto sGdkWaylandDisplayGetWlDisplay =
188           (wl_display * (*)(GdkDisplay *))
189               dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_wl_display");
190 
191       wl_display *display = sGdkWaylandDisplayGetWlDisplay(gdk_display);
192       wl_registry *registry = wl_display_get_registry(display);
193       wl_registry_add_listener(registry, &registry_listener, container);
194       wl_display_dispatch(display);
195       wl_display_roundtrip(display);
196     }
197   }
198 #endif
199 }
200 
201 #if defined(MOZ_WAYLAND)
202 /* We want to draw to GdkWindow owned by mContainer from Compositor thread but
203  * Gtk+ can be used in main thread only. So we create wayland wl_surface
204  * and attach it as an overlay to GdkWindow.
205  *
206  * see gtk_clutter_embed_ensure_subsurface() at gtk-clutter-embed.c
207  *  for reference.
208  */
moz_container_map_surface(MozContainer * container)209 static gboolean moz_container_map_surface(MozContainer *container) {
210   // Available as of GTK 3.8+
211   static auto sGdkWaylandDisplayGetWlCompositor =
212       (wl_compositor * (*)(GdkDisplay *))
213           dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_wl_compositor");
214   static auto sGdkWaylandWindowGetWlSurface = (wl_surface * (*)(GdkWindow *))
215       dlsym(RTLD_DEFAULT, "gdk_wayland_window_get_wl_surface");
216 
217   GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(container));
218   if (GDK_IS_X11_DISPLAY(display)) return false;
219 
220   if (container->subsurface && container->surface) return true;
221 
222   if (!container->surface) {
223     struct wl_compositor *compositor;
224     compositor = sGdkWaylandDisplayGetWlCompositor(display);
225     container->surface = wl_compositor_create_surface(compositor);
226   }
227 
228   if (!container->subsurface) {
229     GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(container));
230     wl_surface *gtk_surface = sGdkWaylandWindowGetWlSurface(window);
231     if (!gtk_surface) {
232       // We requested the underlying wl_surface too early when container
233       // is not realized yet. We'll try again before first rendering
234       // to mContainer.
235       return false;
236     }
237 
238     container->subsurface = wl_subcompositor_get_subsurface(
239         container->subcompositor, container->surface, gtk_surface);
240     gint x, y;
241     gdk_window_get_position(window, &x, &y);
242     wl_subsurface_set_position(container->subsurface, x, y);
243     wl_subsurface_set_desync(container->subsurface);
244 
245     // Route input to parent wl_surface owned by Gtk+ so we get input
246     // events from Gtk+.
247     GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(container));
248     wl_compositor *compositor = sGdkWaylandDisplayGetWlCompositor(display);
249     wl_region *region = wl_compositor_create_region(compositor);
250     wl_surface_set_input_region(container->surface, region);
251     wl_region_destroy(region);
252   }
253   return true;
254 }
255 
moz_container_unmap_surface(MozContainer * container)256 static void moz_container_unmap_surface(MozContainer *container) {
257   g_clear_pointer(&container->subsurface, wl_subsurface_destroy);
258   g_clear_pointer(&container->surface, wl_surface_destroy);
259 }
260 
261 #endif
262 
moz_container_map(GtkWidget * widget)263 void moz_container_map(GtkWidget *widget) {
264   MozContainer *container;
265   GList *tmp_list;
266   GtkWidget *tmp_child;
267 
268   g_return_if_fail(IS_MOZ_CONTAINER(widget));
269   container = MOZ_CONTAINER(widget);
270 
271   gtk_widget_set_mapped(widget, TRUE);
272 
273   tmp_list = container->children;
274   while (tmp_list) {
275     tmp_child = ((MozContainerChild *)tmp_list->data)->widget;
276 
277     if (gtk_widget_get_visible(tmp_child)) {
278       if (!gtk_widget_get_mapped(tmp_child)) gtk_widget_map(tmp_child);
279     }
280     tmp_list = tmp_list->next;
281   }
282 
283   if (gtk_widget_get_has_window(widget)) {
284     gdk_window_show(gtk_widget_get_window(widget));
285 #if defined(MOZ_WAYLAND)
286     moz_container_map_surface(MOZ_CONTAINER(widget));
287 #endif
288   }
289 }
290 
moz_container_unmap(GtkWidget * widget)291 void moz_container_unmap(GtkWidget *widget) {
292   g_return_if_fail(IS_MOZ_CONTAINER(widget));
293 
294   gtk_widget_set_mapped(widget, FALSE);
295 
296   if (gtk_widget_get_has_window(widget)) {
297     gdk_window_hide(gtk_widget_get_window(widget));
298 #if defined(MOZ_WAYLAND)
299     moz_container_unmap_surface(MOZ_CONTAINER(widget));
300 #endif
301   }
302 }
303 
moz_container_realize(GtkWidget * widget)304 void moz_container_realize(GtkWidget *widget) {
305   GdkWindow *parent = gtk_widget_get_parent_window(widget);
306   GdkWindow *window;
307 
308   gtk_widget_set_realized(widget, TRUE);
309 
310   if (gtk_widget_get_has_window(widget)) {
311     GdkWindowAttr attributes;
312     gint attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y;
313     GtkAllocation allocation;
314 
315     gtk_widget_get_allocation(widget, &allocation);
316     attributes.event_mask = gtk_widget_get_events(widget);
317     attributes.x = allocation.x;
318     attributes.y = allocation.y;
319     attributes.width = allocation.width;
320     attributes.height = allocation.height;
321     attributes.wclass = GDK_INPUT_OUTPUT;
322     attributes.visual = gtk_widget_get_visual(widget);
323     attributes.window_type = GDK_WINDOW_CHILD;
324 
325     window = gdk_window_new(parent, &attributes, attributes_mask);
326     gdk_window_set_user_data(window, widget);
327   } else {
328     window = parent;
329     g_object_ref(window);
330   }
331 
332   gtk_widget_set_window(widget, window);
333 }
334 
moz_container_size_allocate(GtkWidget * widget,GtkAllocation * allocation)335 void moz_container_size_allocate(GtkWidget *widget, GtkAllocation *allocation) {
336   MozContainer *container;
337   GList *tmp_list;
338   GtkAllocation tmp_allocation;
339 
340   g_return_if_fail(IS_MOZ_CONTAINER(widget));
341 
342   /*  printf("moz_container_size_allocate %p %d %d %d %d\n",
343       (void *)widget,
344       allocation->x,
345       allocation->y,
346       allocation->width,
347       allocation->height); */
348 
349   /* short circuit if you can */
350   container = MOZ_CONTAINER(widget);
351   gtk_widget_get_allocation(widget, &tmp_allocation);
352   if (!container->children && tmp_allocation.x == allocation->x &&
353       tmp_allocation.y == allocation->y &&
354       tmp_allocation.width == allocation->width &&
355       tmp_allocation.height == allocation->height) {
356     return;
357   }
358 
359   gtk_widget_set_allocation(widget, allocation);
360 
361   tmp_list = container->children;
362 
363   while (tmp_list) {
364     MozContainerChild *child = static_cast<MozContainerChild *>(tmp_list->data);
365 
366     moz_container_allocate_child(container, child);
367 
368     tmp_list = tmp_list->next;
369   }
370 
371   if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
372     gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
373                            allocation->y, allocation->width,
374                            allocation->height);
375   }
376 
377 #if defined(MOZ_WAYLAND)
378   // We need to position our subsurface according to GdkWindow
379   // when offset changes (GdkWindow is maximized for instance).
380   // see gtk-clutter-embed.c for reference.
381   if (container->subsurface) {
382     gint x, y;
383     gdk_window_get_position(gtk_widget_get_window(widget), &x, &y);
384     wl_subsurface_set_position(container->subsurface, x, y);
385   }
386 #endif
387 }
388 
moz_container_remove(GtkContainer * container,GtkWidget * child_widget)389 void moz_container_remove(GtkContainer *container, GtkWidget *child_widget) {
390   MozContainerChild *child;
391   MozContainer *moz_container;
392   GdkWindow *parent_window;
393 
394   g_return_if_fail(IS_MOZ_CONTAINER(container));
395   g_return_if_fail(GTK_IS_WIDGET(child_widget));
396 
397   moz_container = MOZ_CONTAINER(container);
398 
399   child = moz_container_get_child(moz_container, child_widget);
400   g_return_if_fail(child);
401 
402   /* gtk_widget_unparent will remove the parent window (as well as the
403    * parent widget), but, in Mozilla's window hierarchy, the parent window
404    * may need to be kept because it may be part of a GdkWindow sub-hierarchy
405    * that is being moved to another MozContainer.
406    *
407    * (In a conventional GtkWidget hierarchy, GdkWindows being reparented
408    * would have their own GtkWidget and that widget would be the one being
409    * reparented.  In Mozilla's hierarchy, the parent_window needs to be
410    * retained so that the GdkWindow sub-hierarchy is maintained.)
411    */
412   parent_window = gtk_widget_get_parent_window(child_widget);
413   if (parent_window) g_object_ref(parent_window);
414 
415   gtk_widget_unparent(child_widget);
416 
417   if (parent_window) {
418     /* The child_widget will always still exist because g_signal_emit,
419      * which invokes this function, holds a reference.
420      *
421      * If parent_window is the container's root window then it will not be
422      * the parent_window if the child_widget is placed in another
423      * container.
424      */
425     if (parent_window != gtk_widget_get_window(GTK_WIDGET(container)))
426       gtk_widget_set_parent_window(child_widget, parent_window);
427 
428     g_object_unref(parent_window);
429   }
430 
431   moz_container->children = g_list_remove(moz_container->children, child);
432   g_free(child);
433 }
434 
moz_container_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)435 void moz_container_forall(GtkContainer *container, gboolean include_internals,
436                           GtkCallback callback, gpointer callback_data) {
437   MozContainer *moz_container;
438   GList *tmp_list;
439 
440   g_return_if_fail(IS_MOZ_CONTAINER(container));
441   g_return_if_fail(callback != NULL);
442 
443   moz_container = MOZ_CONTAINER(container);
444 
445   tmp_list = moz_container->children;
446   while (tmp_list) {
447     MozContainerChild *child;
448     child = static_cast<MozContainerChild *>(tmp_list->data);
449     tmp_list = tmp_list->next;
450     (*callback)(child->widget, callback_data);
451   }
452 }
453 
moz_container_allocate_child(MozContainer * container,MozContainerChild * child)454 static void moz_container_allocate_child(MozContainer *container,
455                                          MozContainerChild *child) {
456   GtkAllocation allocation;
457 
458   gtk_widget_get_allocation(child->widget, &allocation);
459   allocation.x = child->x;
460   allocation.y = child->y;
461 
462   gtk_widget_size_allocate(child->widget, &allocation);
463 }
464 
moz_container_get_child(MozContainer * container,GtkWidget * child_widget)465 MozContainerChild *moz_container_get_child(MozContainer *container,
466                                            GtkWidget *child_widget) {
467   GList *tmp_list;
468 
469   tmp_list = container->children;
470   while (tmp_list) {
471     MozContainerChild *child;
472 
473     child = static_cast<MozContainerChild *>(tmp_list->data);
474     tmp_list = tmp_list->next;
475 
476     if (child->widget == child_widget) return child;
477   }
478 
479   return NULL;
480 }
481 
moz_container_add(GtkContainer * container,GtkWidget * widget)482 static void moz_container_add(GtkContainer *container, GtkWidget *widget) {
483   moz_container_put(MOZ_CONTAINER(container), widget, 0, 0);
484 }
485 
486 #ifdef MOZ_WAYLAND
moz_container_get_wl_surface(MozContainer * container)487 struct wl_surface *moz_container_get_wl_surface(MozContainer *container) {
488   if (!container->subsurface || !container->surface) {
489     GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(container));
490     if (!gdk_window_is_visible(window)) return nullptr;
491 
492     moz_container_map_surface(container);
493   }
494 
495   return container->surface;
496 }
497 #endif
498