at-spi-bus-launcher.c 26.9 KB
Newer Older
1 2 3 4
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
 * 
 * at-spi-bus-launcher: Manage the a11y bus as a child process 
 *
5
 * Copyright 2011-2018 Red Hat, Inc.
6 7
 *
 * This library is free software; you can redistribute it and/or
Mike Gorse's avatar
Mike Gorse committed
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
Mike Gorse's avatar
Mike Gorse committed
10
 * version 2.1 of the License, or (at your option) any later version.
11 12 13 14
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Mike Gorse's avatar
Mike Gorse committed
15
 * Lesser General Public License for more details.
16
 *
Mike Gorse's avatar
Mike Gorse committed
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, write to the
Mike Gorse's avatar
Mike Gorse committed
19 20
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
21 22 23 24 25 26 27
 */

#include "config.h"

#include <unistd.h>
#include <string.h>
#include <signal.h>
28
#ifdef __linux__
29 30 31 32
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/un.h>
#endif
33 34
#include <sys/wait.h>
#include <errno.h>
35
#include <stdio.h>
36 37

#include <gio/gio.h>
Mike Gorse's avatar
Mike Gorse committed
38
#ifdef HAVE_X11
39 40
#include <X11/Xlib.h>
#include <X11/Xatom.h>
Mike Gorse's avatar
Mike Gorse committed
41
#endif
42 43 44
#ifdef DBUS_BROKER
#include <systemd/sd-login.h>
#endif
45 46 47 48 49 50 51 52 53 54 55

typedef enum {
  A11Y_BUS_STATE_IDLE = 0,
  A11Y_BUS_STATE_READING_ADDRESS,
  A11Y_BUS_STATE_RUNNING,
  A11Y_BUS_STATE_ERROR
} A11yBusState;

typedef struct {
  GMainLoop *loop;
  gboolean launch_immediately;
56
  gboolean a11y_enabled;
57
  gboolean screen_reader_enabled;
58
  GDBusConnection *session_bus;
59 60
  GSettings *a11y_schema;
  GSettings *interface_schema;
Emmanuele Bassi's avatar
Emmanuele Bassi committed
61
  int name_owner_id;
62

63 64
  GDBusProxy *client_proxy;

65 66 67 68
  A11yBusState state;
  /* -1 == error, 0 == pending, > 0 == running */
  int a11y_bus_pid;
  char *a11y_bus_address;
69 70 71
#ifdef HAVE_X11
  gboolean x11_prop_set;
#endif
72
  int pipefd[2];
73
  int listenfd;
74
  char *a11y_launch_error_message;
75
  GDBusProxy *sm_proxy;
76 77 78 79 80 81 82 83 84 85 86
} A11yBusLauncher;

static A11yBusLauncher *_global_app = NULL;

static const gchar introspection_xml[] =
  "<node>"
  "  <interface name='org.a11y.Bus'>"
  "    <method name='GetAddress'>"
  "      <arg type='s' name='address' direction='out'/>"
  "    </method>"
  "  </interface>"
87
  "<interface name='org.a11y.Status'>"
88
  "<property name='IsEnabled' type='b' access='readwrite'/>"
89
  "<property name='ScreenReaderEnabled' type='b' access='readwrite'/>"
90
  "</interface>"
91 92 93
  "</node>";
static GDBusNodeInfo *introspection_data = NULL;

94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
static void
respond_to_end_session (GDBusProxy *proxy)
{
  GVariant *parameters;

  parameters = g_variant_new ("(bs)", TRUE, "");

  g_dbus_proxy_call (proxy,
                     "EndSessionResponse", parameters,
                     G_DBUS_CALL_FLAGS_NONE,
                     -1, NULL, NULL, NULL);
}

