/*
* AT-SPI - Assistive Technology Service Provider Interface
* (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
*
* Copyright 2007 IBM Corp.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* collection.c: implements the Collection interface */
#include <string.h>
#include <atk/atk.h>
#include <droute/droute.h>
#include "bridge.h"
#include "bitarray.h"
#include "spi-dbus.h"
#include "accessible-stateset.h"
#include "accessible-register.h"
#include "object.h"
#include "introspection.h"
typedef struct _MatchRulePrivate MatchRulePrivate;
struct _MatchRulePrivate
{
gint *states;
AtspiCollectionMatchType statematchtype;
AtkAttributeSet *attributes;
AtspiCollectionMatchType attributematchtype;
gint *roles;
AtspiCollectionMatchType rolematchtype;
gchar **ifaces;
AtspiCollectionMatchType interfacematchtype;
gboolean invert;
};
static gboolean
child_interface_p (AtkObject * child, gchar * repo_id)
{
if (!strcasecmp (repo_id, "action"))
return ATK_IS_ACTION (child);
if (!strcasecmp (repo_id, "component"))
return ATK_IS_COMPONENT (child);
if (!strcasecmp (repo_id, "editabletext"))
return ATK_IS_EDITABLE_TEXT (child);
if (!strcasecmp (repo_id, "text"))
return ATK_IS_TEXT (child);
if (!strcasecmp (repo_id, "hypertext"))
return ATK_IS_HYPERTEXT (child);
if (!strcasecmp (repo_id, "image"))
return ATK_IS_IMAGE (child);
if (!strcasecmp (repo_id, "selection"))
return ATK_IS_SELECTION (child);
if (!strcasecmp (repo_id, "table"))
return ATK_IS_TABLE (child);
if (!strcasecmp (repo_id, "value"))
return ATK_IS_VALUE (child);
if (!strcasecmp (repo_id, "streamablecontent"))
return ATK_IS_STREAMABLE_CONTENT (child);
if (!strcasecmp (repo_id, "document"))
return ATK_IS_DOCUMENT (child);
return FALSE;
}
#define child_collection_p(ch) (TRUE)
static gboolean
match_states_all_p (AtkObject * child, gint * set)
{
AtkStateSet *chs;
gint i;
gboolean ret = TRUE;
if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
return TRUE;
chs = atk_object_ref_state_set (child);
// TODO: use atk-state_set_contains_states; would be more efficient
for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
{
if (!atk_state_set_contains_state (chs, set[i]))
{
ret = FALSE;
break;
}
}
g_object_unref (chs);
return ret;
}
static gboolean
match_states_any_p (AtkObject * child, gint * set)
{
AtkStateSet *chs;
gint i;
gboolean ret = FALSE;
if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
return TRUE;
chs = atk_object_ref_state_set (child);
for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
{
if (atk_state_set_contains_state (chs, set[i]))
{
ret = TRUE;
break;
}
}
g_object_unref (chs);
return ret;
}
static gboolean
match_states_none_p (AtkObject * child, gint * set)
{
AtkStateSet *chs;
gint i;
gboolean ret = TRUE;
if (set == NULL || set[0] == BITARRAY_SEQ_TERM)
return TRUE;
chs = atk_object_ref_state_set (child);
for (i = 0; set[i] != BITARRAY_SEQ_TERM; i++)
{
if (atk_state_set_contains_state (chs, set[i]))
{
ret = FALSE;
break;
}
}
g_object_unref (chs);
return ret;
}
// TODO: need to convert at-spi roles/states to atk roles/states */
static gboolean
match_states_lookup (AtkObject * child, MatchRulePrivate * mrp)
{
switch (mrp->statematchtype)
{
case ATSPI_Collection_MATCH_ALL:
if (match_states_all_p (child, mrp->states))
return TRUE;
break;
case ATSPI_Collection_MATCH_ANY:
if (match_states_any_p (child, mrp->states))
return TRUE;
break;
case ATSPI_Collection_MATCH_NONE:
if (match_states_none_p (child, mrp->states))
return TRUE;
break;
default:
break;
}
return FALSE;
}
// TODO: Map at-spi -> atk roles at mrp creation instead of doing this;
// would be more efficient
#define spi_get_role(obj) spi_accessible_role_from_atk_role(atk_object_get_role(obj))
static gboolean
match_roles_all_p (AtkObject * child, gint * roles)
{
if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
return TRUE;
else if (roles[1] != BITARRAY_SEQ_TERM)
return FALSE;
return (atk_object_get_role (child) == roles[0]);
}
static gboolean
match_roles_any_p (AtkObject * child, gint * roles)
{
AtspiRole role;
int i;
if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
return TRUE;
role = spi_accessible_role_from_atk_role (atk_object_get_role (child));
for (i = 0; roles[i] != BITARRAY_SEQ_TERM; i++)
if (role == roles[i])
return TRUE;
return FALSE;
}
static gboolean
match_roles_none_p (AtkObject * child, gint * roles)
{
AtkRole role;
int i;
if (roles == NULL || roles[0] == BITARRAY_SEQ_TERM)
return TRUE;
role = atk_object_get_role (child);
for (i = 0; roles[i] != BITARRAY_SEQ_TERM; i++)
if (role == roles[i])
return FALSE;
return TRUE;
}
static gboolean
match_roles_lookup (AtkObject * child, MatchRulePrivate * mrp)
{
switch (mrp->rolematchtype)
{
case ATSPI_Collection_MATCH_ALL:
if (match_roles_all_p (child, mrp->roles))
return TRUE;
break;
case ATSPI_Collection_MATCH_ANY:
if (match_roles_any_p (child, mrp->roles))
return TRUE;
break;
case ATSPI_Collection_MATCH_NONE:
if (match_roles_none_p (child, mrp->roles))
return TRUE;
break;
default:
break;
}
return FALSE;
}
static gboolean
match_interfaces_all_p (AtkObject * obj, gchar ** ifaces)
{
gint i;
if (ifaces == NULL)
return TRUE;
for (i = 0; ifaces[i]; i++)
if (!child_interface_p (obj, ifaces[i]))
{
return FALSE;
}
return TRUE;
}
static gboolean
match_interfaces_any_p (AtkObject * obj, gchar ** ifaces)
{
gint i;
if (ifaces == NULL)
return TRUE;
for (i = 0; ifaces[i]; i++)
if (child_interface_p (obj, ifaces[i]))
{
return TRUE;
}
return FALSE;
}
static gboolean
match_interfaces_none_p (AtkObject * obj, gchar ** ifaces)
{
gint i;
for (i = 0; ifaces[i]; i++)
if (child_interface_p (obj, ifaces[i]))
return FALSE;
return TRUE;
}
static gboolean
match_interfaces_lookup (AtkObject * child, MatchRulePrivate * mrp)
{
switch (mrp->interfacematchtype)
{
case ATSPI_Collection_MATCH_ALL:
if (match_interfaces_all_p (child, mrp->ifaces))
return TRUE;
break;
case ATSPI_Collection_MATCH_ANY:
if (match_interfaces_any_p (child, mrp->ifaces))
return TRUE;
break;
case ATSPI_Collection_MATCH_NONE:
if (match_interfaces_none_p (child, mrp->ifaces))
return TRUE;
break;
default:
break;
}
return FALSE;
}
#define split_attributes(attributes) (g_strsplit (attributes, ";", 0))
static gboolean
match_attributes_all_p (AtkObject * child, AtkAttributeSet * attributes)
{
int i, k;
AtkAttributeSet *oa;
gint length, oa_length;
gboolean flag = FALSE;
if (attributes == NULL || g_slist_length (attributes) == 0)
return TRUE;
oa = atk_object_get_attributes (child);
length = g_slist_length (attributes);
oa_length = g_slist_length (oa);
for (i = 0; i < length; i++)
{
AtkAttribute *attr = g_slist_nth_data (attributes, i);
for (k = 0; k < oa_length; k++)
{
AtkAttribute *oa_attr = g_slist_nth_data (attributes, i);
if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
!g_ascii_strcasecmp (oa_attr->value, attr->value))
{
flag = TRUE;
break;
}
else
flag = FALSE;
}
if (!flag)
{
atk_attribute_set_free (oa);
return FALSE;
}
}
atk_attribute_set_free (oa);
return TRUE;
}
static gboolean
match_attributes_any_p (AtkObject * child, AtkAttributeSet * attributes)
{
int i, k;
AtkAttributeSet *oa;
gint length, oa_length;
length = g_slist_length (attributes);
if (length == 0)
return TRUE;
oa = atk_object_get_attributes (child);
oa_length = g_slist_length (oa);
for (i = 0; i < length; i++)
{
AtkAttribute *attr = g_slist_nth_data (attributes, i);
for (k = 0; k < oa_length; k++)
{
AtkAttribute *oa_attr = g_slist_nth_data (oa, k);
if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
!g_ascii_strcasecmp (oa_attr->value, attr->value))
{
atk_attribute_set_free (oa);
return TRUE;
}
}
}
atk_attribute_set_free (oa);
return FALSE;
}
static gboolean
match_attributes_none_p (AtkObject * child, AtkAttributeSet * attributes)
{
int i, k;
AtkAttributeSet *oa;
gint length, oa_length;
length = g_slist_length (attributes);
if (length == 0)
return TRUE;
oa = atk_object_get_attributes (child);
oa_length = g_slist_length (oa);
for (i = 0; i < length; i++)
{
AtkAttribute *attr = g_slist_nth_data (attributes, i);
for (k = 0; k < oa_length; k++)
{
AtkAttribute *oa_attr = g_slist_nth_data (attributes, i);
if (!g_ascii_strcasecmp (oa_attr->name, attr->name) &&
!g_ascii_strcasecmp (oa_attr->value, attr->value))
{
atk_attribute_set_free (oa);
return FALSE;
}
}
}
atk_attribute_set_free (oa);
return TRUE;
}
static gboolean
match_attributes_lookup (AtkObject * child, MatchRulePrivate * mrp)
{
switch (mrp->attributematchtype)
{
case ATSPI_Collection_MATCH_ALL:
if (match_attributes_all_p (child, mrp->attributes))
return TRUE;
break;
case ATSPI_Collection_MATCH_ANY:
if (match_attributes_any_p (child, mrp->attributes))
return TRUE;
break;
case ATSPI_Collection_MATCH_NONE:
if (match_attributes_none_p (child, mrp->attributes))
return TRUE;
break;
default:
break;
}
return FALSE;
}
static gboolean
traverse_p (AtkObject * child, const gboolean traverse)
{
if (traverse)
return TRUE;
else
return !child_collection_p (child);
}
static int
sort_order_canonical (MatchRulePrivate * mrp, GList * ls,
gint kount, gint max,
AtkObject * obj, glong index, gboolean flag,
AtkObject * pobj, gboolean recurse, gboolean traverse)
{
gint i = index;
glong acount = atk_object_get_n_accessible_children (obj);
gboolean prev = pobj ? TRUE : FALSE;
for (; i < acount && (max == 0 || kount < max); i++)
{
AtkObject *child = atk_object_ref_accessible_child (obj, i);
if (!child)
continue;
if (prev && child == pobj)
{
g_object_unref (child);
return kount;
}
if (flag && match_interfaces_lookup (child, mrp)
&& match_states_lookup (child, mrp)
&& match_roles_lookup (child, mrp)
&& match_attributes_lookup (child, mrp))
{
ls = g_list_append (ls, child);
kount++;
}
if (!flag)
flag = TRUE;
if (recurse && traverse_p (child, traverse))
kount = sort_order_canonical (mrp, ls, kount,
max, child, 0, TRUE,
pobj, recurse, traverse);
g_object_unref (child);
}
return kount;
}
static int
sort_order_rev_canonical (MatchRulePrivate * mrp, GList * ls,
gint kount, gint max,
AtkObject * obj, gboolean flag, AtkObject * pobj)
{
AtkObject *nextobj;
AtkObject *parent;
glong indexinparent;
/* This breaks us out of the recursion. */
if (!obj || obj == pobj)
{
return kount;
}
/* Add to the list if it matches */
if (flag && match_interfaces_lookup (obj, mrp)
&& match_states_lookup (obj, mrp)
&& match_roles_lookup (obj, mrp) && match_attributes_lookup (obj, mrp)
&& (max == 0 || kount < max))
{
ls = g_list_append (ls, obj);
kount++;
}
if (!flag)
flag = TRUE;
/* Get the current nodes index in it's parent and the parent object. */
indexinparent = atk_object_get_index_in_parent (obj);
parent = atk_object_get_parent (obj);
if (indexinparent > 0 && (max == 0 || kount < max))
{
/* there are still some siblings to visit so get the previous sibling
and get it's last descendant.
First, get the previous sibling */
nextobj = atk_object_ref_accessible_child (parent, indexinparent - 1);
/* Now, drill down the right side to the last descendant */
while (nextobj && atk_object_get_n_accessible_children (nextobj) > 0)
{
AtkObject *follow;
follow = atk_object_ref_accessible_child (nextobj,
atk_object_get_n_accessible_children
(nextobj) - 1);
g_object_unref (nextobj);
nextobj = follow;
}
/* recurse with the last descendant */
kount = sort_order_rev_canonical (mrp, ls, kount, max,
nextobj, TRUE, pobj);
if (nextobj)
g_object_unref (nextobj);
}
else if (max == 0 || kount < max)
{
/* no more siblings so next node must be the parent */
kount = sort_order_rev_canonical (mrp, ls, kount, max,
parent, TRUE, pobj);
}
return kount;
}
static int
query_exec (MatchRulePrivate * mrp, AtspiCollectionSortOrder sortby,
GList * ls, gint kount, gint max,
AtkObject * obj, glong index,
gboolean flag,
AtkObject * pobj, gboolean recurse, gboolean traverse)
{
switch (sortby)
{
case ATSPI_Collection_SORT_ORDER_CANONICAL:
kount = sort_order_canonical (mrp, ls, 0, max, obj, index, flag,
pobj, recurse, traverse);
break;
case ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL:
kount = sort_order_canonical (mrp, ls, 0, max, obj, index, flag,
pobj, recurse, traverse);
break;
default:
kount = 0;
g_warning ("Sort method not implemented yet");
break;
}
return kount;
}
static gboolean
bitarray_to_seq (dbus_uint32_t *array, int array_count, int **ret)
{
int out_size = 4;
int out_count = 0;
int i, j;
int *out = (int *) g_malloc (out_size * sizeof (int));
if (!out)
return FALSE;
for (i = 0; i < array_count; i++)
{
for (j = 0; j < 32; j++)
{
if (array[i] & (1 << j))
{
if (out_count == out_size - 2)
{
out_size <<= 1;
out = (int *) g_realloc (out, out_size * sizeof (int));
if (!out)
return FALSE;
}
out[out_count++] = i * 32 + j;
}
}
}
out[out_count] = BITARRAY_SEQ_TERM;
*ret = out;
return TRUE;
}
static dbus_bool_t
read_mr (DBusMessageIter * iter, MatchRulePrivate * mrp)
{
DBusMessageIter iter_struct, iter_array, iter_dict, iter_dict_entry;
dbus_uint32_t *array;
dbus_int32_t matchType;
int array_count;
AtkAttribute *attr;
int i;
dbus_message_iter_recurse (iter, &iter_struct);
/* states */
dbus_message_iter_recurse (&iter_struct, &iter_array);
dbus_message_iter_get_fixed_array (&iter_array, &array, &array_count);
bitarray_to_seq (array, array_count, &mrp->states);
for (i = 0; mrp->states[i] != BITARRAY_SEQ_TERM; i++)
{
mrp->states[i] = spi_atk_state_from_spi_state (mrp->states[i]);
}
dbus_message_iter_next (&iter_struct);
dbus_message_iter_get_basic (&iter_struct, &matchType);
dbus_message_iter_next (&iter_struct);
mrp->statematchtype = matchType;;
/* attributes */
mrp->attributes = NULL;
dbus_message_iter_recurse (&iter_struct, &iter_dict);
while (dbus_message_iter_get_arg_type (&iter_dict) != DBUS_TYPE_INVALID)
{
const char *key, *val;
const char *p, *q;
dbus_message_iter_recurse (&iter_dict, &iter_dict_entry);
dbus_message_iter_get_basic (&iter_dict_entry, &key);
dbus_message_iter_next (&iter_dict_entry);
dbus_message_iter_get_basic (&iter_dict_entry, &val);
p = q = val;
for (;;)
{
if (*q == '\0' || (*q == ':' && (q == val || q[-1] != '\\')))
{
char *tmp;
attr = g_new (AtkAttribute, 1);
attr->name = g_strdup (key);
attr->value = g_strdup (p);
attr->value[q - p] = '\0';
tmp = attr->value;
while (*tmp != '\0')
{
if (*tmp == '\\')
memmove (tmp, tmp + 1, strlen (tmp));
else
tmp++;
}
mrp->attributes = g_slist_prepend (mrp->attributes, attr);
if (*q == '\0')
break;
else
p = ++q;
}
else
q++;
}
dbus_message_iter_next (&iter_dict);
}
dbus_message_iter_next (&iter_struct);
dbus_message_iter_get_basic (&iter_struct, &matchType);
mrp->attributematchtype = matchType;;
dbus_message_iter_next (&iter_struct);
/* Get roles and role match */
dbus_message_iter_recurse (&iter_struct, &iter_array);
dbus_message_iter_get_fixed_array (&iter_array, &array, &array_count);
bitarray_to_seq (array, array_count, &mrp->roles);
dbus_message_iter_next (&iter_struct);
dbus_message_iter_get_basic (&iter_struct, &matchType);
mrp->rolematchtype = matchType;;
dbus_message_iter_next (&iter_struct);
/* Get interfaces and interface match */
dbus_message_iter_recurse (&iter_struct, &iter_array);
mrp->ifaces = g_new0 (gchar *, 16);
i = 0;
while (i < 15 && dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
char *iface;
dbus_message_iter_get_basic (&iter_array, &iface);
mrp->ifaces [i] = g_strdup (iface);
i++;
dbus_message_iter_next (&iter_array);
}
dbus_message_iter_next (&iter_struct);
dbus_message_iter_get_basic (&iter_struct, &matchType);
mrp->interfacematchtype = matchType;;
dbus_message_iter_next (&iter_struct);
/* get invert */
dbus_message_iter_get_basic (&iter_struct, &mrp->invert);
dbus_message_iter_next (iter);
return TRUE;
}
static DBusMessage *
return_and_free_list (DBusMessage * message, GList * ls)
{
DBusMessage *reply;
DBusMessageIter iter, iter_array;
GList *item;
reply = dbus_message_new_method_return (message);
if (!reply)
return NULL;
dbus_message_iter_init_append (reply, &iter);
if (!dbus_message_iter_open_container
(&iter, DBUS_TYPE_ARRAY, "(so)", &iter_array))
goto oom;
for (item = ls; item; item = g_list_next (item))
{
spi_object_append_reference (&iter_array, ATK_OBJECT (item->data));
}
if (!dbus_message_iter_close_container (&iter, &iter_array))
goto oom;
g_list_free (ls);
return reply;
oom:
// TODO: Handle out of memory
g_list_free (ls);
return reply;
}
static void
free_mrp_data (MatchRulePrivate * mrp)
{
g_free (mrp->states);
atk_attribute_set_free (mrp->attributes);
g_free (mrp->roles);
g_strfreev (mrp->ifaces);
}
static DBusMessage *
GetMatchesFrom (DBusMessage * message,
AtkObject * current_object,
MatchRulePrivate * mrp,
const AtspiCollectionSortOrder sortby,
const dbus_bool_t isrestrict,
dbus_int32_t count, const dbus_bool_t traverse)
{
GList *ls = NULL;
AtkObject *parent;
glong index = atk_object_get_index_in_parent (current_object);
ls = g_list_append (ls, current_object);
if (!isrestrict)
{
parent = atk_object_get_parent (current_object);
query_exec (mrp, sortby, ls, 0, count, parent, index,
FALSE, NULL, TRUE, traverse);
}
else
query_exec (mrp, sortby, ls, 0, count,
current_object, 0, FALSE, NULL, TRUE, traverse);
ls = g_list_remove (ls, ls->data);
if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
ls = g_list_reverse (ls);
free_mrp_data (mrp);
return return_and_free_list (message, ls);
}
/*
inorder traversal from a given object in the hierarchy
*/
static int
inorder (AtkObject * collection, MatchRulePrivate * mrp,
GList * ls, gint kount, gint max,
AtkObject * obj,
gboolean flag, AtkObject * pobj, dbus_bool_t traverse)
{
int i = 0;
/* First, look through the children recursively. */
kount = sort_order_canonical (mrp, ls, kount, max, obj, 0, TRUE,
NULL, TRUE, TRUE);
/* Next, we look through the right subtree */
while ((max == 0 || kount < max) && obj && obj != collection)
{
AtkObject *parent = atk_object_get_parent (obj);
i = atk_object_get_index_in_parent (obj);
kount = sort_order_canonical (mrp, ls, kount, max, parent,
i + 1, TRUE, FALSE, TRUE, TRUE);
obj = parent;
}
if (max == 0 || kount < max)
{
kount = sort_order_canonical (mrp, ls, kount, max,
obj, i + 1, TRUE, FALSE, TRUE, TRUE);
}
return kount;
}
/*
GetMatchesInOrder: get matches from a given object in an inorder traversal.
*/
static DBusMessage *
GetMatchesInOrder (DBusMessage * message,
AtkObject * current_object,
MatchRulePrivate * mrp,
const AtspiCollectionSortOrder sortby,
const dbus_bool_t recurse,
dbus_int32_t count, const dbus_bool_t traverse)
{
GList *ls = NULL;
AtkObject *obj;
ls = g_list_append (ls, current_object);
obj = ATK_OBJECT(spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
inorder (obj, mrp, ls, 0, count,
current_object, TRUE, NULL, traverse);
ls = g_list_remove (ls, ls->data);
if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
ls = g_list_reverse (ls);
free_mrp_data (mrp);
return return_and_free_list (message, ls);
}
/*
GetMatchesInBackOrder: get matches from a given object in a
reverse order traversal.
*/
static DBusMessage *
GetMatchesInBackOrder (DBusMessage * message,
AtkObject * current_object,
MatchRulePrivate * mrp,
const AtspiCollectionSortOrder sortby,
dbus_int32_t count)
{
GList *ls = NULL;
AtkObject *collection;
ls = g_list_append (ls, current_object);
collection = ATK_OBJECT(spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
sort_order_rev_canonical (mrp, ls, 0, count, current_object,
FALSE, collection);
ls = g_list_remove (ls, ls->data);
if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
ls = g_list_reverse (ls);
free_mrp_data (mrp);
return return_and_free_list (message, ls);
}
static DBusMessage *
GetMatchesTo (DBusMessage * message,
AtkObject * current_object,
MatchRulePrivate * mrp,
const AtspiCollectionSortOrder sortby,
const dbus_bool_t recurse,
const dbus_bool_t isrestrict,
dbus_int32_t count, const dbus_bool_t traverse)
{
GList *ls = NULL;
AtkObject *obj;
ls = g_list_append (ls, current_object);
if (recurse)
{
obj = ATK_OBJECT (atk_object_get_parent (current_object));
query_exec (mrp, sortby, ls, 0, count,
obj, 0, TRUE, current_object, TRUE, traverse);
}
else
{
obj = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
query_exec (mrp, sortby, ls, 0, count,
obj, 0, TRUE, current_object, TRUE, traverse);
}
ls = g_list_remove (ls, ls->data);
if (sortby != ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
ls = g_list_reverse (ls);
free_mrp_data (mrp);
return return_and_free_list (message, ls);
}
static DBusMessage *
impl_GetMatchesFrom (DBusConnection * bus, DBusMessage * message,
void *user_data)
{
char *current_object_path = NULL;
AtkObject *current_object;
DBusMessageIter iter;
MatchRulePrivate rule;
dbus_uint32_t sortby;
dbus_uint32_t tree;
dbus_int32_t count;
dbus_bool_t traverse;
const char *signature;
signature = dbus_message_get_signature (message);
if (strcmp (signature, "o(aiia{ss}iaiiasib)uuib") != 0)
{
return droute_invalid_arguments_error (message);
}
dbus_message_iter_init (message, &iter);
dbus_message_iter_get_basic (&iter, ¤t_object_path);
current_object = ATK_OBJECT (spi_register_path_to_object (spi_global_register, current_object_path));
if (!current_object)
{
// TODO: object-not-found error
return spi_dbus_general_error (message);
}
dbus_message_iter_next (&iter);
if (!read_mr (&iter, &rule))
{
return spi_dbus_general_error (message);
}
dbus_message_iter_get_basic (&iter, &sortby);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &tree);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &count);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &traverse);
dbus_message_iter_next (&iter);
switch (tree)
{
case ATSPI_Collection_TREE_RESTRICT_CHILDREN:
return GetMatchesFrom (message, current_object,
&rule, sortby, TRUE, count, traverse);
break;
case ATSPI_Collection_TREE_RESTRICT_SIBLING:
return GetMatchesFrom (message, current_object,
&rule, sortby, FALSE, count, traverse);
break;
case ATSPI_Collection_TREE_INORDER:
return GetMatchesInOrder (message, current_object,
&rule, sortby, TRUE, count, traverse);
break;
default:
return NULL;
}
}
static DBusMessage *
impl_GetMatchesTo (DBusConnection * bus, DBusMessage * message,
void *user_data)
{
char *current_object_path = NULL;
AtkObject *current_object;
DBusMessageIter iter;
MatchRulePrivate rule;
dbus_uint32_t sortby;
dbus_uint32_t tree;
dbus_bool_t recurse;
dbus_int32_t count;
dbus_bool_t traverse;
const char *signature;
signature = dbus_message_get_signature (message);
if (strcmp (signature, "o(aiia{ss}iaiiasib)uubib") != 0)
{
return droute_invalid_arguments_error (message);
}
dbus_message_iter_init (message, &iter);
dbus_message_iter_get_basic (&iter, ¤t_object_path);
current_object = ATK_OBJECT (spi_register_path_to_object (spi_global_register, current_object_path));
if (!current_object)
{
// TODO: object-not-found error
return spi_dbus_general_error (message);
}
dbus_message_iter_next (&iter);
if (!read_mr (&iter, &rule))
{
return spi_dbus_general_error (message);
}
dbus_message_iter_get_basic (&iter, &sortby);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &tree);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &recurse);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &count);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &traverse);
dbus_message_iter_next (&iter);
switch (tree)
{
case ATSPI_Collection_TREE_RESTRICT_CHILDREN:
return GetMatchesTo (message, current_object,
&rule, sortby, recurse, TRUE, count, traverse);
break;
case ATSPI_Collection_TREE_RESTRICT_SIBLING:
return GetMatchesTo (message, current_object,
&rule, sortby, recurse, FALSE, count, traverse);
break;
case ATSPI_Collection_TREE_INORDER:
return GetMatchesInBackOrder (message, current_object,
&rule, sortby, count);
break;
default:
return NULL;
}
}
static void
append_accessible_properties (DBusMessageIter *iter, AtkObject *obj,
GArray *properties)
{
DBusMessageIter iter_struct, iter_dict, iter_dict_entry;
AtkStateSet *set;
gint i;
gint count;
dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT, NULL, &iter_struct);
spi_object_append_reference (&iter_struct, obj);
dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "{sv}", &iter_dict);
if (properties && properties->len)
{
gint i;
for (i = 0; i < properties->len; i++)
{
gchar *prop = g_array_index (properties, char *, i);
DRoutePropertyFunction func;
GType type;
func = _atk_bridge_find_property_func (prop, &type);
if (func && G_TYPE_CHECK_INSTANCE_TYPE (obj, type))
{
dbus_message_iter_open_container (&iter_dict, DBUS_TYPE_DICT_ENTRY,
NULL, &iter_dict_entry);
dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &prop);
func (&iter_dict_entry, obj);
dbus_message_iter_close_container (&iter_dict, &iter_dict_entry);
}
}
}
else
{
GHashTableIter hi;
gpointer key, value;
g_hash_table_iter_init (&hi, spi_global_app_data->property_hash);
while (g_hash_table_iter_next (&hi, &key, &value))
{
const DRouteProperty *prop = value;
GType type = _atk_bridge_type_from_iface (key);
if (!G_TYPE_CHECK_INSTANCE_TYPE (obj, type))
continue;
for (;prop->name; prop++)
{
const char *p = key + strlen (key);
gchar *property_name;
while (p[-1] != '.')
p--;
if (!strcmp (p, "Accessible"))
property_name = g_strdup (prop->name);
else
property_name = g_strconcat (p, ".", prop->name, NULL);
dbus_message_iter_open_container (&iter_dict, DBUS_TYPE_DICT_ENTRY,
NULL, &iter_dict_entry);
dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &property_name);
g_free (property_name);
prop->get (&iter_dict_entry, obj);
dbus_message_iter_close_container (&iter_dict, &iter_dict_entry);
}
}
}
dbus_message_iter_close_container (&iter_struct, &iter_dict);
dbus_message_iter_close_container (iter, &iter_struct);
set = atk_object_ref_state_set (obj);
if (set)
{
gboolean md = atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS);
g_object_unref (set);
if (md)
return;
}
count = atk_object_get_n_accessible_children (obj);
for (i = 0; i < count; i++)
{
AtkObject *child = atk_object_ref_accessible_child (obj, i);
if (child)
{
append_accessible_properties (iter, child, properties);
g_object_unref (child);
}
}
}
static void
skip (const char **p)
{
const char *sig = *p;
gint nest = (*sig != 'a');
sig++;
while (*sig)
{
if (*sig == '(' || *sig == '{')
nest++;
else if (*sig == ')' || *sig == '}')
nest--;
sig++;
}
*p = sig;
}
static gboolean
types_match (DBusMessageIter *iter, char c)
{
char t = dbus_message_iter_get_arg_type (iter);
if (t == 'r' && c == '(')
return TRUE;
else
return (t == c);
}
static void
walk (DBusMessageIter *iter, const char *sig, gboolean array)
{
while (*sig && *sig != ')' && *sig != '}')
{
if (array && dbus_message_iter_get_arg_type (iter) == DBUS_TYPE_INVALID)
break;
if (!types_match (iter, *sig))
{
g_error ("Expected %s, got %c", sig, dbus_message_iter_get_arg_type (iter));
}
switch (*sig)
{
case 's':
{
const char *str;
DBusError error;
dbus_error_init (&error);
dbus_message_iter_get_basic (iter, &str);
g_print ("%s\n", str);
if (!dbus_validate_utf8 (str, &error))
g_error ("Bad UTF-8 string");
}
break;
case 'a':
{
DBusMessageIter iter_array;
dbus_message_iter_recurse (iter, &iter_array);
walk (&iter_array, sig + 1, TRUE);
skip (&sig);
}
break;
case DBUS_TYPE_STRUCT:
case DBUS_TYPE_DICT_ENTRY:
{
DBusMessageIter iter_struct;
dbus_message_iter_recurse (iter, &iter_struct);
walk (&iter_struct, sig + 1, FALSE);
skip (&sig);
}
}
dbus_message_iter_next (iter);
if (!array)
sig++;
}
if (dbus_message_iter_get_arg_type (iter) != DBUS_TYPE_INVALID)
g_error ("Unexpected data '%c'", dbus_message_iter_get_arg_type (iter));
}
static void
walkm (DBusMessage *message)
{
DBusMessageIter iter;
const char *sig = dbus_message_get_signature (message);
g_print ("sig: %s\n", sig);
dbus_message_iter_init (message, &iter);
walk (&iter, sig, FALSE);
}
static DBusMessage *
impl_GetTree (DBusConnection * bus,
DBusMessage * message, void *user_data)
{
AtkObject *object = (AtkObject *) user_data;
DBusMessage *reply;
DBusMessageIter iter, iter_array;
MatchRulePrivate rule;
GArray *properties;
g_return_val_if_fail (ATK_IS_OBJECT (user_data),
droute_not_yet_handled_error (message));
if (strcmp (dbus_message_get_signature (message), "(aiia{ss}iaiiasib)as") != 0)
return droute_invalid_arguments_error (message);
properties = g_array_new (TRUE, TRUE, sizeof (char *));
dbus_message_iter_init (message, &iter);
if (!read_mr (&iter, &rule))
{
return spi_dbus_general_error (message);
}
dbus_message_iter_recurse (&iter, &iter_array);
while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
{
const char *prop;
dbus_message_iter_get_basic (&iter_array, &prop);
g_array_append_val (properties, prop);
dbus_message_iter_next (&iter_array);
}
reply = dbus_message_new_method_return (message);
if (reply)
{
dbus_message_iter_init_append (reply, &iter);
dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "((so)a{sv})",
&iter_array);
append_accessible_properties (&iter_array, object, properties);
dbus_message_iter_close_container (&iter, &iter_array);
}
//walkm (reply);
return reply;
}
static DBusMessage *
impl_GetMatches (DBusConnection * bus, DBusMessage * message, void *user_data)
{
AtkObject *obj = ATK_OBJECT (spi_register_path_to_object (spi_global_register, dbus_message_get_path (message)));
DBusMessageIter iter;
MatchRulePrivate rule;
dbus_uint32_t sortby;
dbus_int32_t count;
dbus_bool_t traverse;
GList *ls = NULL;
const char *signature;
signature = dbus_message_get_signature (message);
if (strcmp (signature, "(aiia{ss}iaiiasib)uib") != 0)
{
return droute_invalid_arguments_error (message);
}
dbus_message_iter_init (message, &iter);
if (!read_mr (&iter, &rule))
{
return spi_dbus_general_error (message);
}
dbus_message_iter_get_basic (&iter, &sortby);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &count);
dbus_message_iter_next (&iter);
dbus_message_iter_get_basic (&iter, &traverse);
dbus_message_iter_next (&iter);
ls = g_list_prepend (ls, obj);
count = query_exec (&rule, sortby, ls, 0, count,
obj, 0, TRUE, NULL, TRUE, traverse);
ls = g_list_remove (ls, ls->data);
if (sortby == ATSPI_Collection_SORT_ORDER_REVERSE_CANONICAL)
ls = g_list_reverse (ls);
free_mrp_data (&rule);
return return_and_free_list (message, ls);
}
static DRouteMethod methods[] = {
{impl_GetMatchesFrom, "GetMatchesFrom"},
{impl_GetMatchesTo, "GetMatchesTo"},
{impl_GetTree, "GetTree"},
{impl_GetMatches, "GetMatches"},
{NULL, NULL}
};
void
spi_initialize_collection (DRoutePath * path)
{
spi_atk_add_interface (path,
ATSPI_DBUS_INTERFACE_COLLECTION, spi_org_a11y_atspi_Collection, methods, NULL);
};