1
/*
2
 * AT-SPI - Assistive Technology Service Provider Interface
3
 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4
 *
5
 * Copyright 2008 Novell, Inc.
6
 * Copyright 2001, 2002 Sun Microsystems Inc.,
7
 * Copyright 2001, 2002 Ximian, Inc.
8
 * Copyright 2008, 2009 Codethink Ltd.
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the
22
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23
 * Boston, MA 02110-1301, USA.
24
 */
25

            
26
#include <string.h>
27

            
28
#include <atk/atk.h>
29
#include <droute/droute.h>
30

            
31
#include "accessible-cache.h"
32
#include "accessible-stateset.h"
33
#include "bridge.h"
34
#include "introspection.h"
35
#include "object.h"
36
#include "spi-dbus.h"
37

            
38
/* TODO - This should possibly be a common define */
39
#define SPI_OBJECT_PREFIX "/org/a11y/atspi"
40
#define SPI_CACHE_OBJECT_SUFFIX "/cache"
41
#define SPI_CACHE_OBJECT_PATH SPI_OBJECT_PREFIX SPI_CACHE_OBJECT_SUFFIX
42

            
43
#define SPI_OBJECT_REFERENCE_SIGNATURE "(" DBUS_TYPE_STRING_AS_STRING \
44
    DBUS_TYPE_OBJECT_PATH_AS_STRING                                   \
45
                                       ")"
46

            
47
#define SPI_CACHE_ITEM_SIGNATURE "(" SPI_OBJECT_REFERENCE_SIGNATURE    \
48
    SPI_OBJECT_REFERENCE_SIGNATURE                                     \
49
        SPI_OBJECT_REFERENCE_SIGNATURE                                 \
50
            DBUS_TYPE_INT32_AS_STRING                                  \
51
                DBUS_TYPE_INT32_AS_STRING                              \
52
                    DBUS_TYPE_ARRAY_AS_STRING                          \
53
                        DBUS_TYPE_STRING_AS_STRING                     \
54
                            DBUS_TYPE_STRING_AS_STRING                 \
55
                                DBUS_TYPE_UINT32_AS_STRING             \
56
                                    DBUS_TYPE_STRING_AS_STRING         \
57
                                        DBUS_TYPE_ARRAY_AS_STRING      \
58
                                            DBUS_TYPE_UINT32_AS_STRING \
59
                                 ")"
60

            
61
/*---------------------------------------------------------------------------*/
62

            
63
static const char *
64
2768
get_toolkit_name (AtkObject *obj)
65
{
66
  static const char *toolkit_name = NULL;
67

            
68
2768
  if (!toolkit_name)
69
131
    toolkit_name = atk_get_toolkit_name ();
70

            
71
2768
  if (!toolkit_name)
72
    return "no toolkit name set yet";
73

            
74
  /* TODO: query object attributes */
75
2768
  return toolkit_name;
76
}
77

            
78
static gboolean
79
1384
should_call_index_in_parent (AtkObject *obj, AtkStateSet *set)
80
{
81
1384
  if (atk_state_set_contains_state (set, ATK_STATE_TRANSIENT))
82
    return FALSE;
83

            
84
1384
  if (!strcmp (get_toolkit_name (obj), "gtk") &&
85
      atk_object_get_role (obj) == ATK_ROLE_MENU_ITEM)
86
    return FALSE;
87

            
88
1384
  return TRUE;
89
}
90

            
91
static gboolean
92
1384
should_cache_children (AtkObject *obj, AtkStateSet *set)
93
{
94
2768
  if (atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS) ||
95
1384
      atk_state_set_contains_state (set, ATK_STATE_DEFUNCT))
96
    return FALSE;
97

            
98
1384
  if (!strcmp (get_toolkit_name (obj), "gtk") &&
99
      atk_object_get_role (obj) == ATK_ROLE_MENU)
100
    return FALSE;
101

            
102
1384
  return TRUE;
103
}
104

            
105
/*
106
 * Marshals the given AtkObject into the provided D-Bus iterator.
107
 *
108
 * The object is marshalled including all its client side cache data.
109
 * The format of the structure is (o(so)iiassusau).
110
 */
