1 /* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
3 * at-spi-bus-launcher: Manage the a11y bus as a child process
5 * Copyright 2011 Red Hat, Inc.
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.
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.
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.
33 #include <X11/Xatom.h>
36 A11Y_BUS_STATE_IDLE = 0,
37 A11Y_BUS_STATE_READING_ADDRESS,
38 A11Y_BUS_STATE_RUNNING,
44 gboolean launch_immediately;
45 GDBusConnection *session_bus;
48 /* -1 == error, 0 == pending, > 0 == running */
50 char *a11y_bus_address;
52 char *a11y_launch_error_message;
55 static A11yBusLauncher *_global_app = NULL;
57 static const gchar introspection_xml[] =
59 " <interface name='org.a11y.Bus'>"
60 " <method name='GetAddress'>"
61 " <arg type='s' name='address' direction='out'/>"
65 static GDBusNodeInfo *introspection_data = NULL;
68 setup_bus_child (gpointer data)
70 A11yBusLauncher *app = data;
73 close (app->pipefd[0]);
74 dup2 (app->pipefd[1], 3);
75 close (app->pipefd[1]);
77 /* On Linux, tell the bus process to exit if this process goes away */
79 #include <sys/prctl.h>
80 prctl (PR_SET_PDEATHSIG, 15);
85 * unix_read_all_fd_to_string:
87 * Read all data from a file descriptor to a C string buffer.
90 unix_read_all_fd_to_string (int fd,
96 while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
101 max_bytes -= bytes_read;
108 on_bus_exited (GPid pid,
112 A11yBusLauncher *app = data;
114 app->a11y_bus_pid = -1;
115 app->state = A11Y_BUS_STATE_ERROR;
116 if (app->a11y_launch_error_message == NULL)
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));
125 g_main_loop_quit (app->loop);
129 ensure_a11y_bus (A11yBusLauncher *app)
132 char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
134 GError *error = NULL;
136 if (app->a11y_bus_pid != 0)
139 argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
141 if (pipe (app->pipefd) < 0)
142 g_error ("Failed to create pipe: %s", strerror (errno));
144 if (!g_spawn_async (NULL,
147 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
153 app->a11y_bus_pid = -1;
154 app->a11y_launch_error_message = g_strdup (error->message);
155 g_clear_error (&error);
159 close (app->pipefd[1]);
162 g_child_watch_add (pid, on_bus_exited, app);
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)))
169 app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
170 kill (app->a11y_bus_pid, SIGTERM);
173 close (app->pipefd[0]);
175 app->state = A11Y_BUS_STATE_RUNNING;
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);
182 Display *display = XOpenDisplay (NULL);
185 Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
186 XChangeProperty (display,
187 XDefaultRootWindow (display),
189 XA_STRING, 8, PropModeReplace,
190 (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
193 XCloseDisplay (display);
199 close (app->pipefd[0]);
200 close (app->pipefd[1]);
201 app->state = A11Y_BUS_STATE_ERROR;
205 handle_method_call (GDBusConnection *connection,
207 const gchar *object_path,
208 const gchar *interface_name,
209 const gchar *method_name,
210 GVariant *parameters,
211 GDBusMethodInvocation *invocation,
214 A11yBusLauncher *app = user_data;
216 if (g_strcmp0 (method_name, "GetAddress") == 0)
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));
223 g_dbus_method_invocation_return_dbus_error (invocation,
224 "org.a11y.Bus.Error",
225 app->a11y_launch_error_message);
229 static const GDBusInterfaceVTable interface_vtable =
233 NULL /* handle_set_property */
237 on_bus_acquired (GDBusConnection *connection,
241 A11yBusLauncher *app = user_data;
243 guint registration_id;
245 if (connection == NULL)
247 g_main_loop_quit (app->loop);
250 app->session_bus = connection;
252 if (app->launch_immediately)
254 ensure_a11y_bus (app);
255 if (app->state == A11Y_BUS_STATE_ERROR)
257 g_main_loop_quit (app->loop);
263 registration_id = g_dbus_connection_register_object (connection,
265 introspection_data->interfaces[0],
270 if (registration_id == 0)
271 g_error ("%s", error->message);
275 on_name_lost (GDBusConnection *connection,
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);
288 on_name_acquired (GDBusConnection *connection,
292 A11yBusLauncher *app = user_data;
296 static int sigterm_pipefd[2];
299 sigterm_handler (int signum)
301 write (sigterm_pipefd[1], "X", 1);
305 on_sigterm_pipe (GIOChannel *channel,
306 GIOCondition condition,
309 A11yBusLauncher *app = data;
311 g_main_loop_quit (app->loop);
317 init_sigterm_handling (A11yBusLauncher *app)
319 GIOChannel *sigterm_channel;
321 if (pipe (sigterm_pipefd) < 0)
322 g_error ("Failed to create pipe: %s", strerror (errno));
323 signal (SIGTERM, sigterm_handler);
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,
333 is_a11y_using_corba (void)
335 char *gconf_argv[] = { "gconftool-2", "--get", "/desktop/gnome/interface/at-spi-corba", NULL };
338 gboolean result = FALSE;
340 if (!g_spawn_sync (NULL, gconf_argv, NULL,
341 G_SPAWN_SEARCH_PATH, NULL, NULL, &stdout, NULL, &estatus, NULL))
345 if (g_str_has_prefix (stdout, "true"))
356 GError *error = NULL;
358 GDBusConnection *session_bus;
363 if (is_a11y_using_corba ())
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);
370 init_sigterm_handling (_global_app);
372 introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
373 g_assert (introspection_data != NULL);
375 name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
377 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
384 g_main_loop_run (_global_app->loop);
386 if (_global_app->a11y_bus_pid > 0)
387 kill (_global_app->a11y_bus_pid, SIGTERM);
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.
394 Display *display = XOpenDisplay (NULL);
397 Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
398 XDeleteProperty (display,
399 XDefaultRootWindow (display),
403 XCloseDisplay (display);
407 if (_global_app->a11y_launch_error_message)
409 g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);