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-2018 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 Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 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
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, write to the
19
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
 * Boston, MA 02110-1301, USA.
21
 */
22

            
23
#include "config.h"
24

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

            
37
#include <gio/gio.h>
38
#ifdef HAVE_X11
39
#include <X11/Xatom.h>
40
#include <X11/Xlib.h>
41
#endif
42
#ifdef DBUS_BROKER
43
#include <systemd/sd-login.h>
44
#endif
45
#include <sys/stat.h>
46

            
47
typedef enum
48
{
49
  A11Y_BUS_STATE_IDLE = 0,
50
  A11Y_BUS_STATE_READING_ADDRESS,
51
  A11Y_BUS_STATE_RUNNING,
52
  A11Y_BUS_STATE_ERROR
53
} A11yBusState;
54

            
55
typedef struct
56
{
57
  GMainLoop *loop;
58
  gboolean launch_immediately;
59
  gboolean a11y_enabled;
60
  gboolean screen_reader_enabled;
61
  GDBusConnection *session_bus;
62
  GSettings *a11y_schema;
63
  GSettings *interface_schema;
64
  int name_owner_id;
65

            
66
  GDBusProxy *client_proxy;
67

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

            
82
static A11yBusLauncher *_global_app = NULL;
83

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

            
98
static void
99
28
respond_to_end_session (GDBusProxy *proxy)
100
{
101
  GVariant *parameters;
102

            
103
28
  parameters = g_variant_new ("(bs)", TRUE, "");
104

            
105
28
  g_dbus_proxy_call (proxy,
106
                     "EndSessionResponse", parameters,
107
                     G_DBUS_CALL_FLAGS_NONE,
108
                     -1, NULL, NULL, NULL);
109
28
}
110

            
111
static void
112
56
g_signal_cb (GDBusProxy *proxy,
113
             gchar *sender_name,
114
             gchar *signal_name,
115
             GVariant *parameters,
116
             gpointer user_data)
117
{
118
56
  A11yBusLauncher *app = user_data;
119

            
120
56
  if (g_strcmp0 (signal_name, "QueryEndSession") == 0)
121
    respond_to_end_session (proxy);
122
56
  else if (g_strcmp0 (signal_name, "EndSession") == 0)
123
28
    respond_to_end_session (proxy);
124
28
  else if (g_strcmp0 (signal_name, "Stop") == 0)
125
28
    g_main_loop_quit (app->loop);
126
56
}
127

            
128
static void
129
28
client_proxy_ready_cb (GObject *source_object,
130
                       GAsyncResult *res,
131
                       gpointer user_data)
132
{
133
28
  A11yBusLauncher *app = user_data;
134
28
  GError *error = NULL;
135

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

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

            
143
      return;
144
    }
145

            
146
28
  g_signal_connect (app->client_proxy, "g-signal",
147
                    G_CALLBACK (g_signal_cb), app);
148
}
149

            
150
static void
151
28
client_registered (GObject *source,
152
                   GAsyncResult *result,
153
                   gpointer user_data)
154
{
155
28
  A11yBusLauncher *app = user_data;
156
28
  GError *error = NULL;
157
  GVariant *variant;
158
  gchar *object_path;
159
  GDBusProxyFlags flags;
160

            
161
28
  variant = g_dbus_proxy_call_finish (app->sm_proxy, result, &error);
162
28
  if (!variant)
163
    {
164
      if (error != NULL)
165
        {
166
          g_warning ("Failed to register client: %s", error->message);
167
          g_error_free (error);
168
        }
169
    }
170
  else
171
    {
172
28
      g_variant_get (variant, "(o)", &object_path);
173
28
      g_variant_unref (variant);
174

            
175
28
      flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
176
28
      g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, flags, NULL,
177
                                "org.gnome.SessionManager", object_path,
178
                                "org.gnome.SessionManager.ClientPrivate",
179
                                NULL, client_proxy_ready_cb, app);
180

            
181
28
      g_free (object_path);
182
    }
183
28
  g_clear_object (&app->sm_proxy);
184
28
}
185

            
186
static void
187
28
register_client (A11yBusLauncher *app)
188
{
189
  GDBusProxyFlags flags;
190
  GError *error;
191
  const gchar *app_id;
192
  const gchar *autostart_id;
193
  gchar *client_startup_id;
194
  GVariant *parameters;
195

            
196
28
  flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
197
          G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
198

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

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

            
211
      return;
212
    }
