1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /*
4  * Copyright (C) 2014 Red Hat, Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include <gio/gunixinputstream.h>
23 #include <gtk/gtk.h>
24 #include <gdk/gdkx.h>
25 #include <gdk/gdkwayland.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <X11/extensions/sync.h>
30 
31 const char *client_id = "0";
32 static gboolean wayland;
33 GHashTable *windows;
34 GQuark event_source_quark;
35 GQuark event_handlers_quark;
36 GQuark can_take_focus_quark;
37 
38 typedef void (*XEventHandler) (GtkWidget *window, XEvent *event);
39 
40 static void read_next_line (GDataInputStream *in);
41 
42 static void
window_export_handle_cb(GdkWindow * window,const char * handle_str,gpointer user_data)43 window_export_handle_cb (GdkWindow  *window,
44                          const char *handle_str,
45                          gpointer    user_data)
46 {
47   GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (user_data));
48 
49   if (!gdk_wayland_window_set_transient_for_exported (gdk_window,
50                                                       (gchar *) handle_str))
51     g_print ("Fail to set transient_for exported window handle %s\n", handle_str);
52   gdk_window_set_modal_hint (gdk_window, TRUE);
53 }
54 
55 static GtkWidget *
lookup_window(const char * window_id)56 lookup_window (const char *window_id)
57 {
58   GtkWidget *window = g_hash_table_lookup (windows, window_id);
59   if (!window)
60     g_print ("Window %s doesn't exist\n", window_id);
61 
62   return window;
63 }
64 
65 typedef struct {
66   GSource base;
67   GSource **self_ref;
68   GPollFD event_poll_fd;
69   Display *xdisplay;
70 } XClientEventSource;
71 
72 static gboolean
x_event_source_prepare(GSource * source,int * timeout)73 x_event_source_prepare (GSource *source,
74                         int     *timeout)
75 {
76   XClientEventSource *x_source = (XClientEventSource *) source;
77 
78   *timeout = -1;
79 
80   return XPending (x_source->xdisplay);
81 }
82 
83 static gboolean
x_event_source_check(GSource * source)84 x_event_source_check (GSource *source)
85 {
86   XClientEventSource *x_source = (XClientEventSource *) source;
87 
88   return XPending (x_source->xdisplay);
89 }
90 
91 static gboolean
x_event_source_dispatch(GSource * source,GSourceFunc callback,gpointer user_data)92 x_event_source_dispatch (GSource     *source,
93                          GSourceFunc  callback,
94                          gpointer     user_data)
95 {
96   XClientEventSource *x_source = (XClientEventSource *) source;
97 
98   while (XPending (x_source->xdisplay))
99     {
100       GHashTableIter iter;
101       XEvent event;
102       gpointer value;
103 
104       XNextEvent (x_source->xdisplay, &event);
105 
106       g_hash_table_iter_init (&iter, windows);
107       while (g_hash_table_iter_next (&iter, NULL, &value))
108         {
109           GList *l;
110           GtkWidget *window = value;
111           GList *handlers =
112             g_object_get_qdata (G_OBJECT (window), event_handlers_quark);
113 
114           for (l = handlers; l; l = l->next)
115             {
116               XEventHandler handler = l->data;
117               handler (window, &event);
118             }
119         }
120     }
121 
122   return TRUE;
123 }
124 
125 static void
x_event_source_finalize(GSource * source)126 x_event_source_finalize (GSource *source)
127 {
128   XClientEventSource *x_source = (XClientEventSource *) source;
129 
130   *x_source->self_ref = NULL;
131 }
132 
133 static GSourceFuncs x_event_funcs = {
134   x_event_source_prepare,
135   x_event_source_check,
136   x_event_source_dispatch,
137   x_event_source_finalize,
138 };
139 
140 static GSource*
ensure_xsource_handler(GdkDisplay * gdkdisplay)141 ensure_xsource_handler (GdkDisplay *gdkdisplay)
142 {
143   static GSource *source = NULL;
144   Display *xdisplay = GDK_DISPLAY_XDISPLAY (gdkdisplay);
145   XClientEventSource *x_source;
146 
147   if (source)
148     return g_source_ref (source);
149 
150   source = g_source_new (&x_event_funcs, sizeof (XClientEventSource));
151   x_source = (XClientEventSource *) source;
152   x_source->self_ref = &source;
153   x_source->xdisplay = xdisplay;
154   x_source->event_poll_fd.fd = ConnectionNumber (xdisplay);
155   x_source->event_poll_fd.events = G_IO_IN;
156   g_source_add_poll (source, &x_source->event_poll_fd);
157 
158   g_source_set_priority (source, GDK_PRIORITY_EVENTS - 1);
159   g_source_set_can_recurse (source, TRUE);
160   g_source_attach (source, NULL);
161 
162   return source;
163 }
164 
165 static gboolean
window_has_x11_event_handler(GtkWidget * window,XEventHandler handler)166 window_has_x11_event_handler (GtkWidget     *window,
167                               XEventHandler  handler)
168 {
169   GList *handlers =
170     g_object_get_qdata (G_OBJECT (window), event_handlers_quark);
171 
172   g_return_val_if_fail (handler, FALSE);
173   g_return_val_if_fail (!wayland, FALSE);
174 
175   return g_list_find (handlers, handler) != NULL;
176 }
177 
178 static void
unref_and_maybe_destroy_gsource(GSource * source)179 unref_and_maybe_destroy_gsource (GSource *source)
180 {
181   g_source_unref (source);
182 
183   if (source->ref_count == 1)
184     g_source_destroy (source);
185 }
186 
187 static void
window_add_x11_event_handler(GtkWidget * window,XEventHandler handler)188 window_add_x11_event_handler (GtkWidget     *window,
189                               XEventHandler  handler)
190 {
191   GSource *source;
192   GList *handlers =
193     g_object_get_qdata (G_OBJECT (window), event_handlers_quark);
194 
195   g_return_if_fail (!window_has_x11_event_handler (window, handler));
196 
197   source = ensure_xsource_handler (gtk_widget_get_display (window));
198   g_object_set_qdata_full (G_OBJECT (window), event_source_quark, source,
199                            (GDestroyNotify) unref_and_maybe_destroy_gsource);
200 
201   handlers = g_list_append (handlers, handler);
202   g_object_set_qdata (G_OBJECT (window), event_handlers_quark, handlers);
203 }
204 
205 static void
window_remove_x11_event_handler(GtkWidget * window,XEventHandler handler)206 window_remove_x11_event_handler (GtkWidget     *window,
207                                  XEventHandler  handler)
208 {
209   GList *handlers =
210     g_object_get_qdata (G_OBJECT (window), event_handlers_quark);
211 
212   g_return_if_fail (window_has_x11_event_handler (window, handler));
213 
214   g_object_set_qdata (G_OBJECT (window), event_source_quark, NULL);
215 
216   handlers = g_list_remove (handlers, handler);
217   g_object_set_qdata (G_OBJECT (window), event_handlers_quark, handlers);
218 }
219 
220 static void
handle_take_focus(GtkWidget * window,XEvent * xevent)221 handle_take_focus (GtkWidget *window,
222                    XEvent    *xevent)
223 {
224   GdkWindow *gdkwindow = gtk_widget_get_window (window);
225   GdkDisplay *display = gtk_widget_get_display (window);
226   Atom wm_protocols =
227     gdk_x11_get_xatom_by_name_for_display (display, "WM_PROTOCOLS");
228   Atom wm_take_focus =
229     gdk_x11_get_xatom_by_name_for_display (display, "WM_TAKE_FOCUS");
230 
231   if (xevent->xany.type != ClientMessage ||
232       xevent->xany.window != GDK_WINDOW_XID (gdkwindow))
233     return;
234 
235   if (xevent->xclient.message_type == wm_protocols &&
236       xevent->xclient.data.l[0] == wm_take_focus)
237     {
238       XSetInputFocus (xevent->xany.display,
239                       GDK_WINDOW_XID (gdkwindow),
240                       RevertToParent,
241                       xevent->xclient.data.l[1]);
242     }
243 }
244 
245 static int
calculate_titlebar_height(GtkWindow * window)246 calculate_titlebar_height (GtkWindow *window)
247 {
248   GtkWidget *titlebar;
249   GdkWindow *gdk_window;
250 
251   gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
252   if (gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_FULLSCREEN)
253     return 0;
254 
255   titlebar = gtk_window_get_titlebar (window);
256   if (!titlebar)
257     return 0;
258 
259   return gtk_widget_get_allocated_height (titlebar);
260 }
261 
262 static void
process_line(const char * line)263 process_line (const char *line)
264 {
265   GError *error = NULL;
266   int argc;
267   char **argv;
268 
269   if (!g_shell_parse_argv (line, &argc, &argv, &error))
270     {
271       g_print ("error parsing command: %s\n", error->message);
272       g_error_free (error);
273       return;
274     }
275 
276   if (argc < 1)
277     {
278       g_print ("Empty command\n");
279       goto out;
280     }
281 
282   if (strcmp (argv[0], "create") == 0)
283     {
284       int i;
285 
286       if (argc  < 2)
287         {
288           g_print ("usage: create <id> [override|csd]\n");
289           goto out;
290         }
291 
292       if (g_hash_table_lookup (windows, argv[1]))
293         {
294           g_print ("window %s already exists\n", argv[1]);
295           goto out;
296         }
297 
298       gboolean override = FALSE;
299       gboolean csd = FALSE;
300       for (i = 2; i < argc; i++)
301         {
302           if (strcmp (argv[i], "override") == 0)
303             override = TRUE;
304           if (strcmp (argv[i], "csd") == 0)
305             csd = TRUE;
306         }
307 
308       if (override && csd)
309         {
310           g_print ("override and csd keywords are exclusive\n");
311           goto out;
312         }
313 
314       GtkWidget *window = gtk_window_new (override ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
315       g_hash_table_insert (windows, g_strdup (argv[1]), window);
316 
317       if (csd)
318         {
319           GtkWidget *headerbar = gtk_header_bar_new ();
320           gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
321           gtk_widget_show (headerbar);
322         }
323 
324       gtk_window_set_default_size (GTK_WINDOW (window), 100, 100);
325 
326       gchar *title = g_strdup_printf ("test/%s/%s", client_id, argv[1]);
327       gtk_window_set_title (GTK_WINDOW (window), title);
328       g_free (title);
329 
330       g_object_set_qdata (G_OBJECT (window), can_take_focus_quark,
331                           GUINT_TO_POINTER (TRUE));
332 
333       gtk_widget_realize (window);
334 
335       if (!wayland)
336         {
337           /* The cairo xlib backend creates a window when initialized, which
338            * confuses our testing if it happens asynchronously the first
339            * time a window is painted. By creating an Xlib surface and
340            * destroying it, we force initialization at a more predictable time.
341            */
342           GdkWindow *window_gdk = gtk_widget_get_window (window);
343           cairo_surface_t *surface = gdk_window_create_similar_surface (window_gdk,
344                                                                         CAIRO_CONTENT_COLOR,
345                                                                         1, 1);
346           cairo_surface_destroy (surface);
347         }
348 
349     }
350   else if (strcmp (argv[0], "set_parent") == 0)
351     {
352       if (argc != 3)
353         {
354           g_print ("usage: set_parent <window-id> <parent-id>\n");
355           goto out;
356         }
357 
358       GtkWidget *window = lookup_window (argv[1]);
359       if (!window)
360         {
361           g_print ("unknown window %s\n", argv[1]);
362           goto out;
363         }
364 
365       GtkWidget *parent_window = lookup_window (argv[2]);
366       if (!parent_window)
367         {
368           g_print ("unknown parent window %s\n", argv[2]);
369           goto out;
370         }
371 
372       gtk_window_set_transient_for (GTK_WINDOW (window),
373                                     GTK_WINDOW (parent_window));
374     }
375   else if (strcmp (argv[0], "set_parent_exported") == 0)
376     {
377       if (argc != 3)
378         {
379           g_print ("usage: set_parent_exported <window-id> <parent-id>\n");
380           goto out;
381         }
382 
383       GtkWidget *window = lookup_window (argv[1]);
384       if (!window)
385         {
386           g_print ("unknown window %s\n", argv[1]);
387           goto out;
388         }
389 
390       GtkWidget *parent_window = lookup_window (argv[2]);
391       if (!parent_window)
392         {
393           g_print ("unknown parent window %s\n", argv[2]);
394           goto out;
395         }
396 
397       GdkWindow *parent_gdk_window = gtk_widget_get_window (parent_window);
398       if (!gdk_wayland_window_export_handle (parent_gdk_window,
399                                              window_export_handle_cb,
400                                              window,
401                                              NULL))
402         g_print ("Fail to export handle for window id %s\n", argv[2]);
403     }
404   else if (strcmp (argv[0], "accept_focus") == 0)
405     {
406       if (argc != 3)
407         {
408           g_print ("usage: %s <window-id> [true|false]\n", argv[0]);
409           goto out;
410         }
411 
412       GtkWidget *window = lookup_window (argv[1]);
413       if (!window)
414         {
415           g_print ("unknown window %s\n", argv[1]);
416           goto out;
417         }
418 
419       if (!wayland &&
420           window_has_x11_event_handler (window, handle_take_focus))
421         {
422           g_print ("Impossible to use %s for windows accepting take focus\n",
423                    argv[1]);
424           goto out;
425         }
426 
427       gboolean enabled = g_ascii_strcasecmp (argv[2], "true") == 0;
428       gtk_window_set_accept_focus (GTK_WINDOW (window), enabled);
429     }
430   else if (strcmp (argv[0], "can_take_focus") == 0)
431     {
432       if (argc != 3)
433         {
434           g_print ("usage: %s <window-id> [true|false]\n", argv[0]);
435           goto out;
436         }
437 
438       GtkWidget *window = lookup_window (argv[1]);
439       if (!window)
440         {
441           g_print ("unknown window %s\n", argv[1]);
442           goto out;
443         }
444 
445       if (wayland)
446         {
447           g_print ("%s not supported under wayland\n", argv[0]);
448           goto out;
449         }
450 
451       if (window_has_x11_event_handler (window, handle_take_focus))
452         {
453           g_print ("Impossible to change %s for windows accepting take focus\n",
454                    argv[1]);
455           goto out;
456         }
457 
458       GdkDisplay *display = gdk_display_get_default ();
459       GdkWindow *gdkwindow = gtk_widget_get_window (window);
460       Display *xdisplay = gdk_x11_display_get_xdisplay (display);
461       Window xwindow = GDK_WINDOW_XID (gdkwindow);
462       Atom wm_take_focus = gdk_x11_get_xatom_by_name_for_display (display, "WM_TAKE_FOCUS");
463       gboolean add = g_ascii_strcasecmp(argv[2], "true") == 0;
464       Atom *protocols = NULL;
465       Atom *new_protocols;
466       int n_protocols = 0;
467       int i, n = 0;
468 
469       gdk_display_sync (display);
470       XGetWMProtocols (xdisplay, xwindow, &protocols, &n_protocols);
471       new_protocols = g_new0 (Atom, n_protocols + (add ? 1 : 0));
472 
473       for (i = 0; i < n_protocols; ++i)
474         {
475           if (protocols[i] != wm_take_focus)
476             new_protocols[n++] = protocols[i];
477         }
478 
479       if (add)
480         new_protocols[n++] = wm_take_focus;
481 
482       XSetWMProtocols (xdisplay, xwindow, new_protocols, n);
483       g_object_set_qdata (G_OBJECT (window), can_take_focus_quark,
484                           GUINT_TO_POINTER (add));
485 
486       XFree (new_protocols);
487       XFree (protocols);
488     }
489   else if (strcmp (argv[0], "accept_take_focus") == 0)
490     {
491       if (argc != 3)
492         {
493           g_print ("usage: %s <window-id> [true|false]\n", argv[0]);
494           goto out;
495         }
496 
497       GtkWidget *window = lookup_window (argv[1]);
498       if (!window)
499         {
500           g_print ("unknown window %s\n", argv[1]);
501           goto out;
502         }
503 
504       if (wayland)
505         {
506           g_print ("%s not supported under wayland\n", argv[0]);
507           goto out;
508         }
509 
510       if (gtk_window_get_accept_focus (GTK_WINDOW (window)))
511         {
512           g_print ("%s not supported for input windows\n", argv[0]);
513           goto out;
514         }
515 
516       if (!g_object_get_qdata (G_OBJECT (window), can_take_focus_quark))
517         {
518           g_print ("%s not supported for windows with no WM_TAKE_FOCUS set\n",
519                    argv[0]);
520           goto out;
521         }
522 
523       if (g_ascii_strcasecmp (argv[2], "true") == 0)
524         window_add_x11_event_handler (window, handle_take_focus);
525       else
526         window_remove_x11_event_handler (window, handle_take_focus);
527     }
528   else if (strcmp (argv[0], "show") == 0)
529     {
530       if (argc != 2)
531         {
532           g_print ("usage: show <id>\n");
533           goto out;
534         }
535 
536       GtkWidget *window = lookup_window (argv[1]);
537       if (!window)
538         goto out;
539 
540       gtk_widget_show (window);
541       gdk_display_sync (gdk_display_get_default ());
542     }
543   else if (strcmp (argv[0], "hide") == 0)
544     {
545       if (argc != 2)
546         {
547           g_print ("usage: hide <id>\n");
548           goto out;
549         }
550 
551       GtkWidget *window = lookup_window (argv[1]);
552       if (!window)
553         goto out;
554 
555       gtk_widget_hide (window);
556     }
557   else if (strcmp (argv[0], "activate") == 0)
558     {
559       if (argc != 2)
560         {
561           g_print ("usage: activate <id>\n");
562           goto out;
563         }
564 
565       GtkWidget *window = lookup_window (argv[1]);
566       if (!window)
567         goto out;
568 
569       gtk_window_present (GTK_WINDOW (window));
570     }
571   else if (strcmp (argv[0], "resize") == 0)
572     {
573       if (argc != 4)
574         {
575           g_print ("usage: resize <id> <width> <height>\n");
576           goto out;
577         }
578 
579       GtkWidget *window = lookup_window (argv[1]);
580       if (!window)
581         goto out;
582 
583       int width = atoi (argv[2]);
584       int height = atoi (argv[3]);
585       int titlebar_height = calculate_titlebar_height (GTK_WINDOW (window));
586       gtk_window_resize (GTK_WINDOW (window),
587                          width,
588                          height - titlebar_height);
589     }
590   else if (strcmp (argv[0], "raise") == 0)
591     {
592       if (argc != 2)
593         {
594           g_print ("usage: raise <id>\n");
595           goto out;
596         }
597 
598       GtkWidget *window = lookup_window (argv[1]);
599       if (!window)
600         goto out;
601 
602       gdk_window_raise (gtk_widget_get_window (window));
603     }
604   else if (strcmp (argv[0], "lower") == 0)
605     {
606       if (argc != 2)
607         {
608           g_print ("usage: lower <id>\n");
609           goto out;
610         }
611 
612       GtkWidget *window = lookup_window (argv[1]);
613       if (!window)
614         goto out;
615 
616       gdk_window_lower (gtk_widget_get_window (window));
617     }
618   else if (strcmp (argv[0], "destroy") == 0)
619     {
620       if (argc != 2)
621         {
622           g_print ("usage: destroy <id>\n");
623           goto out;
624         }
625 
626       GtkWidget *window = lookup_window (argv[1]);
627       if (!window)
628         goto out;
629 
630       g_hash_table_remove (windows, argv[1]);
631       gtk_widget_destroy (window);
632     }
633   else if (strcmp (argv[0], "destroy_all") == 0)
634     {
635       if (argc != 1)
636         {
637           g_print ("usage: destroy_all\n");
638           goto out;
639         }
640 
641       GHashTableIter iter;
642       gpointer key, value;
643 
644       g_hash_table_iter_init (&iter, windows);
645       while (g_hash_table_iter_next (&iter, &key, &value))
646         gtk_widget_destroy (value);
647 
648       g_hash_table_remove_all (windows);
649     }
650   else if (strcmp (argv[0], "sync") == 0)
651     {
652       if (argc != 1)
653         {
654           g_print ("usage: sync\n");
655           goto out;
656         }
657 
658       gdk_display_sync (gdk_display_get_default ());
659     }
660   else if (strcmp (argv[0], "set_counter") == 0)
661     {
662       XSyncCounter counter;
663       int value;
664 
665       if (argc != 3)
666         {
667           g_print ("usage: set_counter <counter> <value>\n");
668           goto out;
669         }
670 
671       if (wayland)
672         {
673           g_print ("usage: set_counter can only be used for X11\n");
674           goto out;
675         }
676 
677       counter = strtoul(argv[1], NULL, 10);
678       value = atoi(argv[2]);
679       XSyncValue sync_value;
680       XSyncIntToValue (&sync_value, value);
681 
682       XSyncSetCounter (gdk_x11_display_get_xdisplay (gdk_display_get_default ()),
683                        counter, sync_value);
684     }
685   else if (strcmp (argv[0], "minimize") == 0)
686     {
687       if (argc != 2)
688         {
689           g_print ("usage: minimize <id>\n");
690           goto out;
691         }
692 
693       GtkWidget *window = lookup_window (argv[1]);
694       if (!window)
695         goto out;
696 
697       gtk_window_iconify (GTK_WINDOW (window));
698     }
699   else if (strcmp (argv[0], "unminimize") == 0)
700     {
701       if (argc != 2)
702         {
703           g_print ("usage: unminimize <id>\n");
704           goto out;
705         }
706 
707       GtkWidget *window = lookup_window (argv[1]);
708       if (!window)
709         goto out;
710 
711       gtk_window_deiconify (GTK_WINDOW (window));
712     }
713   else if (strcmp (argv[0], "maximize") == 0)
714     {
715       if (argc != 2)
716         {
717           g_print ("usage: maximize <id>\n");
718           goto out;
719         }
720 
721       GtkWidget *window = lookup_window (argv[1]);
722       if (!window)
723         goto out;
724 
725       gtk_window_maximize (GTK_WINDOW (window));
726     }
727   else if (strcmp (argv[0], "unmaximize") == 0)
728     {
729       if (argc != 2)
730         {
731           g_print ("usage: unmaximize <id>\n");
732           goto out;
733         }
734 
735       GtkWidget *window = lookup_window (argv[1]);
736       if (!window)
737         goto out;
738 
739       gtk_window_unmaximize (GTK_WINDOW (window));
740     }
741   else if (strcmp (argv[0], "fullscreen") == 0)
742     {
743       if (argc != 2)
744         {
745           g_print ("usage: fullscreen <id>\n");
746           goto out;
747         }
748 
749       GtkWidget *window = lookup_window (argv[1]);
750       if (!window)
751         goto out;
752 
753       gtk_window_fullscreen (GTK_WINDOW (window));
754     }
755   else if (strcmp (argv[0], "unfullscreen") == 0)
756     {
757       if (argc != 2)
758         {
759           g_print ("usage: unfullscreen <id>\n");
760           goto out;
761         }
762 
763       GtkWidget *window = lookup_window (argv[1]);
764       if (!window)
765         goto out;
766 
767       gtk_window_unfullscreen (GTK_WINDOW (window));
768     }
769   else if (strcmp (argv[0], "freeze") == 0)
770     {
771       if (argc != 2)
772         {
773           g_print ("usage: freeze <id>\n");
774           goto out;
775         }
776 
777       GtkWidget *window = lookup_window (argv[1]);
778       if (!window)
779         goto out;
780 
781       gdk_window_freeze_updates (gtk_widget_get_window (window));
782     }
783   else if (strcmp (argv[0], "thaw") == 0)
784     {
785       if (argc != 2)
786         {
787           g_print ("usage: thaw <id>\n");
788           goto out;
789         }
790 
791       GtkWidget *window = lookup_window (argv[1]);
792       if (!window)
793         goto out;
794 
795       gdk_window_thaw_updates (gtk_widget_get_window (window));
796     }
797   else if (strcmp (argv[0], "assert_size") == 0)
798     {
799       int expected_width;
800       int expected_height;
801       int width;
802       int height;
803 
804       if (argc != 4)
805         {
806           g_print ("usage: assert_size <id> <width> <height>\n");
807           goto out;
808         }
809 
810       GtkWidget *window = lookup_window (argv[1]);
811       if (!window)
812         goto out;
813 
814       gtk_window_get_size (GTK_WINDOW (window), &width, &height);
815       height += calculate_titlebar_height (GTK_WINDOW (window));
816 
817       expected_width = atoi (argv[2]);
818       expected_height = atoi (argv[3]);
819       if (expected_width != width || expected_height != height)
820         {
821           g_print ("Expected size %dx%d didn't match actual size %dx%d\n",
822                    expected_width, expected_height,
823                    width, height);
824           goto out;
825         }
826     }
827   else
828     {
829       g_print ("Unknown command %s\n", argv[0]);
830       goto out;
831     }
832 
833   g_print ("OK\n");
834 
835  out:
836   g_strfreev (argv);
837 }
838 
839 static void
on_line_received(GObject * source,GAsyncResult * result,gpointer user_data)840 on_line_received (GObject      *source,
841                   GAsyncResult *result,
842                   gpointer      user_data)
843 {
844   GDataInputStream *in = G_DATA_INPUT_STREAM (source);
845   GError *error = NULL;
846   gsize length;
847   char *line = g_data_input_stream_read_line_finish_utf8 (in, result, &length, &error);
848 
849   if (line == NULL)
850     {
851       if (error != NULL)
852         g_printerr ("Error reading from stdin: %s\n", error->message);
853       gtk_main_quit ();
854       return;
855     }
856 
857   process_line (line);
858   g_free (line);
859   read_next_line (in);
860 }
861 
862 static void
read_next_line(GDataInputStream * in)863 read_next_line (GDataInputStream *in)
864 {
865   g_data_input_stream_read_line_async (in, G_PRIORITY_DEFAULT, NULL,
866                                        on_line_received, NULL);
867 }
868 
869 const GOptionEntry options[] = {
870   {
871     "wayland", 0, 0, G_OPTION_ARG_NONE,
872     &wayland,
873     "Create a wayland client, not an X11 one",
874     NULL
875   },
876   {
877     "client-id", 0, 0, G_OPTION_ARG_STRING,
878     &client_id,
879     "Identifier used in Window titles for this client",
880     "CLIENT_ID",
881   },
882   { NULL }
883 };
884 
885 int
main(int argc,char ** argv)886 main(int argc, char **argv)
887 {
888   GOptionContext *context = g_option_context_new (NULL);
889   GdkScreen *screen;
890   GtkCssProvider *provider;
891   GError *error = NULL;
892 
893   g_option_context_add_main_entries (context, options, NULL);
894 
895   if (!g_option_context_parse (context,
896                                &argc, &argv, &error))
897     {
898       g_printerr ("%s", error->message);
899       return 1;
900     }
901 
902   if (wayland)
903     gdk_set_allowed_backends ("wayland");
904   else
905     gdk_set_allowed_backends ("x11");
906 
907   gtk_init (NULL, NULL);
908 
909   screen = gdk_screen_get_default ();
910   provider = gtk_css_provider_new ();
911   static const char *no_decoration_css =
912     "decoration {"
913     "  border-radius: 0 0 0 0;"
914     "  border-width: 0;"
915     "  padding: 0 0 0 0;"
916     "  box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), 0 0 0 0 rgba(0, 0, 0, 0);"
917     "  margin: 0px;"
918     "}";
919   if (!gtk_css_provider_load_from_data (provider,
920                                         no_decoration_css,
921                                         strlen (no_decoration_css),
922                                         &error))
923     {
924       g_printerr ("%s", error->message);
925       return 1;
926     }
927   gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider),
928                                              GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
929 
930   windows = g_hash_table_new_full (g_str_hash, g_str_equal,
931                                    g_free, NULL);
932   event_source_quark = g_quark_from_static_string ("event-source");
933   event_handlers_quark = g_quark_from_static_string ("event-handlers");
934   can_take_focus_quark = g_quark_from_static_string ("can-take-focus");
935 
936   GInputStream *raw_in = g_unix_input_stream_new (0, FALSE);
937   GDataInputStream *in = g_data_input_stream_new (raw_in);
938 
939   read_next_line (in);
940 
941   gtk_main ();
942 
943   return 0;
944 }
945