1 #include "BridgeAccessible.hpp"
4 using namespace Dali::Accessibility;
6 #define GET_NAVIGABLE_AT_POINT_MAX_RECURSION_DEPTH 10000
8 BridgeAccessible::BridgeAccessible()
12 void BridgeAccessible::RegisterInterfaces()
14 DBus::DBusInterfaceDescription desc{ATSPI_DBUS_INTERFACE_ACCESSIBLE};
15 AddGetPropertyToInterface( desc, "ChildCount", &BridgeAccessible::GetChildCount );
16 AddGetPropertyToInterface( desc, "Name", &BridgeAccessible::GetName );
17 AddGetPropertyToInterface( desc, "Description", &BridgeAccessible::GetDescription );
18 AddGetPropertyToInterface( desc, "Parent", &BridgeAccessible::GetParent );
19 AddFunctionToInterface( desc, "GetRole", &BridgeAccessible::GetRole );
20 AddFunctionToInterface( desc, "GetRoleName", &BridgeAccessible::GetRoleName );
21 AddFunctionToInterface( desc, "GetLocalizedRoleName", &BridgeAccessible::GetLocalizedRoleName );
22 AddFunctionToInterface( desc, "GetState", &BridgeAccessible::GetStates );
23 AddFunctionToInterface( desc, "GetAttributes", &BridgeAccessible::GetAttributes );
24 AddFunctionToInterface( desc, "GetInterfaces", &BridgeAccessible::GetInterfaces );
25 AddFunctionToInterface( desc, "GetChildAtIndex", &BridgeAccessible::GetChildAtIndex );
26 AddFunctionToInterface( desc, "GetChildren", &BridgeAccessible::GetChildren );
27 AddFunctionToInterface( desc, "GetIndexInParent", &BridgeAccessible::GetIndexInParent );
28 AddFunctionToInterface( desc, "GetNavigableAtPoint", &BridgeAccessible::GetNavigableAtPoint );
29 AddFunctionToInterface( desc, "GetNeighbor", &BridgeAccessible::GetNeighbor );
30 AddFunctionToInterface( desc, "GetDefaultLabelInfo", &BridgeAccessible::GetDefaultLabelInfo );
31 AddFunctionToInterface( desc, "DoGesture", &BridgeAccessible::DoGesture );
32 AddFunctionToInterface( desc, "GetReadingMaterial", &BridgeAccessible::GetReadingMaterial );
33 dbusServer.addInterface( "/", desc, true );
36 static bool AcceptObjectCheckRole( Component* obj )
40 switch( obj->GetRole() )
42 case Role::Application:
44 case Role::ScrollPane:
52 case Role::RedundantObject:
53 case Role::ColorChooser:
55 case Role::PageTabList:
57 case Role::SpinButton:
58 case Role::InputMethodWindow:
61 case Role::Notification:
62 case Role::DateEditor:
75 static Component* GetScrollableParent( Accessible* obj )
79 obj = obj->GetParent();
80 auto comp = dynamic_cast< Component* >( obj );
81 if( comp && comp->IsScrollable() )
87 static bool ObjectIsItem( Component* obj )
91 auto role = obj->GetRole();
92 return role == Role::ListItem || role == Role::MenuItem;
95 static bool ObjectIsCollapsed( Component* obj )
99 const auto states = obj->GetStates();
100 return states[State::Expandable] && !states[State::Expanded];
103 static bool OobjectIsZeroSize( Component* obj )
107 auto size = obj->GetExtents( CoordType::Window ).size;
108 return size.height == 0 || size.width == 0;
111 static bool AcceptObject( Component* obj )
115 const auto states = obj->GetStates();
116 if( !states[State::Visible] )
118 if( !AcceptObjectCheckRole( obj ) )
122 if (CALL(get_object_in_relation_by_type, obj, ATSPI_RELATION_CONTROLLED_BY) != NULL) return 0;
124 if( !states[State::Highlightable] )
127 if( GetScrollableParent( obj ) != nullptr )
129 auto parent = dynamic_cast< Component* >( obj->GetParent() );
133 return !ObjectIsItem( obj ) || !ObjectIsCollapsed( parent );
138 if( OobjectIsZeroSize( obj ) )
142 if( !states[State::Showing] )
150 static bool AcceptObject( Accessible* obj )
152 auto c = dynamic_cast< Component* >( obj );
153 return AcceptObject( c );
156 static bool AcceptObjectOrProxy( Component* obj )
158 return obj->IsProxy() || AcceptObject( obj );
161 static Component* CalculateNavigableAccessibleAtPoint( Accessible* root, Point p, CoordType cType, unsigned int maxRecursionDepth )
163 if( !root || maxRecursionDepth == 0 )
165 auto root_component = dynamic_cast< Component* >( root );
166 if( root_component && !root_component->Contains( p, cType ) )
171 auto children = root->GetChildren();
172 for( auto childIt = children.rbegin(); childIt != children.rend(); childIt++ )
174 auto result = CalculateNavigableAccessibleAtPoint( *childIt, p, cType, maxRecursionDepth - 1 );
178 if( root_component && AcceptObjectOrProxy( root_component ) )
179 return root_component;
184 void *relation_obj = CALL(get_object_in_relation_by_type, root, ATSPI_RELATION_CONTROLLED_BY);
185 unsigned char contains = 0;
188 contains = CALL(object_contains, relation_obj, x, y, coordinates_are_screen_based);
189 if (contains) root = relation_obj;
194 BridgeAccessible::ReadingMaterialType BridgeAccessible::GetReadingMaterial()
196 auto self = FindSelf();
197 auto attributes = self->GetAttributes();
198 auto name = self->GetName();
199 std::string labeledByName = "";
200 std::string textIfceName = "";
201 auto role = static_cast< uint32_t >( self->GetRole() );
202 auto states = self->GetStates();
203 auto localizedName = self->GetLocalizedRoleName();
204 auto childCount = static_cast< int32_t >( self->GetChildCount() );
206 double currentValue = 0.0;
207 double minimumIncrement = 0.0;
208 double maximumValue = 0.0;
209 double minimumValue = 0.0;
211 auto description = self->GetDescription();
212 auto indexInParent = static_cast< int32_t >( self->GetIndexInParent() );
213 bool isSelectedInParent = false;
214 bool hasCheckBoxChild = false;
215 int32_t firstSelectedChildIndex = 0;
216 int32_t selectedChildCount = 0;
218 for( auto i = 0u; i < static_cast< size_t >( childCount ); ++i )
220 auto q = self->GetChildAtIndex( i );
221 auto s = q->GetStates();
222 if( s[State::Selectable] )
224 ++selectedChildCount;
225 if( s[State::Selected] )
227 if( firstSelectedChildIndex < 0 )
228 firstSelectedChildIndex = static_cast< int32_t >( i );
231 if( q->GetRole() == Role::CheckBox )
232 hasCheckBoxChild = true;
235 int32_t listChildrenCount = 0;
236 Accessible* parent = self->GetParent();
237 auto parentStateSet = parent ? parent->GetStates() : States{};
238 auto parentChildCount = parent ? static_cast< int32_t >( parent->GetChildCount() ) : 0;
239 auto parentRole = static_cast< uint32_t >( parent ? parent->GetRole() : Role{} );
240 Accessible* describedByObject = nullptr;
260 firstSelectedChildIndex,
269 DBus::ValueOrError< bool > BridgeAccessible::DoGesture( int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, uint32_t )
274 DBus::ValueOrError< Accessible*, uint8_t, Accessible* > BridgeAccessible::GetNavigableAtPoint( int32_t x, int32_t y, uint32_t coordType )
277 Accessible* deputy = nullptr;
278 auto accessible = FindSelf();
279 auto cType = static_cast< CoordType >( coordType );
280 auto component = CalculateNavigableAccessibleAtPoint( accessible, {x, y}, cType, GET_NAVIGABLE_AT_POINT_MAX_RECURSION_DEPTH );
281 bool recurse = false;
284 const auto states = component->GetStates();
285 if( states[State::Modal] )
292 recurse = component->IsProxy();
295 return {component, recurse, deputy};
298 static bool CheckChainEndWithAttribute( Accessible* obj, unsigned char forward )
302 auto attrs = obj->GetAttributes();
303 for( auto& attr : attrs )
305 if( attr.first == "relation_chain_end" )
307 if( ( attr.second == "prev,end" && forward == 0 ) || ( attr.second == "next,end" && forward == 1 ) || attr.second == "prev,next,end" )
316 static Accessible* DeputyOfProxyInParentGet( Accessible* obj )
323 Accessible *deputy = nullptr;
324 auto children = obj->GetChildren();
325 unsigned int index = 0;
326 for (auto child : children) {
327 if (child->IsProxy()) {
329 //WRN("Proxy does not have deputy object");
332 deputy = children[index - 1];
341 Accessible* BridgeAccessible::GetCurrentlyHighlighted()
343 //TODO: add currently highlighted object
347 std::vector< Accessible* > BridgeAccessible::ValidChildrenGet( const std::vector< Accessible* >& children, Accessible* start, Accessible* root )
349 /* condition to find first(last) object regardless of scrollable parent.
350 looping navigation does not care scrollable parent.
351 1. currently highlighted object exists
352 2. both start and root are same */
354 /* TODO: add code, we need a scrollable implementation first
355 Accessible *current = GetCurrentlyHighlighted();
356 if (current && start == root) return children;
357 if(children.size() == 0) return {};
359 Eo *child = children[0];
363 Evas_Coord x = 0, y = 0, w = 0, h = 0;
364 Evas_Coord sx = 0, sy = 0, sw = 0, sh = 0;
366 if (_new_scrollable_parent_viewport_geometry_get(child, start,
369 Eina_List *l, *l_next;
370 EINA_LIST_FOREACH_SAFE(children, l, l_next, child)
373 elm_interface_atspi_component_extents_get(EINA_FALSE,
375 if (w == 0 || h == 0 ||
376 !ELM_RECTS_INTERSECT(x, y, w, h, sx, sy, sw, sh))
377 children = eina_list_remove_list(children, l);
385 static bool DeputyIs( Accessible* obj )
391 static Accessible* GetObjectInRelationFlow( Accessible* ptr, RelationType type )
393 //TODO: add relations
396 static Accessible* ProxyInParentGet( Accessible* obj )
400 auto children = obj->GetChildren();
401 for( auto& child : children )
403 if( child->IsProxy() )
409 static bool ObjectRoleIsAcceptableWhenNavigatingNextPrev( Accessible* obj )
413 auto role = obj->GetRole();
414 return role != Role::PopupMenu && role != Role::Dialog;
418 struct CycleDetection
420 CycleDetection( const T value ) : key( value ), currentSearchSize( 1 ), counter( 1 ) {}
421 bool check( const T value )
427 currentSearchSize <<= 1;
428 if( currentSearchSize == 0 )
429 return true; // UNDEFINED BEHAVIOR
430 counter = currentSearchSize;
436 unsigned int currentSearchSize;
437 unsigned int counter;
440 static Accessible* FindNonDefunctChild( const std::vector< Accessible* >& children, unsigned int currentIndex, unsigned char forward )
442 unsigned int childrenCount = children.size();
443 for( ; currentIndex < childrenCount; forward ? ++currentIndex : --currentIndex )
445 Accessible* n = children[currentIndex];
446 if( n && !n->GetStates()[State::Defunct] )
452 static Accessible* DirectionalDepthFirstSearchTryNonDefunctChild( Accessible* node, const std::vector< Accessible* >& children, unsigned char forward )
456 auto childrenCount = children.size();
457 if( childrenCount > 0 )
459 const bool isShowing = GetScrollableParent( node ) == nullptr ? node->GetStates()[State::Showing] : true;
462 return FindNonDefunctChild( children, forward ? 0 : childrenCount - 1, forward );
468 Accessible* BridgeAccessible::GetNextNonDefunctSibling( Accessible* obj, Accessible* start, Accessible* root, unsigned char forward )
472 auto parent = obj->GetParent();
476 auto children = ValidChildrenGet( parent->GetChildren(), start, root );
478 unsigned int children_count = children.size();
479 if( children_count == 0 )
483 unsigned int current = 0;
484 for( ; current < children_count && children[current] != obj; ++current )
486 if( current >= children_count )
490 forward ? ++current : --current;
491 auto ret = FindNonDefunctChild( children, current, forward );
495 Accessible* BridgeAccessible::DirectionalDepthFirstSearchTryNonDefunctSibling( bool& all_children_visited, Accessible* node, Accessible* start, Accessible* root, unsigned char forward )
499 Accessible* sibling = GetNextNonDefunctSibling( node, start, root, forward );
503 all_children_visited = false;
507 node = node->GetParent();
508 if( node == nullptr || node == root )
511 // in backward traversing stop the walk up on parent
518 Accessible* BridgeAccessible::CalculateNeighbor( Accessible* root, Accessible* start, unsigned char forward, BridgeAccessible::GetNeighborSearchMode search_mode )
520 if( start && CheckChainEndWithAttribute( start, forward ) )
522 if( root && root->GetStates()[State::Defunct] )
524 if( start && start->GetStates()[State::Defunct] )
530 if( search_mode == BridgeAccessible::GetNeighborSearchMode::recurseToOutside )
532 /* This only works if we navigate backward, and it is not possible to
533 find in embedded process. In this case the deputy should be used */
534 return DeputyOfProxyInParentGet( start );
537 Accessible* node = start ? start : root;
541 // initialization of all-children-visited flag for start node - we assume
542 // that when we begin at start node and we navigate backward, then all children
543 // are visited, so navigation will ignore start's children and go to
544 // previous sibling available.
545 /* Regarding condtion (start != root):
546 The last object can be found only if all_children_visited is false.
547 The start is same with root, when looking for the last object. */
548 bool all_children_visited = ( start != root ) && ( search_mode != BridgeAccessible::GetNeighborSearchMode::recurseFromRoot && !forward );
549 // true, if starting element should be ignored. this is only used in rare case of
550 // recursive search failing to find an object.
551 // consider tree, where element A on bus BUS_A has child B on bus BUS_B. when going "next" from
552 // element A algorithm has to descend into BUS_B and search element B and its children. this is done
553 // by returning to our caller object B with special flag set (meaning - continue the search from B on bus BUS_B).
554 // if next object will be found there (on BUS_B), then search ends. but if not, then our caller will find it out
555 // and will call us again with object A and flag search_mode set to NEIGHBOR_SEARCH_MODE_CONTINUE_AFTER_FAILED_RECURSING.
556 // this flag means, that object A was already checked previously and we should skip it and its children.
557 bool force_next = ( search_mode == BridgeAccessible::GetNeighborSearchMode::continueAfterFailedRecursion );
559 CycleDetection< Accessible* > cycleDetection( node );
562 if( node->GetStates()[State::Defunct] )
565 // always accept proxy object from different world
566 if( !force_next && node->IsProxy() )
569 auto children = node->GetChildren();
570 children = ValidChildrenGet( children, start, root );
574 // 2. parent after all children in backward traversing
575 // 3. Nodes with roles: ATSPI_ROLE_PAGE_TAB, ATSPI_ROLE_POPUP_MENU and ATSPI_ROLE_DIALOG, only when looking for first or last element.
576 // Objects with those roles shouldnt be reachable, when navigating next / prev.
577 bool all_children_visited_or_moving_forward = ( children.size() == 0 || forward || all_children_visited );
578 if( !force_next && node != start && all_children_visited_or_moving_forward && AcceptObject( node ) )
580 if( start == NULL || ObjectRoleIsAcceptableWhenNavigatingNextPrev( node ) )
584 Accessible* next_related_in_direction = !force_next ? GetObjectInRelationFlow( node, forward ? RelationType::FlowsTo : RelationType::FlowsFrom ) : nullptr;
585 /* force_next means that the search_mode is NEIGHBOR_SEARCH_MODE_CONTINUE_AFTER_FAILED_RECURSING
586 in this case the node is elm_layout which is parent of proxy object.
587 There is an access object working for the proxy object, and the access
588 object could have relation information. This relation information should
589 be checked first before using the elm_layout as a node. */
590 if( force_next && forward )
592 auto deputy = DeputyOfProxyInParentGet( node );
593 next_related_in_direction =
594 GetObjectInRelationFlow( deputy, forward ? RelationType::FlowsTo : RelationType::FlowsFrom );
597 if( next_related_in_direction && start->GetStates()[State::Defunct] )
598 next_related_in_direction = NULL;
599 unsigned char want_cycle_detection = 0;
600 if( next_related_in_direction )
602 /* Check next_related_in_direction is deputy object */
606 /* If the prev object is deputy, then go to inside of its proxy first */
607 if( DeputyIs( next_related_in_direction ) )
609 parent = next_related_in_direction->GetParent();
610 next_related_in_direction = ProxyInParentGet( parent );
615 /* If current object is deputy, and it has relation next object,
616 then do not use the relation next object, and use proxy first */
617 if( DeputyIs( node ) )
619 parent = node->GetParent();
620 next_related_in_direction = ProxyInParentGet( parent );
623 node = next_related_in_direction;
624 want_cycle_detection = 1;
628 auto child = !force_next && !all_children_visited ? DirectionalDepthFirstSearchTryNonDefunctChild( node, children, forward ) : nullptr;
631 want_cycle_detection = 1;
635 if( !force_next && node == root )
637 all_children_visited = true;
638 child = DirectionalDepthFirstSearchTryNonDefunctSibling( all_children_visited, node, start, root, forward );
643 if( want_cycle_detection && cycleDetection.check( node ) )
651 DBus::ValueOrError< Accessible*, uint8_t > BridgeAccessible::GetNeighbor( std::string rootPath, int32_t direction, int32_t search_mode )
653 auto start = FindSelf();
654 rootPath = StripPrefix( rootPath );
655 auto root = !rootPath.empty() ? Find( rootPath ) : nullptr;
656 auto accessible = CalculateNeighbor( root, start, direction == 1, static_cast< GetNeighborSearchMode >( search_mode ) );
657 unsigned char recurse = 0;
660 recurse = accessible->IsProxy();
662 return {accessible, recurse};
665 Accessible* BridgeAccessible::GetParent()
667 // NOTE: currently bridge supports single application root element.
668 // only element set as application root might return nullptr from GetParent
669 // if you want more, then you need to change setApplicationRoot to
670 // add/remove ApplicationRoot and make roots a vector.
672 auto p = z->GetParent();
676 DBus::ValueOrError< std::vector< Accessible* > > BridgeAccessible::GetChildren()
678 return FindSelf()->GetChildren();
680 std::string BridgeAccessible::GetDescription()
682 return FindSelf()->GetDescription();
684 DBus::ValueOrError< uint32_t > BridgeAccessible::GetRole()
686 return static_cast< unsigned int >( FindSelf()->GetRole() );
688 DBus::ValueOrError< std::string > BridgeAccessible::GetRoleName()
690 return FindSelf()->GetRoleName();
692 DBus::ValueOrError< std::string > BridgeAccessible::GetLocalizedRoleName()
694 return FindSelf()->GetLocalizedRoleName();
696 DBus::ValueOrError< int32_t > BridgeAccessible::GetIndexInParent()
698 return FindSelf()->GetIndexInParent();
700 DBus::ValueOrError< std::array< uint32_t, 2 > > BridgeAccessible::GetStates()
702 return FindSelf()->GetStates().GetRawData();
704 DBus::ValueOrError< std::unordered_map< std::string, std::string > > BridgeAccessible::GetAttributes()
706 return FindSelf()->GetAttributes();
708 DBus::ValueOrError< std::vector< std::string > > BridgeAccessible::GetInterfaces()
710 return FindSelf()->GetInterfaces();
712 int BridgeAccessible::GetChildCount()
714 return FindSelf()->GetChildCount();
716 DBus::ValueOrError< Accessible* > BridgeAccessible::GetChildAtIndex( int index )
719 throw AccessibleError{"negative index (" + std::to_string( index ) + ")"};
720 return FindSelf()->GetChildAtIndex( static_cast< size_t >( index ) );
723 std::string BridgeAccessible::GetName()
725 return FindSelf()->GetName();
728 DBus::ValueOrError< Accessible*, uint32_t > BridgeAccessible::GetDefaultLabelInfo()
731 return {p, static_cast< uint32_t >( p->GetRole() )};