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
28
  gchar *object_path = NULL;
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
      if (object_path == NULL)
176
        {
177
          g_warning ("Failed to register client: no object in reply");
178
        }
179
      else
180
        {
181
28
          flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
182
28
          g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, flags, NULL,
183
                                    "org.gnome.SessionManager", object_path,
184
                                    "org.gnome.SessionManager.ClientPrivate",
185
                                    NULL, client_proxy_ready_cb, app);
186

            
187
28
          g_free (object_path);
188
        }
189
    }
190
28
  g_clear_object (&app->sm_proxy);
191
28
}
192

            
193
static void
194
28
register_client (A11yBusLauncher *app)
195
{
196
  GDBusProxyFlags flags;
197
  GError *error;
198
  const gchar *app_id;
199
  const gchar *autostart_id;
200
  gchar *client_startup_id;
201
  GVariant *parameters;
202

            
203
28
  flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
204
          G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
205

            
206
28
  error = NULL;
207
28
  app->sm_proxy = g_dbus_proxy_new_sync (app->session_bus, flags, NULL,
208
                                         "org.gnome.SessionManager",
209
                                         "/org/gnome/SessionManager",
210
                                         "org.gnome.SessionManager",
211
                                         NULL, &error);
212

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

            
218
      return;
219
    }
220

            
221
28
  app_id = "at-spi-bus-launcher";
222
28
  autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
223

            
224
28
  if (autostart_id != NULL)
225
    {
226
      client_startup_id = g_strdup (autostart_id);
227
      g_unsetenv ("DESKTOP_AUTOSTART_ID");
228
    }
229
  else
230
    {
231
28
      client_startup_id = g_strdup ("");
232
    }
233

            
234
28
  parameters = g_variant_new ("(ss)", app_id, client_startup_id);
235
28
  g_free (client_startup_id);
236

            
237
28
  error = NULL;
238
28
  g_dbus_proxy_call (app->sm_proxy,
239
                     "RegisterClient", parameters,
240
                     G_DBUS_CALL_FLAGS_NONE,
241
                     G_MAXINT, NULL, client_registered, app);
242
}
243

            
244
static void
245
28
name_appeared_handler (GDBusConnection *connection,
246
                       const gchar *name,
247
                       const gchar *name_owner,
248
                       gpointer user_data)
249
{
250
28
  A11yBusLauncher *app = user_data;
251

            
252
28
  register_client (app);
253
28
}
254

            
255
/**
256
 * unix_read_all_fd_to_string:
257
 *
258
 * Read all data from a file descriptor to a C string buffer.
259
 */
260
static gboolean
261
28
unix_read_all_fd_to_string (int fd,
262
                            char *buf,
263
                            ssize_t max_bytes,
264
                            char **error_msg)