static void
g_signal_cb (GDBusProxy *proxy,
             gchar      *sender_name,
             gchar      *signal_name,
             GVariant   *parameters,
             gpointer    user_data)
{
  A11yBusLauncher *app = user_data;

  if (g_strcmp0 (signal_name, "QueryEndSession") == 0)
    respond_to_end_session (proxy);
  else if (g_strcmp0 (signal_name, "EndSession") == 0)
    respond_to_end_session (proxy);
  else if (g_strcmp0 (signal_name, "Stop") == 0)
    g_main_loop_quit (app->loop);
}

static void
client_proxy_ready_cb (GObject      *source_object,
                       GAsyncResult *res,
                       gpointer      user_data)
{
  A11yBusLauncher *app = user_data;
  GError *error = NULL;

  app->client_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);

  if (error != NULL)
    {
      g_warning ("Failed to get a client proxy: %s", error->message);
      g_error_free (error);

      return;
    }

  g_signal_connect (app->client_proxy, "g-signal",
                    G_CALLBACK (g_signal_cb), app);
}

146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
static void
client_registered (GObject *source,
                   GAsyncResult *result,
                   gpointer user_data)
{
  A11yBusLauncher *app = user_data;
  GError *error = NULL;
  GVariant *variant;
  gchar *object_path;
  GDBusProxyFlags flags;

  variant = g_dbus_proxy_call_finish (app->sm_proxy, result, &error);
  if (!variant)
    {
      if (error != NULL)
        {
          g_warning ("Failed to register client: %s", error->message);
          g_error_free (error);
        }
    }
  else
    {
      g_variant_get (variant, "(o)", &object_path);
      g_variant_unref (variant);

      flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
      g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, flags, NULL,
                                "org.gnome.SessionManager", object_path,
                                "org.gnome.SessionManager.ClientPrivate",
                                NULL, client_proxy_ready_cb, app);

      g_free (object_path);
    }
  g_clear_object (&app->sm_proxy);
}

182 183 184 185 186 187 188 189 190 191
static void
register_client (A11yBusLauncher *app)
{
  GDBusProxyFlags flags;
  GError *error;
  const gchar *app_id;
  const gchar *autostart_id;
  gchar *client_startup_id;
  GVariant *parameters;

192 193
  flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
          G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
194 195

  error = NULL;
196 197 198 199 200
  app->sm_proxy = g_dbus_proxy_new_sync (app->session_bus, flags, NULL,
                                         "org.gnome.SessionManager",
                                         "/org/gnome/SessionManager",
                                         "org.gnome.SessionManager",
                                         NULL, &error);
201 202 203 204 205 206 207 208

  if (error != NULL)
    {
      g_warning ("Failed to get session manager proxy: %s", error->message);
      g_error_free (error);

      return;
    }
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

  app_id = "at-spi-bus-launcher";
  autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");

  if (autostart_id != NULL)
    {
      client_startup_id = g_strdup (autostart_id);
      g_unsetenv ("DESKTOP_AUTOSTART_ID");
    }
  else
    {
      client_startup_id = g_strdup ("");
    }

  parameters = g_variant_new ("(ss)", app_id, client_startup_id);
  g_free (client_startup_id);

  error = NULL;
227 228 229 230
  g_dbus_proxy_call (app->sm_proxy,
                     "RegisterClient", parameters,
                     G_DBUS_CALL_FLAGS_NONE,
                     G_MAXINT, NULL, client_registered, app);
231 232 233

}

234 235 236 237 238 239 240 241 242 243 244
static void
name_appeared_handler (GDBusConnection *connection,
                       const gchar     *name,
                       const gchar     *name_owner,
                       gpointer         user_data)
{
  A11yBusLauncher *app = user_data;

  register_client (app);
}

245 246 247 248 249 250 251 252 253 254 255 256
/**
 * unix_read_all_fd_to_string:
 *
 * Read all data from a file descriptor to a C string buffer.
 */
static gboolean
unix_read_all_fd_to_string (int      fd,
                            char    *buf,
                            ssize_t  max_bytes)
{
  ssize_t bytes_read;

Maya Rashish's avatar
Maya Rashish committed
257
  while (max_bytes > 1 && (bytes_read = read (fd, buf, MIN (4096, max_bytes - 1))))
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    {
      if (bytes_read < 0)
        return FALSE;
      buf += bytes_read;
      max_bytes -= bytes_read;
    }
  *buf = '\0';
  return TRUE;
}

