launch universal-switch, if any of associated vconf flags is set
[platform/upstream/at-spi2-core.git] / bus / at-spi-bus-launcher.c
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 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 Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
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  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include "config.h"
24
25 #include <unistd.h>
26 #include <string.h>
27 #include <signal.h>
28 #include <sys/wait.h>
29 #include <errno.h>
30 #include <stdio.h>
31
32 #include <gio/gio.h>
33 #ifdef HAVE_X11
34 #include <X11/Xlib.h>
35 #include <X11/Xatom.h>
36 #endif
37
38 //TODO: move to vconf/vconf-internal-setting-keys.h?
39 #define VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH "db/setting/accessibility/universal-switch"
40 #define VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH_SCANNING "db/setting/accessibility/universal-switch/scanning"
41
42 #define MAX_NUMBER_OF_KEYS_PER_CLIENT 2
43
44 #define APP_CONTROL_OPERATION_SCREEN_READ "http://tizen.org/appcontrol/operation/read_screen"
45 #define APP_CONTROL_OPERATION_UNIVERSAL_SWITCH "http://tizen.org/appcontrol/operation/universal_switch"
46 #include <appsvc.h>
47 #include <vconf.h>
48
49 //uncomment if you want debug
50 //#ifndef TIZEN_ENGINEER_MODE
51 //#define TIZEN_ENGINEER_MODE
52 //#endif
53 #ifdef LOG_TAG
54 #undef LOG_TAG
55 #endif
56
57 #define LOG_TAG "ATSPI_BUS_LAUNCHER"
58
59 #include <dlog.h>
60 #include <aul.h>
61
62 //uncomment this if you want log suring startup
63 //seems like dlog is not working at startup time
64 #define ATSPI_BUS_LAUNCHER_LOG_TO_FILE
65
66 #ifdef ATSPI_BUS_LAUNCHER_LOG_TO_FILE
67 FILE *log_file;
68 #ifdef LOGD
69 #undef LOGD
70 #endif
71 #define LOGD(arg...) do {if (log_file) {fprintf(log_file, ##arg);fprintf(log_file, "\n"); fflush(log_file);}} while(0)
72 #endif
73
74 static gboolean _launch_process_repeat_until_success(gpointer user_data);
75
76 typedef enum {
77   A11Y_BUS_STATE_IDLE = 0,
78   A11Y_BUS_STATE_READING_ADDRESS,
79   A11Y_BUS_STATE_RUNNING,
80   A11Y_BUS_STATE_ERROR
81 } A11yBusState;
82
83 typedef struct {
84   const char * name;
85   const char * app_control_operation;
86   const char * vconf_key[MAX_NUMBER_OF_KEYS_PER_CLIENT];
87   int number_of_keys;
88   int launch_repeats;
89   int pid;
90 } A11yBusClient;
91
92 typedef struct {
93   GMainLoop *loop;
94   gboolean launch_immediately;
95   gboolean a11y_enabled;
96   gboolean screen_reader_enabled;
97   GHashTable *client_watcher_id;
98   GDBusConnection *session_bus;
99   GSettings *a11y_schema;
100   GSettings *interface_schema;
101
102   A11yBusClient screen_reader;
103   A11yBusClient universal_switch;
104
105   GDBusProxy *client_proxy;
106
107   A11yBusState state;
108
109   /* -1 == error, 0 == pending, > 0 == running */
110   int a11y_bus_pid;
111   char *a11y_bus_address;
112   int pipefd[2];
113   char *a11y_launch_error_message;
114 } A11yBusLauncher;
115
116 static A11yBusLauncher *_global_app = NULL;
117
118 static const gchar introspection_xml[] =
119   "<node>"
120   "  <interface name='org.a11y.Bus'>"
121   "    <method name='GetAddress'>"
122   "      <arg type='s' name='address' direction='out'/>"
123   "    </method>"
124   "  </interface>"
125   "<interface name='org.a11y.Status'>"
126   "<property name='IsEnabled' type='b' access='readwrite'/>"
127   "<property name='ScreenReaderEnabled' type='b' access='readwrite'/>"
128   "</interface>"
129   "</node>";
130 static GDBusNodeInfo *introspection_data = NULL;
131
132 static void
133 respond_to_end_session (GDBusProxy *proxy)
134 {
135   GVariant *parameters;
136
137   parameters = g_variant_new ("(bs)", TRUE, "");
138
139   g_dbus_proxy_call (proxy,
140                      "EndSessionResponse", parameters,
141                      G_DBUS_CALL_FLAGS_NONE,
142                      -1, NULL, NULL, NULL);
143 }
144
145 static void
146 g_signal_cb (GDBusProxy *proxy,
147              gchar      *sender_name,
148              gchar      *signal_name,
149              GVariant   *parameters,
150              gpointer    user_data)
151 {
152   A11yBusLauncher *app = user_data;
153
154   if (g_strcmp0 (signal_name, "QueryEndSession") == 0)
155     respond_to_end_session (proxy);
156   else if (g_strcmp0 (signal_name, "EndSession") == 0)
157     respond_to_end_session (proxy);
158   else if (g_strcmp0 (signal_name, "Stop") == 0)
159     g_main_loop_quit (app->loop);
160 }
161
162 static void
163 client_proxy_ready_cb (GObject      *source_object,
164                        GAsyncResult *res,
165                        gpointer      user_data)
166 {
167   A11yBusLauncher *app = user_data;
168   GError *error = NULL;
169
170   app->client_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
171
172   if (error != NULL)
173     {
174       g_warning ("Failed to get a client proxy: %s", error->message);
175       g_error_free (error);
176
177       return;
178     }
179
180   g_signal_connect (app->client_proxy, "g-signal",
181                     G_CALLBACK (g_signal_cb), app);
182 }
183
184 static void
185 register_client (A11yBusLauncher *app)
186 {
187   GDBusProxyFlags flags;
188   GDBusProxy *sm_proxy;
189   GError *error;
190   const gchar *app_id;
191   const gchar *autostart_id;
192   gchar *client_startup_id;
193   GVariant *parameters;
194   GVariant *variant;
195   gchar *object_path;
196
197   flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
198           G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS;
199
200   error = NULL;
201   sm_proxy = g_dbus_proxy_new_sync (app->session_bus, flags, NULL,
202                                     "org.gnome.SessionManager",
203                                     "/org/gnome/SessionManager",
204                                     "org.gnome.SessionManager",
205                                     NULL, &error);
206
207   if (error != NULL)
208     {
209       g_warning ("Failed to get session manager proxy: %s", error->message);
210       g_error_free (error);
211
212       return;
213     }
214
215   app_id = "at-spi-bus-launcher";
216   autostart_id = g_getenv ("DESKTOP_AUTOSTART_ID");
217
218   if (autostart_id != NULL)
219     {
220       client_startup_id = g_strdup (autostart_id);
221       g_unsetenv ("DESKTOP_AUTOSTART_ID");
222     }
223   else
224     {
225       client_startup_id = g_strdup ("");
226     }
227
228   parameters = g_variant_new ("(ss)", app_id, client_startup_id);
229   g_free (client_startup_id);
230
231   error = NULL;
232   variant = g_dbus_proxy_call_sync (sm_proxy,
233                                     "RegisterClient", parameters,
234                                     G_DBUS_CALL_FLAGS_NONE,
235                                     -1, NULL, &error);
236
237   g_object_unref (sm_proxy);
238
239   if (error != NULL)
240     {
241       g_warning ("Failed to register client: %s", error->message);
242       g_error_free (error);
243
244       return;
245     }
246
247   g_variant_get (variant, "(o)", &object_path);
248   g_variant_unref (variant);
249
250   flags = G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
251   g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, flags, NULL,
252                             "org.gnome.SessionManager", object_path,
253                             "org.gnome.SessionManager.ClientPrivate",
254                             NULL, client_proxy_ready_cb, app);
255
256   g_free (object_path);
257 }
258
259 static void
260 name_appeared_handler (GDBusConnection *connection,
261                        const gchar     *name,
262                        const gchar     *name_owner,
263                        gpointer         user_data)
264 {
265   A11yBusLauncher *app = user_data;
266
267   register_client (app);
268 }
269
270 static void
271 setup_bus_child (gpointer data)
272 {
273   A11yBusLauncher *app = data;
274   (void) app;
275
276   close (app->pipefd[0]);
277   dup2 (app->pipefd[1], 3);
278   close (app->pipefd[1]);
279
280   /* On Linux, tell the bus process to exit if this process goes away */
281 #ifdef __linux
282 #include <sys/prctl.h>
283   prctl (PR_SET_PDEATHSIG, 15);
284 #endif
285 }
286
287 /**
288  * unix_read_all_fd_to_string:
289  *
290  * Read all data from a file descriptor to a C string buffer.
291  */
292 static gboolean
293 unix_read_all_fd_to_string (int      fd,
294                             char    *buf,
295                             ssize_t  max_bytes)
296 {
297   ssize_t bytes_read;
298
299   while (max_bytes > 1 && (bytes_read = read (fd, buf, MAX (4096, max_bytes - 1))))
300     {
301       if (bytes_read < 0)
302         return FALSE;
303       buf += bytes_read;
304       max_bytes -= bytes_read;
305     }
306   *buf = '\0';
307   return TRUE;
308 }
309
310 static void
311 on_bus_exited (GPid     pid,
312                gint     status,
313                gpointer data)
314 {
315   A11yBusLauncher *app = data;
316
317   app->a11y_bus_pid = -1;
318   app->state = A11Y_BUS_STATE_ERROR;
319   if (app->a11y_launch_error_message == NULL)
320     {
321       if (WIFEXITED (status))
322         app->a11y_launch_error_message = g_strdup_printf ("Bus exited with code %d", WEXITSTATUS (status));
323       else if (WIFSIGNALED (status))
324         app->a11y_launch_error_message = g_strdup_printf ("Bus killed by signal %d", WTERMSIG (status));
325       else if (WIFSTOPPED (status))
326         app->a11y_launch_error_message = g_strdup_printf ("Bus stopped by signal %d", WSTOPSIG (status));
327     }
328   g_main_loop_quit (app->loop);
329 }
330
331 static gboolean
332 ensure_a11y_bus (A11yBusLauncher *app)
333 {
334   GPid pid;
335   char *argv[] = { DBUS_DAEMON, NULL, "--nofork", "--print-address", "3", NULL };
336   char addr_buf[2048];
337   GError *error = NULL;
338   const char *config_path = NULL;
339
340   if (app->a11y_bus_pid != 0)
341     return FALSE;
342
343   if (g_file_test (SYSCONFDIR"/at-spi2/accessibility.conf", G_FILE_TEST_EXISTS))
344       config_path = "--config-file="SYSCONFDIR"/at-spi2/accessibility.conf";
345   else
346       config_path = "--config-file="DATADIR"/defaults/at-spi2/accessibility.conf";
347
348   argv[1] = (char*)config_path;
349
350   if (pipe (app->pipefd) < 0)
351     g_error ("Failed to create pipe: %s", strerror (errno));
352
353   if (!g_spawn_async (NULL,
354                       argv,
355                       NULL,
356                       G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
357                       setup_bus_child,
358                       app,
359                       &pid,
360                       &error))
361     {
362       app->a11y_bus_pid = -1;
363       app->a11y_launch_error_message = g_strdup (error->message);
364       g_clear_error (&error);
365       goto error;
366     }
367
368   close (app->pipefd[1]);
369   app->pipefd[1] = -1;
370
371   g_child_watch_add (pid, on_bus_exited, app);
372
373   app->state = A11Y_BUS_STATE_READING_ADDRESS;
374   app->a11y_bus_pid = pid;
375   LOGD("Launched a11y bus, child is %ld", (long) pid);
376   if (!unix_read_all_fd_to_string (app->pipefd[0], addr_buf, sizeof (addr_buf)))
377     {
378       app->a11y_launch_error_message = g_strdup_printf ("Failed to read address: %s", strerror (errno));
379       kill (app->a11y_bus_pid, SIGTERM);
380       goto error;
381     }
382   close (app->pipefd[0]);
383   app->pipefd[0] = -1;
384   app->state = A11Y_BUS_STATE_RUNNING;
385
386   /* Trim the trailing newline */
387   app->a11y_bus_address = g_strchomp (g_strdup (addr_buf));
388   LOGD("a11y bus address: %s", app->a11y_bus_address);
389
390 #ifdef HAVE_X11
391   {
392     Display *display = XOpenDisplay (NULL);
393     if (display)
394       {
395         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
396         XChangeProperty (display,
397                          XDefaultRootWindow (display),
398                          bus_address_atom,
399                          XA_STRING, 8, PropModeReplace,
400                          (guchar *) app->a11y_bus_address, strlen (app->a11y_bus_address));
401         XFlush (display);
402         XCloseDisplay (display);
403       }
404   }
405 #endif
406
407   return TRUE;
408
409  error:
410   close (app->pipefd[0]);
411   close (app->pipefd[1]);
412   app->state = A11Y_BUS_STATE_ERROR;
413
414   return FALSE;
415 }
416
417 static void
418 handle_method_call (GDBusConnection       *connection,
419                     const gchar           *sender,
420                     const gchar           *object_path,
421                     const gchar           *interface_name,
422                     const gchar           *method_name,
423                     GVariant              *parameters,
424                     GDBusMethodInvocation *invocation,
425                     gpointer               user_data)
426 {
427   A11yBusLauncher *app = user_data;
428
429   if (g_strcmp0 (method_name, "GetAddress") == 0)
430     {
431       ensure_a11y_bus (app);
432       if (app->a11y_bus_pid > 0)
433         g_dbus_method_invocation_return_value (invocation,
434                                                g_variant_new ("(s)", app->a11y_bus_address));
435       else
436         g_dbus_method_invocation_return_dbus_error (invocation,
437                                                     "org.a11y.Bus.Error",
438                                                     app->a11y_launch_error_message);
439     }
440 }
441
442 static GVariant *
443 handle_get_property  (GDBusConnection       *connection,
444                       const gchar           *sender,
445                       const gchar           *object_path,
446                       const gchar           *interface_name,
447                       const gchar           *property_name,
448                     GError **error,
449                     gpointer               user_data)
450 {
451   A11yBusLauncher *app = user_data;
452
453   if (g_strcmp0 (property_name, "IsEnabled") == 0)
454     return g_variant_new ("b", app->a11y_enabled);
455   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
456     return g_variant_new ("b", app->screen_reader_enabled);
457   else
458     return NULL;
459 }
460
461 static void
462 handle_a11y_enabled_change (A11yBusLauncher *app, gboolean enabled,
463                                gboolean notify_gsettings)
464 {
465   GVariantBuilder builder;
466   GVariantBuilder invalidated_builder;
467
468   if (enabled == app->a11y_enabled)
469     return;
470
471   app->a11y_enabled = enabled;
472
473   if (notify_gsettings && app->interface_schema)
474     {
475       g_settings_set_boolean (app->interface_schema, "toolkit-accessibility",
476                               enabled);
477       g_settings_sync ();
478     }
479
480   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
481   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
482   g_variant_builder_add (&builder, "{sv}", "IsEnabled",
483                          g_variant_new_boolean (enabled));
484
485   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
486                                  "org.freedesktop.DBus.Properties",
487                                  "PropertiesChanged",
488                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
489                                                 &builder,
490                                                 &invalidated_builder),
491                                  NULL);
492
493   g_variant_builder_clear (&builder);
494   g_variant_builder_clear (&invalidated_builder);
495 }
496
497 static void
498 handle_screen_reader_enabled_change (A11yBusLauncher *app, gboolean enabled,
499                                gboolean notify_gsettings)
500 {
501   GVariantBuilder builder;
502   GVariantBuilder invalidated_builder;
503
504   if (enabled == app->screen_reader_enabled)
505     return;
506
507   app->screen_reader_enabled = enabled;
508
509   if (notify_gsettings && app->a11y_schema)
510     {
511       g_settings_set_boolean (app->a11y_schema, "screen-reader-enabled",
512                               enabled);
513       g_settings_sync ();
514     }
515
516   g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
517   g_variant_builder_init (&invalidated_builder, G_VARIANT_TYPE ("as"));
518   g_variant_builder_add (&builder, "{sv}", "ScreenReaderEnabled",
519                          g_variant_new_boolean (enabled));
520
521   g_dbus_connection_emit_signal (app->session_bus, NULL, "/org/a11y/bus",
522                                  "org.freedesktop.DBus.Properties",
523                                  "PropertiesChanged",
524                                  g_variant_new ("(sa{sv}as)", "org.a11y.Status",
525                                                 &builder,
526                                                 &invalidated_builder),
527                                  NULL);
528   g_variant_builder_clear (&builder);
529   g_variant_builder_clear (&invalidated_builder);
530 }
531
532 static gboolean
533 is_client_connected(A11yBusLauncher *app)
534 {
535   guint watchers = g_hash_table_size(app->client_watcher_id);
536   LOGD("clients connected: %d", watchers);
537   return watchers > 0;
538 }
539
540 static void
541 remove_client_watch(A11yBusLauncher *app,
542                                   const gchar     *sender)
543 {
544   LOGD("Remove client watcher for %s", sender);
545   guint watcher_id = GPOINTER_TO_UINT(g_hash_table_lookup(app->client_watcher_id, sender));
546   if (watcher_id)
547     g_bus_unwatch_name(watcher_id);
548
549   g_hash_table_remove(app->client_watcher_id, sender);
550   if (!is_client_connected(app))
551     handle_a11y_enabled_change (app, FALSE, TRUE);
552 }
553
554 static void
555 on_client_name_vanished (GDBusConnection *connection,
556                                        const gchar     *name,
557                                        gpointer         user_data)
558 {
559   A11yBusLauncher *app = user_data;
560   remove_client_watch(app, name);
561 }
562
563 static void
564 add_client_watch(A11yBusLauncher *app,
565                                const gchar     *sender)
566 {
567   LOGD("Add client watcher for %s", sender);
568
569   if (g_hash_table_contains(app->client_watcher_id, sender))
570     {
571       LOGI("Watcher for %s already registered", sender);
572       return;
573     }
574
575   guint watcher_id = g_bus_watch_name(G_BUS_TYPE_SESSION,
576                      sender,
577                      G_BUS_NAME_WATCHER_FLAGS_NONE,
578                      NULL,
579                      on_client_name_vanished,
580                      app,
581                      NULL);
582
583   g_hash_table_insert(app->client_watcher_id, g_strdup(sender), GUINT_TO_POINTER(watcher_id));
584   handle_a11y_enabled_change (app, TRUE, TRUE);
585 }
586
587 static gboolean
588 handle_set_property  (GDBusConnection       *connection,
589                       const gchar           *sender,
590                       const gchar           *object_path,
591                       const gchar           *interface_name,
592                       const gchar           *property_name,
593                       GVariant *value,
594                     GError **error,
595                     gpointer               user_data)
596 {
597   A11yBusLauncher *app = user_data;
598   const gchar *type = g_variant_get_type_string (value);
599   gboolean enabled;
600
601   if (g_strcmp0 (type, "b") != 0)
602     {
603       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
604                        "org.a11y.Status.%s expects a boolean but got %s", property_name, type);
605       return FALSE;
606     }
607
608   enabled = g_variant_get_boolean (value);
609
610   if (g_strcmp0 (property_name, "IsEnabled") == 0)
611     {
612       if (enabled)
613         add_client_watch(app, sender);
614       else
615         remove_client_watch(app, sender);
616       return TRUE;
617     }
618   else if (g_strcmp0 (property_name, "ScreenReaderEnabled") == 0)
619     {
620       handle_screen_reader_enabled_change (app, enabled, TRUE);
621       return TRUE;
622     }
623   else
624     {
625       g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
626                        "Unknown property '%s'", property_name);
627       return FALSE;
628     }
629 }
630
631 static const GDBusInterfaceVTable bus_vtable =
632 {
633   handle_method_call,
634   NULL, /* handle_get_property, */
635   NULL  /* handle_set_property */
636 };
637
638 static const GDBusInterfaceVTable status_vtable =
639 {
640   NULL, /* handle_method_call */
641   handle_get_property,
642   handle_set_property
643 };
644
645 static void
646 on_bus_acquired (GDBusConnection *connection,
647                  const gchar     *name,
648                  gpointer         user_data)
649 {
650   A11yBusLauncher *app = user_data;
651   GError *error;
652   guint registration_id;
653
654   if (connection == NULL)
655     {
656       g_main_loop_quit (app->loop);
657       return;
658     }
659   app->session_bus = connection;
660
661   if (app->launch_immediately)
662     {
663       ensure_a11y_bus (app);
664       if (app->state == A11Y_BUS_STATE_ERROR)
665         {
666           g_main_loop_quit (app->loop);
667           return;
668         }
669     }
670
671   error = NULL;
672   registration_id = g_dbus_connection_register_object (connection,
673                                                        "/org/a11y/bus",
674                                                        introspection_data->interfaces[0],
675                                                        &bus_vtable,
676                                                        _global_app,
677                                                        NULL,
678                                                        &error);
679   if (registration_id == 0)
680     {
681       g_error ("%s", error->message);
682       g_clear_error (&error);
683     }
684
685   g_dbus_connection_register_object (connection,
686                                      "/org/a11y/bus",
687                                      introspection_data->interfaces[1],
688                                      &status_vtable,
689                                      _global_app,
690                                      NULL,
691                                      NULL);
692 }
693
694 static void
695 on_name_lost (GDBusConnection *connection,
696               const gchar     *name,
697               gpointer         user_data)
698 {
699   A11yBusLauncher *app = user_data;
700   if (app->session_bus == NULL
701       && connection == NULL
702       && app->a11y_launch_error_message == NULL)
703     app->a11y_launch_error_message = g_strdup ("Failed to connect to session bus");
704   g_main_loop_quit (app->loop);
705 }
706
707 static void
708 on_name_acquired (GDBusConnection *connection,
709                   const gchar     *name,
710                   gpointer         user_data)
711 {
712   g_bus_watch_name (G_BUS_TYPE_SESSION,
713                     "org.gnome.SessionManager",
714                     G_BUS_NAME_WATCHER_FLAGS_NONE,
715                     name_appeared_handler, NULL,
716                     user_data, NULL);
717 }
718
719 static int sigterm_pipefd[2];
720
721 static void
722 sigterm_handler (int signum)
723 {
724   write (sigterm_pipefd[1], "X", 1);
725 }
726
727 static gboolean
728 on_sigterm_pipe (GIOChannel  *channel,
729                  GIOCondition condition,
730                  gpointer     data)
731 {
732   A11yBusLauncher *app = data;
733
734   g_main_loop_quit (app->loop);
735
736   return FALSE;
737 }
738
739 static void
740 init_sigterm_handling (A11yBusLauncher *app)
741 {
742   GIOChannel *sigterm_channel;
743
744   if (pipe (sigterm_pipefd) < 0)
745     g_error ("Failed to create pipe: %s", strerror (errno));
746   signal (SIGTERM, sigterm_handler);
747
748   sigterm_channel = g_io_channel_unix_new (sigterm_pipefd[0]);
749   g_io_add_watch (sigterm_channel,
750                   G_IO_IN | G_IO_ERR | G_IO_HUP,
751                   on_sigterm_pipe,
752                   app);
753 }
754
755 static gboolean
756 already_running ()
757 {
758 #ifdef HAVE_X11
759   Atom AT_SPI_BUS;
760   Atom actual_type;
761   Display *bridge_display;
762   int actual_format;
763   unsigned char *data = NULL;
764   unsigned long nitems;
765   unsigned long leftover;
766   gboolean result = FALSE;
767
768   bridge_display = XOpenDisplay (NULL);
769   if (!bridge_display)
770               return FALSE;
771
772   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
773   XGetWindowProperty (bridge_display,
774                       XDefaultRootWindow (bridge_display),
775                       AT_SPI_BUS, 0L,
776                       (long) BUFSIZ, False,
777                       (Atom) 31, &actual_type, &actual_format,
778                       &nitems, &leftover, &data);
779
780   if (data)
781   {
782     GDBusConnection *bus;
783     bus = g_dbus_connection_new_for_address_sync ((const gchar *)data, 0,
784                                                   NULL, NULL, NULL);
785     if (bus != NULL)
786       {
787         result = TRUE;
788         g_object_unref (bus);
789       }
790   }
791
792   XCloseDisplay (bridge_display);
793   return result;
794 #else
795   return FALSE;
796 #endif
797 }
798
799 static GSettings *
800 get_schema (const gchar *name)
801 {
802   const char * const *schemas = NULL;
803   gint i;
804
805   schemas = g_settings_list_schemas ();
806   for (i = 0; schemas[i]; i++)
807   {
808     if (!strcmp (schemas[i], name))
809       return g_settings_new (schemas[i]);
810   }
811
812   return NULL;
813 }
814
815 static void
816 gsettings_key_changed (GSettings *gsettings, const gchar *key, void *user_data)
817 {
818   gboolean new_val = g_settings_get_boolean (gsettings, key);
819
820   if (!strcmp (key, "toolkit-accessibility"))
821     handle_a11y_enabled_change (_global_app, new_val, FALSE);
822   else if (!strcmp (key, "screen-reader-enabled"))
823     handle_screen_reader_enabled_change (_global_app, new_val, FALSE);
824 }
825
826 static int
827 _process_dead_tracker (int pid, void *data)
828 {
829   A11yBusLauncher *app = data;
830
831   if (app->screen_reader.pid > 0 && pid == app->screen_reader.pid)
832     {
833       LOGE("screen reader is dead, pid: %d, restarting", pid);
834       app->screen_reader.pid = 0;
835       g_timeout_add_seconds (2, _launch_process_repeat_until_success, &app->screen_reader);
836     }
837
838   if (app->universal_switch.pid > 0 && pid == app->universal_switch.pid)
839     {
840       LOGE("universal switch is dead, pid: %d, restarting", pid);
841       app->universal_switch.pid = 0;
842       g_timeout_add_seconds (2, _launch_process_repeat_until_success, &app->universal_switch);
843     }
844   return 0;
845 }
846
847 static void
848 _register_process_dead_tracker ()
849 {
850         if(_global_app->screen_reader.pid > 0 || _global_app->universal_switch.pid > 0) {
851                 LOGD("registering process dead tracker");
852                 aul_listen_app_dead_signal(_process_dead_tracker, _global_app);
853         } else {
854                 LOGD("unregistering process dead tracker");
855                 aul_listen_app_dead_signal(NULL, NULL);
856         }
857 }
858
859
860 static gboolean
861 _launch_client(A11yBusClient *client, gboolean by_vconf_change)
862 {
863    LOGD("Launching %s", client->name);
864
865    bundle *kb = NULL;
866    gboolean ret = FALSE;
867
868    kb = bundle_create();
869
870    if (kb == NULL)
871      {
872         LOGD("Can't create bundle");
873         return FALSE;
874      }
875
876    if (by_vconf_change)
877      {
878         if (bundle_add_str(kb, "by_vconf_change", "yes") != BUNDLE_ERROR_NONE)
879           {
880              LOGD("Can't add information to bundle");
881           }
882      }
883
884    int operation_error = appsvc_set_operation(kb, client->app_control_operation);
885    LOGD("appsvc_set_operation: %i", operation_error);
886
887    client->pid = appsvc_run_service(kb, 0, NULL, NULL);
888
889    if (client->pid > 0)
890      {
891         LOGD("Process launched with pid: %i", client->pid);
892         _register_process_dead_tracker();
893         ret = TRUE;
894      }
895    else
896      {
897         LOGD("Can't start %s - error code: %i", client->name, client->pid);
898      }
899
900    bundle_free(kb);
901    return ret;
902 }
903
904 static gboolean
905 _launch_process_repeat_until_success(gpointer user_data) {
906     A11yBusClient *client = user_data;
907
908     if (client->launch_repeats > 100 || client->pid > 0)
909       {
910          //do not try anymore
911          return FALSE;
912       }
913
914     gboolean ret = _launch_client(client, FALSE);
915
916     if (ret)
917       {
918          //we managed to
919          client->launch_repeats = 0;
920          return FALSE;
921       }
922     client->launch_repeats++;
923     //try again
924     return TRUE;
925 }
926
927 static gboolean
928 _terminate_process(int pid)
929 {
930    int ret;
931    int ret_aul;
932    if (pid <= 0)
933      return FALSE;
934
935    int status = aul_app_get_status_bypid(pid);
936
937    if (status < 0)
938      {
939        LOGD("App with pid %d already terminated", pid);
940        return TRUE;
941      }
942
943    LOGD("terminate process with pid %d", pid);
944    ret_aul = aul_terminate_pid(pid);
945    if (ret_aul >= 0)
946      {
947         LOGD("Terminating with aul_terminate_pid: return is %d", ret_aul);
948         return TRUE;
949      }
950    else
951      LOGD("aul_terminate_pid failed: return is %d", ret_aul);
952
953    LOGD("Unable to terminate process using aul api. Sending SIGTERM signal");
954    ret = kill(pid, SIGTERM);
955    if (!ret)
956      {
957         return TRUE;
958      }
959
960    LOGD("Unable to terminate process: %d with api or signal.", pid);
961    return FALSE;
962 }
963
964 static gboolean
965 _terminate_client(A11yBusClient *client)
966 {
967    LOGD("Terminating %s", client->name);
968    int pid = client->pid;
969    client->pid = 0;
970    _register_process_dead_tracker();
971    gboolean ret = _terminate_process(pid);
972    return ret;
973 }
974
975 void vconf_client_cb(keynode_t *node, void *user_data)
976 {
977    A11yBusClient *client = user_data;
978
979    gboolean client_needed = FALSE;
980    int i;
981    for (i = 0; i < client->number_of_keys; i++) {
982       int status = 0;
983       int ret =vconf_get_bool(client->vconf_key[i], &status);
984       if (ret != 0)
985       {
986         LOGD("Could not read %s key value.\n", client->vconf_key[i]);
987         return;
988       }
989       LOGD("vconf_keynode_get_bool(node): %i", status);
990       if (status < 0)
991         return;
992
993       if (status == 1) {
994         client_needed = TRUE;
995         break;
996       }
997    }
998
999    //check if process really exists (e.g didn't crash)
1000    if (client->pid > 0)
1001      {
1002         int err = kill(client->pid,0);
1003         //process doesn't exist
1004         if (err == ESRCH)
1005           client->pid = 0;
1006      }
1007
1008    LOGD("client_needed: %i, client->pid: %i", client_needed, client->pid);
1009    if (!client_needed && (client->pid > 0))
1010            _terminate_client(client);
1011    else if (client_needed && (client->pid <= 0))
1012      _launch_client(client, TRUE);
1013 }
1014
1015
1016 static gboolean register_executable(A11yBusClient *client)
1017 {
1018   gboolean client_needed = FALSE;
1019
1020   int i;
1021   for (i = 0; i < client->number_of_keys; i++) {
1022     if (!client->vconf_key[i]) {
1023       LOGE("Vconf_key missing for client: %d \n", i);
1024       return FALSE;
1025     }
1026
1027     int status = 0;
1028     int ret = vconf_get_bool(client->vconf_key[i], &status);
1029     if (ret != 0)
1030     {
1031       LOGD("Could not read %s key value.\n", client->vconf_key[i]);
1032       return FALSE;
1033     }
1034     ret = vconf_notify_key_changed(client->vconf_key[i], vconf_client_cb, client);
1035     if (ret != 0)
1036     {
1037       LOGD("Could not add information level callback\n");
1038       return FALSE;
1039     }
1040     if (status)
1041       client_needed = TRUE;
1042   }
1043
1044   if (client_needed)
1045   g_timeout_add_seconds(2,_launch_process_repeat_until_success, client);
1046   return TRUE;
1047 }
1048
1049 int
1050 main (int    argc,
1051       char **argv)
1052 {
1053 #ifdef ATSPI_BUS_LAUNCHER_LOG_TO_FILE
1054   log_file = fopen("/tmp/at-spi-bus-launcher.log", "a");
1055 #endif
1056
1057   LOGD("Starting atspi bus launcher");
1058   gboolean a11y_set = FALSE;
1059   gboolean screen_reader_set = FALSE;
1060   gint i;
1061
1062   if (already_running ())
1063     {
1064        LOGD("atspi bus launcher is already running");
1065        return 0;
1066     }
1067
1068   _global_app = g_slice_new0 (A11yBusLauncher);
1069   _global_app->loop = g_main_loop_new (NULL, FALSE);
1070   _global_app->client_watcher_id = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1071
1072   _global_app->screen_reader.name = "screen-reader";
1073   _global_app->screen_reader.app_control_operation = APP_CONTROL_OPERATION_SCREEN_READ;
1074   _global_app->screen_reader.vconf_key[0] = VCONFKEY_SETAPPL_ACCESSIBILITY_TTS;
1075   _global_app->screen_reader.number_of_keys = 1;
1076
1077   _global_app->universal_switch.name = "universal-switch";
1078   _global_app->universal_switch.app_control_operation = APP_CONTROL_OPERATION_UNIVERSAL_SWITCH;
1079   _global_app->universal_switch.vconf_key[0] = VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH;
1080   _global_app->universal_switch.vconf_key[1] = VCONFKEY_SETAPPL_ACCESSIBILITY_UNIVERSAL_SWITCH_SCANNING;
1081   _global_app->universal_switch.number_of_keys = 2;
1082
1083   for (i = 1; i < argc; i++)
1084     {
1085       if (!strcmp (argv[i], "--launch-immediately"))
1086         _global_app->launch_immediately = TRUE;
1087       else if (sscanf (argv[i], "--a11y=%d", &_global_app->a11y_enabled) == 1)
1088         a11y_set = TRUE;
1089       else if (sscanf (argv[i], "--screen-reader=%d",
1090                        &_global_app->screen_reader_enabled) == 1)
1091         screen_reader_set = TRUE;
1092     else
1093       g_error ("usage: %s [--launch-immediately] [--a11y=0|1] [--screen-reader=0|1]", argv[0]);
1094     }
1095
1096   _global_app->interface_schema = get_schema ("org.gnome.desktop.interface");
1097   _global_app->a11y_schema = get_schema ("org.gnome.desktop.a11y.applications");
1098
1099   if (!a11y_set)
1100     {
1101       _global_app->a11y_enabled = _global_app->interface_schema
1102                                   ? g_settings_get_boolean (_global_app->interface_schema, "toolkit-accessibility")
1103                                   : _global_app->launch_immediately;
1104     }
1105
1106   if (!screen_reader_set)
1107     {
1108       _global_app->screen_reader_enabled = _global_app->a11y_schema
1109                                   ? g_settings_get_boolean (_global_app->a11y_schema, "screen-reader-enabled")
1110                                   : FALSE;
1111     }
1112
1113   if (_global_app->interface_schema)
1114     g_signal_connect (_global_app->interface_schema,
1115                       "changed::toolkit-accessibility",
1116                       G_CALLBACK (gsettings_key_changed), _global_app);
1117
1118   if (_global_app->a11y_schema)
1119     g_signal_connect (_global_app->a11y_schema,
1120                       "changed::screen-reader-enabled",
1121                       G_CALLBACK (gsettings_key_changed), _global_app);
1122
1123   init_sigterm_handling (_global_app);
1124
1125   introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
1126   g_assert (introspection_data != NULL);
1127
1128   g_bus_own_name (G_BUS_TYPE_SESSION,
1129                                   "org.a11y.Bus",
1130                                   G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
1131                                   on_bus_acquired,
1132                                   on_name_acquired,
1133                                   on_name_lost,
1134                                   _global_app,
1135                                   NULL);
1136
1137   register_executable (&_global_app->screen_reader);
1138   register_executable (&_global_app->universal_switch);
1139
1140   g_main_loop_run (_global_app->loop);
1141
1142   if (_global_app->a11y_bus_pid > 0)
1143     kill (_global_app->a11y_bus_pid, SIGTERM);
1144
1145   /* Clear the X property if our bus is gone; in the case where e.g.
1146    * GDM is launching a login on an X server it was using before,
1147    * we don't want early login processes to pick up the stale address.
1148    */
1149 #ifdef HAVE_X11
1150   {
1151     Display *display = XOpenDisplay (NULL);
1152     if (display)
1153       {
1154         Atom bus_address_atom = XInternAtom (display, "AT_SPI_BUS", False);
1155         XDeleteProperty (display,
1156                          XDefaultRootWindow (display),
1157                          bus_address_atom);
1158
1159         XFlush (display);
1160         XCloseDisplay (display);
1161       }
1162   }
1163 #endif
1164
1165   if (_global_app->a11y_launch_error_message)
1166     {
1167       g_printerr ("Failed to launch bus: %s", _global_app->a11y_launch_error_message);
1168       return 1;
1169     }
1170   return 0;
1171 }