265
{
266
28
  g_assert (max_bytes > 1);
267
28
  *error_msg = NULL;
268

            
269
28
  max_bytes -= 1; /* allow space for nul terminator */
270

            
271
56
  while (max_bytes > 1)
272
    {
273
      ssize_t bytes_read;
274

            
275
56
    again:
276
56
      bytes_read = read (fd, buf, max_bytes);
277

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

            
299
28
  *buf = '\0';
300
28
  return TRUE;
301
}
302

            
303
static void
304
on_bus_exited (GPid pid,
305
               gint status,
306
               gpointer data)
307
{
308
  A11yBusLauncher *app = data;
309

            
310
  app->a11y_bus_pid = -1;
311
  app->state = A11Y_BUS_STATE_ERROR;
312
  if (app->a11y_launch_error_message == NULL)
313
    {
314
      if (WIFEXITED (status))
315
        app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
316
      else if (WIFSIGNALED (status))
317
        app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
318
      else if (WIFSTOPPED (status))
319
        app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
320
    }
321
  g_main_loop_quit (app->loop);
322
}
323

            
324
#ifdef DBUS_DAEMON
325
static gboolean
326
28
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
327
{
328
  char *address_param;
329

            
330
28
  if (app->socket_name)
331
    {
332
28
      gchar *escaped_address = g_dbus_address_escape_value (app->socket_name);
333
28
      address_param = g_strconcat ("--address=unix:path=", escaped_address, NULL);
334
28
      g_free (escaped_address);
335
    }
336
  else
337
    {
338
      address_param = NULL;
339
    }
340

            
341
28
  if (pipe (app->pipefd) < 0)
342
    g_error ("Failed to create pipe: %s", strerror (errno));
343

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

            
346
28
  char *argv[] = { DBUS_DAEMON, config_path, "--nofork", "--print-address", print_address_fd_param, address_param, NULL };
347
28
  gint source_fds[1] = { app->pipefd[1] };
348
28
  gint target_fds[1] = { app->pipefd[1] };
349
  G_STATIC_ASSERT (G_N_ELEMENTS (source_fds) == G_N_ELEMENTS (target_fds));
350
  GPid pid;
351
  char addr_buf[2048];
352
28
  GError *error = NULL;
353
  char *error_from_read;
354

            
355
28
  g_clear_pointer (&app->a11y_launch_error_message, g_free);
356

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

            
383
28
  g_free (address_param);
384
28
  g_free (print_address_fd_param);
385
28
  close (app->pipefd[1]);
386
28
  app->pipefd[1] = -1;
387

            
388
28
  g_child_watch_add (pid, on_bus_exited, app);
389

            
390
28
  app->state = A11Y_BUS_STATE_READING_ADDRESS;
391
28
  app->a11y_bus_pid = pid;
392
28
  g_debug ("Launched a11y bus, child is %ld", (long) pid);
393
28
  error_from_read = NULL;
394
28
  if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf), &error_from_read))
395
    {
396
      app->a11y_launch_error_message = error_from_read;
397
      kill (app->a11y_bus_pid, SIGTERM);
398
      g_spawn_close_pid (app->a11y_bus_pid);
399
      app->a11y_bus_pid = -1;
400
      goto error;
401
    }
402
28
  close (app->pipefd[0]);
403
28
  app->pipefd[0] = -1;
404
28
  app->state = A11Y_BUS_STATE_RUNNING;
405

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

            
410
28
  return TRUE;
411

            
412
error:
413
  close (app->pipefd[0]);
414
  close (app->pipefd[1]);
415
  app->state = A11Y_BUS_STATE_ERROR;
416

            
417
  return FALSE;