static void
on_bus_exited (GPid     pid,
               gint     status,
               gpointer data)
{
  A11yBusLauncher *app = data;
  
  app->a11y_bus_pid = -1;
  app->state = A11Y_BUS_STATE_ERROR;
  if (app->a11y_launch_error_message == NULL)
    {
      if (WIFEXITED (status))
        app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
      else if (WIFSIGNALED (status))
        app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
      else if (WIFSTOPPED (status))
        app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
    }
  g_main_loop_quit (app->loop);
} 

289 290 291 292 293 294 295 296 297 298 299 300
#ifdef DBUS_DAEMON
static void
setup_bus_child_daemon (gpointer data)
{
  A11yBusLauncher *app = data;
  (void) app;

  close (app->pipefd[0]);
  dup2 (app->pipefd[1], 3);
  close (app->pipefd[1]);

  /* On Linux, tell the bus process to exit if this process goes away */
301
#ifdef __linux__
302 303 304 305
  prctl (PR_SET_PDEATHSIG, 15);
#endif
}

306
static gboolean
307
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
308
{
309
  char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", "3", NULL };
310 311 312 313 314 315
  GPid pid;
  char addr_buf[2048];
  GError *error = NULL;

  if (pipe (app->pipefd) < 0)
    g_error ("Failed to create pipe: %s", strerror (errno));
316 317 318

  g_clear_pointer (&app->a11y_launch_error_message, g_free);

319 320 321 322
  if (!g_spawn_async (NULL,
                      argv,
                      NULL,
                      G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
323
                      setup_bus_child_daemon,
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
                      app,
                      &pid,
                      &error))
    {
      app->a11y_bus_pid = -1;
      app->a11y_launch_error_message = g_strdup (error->message);
      g_clear_error (&error);
      goto error;
    }

  close (app->pipefd[1]);
  app->pipefd[1] = -1;

  g_child_watch_add (pid, on_bus_exited, app);

  app->state = A11Y_BUS_STATE_READING_ADDRESS;
  app->a11y_bus_pid = pid;
  g_debug ("Launched a11y bus, child is %ld", (long) pid);
  if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
    {
      app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
      kill (app->a11y_bus_pid, SIGTERM);
346
      app->a11y_bus_pid = -1;
347 348 349 350 351 352 353 354 355 356
      goto error;
    }
  close (app->pipefd[0]);
  app->pipefd[0] = -1;
  app->state = A11Y_BUS_STATE_RUNNING;

  /* Trim the trailing newline */
  app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
  g_debug ("a11y bus address: %s", app->a11y_bus_address);

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
  return TRUE;

error:
  close (app->pipefd[0]);
  close (app->pipefd[1]);
  app->state = A11Y_BUS_STATE_ERROR;

  return FALSE;
}
#else
static gboolean
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
{
	return FALSE;
}
#endif

#ifdef DBUS_BROKER
static void
setup_bus_child_broker (gpointer data)
{
  A11yBusLauncher *app = data;
  gchar *pid_str;
  (void) app;

  dup2 (app->listenfd, 3);
  close (app->listenfd);
  g_setenv("LISTEN_FDS", "1", TRUE);

  pid_str = g_strdup_printf("%u", getpid());
  g_setenv("LISTEN_PID", pid_str, TRUE);
  g_free(pid_str);

  /* Tell the bus process to exit if this process goes away */
  prctl (PR_SET_PDEATHSIG, SIGTERM);
}