213

            
214
28
  app_id = "at-spi-bus-launcher";
215
28
  autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
216

            
217
28
  if (autostart_id != NULL)
218
    {
219
      client_startup_id = g_strdup (autostart_id);
220
      g_unsetenv ("DESKTOP_AUTOSTART_ID");
221
    }
222
  else
223
    {
224
28
      client_startup_id = g_strdup ("");
225
    }
226

            
227
28
  parameters = g_variant_new ("(ss)", app_id, client_startup_id);
228
28
  g_free (client_startup_id);
229

            
230
28
  error = NULL;
231
28
  g_dbus_proxy_call (app->sm_proxy,
232
                     "RegisterClient", parameters,
233
                     G_DBUS_CALL_FLAGS_NONE,
234
                     G_MAXINT, NULL, client_registered, app);
235
}
236

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

            
245
28
  register_client (app);
246
28
}
247

            
248
/**
249
 * unix_read_all_fd_to_string:
250
 *
251
 * Read all data from a file descriptor to a C string buffer.
252
 */
253
static gboolean
254
28
unix_read_all_fd_to_string (int fd,
255
                            char *buf,
256
                            ssize_t max_bytes,
257
                            char **error_msg)
258
{
259
28
  g_assert (max_bytes > 1);
260
28
  *error_msg = NULL;
261

            
262
28
  max_bytes -= 1; /* allow space for nul terminator */
263

            
264
56
  while (max_bytes > 1)
265
    {
266
      ssize_t bytes_read;
267

            
268
56
    again:
269
56
      bytes_read = read (fd, buf, max_bytes);
270

            
271
56
      if (bytes_read == 0)
272
        {
273
28
          break;
274
        }
275
28
      else if (bytes_read > 0)
276
        {
277
28
          buf += bytes_read;
278
28
          max_bytes -= bytes_read;
279
        }
280
      else if (errno == EINTR)
281
        {
282
          goto again;
283
        }
284
      else
285
        {
286
          int err_save = errno;
287
          *error_msg = g_strdup_printf ("Failed to read data from accessibility bus: %s", g_strerror (err_save));
288
          return FALSE;
289
        }
290
    }
291

            
292
28
  *buf = '\0';
293
28
  return TRUE;
294
}
295

            
296
static void
297
on_bus_exited (GPid pid,
298
               gint status,
299
               gpointer data)
300
{
301
  A11yBusLauncher *app = data;
302

            
303
  app->a11y_bus_pid = -1;
304
  app->state = A11Y_BUS_STATE_ERROR;
305
  if (app->a11y_launch_error_message == NULL)
306
    {
307
      if (WIFEXITED (status))
308
        app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
309
      else if (WIFSIGNALED (status))
310
        app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
311
      else if (WIFSTOPPED (status))
312
        app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
313
    }
314
  g_main_loop_quit (app->loop);
315
}
316

            
317
#ifdef DBUS_DAEMON
318
static gboolean
319
28
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
320
{
321
  char *address_param;
322

            
323
28
  if (app->socket_name)
324
    {
325
28
      gchar *escaped_address = g_dbus_address_escape_value (app->socket_name);
326
28
      address_param = g_strconcat ("--address=unix:path=", escaped_address, NULL);
327
28
      g_free (escaped_address);
328
    }
329
  else
330
    {
331
      address_param = NULL;
332
    }
333

            
334
28
  if (pipe (app->pipefd) < 0)
335
    g_error ("Failed to create pipe: %s", strerror (errno));
336

            
337
28
  char *print_address_fd_param = g_strdup_printf ("%d", app->pipefd[1]);
338

            
339
28
  char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", print_address_fd_param, address_param, NULL };
340
28
  gint source_fds[1] = { app->pipefd[1] };
341
28
  gint target_fds[1] = { app->pipefd[1] };
342
  G_STATIC_ASSERT (G_N_ELEMENTS (source_fds) == G_N_ELEMENTS (target_fds));
343
  GPid pid;
344
  char addr_buf[2048];
345
28
  GError *error = NULL;
346
  char *error_from_read;
347

            
348
28
  g_clear_pointer (&app->a11y_launch_error_message, g_free);
349

            
350
28
  if (!g_spawn_async_with_pipes_and_fds (NULL,
351
                                         (const gchar *const *) argv,
352
                                         NULL,
353
                                         G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
354
                                         NULL, /* child_setup */
355
                                         app,
356
                                         -1, /* stdin_fd */
357
                                         -1, /* stdout_fd */
358
                                         -1, /* stdout_fd */
359
                                         source_fds,
360
                                         target_fds,
361
                                         G_N_ELEMENTS (source_fds), /* n_fds in source_fds and target_fds */
362
                                         &pid,
363
                                         NULL, /* stdin_pipe_out */
364
                                         NULL, /* stdout_pipe_out */
365
                                         NULL, /* stderr_pipe_out */
366
                                         &error))
367
    {
368
      app->a11y_bus_pid = -1;
369
      app->a11y_launch_error_message = g_strdup (error->message);
370
      g_clear_error (&error);
371
      g_free (address_param);
372
      g_free (print_address_fd_param);
373
      goto error;
374
    }
375

            
376
28
  g_free (address_param);
377
28
  g_free (print_address_fd_param);
378
28
  close (app->pipefd[1]);
379
28
  app->pipefd[1] = -1;
380

            
381
28
  g_child_watch_add (pid, on_bus_exited, app);
382

            
383
28
  app->state = A11Y_BUS_STATE_READING_ADDRESS;
384
28
  app->a11y_bus_pid = pid;
385
28
  g_debug ("Launched a11y bus, child is %ld", (long) pid);
386
28
  error_from_read = NULL;
387
28
  if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf), &error_from_read))