418
}
419
#else
420
static gboolean
421
ensure_a11y_bus_daemon (A11yBusLauncher *app, char *config_path)
422
{
423
  return FALSE;
424
}
425
#endif
426

            
427
#ifdef DBUS_BROKER
428
static void
429
setup_bus_child_broker (gpointer data)
430
{
431
  A11yBusLauncher *app = data;
432
  gchar *pid_str;
433
  (void) app;
434

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

            
439
  pid_str = g_strdup_printf ("%u", getpid ());
440
  g_setenv ("LISTEN_PID", pid_str, TRUE);
441
  g_free (pid_str);
442
}
443

            
444
static gboolean
445
28
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
446
{
447
28
  char *argv[] = { DBUS_BROKER, config_path, "--scope", "user", NULL };
448
  char *unit;
449
28
  struct sockaddr_un addr = { .sun_family = AF_UNIX, "" };
450
28
  socklen_t addr_len = sizeof (addr);
451
  GPid pid;
452
28
  GError *error = NULL;
453

            
454
28
  if (app->socket_name)
455
    {
456
28
      strcpy (addr.sun_path, app->socket_name);
457
28
      unlink (app->socket_name);
458
    }
459

            
460
  /* This detects whether we are running under systemd. We only try to
461
   * use dbus-broker if we are running under systemd because D-Bus
462
   * service activation won't work otherwise.
463
   */
464
28
  if (sd_pid_get_user_unit (getpid (), &unit) >= 0)
465
    {
466
      free (unit);
467
    }
468
  else
469
    {
470
28
      app->state = A11Y_BUS_STATE_ERROR;
471
28
      return FALSE;
472
    }
473

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

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

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

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

            
487
  g_clear_pointer (&app->a11y_launch_error_message, g_free);
488

            
489
  if (!g_spawn_async (NULL,
490
                      argv,
491
                      NULL,
492
                      G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
493
                      setup_bus_child_broker,
494
                      app,
495
                      &pid,
496
                      &error))
497
    {
498
      app->a11y_bus_pid = -1;
499
      app->a11y_launch_error_message = g_strdup (error->message);
500
      g_clear_error (&error);
501
      goto error;
502
    }
503

            
504
  close (app->listenfd);
505
  app->listenfd = -1;
506

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

            
512
  if (app->socket_name)
513
    app->a11y_bus_address = g_strconcat ("unix:path=", addr.sun_path, NULL);
514
  else
515
    app->a11y_bus_address = g_strconcat ("unix:abstract=", addr.sun_path + 1, NULL);
516
  g_debug ("a11y bus address: %s", app->a11y_bus_address);
517

            
518
  return TRUE;
519

            
520
error:
521
  close (app->listenfd);
522
  app->state = A11Y_BUS_STATE_ERROR;
523

            
524
  return FALSE;
525
}
526
#else
527
static gboolean
528
ensure_a11y_bus_broker (A11yBusLauncher *app, char *config_path)
529
{
530
  return FALSE;
531
}
532
#endif
533

            
534
static gboolean
535
220
ensure_a11y_bus (A11yBusLauncher *app)
536
{
537
220
  char *config_path = NULL;
538
220
  gboolean success = FALSE;
539
  const gchar *xdg_runtime_dir;
540

            
541
220
  if (app->a11y_bus_pid != 0)
542
192
    return FALSE;
543

            
544
28
  if (g_file_test (SYSCONFDIR "/at-spi2/accessibility.conf", G_FILE_TEST_EXISTS))
545
    config_path = "--config-file=" SYSCONFDIR "/at-spi2/accessibility.conf";
546
  else
547
28
    config_path = "--config-file=" DATADIR "/defaults/at-spi2/accessibility.conf";
548

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

            
579
28
  const gchar *dbus_preference = g_getenv ("ATSPI_DBUS_IMPLEMENTATION");
580
#ifdef WANT_DBUS_BROKER
581
  // try dbus-broker first unless dbus-daemon explicitly selected via env var
582
28
  if (g_strcmp0 (dbus_preference, "dbus-daemon") != 0)
583
28
    success = ensure_a11y_bus_broker (app, config_path);
584
28
  if (!success)
585
    {
586
28
      if (!ensure_a11y_bus_daemon (app, config_path))
587
        return FALSE;
588
    }
589
#else
590
  // try dbus-daemon first unless dbus-broker explicitly selected via env var
591
  if (g_strcmp0 (dbus_preference, "dbus-broker") != 0)
592
    success = ensure_a11y_bus_daemon (app, config_path);
593
  if (!success)
594
    {
595
      if (!ensure_a11y_bus_broker (app, config_path))
596
        return FALSE;
597
    }
598
#endif
599

            
600
#ifdef HAVE_X11
601
28
  if (g_getenv ("DISPLAY") != NULL && g_getenv ("WAYLAND_DISPLAY") == NULL)
602
    {
603
28
      Display *display = XOpenDisplay (NULL);
604
28
      if (display)
605
        {
606
28
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
607
28
          XChangeProperty (display,
608
                           XDefaultRootWindow (display),
609
                           bus_address_atom,
610
                           XA_STRING, 8, PropModeReplace,
611
28
                           (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
612
28
          XFlush (display);
613
28
          XCloseDisplay (display);
614
28
          app->x11_prop_set = TRUE;
615
        }
616
    }
617
#endif
618

            
619
28
  return TRUE;
620
}
621

            
622
static void
623
220
handle_method_call (GDBusConnection *connection,
624
                    const gchar *sender,
625
                    const gchar *object_path,
626
                    const gchar *interface_name,
627
                    const gchar *method_name,
628
                    GVariant *parameters,
629
                    GDBusMethodInvocation *invocation,
630
                    gpointer user_data)
631
{
632
220
  A11yBusLauncher *app = user_data;
633

            
634
220
  if (g_strcmp0 (method_name, "GetAddress") == 0)
635
    {
636
220
      ensure_a11y_bus (app);
637
220
      if (app->a11y_bus_pid > 0)
638
220
        g_dbus_method_invocation_return_value (invocation,
639
                                               g_variant_new ("(s)", app->a11y_bus_address));
640
      else
641
        g_dbus_method_invocation_return_dbus_error (invocation,
642
                                                    "org.a11y.Bus.Error",
643
                                                    app->a11y_launch_error_message);
644
    }
645
220
}
646

            
647
static GVariant *
648
handle_get_property (GDBusConnection *connection,
649
                     const gchar *sender,
650
                     const gchar *object_path,
651
                     const gchar *interface_name,
652
                     const gchar *property_name,
653
                     GError **error,
654
                     gpointer user_data)
655
{
656
  A11yBusLauncher *app = user_data;
657

            
658
  if (g_strcmp0 (property_name, "IsEnabled") == 0)
659
    return g_variant_new ("b", app->a11y_enabled);
660
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
661
    return g_variant_new ("b", app->screen_reader_enabled);
662
  else
663
    return NULL;
664
}
665

            
666
static void
667
handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled, gboolean notify_gsettings)
668
{
669
  GVariantBuilder builder;
670
  GVariantBuilder invalidated_builder;
671

            
672
  if (enabled == app->a11y_enabled)
673
    return;
674

            
675
  app->a11y_enabled = enabled;
676

            
677
  if (notify_gsettings && app->interface_schema)
678
    {
679
      g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
680
                              enabled);
681
      g_settings_sync ();
682
    }
683

            
684
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
685
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
686
  g_variant_builder_add (&builder, "{sv}", "IsEnabled",
687
                         g_variant_new_boolean (enabled));
688

            
689
  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
690
                                 "org.freedesktop.DBus.Properties",
691
                                 "PropertiesChanged",
692
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
693
                                                &builder,
694
                                                &invalidated_builder),
