Clear AT_SPI_BUS property on launcher exit
[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
31 #include <gio/gio.h>
32 #include <X11/Xlib.h>
33 #include <X11/Xatom.h>
34
35 typedef enum {
36   A11Y_BUS_STATE_IDLE = 0,
37   A11Y_BUS_STATE_READING_ADDRESS,
38   A11Y_BUS_STATE_RUNNING,
39   A11Y_BUS_STATE_ERROR
40 } A11yBusState;
41
42 typedef struct {
43   GMainLoop *loop;
44   gboolean launch_immediately;
45   GDBusConnection *session_bus;
46
47   A11yBusState state;
48   /* -1 == error, 0 == pending, > 0 == running */
49   int a11y_bus_pid;
50   char *a11y_bus_address;
51   int pipefd[2];
52   char *a11y_launch_error_message;
53 } A11yBusLauncher;
54
55 static A11yBusLauncher *_global_app = NULL;
56
57 static const gchar introspection_xml[] =
58   "<node>"
59   "  <interface name='org.a11y.Bus'>"
60   "    <method name='GetAddress'>"
61   "      <arg type='s' name='address' direction='out'/>"
62   "    </method>"
63   "  </interface>"
64   "</node>";
65 static GDBusNodeInfo *introspection_data = NULL;
66
67 static void
68 setup_bus_child (gpointer data)
69 {
70   A11yBusLauncher *app = data;
71   (void) app;
72
73   close (app->pipefd[0]);
74   dup2 (app->pipefd[1], 3);
75   close (app->pipefd[1]);
76
77   /* On Linux, tell the bus process to exit if this process goes away */
78 #ifdef __linux
79 #include <sys/prctl.h>
80   prctl (PR_SET_PDEATHSIG, 15);
81 #endif  
82 }
83
84 /**
85  * unix_read_all_fd_to_string:
86  *
87  * Read all data from a file descriptor to a C string buffer.
88  */
89 static gboolean
90 unix_read_all_fd_to_string (int      fd,
91                             char    *buf,
92                             ssize_t  max_bytes)
93 {
94   ssize_t bytes_read;
95
96   while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
97     {
98       if (bytes_read < 0)
99         return FALSE;
100       buf += bytes_read;
101       max_bytes -= bytes_read;
102     }
103   *buf = '\0';
104   return TRUE;
105 }
106
107 static void
108 on_bus_exited (GPid     pid,
109                gint     status,
110                gpointer data)
111 {
112   A11yBusLauncher *app = data;
113   
114   app->a11y_bus_pid = -1;
115   app->state = A11Y_BUS_STATE_ERROR;
116   if (app->a11y_launch_error_message == NULL)
117     {
118       if (WIFEXITED (status))
119         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
120       else if (WIFSIGNALED (status))
121         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
122       else if (WIFSTOPPED (status))
123         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
124     }
125   g_main_loop_quit (app->loop);
126
127
128 static void
129 ensure_a11y_bus (A11yBusLauncher *app)
130 {
131   GPid pid;
132   char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
133   char addr_buf[2048];
134   GError *error = NULL;
135
136   if (app->a11y_bus_pid != 0)
137     return;
138   
139   argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
140
141   if (pipe (app->pipefd) < 0)
142     g_error ("Failed to create pipe: %s", strerror (errno));
143   
144   if (!g_spawn_async (NULL,
145                       argv,
146                       NULL,
147                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
148                       setup_bus_child,
149                       app,
150                       &pid,
151                       &error))
152     {
153       app->a11y_bus_pid = -1;
154       app->a11y_launch_error_message = g_strdup (error->message);
155       g_clear_error (&error);
156       goto error;
157     }
158
159   close (app->pipefd[1]);
160   app->pipefd[1] = -1;
161
162   g_child_watch_add (pid, on_bus_exited, app);
163
164   app->state = A11Y_BUS_STATE_READING_ADDRESS;
165   app->a11y_bus_pid = pid;
166   g_debug ("Launched a11y bus, child is %ld", (long) pid);
167   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
168     {
169       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
170       kill (app->a11y_bus_pid, SIGTERM);
171       goto error;
172     }
173   close (app->pipefd[0]);
174   app->pipefd[0] = -1;
175   app->state = A11Y_BUS_STATE_RUNNING;
176
177   /* Trim the trailing newline */
178   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
179   g_debug ("a11y bus address: %s", app->a11y_bus_address);
180
181   {
182     Display *display = XOpenDisplay (NULL);
183     if (display)
184       {
185         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
186         XChangeProperty (display,
187                          XDefaultRootWindow (display),
188                          bus_address_atom,
189                          XA_STRING, 8, PropModeReplace,
190                          (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
191       }
192     XFlush (display);
193     XCloseDisplay (display);
194   }
195
196   return;
197   
198  error:
199   close (app->pipefd[0]);
200   close (app->pipefd[1]);
201   app->state = A11Y_BUS_STATE_ERROR;
202 }
203
204 static void
205 handle_method_call (GDBusConnection       *connection,
206                     const gchar           *sender,
207                     const gchar           *object_path,
208                     const gchar           *interface_name,
209                     const gchar           *method_name,
210                     GVariant              *parameters,
211                     GDBusMethodInvocation *invocation,
212                     gpointer               user_data)
213 {
214   A11yBusLauncher *app = user_data;
215
216   if (g_strcmp0 (method_name, "GetAddress") == 0)
217     {
218       ensure_a11y_bus (app);
219       if (app->a11y_bus_pid > 0)
220         g_dbus_method_invocation_return_value (invocation,
221                                                g_variant_new ("(s)", app->a11y_bus_address));
222       else
223         g_dbus_method_invocation_return_dbus_error (invocation,
224                                                     "org.a11y.Bus.Error",
225                                                     app->a11y_launch_error_message);
226     }
227 }
228
229 static const GDBusInterfaceVTable interface_vtable =
230 {
231   handle_method_call,
232   NULL,
233   NULL  /* handle_set_property */
234 };
235
236 static void
237 on_bus_acquired (GDBusConnection *connection,
238                  const gchar     *name,
239                  gpointer         user_data)
240 {
241   A11yBusLauncher *app = user_data;
242   GError *error;
243   guint registration_id;
244   
245   if (connection == NULL)
246     {
247       g_main_loop_quit (app->loop);
248       return;
249     }
250   app->session_bus = connection;
251
252   if (app->launch_immediately)
253     {
254       ensure_a11y_bus (app);
255       if (app->state == A11Y_BUS_STATE_ERROR)
256         {
257           g_main_loop_quit (app->loop);
258           return;
259         }
260     }
261
262   error = NULL;
263   registration_id = g_dbus_connection_register_object (connection,
264                                                        "/org/a11y/bus",
265                                                        introspection_data->interfaces[0],
266                                                        &interface_vtable,
267                                                        _global_app,
268                                                        NULL,
269                                                        &error);
270   if (registration_id == 0)
271     g_error ("%s", error->message);
272 }
273
274 static void
275 on_name_lost (GDBusConnection *connection,
276               const gchar     *name,
277               gpointer         user_data)
278 {
279   A11yBusLauncher *app = user_data;
280   if (app->session_bus == NULL
281       && connection == NULL
282       && app->a11y_launch_error_message == NULL)
283     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
284   g_main_loop_quit (app->loop);
285 }
286
287 static void
288 on_name_acquired (GDBusConnection *connection,
289                   const gchar     *name,
290                   gpointer         user_data)
291 {
292   A11yBusLauncher *app = user_data;
293   (void) app;
294 }
295
296 static int sigterm_pipefd[2];
297
298 static void
299 sigterm_handler (int signum)
300 {
301   write (sigterm_pipefd[1], "X", 1);
302 }
303
304 static gboolean
305 on_sigterm_pipe (GIOChannel  *channel,
306                  GIOCondition condition,
307                  gpointer     data)
308 {
309   A11yBusLauncher *app = data;
310   
311   g_main_loop_quit (app->loop);
312
313   return FALSE;
314 }
315
316 static void
317 init_sigterm_handling (A11yBusLauncher *app)
318 {
319   GIOChannel *sigterm_channel;
320
321   if (pipe (sigterm_pipefd) < 0)
322     g_error ("Failed to create pipe: %s", strerror (errno));
323   signal (SIGTERM, sigterm_handler);
324
325   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
326   g_io_add_watch (sigterm_channel,
327                   G_IO_IN | G_IO_ERR | G_IO_HUP,
328                   on_sigterm_pipe,
329                   app);
330 }
331
332 static gboolean
333 is_a11y_using_corba (void)
334 {
335   char *gconf_argv[] = { "gconftool-2", "--get", "/desktop/gnome/interface/at-spi-corba", NULL };
336   char *stdout = NULL;
337   int estatus;
338   gboolean result = FALSE;
339
340   if (!g_spawn_sync (NULL, gconf_argv, NULL,
341                      G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout, NULL, &estatus, NULL))
342     goto out;
343   if (estatus != 0)
344     goto out;
345   if (g_str_has_prefix (stdout, "true"))
346     result = TRUE;
347  out:
348   g_free (stdout);
349   return result;
350 }
351
352 int
353 main (int    argc,
354       char **argv)
355 {
356   GError *error = NULL;
357   GMainLoop *loop;
358   GDBusConnection *session_bus;
359   int name_owner_id;
360
361   g_type_init ();
362
363   if (is_a11y_using_corba ())
364     return 0;
365
366   _global_app = g_slice_new0 (A11yBusLauncher);
367   _global_app->loop = g_main_loop_new (NULL, FALSE);
368   _global_app->launch_immediately = (argc == 2 && strcmp (argv[1], "--launch-immediately") == 0);
369
370   init_sigterm_handling (_global_app);
371
372   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
373   g_assert (introspection_data != NULL);
374
375   name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
376                                   "org.a11y.Bus",
377                                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
378                                   on_bus_acquired,
379                                   on_name_acquired,
380                                   on_name_lost,
381                                   _global_app,
382                                   NULL);
383
384   g_main_loop_run (_global_app->loop);
385
386   if (_global_app->a11y_bus_pid > 0)
387     kill (_global_app->a11y_bus_pid, SIGTERM);
388
389   /* Clear the X property if our bus is gone; in the case where e.g. 
390    * GDM is launching a login on an X server it was using before,
391    * we don't want early login processes to pick up the stale address.
392    */
393   {
394     Display *display = XOpenDisplay (NULL);
395     if (display)
396       {
397         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
398         XDeleteProperty (display,
399                          XDefaultRootWindow (display),
400                          bus_address_atom);
401       }
402     XFlush (display);
403     XCloseDisplay (display);
404   }
405     
406   if (_global_app->a11y_launch_error_message)
407     {
408       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
409       return 1;
410     }
411   return 0;
412 }