388
    {
389
      app->a11y_launch_error_message = error_from_read;
390
      kill (app->a11y_bus_pid, SIGTERM);
391
      g_spawn_close_pid (app->a11y_bus_pid);
392
      app->a11y_bus_pid = -1;
393
      goto error;
394
    }
395
28
  close (app->pipefd[0]);
396
28
  app->pipefd[0] = -1;
397
28
  app->state = A11Y_BUS_STATE_RUNNING;
398

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

            
403
28
  return TRUE;
404

            
405
error:
406
  close (app->pipefd[0]);
407
  close (app->pipefd[1]);
408
  app->state = A11Y_BUS_STATE_ERROR;
409

            
410
  return FALSE;
411
}
412
#else
413
static gboolean
414
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
415
{
416
  return FALSE;
417
}
418
#endif
419

            
420
#ifdef DBUS_BROKER
421
static void
422
setup_bus_child_broker (gpointer data)
423
{
424
  A11yBusLauncher *app = data;
425
  gchar *pid_str;
426
  (void) app;
427

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

            
432
  pid_str = g_strdup_printf ("%u", getpid ());
433
  g_setenv ("LISTEN_PID", pid_str, TRUE);
434
  g_free (pid_str);
435
}
436

            
437
static gboolean
438
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
439
{
440
  char *argv[] = { DBUS_BROKER, config_path, "--scope", "user", NULL };
441
  char *unit;
442
  struct sockaddr_un addr = { .sun_family = AF_UNIX, "" };
443
  socklen_t addr_len = sizeof (addr);
444
  GPid pid;
445
  GError *error = NULL;
446

            
447
  if (app->socket_name)
448
    {
449
      strcpy (addr.sun_path, app->socket_name);
450
      unlink (app->socket_name);
451
    }
452

            
453
  /* This detects whether we are running under systemd. We only try to
454
   * use dbus-broker if we are running under systemd because D-Bus
455
   * service activation won't work otherwise.
456
   */
457
  if (sd_pid_get_user_unit (getpid (), &unit) >= 0)
458
    {
459
      free (unit);
460
    }
461
  else
462
    {
463
      app->state = A11Y_BUS_STATE_ERROR;
464
      return FALSE;
465
    }
466

            
467
  if ((app->listenfd = socket (PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0)
468
    g_error ("Failed to create listening socket: %s", strerror (errno));
469

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

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

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

            
480
  g_clear_pointer (&app->a11y_launch_error_message, g_free);
481

            
482
  if (!g_spawn_async (NULL,
483
                      argv,
484
                      NULL,
485
                      G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
486
                      setup_bus_child_broker,
487
                      app,
488
                      &pid,
489
                      &error))
490
    {
491
      app->a11y_bus_pid = -1;
492
      app->a11y_launch_error_message = g_strdup (error->message);
493
      g_clear_error (&error);
494
      goto error;
495
    }
496

            
497
  close (app->listenfd);
498
  app->listenfd = -1;
499

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

            
505
  if (app->socket_name)
506
    app->a11y_bus_address = g_strconcat ("unix:path=", addr.sun_path, NULL);
507
  else
508
    app->a11y_bus_address = g_strconcat ("unix:abstract=", addr.sun_path + 1, NULL);
509
  g_debug ("a11y bus address: %s", app->a11y_bus_address);
510

            
511
  return TRUE;
512

            
513
error:
514
  close (app->listenfd);
515
  app->state = A11Y_BUS_STATE_ERROR;
516

            
517
  return FALSE;
518
}
519
#else
520
static gboolean
521
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
522
{
523
  return FALSE;
524
}
525
#endif
526

            
527
static gboolean
528
220
ensure_a11y_bus (A11yBusLauncher *app)
529
{
530
220
  char *config_path = NULL;
531
220
  gboolean success = FALSE;
532
  const gchar *xdg_runtime_dir;
533

            
534
220
  if (app->a11y_bus_pid != 0)
535
192
    return FALSE;
536

            
537
28
  if (g_file_test (SYSCONFDIR "/at-spi2/accessibility.conf", G_FILE_TEST_EXISTS))
538
    config_path = "--config-file=" SYSCONFDIR "/at-spi2/accessibility.conf";
539
  else
540
28
    config_path = "--config-file=" DATADIR "/defaults/at-spi2/accessibility.conf";
541

            
542
28
  xdg_runtime_dir = g_get_user_runtime_dir ();
543
28
  if (xdg_runtime_dir)
544
    {
545
28
      const gchar *display = g_getenv ("DISPLAY");
546
28
      gchar *at_spi_dir = g_strconcat (xdg_runtime_dir, "/at-spi", NULL);
547
      gchar *p;
548
28
      mkdir (xdg_runtime_dir, 0700);
549
28
      if (!g_path_is_absolute (at_spi_dir))
550
        {
551
          gchar *new_dir = g_canonicalize_filename (at_spi_dir, NULL);
552
          g_free (at_spi_dir);
553
          at_spi_dir = new_dir;
554
        }
555
28
      if (mkdir (at_spi_dir, 0700) == 0 || errno == EEXIST)
556
        {
557
28
          app->socket_name = g_strconcat (at_spi_dir, "/bus", display, NULL);
558
28
          g_free (at_spi_dir);
559
28
          p = strchr (app->socket_name, ':');
560
28
          if (p)
561
28
            *p = '_';
562
28
          if (strlen (app->socket_name) >= 100)
563
            {
564
              g_free (app->socket_name);
565
              app->socket_name = NULL;
566
            }
567
        }
568
      else
569
        g_free (at_spi_dir);
570
    }
571

            
572
#ifdef WANT_DBUS_BROKER
573
  success = ensure_a11y_bus_broker (app, config_path);
574
  if (!success)
575
    {
576
      if (!ensure_a11y_bus_daemon (app, config_path))
577
        return FALSE;
578
    }
579
#else
580
28
  success = ensure_a11y_bus_daemon (app, config_path);
581
28
  if (!success)
582
    {
583
      if (!ensure_a11y_bus_broker (app, config_path))
584
        return FALSE;
585
    }
586
#endif
587

            
588
#ifdef HAVE_X11
589
28
  if (g_getenv ("DISPLAY") != NULL && g_getenv ("WAYLAND_DISPLAY") == NULL)
590
    {
591
28
      Display *display = XOpenDisplay (NULL);
592
28
      if (display)
593
        {
594
28
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
595
28
          XChangeProperty (display,
596
                           XDefaultRootWindow (display),
597
                           bus_address_atom,
598
                           XA_STRING, 8, PropModeReplace,
599
28
                           (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
600
28
          XFlush (display);
601
28
          XCloseDisplay (display);
602
28
          app->x11_prop_set = TRUE;
603
        }
604
    }
605
#endif
606

            
607
28
  return TRUE;
608
}
609

            
610
static void
611
220
handle_method_call (GDBusConnection *connection,
612
                    const gchar *sender,
613
                    const gchar *object_path,
614
                    const gchar *interface_name,
615
                    const gchar *method_name,
616
                    GVariant *parameters,
617
                    GDBusMethodInvocation *invocation,
618
                    gpointer user_data)
619
{
620
220
  A11yBusLauncher *app = user_data;
621

            
622
220
  if (g_strcmp0 (method_name, "GetAddress") == 0)
623
    {
624
220
      ensure_a11y_bus (app);
625
220
      if (app->a11y_bus_pid > 0)
626
220
        g_dbus_method_invocation_return_value (invocation,
627
                                               g_variant_new ("(s)", app->a11y_bus_address));
628
      else
629
        g_dbus_method_invocation_return_dbus_error (invocation,
630
                                                    "org.a11y.Bus.Error",
631
                                                    app->a11y_launch_error_message);
632
    }
633
220
}
634

            
635
static GVariant *
636
handle_get_property (GDBusConnection *connection,
637
                     const gchar *sender,
638
                     const gchar *object_path,
639
                     const gchar *interface_name,
640
                     const gchar *property_name,
641
                     GError **error,
642
                     gpointer user_data)
643
{
644
  A11yBusLauncher *app = user_data;
645

            
646
  if (g_strcmp0 (property_name, "IsEnabled") == 0)
647
    return g_variant_new ("b", app->a11y_enabled);
648
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
649
    return g_variant_new ("b", app->screen_reader_enabled);
650
  else
651
    return NULL;
652
}
653

            
654
static void
655
handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled, gboolean notify_gsettings)
656
{
657
  GVariantBuilder builder;
658
  GVariantBuilder invalidated_builder;
659

            
660
  if (enabled == app->a11y_enabled)
661
    return;
662

            
663
  app->a11y_enabled = enabled;
664

            
665
  if (notify_gsettings && app->interface_schema)
666
    {
667
      g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
668
                              enabled);
669
      g_settings_sync ();
670
    }
671

            
672
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
673
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
674
  g_variant_builder_add (&builder, "{sv}", "IsEnabled",
675
                         g_variant_new_boolean (enabled));
676

            
677
  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
678
                                 "org.freedesktop.DBus.Properties",
679
                                 "PropertiesChanged",
680
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
681
                                                &builder,
682
                                                &invalidated_builder),
683
                                 NULL);
684

            
685
  g_variant_builder_clear (&builder);
686
  g_variant_builder_clear (&invalidated_builder);
687
}
688

            
689
static void
690
handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled, gboolean notify_gsettings)
691
{
692
  GVariantBuilder builder;
693
  GVariantBuilder invalidated_builder;
694

            
695
  if (enabled == app->screen_reader_enabled)
696
    return;
697

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

            
703
  app->screen_reader_enabled = enabled;
704

            
705
  if (notify_gsettings && app->a11y_schema)
706
    {
707
      g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
708
                              enabled);
709
      g_settings_sync ();
710
    }