695
                                 NULL);
696

            
697
  g_variant_builder_clear (&builder);
698
  g_variant_builder_clear (&invalidated_builder);
699
}
700

            
701
static void
702
handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled, gboolean notify_gsettings)
703
{
704
  GVariantBuilder builder;
705
  GVariantBuilder invalidated_builder;
706

            
707
  if (enabled == app->screen_reader_enabled)
708
    return;
709

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

            
715
  app->screen_reader_enabled = enabled;
716

            
717
  if (notify_gsettings && app->a11y_schema)
718
    {
719
      g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
720
                              enabled);
721
      g_settings_sync ();
722
    }
723

            
724
  g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
725
  g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
726
  g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
727
                         g_variant_new_boolean (enabled));
728

            
729
  g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
730
                                 "org.freedesktop.DBus.Properties",
731
                                 "PropertiesChanged",
732
                                 g_variant_new ("(sa{sv}as)", "org.a11y.Status",
733
                                                &builder,
734
                                                &invalidated_builder),
735
                                 NULL);
736

            
737
  g_variant_builder_clear (&builder);
738
  g_variant_builder_clear (&invalidated_builder);
739
}
740

            
741
static gboolean
742
handle_set_property (GDBusConnection *connection,
743
                     const gchar *sender,
744
                     const gchar *object_path,
745
                     const gchar *interface_name,
746
                     const gchar *property_name,
747
                     GVariant *value,
748
                     GError **error,
749
                     gpointer user_data)
