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