static gboolean
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
{
  char *argv[] = { DBUS_BROKER, config_path, "--scope", "user", NULL };
398
  char *unit;
399 400 401 402 403
  struct sockaddr_un addr = { .sun_family = AF_UNIX };
  socklen_t addr_len = sizeof(addr);
  GPid pid;
  GError *error = NULL;

404 405 406 407 408 409 410 411 412 413 414 415 416 417
  /* This detects whether we are running under systemd. We only try to
   * use dbus-broker if we are running under systemd because D-Bus
   * service activation won't work otherwise.
   */
  if (sd_pid_get_user_unit (getpid (), &unit) >= 0)
    {
      free (unit);
    }
  else
    {
      app->state = A11Y_BUS_STATE_ERROR;
      return FALSE;
    }

418 419 420 421 422 423 424 425 426 427 428 429
  if ((app->listenfd = socket (PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
    g_error ("Failed to create listening socket: %s", strerror (errno));

  if (bind (app->listenfd, (struct sockaddr *)&addr, sizeof(sa_family_t)) < 0)
    g_error ("Failed to bind listening socket: %s", strerror (errno));

  if (getsockname (app->listenfd, (struct sockaddr *)&addr, &addr_len) < 0)
    g_error ("Failed to get socket name for listening socket: %s", strerror(errno));

  if (listen (app->listenfd, 1024) < 0)
    g_error ("Failed to listen on socket: %s", strerror(errno));

430 431
  g_clear_pointer (&app->a11y_launch_error_message, g_free);

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
  if (!g_spawn_async (NULL,
                      argv,
                      NULL,
                      G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
                      setup_bus_child_broker,
                      app,
                      &pid,
                      &error))
    {
      app->a11y_bus_pid = -1;
      app->a11y_launch_error_message = g_strdup (error->message);
      g_clear_error (&error);
      goto error;
    }

  close (app->listenfd);
  app->listenfd = -1;

  g_child_watch_add (pid, on_bus_exited, app);
  app->a11y_bus_pid = pid;
  g_debug ("Launched a11y bus, child is %ld", (long) pid);
  app->state = A11Y_BUS_STATE_RUNNING;

  app->a11y_bus_address = g_strconcat("unix:abstract=", addr.sun_path + 1, NULL);
  g_debug ("a11y bus address: %s", app->a11y_bus_address);

  return TRUE;

error:
  close (app->listenfd);
  app->state = A11Y_BUS_STATE_ERROR;

  return FALSE;
}
#else
static gboolean
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
{
	return FALSE;
}
#endif

static gboolean
ensure_a11y_bus (A11yBusLauncher *app)
{
  char *config_path = NULL;
  gboolean success = FALSE;

  if (app->a11y_bus_pid != 0)
    return FALSE;

  if (g_file_test (SYSCONFDIR"/at-spi2/accessibility.conf", G_FILE_TEST_EXISTS))
      config_path = "--config-file="SYSCONFDIR"/at-spi2/accessibility.conf";
  else
      config_path = "--config-file="DATADIR"/defaults/at-spi2/accessibility.conf";

#ifdef WANT_DBUS_BROKER
    success = ensure_a11y_bus_broker (app, config_path);
    if (!success)
      {
        if (!ensure_a11y_bus_daemon (app, config_path))
            return FALSE;
      }
#else
    success = ensure_a11y_bus_daemon (app, config_path);
    if (!success)
      {
        if (!ensure_a11y_bus_broker (app, config_path))
            return FALSE;
      }
#endif

Mike Gorse's avatar
Mike Gorse committed
504
#ifdef HAVE_X11
505
  if (g_getenv ("DISPLAY") != NULL && g_getenv ("WAYLAND_DISPLAY") == NULL)
506 507 508 509 510 511 512 513 514 515 516 517
    {
      Display *display = XOpenDisplay (NULL);
      if (display)
        {
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
          XChangeProperty (display,
                           XDefaultRootWindow (display),
                           bus_address_atom,
                           XA_STRING, 8, PropModeReplace,
                           (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
          XFlush (display);
          XCloseDisplay (display);
518
          app->x11_prop_set = TRUE;
519 520
        }
    }
Mike Gorse's avatar
Mike Gorse committed
521
#endif
522

523
  return TRUE;
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
}

static void
handle_method_call (GDBusConnection       *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
  A11yBusLauncher *app = user_data;

  if (g_strcmp0 (method_name, "GetAddress") == 0)
    {
      ensure_a11y_bus (app);
      if (app->a11y_bus_pid > 0)
        g_dbus_method_invocation_return_value (invocation,
                                               g_variant_new ("(s)", app->a11y_bus_address));
      else
        g_dbus_method_invocation_return_dbus_error (invocation,
                                                    "org.a11y.Bus.Error",
                                                    app->a11y_launch_error_message);
    }
}

551 552 553 554 555 556 557 558 559 560 561 562
static GVariant *
handle_get_property  (GDBusConnection       *connection,
                      const gchar           *sender,
                      const gchar           *object_path,
                      const gchar           *interface_name,
                      const gchar           *property_name,
                    GError **error,
                    gpointer               user_data)
{
  A11yBusLauncher *app = user_data;

  if (g_strcmp0 (property_name, "IsEnabled") == 0)
563 564 565
    return g_variant_new ("b", app->a11y_enabled);
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
    return g_variant_new ("b", app->screen_reader_enabled);
566 567 568 569
  else
    return NULL;
}

570
static void
571 572 573
handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled,
                               gboolean notify_gsettings)
{
574 575
  GVariantBuilder builder;
  GVariantBuilder invalidated_builder;
576 577 578 579 580 581

  if (enabled == app->a11y_enabled)
    return;

  app->a11y_enabled = enabled;

582
  if (notify_gsettings && app->interface_schema)
583
    {
584
      g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
585 586 587
                              enabled);
      g_settings_sync ();
    }
588

589 590 591
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
  g_variant_builder_add (&builder, "{sv}", "IsEnabled",
592 593 594
                         g_variant_new_boolean (enabled));

  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
595 596
                                 "org.freedesktop.DBus.Properties",
                                 "PropertiesChanged",
597
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
598 599
                                                &builder,
                                                &invalidated_builder),
600
                                 NULL);
601 602 603

  g_variant_builder_clear (&builder);
  g_variant_builder_clear (&invalidated_builder);
604 605
}

606 607 608 609
static void
handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled,
                               gboolean notify_gsettings)
{
610 611
  GVariantBuilder builder;
  GVariantBuilder invalidated_builder;
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629

  if (enabled == app->screen_reader_enabled)
    return;

  /* If the screen reader is being enabled, we should enable accessibility
   * if it isn't enabled already */
  if (enabled)
    handle_a11y_enabled_change (app, enabled, notify_gsettings);

  app->screen_reader_enabled = enabled;

  if (notify_gsettings && app->a11y_schema)
    {
      g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
                              enabled);
      g_settings_sync ();
    }