750
{
751
  A11yBusLauncher *app = user_data;
752
  const gchar *type = g_variant_get_type_string (value);
753
  gboolean enabled;
754

            
755
  if (g_strcmp0 (type, "b") != 0)
756
    {
757
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
758
                   "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
759
      return FALSE;
760
    }
761

            
762
  enabled = g_variant_get_boolean (value);
763

            
764
  if (g_strcmp0 (property_name, "IsEnabled") == 0)
765
    {
766
      handle_a11y_enabled_change (app, enabled, TRUE);
767
      return TRUE;
768
    }
769
  else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
770
    {
771
      handle_screen_reader_enabled_change (app, enabled, TRUE);
772
      return TRUE;
773
    }
774
  else
775
    {
776
      g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
777
                   "Unknown property '%s'", property_name);
778
      return FALSE;
779
    }
780
}
781

            
782
static const GDBusInterfaceVTable bus_vtable = {
783
  handle_method_call,
784
  NULL, /* handle_get_property, */
785
  NULL  /* handle_set_property */
786
};
787

            
788
static const GDBusInterfaceVTable status_vtable = {
789
  NULL, /* handle_method_call */
790
  handle_get_property,
791
  handle_set_property
792
};
793

            
794
static void
795
28
on_bus_acquired (GDBusConnection *connection,
796
                 const gchar *name,
797
                 gpointer user_data)
798
{
799
28
  A11yBusLauncher *app = user_data;
800
  GError *error;
801
  guint registration_id;
802

            
803
28
  if (connection == NULL)
804
    {
805
      g_main_loop_quit (app->loop);
806
      return;
807
    }
808
28
  app->session_bus = connection;
809

            
810
28
  error = NULL;
811
28
  registration_id = g_dbus_connection_register_object (connection,
812
                                                       "/org/a11y/bus",
813
28
                                                       introspection_data->interfaces[0],
814
                                                       &bus_vtable,
815
                                                       _global_app,
816
                                                       NULL,
817
                                                       &error);
818
28
  if (registration_id == 0)
819
    {
820
      g_error ("%s", error->message);
821
      g_clear_error (&error);
822
    }
823

            
824
28
  g_dbus_connection_register_object (connection,
825
                                     "/org/a11y/bus",
826
28
                                     introspection_data->interfaces[1],
827
                                     &status_vtable,
828
                                     _global_app,
829
                                     NULL,
830
                                     NULL);
831
}
832

            
833
static void
834
on_name_lost (GDBusConnection *connection,
835
              const gchar *name,
836
              gpointer user_data)
837
{
838
  A11yBusLauncher *app = user_data;
839
  if (app->session_bus == NULL && connection == NULL && app->a11y_launch_error_message == NULL)
840
    app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
841
  g_main_loop_quit (app->loop);
842
}
843

            
844
static void
845
28
on_name_acquired (GDBusConnection *connection,
846
                  const gchar *name,
847
                  gpointer user_data)
848
{
849
28
  A11yBusLauncher *app = user_data;
850

            
851
28
  if (app->launch_immediately)
852
    {
853
      ensure_a11y_bus (app);
854
      if (app->state == A11Y_BUS_STATE_ERROR)
855
        {
856
          g_main_loop_quit (app->loop);
857
          return;
858
        }
859
    }
860

            
861
28
  g_bus_watch_name (G_BUS_TYPE_SESSION,
862
                    "org.gnome.SessionManager",
863
                    G_BUS_NAME_WATCHER_FLAGS_NONE,
864
                    name_appeared_handler, NULL,
865
                    user_data, NULL);
866
}
867

            
868
static int sigterm_pipefd[2];
869

            
870
static void
871
sigterm_handler (int signum)
872
{
873
  write (sigterm_pipefd[1], "X", 1);
874
}
875

            
876
static gboolean
877
on_sigterm_pipe (GIOChannel *channel,
878
                 GIOCondition condition,
879
                 gpointer data)
