1 /*
2  * Copyright (C) 2011 Red Hat Inc.
3  *
4  * Author:
5  *      Benjamin Otte <otte@gnome.org>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include "reftest-snapshot.h"
24 
25 #include "reftest-module.h"
26 #include "gtk-reftest.h"
27 
28 #include <string.h>
29 
30 typedef enum {
31   SNAPSHOT_WINDOW,
32   SNAPSHOT_DRAW
33 } SnapshotMode;
34 
35 static GtkWidget *
builder_get_toplevel(GtkBuilder * builder)36 builder_get_toplevel (GtkBuilder *builder)
37 {
38   GSList *list, *walk;
39   GtkWidget *window = NULL;
40 
41   list = gtk_builder_get_objects (builder);
42   for (walk = list; walk; walk = walk->next)
43     {
44       if (GTK_IS_WINDOW (walk->data) &&
45           gtk_widget_get_parent (walk->data) == NULL)
46         {
47           window = walk->data;
48           break;
49         }
50     }
51 
52   g_slist_free (list);
53 
54   return window;
55 }
56 
57 static gboolean
quit_when_idle(gpointer loop)58 quit_when_idle (gpointer loop)
59 {
60   g_main_loop_quit (loop);
61 
62   return G_SOURCE_REMOVE;
63 }
64 
65 static gint inhibit_count;
66 static GMainLoop *loop;
67 
68 void
reftest_inhibit_snapshot(void)69 reftest_inhibit_snapshot (void)
70 {
71   inhibit_count++;
72 }
73 
74 void
reftest_uninhibit_snapshot(void)75 reftest_uninhibit_snapshot (void)
76 {
77   g_assert (inhibit_count > 0);
78   inhibit_count--;
79 
80   if (inhibit_count == 0)
81     g_idle_add (quit_when_idle, loop);
82 }
83 
84 static void
check_for_draw(GdkEvent * event,gpointer data)85 check_for_draw (GdkEvent *event, gpointer data)
86 {
87   if (event->type == GDK_EXPOSE)
88     {
89       reftest_uninhibit_snapshot ();
90       gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL);
91     }
92 
93   gtk_main_do_event (event);
94 }
95 
96 static cairo_surface_t *
snapshot_widget(GtkWidget * widget,SnapshotMode mode)97 snapshot_widget (GtkWidget *widget, SnapshotMode mode)
98 {
99   cairo_surface_t *surface;
100   cairo_pattern_t *bg;
101   cairo_t *cr;
102 
103   g_assert (gtk_widget_get_realized (widget));
104 
105   loop = g_main_loop_new (NULL, FALSE);
106 
107   /* We wait until the widget is drawn for the first time.
108    * We can not wait for a GtkWidget::draw event, because that might not
109    * happen if the window is fully obscured by windowed child widgets.
110    * Alternatively, we could wait for an expose event on widget's window.
111    * Both of these are rather hairy, not sure what's best.
112    *
113    * We also use an inhibit mechanism, to give module functions a chance
114    * to delay the snapshot.
115    */
116   reftest_inhibit_snapshot ();
117   gdk_event_handler_set (check_for_draw, NULL, NULL);
118   g_main_loop_run (loop);
119 
120   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
121                                                CAIRO_CONTENT_COLOR,
122                                                gtk_widget_get_allocated_width (widget),
123                                                gtk_widget_get_allocated_height (widget));
124 
125   cr = cairo_create (surface);
126 
127   switch (mode)
128     {
129     case SNAPSHOT_WINDOW:
130       {
131         GdkWindow *window = gtk_widget_get_window (widget);
132         if (gdk_window_get_window_type (window) == GDK_WINDOW_TOPLEVEL ||
133             gdk_window_get_window_type (window) == GDK_WINDOW_FOREIGN)
134           {
135             /* give the WM/server some time to sync. They need it.
136              * Also, do use popups instead of toplevels in your tests
137              * whenever you can.
138              */
139             gdk_display_sync (gdk_window_get_display (window));
140             g_timeout_add (500, quit_when_idle, loop);
141             g_main_loop_run (loop);
142           }
143         gdk_cairo_set_source_window (cr, window, 0, 0);
144         cairo_paint (cr);
145       }
146       break;
147     case SNAPSHOT_DRAW:
148       bg = gdk_window_get_background_pattern (gtk_widget_get_window (widget));
149       if (bg)
150         {
151           cairo_set_source (cr, bg);
152           cairo_paint (cr);
153         }
154       gtk_widget_draw (widget, cr);
155       break;
156     default:
157       g_assert_not_reached();
158       break;
159     }
160 
161   cairo_destroy (cr);
162   g_main_loop_unref (loop);
163   gtk_widget_destroy (widget);
164 
165   return surface;
166 }
167 
168 static void
connect_signals(GtkBuilder * builder,GObject * object,const gchar * signal_name,const gchar * handler_name,GObject * connect_object,GConnectFlags flags,gpointer user_data)169 connect_signals (GtkBuilder    *builder,
170                  GObject       *object,
171                  const gchar   *signal_name,
172                  const gchar   *handler_name,
173                  GObject       *connect_object,
174                  GConnectFlags  flags,
175                  gpointer       user_data)
176 {
177   ReftestModule *module;
178   const char *directory;
179   GCallback func;
180   GClosure *closure;
181   char **split;
182 
183   directory = user_data;
184   split = g_strsplit (handler_name, ":", -1);
185 
186   switch (g_strv_length (split))
187     {
188     case 1:
189       func = gtk_builder_lookup_callback_symbol (builder, split[0]);
190 
191       if (func)
192         {
193           module = NULL;
194         }
195       else
196         {
197           module = reftest_module_new_self ();
198           if (module == NULL)
199             {
200               g_error ("glib compiled without module support.");
201               return;
202             }
203           func = reftest_module_lookup (module, split[0]);
204           if (!func)
205             {
206               g_error ("failed to lookup handler for name '%s' when connecting signals", split[0]);
207               return;
208             }
209         }
210       break;
211     case 2:
212       if (g_getenv ("REFTEST_MODULE_DIR"))
213         directory = g_getenv ("REFTEST_MODULE_DIR");
214       module = reftest_module_new (directory, split[0]);
215       if (module == NULL)
216         {
217           g_error ("Could not load module '%s' from '%s' when looking up '%s'", split[0], directory, handler_name);
218           return;
219         }
220       func = reftest_module_lookup (module, split[1]);
221       if (!func)
222         {
223           g_error ("failed to lookup handler for name '%s' in module '%s'", split[1], split[0]);
224           return;
225         }
226       break;
227     default:
228       g_error ("Could not connect signal handler named '%s'", handler_name);
229       return;
230     }
231 
232   g_strfreev (split);
233 
234   if (connect_object)
235     {
236       if (flags & G_CONNECT_SWAPPED)
237         closure = g_cclosure_new_object_swap (func, connect_object);
238       else
239         closure = g_cclosure_new_object (func, connect_object);
240     }
241   else
242     {
243       if (flags & G_CONNECT_SWAPPED)
244         closure = g_cclosure_new_swap (func, NULL, NULL);
245       else
246         closure = g_cclosure_new (func, NULL, NULL);
247     }
248 
249   if (module)
250     g_closure_add_finalize_notifier (closure, module, (GClosureNotify) reftest_module_unref);
251 
252   g_signal_connect_closure (object, signal_name, closure, flags & G_CONNECT_AFTER ? TRUE : FALSE);
253 }
254 
255 cairo_surface_t *
reftest_snapshot_ui_file(const char * ui_file)256 reftest_snapshot_ui_file (const char *ui_file)
257 {
258   GtkWidget *window;
259   GtkBuilder *builder;
260   GError *error = NULL;
261   char *directory;
262 
263   directory = g_path_get_dirname (ui_file);
264 
265   builder = gtk_builder_new ();
266   gtk_builder_add_from_file (builder, ui_file, &error);
267   g_assert_no_error (error);
268   gtk_builder_connect_signals_full (builder, connect_signals, directory);
269   window = builder_get_toplevel (builder);
270   g_object_unref (builder);
271   g_free (directory);
272   g_assert (window);
273 
274   gtk_widget_show (window);
275 
276   return snapshot_widget (window, SNAPSHOT_WINDOW);
277 }
278