630 631 632
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
  g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
633 634 635
                         g_variant_new_boolean (enabled));

  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
636 637
                                 "org.freedesktop.DBus.Properties",
                                 "PropertiesChanged",
638
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
639 640
                                                &builder,
                                                &invalidated_builder),
641
                                 NULL);
642 643 644

  g_variant_builder_clear (&builder);
  g_variant_builder_clear (&invalidated_builder);
645 646
}

647 648 649 650 651 652 653 654 655 656 657
static gboolean
handle_set_property  (GDBusConnection       *connection,
                      const gchar           *sender,
                      const gchar           *object_path,
                      const gchar           *interface_name,
                      const gchar           *property_name,
                      GVariant *value,
                    GError **error,
                    gpointer               user_data)
{
  A11yBusLauncher *app = user_data;
658 659 660 661 662 663 664 665 666 667 668
  const gchar *type = g_variant_get_type_string (value);
  gboolean enabled;
  
  if (g_strcmp0 (type, "b") != 0)
    {
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                       "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
      return FALSE;
    }

  enabled = g_variant_get_boolean (value);
669 670 671 672 673 674

  if (g_strcmp0 (property_name, "IsEnabled") == 0)
    {
      handle_a11y_enabled_change (app, enabled, TRUE);
      return TRUE;
    }
675 676 677 678 679
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
    {
      handle_screen_reader_enabled_change (app, enabled, TRUE);
      return TRUE;
    }
680 681 682 683 684 685 686 687
  else
    {
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
                       "Unknown property '%s'", property_name);
      return FALSE;
    }
}