711

            
712
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
713
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
714
  g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
715
                         g_variant_new_boolean (enabled));
716

            
717
  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
718
                                 "org.freedesktop.DBus.Properties",
719
                                 "PropertiesChanged",
720
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
721
                                                &builder,
722
                                                &invalidated_builder),
723
                                 NULL);
724

            
725
  g_variant_builder_clear (&builder);
726
  g_variant_builder_clear (&invalidated_builder);
727
}
728

            
729
static gboolean
730
handle_set_property (GDBusConnection *connection,
731
                     const gchar *sender,
732
                     const gchar *object_path,
733
                     const gchar *interface_name,
734
                     const gchar *property_name,
735
                     GVariant *value,
736
                     GError **error,
737
                     gpointer user_data)
738
{
739
  A11yBusLauncher *app = user_data;
740
  const gchar *type = g_variant_get_type_string (value);
741
  gboolean enabled;
742

            
743
  if (g_strcmp0 (type, "b") != 0)
744
    {
745
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
746
                   "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
747
      return FALSE;
748
    }
749

            
750
  enabled = g_variant_get_boolean (value);
751

            
752
  if (g_strcmp0 (property_name, "IsEnabled") == 0)
753
    {
754
      handle_a11y_enabled_change (app, enabled, TRUE);
755
      return TRUE;
756
    }
757
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
758
    {
759
      handle_screen_reader_enabled_change (app, enabled, TRUE);
760
      return TRUE;
761
    }
762
  else
763
    {
764
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
765
                   "Unknown property '%s'", property_name);
766
      return FALSE;
767
    }