111
static void
112
1384
append_cache_item (AtkObject *obj, gpointer data)
113
{
114
  DBusMessageIter iter_struct, iter_sub_array;
115
  dbus_uint32_t states[2];
116
  dbus_int32_t count, index;
117
  AtkStateSet *set;
118
1384
  DBusMessageIter *iter_array = (DBusMessageIter *) data;
119
  const char *name, *desc;
120
  dbus_uint32_t role;
121

            
122
1384
  set = atk_object_ref_state_set (obj);
123
  AtkObject *application, *parent;
124

            
125
1384
  dbus_message_iter_open_container (iter_array, DBUS_TYPE_STRUCT, NULL,
126
                                    &iter_struct);
127

            
128
  /* Marshal object path */
129
1384
  spi_object_append_reference (&iter_struct, obj);
130

            
131
1384
  role = spi_accessible_role_from_atk_role (atk_object_get_role (obj));
132

            
133
  /* Marshal application */
134
1384
  application = spi_global_app_data->root;
135
1384
  spi_object_append_reference (&iter_struct, application);
136

            
137
  /* Marshal parent */
138
1384
  parent = atk_object_get_parent (obj);
139
1384
  if (parent == NULL)
140
    {
141
      /* TODO, move in to a 'Plug' wrapper. */
142
131
      if (ATK_IS_PLUG (obj))
143
        {
144
          char *id = g_object_get_data (G_OBJECT (obj), "dbus-plug-parent");
145
          char *bus_parent;
146
          char *path_parent;
147

            
148
          if (id)
149
            {
150
              bus_parent = g_strdup (id);
151
              if (bus_parent && (path_parent = g_utf8_strchr (bus_parent + 1, -1, ':')))
152
                {
153
                  DBusMessageIter iter_parent;
154
                  *(path_parent++) = '\0';
155
                  dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_STRUCT, NULL,
156
                                                    &iter_parent);
157
                  dbus_message_iter_append_basic (&iter_parent, DBUS_TYPE_STRING, &bus_parent);
158
                  dbus_message_iter_append_basic (&iter_parent, DBUS_TYPE_OBJECT_PATH, &path_parent);
159
                  dbus_message_iter_close_container (&iter_struct, &iter_parent);
160
                }
161
              else
162
                {
163
                  spi_object_append_null_reference (&iter_struct);
164
                }
165
            }
166
          else
167
            {
168
              spi_object_append_null_reference (&iter_struct);
169
            }
170
        }
171
131
      else if (role != ATSPI_ROLE_APPLICATION)
172
128
        spi_object_append_null_reference (&iter_struct);
173
      else
174
3
        spi_object_append_desktop_reference (&iter_struct);
175
    }
176
  else
177
    {
178
1253
      spi_object_append_reference (&iter_struct, parent);
179
    }
180

            
181
  /* Marshal index in parent */
182
1384
  index = (should_call_index_in_parent (obj, set)
183
1384
               ? atk_object_get_index_in_parent (obj)
184
1384
               : -1);
185
1384
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32, &index);
186

            
187
  /* marshal child count */
188
1384
  count = (should_cache_children (obj, set)
189
1384
               ? atk_object_get_n_accessible_children (obj)
190
1384
               : -1);
191

            
192
1384
  if (ATK_IS_SOCKET (obj) && atk_socket_is_occupied (ATK_SOCKET (obj)))
193
    count = 1;
194
1384
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_INT32, &count);
195

            
196
  /* Marshal interfaces */
197
1384
  dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "s",
198
                                    &iter_sub_array);
199
1384
  spi_object_append_interfaces (&iter_sub_array, obj);
200
1384
  dbus_message_iter_close_container (&iter_struct, &iter_sub_array);
201

            
202
  /* Marshal name */
203
1384
  name = atk_object_get_name (obj);
204
1384
  if (!name)
205
    name = "";
206
1384
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &name);
207

            
208
  /* Marshal role */
209
1384
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_UINT32, &role);
210

            
211
  /* Marshal description */
212
1384
  desc = atk_object_get_description (obj);
213
1384
  if (!desc)
214
    desc = "";
215
1384
  dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &desc);
216

            
217
  /* Marshal state set */
218
1384
  spi_atk_state_set_to_dbus_array (set, states);
219
1384
  dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "u",
220
                                    &iter_sub_array);
221
4152
  for (count = 0; count < 2; count++)
222
    {
223
2768
      dbus_message_iter_append_basic (&iter_sub_array, DBUS_TYPE_UINT32,
224
2768
                                      &states[count]);
225
    }
226
1384
  dbus_message_iter_close_container (&iter_struct, &iter_sub_array);
227

            
228
1384
  dbus_message_iter_close_container (iter_array, &iter_struct);
229
1384
  g_object_unref (set);