688
static const GDBusInterfaceVTable bus_vtable =
689 690
{
  handle_method_call,
691 692 693 694 695 696 697 698
  NULL, /* handle_get_property, */
  NULL  /* handle_set_property */
};

static const GDBusInterfaceVTable status_vtable =
{
  NULL, /* handle_method_call */
  handle_get_property,
699
  handle_set_property
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
};

static void
on_bus_acquired (GDBusConnection *connection,
                 const gchar     *name,
                 gpointer         user_data)
{
  A11yBusLauncher *app = user_data;
  GError *error;
  guint registration_id;
  
  if (connection == NULL)
    {
      g_main_loop_quit (app->loop);
      return;
    }
  app->session_bus = connection;

  error = NULL;
  registration_id = g_dbus_connection_register_object (connection,
                                                       "/org/a11y/bus",
                                                       introspection_data->interfaces[0],
722
                                                       &bus_vtable,
723 724 725 726
                                                       _global_app,
                                                       NULL,
                                                       &error);
  if (registration_id == 0)
William Jon McCann's avatar
William Jon McCann committed
727 728 729 730
    {
      g_error ("%s", error->message);
      g_clear_error (&error);
    }
731 732

  g_dbus_connection_register_object (connection,
William Jon McCann's avatar
William Jon McCann committed
733 734 735 736 737 738
                                     "/org/a11y/bus",
                                     introspection_data->interfaces[1],
                                     &status_vtable,
                                     _global_app,
                                     NULL,
                                     NULL);
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758
}

static void
on_name_lost (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
  A11yBusLauncher *app = user_data;
  if (app->session_bus == NULL
      && connection == NULL
      && app->a11y_launch_error_message == NULL)
    app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
  g_main_loop_quit (app->loop);
}

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
759 760 761 762 763 764 765 766 767 768 769 770
  A11yBusLauncher *app = user_data;

  if (app->launch_immediately)
    {
      ensure_a11y_bus (app);
      if (app->state == A11Y_BUS_STATE_ERROR)
        {
          g_main_loop_quit (app->loop);
          return;
        }
    }

771 772 773 774 775
  g_bus_watch_name (G_BUS_TYPE_SESSION,
                    "org.gnome.SessionManager",
                    G_BUS_NAME_WATCHER_FLAGS_NONE,
                    name_appeared_handler, NULL,
                    user_data, NULL);
776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
}

static int sigterm_pipefd[2];

static void
sigterm_handler (int signum)
{
  write (sigterm_pipefd[1], "X", 1);
}

static gboolean
on_sigterm_pipe (GIOChannel  *channel,
                 GIOCondition condition,
                 gpointer     data)
{
  A11yBusLauncher *app = data;
  
  g_main_loop_quit (app->loop);

  return FALSE;
}

static void
init_sigterm_handling (A11yBusLauncher *app)
{
  GIOChannel *sigterm_channel;

  if (pipe (sigterm_pipefd) < 0)
    g_error ("Failed to create pipe: %s", strerror (errno));
  signal (SIGTERM, sigterm_handler);

  sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
  g_io_add_watch (sigterm_channel,
                  G_IO_IN | G_IO_ERR | G_IO_HUP,
                  on_sigterm_pipe,
                  app);
}

814
static GSettings *
815
get_schema (const gchar *name)
816
{
817 818 819 820 821 822 823 824 825
#if GLIB_CHECK_VERSION (2, 32, 0)
  GSettingsSchemaSource *source = g_settings_schema_source_get_default ();
  GSettingsSchema *schema = g_settings_schema_source_lookup (source, name, FALSE);

  if (schema == NULL)
    return NULL;

  return g_settings_new_full (schema, NULL, NULL);
#else
826 827 828 829 830 831
  const char * const *schemas = NULL;
  gint i;

  schemas = g_settings_list_schemas ();
  for (i = 0; schemas[i]; i++)
  {
832
    if (!strcmp (schemas[i], name))
833 834
      return g_settings_new (schemas[i]);
  }
Vincent Untz's avatar
Vincent Untz committed
835 836

  return NULL;
837
#endif
838 839 840 841 842 843 844
}

