Fix crash, when dbus call in GetNeighbor fails
[platform/upstream/at-spi2-core.git] / atspi / atspi-accessible.c
index a579254..7e889e9 100644 (file)
@@ -340,13 +340,44 @@ atspi_accessible_get_navigable_at_point (AtspiAccessible *root,
   DBusMessage *reply;
   AtspiAccessible *return_value = NULL;
   unsigned char recurse = 0;
+  DBusMessageIter iter;
+  AtspiAccessible *deputy = NULL;
 
-  g_return_val_if_fail (root != NULL, FALSE);
+  g_return_val_if_fail (root != NULL, NULL);
   do {
     reply = _atspi_dbus_call_partial (root, atspi_interface_accessible, "GetNavigableAtPoint", error, "iiu", d_x, d_y, d_ctype);
+    // call failed, error is set, so we bail out
+    if (!reply) {
+      if (deputy) g_object_unref(deputy);
+      if (return_value) g_object_unref(return_value);
+      return NULL;
+    }
+    _ATSPI_DBUS_CHECK_SIG (reply, "(so)y(so)", NULL, NULL);
+
+    dbus_message_iter_init (reply, &iter);
+    AtspiAccessible *tmp = _atspi_dbus_return_accessible_from_iter (&iter);
+
+    unsigned char value = 0;
+    dbus_message_iter_get_basic (&iter, &value);
+    dbus_message_iter_next (&iter);
+    recurse = (value != 0);
+
+    /* keep deputy if tmp has deputy */
+    if (!deputy)
+      deputy = _atspi_dbus_return_accessible_from_iter (&iter);
+
+    dbus_message_unref(reply);
+
+    if (!tmp) {
+      if (deputy) {
+        /* TODO: need to check deputy works for return value */
+        if (return_value)
+          g_object_unref(return_value);
+        return deputy;
+      }
+      break;
+    }
 
-    AtspiAccessible *tmp = _atspi_dbus_return_accessible_and_recurse_info_from_message (reply, &recurse);
-    if (!tmp) break;
     if (return_value)
       g_object_unref(return_value);
     return_value = root = tmp;
@@ -355,6 +386,321 @@ atspi_accessible_get_navigable_at_point (AtspiAccessible *root,
 }
 
 /**
+ * atspi_accessible_get_reading_material:
+ * @obj: a pointer to the #AtspiAccessible object on which to operate.
+ *
+ * Gets reading material
+ *
+ * Returns: reading material to be used screen-reader side. This is not stable.
+ * You have to handle all alocated memory as below on screen-reader side.
+ *
+ * AtspiAccessibleReadingMaterial *rm
+ * g_object_unref(rm->parent);
+ * g_object_unref(rm->described_by_accessible);
+ * g_hash_table_unref(rm->attributes);
+ * free(rm->name);
+ * free(rm->labeled_by_name);
+ * free(rm->text_interface_name);
+ * free(rm->localized_role_name);
+ * free(rm->description);
+ * free(rm);
+ **/
+AtspiAccessibleReadingMaterial *
+atspi_accessible_get_reading_material (AtspiAccessible *obj, GError **error)
+{
+  AtspiAccessible *parent;
+  AtspiAccessibleReadingMaterial *reading_material = NULL;
+  const char *name;
+  double current_value;
+  gint count;
+  guint64 val;
+  DBusMessage *reply;
+  DBusMessageIter iter;
+  DBusMessageIter iter_array;
+  dbus_uint32_t role;
+  dbus_uint32_t *states;
+  dbus_int32_t index_in_parent;
+  dbus_int32_t child_count;
+  dbus_bool_t is_selected;
+
+  g_return_val_if_fail (obj != NULL, NULL);
+
+  reply = _atspi_dbus_call_partial (obj, atspi_interface_accessible, "GetReadingMaterial", error, "");
+
+  _ATSPI_DBUS_CHECK_SIG (reply, "a{ss}sssuausiddddsibbii(so)auiui(so)", NULL, NULL);
+
+  reading_material = calloc(1, sizeof(AtspiAccessibleReadingMaterial));
+  if (!reading_material)
+  {
+    return reading_material;
+  }
+
+  dbus_message_iter_init (reply, &iter);
+
+  /* get attributes */
+  reading_material->attributes =  _atspi_dbus_hash_from_iter (&iter);
+  dbus_message_iter_next (&iter);
+
+  /* get name */
+  dbus_message_iter_get_basic (&iter, &name);
+  reading_material->name = g_strdup (name);
+  dbus_message_iter_next (&iter);
+
+  /* get name of relation LABELED_BY */
+  dbus_message_iter_get_basic (&iter, &name);
+  reading_material->labeled_by_name = g_strdup (name);
+  dbus_message_iter_next (&iter);
+
+  /* get name of text interface */
+  dbus_message_iter_get_basic (&iter, &name);
+  reading_material->text_interface_name = g_strdup (name);
+  dbus_message_iter_next (&iter);
+
+  /* get role */
+  dbus_message_iter_get_basic (&iter, &role);
+  reading_material->role = role;
+  dbus_message_iter_next (&iter);
+
+  /* get state set */
+  dbus_message_iter_recurse (&iter, &iter_array);
+  dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
+  val = ((guint64)states [1]) << 32;
+  val += states [0];
+  reading_material->states = val;
+  dbus_message_iter_next (&iter);
+
+  /* get localized role name */
+  dbus_message_iter_get_basic (&iter, &name);
+  reading_material->localized_role_name = g_strdup (name);
+  dbus_message_iter_next (&iter);
+
+  /* get child count */
+  dbus_message_iter_get_basic (&iter, &child_count);
+  reading_material->child_count = child_count;
+  dbus_message_iter_next (&iter);
+
+  /* get current value */
+  dbus_message_iter_get_basic (&iter, &current_value);
+  reading_material->value = current_value;
+  dbus_message_iter_next (&iter);
+
+  /* get minimum increment */
+  dbus_message_iter_get_basic (&iter, &current_value);
+  reading_material->increment = current_value;
+  dbus_message_iter_next (&iter);
+
+  /* get maximum value */
+  dbus_message_iter_get_basic (&iter, &current_value);
+  reading_material->upper = current_value;
+  dbus_message_iter_next (&iter);
+
+  /* get minimum value */
+  dbus_message_iter_get_basic (&iter, &current_value);
+  reading_material->lower = current_value;
+  dbus_message_iter_next (&iter);
+
+  /* get description */
+  dbus_message_iter_get_basic (&iter, &name);
+  reading_material->description = g_strdup (name);
+  dbus_message_iter_next (&iter);
+
+  /* get index in parent */
+  dbus_message_iter_get_basic (&iter, &index_in_parent);
+  reading_material->index_in_parent = index_in_parent;
+  dbus_message_iter_next (&iter);
+
+  /* get selected in parent */
+  dbus_message_iter_get_basic (&iter, &is_selected);
+  reading_material->is_selected_in_parent = is_selected;
+  dbus_message_iter_next (&iter);
+
+  /* get has checkbox child */
+  dbus_message_iter_get_basic (&iter, &is_selected);
+  reading_material->has_checkbox_child = is_selected;
+  dbus_message_iter_next (&iter);
+
+  /* get list children count */
+  dbus_message_iter_get_basic (&iter, &child_count);
+  reading_material->list_children_count = child_count;
+  dbus_message_iter_next (&iter);
+
+  /* get first selected child index */
+  dbus_message_iter_get_basic (&iter, &index_in_parent);
+  reading_material->first_selected_child_index = index_in_parent;
+  dbus_message_iter_next (&iter);
+
+  ////////////////
+  /* get parent */
+  parent =  _atspi_dbus_return_accessible_from_iter (&iter);
+  reading_material->parent = parent;
+
+  /* parent states */
+  dbus_message_iter_recurse (&iter, &iter_array);
+  dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
+  val = ((guint64)states [1]) << 32;
+  val += states [0];
+  reading_material->parent_states = val;
+  dbus_message_iter_next (&iter);
+
+  /* get parent child count */
+  dbus_message_iter_get_basic (&iter, &child_count);
+  reading_material->parent_child_count = child_count;
+  dbus_message_iter_next (&iter);
+
+  /* get parent role */
+  dbus_message_iter_get_basic (&iter, &role);
+  reading_material->parent_role = role;
+  dbus_message_iter_next (&iter);
+
+  /* get parent selected child count */
+  dbus_message_iter_get_basic (&iter, &child_count);
+  reading_material->parent_selected_child_count = child_count;
+  dbus_message_iter_next (&iter);
+
+  ////////////////////////////////////////
+  /* get relation object - DESCRIBED_BY */
+  parent =  _atspi_dbus_return_accessible_from_iter (&iter);
+  reading_material->described_by_accessible = parent;
+
+  return reading_material;
+}
+
+static unsigned char are_objects_on_the_same_bus(AtspiAccessible *obj1, AtspiAccessible *obj2)
+{
+  const char *bus_name_1 = obj1->parent.app->bus_name;
+  const char *bus_name_2 = obj2->parent.app->bus_name;
+  return strcmp(bus_name_1, bus_name_2) == 0;
+}
+
+static unsigned char object_is_valid(AtspiAccessible *obj)
+{
+  if (!obj)
+    return 0;
+  AtspiStateSet *ss = atspi_accessible_get_state_set(obj);
+  if (!ss)
+    return 0;
+  unsigned char valid = atspi_state_set_contains(ss, ATSPI_STATE_DEFUNCT) == 0;
+  g_object_unref(ss);
+  return valid;
+}
+
+typedef enum {
+  NEIGHBOR_SEARCH_MODE_NORMAL = 0,
+  NEIGHBOR_SEARCH_MODE_RECURSE_FROM_ROOT = 1,
+  NEIGHBOR_SEARCH_MODE_CONTINUE_AFTER_FAILED_RECURSING = 2,
+  NEIGHBOR_SEARCH_MODE_RECURSE_TO_OUTSIDE = 3,
+} GetNeighborSearchMode;
+/**
+ * atspi_accessible_get_neighbor:
+ * @root: a pointer to a #AtspiAccessible, which represents current root of subtree to search
+ * @start: a pointer to the #AtspiAccessible to start search from (can be null, which means start from root)
+ * @direction: direction, in which search (forward or backward)
+ *
+ * Calculates next (or previous) accessible element in logical order or null if none found.
+ *
+ * Returns: (nullable) (transfer full): a pointer to an
+ *          #AtspiAccessible element, which is next (previous) in logical order or null if none found.
+ **/
+AtspiAccessible *
+atspi_accessible_get_neighbor (AtspiAccessible *root,
+                                          AtspiAccessible *start,
+                                          AtspiNeighborSearchDirection direction,
+                                          GError **error)
+{
+  g_return_val_if_fail (object_is_valid(root), NULL);
+  if (!object_is_valid(start))
+    start = root;
+  const char *root_path = ATSPI_OBJECT(root)->path;
+  AtspiAccessible *return_value = NULL;
+  g_object_ref(start);
+  unsigned char recurse;
+  GetNeighborSearchMode search_mode = NEIGHBOR_SEARCH_MODE_NORMAL;
+  GQueue *children_root_stack = g_queue_new();
+  DBusMessageIter iter;
+
+  while(1) {
+    const char *path = are_objects_on_the_same_bus(root, start) ? root_path : "";
+    DBusMessage *reply = _atspi_dbus_call_partial (start, atspi_interface_accessible, "GetNeighbor", error, "sii", path, (int)direction, (int)search_mode);
+    // call failed, error is set, so we bail out
+    if (!reply) break;
+
+    _ATSPI_DBUS_CHECK_SIG (reply, "(so)y", error, NULL);
+    dbus_message_iter_init (reply, &iter);
+    AtspiAccessible *ret = _atspi_dbus_return_accessible_from_iter (&iter);
+
+    unsigned char value = 0;
+    dbus_message_iter_get_basic (&iter, &value);
+    dbus_message_iter_next (&iter);
+    recurse = (value != 0);
+
+    dbus_message_unref(reply);
+
+    // got return value and request for recursive search, it means ret is on another bridge, than start
+    // thus we're recursing. should the recurse failed to find anything it will end with
+    if (ret && recurse) {
+      g_object_unref(G_OBJECT(start));
+      start = ret;
+      g_object_ref(start);
+      if (are_objects_on_the_same_bus(root, ret))
+        {
+          search_mode = NEIGHBOR_SEARCH_MODE_RECURSE_TO_OUTSIDE;
+        }
+      else
+        {
+          g_queue_push_tail(children_root_stack, ret);
+          search_mode = NEIGHBOR_SEARCH_MODE_RECURSE_FROM_ROOT;
+        }
+      continue;
+    }
+    // found the one we've been looking for
+    if (ret) {
+      g_object_unref(G_OBJECT(start));
+      return_value = ret;
+      break;
+    }
+
+    // we've stepped into different bridges previously and now we're going back to the last one
+    // and continuing search where we left
+    if (!g_queue_is_empty(children_root_stack)) {
+      g_object_unref(G_OBJECT(start));
+      start = g_queue_pop_tail(children_root_stack);
+
+      search_mode = NEIGHBOR_SEARCH_MODE_CONTINUE_AFTER_FAILED_RECURSING;
+      continue;
+    }
+    // there's no more bridges to check, but we might have started from one
+    // in that case there might be bridges "below" start, which we yet have to visit
+    if (!are_objects_on_the_same_bus(root, start)) {
+      unsigned char continue_loop = 1;
+      while(continue_loop) {
+        AtspiAccessible *parent = atspi_accessible_get_parent(start, NULL);
+        continue_loop = parent ? are_objects_on_the_same_bus(start, parent) : 0;
+        g_object_unref(G_OBJECT(start));
+        start = parent;
+      }
+
+      // going up thru parents put us in weird place (we didnt meet root on the way)
+      // so we bail out
+      if (!start)
+        break;
+
+      // start object now points to different bridge and must be treated as "resume after recursing"
+      search_mode = NEIGHBOR_SEARCH_MODE_CONTINUE_AFTER_FAILED_RECURSING;
+      continue;
+    }
+
+    // nothing found
+    g_object_unref(start);
+    break;
+  }
+  while(!g_queue_is_empty(children_root_stack))
+    g_object_unref(g_queue_pop_tail(children_root_stack));
+  g_queue_free(children_root_stack);
+
+  return return_value;
+}
+
+/**
  * atspi_accessible_get_description:
  * @obj: a pointer to the #AtspiAccessible object on which to operate.
  *