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 2008, 2009, 2010 Codethink Ltd.
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the
20
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21
 * Boston, MA 02110-1301, USA.
22
 */
23

            
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27

            
28
#include "accessible-register.h"
29
#include "bridge.h"
30

            
31
/*
32
 * This module is responsible for keeping track of all the AtkObjects in
33
 * the application, so that they can be accessed remotely and placed in
34
 * a client side cache.
35
 *
36
 * To access an AtkObject remotely we need to provide a D-Bus object
37
 * path for it. The D-Bus object paths used have a standard prefix
38
 * (SPI_ATK_OBJECT_PATH_PREFIX). Appended to this prefix is a string
39
 * representation of an integer reference. So to access an AtkObject
40
 * remotely we keep a Hashtable that maps the given reference to
41
 * the AtkObject pointer. An object in this hash table is said to be 'registered'.
42
 *
43
 * The architecture of AT-SPI dbus is such that AtkObjects are not
44
 * remotely reference counted. This means that we need to keep track of
45
 * object destruction. When an object is destroyed it must be 'deregistered'
46
 * To do this lookup we keep a dbus-id attribute on each AtkObject.
47
 *
48
 */
49

            
50
#define SPI_ATK_PATH_PREFIX_LENGTH 27
51
#define SPI_ATK_OBJECT_PATH_PREFIX "/org/a11y/atspi/accessible/"
52
#define SPI_ATK_OBJECT_PATH_ROOT "root"
53

            
54
#define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "%d"
55

            
56
#define SPI_DBUS_ID "spi-dbus-id"
57

            
58
SpiRegister *spi_global_register = NULL;
59

            
60
static const gchar *spi_register_root_path = SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_ROOT;
61

            
62
enum
63
{
64
  OBJECT_REGISTERED,
65
  OBJECT_DEREGISTERED,
66
  LAST_SIGNAL
67
};
68
static guint register_signals[LAST_SIGNAL] = { 0 };
69

            
70
/*---------------------------------------------------------------------------*/
71

            
72
static void
73
spi_register_finalize (GObject *object);
74

            
75
/*---------------------------------------------------------------------------*/
76

            
77
161
G_DEFINE_TYPE (SpiRegister, spi_register, G_TYPE_OBJECT)
78

            
79
static void
80
161
spi_register_class_init (SpiRegisterClass *klass)
81
{
82
161
  GObjectClass *object_class = (GObjectClass *) klass;
83

            
84
161
  spi_register_parent_class = g_type_class_ref (G_TYPE_OBJECT);
85

            
86
161
  object_class->finalize = spi_register_finalize;
87

            
88
161
  register_signals[OBJECT_REGISTERED] =
89
161
      g_signal_new ("object-registered",
90
                    SPI_REGISTER_TYPE,
91
                    G_SIGNAL_ACTION,
92
                    0,
93
                    NULL,
94
                    NULL,
95
                    g_cclosure_marshal_VOID__OBJECT,
96
                    G_TYPE_NONE,
97
                    1,
98
                    G_TYPE_OBJECT);
99

            
100
161
  register_signals[OBJECT_DEREGISTERED] =
101
161
      g_signal_new ("object-deregistered",
102
                    SPI_REGISTER_TYPE,
103
                    G_SIGNAL_ACTION,
104
                    0,
105
                    NULL,
106
                    NULL,
107
                    g_cclosure_marshal_VOID__OBJECT,
108
                    G_TYPE_NONE,
109
                    1,
110
                    G_TYPE_OBJECT);
111
161
}
112

            
113
static void
114
161
spi_register_init (SpiRegister *reg)
115
{
116
161
  reg->ref2ptr = g_hash_table_new (g_direct_hash, g_direct_equal);
117
161
  reg->reference_counter = 0;
118
161
}
119

            
120
static void
121
deregister_object (gpointer data, GObject *gobj)
122
{
123
  SpiRegister *reg = SPI_REGISTER (data);
124

            
125
  spi_register_deregister_object (reg, gobj, FALSE);
126
}
127

            
128
static void
129
1353
spi_register_remove_weak_ref (gpointer key, gpointer val, gpointer reg)
130
{
131
1353
  g_object_weak_unref (val, deregister_object, reg);
132
1353
}
133

            
134
static void
135
161
spi_register_finalize (GObject *object)
136
{
137
161
  SpiRegister *reg = SPI_REGISTER (object);
138

            
139
161
  g_hash_table_foreach (reg->ref2ptr, spi_register_remove_weak_ref, reg);
140
161
  g_hash_table_unref (reg->ref2ptr);
141

            
142
161
  G_OBJECT_CLASS (spi_register_parent_class)->finalize (object);
143
161
}
144

            
145
/*---------------------------------------------------------------------------*/
146

            
147
/*
148
 * Each AtkObject must be asssigned a D-Bus path (Reference)
149
 *
150
 * This function provides an integer reference for a new
151
 * AtkObject.
152
 *
153
 * TODO: Make this reference a little more unique, this is shoddy.
154
 */