static void
gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
{
  gboolean new_val = g_settings_get_boolean (gsettings, key);

845 846 847 848
  if (!strcmp (key, "toolkit-accessibility"))
    handle_a11y_enabled_change (_global_app, new_val, FALSE);
  else if (!strcmp (key, "screen-reader-enabled"))
    handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
849
}
850

851 852 853 854
int
main (int    argc,
      char **argv)
{
855
  gboolean a11y_set = FALSE;
856
  gboolean screen_reader_set = FALSE;
857
  gint i;
858 859 860

  _global_app = g_slice_new0 (A11yBusLauncher);
  _global_app->loop = g_main_loop_new (NULL, FALSE);
861 862 863 864 865

  for (i = 1; i < argc; i++)
    {
      if (!strcmp (argv[i], "--launch-immediately"))
        _global_app->launch_immediately = TRUE;
866
      else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 1)
867
        a11y_set = TRUE;
868
      else if (sscanf (argv[i], "--screen-reader=%d",
869
                       &_global_app->screen_reader_enabled) == 1)
870
        screen_reader_set = TRUE;
871
    else
872
      g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
873 874
    }

875 876
  _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
  _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
877

878 879
  if (!a11y_set)
    {
880 881
      _global_app->a11y_enabled = _global_app->interface_schema
                                  ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
882 883 884
                                  : _global_app->launch_immediately;
    }

885 886 887 888 889 890 891 892 893
  if (!screen_reader_set)
    {
      _global_app->screen_reader_enabled = _global_app->a11y_schema
                                  ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
                                  : FALSE;
    }

  if (_global_app->interface_schema)
    g_signal_connect (_global_app->interface_schema,
894 895
                      "changed::toolkit-accessibility",
                      G_CALLBACK (gsettings_key_changed), _global_app);
896

897 898
  if (_global_app->a11y_schema)
    g_signal_connect (_global_app->a11y_schema,
899
                      "changed::screen-reader-enabled",
900 901
                      G_CALLBACK (gsettings_key_changed), _global_app);

902 903 904 905 906
  init_sigterm_handling (_global_app);

  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
  g_assert (introspection_data != NULL);

Emmanuele Bassi's avatar
Emmanuele Bassi committed
907 908 909 910 911 912 913 914 915
  _global_app->name_owner_id =
    g_bus_own_name (G_BUS_TYPE_SESSION,
                    "org.a11y.Bus",
                    G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
                    on_bus_acquired,
                    on_name_acquired,
                    on_name_lost,
                    _global_app,
                    NULL);
916 917 918 919 920

  g_main_loop_run (_global_app->loop);

  if (_global_app->a11y_bus_pid > 0)
    kill (_global_app->a11y_bus_pid, SIGTERM);
921 922 923 924 925

  /* Clear the X property if our bus is gone; in the case where e.g. 
   * GDM is launching a login on an X server it was using before,
   * we don't want early login processes to pick up the stale address.
   */
Mike Gorse's avatar
Mike Gorse committed
926
#ifdef HAVE_X11
927
  if (_global_app->x11_prop_set)
928 929 930 931 932 933 934 935
    {
      Display *display = XOpenDisplay (NULL);
      if (display)
        {
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
          XDeleteProperty (display,
                           XDefaultRootWindow (display),
                           bus_address_atom);
936

937 938 939 940
          XFlush (display);
          XCloseDisplay (display);
        }
    }
Mike Gorse's avatar
Mike Gorse committed
941
#endif
942

943 944 945 946 947 948 949
  if (_global_app->a11y_launch_error_message)
    {
      g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
      return 1;
    }
  return 0;
}