/*
- * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali/internal/accessibility/bridge/bridge-base.h>
// EXTERNAL INCLUDES
+#include <dali/devel-api/common/stage.h>
#include <atomic>
#include <cstdlib>
#include <memory>
// INTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/window-devel.h>
#include <dali/public-api/adaptor-framework/timer.h>
using namespace Dali::Accessibility;
static Dali::Timer tickTimer;
-BridgeBase::~BridgeBase()
+BridgeBase::BridgeBase()
{
}
-BridgeBase::BridgeBase()
+BridgeBase::~BridgeBase()
{
}
-void BridgeBase::addFilteredEvent( FilteredEvents kind, Dali::Accessibility::Accessible* obj, float delay, std::function<void()> functor )
+void BridgeBase::AddCoalescableMessage(CoalescableMessages kind, Dali::Accessibility::Accessible* obj, float delay, std::function<void()> functor)
{
- if( delay < 0 )
+ if(delay < 0)
{
delay = 0;
}
+ auto countdownBase = static_cast<unsigned int>(delay * 10);
- auto it = filteredEvents.insert({ { kind, obj }, { static_cast<unsigned int>(delay * 10), {} } });
- if (it.second)
+ auto it = mCoalescableMessages.insert({{kind, obj}, {countdownBase, countdownBase, {}}});
+ if(it.second)
{
functor();
}
else
{
- it.first->second.second = std::move(functor);
+ std::get<1>(it.first->second) = countdownBase;
+ std::get<2>(it.first->second) = std::move(functor);
}
- if (!tickTimer)
+ if(!tickTimer)
{
tickTimer = Dali::Timer::New(100);
- tickTimer.TickSignal().Connect(this, &BridgeBase::tickFilteredEvents);
+ tickTimer.TickSignal().Connect(this, &BridgeBase::TickCoalescableMessages);
+ }
+
+ if(!tickTimer.IsRunning())
+ {
+ tickTimer.Start();
}
}
-bool BridgeBase::tickFilteredEvents()
+bool BridgeBase::TickCoalescableMessages()
{
- for(auto it = filteredEvents.begin(); it != filteredEvents.end(); )
+ for(auto it = mCoalescableMessages.begin(); it != mCoalescableMessages.end();)
{
- if (it->second.first)
+ auto& countdown = std::get<0>(it->second);
+ auto countdownBase = std::get<1>(it->second);
+ auto& functor = std::get<2>(it->second);
+ if(countdown)
{
- --it->second.first;
+ --countdown;
}
else
{
- if (it->second.second)
+ if(functor)
{
- it->second.second();
- it->second.second = {};
+ functor();
+ functor = {};
+ countdown = countdownBase;
}
else
{
- it = filteredEvents.erase(it);
+ it = mCoalescableMessages.erase(it);
continue;
}
}
++it;
}
- return !filteredEvents.empty();
+ return !mCoalescableMessages.empty();
+}
+
+void BridgeBase::UpdateRegisteredEvents()
+{
+ using ReturnType = std::vector<std::tuple<std::string, std::string>>;
+ mRegistry.method<DBus::ValueOrError<ReturnType>()>("GetRegisteredEvents").asyncCall([this](DBus::ValueOrError<ReturnType> msg) {
+ if(!msg)
+ {
+ LOG() << "Get registered events failed";
+ return;
+ }
+
+ IsBoundsChangedEventAllowed = false;
+
+ ReturnType values = std::get<ReturnType>(msg.getValues());
+ for(long unsigned int i = 0; i < values.size(); i++)
+ {
+ if(!std::get<1>(values[i]).compare("Object:BoundsChanged"))
+ {
+ IsBoundsChangedEventAllowed = true;
+ }
+ }
+ });
}
BridgeBase::ForceUpResult BridgeBase::ForceUp()
{
- if( Bridge::ForceUp() == ForceUpResult::ALREADY_UP )
+ //TODO: checking mBusName is enough? or a new variable to check bridge state?
+ if(Bridge::ForceUp() == ForceUpResult::ALREADY_UP && !GetBusName().empty())
{
return ForceUpResult::ALREADY_UP;
}
auto proxy = DBus::DBusClient{dbusLocators::atspi::BUS, dbusLocators::atspi::OBJ_PATH, dbusLocators::atspi::BUS_INTERFACE, DBus::ConnectionType::SESSION};
- auto addr = proxy.method< std::string() >( dbusLocators::atspi::GET_ADDRESS ).call();
+ auto addr = proxy.method<std::string()>(dbusLocators::atspi::GET_ADDRESS).call();
- if( !addr )
+ if(!addr)
{
- throw std::domain_error{std::string( "failed at call '" ) + dbusLocators::atspi::GET_ADDRESS + "': " + addr.getError().message};
+ DALI_LOG_ERROR("failed at call '%s': %s\n", dbusLocators::atspi::GET_ADDRESS, addr.getError().message.c_str());
+ return ForceUpResult::FAILED;
}
- con = DBusWrapper::Installed()->eldbus_address_connection_get_impl( std::get< 0 >( addr ) );
- data->busName = DBus::getConnectionName( con );
- dbusServer = { con };
+ mConnectionPtr = DBusWrapper::Installed()->eldbus_address_connection_get_impl(std::get<0>(addr));
+ mData->mBusName = DBus::getConnectionName(mConnectionPtr);
+ mDbusServer = {mConnectionPtr};
{
- DBus::DBusInterfaceDescription desc{"org.a11y.atspi.Cache"};
- AddFunctionToInterface( desc, "GetItems", &BridgeBase::GetItems );
- dbusServer.addInterface( "/org/a11y/atspi/cache", desc );
+ DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::CACHE)};
+ AddFunctionToInterface(desc, "GetItems", &BridgeBase::GetItems);
+ mDbusServer.addInterface(AtspiDbusPathCache, desc);
}
{
- DBus::DBusInterfaceDescription desc{"org.a11y.atspi.Application"};
- AddGetSetPropertyToInterface( desc, "Id", &BridgeBase::IdGet, &BridgeBase::IdSet );
- dbusServer.addInterface( AtspiPath, desc );
+ DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::APPLICATION)};
+ AddGetSetPropertyToInterface(desc, "Id", &BridgeBase::GetId, &BridgeBase::SetId);
+ mDbusServer.addInterface(AtspiPath, desc);
}
+ mRegistry = {AtspiDbusNameRegistry, AtspiDbusPathRegistry, Accessible::GetInterfaceName(AtspiInterface::REGISTRY), mConnectionPtr};
+
+ UpdateRegisteredEvents();
+
+ mRegistry.addSignal<void(void)>("EventListenerRegistered", [this](void) {
+ UpdateRegisteredEvents();
+ });
+
+ mRegistry.addSignal<void(void)>("EventListenerDeregistered", [this](void) {
+ UpdateRegisteredEvents();
+ });
+
return ForceUpResult::JUST_STARTED;
}
void BridgeBase::ForceDown()
{
Bridge::ForceDown();
- dbusServer = {};
- con = {};
+ tickTimer.Reset();
+ mCoalescableMessages.clear();
+ mRegistry = {};
+ mDbusServer = {};
+ mConnectionPtr = {};
}
const std::string& BridgeBase::GetBusName() const
{
static std::string empty;
- return data ? data->busName : empty;
+ return mData ? mData->mBusName : empty;
}
-Accessible* BridgeBase::FindByPath( const std::string& name ) const
+Accessible* BridgeBase::FindByPath(const std::string& name) const
{
try
{
- return Find( name );
+ return Find(name);
}
- catch( std::domain_error& )
+ catch(std::domain_error&)
{
return nullptr;
}
}
-void BridgeBase::AddPopup( Accessible* obj )
+void BridgeBase::AddTopLevelWindow(Accessible* windowAccessible)
{
- if( std::find( popups.begin(), popups.end(), obj ) != popups.end() )
+ if(windowAccessible->GetInternalActor() == nullptr)
{
return;
}
- popups.push_back( obj );
- if (IsUp())
+
+ // Prevent adding the default window twice.
+ if(!mApplication.mChildren.empty() &&
+ mApplication.mChildren[0]->GetInternalActor() == windowAccessible->GetInternalActor())
{
- obj->Emit( WindowEvent::ACTIVATE, 0 );
+ return;
}
+
+ // Adds Window to a list of Windows.
+ mApplication.mChildren.push_back(windowAccessible);
+ SetIsOnRootLevel(windowAccessible);
}
-void BridgeBase::RemovePopup( Accessible* obj )
+void BridgeBase::RemoveTopLevelWindow(Accessible* windowAccessible)
{
- auto it = std::find( popups.begin(), popups.end(), obj );
- if( it == popups.end() )
+ for(auto i = 0u; i < mApplication.mChildren.size(); ++i)
{
+ if(mApplication.mChildren[i] == windowAccessible)
+ {
+ mApplication.mChildren.erase(mApplication.mChildren.begin() + i);
+ Emit(windowAccessible, WindowEvent::DESTROY);
+ break;
+ }
+ }
+}
+
+void BridgeBase::CompressDefaultLabels()
+{
+ // Remove entries for objects which no longer exist
+ mDefaultLabels.remove_if([](const DefaultLabelType& label) {
+ // Check 1) window's weak handle; 2) accessible's ref object
+ return !label.first.GetBaseHandle() || label.second.expired();
+ });
+}
+
+void BridgeBase::RegisterDefaultLabel(std::shared_ptr<Accessible> object)
+{
+ CompressDefaultLabels();
+
+ Dali::WeakHandle<Dali::Window> window = GetWindow(object.get());
+ if(!window.GetBaseHandle()) // true also if `object` is null
+ {
+ DALI_LOG_ERROR("Cannot register default label: object does not belong to any window");
return;
}
- popups.erase( it );
- if (IsUp())
+
+ auto it = std::find_if(mDefaultLabels.begin(), mDefaultLabels.end(), [&object](const DefaultLabelType& label) {
+ auto labelPtr = label.second.lock();
+ return labelPtr && object == labelPtr;
+ });
+
+ if(it == mDefaultLabels.end())
{
- obj->Emit( WindowEvent::DEACTIVATE, 0 );
- if( popups.empty() )
- {
- application.children.back()->Emit( WindowEvent::ACTIVATE, 0 );
- }
- else
- {
- popups.back()->Emit( WindowEvent::ACTIVATE, 0 );
- }
+ mDefaultLabels.push_back({window, object});
+ }
+ else if(it->first != window)
+ {
+ // TODO: Tentative implementation. It is yet to be specified what should happen
+ // when the same object is re-registered as a default label for another window.
+ *it = {window, object};
+ }
+ else // it->first == window && it->second == object
+ {
+ // Nothing to do
}
}
-void BridgeBase::AddTopLevelWindow( Accessible* root )
+void BridgeBase::UnregisterDefaultLabel(std::shared_ptr<Accessible> object)
{
- application.children.push_back( root );
- SetIsOnRootLevel( root );
+ CompressDefaultLabels();
+
+ mDefaultLabels.remove_if([&object](const DefaultLabelType& label) {
+ auto labelPtr = label.second.lock();
+ return labelPtr && object == labelPtr;
+ });
}
-void BridgeBase::RemoveTopLevelWindow( Accessible* root )
+Accessible* BridgeBase::GetDefaultLabel(Accessible* root)
{
- for(auto i = 0u; i < application.children.size(); ++i)
+ CompressDefaultLabels();
+
+ Dali::WeakHandle<Dali::Window> window = GetWindow(root);
+ if(!window.GetBaseHandle())
+ {
+ return root;
+ }
+
+ auto it = std::find_if(mDefaultLabels.rbegin(), mDefaultLabels.rend(), [&window](const DefaultLabelType& label) {
+ return window == label.first;
+ });
+
+ Accessible* rawPtr = root;
+ if(it != mDefaultLabels.rend())
{
- if( application.children[i] == root )
+ if(auto labelPtr = it->second.lock())
{
- application.children.erase(application.children.begin() + i);
- break;
+ rawPtr = labelPtr.get();
}
}
+
+ return rawPtr;
}
-std::string BridgeBase::StripPrefix( const std::string& path )
+std::string BridgeBase::StripPrefix(const std::string& path)
{
- auto size = strlen( AtspiPath );
- return path.substr( size + 1 );
+ auto size = strlen(AtspiPath);
+ return path.substr(size + 1);
}
-Accessible* BridgeBase::Find( const std::string& path ) const
+Accessible* BridgeBase::Find(const std::string& path) const
{
- if( path == "root" )
+ if(path == "root")
{
- return &application;
+ return &mApplication;
}
- void* p;
- std::istringstream tmp{ path };
- if (! ( tmp >> p) )
+
+ void* accessible;
+ std::istringstream tmp{path};
+ if(!(tmp >> accessible))
{
throw std::domain_error{"invalid path '" + path + "'"};
}
- auto it = data->knownObjects.find( static_cast<Accessible*>( p ) );
- if( it == data->knownObjects.end() )
+
+ auto it = mData->mKnownObjects.find(static_cast<Accessible*>(accessible));
+ if(it == mData->mKnownObjects.end() || (!mApplication.mShouldIncludeHidden && (*it)->IsHidden()))
{
throw std::domain_error{"unknown object '" + path + "'"};
}
- return static_cast<Accessible*>( p );
+
+ return static_cast<Accessible*>(accessible);
}
-Accessible* BridgeBase::Find( const Address& ptr ) const
+Accessible* BridgeBase::Find(const Address& ptr) const
{
- assert( ptr.GetBus() == data->busName );
- return Find( ptr.GetPath() );
+ assert(ptr.GetBus() == mData->mBusName);
+ return Find(ptr.GetPath());
}
-Accessible* BridgeBase::FindSelf() const
+Accessible* BridgeBase::FindCurrentObject() const
{
- auto pth = DBus::DBusServer::getCurrentObjectPath();
- auto size = strlen( AtspiPath );
- if( pth.size() <= size )
+ auto path = DBus::DBusServer::getCurrentObjectPath();
+ auto size = strlen(AtspiPath);
+ if(path.size() <= size)
{
- throw std::domain_error{"invalid path '" + pth + "'"};
+ throw std::domain_error{"invalid path '" + path + "'"};
}
- if( pth.substr( 0, size ) != AtspiPath )
+ if(path.substr(0, size) != AtspiPath)
{
- throw std::domain_error{"invalid path '" + pth + "'"};
+ throw std::domain_error{"invalid path '" + path + "'"};
}
- if( pth[size] != '/' )
+ if(path[size] != '/')
{
- throw std::domain_error{"invalid path '" + pth + "'"};
+ throw std::domain_error{"invalid path '" + path + "'"};
}
- return Find( StripPrefix( pth ) );
+ return Find(StripPrefix(path));
}
-void BridgeBase::IdSet( int id )
+void BridgeBase::SetId(int id)
{
- this->id = id;
+ this->mId = id;
}
-int BridgeBase::IdGet()
+int BridgeBase::GetId()
{
- return this->id;
+ return this->mId;
}
-auto BridgeBase::GetItems() -> DBus::ValueOrError< std::vector< CacheElementType > >
+auto BridgeBase::GetItems() -> DBus::ValueOrError<std::vector<CacheElementType>>
{
- auto root = &application;
+ auto root = &mApplication;
- std::vector< CacheElementType > res;
+ std::vector<CacheElementType> res;
- std::function< void(Accessible*) > proc =
- [&]( Accessible* item )
- {
- res.emplace_back( std::move( CreateCacheElement( root ) ) );
- for( auto i = 0u; i < item->GetChildCount(); ++i )
+ std::function<void(Accessible*)> proc =
+ [&](Accessible* item) {
+ res.emplace_back(std::move(CreateCacheElement(root)));
+ for(auto i = 0u; i < item->GetChildCount(); ++i)
{
- proc( item->GetChildAtIndex( i ) );
+ proc(item->GetChildAtIndex(i));
}
};
return res;
}
-auto BridgeBase::CreateCacheElement( Accessible* item ) -> CacheElementType
+auto BridgeBase::CreateCacheElement(Accessible* item) -> CacheElementType
{
- if( !item )
+ if(!item)
{
return {};
}
- auto root = &application;
+ auto root = &mApplication;
auto parent = item->GetParent();
- std::vector< Address > children;
- for( auto i = 0u; i < item->GetChildCount(); ++i )
+ std::vector<Address> children;
+ for(auto i = 0u; i < item->GetChildCount(); ++i)
{
- children.emplace_back( item->GetChildAtIndex( i )->GetAddress() );
+ children.emplace_back(item->GetChildAtIndex(i)->GetAddress());
}
return std::make_tuple(
root->GetAddress(),
parent ? parent->GetAddress() : Address{},
children,
- item->GetInterfaces(),
+ item->GetInterfacesAsStrings(),
item->GetName(),
item->GetRole(),
item->GetDescription(),
- item->GetStates().GetRawData()
- );
+ item->GetStates().GetRawData());
}
+Dali::WeakHandle<Dali::Window> BridgeBase::GetWindow(Dali::Accessibility::Accessible* accessible)
+{
+ Dali::WeakHandle<Dali::Window> windowHandle;
+ Dali::Actor actor = accessible ? accessible->GetInternalActor() : Dali::Actor();
+
+ if(actor)
+ {
+ Dali::Window window = Dali::DevelWindow::Get(actor);
+ windowHandle = {window};
+ }
+
+ return windowHandle;
+}