155
static guint
156
1353
assign_reference (SpiRegister *reg)
157
{
158
1353
  reg->reference_counter++;
159
  /* Reference of 0 not allowed as used as direct key in hash table */
160
1353
  if (reg->reference_counter == 0)
161
    reg->reference_counter++;
162
1353
  return reg->reference_counter;
163
}
164

            
165
/*---------------------------------------------------------------------------*/
166

            
167
/*
168
 * Returns the reference of the object, or 0 if it is not registered.
169
 */
170
static guint
171
4131
object_to_ref (GObject *gobj)
172
{
173
4131
  return GPOINTER_TO_INT (g_object_get_data (gobj, SPI_DBUS_ID));
174
}
175

            
176
/*
177
 * Converts the Accessible object reference to its D-Bus object path
178
 */
179
static gchar *
180
2778
ref_to_path (guint ref)
181
{
182
2778
  return g_strdup_printf (SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref);
183
}
184

            
185
/*---------------------------------------------------------------------------*/
186

            
187
/*
188
 * Callback for when a registered AtkObject is destroyed.
189
 *
190
 * Removes the AtkObject from the reference lookup tables, meaning
191
 * it is no longer exposed over D-Bus.
192
 */
193
void
194
spi_register_deregister_object (SpiRegister *reg, GObject *gobj, gboolean unref)
195
{
196
  guint ref;
197

            
198
  ref = object_to_ref (gobj);
199
  if (ref != 0)
200
    {
201
      if (unref)
202
        {
203
          g_signal_emit (reg,
204
                         register_signals[OBJECT_DEREGISTERED],
205
                         0,
206
                         gobj);
207
          g_object_weak_unref (gobj, deregister_object, reg);
208
        }
209
      g_hash_table_remove (reg->ref2ptr, GINT_TO_POINTER (ref));
210

            
211
#ifdef SPI_ATK_DEBUG
212
      g_debug ("DEREG  - %d", ref);
213
#endif
214
    }
215
}
216

            
217
static void
218
1353
register_object (SpiRegister *reg, GObject *gobj)
219
{
220
  guint ref;
221
1353
  g_return_if_fail (G_IS_OBJECT (gobj));
222

            
223
1353
  ref = assign_reference (reg);
224

            
225
1353
  g_hash_table_insert (reg->ref2ptr, GINT_TO_POINTER (ref), gobj);
226
1353
  g_object_set_data (G_OBJECT (gobj), SPI_DBUS_ID, GINT_TO_POINTER (ref));
227
1353
  g_object_weak_ref (G_OBJECT (gobj), deregister_object, reg);
228

            
229
#ifdef SPI_ATK_DEBUG
230
  g_debug ("REG  - %d", ref);
231
#endif
232

            
233
1353
  g_signal_emit (reg, register_signals[OBJECT_REGISTERED], 0, gobj);
234
}
235

            
236
/*---------------------------------------------------------------------------*/
237

            
238
/*
239
 * Used to lookup an GObject from its D-Bus path.
240
 *
241
 * If the D-Bus path is not found this function returns NULL.
242
 */
243
GObject *
244
1001
spi_register_path_to_object (SpiRegister *reg, const char *path)
245
{
246
  guint index;
247
  void *data;
248

            
249
1001
  g_return_val_if_fail (path, NULL);
250

            
251
1001
  if (strncmp (path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0)
252
    return NULL;
253

            
254
1001
  path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */
255

            
256
  /* Map the root path to the root object. */
257
1001
  if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_ROOT, path))
258
665
    return G_OBJECT (spi_global_app_data->root);
259

            
260
336
  index = atoi (path);
261
336
  data = g_hash_table_lookup (reg->ref2ptr, GINT_TO_POINTER (index));
262
336
  if (data)
263
336
    return G_OBJECT (data);
264
  else
265
    return NULL;
266
}
267

            
268
GObject *
269
986
spi_global_register_path_to_object (const char *path)
270
{
271
986
  return spi_register_path_to_object (spi_global_register, path);
272
}
273

            
274
/*
275
 * Used to lookup a D-Bus path from the GObject.
276
 *
277
 * If the objects is not already registered,
278
 * this function will register it.
279
 */
280
gchar *
281
4699
spi_register_object_to_path (SpiRegister *reg, GObject *gobj)
282
{
283
  guint ref;
284

            
285
4699
  if (gobj == NULL)
286
    return NULL;
287

            
288
  /* Map the root object to the root path. */
289
4699
  if ((void *) gobj == (void *) spi_global_app_data->root)
290
3842
    return g_strdup (spi_register_root_path);
291

            
292
2778
  ref = object_to_ref (gobj);
293
2778
  if (!ref)
294
    {
295
1353
      register_object (reg, gobj);
296
1353
      ref = object_to_ref (gobj);
297
    }
298

            
299
2778
  if (!ref)
300
    return NULL;
301
  else
302
2778
    return ref_to_path (ref);
303
}
304

            
305
guint
306
spi_register_object_to_ref (GObject *gobj)
307
{
308
  return object_to_ref (gobj);
309
}
310

            
311
/*
312
 * Gets the path that indicates the accessible desktop object.
313
 * This object is logically located on the registry daemon and not
314
 * within any particular application.
315
 */
316
gchar *
317
spi_register_root_object_path ()
318
{
319
  return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_ROOT);
320
}
321

            
322
/*END------------------------------------------------------------------------*/