1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* gnome-shell-perf-helper: a program to create windows for performance tests
4  *
5  * Running performance tests with whatever windows a user has open results
6  * in unreliable results, so instead we hide all other windows and talk
7  * to this program over D-Bus to create just the windows we want.
8  */
9 
10 #include "config.h"
11 
12 #include <math.h>
13 
14 #include <gtk/gtk.h>
15 
16 #define BUS_NAME "org.gnome.Shell.PerfHelper"
17 
18 static void destroy_windows           (void);
19 static void finish_wait_windows       (void);
20 static void check_finish_wait_windows (void);
21 
22 static const gchar introspection_xml[] =
23 	  "<node>"
24 	  "  <interface name='org.gnome.Shell.PerfHelper'>"
25 	  "    <method name='Exit'/>"
26 	  "    <method name='CreateWindow'>"
27 	  "      <arg type='i' name='width' direction='in'/>"
28 	  "      <arg type='i' name='height' direction='in'/>"
29 	  "      <arg type='b' name='alpha' direction='in'/>"
30 	  "      <arg type='b' name='maximized' direction='in'/>"
31 	  "      <arg type='b' name='redraws' direction='in'/>"
32 	  "    </method>"
33 	  "    <method name='WaitWindows'/>"
34 	  "    <method name='DestroyWindows'/>"
35 	  "  </interface>"
36 	"</node>";
37 
38 typedef struct {
39   GtkWidget *window;
40   int width;
41   int height;
42 
43   guint alpha : 1;
44   guint maximized : 1;
45   guint redraws : 1;
46   guint mapped : 1;
47   guint exposed : 1;
48   guint pending : 1;
49 
50   gint64 start_time;
51   gint64 time;
52 } WindowInfo;
53 
54 static int opt_idle_timeout = 30;
55 
56 static GOptionEntry opt_entries[] =
57   {
58     { "idle-timeout", 'r', 0, G_OPTION_ARG_INT, &opt_idle_timeout, "Exit after N seconds", "N" },
59     { NULL }
60   };
61 
62 static guint timeout_id;
63 static GList *our_windows;
64 static GList *wait_windows_invocations;
65 
66 static gboolean
on_timeout(gpointer data)67 on_timeout (gpointer data)
68 {
69   timeout_id = 0;
70 
71   destroy_windows ();
72   gtk_main_quit ();
73 
74   return FALSE;
75 }
76 
77 static void
establish_timeout(void)78 establish_timeout (void)
79 {
80   g_clear_handle_id (&timeout_id, g_source_remove);
81 
82   timeout_id = g_timeout_add (opt_idle_timeout * 1000, on_timeout, NULL);
83   g_source_set_name_by_id (timeout_id, "[gnome-shell] on_timeout");
84 }
85 
86 static void
destroy_windows(void)87 destroy_windows (void)
88 {
89   GList *l;
90 
91   for (l = our_windows; l; l = l->next)
92     {
93       WindowInfo *info = l->data;
94       gtk_widget_destroy (info->window);
95       g_free (info);
96     }
97 
98   g_list_free (our_windows);
99   our_windows = NULL;
100 
101   check_finish_wait_windows ();
102 }
103 
104 static gboolean
on_window_map_event(GtkWidget * window,GdkEventAny * event,WindowInfo * info)105 on_window_map_event (GtkWidget   *window,
106                      GdkEventAny *event,
107                      WindowInfo  *info)
108 {
109   info->mapped = TRUE;
110 
111   return FALSE;
112 }
113 
114 static gboolean
on_child_draw(GtkWidget * window,cairo_t * cr,WindowInfo * info)115 on_child_draw (GtkWidget  *window,
116                cairo_t    *cr,
117                WindowInfo *info)
118 {
119   cairo_rectangle_int_t allocation;
120   double x_offset, y_offset;
121 
122   gtk_widget_get_allocation (window, &allocation);
123 
124   /* We draw an arbitrary pattern of red lines near the border of the
125    * window to make it more clear than empty windows if something
126    * is drastrically wrong.
127    */
128 
129   cairo_save (cr);
130   cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
131 
132   if (info->alpha)
133     cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
134   else
135     cairo_set_source_rgb (cr, 1, 1, 1);
136 
137   cairo_paint (cr);
138   cairo_restore (cr);
139 
140   if (info->redraws)
141     {
142       double position = (info->time - info->start_time) / 1000000.;
143       x_offset = 20 * cos (2 * M_PI * position);
144       y_offset = 20 * sin (2 * M_PI * position);
145     }
146   else
147     {
148       x_offset = y_offset = 0;
149     }
150 
151   cairo_set_source_rgb (cr, 1, 0, 0);
152   cairo_set_line_width (cr, 10);
153   cairo_move_to (cr, 0, 40 + y_offset);
154   cairo_line_to (cr, allocation.width, 40 + y_offset);
155   cairo_move_to (cr, 0, allocation.height - 40 + y_offset);
156   cairo_line_to (cr, allocation.width, allocation.height - 40 + y_offset);
157   cairo_move_to (cr, 40 + x_offset, 0);
158   cairo_line_to (cr, 40 + x_offset, allocation.height);
159   cairo_move_to (cr, allocation.width - 40 + x_offset, 0);
160   cairo_line_to (cr, allocation.width - 40 + x_offset, allocation.height);
161   cairo_stroke (cr);
162 
163   info->exposed = TRUE;
164 
165   if (info->exposed && info->mapped && info->pending)
166     {
167       info->pending = FALSE;
168       check_finish_wait_windows ();
169     }
170 
171   return FALSE;
172 }
173 
174 static gboolean
tick_callback(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)175 tick_callback (GtkWidget     *widget,
176                GdkFrameClock *frame_clock,
177                gpointer       user_data)
178 {
179   WindowInfo *info = user_data;
180 
181   if (info->start_time < 0)
182     info->start_time = info->time = gdk_frame_clock_get_frame_time (frame_clock);
183   else
184     info->time = gdk_frame_clock_get_frame_time (frame_clock);
185 
186   gtk_widget_queue_draw (widget);
187 
188   return TRUE;
189 }
190 
191 static void
create_window(int width,int height,gboolean alpha,gboolean maximized,gboolean redraws)192 create_window (int      width,
193 	       int      height,
194                gboolean alpha,
195                gboolean maximized,
196                gboolean redraws)
197 {
198   WindowInfo *info;
199   GtkWidget *child;
200 
201   info = g_new0 (WindowInfo, 1);
202   info->width = width;
203   info->height = height;
204   info->alpha = alpha;
205   info->maximized = maximized;
206   info->redraws = redraws;
207   info->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
208   if (alpha)
209     gtk_widget_set_visual (info->window, gdk_screen_get_rgba_visual (gdk_screen_get_default ()));
210   if (maximized)
211     gtk_window_maximize (GTK_WINDOW (info->window));
212   info->pending = TRUE;
213   info->start_time = -1;
214 
215   child = g_object_new (GTK_TYPE_BOX, "visible", TRUE, "app-paintable", TRUE, NULL);
216   gtk_container_add (GTK_CONTAINER (info->window), child);
217 
218   gtk_widget_set_size_request (info->window, width, height);
219   gtk_widget_set_app_paintable (info->window, TRUE);
220   g_signal_connect (info->window, "map-event", G_CALLBACK (on_window_map_event), info);
221   g_signal_connect (child, "draw", G_CALLBACK (on_child_draw), info);
222   gtk_widget_show (info->window);
223 
224   if (info->redraws)
225     gtk_widget_add_tick_callback (info->window, tick_callback,
226                                   info, NULL);
227 
228   our_windows = g_list_prepend (our_windows, info);
229 }
230 
231 static void
finish_wait_windows(void)232 finish_wait_windows (void)
233 {
234   GList *l;
235 
236   for (l = wait_windows_invocations; l; l = l->next)
237     g_dbus_method_invocation_return_value (l->data, NULL);
238 
239   g_list_free (wait_windows_invocations);
240   wait_windows_invocations = NULL;
241 }
242 
243 static void
check_finish_wait_windows(void)244 check_finish_wait_windows (void)
245 {
246   GList *l;
247   gboolean have_pending = FALSE;
248 
249   for (l = our_windows; l; l = l->next)
250     {
251       WindowInfo *info = l->data;
252       if (info->pending)
253         have_pending = TRUE;
254     }
255 
256   if (!have_pending)
257     finish_wait_windows ();
258 }
259 
260 static void
handle_method_call(GDBusConnection * connection,const gchar * sender,const gchar * object_path,const gchar * interface_name,const gchar * method_name,GVariant * parameters,GDBusMethodInvocation * invocation,gpointer user_data)261 handle_method_call (GDBusConnection       *connection,
262 		    const gchar           *sender,
263 		    const gchar           *object_path,
264 		    const gchar           *interface_name,
265 		    const gchar           *method_name,
266 		    GVariant              *parameters,
267 		    GDBusMethodInvocation *invocation,
268 		    gpointer               user_data)
269 {
270   /* Push off the idle timeout */
271   establish_timeout ();
272 
273   if (g_strcmp0 (method_name, "Exit") == 0)
274     {
275       destroy_windows ();
276 
277       g_dbus_method_invocation_return_value (invocation, NULL);
278       g_dbus_connection_flush_sync (connection, NULL, NULL);
279 
280       gtk_main_quit ();
281     }
282   else if (g_strcmp0 (method_name, "CreateWindow") == 0)
283     {
284       int width, height;
285       gboolean alpha, maximized, redraws;
286 
287       g_variant_get (parameters, "(iibbb)", &width, &height, &alpha, &maximized, &redraws);
288 
289       create_window (width, height, alpha, maximized, redraws);
290       g_dbus_method_invocation_return_value (invocation, NULL);
291     }
292   else if (g_strcmp0 (method_name, "WaitWindows") == 0)
293     {
294       wait_windows_invocations = g_list_prepend (wait_windows_invocations, invocation);
295       check_finish_wait_windows ();
296     }
297   else if (g_strcmp0 (method_name, "DestroyWindows") == 0)
298     {
299       destroy_windows ();
300       g_dbus_method_invocation_return_value (invocation, NULL);
301     }
302 }
303 
304 static const GDBusInterfaceVTable interface_vtable =
305 {
306   handle_method_call,
307   NULL,
308   NULL
309 };
310 
311 static void
on_bus_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)312 on_bus_acquired (GDBusConnection *connection,
313 		 const gchar     *name,
314 		 gpointer         user_data)
315 {
316   GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
317 
318   g_dbus_connection_register_object (connection,
319 				     "/org/gnome/Shell/PerfHelper",
320 				     introspection_data->interfaces[0],
321 				     &interface_vtable,
322 				     NULL,  /* user_data */
323 				     NULL,  /* user_data_free_func */
324 				     NULL); /* GError** */
325 }
326 
327 static void
on_name_acquired(GDBusConnection * connection,const gchar * name,gpointer user_data)328 on_name_acquired (GDBusConnection *connection,
329 		  const gchar     *name,
330 		  gpointer         user_data)
331 {
332 }
333 
334 static void
on_name_lost(GDBusConnection * connection,const gchar * name,gpointer user_data)335 on_name_lost  (GDBusConnection *connection,
336 	       const gchar     *name,
337 	       gpointer         user_data)
338 {
339   destroy_windows ();
340   gtk_main_quit ();
341 }
342 
343 int
main(int argc,char ** argv)344 main (int argc, char **argv)
345 {
346   GOptionContext *context;
347   GError *error = NULL;
348 
349   /* Since we depend on this, avoid the possibility of lt-gnome-shell-perf-helper */
350   g_set_prgname ("gnome-shell-perf-helper");
351 
352   context = g_option_context_new (" - server to create windows for performance testing");
353   g_option_context_add_main_entries (context, opt_entries, NULL);
354   g_option_context_add_group (context, gtk_get_option_group (TRUE));
355   if (!g_option_context_parse (context, &argc, &argv, &error))
356     {
357       g_print ("option parsing failed: %s\n", error->message);
358       return 1;
359     }
360 
361   g_bus_own_name (G_BUS_TYPE_SESSION,
362                   BUS_NAME,
363                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
364                   G_BUS_NAME_OWNER_FLAGS_REPLACE,
365                   on_bus_acquired,
366                   on_name_acquired,
367                   on_name_lost,
368                   NULL,
369                   NULL);
370 
371   establish_timeout ();
372 
373   gtk_main ();
374 
375   return 0;
376 }
377