230
1384
}
231

            
232
/*---------------------------------------------------------------------------*/
233

            
234
static void
235
1384
ref_accessible_hf (gpointer key, gpointer obj_data, gpointer data)
236
{
237
1384
  g_object_ref (key);
238
1384
}
239

            
240
/* For use as a GHFunc */
241
static void
242
1384
append_accessible_hf (gpointer key, gpointer obj_data, gpointer data)
243
{
244
  /* Make sure it isn't a hyperlink */
245
1384
  if (ATK_IS_OBJECT (key))
246
1384
    append_cache_item (ATK_OBJECT (key), data);
247
1384
}
248

            
249
static void
250
1384
add_to_list_hf (gpointer key, gpointer obj_data, gpointer data)
251
{
252
1384
  GSList **listptr = data;
253
1384
  *listptr = g_slist_prepend (*listptr, key);
254
1384
}
255

            
256
/*---------------------------------------------------------------------------*/
257

            
258
static void
259
emit_cache_remove (SpiCache *cache, GObject *obj)
260
{
261
  DBusMessage *message;
262

            
263
  if (!spi_global_app_data->bus)
264
    return;
265

            
266
  if ((message = dbus_message_new_signal (SPI_CACHE_OBJECT_PATH,
267
                                          ATSPI_DBUS_INTERFACE_CACHE,
268
                                          "RemoveAccessible")))
269
    {
270
      DBusMessageIter iter;
271

            
272
      dbus_message_iter_init_append (message, &iter);
273

            
274
      spi_object_append_reference (&iter, ATK_OBJECT (obj));
275

            
276
      dbus_connection_send (spi_global_app_data->bus, message, NULL);
277

            
278
      dbus_message_unref (message);
279
    }
280
}
281

            
282
static void
283
emit_cache_add (SpiCache *cache, GObject *obj)
284
{
285
  AtkObject *accessible = ATK_OBJECT (obj);
286
  DBusMessage *message;
287

            
288
  if (!spi_global_app_data->bus)
289
    return;
290

            
291
  if ((message = dbus_message_new_signal (SPI_CACHE_OBJECT_PATH,
292
                                          ATSPI_DBUS_INTERFACE_CACHE,
293
                                          "AddAccessible")))
294
    {
295
      DBusMessageIter iter;
296

            
297
      dbus_message_iter_init_append (message, &iter);
298
      g_object_ref (accessible);
299
      append_cache_item (accessible, &iter);
300
      g_object_unref (accessible);
301

            
302
      dbus_connection_send (spi_global_app_data->bus, message, NULL);
303

            
304
      dbus_message_unref (message);
305
    }
306
}
307

            
308
/*---------------------------------------------------------------------------*/
309

            
310
static DBusMessage *
311
impl_GetRoot (DBusConnection *bus, DBusMessage *message, void *user_data)
312
{
313
  return spi_object_return_reference (message, spi_global_app_data->root);
314
}
315

            
316
/*---------------------------------------------------------------------------*/
317

            
318
static DBusMessage *
319
131
impl_GetItems (DBusConnection *bus, DBusMessage *message, void *user_data)
320
{
321
  DBusMessage *reply;
322
  DBusMessageIter iter, iter_array;
323
131
  GSList *pending_unrefs = NULL;
324

            
325
131
  if (bus == spi_global_app_data->bus)
326
    spi_atk_add_client (dbus_message_get_sender (message));
327

            
328
131
  reply = dbus_message_new_method_return (message);
329

            
330
131
  dbus_message_iter_init_append (reply, &iter);
331
131
  dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY,
332
                                    SPI_CACHE_ITEM_SIGNATURE, &iter_array);
333
131
  spi_cache_foreach (spi_global_cache, ref_accessible_hf, NULL);
334
131
  spi_cache_foreach (spi_global_cache, append_accessible_hf, &iter_array);
335
131
  spi_cache_foreach (spi_global_cache, add_to_list_hf, &pending_unrefs);
336
131
  g_slist_free_full (pending_unrefs, g_object_unref);
337
131
  dbus_message_iter_close_container (&iter, &iter_array);
338
131
  return reply;
339
}
340

            
341
static dbus_bool_t
342
impl_get_Version (DBusMessageIter *iter, void *user_data)
343
{
344
  return droute_return_v_uint32 (iter, SPI_DBUS_CACHE_VERSION);
345
}
346

            
347
/*---------------------------------------------------------------------------*/
348

            
349
static DRouteMethod methods[] = {
350
  { impl_GetRoot, "GetRoot" },
351
  { impl_GetItems, "GetItems" },
352
  { NULL, NULL }
353
};
354

            
355
static DRouteProperty properties[] = {
356
  { impl_get_Version, NULL, "version" },
357
  { NULL, NULL }
358
};
359

            
360
void
361
161
spi_initialize_cache (DRoutePath *path)
362
{
363
161
  droute_path_add_interface (path, ATSPI_DBUS_INTERFACE_CACHE, spi_org_a11y_atspi_Cache,
364
                             methods, properties);
365

            
366
161
  g_signal_connect (spi_global_cache, "object-added",
367
                    (GCallback) emit_cache_add, NULL);
368

            
369
161
  g_signal_connect (spi_global_cache, "object-removed",
370
                    (GCallback) emit_cache_remove, NULL);
371
161
};
372

            
373
/*END------------------------------------------------------------------------*/