768
}
769

            
770
static const GDBusInterfaceVTable bus_vtable = {
771
  handle_method_call,
772
  NULL, /* handle_get_property, */
773
  NULL  /* handle_set_property */
774
};
775

            
776
static const GDBusInterfaceVTable status_vtable = {
777
  NULL, /* handle_method_call */
778
  handle_get_property,
779
  handle_set_property
780
};
781

            
782
static void
783
28
on_bus_acquired (GDBusConnection *connection,
784
                 const gchar *name,
785
                 gpointer user_data)
786
{
787
28
  A11yBusLauncher *app = user_data;
788
  GError *error;
789
  guint registration_id;
790

            
791
28
  if (connection == NULL)
792
    {
793
      g_main_loop_quit (app->loop);
794
      return;
795
    }
796
28
  app->session_bus = connection;
797

            
798
28
  error = NULL;
799
28
  registration_id = g_dbus_connection_register_object (connection,
800
                                                       "/org/a11y/bus",
801
28
                                                       introspection_data->interfaces[0],
802
                                                       &bus_vtable,
803
                                                       _global_app,
804
                                                       NULL,
805
                                                       &error);
806
28
  if (registration_id == 0)
807
    {
808
      g_error ("%s", error->message);
809
      g_clear_error (&error);
810
    }
811

            
812
28
  g_dbus_connection_register_object (connection,
813
                                     "/org/a11y/bus",
814
28
                                     introspection_data->interfaces[1],
815
                                     &status_vtable,
816
                                     _global_app,
817
                                     NULL,
818
                                     NULL);
819
}
820

            
821
static void
822
on_name_lost (GDBusConnection *connection,
823
              const gchar *name,
824
              gpointer user_data)