880
{
881
  A11yBusLauncher *app = data;
882

            
883
  g_main_loop_quit (app->loop);
884

            
885
  return FALSE;
886
}
887

            
888
static void
889
28
init_sigterm_handling (A11yBusLauncher *app)
890
{
891
  GIOChannel *sigterm_channel;
892

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

            
897
28
  sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
898
28
  g_io_add_watch (sigterm_channel,
899
                  G_IO_IN | G_IO_ERR | G_IO_HUP,
900
                  on_sigterm_pipe,
901
                  app);
902
28
  g_io_channel_unref (sigterm_channel);
903
28
}
904

            
905
static GSettings *
906
56
get_schema (const gchar *name)
907
{
908
#if GLIB_CHECK_VERSION(2, 32, 0)
909
56
  GSettingsSchemaSource *source = g_settings_schema_source_get_default ();
910
56
  if (!source)
911
    {
912
      g_error ("Cannot get the default GSettingsSchemaSource - is the gsettings-desktop-schemas package installed?");
913
    }
914

            
915
56
  GSettingsSchema *schema = g_settings_schema_source_lookup (source, name, FALSE);
916

            
917
56
  if (schema == NULL)
918
    return NULL;
919

            
920
56
  return g_settings_new_full (schema, NULL, NULL);
921
#else
922
  const char *const *schemas = NULL;
923
  gint i;
924

            
925
  schemas = g_settings_list_schemas ();
926
  for (i = 0; schemas[i]; i++)
927
    {
928
      if (!strcmp (schemas[i], name))
929
        return g_settings_new (schemas[i]);
930
    }
931

            
932
  return NULL;
933
#endif
934
}
935

            
936
static void
937
gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
938
{
939
  gboolean new_val = g_settings_get_boolean (gsettings, key);
940

            
941
  if (!strcmp (key, "toolkit-accessibility"))
942
    handle_a11y_enabled_change (_global_app, new_val, FALSE);
943
  else if (!strcmp (key, "screen-reader-enabled"))
944
    handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
945
}
946

            
947
int
948
28
main (int argc,
949
      char **argv)
950
{
951
28
  gboolean a11y_set = FALSE;
952
28
  gboolean screen_reader_set = FALSE;
953
  gint i;
954

            
955
28
  _global_app = g_new0 (A11yBusLauncher, 1);
956
28
  _global_app->loop = g_main_loop_new (NULL, FALSE);
957

            
958
28
  for (i = 1; i < argc; i++)
959
    {
960
      if (!strcmp (argv[i], "--launch-immediately"))
961
        _global_app->launch_immediately = TRUE;
962
      else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 1)
963
        a11y_set = TRUE;
964
      else if (sscanf (argv[i], "--screen-reader=%d",
965
                       &_global_app->screen_reader_enabled) == 1)
966
        screen_reader_set = TRUE;
967
      else
968
        g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
969
    }
970

            
971
28
  _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
972
28
  _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
973

            
974
28
  if (!a11y_set)
975
    {
976
28
      _global_app->a11y_enabled = _global_app->interface_schema
977
28
                                      ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
978
28
                                      : _global_app->launch_immediately;
979
    }
980

            
981
28
  if (!screen_reader_set)
982
    {
983
28
      _global_app->screen_reader_enabled = _global_app->a11y_schema
984
28
                                               ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
985
28
                                               : FALSE;
986
    }
987

            
988
28
  if (_global_app->interface_schema)
989
28
    g_signal_connect (_global_app->interface_schema,
990
                      "changed::toolkit-accessibility",
991
                      G_CALLBACK (gsettings_key_changed), _global_app);
992

            
993
28
  if (_global_app->a11y_schema)
994
28
    g_signal_connect (_global_app->a11y_schema,
995
                      "changed::screen-reader-enabled",
996
                      G_CALLBACK (gsettings_key_changed), _global_app);
997

            
998
28
  init_sigterm_handling (_global_app);
999

            
28
  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
28
  g_assert (introspection_data != NULL);
28
  _global_app->name_owner_id =
28
      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);
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)
        {
28
          Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
28
          XDeleteProperty (display,
                           XDefaultRootWindow (display),
                           bus_address_atom);
28
          XFlush (display);
28
          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;
}