2.34.0
[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-2018 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 Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 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  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22
23 #include "config.h"
24
25 #include <unistd.h>
26 #include <string.h>
27 #include <signal.h>
28 #ifdef __linux__
29 #include <sys/prctl.h>
30 #include <sys/socket.h>
31 #include <sys/un.h>
32 #endif
33 #include <sys/wait.h>
34 #include <errno.h>
35 #include <stdio.h>
36
37 #include <gio/gio.h>
38 #ifdef HAVE_X11
39 #include <X11/Xlib.h>
40 #include <X11/Xatom.h>
41 #endif
42
43 typedef enum {
44   A11Y_BUS_STATE_IDLE = 0,
45   A11Y_BUS_STATE_READING_ADDRESS,
46   A11Y_BUS_STATE_RUNNING,
47   A11Y_BUS_STATE_ERROR
48 } A11yBusState;
49
50 typedef struct {
51   GMainLoop *loop;
52   gboolean launch_immediately;
53   gboolean a11y_enabled;
54   gboolean screen_reader_enabled;
55   GDBusConnection *session_bus;
56   GSettings *a11y_schema;
57   GSettings *interface_schema;
58   int name_owner_id;
59
60   GDBusProxy *client_proxy;
61
62   A11yBusState state;
63   /* -1 == error, 0 == pending, > 0 == running */
64   int a11y_bus_pid;
65   char *a11y_bus_address;
66 #ifdef HAVE_X11
67   gboolean x11_prop_set;
68 #endif
69   int pipefd[2];
70   int listenfd;
71   char *a11y_launch_error_message;
72 } A11yBusLauncher;
73
74 static A11yBusLauncher *_global_app = NULL;
75
76 static const gchar introspection_xml[] =
77   "<node>"
78   "  <interface name='org.a11y.Bus'>"
79   "    <method name='GetAddress'>"
80   "      <arg type='s' name='address' direction='out'/>"
81   "    </method>"
82   "  </interface>"
83   "<interface name='org.a11y.Status'>"
84   "<property name='IsEnabled' type='b' access='readwrite'/>"
85   "<property name='ScreenReaderEnabled' type='b' access='readwrite'/>"
86   "</interface>"
87   "</node>";
88 static GDBusNodeInfo *introspection_data = NULL;
89
90 static void
91 respond_to_end_session (GDBusProxy *proxy)
92 {
93   GVariant *parameters;
94
95   parameters = g_variant_new ("(bs)", TRUE, "");
96
97   g_dbus_proxy_call (proxy,
98                      "EndSessionResponse", parameters,
99                      G_DBUS_CALL_FLAGS_NONE,
100                      -1, NULL, NULL, NULL);
101 }
102
103 static void
104 g_signal_cb (GDBusProxy *proxy,
105              gchar      *sender_name,
106              gchar      *signal_name,
107              GVariant   *parameters,
108              gpointer    user_data)
109 {
110   A11yBusLauncher *app = user_data;
111
112   if (g_strcmp0 (signal_name, "QueryEndSession") == 0)
113     respond_to_end_session (proxy);
114   else if (g_strcmp0 (signal_name, "EndSession") == 0)
115     respond_to_end_session (proxy);
116   else if (g_strcmp0 (signal_name, "Stop") == 0)
117     g_main_loop_quit (app->loop);
118 }
119
120 static void
121 client_proxy_ready_cb (GObject      *source_object,
122                        GAsyncResult *res,
123                        gpointer      user_data)
124 {
125   A11yBusLauncher *app = user_data;
126   GError *error = NULL;
127
128   app->client_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
129
130   if (error != NULL)
131     {
132       g_warning ("Failed to get a client proxy: %s", error->message);
133       g_error_free (error);
134
135       return;
136     }
137
138   g_signal_connect (app->client_proxy, "g-signal",
139                     G_CALLBACK (g_signal_cb), app);
140 }
141
142 static void
143 register_client (A11yBusLauncher *app)
144 {
145   GDBusProxyFlags flags;
146   GDBusProxy *sm_proxy;
147   GError *error;
148   const gchar *app_id;
149   const gchar *autostart_id;
150   gchar *client_startup_id;
151   GVariant *parameters;
152   GVariant *variant;
153   gchar *object_path;
154
155   flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
156           G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
157
158   error = NULL;
159   sm_proxy = g_dbus_proxy_new_sync (app->session_bus, flags, NULL,
160                                     "org.gnome.SessionManager",
161                                     "/org/gnome/SessionManager",
162                                     "org.gnome.SessionManager",
163                                     NULL, &error);
164
165   if (error != NULL)
166     {
167       g_warning ("Failed to get session manager proxy: %s", error->message);
168       g_error_free (error);
169
170       return;
171     }
172
173   app_id = "at-spi-bus-launcher";
174   autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
175
176   if (autostart_id != NULL)
177     {
178       client_startup_id = g_strdup (autostart_id);
179       g_unsetenv ("DESKTOP_AUTOSTART_ID");
180     }
181   else
182     {
183       client_startup_id = g_strdup ("");
184     }
185
186   parameters = g_variant_new ("(ss)", app_id, client_startup_id);
187   g_free (client_startup_id);
188
189   error = NULL;
190   variant = g_dbus_proxy_call_sync (sm_proxy,
191                                     "RegisterClient", parameters,
192                                     G_DBUS_CALL_FLAGS_NONE,
193                                     -1, NULL, &error);
194
195   g_object_unref (sm_proxy);
196
197   if (error != NULL)
198     {
199       g_warning ("Failed to register client: %s", error->message);
200       g_error_free (error);
201
202       return;
203     }
204
205   g_variant_get (variant, "(o)", &object_path);
206   g_variant_unref (variant);
207
208   flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
209   g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, flags, NULL,
210                             "org.gnome.SessionManager", object_path,
211                             "org.gnome.SessionManager.ClientPrivate",
212                             NULL, client_proxy_ready_cb, app);
213
214   g_free (object_path);
215 }
216
217 static void
218 name_appeared_handler (GDBusConnection *connection,
219                        const gchar     *name,
220                        const gchar     *name_owner,
221                        gpointer         user_data)
222 {
223   A11yBusLauncher *app = user_data;
224
225   register_client (app);
226 }
227
228 /**
229  * unix_read_all_fd_to_string:
230  *
231  * Read all data from a file descriptor to a C string buffer.
232  */
233 static gboolean
234 unix_read_all_fd_to_string (int      fd,
235                             char    *buf,
236                             ssize_t  max_bytes)
237 {
238   ssize_t bytes_read;
239
240   while (max_bytes > 1 && (bytes_read = read (fd, buf, MIN (4096, max_bytes - 1))))
241     {
242       if (bytes_read < 0)
243         return FALSE;
244       buf += bytes_read;
245       max_bytes -= bytes_read;
246     }
247   *buf = '\0';
248   return TRUE;
249 }
250
251 static void
252 on_bus_exited (GPid     pid,
253                gint     status,
254                gpointer data)
255 {
256   A11yBusLauncher *app = data;
257   
258   app->a11y_bus_pid = -1;
259   app->state = A11Y_BUS_STATE_ERROR;
260   if (app->a11y_launch_error_message == NULL)
261     {
262       if (WIFEXITED (status))
263         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
264       else if (WIFSIGNALED (status))
265         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
266       else if (WIFSTOPPED (status))
267         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
268     }
269   g_main_loop_quit (app->loop);
270
271
272 #ifdef DBUS_DAEMON
273 static void
274 setup_bus_child_daemon (gpointer data)
275 {
276   A11yBusLauncher *app = data;
277   (void) app;
278
279   close (app->pipefd[0]);
280   dup2 (app->pipefd[1], 3);
281   close (app->pipefd[1]);
282
283   /* On Linux, tell the bus process to exit if this process goes away */
284 #ifdef __linux__
285   prctl (PR_SET_PDEATHSIG, 15);
286 #endif
287 }
288
289 static gboolean
290 ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
291 {
292   char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", "3", NULL };
293   GPid pid;
294   char addr_buf[2048];
295   GError *error = NULL;
296
297   if (pipe (app->pipefd) < 0)
298     g_error ("Failed to create pipe: %s", strerror (errno));
299
300   g_clear_pointer (&app->a11y_launch_error_message, g_free);
301
302   if (!g_spawn_async (NULL,
303                       argv,
304                       NULL,
305                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
306                       setup_bus_child_daemon,
307                       app,
308                       &pid,
309                       &error))
310     {
311       app->a11y_bus_pid = -1;
312       app->a11y_launch_error_message = g_strdup (error->message);
313       g_clear_error (&error);
314       goto error;
315     }
316
317   close (app->pipefd[1]);
318   app->pipefd[1] = -1;
319
320   g_child_watch_add (pid, on_bus_exited, app);
321
322   app->state = A11Y_BUS_STATE_READING_ADDRESS;
323   app->a11y_bus_pid = pid;
324   g_debug ("Launched a11y bus, child is %ld", (long) pid);
325   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
326     {
327       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
328       kill (app->a11y_bus_pid, SIGTERM);
329       app->a11y_bus_pid = -1;
330       goto error;
331     }
332   close (app->pipefd[0]);
333   app->pipefd[0] = -1;
334   app->state = A11Y_BUS_STATE_RUNNING;
335
336   /* Trim the trailing newline */
337   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
338   g_debug ("a11y bus address: %s", app->a11y_bus_address);
339
340   return TRUE;
341
342 error:
343   close (app->pipefd[0]);
344   close (app->pipefd[1]);
345   app->state = A11Y_BUS_STATE_ERROR;
346
347   return FALSE;
348 }
349 #else
350 static gboolean
351 ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
352 {
353         return FALSE;
354 }
355 #endif
356
357 #ifdef DBUS_BROKER
358 static void
359 setup_bus_child_broker (gpointer data)
360 {
361   A11yBusLauncher *app = data;
362   gchar *pid_str;
363   (void) app;
364
365   dup2 (app->listenfd, 3);
366   close (app->listenfd);
367   g_setenv("LISTEN_FDS", "1", TRUE);
368
369   pid_str = g_strdup_printf("%u", getpid());
370   g_setenv("LISTEN_PID", pid_str, TRUE);
371   g_free(pid_str);
372
373   /* Tell the bus process to exit if this process goes away */
374   prctl (PR_SET_PDEATHSIG, SIGTERM);
375 }
376
377 static gboolean
378 ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
379 {
380   char *argv[] = { DBUS_BROKER, config_path, "--scope", "user", NULL };
381   struct sockaddr_un addr = { .sun_family = AF_UNIX };
382   socklen_t addr_len = sizeof(addr);
383   GPid pid;
384   GError *error = NULL;
385
386   if ((app->listenfd = socket (PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
387     g_error ("Failed to create listening socket: %s", strerror (errno));
388
389   if (bind (app->listenfd, (struct sockaddr *)&addr, sizeof(sa_family_t)) < 0)
390     g_error ("Failed to bind listening socket: %s", strerror (errno));
391
392   if (getsockname (app->listenfd, (struct sockaddr *)&addr, &addr_len) < 0)
393     g_error ("Failed to get socket name for listening socket: %s", strerror(errno));
394
395   if (listen (app->listenfd, 1024) < 0)
396     g_error ("Failed to listen on socket: %s", strerror(errno));
397
398   g_clear_pointer (&app->a11y_launch_error_message, g_free);
399
400   if (!g_spawn_async (NULL,
401                       argv,
402                       NULL,
403                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
404                       setup_bus_child_broker,
405                       app,
406                       &pid,
407                       &error))
408     {
409       app->a11y_bus_pid = -1;
410       app->a11y_launch_error_message = g_strdup (error->message);
411       g_clear_error (&error);
412       goto error;
413     }
414
415   close (app->listenfd);
416   app->listenfd = -1;
417
418   g_child_watch_add (pid, on_bus_exited, app);
419   app->a11y_bus_pid = pid;
420   g_debug ("Launched a11y bus, child is %ld", (long) pid);
421   app->state = A11Y_BUS_STATE_RUNNING;
422
423   app->a11y_bus_address = g_strconcat("unix:abstract=", addr.sun_path + 1, NULL);
424   g_debug ("a11y bus address: %s", app->a11y_bus_address);
425
426   return TRUE;
427
428 error:
429   close (app->listenfd);
430   app->state = A11Y_BUS_STATE_ERROR;
431
432   return FALSE;
433 }
434 #else
435 static gboolean
436 ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
437 {
438         return FALSE;
439 }
440 #endif
441
442 static gboolean
443 ensure_a11y_bus (A11yBusLauncher *app)
444 {
445   char *config_path = NULL;
446   gboolean success = FALSE;
447
448   if (app->a11y_bus_pid != 0)
449     return FALSE;
450
451   if (g_file_test (SYSCONFDIR"/at-spi2/accessibility.conf", G_FILE_TEST_EXISTS))
452       config_path = "--config-file="SYSCONFDIR"/at-spi2/accessibility.conf";
453   else
454       config_path = "--config-file="DATADIR"/defaults/at-spi2/accessibility.conf";
455
456 #ifdef WANT_DBUS_BROKER
457     success = ensure_a11y_bus_broker (app, config_path);
458     if (!success)
459       {
460         if (!ensure_a11y_bus_daemon (app, config_path))
461             return FALSE;
462       }
463 #else
464     success = ensure_a11y_bus_daemon (app, config_path);
465     if (!success)
466       {
467         if (!ensure_a11y_bus_broker (app, config_path))
468             return FALSE;
469       }
470 #endif
471
472 #ifdef HAVE_X11
473   if (g_getenv ("DISPLAY") != NULL && g_getenv ("WAYLAND_DISPLAY") == NULL)
474     {
475       Display *display = XOpenDisplay (NULL);
476       if (display)
477         {
478           Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
479           XChangeProperty (display,
480                            XDefaultRootWindow (display),
481                            bus_address_atom,
482                            XA_STRING, 8, PropModeReplace,
483                            (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
484           XFlush (display);
485           XCloseDisplay (display);
486           app->x11_prop_set = TRUE;
487         }
488     }
489 #endif
490
491   return TRUE;
492 }
493
494 static void
495 handle_method_call (GDBusConnection       *connection,
496                     const gchar           *sender,
497                     const gchar           *object_path,
498                     const gchar           *interface_name,
499                     const gchar           *method_name,
500                     GVariant              *parameters,
501                     GDBusMethodInvocation *invocation,
502                     gpointer               user_data)
503 {
504   A11yBusLauncher *app = user_data;
505
506   if (g_strcmp0 (method_name, "GetAddress") == 0)
507     {
508       ensure_a11y_bus (app);
509       if (app->a11y_bus_pid > 0)
510         g_dbus_method_invocation_return_value (invocation,
511                                                g_variant_new ("(s)", app->a11y_bus_address));
512       else
513         g_dbus_method_invocation_return_dbus_error (invocation,
514                                                     "org.a11y.Bus.Error",
515                                                     app->a11y_launch_error_message);
516     }
517 }
518
519 static GVariant *
520 handle_get_property  (GDBusConnection       *connection,
521                       const gchar           *sender,
522                       const gchar           *object_path,
523                       const gchar           *interface_name,
524                       const gchar           *property_name,
525                     GError **error,
526                     gpointer               user_data)
527 {
528   A11yBusLauncher *app = user_data;
529
530   if (g_strcmp0 (property_name, "IsEnabled") == 0)
531     return g_variant_new ("b", app->a11y_enabled);
532   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
533     return g_variant_new ("b", app->screen_reader_enabled);
534   else
535     return NULL;
536 }
537
538 static void
539 handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled,
540                                gboolean notify_gsettings)
541 {
542   GVariantBuilder builder;
543   GVariantBuilder invalidated_builder;
544
545   if (enabled == app->a11y_enabled)
546     return;
547
548   app->a11y_enabled = enabled;
549
550   if (notify_gsettings && app->interface_schema)
551     {
552       g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
553                               enabled);
554       g_settings_sync ();
555     }
556
557   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
558   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
559   g_variant_builder_add (&builder, "{sv}", "IsEnabled",
560                          g_variant_new_boolean (enabled));
561
562   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
563                                  "org.freedesktop.DBus.Properties",
564                                  "PropertiesChanged",
565                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
566                                                 &builder,
567                                                 &invalidated_builder),
568                                  NULL);
569
570   g_variant_builder_clear (&builder);
571   g_variant_builder_clear (&invalidated_builder);
572 }
573
574 static void
575 handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled,
576                                gboolean notify_gsettings)
577 {
578   GVariantBuilder builder;
579   GVariantBuilder invalidated_builder;
580
581   if (enabled == app->screen_reader_enabled)
582     return;
583
584   /* If the screen reader is being enabled, we should enable accessibility
585    * if it isn't enabled already */
586   if (enabled)
587     handle_a11y_enabled_change (app, enabled, notify_gsettings);
588
589   app->screen_reader_enabled = enabled;
590
591   if (notify_gsettings && app->a11y_schema)
592     {
593       g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
594                               enabled);
595       g_settings_sync ();
596     }
597
598   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
599   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
600   g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
601                          g_variant_new_boolean (enabled));
602
603   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
604                                  "org.freedesktop.DBus.Properties",
605                                  "PropertiesChanged",
606                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
607                                                 &builder,
608                                                 &invalidated_builder),
609                                  NULL);
610
611   g_variant_builder_clear (&builder);
612   g_variant_builder_clear (&invalidated_builder);
613 }
614
615 static gboolean
616 handle_set_property  (GDBusConnection       *connection,
617                       const gchar           *sender,
618                       const gchar           *object_path,
619                       const gchar           *interface_name,
620                       const gchar           *property_name,
621                       GVariant *value,
622                     GError **error,
623                     gpointer               user_data)
624 {
625   A11yBusLauncher *app = user_data;
626   const gchar *type = g_variant_get_type_string (value);
627   gboolean enabled;
628   
629   if (g_strcmp0 (type, "b") != 0)
630     {
631       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
632                        "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
633       return FALSE;
634     }
635
636   enabled = g_variant_get_boolean (value);
637
638   if (g_strcmp0 (property_name, "IsEnabled") == 0)
639     {
640       handle_a11y_enabled_change (app, enabled, TRUE);
641       return TRUE;
642     }
643   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
644     {
645       handle_screen_reader_enabled_change (app, enabled, TRUE);
646       return TRUE;
647     }
648   else
649     {
650       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
651                        "Unknown property '%s'", property_name);
652       return FALSE;
653     }
654 }
655
656 static const GDBusInterfaceVTable bus_vtable =
657 {
658   handle_method_call,
659   NULL, /* handle_get_property, */
660   NULL  /* handle_set_property */
661 };
662
663 static const GDBusInterfaceVTable status_vtable =
664 {
665   NULL, /* handle_method_call */
666   handle_get_property,
667   handle_set_property
668 };
669
670 static void
671 on_bus_acquired (GDBusConnection *connection,
672                  const gchar     *name,
673                  gpointer         user_data)
674 {
675   A11yBusLauncher *app = user_data;
676   GError *error;
677   guint registration_id;
678   
679   if (connection == NULL)
680     {
681       g_main_loop_quit (app->loop);
682       return;
683     }
684   app->session_bus = connection;
685
686   error = NULL;
687   registration_id = g_dbus_connection_register_object (connection,
688                                                        "/org/a11y/bus",
689                                                        introspection_data->interfaces[0],
690                                                        &bus_vtable,
691                                                        _global_app,
692                                                        NULL,
693                                                        &error);
694   if (registration_id == 0)
695     {
696       g_error ("%s", error->message);
697       g_clear_error (&error);
698     }
699
700   g_dbus_connection_register_object (connection,
701                                      "/org/a11y/bus",
702                                      introspection_data->interfaces[1],
703                                      &status_vtable,
704                                      _global_app,
705                                      NULL,
706                                      NULL);
707 }
708
709 static void
710 on_name_lost (GDBusConnection *connection,
711               const gchar     *name,
712               gpointer         user_data)
713 {
714   A11yBusLauncher *app = user_data;
715   if (app->session_bus == NULL
716       && connection == NULL
717       && app->a11y_launch_error_message == NULL)
718     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
719   g_main_loop_quit (app->loop);
720 }
721
722 static void
723 on_name_acquired (GDBusConnection *connection,
724                   const gchar     *name,
725                   gpointer         user_data)
726 {
727   A11yBusLauncher *app = user_data;
728
729   if (app->launch_immediately)
730     {
731       ensure_a11y_bus (app);
732       if (app->state == A11Y_BUS_STATE_ERROR)
733         {
734           g_main_loop_quit (app->loop);
735           return;
736         }
737     }
738
739   g_bus_watch_name (G_BUS_TYPE_SESSION,
740                     "org.gnome.SessionManager",
741                     G_BUS_NAME_WATCHER_FLAGS_NONE,
742                     name_appeared_handler, NULL,
743                     user_data, NULL);
744 }
745
746 static int sigterm_pipefd[2];
747
748 static void
749 sigterm_handler (int signum)
750 {
751   write (sigterm_pipefd[1], "X", 1);
752 }
753
754 static gboolean
755 on_sigterm_pipe (GIOChannel  *channel,
756                  GIOCondition condition,
757                  gpointer     data)
758 {
759   A11yBusLauncher *app = data;
760   
761   g_main_loop_quit (app->loop);
762
763   return FALSE;
764 }
765
766 static void
767 init_sigterm_handling (A11yBusLauncher *app)
768 {
769   GIOChannel *sigterm_channel;
770
771   if (pipe (sigterm_pipefd) < 0)
772     g_error ("Failed to create pipe: %s", strerror (errno));
773   signal (SIGTERM, sigterm_handler);
774
775   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
776   g_io_add_watch (sigterm_channel,
777                   G_IO_IN | G_IO_ERR | G_IO_HUP,
778                   on_sigterm_pipe,
779                   app);
780 }
781
782 static GSettings *
783 get_schema (const gchar *name)
784 {
785 #if GLIB_CHECK_VERSION (2, 32, 0)
786   GSettingsSchemaSource *source = g_settings_schema_source_get_default ();
787   GSettingsSchema *schema = g_settings_schema_source_lookup (source, name, FALSE);
788
789   if (schema == NULL)
790     return NULL;
791
792   return g_settings_new_full (schema, NULL, NULL);
793 #else
794   const char * const *schemas = NULL;
795   gint i;
796
797   schemas = g_settings_list_schemas ();
798   for (i = 0; schemas[i]; i++)
799   {
800     if (!strcmp (schemas[i], name))
801       return g_settings_new (schemas[i]);
802   }
803
804   return NULL;
805 #endif
806 }
807
808 static void
809 gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
810 {
811   gboolean new_val = g_settings_get_boolean (gsettings, key);
812
813   if (!strcmp (key, "toolkit-accessibility"))
814     handle_a11y_enabled_change (_global_app, new_val, FALSE);
815   else if (!strcmp (key, "screen-reader-enabled"))
816     handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
817 }
818
819 int
820 main (int    argc,
821       char **argv)
822 {
823   gboolean a11y_set = FALSE;
824   gboolean screen_reader_set = FALSE;
825   gint i;
826
827   _global_app = g_slice_new0 (A11yBusLauncher);
828   _global_app->loop = g_main_loop_new (NULL, FALSE);
829
830   for (i = 1; i < argc; i++)
831     {
832       if (!strcmp (argv[i], "--launch-immediately"))
833         _global_app->launch_immediately = TRUE;
834       else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 1)
835         a11y_set = TRUE;
836       else if (sscanf (argv[i], "--screen-reader=%d",
837                        &_global_app->screen_reader_enabled) == 1)
838         screen_reader_set = TRUE;
839     else
840       g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
841     }
842
843   _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
844   _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
845
846   if (!a11y_set)
847     {
848       _global_app->a11y_enabled = _global_app->interface_schema
849                                   ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
850                                   : _global_app->launch_immediately;
851     }
852
853   if (!screen_reader_set)
854     {
855       _global_app->screen_reader_enabled = _global_app->a11y_schema
856                                   ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
857                                   : FALSE;
858     }
859
860   if (_global_app->interface_schema)
861     g_signal_connect (_global_app->interface_schema,
862                       "changed::toolkit-accessibility",
863                       G_CALLBACK (gsettings_key_changed), _global_app);
864
865   if (_global_app->a11y_schema)
866     g_signal_connect (_global_app->a11y_schema,
867                       "changed::screen-reader-enabled",
868                       G_CALLBACK (gsettings_key_changed), _global_app);
869
870   init_sigterm_handling (_global_app);
871
872   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
873   g_assert (introspection_data != NULL);
874
875   _global_app->name_owner_id =
876     g_bus_own_name (G_BUS_TYPE_SESSION,
877                     "org.a11y.Bus",
878                     G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
879                     on_bus_acquired,
880                     on_name_acquired,
881                     on_name_lost,
882                     _global_app,
883                     NULL);
884
885   g_main_loop_run (_global_app->loop);
886
887   if (_global_app->a11y_bus_pid > 0)
888     kill (_global_app->a11y_bus_pid, SIGTERM);
889
890   /* Clear the X property if our bus is gone; in the case where e.g. 
891    * GDM is launching a login on an X server it was using before,
892    * we don't want early login processes to pick up the stale address.
893    */
894 #ifdef HAVE_X11
895   if (_global_app->x11_prop_set)
896     {
897       Display *display = XOpenDisplay (NULL);
898       if (display)
899         {
900           Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
901           XDeleteProperty (display,
902                            XDefaultRootWindow (display),
903                            bus_address_atom);
904
905           XFlush (display);
906           XCloseDisplay (display);
907         }
908     }
909 #endif
910
911   if (_global_app->a11y_launch_error_message)
912     {
913       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
914       return 1;
915     }
916   return 0;
917 }