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.
34 #include <X11/Xatom.h>
37 A11Y_BUS_STATE_IDLE = 0,
38 A11Y_BUS_STATE_READING_ADDRESS,
39 A11Y_BUS_STATE_RUNNING,
45 gboolean launch_immediately;
46 GDBusConnection *session_bus;
49 /* -1 == error, 0 == pending, > 0 == running */
51 char *a11y_bus_address;
53 char *a11y_launch_error_message;
56 static A11yBusLauncher *_global_app = NULL;
58 static const gchar introspection_xml[] =
60 " <interface name='org.a11y.Bus'>"
61 " <method name='GetAddress'>"
62 " <arg type='s' name='address' direction='out'/>"
66 static GDBusNodeInfo *introspection_data = NULL;
69 setup_bus_child (gpointer data)
71 A11yBusLauncher *app = data;
74 close (app->pipefd[0]);
75 dup2 (app->pipefd[1], 3);
76 close (app->pipefd[1]);
78 /* On Linux, tell the bus process to exit if this process goes away */
80 #include <sys/prctl.h>
81 prctl (PR_SET_PDEATHSIG, 15);
86 * unix_read_all_fd_to_string:
88 * Read all data from a file descriptor to a C string buffer.
91 unix_read_all_fd_to_string (int fd,
97 while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
102 max_bytes -= bytes_read;
109 on_bus_exited (GPid pid,
113 A11yBusLauncher *app = data;
115 app->a11y_bus_pid = -1;
116 app->state = A11Y_BUS_STATE_ERROR;
117 if (app->a11y_launch_error_message == NULL)
119 if (WIFEXITED (status))
120 app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
121 else if (WIFSIGNALED (status))
122 app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
123 else if (WIFSTOPPED (status))
124 app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
126 g_main_loop_quit (app->loop);
130 ensure_a11y_bus (A11yBusLauncher *app)
133 char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
135 GError *error = NULL;
137 if (app->a11y_bus_pid != 0)
140 argv[1] = g_strdup_printf ("--config-file=%s/at-spi2/accessibility.conf", SYSCONFDIR);
142 if (pipe (app->pipefd) < 0)
143 g_error ("Failed to create pipe: %s", strerror (errno));
145 if (!g_spawn_async (NULL,
148 G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
154 app->a11y_bus_pid = -1;
155 app->a11y_launch_error_message = g_strdup (error->message);
156 g_clear_error (&error);
160 close (app->pipefd[1]);
163 g_child_watch_add (pid, on_bus_exited, app);
165 app->state = A11Y_BUS_STATE_READING_ADDRESS;
166 app->a11y_bus_pid = pid;
167 g_debug ("Launched a11y bus, child is %ld", (long) pid);
168 if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
170 app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
171 kill (app->a11y_bus_pid, SIGTERM);
174 close (app->pipefd[0]);
176 app->state = A11Y_BUS_STATE_RUNNING;
178 /* Trim the trailing newline */
179 app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
180 g_debug ("a11y bus address: %s", app->a11y_bus_address);
183 Display *display = XOpenDisplay (NULL);
186 Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
187 XChangeProperty (display,
188 XDefaultRootWindow (display),
190 XA_STRING, 8, PropModeReplace,
191 (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
194 XCloseDisplay (display);
200 close (app->pipefd[0]);
201 close (app->pipefd[1]);
202 app->state = A11Y_BUS_STATE_ERROR;
206 handle_method_call (GDBusConnection *connection,
208 const gchar *object_path,
209 const gchar *interface_name,
210 const gchar *method_name,
211 GVariant *parameters,
212 GDBusMethodInvocation *invocation,
215 A11yBusLauncher *app = user_data;
217 if (g_strcmp0 (method_name, "GetAddress") == 0)
219 ensure_a11y_bus (app);
220 if (app->a11y_bus_pid > 0)
221 g_dbus_method_invocation_return_value (invocation,
222 g_variant_new ("(s)", app->a11y_bus_address));
224 g_dbus_method_invocation_return_dbus_error (invocation,
225 "org.a11y.Bus.Error",
226 app->a11y_launch_error_message);
230 static const GDBusInterfaceVTable interface_vtable =
234 NULL /* handle_set_property */
238 on_bus_acquired (GDBusConnection *connection,
242 A11yBusLauncher *app = user_data;
244 guint registration_id;
246 if (connection == NULL)
248 g_main_loop_quit (app->loop);
251 app->session_bus = connection;
253 if (app->launch_immediately)
255 ensure_a11y_bus (app);
256 if (app->state == A11Y_BUS_STATE_ERROR)
258 g_main_loop_quit (app->loop);
264 registration_id = g_dbus_connection_register_object (connection,
266 introspection_data->interfaces[0],
271 if (registration_id == 0)
272 g_error ("%s", error->message);
276 on_name_lost (GDBusConnection *connection,
280 A11yBusLauncher *app = user_data;
281 if (app->session_bus == NULL
282 && connection == NULL
283 && app->a11y_launch_error_message == NULL)
284 app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
285 g_main_loop_quit (app->loop);
289 on_name_acquired (GDBusConnection *connection,
293 A11yBusLauncher *app = user_data;
297 static int sigterm_pipefd[2];
300 sigterm_handler (int signum)
302 write (sigterm_pipefd[1], "X", 1);
306 on_sigterm_pipe (GIOChannel *channel,
307 GIOCondition condition,
310 A11yBusLauncher *app = data;
312 g_main_loop_quit (app->loop);
318 init_sigterm_handling (A11yBusLauncher *app)
320 GIOChannel *sigterm_channel;
322 if (pipe (sigterm_pipefd) < 0)
323 g_error ("Failed to create pipe: %s", strerror (errno));
324 signal (SIGTERM, sigterm_handler);
326 sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
327 g_io_add_watch (sigterm_channel,
328 G_IO_IN | G_IO_ERR | G_IO_HUP,
338 Display *bridge_display;
340 unsigned char *data = NULL;
341 unsigned long nitems;
342 unsigned long leftover;
343 gboolean result = FALSE;
345 bridge_display = XOpenDisplay (NULL);
349 AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
350 XGetWindowProperty (bridge_display,
351 XDefaultRootWindow (bridge_display),
353 (long) BUFSIZ, False,
354 (Atom) 31, &actual_type, &actual_format,
355 &nitems, &leftover, &data);
359 GDBusConnection *bus;
360 GError *error = NULL;
361 const gchar *old_session = g_getenv ("DBUS_SESSION_BUS_ADDRESS");
362 /* TODO: Is there a better way to connect? This is really hacky */
363 g_setenv ("DBUS_SESSION_BUS_ADDRESS", data, TRUE);
364 bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
365 g_setenv ("DBUS_SESSION_BUS_ADDRESS", old_session, TRUE);
368 g_object_unref (bus);
371 XCloseDisplay (bridge_display);
380 GError *error = NULL;
382 GDBusConnection *session_bus;
387 if (already_running ())
390 _global_app = g_slice_new0 (A11yBusLauncher);
391 _global_app->loop = g_main_loop_new (NULL, FALSE);
392 _global_app->launch_immediately = (argc == 2 && strcmp (argv[1], "--launch-immediately") == 0);
394 init_sigterm_handling (_global_app);
396 introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
397 g_assert (introspection_data != NULL);
399 name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
401 G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
408 g_main_loop_run (_global_app->loop);
410 if (_global_app->a11y_bus_pid > 0)
411 kill (_global_app->a11y_bus_pid, SIGTERM);
413 /* Clear the X property if our bus is gone; in the case where e.g.
414 * GDM is launching a login on an X server it was using before,
415 * we don't want early login processes to pick up the stale address.
418 Display *display = XOpenDisplay (NULL);
421 Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
422 XDeleteProperty (display,
423 XDefaultRootWindow (display),
427 XCloseDisplay (display);
431 if (_global_app->a11y_launch_error_message)
433 g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);