+
+/**
+ * atspi_accessible_get_unique_id:
+ * @obj: a pointer to the #AtspiAccessible object on which to operate.
+ *
+ * Gets the identificator, uniquely identifying object, or NULL if an error occured.
+ *
+ * Returns: a UTF-8 string describing the #AtspiAccessible object
+ * or NULL on exception or NULL object passed.
+ **/
+gchar *
+atspi_accessible_get_unique_id(AtspiAccessible *obj, GError **error)
+{
+ if (!obj) {
+ g_set_error(error, ATSPI_ERROR, ATSPI_ERROR_IPC, "argument is null");
+ return NULL;
+ }
+
+ gchar *id = NULL;
+ gchar *bus_name = atspi_accessible_get_bus_name(obj, error);
+ if (bus_name && bus_name[0]) {
+ gchar *path = atspi_accessible_get_path(obj, error);
+ if (path && path[0])
+ id = g_strdup_printf("%s:%s", bus_name, path);
+ else
+ g_set_error(error, ATSPI_ERROR, ATSPI_ERROR_IPC, "failed to get path");
+ g_free(path);
+ }
+ else
+ g_set_error(error, ATSPI_ERROR, ATSPI_ERROR_IPC, "failed to get bus name");
+ g_free(bus_name);
+ return id;
+}
+
+/**
+ * atspi_accessible_get_bus_name:
+ * @obj: a pointer to the #AtspiAccessible object on which to operate.
+ *
+ * Gets the bus name, where object belongs.
+ *
+ * Returns: a UTF-8 string describing the #AtspiAccessible object's
+ * bus name or empty string on exception or NULL object passed.
+ **/
+gchar *
+atspi_accessible_get_bus_name(AtspiAccessible *obj, GError **error)
+{
+ if (!obj || !obj->parent.app)
+ return g_strdup("");
+ return g_strdup (obj->parent.app->bus_name);
+}
+
+/**
+ * atspi_accessible_get_path:
+ * @obj: a pointer to the #AtspiAccessible object on which to operate.
+ *
+ * Gets the path, uniquely identifying object over its bus name.
+ *
+ * Returns: a UTF-8 string describing the #AtspiAccessible object
+ * or empty string on exception or NULL object passed.
+ **/
+gchar *
+atspi_accessible_get_path(AtspiAccessible *obj, GError **error)
+{
+ static const char *prefix = "/org/a11y/atspi/accessible/";
+ static int prefix_len = 27;
+
+ if (!obj)
+ return g_strdup("");
+ AtspiObject *o = ATSPI_OBJECT (obj);
+ if (!o)
+ return g_strdup("");
+ if (strncmp(o->path, prefix, prefix_len) == 0)
+ return g_strdup(o->path + prefix_len);
+ return g_strdup (o->path);
+}
+
+/**
+ * atspi_accessible_get_navigable_at_point:
+ * @root: a pointer to the #AtspiAccessible to start search from.
+ * @x: a #gint specifying the x coordinate of the point in question.
+ * @y: a #gint specifying the y coordinate of the point in question.
+ * @ctype: the coordinate system of the point (@x, @y)
+ * (e.g. ATSPI_COORD_TYPE_WINDOW, ATSPI_COORD_TYPE_SCREEN).
+ *
+ * Finds the accessible element closest to user (highest in z-order), at a given coordinate within an #AtspiAccessible.
+ * This should be the element, that should be picked, when doing mouse click or finger tap at given coordinates.
+ *
+ * Returns: (nullable) (transfer full): a pointer to an
+ * #AtspiAccessible descendant (of any depth) of the specified component which
+ * contains the point (@x, @y), or NULL if no descendant contains
+ * the point.
+ **/
+AtspiAccessible *
+atspi_accessible_get_navigable_at_point (AtspiAccessible *root,
+ gint x,
+ gint y,
+ AtspiCoordType ctype, GError **error)
+{
+ dbus_int32_t d_x = x, d_y = y;
+ dbus_uint32_t d_ctype = ctype;
+ DBusMessage *reply;
+ AtspiAccessible *return_value = NULL;
+ unsigned char recurse = 0;
+
+ 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);
+
+ 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;
+ } while(recurse);
+ return return_value;
+}
+
+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,
+} 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();
+
+ 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);
+ AtspiAccessible *ret = _atspi_dbus_return_accessible_and_recurse_info_from_message (reply, &recurse);
+ // 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));
+ g_queue_push_tail(children_root_stack, ret);
+ start = ret;
+ g_object_ref(start);
+ 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);
+ return_value = NULL;
+ 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;
+}
+