825
{
826
  A11yBusLauncher *app = user_data;
827
  if (app->session_bus == NULL && connection == NULL && app->a11y_launch_error_message == NULL)
828
    app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
829
  g_main_loop_quit (app->loop);
830
}
831

            
832
static void
833
28
on_name_acquired (GDBusConnection *connection,
834
                  const gchar *name,
835
                  gpointer user_data)
836
{
837
28
  A11yBusLauncher *app = user_data;
838

            
839
28
  if (app->launch_immediately)
840
    {
841
      ensure_a11y_bus (app);
842
      if (app->state == A11Y_BUS_STATE_ERROR)
843
        {
844
          g_main_loop_quit (app->loop);
845
          return;
846
        }
847
    }
848

            
849
28
  g_bus_watch_name (G_BUS_TYPE_SESSION,
850
                    "org.gnome.SessionManager",
851
                    G_BUS_NAME_WATCHER_FLAGS_NONE,
852
                    name_appeared_handler, NULL,
853
                    user_data, NULL);
854
}
855

            
856
static int sigterm_pipefd[2];
857

            
858
static void
859
sigterm_handler (int signum)
860
{
861
  write (sigterm_pipefd[1], "X", 1);
862
}
863

            
864
static gboolean
865
on_sigterm_pipe (GIOChannel *channel,
866
                 GIOCondition condition,
867
                 gpointer data)
