At-spi-bus-launcher signal emission fixes
[platform/upstream/at-spi2-core.git] / bus / at-spi-bus-launcher.c
1 /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * 
3  * at-spi-bus-launcher: Manage the a11y bus as a child process 
4  *
5  * Copyright 2011 Red Hat, Inc.
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, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24
25 #include <unistd.h>
26 #include <string.h>
27 #include <signal.h>
28 #include <sys/wait.h>
29 #include <errno.h>
30 #include <stdio.h>
31
32 #include <gio/gio.h>
33 #include <X11/Xlib.h>
34 #include <X11/Xatom.h>
35
36 typedef enum {
37   A11Y_BUS_STATE_IDLE = 0,
38   A11Y_BUS_STATE_READING_ADDRESS,
39   A11Y_BUS_STATE_RUNNING,
40   A11Y_BUS_STATE_ERROR
41 } A11yBusState;
42
43 typedef struct {
44   GMainLoop *loop;
45   gboolean launch_immediately;
46   gboolean a11y_enabled;
47   gboolean screen_reader_enabled;
48   GDBusConnection *session_bus;
49   GSettings *a11y_schema;
50   GSettings *interface_schema;
51
52   A11yBusState state;
53   /* -1 == error, 0 == pending, > 0 == running */
54   int a11y_bus_pid;
55   char *a11y_bus_address;
56   int pipefd[2];
57   char *a11y_launch_error_message;
58 } A11yBusLauncher;
59
60 static A11yBusLauncher *_global_app = NULL;
61
62 static const gchar introspection_xml[] =
63   "<node>"
64   "  <interface name='org.a11y.Bus'>"
65   "    <method name='GetAddress'>"
66   "      <arg type='s' name='address' direction='out'/>"
67   "    </method>"
68   "  </interface>"
69   "<interface name='org.a11y.Status'>"
70   "<property name='IsEnabled' type='b' access='readwrite'/>"
71   "<property name='ScreenReaderEnabled' type='b' access='readwrite'/>"
72   "</interface>"
73   "</node>";
74 static GDBusNodeInfo *introspection_data = NULL;
75
76 static void
77 setup_bus_child (gpointer data)
78 {
79   A11yBusLauncher *app = data;
80   (void) app;
81
82   close (app->pipefd[0]);
83   dup2 (app->pipefd[1], 3);
84   close (app->pipefd[1]);
85
86   /* On Linux, tell the bus process to exit if this process goes away */
87 #ifdef __linux
88 #include <sys/prctl.h>
89   prctl (PR_SET_PDEATHSIG, 15);
90 #endif  
91 }
92
93 /**
94  * unix_read_all_fd_to_string:
95  *
96  * Read all data from a file descriptor to a C string buffer.
97  */
98 static gboolean
99 unix_read_all_fd_to_string (int      fd,
100                             char    *buf,
101                             ssize_t  max_bytes)
102 {
103   ssize_t bytes_read;
104
105   while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
106     {
107       if (bytes_read < 0)
108         return FALSE;
109       buf += bytes_read;
110       max_bytes -= bytes_read;
111     }
112   *buf = '\0';
113   return TRUE;
114 }
115
116 static void
117 on_bus_exited (GPid     pid,
118                gint     status,
119                gpointer data)
120 {
121   A11yBusLauncher *app = data;
122   
123   app->a11y_bus_pid = -1;
124   app->state = A11Y_BUS_STATE_ERROR;
125   if (app->a11y_launch_error_message == NULL)
126     {
127       if (WIFEXITED (status))
128         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
129       else if (WIFSIGNALED (status))
130         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
131       else if (WIFSTOPPED (status))
132         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
133     }
134   g_main_loop_quit (app->loop);
135
136
137 static gboolean
138 ensure_a11y_bus (A11yBusLauncher *app)
139 {
140   GPid pid;
141   char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
142   char addr_buf[2048];
143   GError *error = NULL;
144
145   if (app->a11y_bus_pid != 0)
146     return FALSE;
147   
148   argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
149
150   if (pipe (app->pipefd) < 0)
151     g_error ("Failed to create pipe: %s", strerror (errno));
152   
153   if (!g_spawn_async (NULL,
154                       argv,
155                       NULL,
156                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
157                       setup_bus_child,
158                       app,
159                       &pid,
160                       &error))
161     {
162       app->a11y_bus_pid = -1;
163       app->a11y_launch_error_message = g_strdup (error->message);
164       g_clear_error (&error);
165       goto error;
166     }
167
168   close (app->pipefd[1]);
169   app->pipefd[1] = -1;
170
171   g_child_watch_add (pid, on_bus_exited, app);
172
173   app->state = A11Y_BUS_STATE_READING_ADDRESS;
174   app->a11y_bus_pid = pid;
175   g_debug ("Launched a11y bus, child is %ld", (long) pid);
176   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
177     {
178       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
179       kill (app->a11y_bus_pid, SIGTERM);
180       goto error;
181     }
182   close (app->pipefd[0]);
183   app->pipefd[0] = -1;
184   app->state = A11Y_BUS_STATE_RUNNING;
185
186   /* Trim the trailing newline */
187   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
188   g_debug ("a11y bus address: %s", app->a11y_bus_address);
189
190   {
191     Display *display = XOpenDisplay (NULL);
192     if (display)
193       {
194         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
195         XChangeProperty (display,
196                          XDefaultRootWindow (display),
197                          bus_address_atom,
198                          XA_STRING, 8, PropModeReplace,
199                          (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
200         XFlush (display);
201         XCloseDisplay (display);
202       }
203   }
204
205   return TRUE;
206   
207  error:
208   close (app->pipefd[0]);
209   close (app->pipefd[1]);
210   app->state = A11Y_BUS_STATE_ERROR;
211
212   return FALSE;
213 }
214
215 static void
216 handle_method_call (GDBusConnection       *connection,
217                     const gchar           *sender,
218                     const gchar           *object_path,
219                     const gchar           *interface_name,
220                     const gchar           *method_name,
221                     GVariant              *parameters,
222                     GDBusMethodInvocation *invocation,
223                     gpointer               user_data)
224 {
225   A11yBusLauncher *app = user_data;
226
227   if (g_strcmp0 (method_name, "GetAddress") == 0)
228     {
229       ensure_a11y_bus (app);
230       if (app->a11y_bus_pid > 0)
231         g_dbus_method_invocation_return_value (invocation,
232                                                g_variant_new ("(s)", app->a11y_bus_address));
233       else
234         g_dbus_method_invocation_return_dbus_error (invocation,
235                                                     "org.a11y.Bus.Error",
236                                                     app->a11y_launch_error_message);
237     }
238 }
239
240 static GVariant *
241 handle_get_property  (GDBusConnection       *connection,
242                       const gchar           *sender,
243                       const gchar           *object_path,
244                       const gchar           *interface_name,
245                       const gchar           *property_name,
246                     GError **error,
247                     gpointer               user_data)
248 {
249   A11yBusLauncher *app = user_data;
250
251   if (g_strcmp0 (property_name, "IsEnabled") == 0)
252     return g_variant_new ("b", app->a11y_enabled);
253   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
254     return g_variant_new ("b", app->screen_reader_enabled);
255   else
256     return NULL;
257 }
258
259 static void
260 handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled,
261                                gboolean notify_gsettings)
262 {
263   GVariantBuilder builder;
264   GVariantBuilder invalidated_builder;
265
266   if (enabled == app->a11y_enabled)
267     return;
268
269   app->a11y_enabled = enabled;
270
271   if (notify_gsettings && app->interface_schema)
272     {
273       g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
274                               enabled);
275       g_settings_sync ();
276     }
277
278   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
279   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
280   g_variant_builder_add (&builder, "{sv}", "IsEnabled",
281                          g_variant_new_boolean (enabled));
282
283   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
284                                  "org.freedesktop.DBus.Properties",
285                                  "PropertiesChanged",
286                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
287                                                 &builder,
288                                                 &invalidated_builder),
289                                  NULL);
290 }
291
292 static void
293 handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled,
294                                gboolean notify_gsettings)
295 {
296   GVariantBuilder builder;
297   GVariantBuilder invalidated_builder;
298
299   if (enabled == app->screen_reader_enabled)
300     return;
301
302   /* If the screen reader is being enabled, we should enable accessibility
303    * if it isn't enabled already */
304   if (enabled)
305     handle_a11y_enabled_change (app, enabled, notify_gsettings);
306
307   app->screen_reader_enabled = enabled;
308
309   if (notify_gsettings && app->a11y_schema)
310     {
311       g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
312                               enabled);
313       g_settings_sync ();
314     }
315
316   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
317   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
318   g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
319                          g_variant_new_boolean (enabled));
320
321   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
322                                  "org.freedesktop.DBus.Properties",
323                                  "PropertiesChanged",
324                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
325                                                 &builder,
326                                                 &invalidated_builder),
327                                  NULL);
328 }
329
330 static gboolean
331 handle_set_property  (GDBusConnection       *connection,
332                       const gchar           *sender,
333                       const gchar           *object_path,
334                       const gchar           *interface_name,
335                       const gchar           *property_name,
336                       GVariant *value,
337                     GError **error,
338                     gpointer               user_data)
339 {
340   A11yBusLauncher *app = user_data;
341   const gchar *type = g_variant_get_type_string (value);
342   gboolean enabled;
343   
344   if (g_strcmp0 (type, "b") != 0)
345     {
346       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
347                        "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
348       return FALSE;
349     }
350
351   enabled = g_variant_get_boolean (value);
352
353   if (g_strcmp0 (property_name, "IsEnabled") == 0)
354     {
355       handle_a11y_enabled_change (app, enabled, TRUE);
356       return TRUE;
357     }
358   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
359     {
360       handle_screen_reader_enabled_change (app, enabled, TRUE);
361       return TRUE;
362     }
363   else
364     {
365       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
366                        "Unknown property '%s'", property_name);
367       return FALSE;
368     }
369 }
370
371 static const GDBusInterfaceVTable bus_vtable =
372 {
373   handle_method_call,
374   NULL, /* handle_get_property, */
375   NULL  /* handle_set_property */
376 };
377
378 static const GDBusInterfaceVTable status_vtable =
379 {
380   NULL, /* handle_method_call */
381   handle_get_property,
382   handle_set_property
383 };
384
385 static void
386 on_bus_acquired (GDBusConnection *connection,
387                  const gchar     *name,
388                  gpointer         user_data)
389 {
390   A11yBusLauncher *app = user_data;
391   GError *error;
392   guint registration_id;
393   
394   if (connection == NULL)
395     {
396       g_main_loop_quit (app->loop);
397       return;
398     }
399   app->session_bus = connection;
400
401   if (app->launch_immediately)
402     {
403       ensure_a11y_bus (app);
404       if (app->state == A11Y_BUS_STATE_ERROR)
405         {
406           g_main_loop_quit (app->loop);
407           return;
408         }
409     }
410
411   error = NULL;
412   registration_id = g_dbus_connection_register_object (connection,
413                                                        "/org/a11y/bus",
414                                                        introspection_data->interfaces[0],
415                                                        &bus_vtable,
416                                                        _global_app,
417                                                        NULL,
418                                                        &error);
419   if (registration_id == 0)
420     g_error ("%s", error->message);
421
422   g_dbus_connection_register_object (connection,
423                                                        "/org/a11y/bus",
424                                                        introspection_data->interfaces[1],
425                                                        &status_vtable,
426                                                        _global_app,
427                                                        NULL,
428                                                        &error);
429 }
430
431 static void
432 on_name_lost (GDBusConnection *connection,
433               const gchar     *name,
434               gpointer         user_data)
435 {
436   A11yBusLauncher *app = user_data;
437   if (app->session_bus == NULL
438       && connection == NULL
439       && app->a11y_launch_error_message == NULL)
440     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
441   g_main_loop_quit (app->loop);
442 }
443
444 static void
445 on_name_acquired (GDBusConnection *connection,
446                   const gchar     *name,
447                   gpointer         user_data)
448 {
449   A11yBusLauncher *app = user_data;
450   (void) app;
451 }
452
453 static int sigterm_pipefd[2];
454
455 static void
456 sigterm_handler (int signum)
457 {
458   write (sigterm_pipefd[1], "X", 1);
459 }
460
461 static gboolean
462 on_sigterm_pipe (GIOChannel  *channel,
463                  GIOCondition condition,
464                  gpointer     data)
465 {
466   A11yBusLauncher *app = data;
467   
468   g_main_loop_quit (app->loop);
469
470   return FALSE;
471 }
472
473 static void
474 init_sigterm_handling (A11yBusLauncher *app)
475 {
476   GIOChannel *sigterm_channel;
477
478   if (pipe (sigterm_pipefd) < 0)
479     g_error ("Failed to create pipe: %s", strerror (errno));
480   signal (SIGTERM, sigterm_handler);
481
482   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
483   g_io_add_watch (sigterm_channel,
484                   G_IO_IN | G_IO_ERR | G_IO_HUP,
485                   on_sigterm_pipe,
486                   app);
487 }
488
489 static gboolean
490 already_running ()
491 {
492   Atom AT_SPI_BUS;
493   Atom actual_type;
494   Display *bridge_display;
495   int actual_format;
496   unsigned char *data = NULL;
497   unsigned long nitems;
498   unsigned long leftover;
499   gboolean result = FALSE;
500
501   bridge_display = XOpenDisplay (NULL);
502   if (!bridge_display)
503               return FALSE;
504       
505   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
506   XGetWindowProperty (bridge_display,
507                       XDefaultRootWindow (bridge_display),
508                       AT_SPI_BUS, 0L,
509                       (long) BUFSIZ, False,
510                       (Atom) 31, &actual_type, &actual_format,
511                       &nitems, &leftover, &data);
512
513   if (data)
514   {
515     GDBusConnection *bus;
516     GError *error = NULL;
517     const gchar *old_session = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
518     /* TODO: Is there a better way to connect? This is really hacky */
519     g_setenv ("DBUS_SESSION_BUS_ADDRESS", data, TRUE);
520     bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
521     g_setenv ("DBUS_SESSION_BUS_ADDRESS", old_session, TRUE);
522     if (bus != NULL)
523       {
524         result = TRUE;
525         g_object_unref (bus);
526       }
527   }
528
529   XCloseDisplay (bridge_display);
530   return result;
531 }
532
533 static GSettings *
534 get_schema (const gchar *name)
535 {
536   const char * const *schemas = NULL;
537   gint i;
538
539   schemas = g_settings_list_schemas ();
540   for (i = 0; schemas[i]; i++)
541   {
542     if (!strcmp (schemas[i], name))
543       return g_settings_new (schemas[i]);
544   }
545
546   return NULL;
547 }
548
549 static void
550 gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
551 {
552   gboolean new_val = g_settings_get_boolean (gsettings, key);
553   A11yBusLauncher *app = user_data;
554
555   if (!strcmp (key, "toolkit-accessibility"))
556     handle_a11y_enabled_change (_global_app, new_val, FALSE);
557   else if (!strcmp (key, "screen-reader-enabled"))
558     handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
559 }
560
561 int
562 main (int    argc,
563       char **argv)
564 {
565   GError *error = NULL;
566   GMainLoop *loop;
567   GDBusConnection *session_bus;
568   int name_owner_id;
569   gboolean a11y_set = FALSE;
570   gboolean screen_reader_set = FALSE;
571   gint i;
572
573   g_type_init ();
574
575   if (already_running ())
576     return 0;
577
578   _global_app = g_slice_new0 (A11yBusLauncher);
579   _global_app->loop = g_main_loop_new (NULL, FALSE);
580
581   for (i = 1; i < argc; i++)
582     {
583       if (!strcmp (argv[i], "--launch-immediately"))
584         _global_app->launch_immediately = TRUE;
585       else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 2)
586         a11y_set = TRUE;
587       else if (sscanf (argv[i], "--screen-reader=%d",
588                        &_global_app->screen_reader_enabled) == 2)
589         screen_reader_set = TRUE;
590     else
591       g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
592     }
593
594   _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
595   _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
596
597   if (!a11y_set)
598     {
599       _global_app->a11y_enabled = _global_app->interface_schema
600                                   ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
601                                   : _global_app->launch_immediately;
602     }
603
604   if (!screen_reader_set)
605     {
606       _global_app->screen_reader_enabled = _global_app->a11y_schema
607                                   ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
608                                   : FALSE;
609     }
610
611   if (_global_app->interface_schema)
612     g_signal_connect (_global_app->interface_schema,
613                       "changed::toolkit-accessibility",
614                       G_CALLBACK (gsettings_key_changed), _global_app);
615
616   if (_global_app->a11y_schema)
617     g_signal_connect (_global_app->a11y_schema,
618                       "changed::screen-reader-enabled",
619                       G_CALLBACK (gsettings_key_changed), _global_app);
620
621   init_sigterm_handling (_global_app);
622
623   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
624   g_assert (introspection_data != NULL);
625
626   name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
627                                   "org.a11y.Bus",
628                                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
629                                   on_bus_acquired,
630                                   on_name_acquired,
631                                   on_name_lost,
632                                   _global_app,
633                                   NULL);
634
635   g_main_loop_run (_global_app->loop);
636
637   if (_global_app->a11y_bus_pid > 0)
638     kill (_global_app->a11y_bus_pid, SIGTERM);
639
640   /* Clear the X property if our bus is gone; in the case where e.g. 
641    * GDM is launching a login on an X server it was using before,
642    * we don't want early login processes to pick up the stale address.
643    */
644   {
645     Display *display = XOpenDisplay (NULL);
646     if (display)
647       {
648         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
649         XDeleteProperty (display,
650                          XDefaultRootWindow (display),
651                          bus_address_atom);
652
653         XFlush (display);
654         XCloseDisplay (display);
655       }
656   }
657
658   if (_global_app->a11y_launch_error_message)
659     {
660       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
661       return 1;
662     }
663   return 0;
664 }