868
{
869
  A11yBusLauncher *app = data;
870

            
871
  g_main_loop_quit (app->loop);
872

            
873
  return FALSE;
874
}
875

            
876
static void
877
28
init_sigterm_handling (A11yBusLauncher *app)
878
{
879
  GIOChannel *sigterm_channel;
880

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

            
885
28
  sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
886
28
  g_io_add_watch (sigterm_channel,
887
                  G_IO_IN | G_IO_ERR | G_IO_HUP,
888
                  on_sigterm_pipe,
889
                  app);
890
28
}
891

            
892
static GSettings *
893
56
get_schema (const gchar *name)
894
{
895
#if GLIB_CHECK_VERSION(2, 32, 0)
896
56
  GSettingsSchemaSource *source = g_settings_schema_source_get_default ();
897
56
  if (!source)
898
    {
899
      g_error ("Cannot get the default GSettingsSchemaSource - is the gsettings-desktop-schemas package installed?");
900
    }
901

            
902
56
  GSettingsSchema *schema = g_settings_schema_source_lookup (source, name, FALSE);
903

            
904
56
  if (schema == NULL)
905
    return NULL;
906

            
907
56
  return g_settings_new_full (schema, NULL, NULL);
908
#else
909
  const char *const *schemas = NULL;
910
  gint i;
911

            
912
  schemas = g_settings_list_schemas ();
913
  for (i = 0; schemas[i]; i++)
914
    {
915
      if (!strcmp (schemas[i], name))
916
        return g_settings_new (schemas[i]);
917
    }
918

            
919
  return NULL;
920
#endif
921
}
922

            
923
static void
924
gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
925
{
926
  gboolean new_val = g_settings_get_boolean (gsettings, key);
927

            
928
  if (!strcmp (key, "toolkit-accessibility"))
929
    handle_a11y_enabled_change (_global_app, new_val, FALSE);
930
  else if (!strcmp (key, "screen-reader-enabled"))
931
    handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
932
}
933

            
934
int
935
28
main (int argc,
936
      char **argv)
937
{
938
28
  gboolean a11y_set = FALSE;
939
28
  gboolean screen_reader_set = FALSE;
940
  gint i;
941

            
942
28
  _global_app = g_new0 (A11yBusLauncher, 1);
943
28
  _global_app->loop = g_main_loop_new (NULL, FALSE);
944

            
945
28
  for (i = 1; i < argc; i++)
946
    {
947
      if (!strcmp (argv[i], "--launch-immediately"))
948
        _global_app->launch_immediately = TRUE;
949
      else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 1)
950
        a11y_set = TRUE;
951
      else if (sscanf (argv[i], "--screen-reader=%d",
952
                       &_global_app->screen_reader_enabled) == 1)
953
        screen_reader_set = TRUE;
954
      else
955
        g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
956
    }
957

            
958
28
  _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
959
28
  _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
960

            
961
28
  if (!a11y_set)
962
    {
963
28
      _global_app->a11y_enabled = _global_app->interface_schema
964
28
                                      ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
965
28
                                      : _global_app->launch_immediately;
966
    }
967

            
968
28
  if (!screen_reader_set)
969
    {
970
28
      _global_app->screen_reader_enabled = _global_app->a11y_schema
971
28
                                               ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
972
28
                                               : FALSE;
973
    }
974

            
975
28
  if (_global_app->interface_schema)
976
28
    g_signal_connect (_global_app->interface_schema,
977
                      "changed::toolkit-accessibility",
978
                      G_CALLBACK (gsettings_key_changed), _global_app);
979

            
980
28
  if (_global_app->a11y_schema)
981
28
    g_signal_connect (_global_app->a11y_schema,
982
                      "changed::screen-reader-enabled",
983
                      G_CALLBACK (gsettings_key_changed), _global_app);
984

            
985
28
  init_sigterm_handling (_global_app);
986

            
987
28
  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
988
28
  g_assert (introspection_data != NULL);
989

            
990
28
  _global_app->name_owner_id =
991
28
      g_bus_own_name (G_BUS_TYPE_SESSION,
992
                      "org.a11y.Bus",
993
                      G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
994
                      on_bus_acquired,
995
                      on_name_acquired,
996
                      on_name_lost,
997
                      _global_app,
998
                      NULL);
999

            
28
  g_main_loop_run (_global_app->loop);
28
  if (_global_app->a11y_bus_pid > 0)
    {
28
      kill (_global_app->a11y_bus_pid, SIGTERM);
28
      g_spawn_close_pid (_global_app->a11y_bus_pid);
28
      _global_app->a11y_bus_pid = -1;
    }
    /* 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.
     */
#ifdef HAVE_X11
28
  if (_global_app->x11_prop_set)
    {
28
      Display *display = XOpenDisplay (NULL);
28
      if (display)
        {
27
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
27
          XDeleteProperty (display,
                           XDefaultRootWindow (display),
                           bus_address_atom);
27
          XFlush (display);
27
          XCloseDisplay (display);
        }
    }
#endif
28
  if (_global_app->a11y_launch_error_message)
    {
      g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
      return 1;
    }
28
  return 0;
}