diff options
28 files changed, 1213 insertions, 1189 deletions
diff --git a/dali/devel-api/adaptor-framework/accessibility-bridge.h b/dali/devel-api/adaptor-framework/accessibility-bridge.h index de570532d..e54458153 100644 --- a/dali/devel-api/adaptor-framework/accessibility-bridge.h +++ b/dali/devel-api/adaptor-framework/accessibility-bridge.h @@ -95,40 +95,30 @@ struct DALI_ADAPTOR_API Bridge /** * @brief Adds object on the top of the stack of "default label" sourcing objects. * - * @see GetDefaultLabel + * The "default label" is a reading material (text) derived from an accesibility object + * that could be read by screen-reader immediately after the navigation context has changed + * (window activates, popup shows up, tab changes) and before first UI element is highlighted. * * @param[in] object The accessible object + * + * @note This is a Tizen only feature not present in upstream ATSPI. + * Feature can be enabled/disabled for particular context root object + * by setting value of its accessibility attribute "default_label". + * Following strings are valid values for "default_label" attribute: "enabled", "disabled". + * Any other value will be interpreted as "enabled". */ virtual void RegisterDefaultLabel(Accessible* object) = 0; /** * @brief Removes object from the stack of "default label" sourcing objects. * - * @see GetDefaultLabel + * @see RegisterDefaultLabel * * @param[in] object The accessible object */ virtual void UnregisterDefaultLabel(Accessible* object) = 0; /** - * @brief Gets the top-most object from the stack of "default label" sourcing objects. - * - * The "default label" is a reading material (text) derived from an accesibility object - * that could be read by screen-reader immediately after the navigation context has changed - * (window activates, popup shows up, tab changes) and before first UI element is highlighted. - * - * @param[in] root The root of the navigation context for which to retrieve the default label. - * - * @return The handler to accessibility object - * @note This is a Tizen only feature not present in upstream ATSPI. - * Feature can be enabled/disabled for particular context root object - * by setting value of its accessibility attribute "default_label". - * Following strings are valid values for "default_label" attribute: "enabled", "disabled". - * Any other value will be interpreted as "enabled". - */ - virtual Accessible* GetDefaultLabel(Accessible* root) const = 0; - - /** * @brief Sets name of current application which will be visible on accessibility bus. * * @param[in] name The application name @@ -230,15 +220,7 @@ struct DALI_ADAPTOR_API Bridge /** * @brief This method is called, when bridge is being activated. */ - virtual ForceUpResult ForceUp() - { - if(mData) - { - return ForceUpResult::ALREADY_UP; - } - mData = std::make_shared<Data>(); - return ForceUpResult::JUST_STARTED; - } + virtual ForceUpResult ForceUp() = 0; /** * @brief This method is called, when bridge is being deactivated. @@ -249,10 +231,7 @@ struct DALI_ADAPTOR_API Bridge * @brief Checks if bridge is activated or not. * @return True if brige is activated. */ - bool IsUp() const - { - return bool(mData); - } + virtual bool IsUp() const = 0; /** * @brief Emits cursor-moved event on at-spi bus. @@ -468,25 +447,10 @@ struct DALI_ADAPTOR_API Bridge * * @note Remote object pointed to by 'socket' must implement 'org.a11y.atspi.Socket'. * @see EmbedSocket() - * @see SetExtentsOffset() */ virtual void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) = 0; /** - * @brief Sets the global extents offset. - * - * This offset will be added during serialization of GetExtents() return value to D-Bus. - * Local calls to GetExtents() are unaffected. - * - * @param[in] x Horizontal offset - * @param[in] y Vertical offset - * - * @see SetSocketOffset() - * @see Dali::Accessibility::Component::GetExtents() - */ - virtual void SetExtentsOffset(std::int32_t x, std::int32_t y) = 0; - - /** * @brief Sets the preferred bus name. * * If the Bridge is enabled, it will immediately release the previous name and request the new one. @@ -499,6 +463,24 @@ struct DALI_ADAPTOR_API Bridge virtual void SetPreferredBusName(std::string_view preferredBusName) = 0; /** + * @brief Registers accessible object to be known in bridge object. + * + * Bridge must known about all currently alive accessible objects, as some requst + * might come and object will be identified by number id (it's memory address). + * To avoid memory corruption number id is checked against set of known objects. + * + * @param[in] object The accessible object + **/ + virtual void RegisterAccessible(const Accessible* object) override; + + /** + * @brief Unregisters accessible object from the bridge object. + * + * @param[in] object The accessible object + **/ + virtual void UnregisterAccessible(const Accessible* object) override; + + /** * @brief Returns instance of bridge singleton object. * * @return The current bridge object @@ -564,9 +546,10 @@ struct DALI_ADAPTOR_API Bridge } protected: + +// TODO: rename and move to BridgeImpl struct Data { - std::unordered_set<const Accessible*> mKnownObjects; std::string mBusName; Actor mHighlightActor; Actor mCurrentlyHighlightedActor; @@ -583,38 +566,11 @@ protected: inline static AutoInitState mAutoInitState = AutoInitState::ENABLED; +// TODO: check if these need to be static inline static Signal<void()> mEnabledSignal; inline static Signal<void()> mDisabledSignal; inline static Signal<void()> mScreenReaderEnabledSignal; inline static Signal<void()> mScreenReaderDisabledSignal; - - /** - * @brief Registers accessible object to be known in bridge object. - * - * Bridge must known about all currently alive accessible objects, as some requst - * might come and object will be identified by number id (it's memory address). - * To avoid memory corruption number id is checked against set of known objects. - * - * @param[in] object The accessible object - **/ - void RegisterAccessible(const Accessible* object); - - /** - * @brief Unregisters accessible object from the bridge object. - * - * @param[in] object The accessible object - **/ - void UnregisterAccessible(const Accessible* object); - - /** - * @brief Tells bridge, that given object is considered root (doesn't have any parents). - * - * All root objects will have the same parent - application object. Application object - * is controlled by bridge and private. - * - * @param[in] owner The accessible object - **/ - void SetIsOnRootLevel(Accessible* owner); }; /** diff --git a/dali/devel-api/adaptor-framework/accessibility.cpp b/dali/devel-api/adaptor-framework/accessibility.cpp index 8b7a10ff2..445501ffe 100644 --- a/dali/devel-api/adaptor-framework/accessibility.cpp +++ b/dali/devel-api/adaptor-framework/accessibility.cpp @@ -319,25 +319,6 @@ void Accessible::SetHighlightActor(Dali::Actor actor) } } -void Bridge::ForceDown() -{ - auto highlighted = Accessible::GetCurrentlyHighlightedActor(); - if(highlighted) - { - auto component = dynamic_cast<Component*>(Accessible::Get(highlighted)); - if(component) - { - component->ClearHighlight(); - } - } - mData = {}; -} - -void Bridge::SetIsOnRootLevel(Accessible* owner) -{ - owner->mIsOnRootLevel = true; -} - namespace { class AdaptorAccessible : public ActorAccessible diff --git a/dali/devel-api/atspi-interfaces/accessible.h b/dali/devel-api/atspi-interfaces/accessible.h index 1e6f25200..b33ff4460 100644 --- a/dali/devel-api/atspi-interfaces/accessible.h +++ b/dali/devel-api/atspi-interfaces/accessible.h @@ -369,6 +369,17 @@ public: } /** + * @brief Set the object as root level (doesn't have any parents). + * + * All root objects will have the same parent - application object. Application object + * is controlled by bridge and private. + **/ + void SetIsOnRootLevel() + { + mIsOnRootLevel = true; + } + + /** * @brief Gets all suppressed events. * * @return All suppressed events diff --git a/dali/internal/accessibility/bridge/accessible.cpp b/dali/internal/accessibility/bridge/accessible.cpp index baf67035c..a3aa96daa 100644 --- a/dali/internal/accessibility/bridge/accessible.cpp +++ b/dali/internal/accessibility/bridge/accessible.cpp @@ -126,20 +126,7 @@ Address Accessible::GetAddress() const } std::ostringstream tmp; tmp << this; - auto bridgeData = bridge->mData; - return {bridgeData ? bridgeData->mBusName : "", tmp.str()}; -} - -void Bridge::RegisterAccessible(const Accessible* object) -{ - assert(mData); - mData->mKnownObjects.insert(object); -} - -void Bridge::UnregisterAccessible(const Accessible* object) -{ - assert(mData); - mData->mKnownObjects.erase(object); + return {bridge->GetBusName(), tmp.str()}; } bool Accessible::IsHidden() const diff --git a/dali/internal/accessibility/bridge/bridge-accessible.cpp b/dali/internal/accessibility/bridge/bridge-accessible.cpp index c57a887c4..4616bccf6 100644 --- a/dali/internal/accessibility/bridge/bridge-accessible.cpp +++ b/dali/internal/accessibility/bridge/bridge-accessible.cpp @@ -365,13 +365,33 @@ static std::vector<Component*> GetNonDuplicatedScrollableParents(Accessible* chi return scrollableParentsOfChild; } -} // anonymous namespace +/** + * @brief Gets the window to which this accessible belongs (or an empty handle). + * + * @param accessible The accessible + * @return The window + */ -BridgeAccessible::BridgeAccessible() +Dali::WeakHandle<Dali::Window> 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; } -void BridgeAccessible::RegisterInterfaces() +} // anonymous namespace + +BridgeAccessible::BridgeAccessible(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeAccessible::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::ACCESSIBLE)}; AddGetPropertyToInterface(desc, "ChildCount", &BridgeAccessible::GetChildCount); @@ -395,12 +415,12 @@ void BridgeAccessible::RegisterInterfaces() AddFunctionToInterface(desc, "GetRelationSet", &BridgeAccessible::GetRelationSet); AddFunctionToInterface(desc, "SetListenPostRender", &BridgeAccessible::SetListenPostRender); AddFunctionToInterface(desc, "GetNodeInfo", &BridgeAccessible::GetNodeInfo); - mDbusServer.addInterface("/", desc, true); + return desc; } Accessible* BridgeAccessible::FindSelf() const { - return FindCurrentObject(); + return mBridgeData->FindCurrentObject(); } Component* BridgeAccessible::GetObjectInRelation(Accessible* obj, RelationType relationType) @@ -657,8 +677,9 @@ DBus::ValueOrError<Accessible*, uint8_t, Accessible*> BridgeAccessible::GetNavig auto accessible = FindSelf(); auto cType = static_cast<CoordinateType>(coordinateType); - x -= mData->mExtentsOffset.first; - y -= mData->mExtentsOffset.second; + const auto extentsOffset = mBridgeData->GetExtentsOffset(); + x -= extentsOffset.first; + y -= extentsOffset.second; LOG() << "GetNavigableAtPoint: " << x << ", " << y << " type: " << coordinateType; auto component = CalculateNavigableAccessibleAtPoint(accessible, {x, y}, cType, GET_NAVIGABLE_AT_POINT_MAX_RECURSION_DEPTH); @@ -1072,3 +1093,70 @@ DBus::ValueOrError<void> BridgeAccessible::SetListenPostRender(bool enabled) FindSelf()->SetListenPostRender(enabled); return {}; } + +void BridgeAccessible::RegisterDefaultLabel(Dali::Accessibility::Accessible* object) +{ + CompressDefaultLabels(); + + Dali::WeakHandle<Dali::Window> window = GetWindow(object); + if(!window.GetBaseHandle()) // true also if `object` is null + { + DALI_LOG_ERROR("Cannot register default label: object does not belong to any window"); + return; + } + + auto it = std::find_if(mDefaultLabels.begin(), mDefaultLabels.end(), [object](const DefaultLabelType& label) { + return object == label.second; + }); + + if(it == mDefaultLabels.end()) + { + 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 BridgeAccessible::UnregisterDefaultLabel(Dali::Accessibility::Accessible* object) +{ + CompressDefaultLabels(); + + mDefaultLabels.remove_if([object](const DefaultLabelType& label) { + return object == label.second; + }); +} + +Dali::Accessibility::Accessible* BridgeAccessible::GetDefaultLabel(Dali::Accessibility::Accessible* root) const +{ + 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; + }); + + return (it == mDefaultLabels.rend()) ? root : it->second; +} + +/** + * @brief Removes expired elements from the default label collection. + */ +void BridgeAccessible::CompressDefaultLabels() +{ + // Remove entries for objects which no longer exist + mDefaultLabels.remove_if([](const DefaultLabelType& label) { + return !label.first.GetBaseHandle(); // Check window's weak handle + // TODO: Once Accessible becomes a handle type, check its weak handle here as well + }); +} diff --git a/dali/internal/accessibility/bridge/bridge-accessible.h b/dali/internal/accessibility/bridge/bridge-accessible.h index 0f8533d99..29fe9b05a 100644 --- a/dali/internal/accessibility/bridge/bridge-accessible.h +++ b/dali/internal/accessibility/bridge/bridge-accessible.h @@ -34,25 +34,17 @@ */ class BridgeAccessible : public virtual BridgeBase { -protected: +public: /** * @brief Constructor. */ - BridgeAccessible(); - - /** - * @brief Registers Accessible functions to dbus interfaces. - */ - void RegisterInterfaces(); + explicit BridgeAccessible(std::shared_ptr<BridgeData> bridgeData); /** - * @brief Returns the Accessible object of the currently executed DBus method call. - * - * @return The Accessible object + * @brief Gets dbus interface description for Text type. */ - Dali::Accessibility::Accessible* FindSelf() const; + DBus::DBusInterfaceDescription GetInterfaces() override; -public: /** * @brief Enumeration for NeighborSearchMode. */ @@ -233,8 +225,25 @@ public: */ NodeInfoType GetNodeInfo(); + /** + * @copydoc Dali::Accessibility::Bridge::RegisterDefaultLabel() + */ + void RegisterDefaultLabel(Dali::Accessibility::Accessible* object); + + /** + * @copydoc Dali::Accessibility::Bridge::UnregisterDefaultLabel() + */ + void UnregisterDefaultLabel(Dali::Accessibility::Accessible* object); + private: /** + * @brief Returns the Accessible object of the currently executed DBus method call. + * + * @return The Accessible object + */ + Dali::Accessibility::Accessible* FindSelf() const; + + /** * @brief Calculates Neighbor candidate object in root node. * * The DFS algorithm in the method is implemented in iterative way. @@ -314,6 +323,20 @@ private: * @return The Component object */ Dali::Accessibility::Component* CalculateNavigableAccessibleAtPoint(Dali::Accessibility::Accessible* root, Dali::Accessibility::Point point, Dali::Accessibility::CoordinateType type, unsigned int maxRecursionDepth); + + /** + * @brief Gets the top-most object from the stack of "default label" sourcing objects. + * + * @param[in] root The root of the navigation context for which to retrieve the default label. + * + * @return The handler to accessibility object + */ + Accessible* GetDefaultLabel(Accessible* root) const; + + // We use a weak handle in order not to keep a window alive forever if someone forgets to UnregisterDefaultLabel() + using DefaultLabelType = std::pair<Dali::WeakHandle<Dali::Window>, Dali::Accessibility::Accessible*>; + using DefaultLabelsType = std::list<DefaultLabelType>; + DefaultLabelsType mDefaultLabels; }; #endif // DALI_INTERNAL_ACCESSIBILITY_BRIDGE_ACCESSIBLE_H diff --git a/dali/internal/accessibility/bridge/bridge-action.cpp b/dali/internal/accessibility/bridge/bridge-action.cpp index 90d1674c8..f54e081fc 100644 --- a/dali/internal/accessibility/bridge/bridge-action.cpp +++ b/dali/internal/accessibility/bridge/bridge-action.cpp @@ -20,7 +20,11 @@ using namespace Dali::Accessibility; -void BridgeAction::RegisterInterfaces() + +BridgeAction::BridgeAction(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeAction::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::ACTION)}; @@ -32,12 +36,13 @@ void BridgeAction::RegisterInterfaces() AddFunctionToInterface(desc, "GetKeyBinding", &BridgeAction::GetActionKeyBinding); AddFunctionToInterface(desc, "DoAction", &BridgeAction::DoAction); AddFunctionToInterface(desc, "DoActionName", &BridgeAction::DoActionName); - mDbusServer.addInterface("/", desc, true); + + return desc; } Action* BridgeAction::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::ACTION>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::ACTION>(); } DBus::ValueOrError<std::string> BridgeAction::GetActionName(int32_t index) diff --git a/dali/internal/accessibility/bridge/bridge-application.cpp b/dali/internal/accessibility/bridge/bridge-application.cpp index 9806bad2f..6bb7f8201 100644 --- a/dali/internal/accessibility/bridge/bridge-application.cpp +++ b/dali/internal/accessibility/bridge/bridge-application.cpp @@ -23,17 +23,21 @@ using namespace Dali::Accessibility; -void BridgeApplication::RegisterInterfaces() +BridgeApplication::BridgeApplication(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeApplication::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::APPLICATION)}; AddGetPropertyToInterface(desc, "ToolkitName", &BridgeApplication::GetToolkitName); AddGetPropertyToInterface(desc, "Version", &BridgeApplication::GetVersion); - mDbusServer.addInterface("/", desc, true); + + return desc; } Application* BridgeApplication::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::APPLICATION>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::APPLICATION>(); } std::string BridgeApplication::GetToolkitName() diff --git a/dali/internal/accessibility/bridge/bridge-base.cpp b/dali/internal/accessibility/bridge/bridge-base.cpp index a819f259a..e69de29bb 100644 --- a/dali/internal/accessibility/bridge/bridge-base.cpp +++ b/dali/internal/accessibility/bridge/bridge-base.cpp @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2021 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -// CLASS HEADER -#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() -{ - mApplication.mChildren.clear(); -} - -void BridgeBase::AddCoalescableMessage(CoalescableMessages kind, Dali::Accessibility::Accessible* obj, float delay, std::function<void()> functor) -{ - if(delay < 0) - { - delay = 0; - } - auto countdownBase = static_cast<unsigned int>(delay * 10); - - auto it = mCoalescableMessages.insert({{kind, obj}, {countdownBase, countdownBase, {}}}); - if(it.second) - { - functor(); - } - else - { - std::get<1>(it.first->second) = countdownBase; - std::get<2>(it.first->second) = std::move(functor); - } - - if(!tickTimer) - { - tickTimer = Dali::Timer::New(100); - tickTimer.TickSignal().Connect(this, &BridgeBase::TickCoalescableMessages); - } - - if(!tickTimer.IsRunning()) - { - tickTimer.Start(); - } -} - -bool BridgeBase::TickCoalescableMessages() -{ - for(auto it = mCoalescableMessages.begin(); it != mCoalescableMessages.end();) - { - auto& countdown = std::get<0>(it->second); - auto countdownBase = std::get<1>(it->second); - auto& functor = std::get<2>(it->second); - if(countdown) - { - --countdown; - } - else - { - if(functor) - { - functor(); - functor = {}; - countdown = countdownBase; - } - else - { - it = mCoalescableMessages.erase(it); - continue; - } - } - ++it; - } - 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() -{ - //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(); - - if(!addr) - { - DALI_LOG_ERROR("failed at call '%s': %s\n", dbusLocators::atspi::GET_ADDRESS, addr.getError().message.c_str()); - return ForceUpResult::FAILED; - } - - mConnectionPtr = DBusWrapper::Installed()->eldbus_address_connection_get_impl(std::get<0>(addr)); - mData->mBusName = DBus::getConnectionName(mConnectionPtr); - mDbusServer = {mConnectionPtr}; - - { - DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::CACHE)}; - AddFunctionToInterface(desc, "GetItems", &BridgeBase::GetItems); - mDbusServer.addInterface(AtspiDbusPathCache, 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(); - mRegistry = {}; - mDbusServer = {}; - mConnectionPtr = {}; -} - -const std::string& BridgeBase::GetBusName() const -{ - static std::string empty; - return mData ? mData->mBusName : empty; -} - -Accessible* BridgeBase::FindByPath(const std::string& name) const -{ - try - { - return Find(name); - } - catch(std::domain_error&) - { - return nullptr; - } -} - -void BridgeBase::AddTopLevelWindow(Accessible* windowAccessible) -{ - if(windowAccessible->GetInternalActor() == nullptr) - { - return; - } - - // Prevent adding the default window twice. - if(!mApplication.mChildren.empty() && - mApplication.mChildren[0]->GetInternalActor() == windowAccessible->GetInternalActor()) - { - return; - } - - // Adds Window to a list of Windows. - mApplication.mChildren.push_back(windowAccessible); - SetIsOnRootLevel(windowAccessible); -} - -void BridgeBase::RemoveTopLevelWindow(Accessible* windowAccessible) -{ - for(auto i = 0u; i < mApplication.mChildren.size(); ++i) - { - if(mApplication.mChildren[i] == windowAccessible) - { - mApplication.mChildren.erase(mApplication.mChildren.begin() + i); - break; - } - } -} - -void BridgeBase::CompressDefaultLabels() -{ - // Remove entries for objects which no longer exist - mDefaultLabels.remove_if([](const DefaultLabelType& label) { - return !label.first.GetBaseHandle(); // Check window's weak handle - // TODO: Once Accessible becomes a handle type, check its weak handle here as well - }); -} - -void BridgeBase::RegisterDefaultLabel(Accessible* object) -{ - CompressDefaultLabels(); - - Dali::WeakHandle<Dali::Window> window = GetWindow(object); - if(!window.GetBaseHandle()) // true also if `object` is null - { - DALI_LOG_ERROR("Cannot register default label: object does not belong to any window"); - return; - } - - auto it = std::find_if(mDefaultLabels.begin(), mDefaultLabels.end(), [object](const DefaultLabelType& label) { - return object == label.second; - }); - - if(it == mDefaultLabels.end()) - { - 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::UnregisterDefaultLabel(Accessible* object) -{ - CompressDefaultLabels(); - - mDefaultLabels.remove_if([object](const DefaultLabelType& label) { - return object == label.second; - }); -} - -Accessible* BridgeBase::GetDefaultLabel(Accessible* root) const -{ - 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; - }); - - return (it == mDefaultLabels.rend()) ? root : it->second; -} - -std::string BridgeBase::StripPrefix(const std::string& path) -{ - auto size = strlen(AtspiPath); - return path.substr(size + 1); -} - -Accessible* BridgeBase::Find(const std::string& path) const -{ - if(path == "root") - { - return &mApplication; - } - - void* accessible; - std::istringstream tmp{path}; - if(!(tmp >> accessible)) - { - throw std::domain_error{"invalid path '" + path + "'"}; - } - - auto it = mData->mKnownObjects.find(static_cast<Accessible*>(accessible)); - if(it == mData->mKnownObjects.end() || (*it)->IsHidden()) - { - throw std::domain_error{"unknown object '" + path + "'"}; - } - - return static_cast<Accessible*>(accessible); -} - -Accessible* BridgeBase::Find(const Address& ptr) const -{ - assert(ptr.GetBus() == mData->mBusName); - return Find(ptr.GetPath()); -} - -Accessible* BridgeBase::FindCurrentObject() const -{ - auto path = DBus::DBusServer::getCurrentObjectPath(); - auto size = strlen(AtspiPath); - if(path.size() <= size) - { - throw std::domain_error{"invalid path '" + path + "'"}; - } - if(path.substr(0, size) != AtspiPath) - { - throw std::domain_error{"invalid path '" + path + "'"}; - } - if(path[size] != '/') - { - throw std::domain_error{"invalid path '" + path + "'"}; - } - return Find(StripPrefix(path)); -} - -void BridgeBase::SetId(int id) -{ - this->mId = id; -} - -int BridgeBase::GetId() -{ - return this->mId; -} - -auto BridgeBase::GetItems() -> DBus::ValueOrError<std::vector<CacheElementType>> -{ - auto root = &mApplication; - - 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) - { - proc(item->GetChildAtIndex(i)); - } - }; - - return res; -} - -auto BridgeBase::CreateCacheElement(Accessible* item) -> CacheElementType -{ - if(!item) - { - return {}; - } - - auto root = &mApplication; - auto parent = item->GetParent(); - - std::vector<Address> children; - for(auto i = 0u; i < item->GetChildCount(); ++i) - { - children.emplace_back(item->GetChildAtIndex(i)->GetAddress()); - } - - return std::make_tuple( - item->GetAddress(), - root->GetAddress(), - parent ? parent->GetAddress() : Address{}, - children, - item->GetInterfacesAsStrings(), - item->GetName(), - item->GetRole(), - item->GetDescription(), - 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; -} diff --git a/dali/internal/accessibility/bridge/bridge-base.h b/dali/internal/accessibility/bridge/bridge-base.h index 9396f4222..6f2f3e1f1 100644 --- a/dali/internal/accessibility/bridge/bridge-base.h +++ b/dali/internal/accessibility/bridge/bridge-base.h @@ -35,343 +35,22 @@ #include <dali/devel-api/atspi-interfaces/socket.h> #include <dali/internal/accessibility/bridge/accessibility-common.h> -/** - * @brief The ApplicationAccessible class is to define Accessibility Application. - */ -class ApplicationAccessible : public virtual Dali::Accessibility::Accessible, - public virtual Dali::Accessibility::Application, - public virtual Dali::Accessibility::Collection, - public virtual Dali::Accessibility::Component, - public virtual Dali::Accessibility::Socket -{ -public: - Dali::Accessibility::ProxyAccessible mParent; - std::vector<Dali::Accessibility::Accessible*> mChildren; - std::string mName; - std::string mToolkitName{"dali"}; - bool mIsEmbedded{false}; - - std::string GetName() const override - { - return mName; - } - - std::string GetDescription() const override - { - return ""; - } - - Dali::Accessibility::Accessible* GetParent() override - { - return &mParent; - } - - size_t GetChildCount() const override - { - return mChildren.size(); - } - - std::vector<Dali::Accessibility::Accessible*> GetChildren() override - { - return mChildren; - } - - Dali::Accessibility::Accessible* GetChildAtIndex(size_t index) override - { - auto size = mChildren.size(); - if(index >= size) - { - throw std::domain_error{"invalid index " + std::to_string(index) + " for object with " + std::to_string(size) + " children"}; - } - return mChildren[index]; - } - - size_t GetIndexInParent() override - { - if(mIsEmbedded) - { - return 0u; - } - - throw std::domain_error{"can't call GetIndexInParent on application object"}; - } - - Dali::Accessibility::Role GetRole() const override - { - return Dali::Accessibility::Role::APPLICATION; - } - - Dali::Accessibility::States GetStates() override - { - Dali::Accessibility::States result; - - for(auto* child : mChildren) - { - result = result | child->GetStates(); - } - - // The Application object should never have the SENSITIVE state - result[Dali::Accessibility::State::SENSITIVE] = false; - - return result; - } - - Dali::Accessibility::Attributes GetAttributes() const override - { - return {}; - } - - /** - * @brief Gets the Accessible object from the window. - * - * @param[in] window The window to find - * @return Null if mChildren is empty, otherwise the Accessible object - * @note Currently, the default window would be returned when mChildren is not empty. - */ - Dali::Accessibility::Accessible* GetWindowAccessible(Dali::Window window) - { - if(mChildren.empty()) - { - return nullptr; - } - - Dali::Layer rootLayer = window.GetRootLayer(); - - // Find a child which is related to the window. - for(auto i = 0u; i < mChildren.size(); ++i) - { - if(rootLayer == mChildren[i]->GetInternalActor()) - { - return mChildren[i]; - } - } - - // If can't find its children, return the default window. - return mChildren[0]; - } - - bool DoGesture(const Dali::Accessibility::GestureInfo& gestureInfo) override - { - return false; - } - - std::vector<Dali::Accessibility::Relation> GetRelationSet() override - { - return {}; - } - - Dali::Actor GetInternalActor() override - { - return Dali::Actor{}; - } - - Dali::Accessibility::Address GetAddress() const override - { - return {"", "root"}; - } - - // Application - - std::string GetToolkitName() const override - { - return mToolkitName; - } - - std::string GetVersion() const override - { - return std::to_string(Dali::ADAPTOR_MAJOR_VERSION) + "." + std::to_string(Dali::ADAPTOR_MINOR_VERSION); - } - - // Socket - - Dali::Accessibility::Address Embed(Dali::Accessibility::Address plug) override - { - mIsEmbedded = true; - mParent.SetAddress(plug); - - return GetAddress(); - } - - void Unembed(Dali::Accessibility::Address plug) override - { - if(mParent.GetAddress() == plug) - { - mIsEmbedded = false; - mParent.SetAddress({}); - Dali::Accessibility::Bridge::GetCurrentBridge()->SetExtentsOffset(0, 0); - } - } - - void SetOffset(std::int32_t x, std::int32_t y) override - { - if(!mIsEmbedded) - { - return; - } - - Dali::Accessibility::Bridge::GetCurrentBridge()->SetExtentsOffset(x, y); - } - - // Component - - Dali::Rect<> GetExtents(Dali::Accessibility::CoordinateType type) const override - { - using limits = std::numeric_limits<float>; - - float minX = limits::max(); - float minY = limits::max(); - float maxX = limits::min(); - float maxY = limits::min(); - - for(Dali::Accessibility::Accessible* child : mChildren) - { - auto* component = Dali::Accessibility::Component::DownCast(child); - if(!component) - { - continue; - } - - auto extents = component->GetExtents(type); - - minX = std::min(minX, extents.x); - minY = std::min(minY, extents.y); - maxX = std::max(maxX, extents.x + extents.width); - maxY = std::max(maxY, extents.y + extents.height); - } - - return {minX, minY, maxX - minX, maxY - minY}; - } - - Dali::Accessibility::ComponentLayer GetLayer() const override - { - return Dali::Accessibility::ComponentLayer::WINDOW; - } - - std::int16_t GetMdiZOrder() const override - { - return 0; - } - - bool GrabFocus() override - { - return false; - } - - double GetAlpha() const override - { - return 0.0; - } - - bool GrabHighlight() override - { - return false; - } - - bool ClearHighlight() override - { - return false; - } - - bool IsScrollable() const override - { - return false; - } -}; - -/** - * @brief Enumeration for CoalescableMessages. - */ -enum class CoalescableMessages -{ - BOUNDS_CHANGED, ///< Bounds changed - SET_OFFSET, ///< Set offset - POST_RENDER, ///< Post render -}; - -// Custom specialization of std::hash -namespace std -{ -template<> -struct hash<std::pair<CoalescableMessages, Dali::Accessibility::Accessible*>> -{ - size_t operator()(std::pair<CoalescableMessages, Dali::Accessibility::Accessible*> value) const - { - return (static_cast<size_t>(value.first) * 131) ^ reinterpret_cast<size_t>(value.second); - } -}; -} // namespace std /** * @brief The BridgeBase class is basic class for Bridge functions. */ -class BridgeBase : public Dali::Accessibility::Bridge, public Dali::ConnectionTracker +class BridgeBase { - std::unordered_map<std::pair<CoalescableMessages, Dali::Accessibility::Accessible*>, std::tuple<unsigned int, unsigned int, std::function<void()>>> mCoalescableMessages; - - /** - * @brief Removes all CoalescableMessages using Tick signal. - * - * @return False if mCoalescableMessages is empty, otherwise true. - */ - bool TickCoalescableMessages(); - public: - /** - * @brief Adds CoalescableMessages, Accessible, and delay time to mCoalescableMessages. - * - * @param[in] kind CoalescableMessages enum value - * @param[in] obj Accessible object - * @param[in] delay The delay time - * @param[in] functor The function to be called // NEED TO UPDATE! - */ - void AddCoalescableMessage(CoalescableMessages kind, Dali::Accessibility::Accessible* obj, float delay, std::function<void()> functor); - - /** - * @brief Callback when the visibility of the window is changed. - * - * @param[in] window The window to be changed - * @param[in] visible The visibility of the window - */ - void OnWindowVisibilityChanged(Dali::Window window, bool visible); - - /** - * @copydoc Dali::Accessibility::Bridge::GetBusName() - */ - const std::string& GetBusName() const override; - - /** - * @copydoc Dali::Accessibility::Bridge::AddTopLevelWindow() - */ - void AddTopLevelWindow(Dali::Accessibility::Accessible* windowAccessible) override; - - /** - * @copydoc Dali::Accessibility::Bridge::RemoveTopLevelWindow() - */ - void RemoveTopLevelWindow(Dali::Accessibility::Accessible* windowAccessible) override; - - /** - * @copydoc Dali::Accessibility::Bridge::RegisterDefaultLabel() - */ - void RegisterDefaultLabel(Dali::Accessibility::Accessible* object) override; - - /** - * @copydoc Dali::Accessibility::Bridge::UnregisterDefaultLabel() - */ - void UnregisterDefaultLabel(Dali::Accessibility::Accessible* object) override; + explicit BridgeBase(std::shared_ptr<BridgeData> bridgeData) + : mBridgeData{std::move(bridgeData)} {} /** - * @copydoc Dali::Accessibility::Bridge::GetDefaultLabel() + * @brief Gets dbus interface description for given type. */ - Dali::Accessibility::Accessible* GetDefaultLabel(Dali::Accessibility::Accessible* root) const override; - - /** - * @copydoc Dali::Accessibility::Bridge::GetApplication() - */ - Dali::Accessibility::Accessible* GetApplication() const override - { - return &mApplication; - } + virtual DBus::DBusInterfaceDescription GetInterfaces() const = 0; +protected: /** * @brief Adds function to dbus interface. */ @@ -509,190 +188,7 @@ public: }); } - /** - * @brief Gets the string of the path excluding the specified prefix. - * - * @param path The path to get - * @return The string stripped of the specific prefix - */ - static std::string StripPrefix(const std::string& path); - - /** - * @brief Finds the Accessible object according to the path. - * - * @param[in] path The path for Accessible object - * @return The Accessible object corresponding to the path - */ - Dali::Accessibility::Accessible* Find(const std::string& path) const; - - /** - * @brief Finds the Accessible object with the given address. - * - * @param[in] ptr The unique Address of the object - * @return The Accessible object corresponding to the path - */ - Dali::Accessibility::Accessible* Find(const Dali::Accessibility::Address& ptr) const; - - /** - * @brief Returns the target object of the currently executed DBus method call. - * - * @return The Accessible object - * @note When a DBus method is called on some object, this target object (`currentObject`) is temporarily saved by the bridge, - * because DBus handles the invocation target separately from the method arguments. - * We then use the saved object inside the 'glue' method (e.g. BridgeValue::GetMinimum) - * to call the equivalent method on the respective C++ object (this could be ScrollBar::AccessibleImpl::GetMinimum in the example given). - */ - Dali::Accessibility::Accessible* FindCurrentObject() const; - - /** - * @brief Returns the target object of the currently executed DBus method call. - * - * This method tries to downcast the return value of FindCurrentObject() to the requested type, - * issuing an error reply to the DBus caller if the requested type is not implemented. Whether - * a given type is implemented is decided based on the return value of Accessible::GetInterfaces() - * for the current object. - * - * @tparam I The requested AT-SPI interface - * @return The Accessible object (cast to a more derived type) - * - * @see FindCurrentObject() - * @see Dali::Accessibility::AtspiInterface - * @see Dali::Accessibility::AtspiInterfaceType - * @see Dali::Accessibility::Accessible::GetInterfaces() - */ - template<Dali::Accessibility::AtspiInterface I> - auto* FindCurrentObjectWithInterface() const - { - using Type = Dali::Accessibility::AtspiInterfaceType<I>; - - Type* result; - auto* currentObject = FindCurrentObject(); - DALI_ASSERT_DEBUG(currentObject); // FindCurrentObject() throws domain_error - - if(!(result = Dali::Accessibility::Accessible::DownCast<I>(currentObject))) - { - std::stringstream s; - - s << "Object " << currentObject->GetAddress().ToString(); - s << " does not implement "; - s << Dali::Accessibility::Accessible::GetInterfaceName(I); - - throw std::domain_error{s.str()}; - } - - return result; - } - - /** - * @copydoc Dali::Accessibility::Bridge::FindByPath() - */ - Dali::Accessibility::Accessible* FindByPath(const std::string& name) const override; - - /** - * @copydoc Dali::Accessibility::Bridge::SetApplicationName() - */ - void SetApplicationName(std::string name) override - { - mApplication.mName = std::move(name); - } - - /** - * @copydoc Dali::Accessibility::Bridge::SetToolkitName() - */ - void SetToolkitName(std::string_view toolkitName) override - { - mApplication.mToolkitName = std::string{toolkitName}; - } - -protected: - // We use a weak handle in order not to keep a window alive forever if someone forgets to UnregisterDefaultLabel() - using DefaultLabelType = std::pair<Dali::WeakHandle<Dali::Window>, Dali::Accessibility::Accessible*>; - using DefaultLabelsType = std::list<DefaultLabelType>; - - mutable ApplicationAccessible mApplication; - DefaultLabelsType mDefaultLabels; - bool mIsScreenReaderSuppressed = false; - -private: - /** - * @brief Sets an ID. - * @param[in] id An ID (integer value) - */ - void SetId(int id); - - /** - * @brief Gets the ID. - * @return The ID to be set - */ - int GetId(); - - /** - * @brief Update registered events. - */ - void UpdateRegisteredEvents(); - - using CacheElementType = std::tuple< - Dali::Accessibility::Address, - Dali::Accessibility::Address, - Dali::Accessibility::Address, - std::vector<Dali::Accessibility::Address>, - std::vector<std::string>, - std::string, - Dali::Accessibility::Role, - std::string, - std::array<uint32_t, 2>>; - - /** - * @brief Gets Items // NEED TO UPDATE! - * - * @return - */ - DBus::ValueOrError<std::vector<CacheElementType>> GetItems(); - - /** - * @brief Creates CacheElement. - * - * CreateCacheElement method works for GetItems which is a part of ATSPI protocol. - * ATSPI client library (libatspi from at-spi2-core) depending on cacheing policy configuration uses GetItems - * to pre-load entire accessible tree from application to its own cache in single dbus call. - * Otherwise the particular nodes in a tree are cached lazily when client library tries to access them. - * @param item Accessible to get information - * @return The elements to be cached - */ - CacheElementType CreateCacheElement(Dali::Accessibility::Accessible* item); - - /** - * @brief Removes expired elements from the default label collection. - */ - void CompressDefaultLabels(); - - /** - * @brief Gets the window to which this accessible belongs (or an empty handle). - * - * @param accessible The accessible - * @return The window - */ - static Dali::WeakHandle<Dali::Window> GetWindow(Dali::Accessibility::Accessible* accessible); - -protected: - BridgeBase(); - virtual ~BridgeBase(); - - /** - * @copydoc Dali::Accessibility::Bridge::ForceUp() - */ - ForceUpResult ForceUp() override; - - /** - * @copydoc Dali::Accessibility::Bridge::ForceDown() - */ - void ForceDown() override; - - DBus::DBusServer mDbusServer; - DBusWrapper::ConnectionPtr mConnectionPtr; - int mId = 0; - DBus::DBusClient mRegistry; - bool IsBoundsChangedEventAllowed{false}; + const std::shared_ptr<BridgeData> mBridgeData; }; #endif // DALI_INTERNAL_ACCESSIBILITY_BRIDGE_BASE_H diff --git a/dali/internal/accessibility/bridge/bridge-collection.cpp b/dali/internal/accessibility/bridge/bridge-collection.cpp index 20111301d..08d40469b 100644 --- a/dali/internal/accessibility/bridge/bridge-collection.cpp +++ b/dali/internal/accessibility/bridge/bridge-collection.cpp @@ -43,18 +43,21 @@ enum class AtspiCollection }; } // anonymous namespace -void BridgeCollection::RegisterInterfaces() +BridgeCollection::BridgeCollection(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeCollection::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::COLLECTION)}; AddFunctionToInterface(desc, "GetMatches", &BridgeCollection::GetMatches); AddFunctionToInterface(desc, "GetMatchesInMatches", &BridgeCollection::GetMatchesInMatches); - mDbusServer.addInterface("/", desc, true); + return desc; } Collection* BridgeCollection::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::COLLECTION>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::COLLECTION>(); } /** @@ -500,7 +503,7 @@ void BridgeCollection::VisitNodes(Accessible* obj, std::vector<Accessible*>& res DBus::ValueOrError<std::vector<Accessible*> > BridgeCollection::GetMatches(MatchRule rule, uint32_t sortBy, int32_t count, bool traverse) { std::vector<Accessible*> res; - auto self = BridgeBase::FindCurrentObject(); + auto self = mBridgeData->FindCurrentObject(); auto matcher = Comparer{&rule}; VisitNodes(self, res, matcher, count); @@ -532,7 +535,7 @@ DBus::ValueOrError<std::vector<Accessible*> > BridgeCollection::GetMatchesInMatc std::vector<Accessible*> res; std::vector<Accessible*> firstRes; std::vector<Accessible*> secondRes; - auto self = BridgeBase::FindCurrentObject(); + auto self = mBridgeData->FindCurrentObject(); auto firstMatcher = Comparer{&firstRule}; auto secondMatcher = Comparer{&secondRule}; VisitNodes(self, firstRes, firstMatcher, firstCount); diff --git a/dali/internal/accessibility/bridge/bridge-collection.h b/dali/internal/accessibility/bridge/bridge-collection.h index 32ea9c154..441dd6865 100644 --- a/dali/internal/accessibility/bridge/bridge-collection.h +++ b/dali/internal/accessibility/bridge/bridge-collection.h @@ -57,14 +57,6 @@ private: */ static void VisitNodes(Dali::Accessibility::Accessible* obj, std::vector<Dali::Accessibility::Accessible*>& result, Comparer& comparer, size_t maxCount); -protected: - BridgeCollection() = default; - - /** - * @brief Registers Collection functions to dbus interfaces. - */ - void RegisterInterfaces(); - /** * @brief Returns the Collection object of the currently executed DBus method call. * @@ -73,6 +65,13 @@ protected: Dali::Accessibility::Collection* FindSelf() const; public: + explicit BridgeCollection(std::shared_ptr<BridgeData> bridgeData); + + /** + * @brief Gets dbus interface description for Collection type. + */ + DBus::DBusInterfaceDescription GetInterfaces() override; + /** * MatchRule type is a tuple that only carries data of de-serialized parameter from BridgeCollection::GetMatches dbus method. */ diff --git a/dali/internal/accessibility/bridge/bridge-component.cpp b/dali/internal/accessibility/bridge/bridge-component.cpp index ef0ff0fb4..0cc698bf7 100644 --- a/dali/internal/accessibility/bridge/bridge-component.cpp +++ b/dali/internal/accessibility/bridge/bridge-component.cpp @@ -22,11 +22,10 @@ using namespace Dali::Accessibility; -BridgeComponent::BridgeComponent() -{ -} +BridgeComponent::BridgeComponent(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} -void BridgeComponent::RegisterInterfaces() +DBus::DBusInterfaceDescription BridgeComponent::GetInterfaces() const { // The second arguments below are the names (or signatures) of DBus methods. // Screen Reader will call the methods with the exact names as specified in the AT-SPI Component interface: @@ -44,12 +43,12 @@ void BridgeComponent::RegisterInterfaces() AddFunctionToInterface(desc, "GrabHighlight", &BridgeComponent::GrabHighlight); AddFunctionToInterface(desc, "GrabFocus", &BridgeComponent::GrabFocus); AddFunctionToInterface(desc, "ClearHighlight", &BridgeComponent::ClearHighlight); - mDbusServer.addInterface("/", desc, true); + return desc; } Component* BridgeComponent::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::COMPONENT>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::COMPONENT>(); } DBus::ValueOrError<bool> BridgeComponent::IsAccessibleContainingPoint(int32_t x, int32_t y, uint32_t coordType) @@ -66,8 +65,9 @@ DBus::ValueOrError<std::tuple<int32_t, int32_t, int32_t, int32_t> > BridgeCompon { auto rect = FindSelf()->GetExtents(static_cast<CoordinateType>(coordType)); - rect.x += mData->mExtentsOffset.first; - rect.y += mData->mExtentsOffset.second; + const auto extentsOffset = mBridgeData->GetExtentsOffset(); + rect.x += extentsOffset.first; + rect.y += extentsOffset.second; return std::tuple<int32_t, int32_t, int32_t, int32_t>{rect.x, rect.y, rect.width, rect.height}; } @@ -76,8 +76,9 @@ DBus::ValueOrError<int32_t, int32_t> BridgeComponent::GetPosition(uint32_t coord { auto rect = FindSelf()->GetExtents(static_cast<CoordinateType>(coordType)); - rect.x += mData->mExtentsOffset.first; - rect.y += mData->mExtentsOffset.second; + const auto extentsOffset = mBridgeData->GetExtentsOffset(); + rect.x += extentsOffset.first; + rect.y += extentsOffset.second; return {static_cast<int32_t>(rect.x), static_cast<int32_t>(rect.y)}; } diff --git a/dali/internal/accessibility/bridge/bridge-component.h b/dali/internal/accessibility/bridge/bridge-component.h index ec24f422a..78a045922 100644 --- a/dali/internal/accessibility/bridge/bridge-component.h +++ b/dali/internal/accessibility/bridge/bridge-component.h @@ -34,17 +34,6 @@ */ class BridgeComponent : public virtual BridgeBase { -protected: - /** - * @brief Constructor. - */ - BridgeComponent(); - - /** - * @brief Registers Component functions to dbus interfaces. - */ - void RegisterInterfaces(); - /** * @brief Returns the Component object of the currently executed DBus method call. * @@ -53,6 +42,13 @@ protected: Dali::Accessibility::Component* FindSelf() const; public: + explicit BridgeComponent(std::shared_ptr<BridgeData> bridgeData); + + /** + * @brief Gets dbus interface description for Component type. + */ + DBus::DBusInterfaceDescription GetInterfaces() override; + /** * @copydoc Dali::Accessibility::Component::IsAccessibleContainingPoint() */ diff --git a/dali/internal/accessibility/bridge/bridge-data.cpp b/dali/internal/accessibility/bridge/bridge-data.cpp new file mode 100644 index 000000000..a8bf12cda --- /dev/null +++ b/dali/internal/accessibility/bridge/bridge-data.cpp @@ -0,0 +1,93 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// EXTERNAL INCLUDES +#include <string> + +// INTERNAL INCLUDES +#include <dali/internal/accessibility/bridge/bridge-data.h> +#include <dali/internal/accessibility/bridge/dbus/dbus.h> + +using namespace Dali::Accessibility; + +std::string BridgeData::StripPrefix(const std::string& path) +{ + auto size = strlen(AtspiPath); + return path.substr(size + 1); +} + +Accessible* BridgeData::Find(const std::string& path) const +{ + if(path == "root") + { + return &mApplication; + } + + void* accessible; + std::istringstream tmp{path}; + if(!(tmp >> accessible)) + { + throw std::domain_error{"invalid path '" + path + "'"}; + } + + auto it = mKnownObjects.find(static_cast<Accessible*>(accessible)); + if(it == mKnownObjects.end() || (*it)->IsHidden()) + { + throw std::domain_error{"unknown object '" + path + "'"}; + } + + return static_cast<Accessible*>(accessible); +} + +Accessible* BridgeData::FindCurrentObject() const +{ + auto path = DBus::DBusServer::getCurrentObjectPath(); + auto size = strlen(AtspiPath); + if(path.size() <= size) + { + throw std::domain_error{"invalid path '" + path + "'"}; + } + if(path.substr(0, size) != AtspiPath) + { + throw std::domain_error{"invalid path '" + path + "'"}; + } + if(path[size] != '/') + { + throw std::domain_error{"invalid path '" + path + "'"}; + } + return Find(StripPrefix(path)); +} + +void BridgeData::AddKnownObject(const Accessible* object) +{ + mKnownObjects.insert(object); +} + +void BridgeData::RemoveKnownObject(const Accessible* object) +{ + mKnownObjects.erase(object); +} + +void BridgeData::SetExtentsOffset(int32_t x, int32_t y) +{ + mExtentsOffset = {x, y}; +} + +OffsetType BridgeData::GetExtentsOffset() const +{ + return mExtentsOffset; +} diff --git a/dali/internal/accessibility/bridge/bridge-data.h b/dali/internal/accessibility/bridge/bridge-data.h new file mode 100644 index 000000000..8627d665f --- /dev/null +++ b/dali/internal/accessibility/bridge/bridge-data.h @@ -0,0 +1,122 @@ +#ifndef DALI_INTERNAL_ACCESSIBILITY_ACCESSIBLE_OBJECTS_HOLDER_H +#define DALI_INTERNAL_ACCESSIBILITY_ACCESSIBLE_OBJECTS_HOLDER_H + +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// INTERNAL INCLUDES +#include <dali/devel-api/atspi-interfaces/accessible.h> + +/** + * @brief The BridgeText class is to correspond with Dali::Accessibility::Text. + */ +class BridgeData +{ +public: + /** + * @brief Gets the string of the path excluding the specified prefix. + * + * @param path The path to get + * @return The string stripped of the specific prefix + */ + static std::string StripPrefix(const std::string& path); + + /** + * @brief Finds the Accessible object according to the path. + * + * @param[in] path The path for Accessible object + * @return The Accessible object corresponding to the path + */ + Dali::Accessibility::Accessible* Find(const std::string& path) const; + + /** + * @brief Returns the target object of the currently executed DBus method call. + * + * @return The Accessible object + * @note When a DBus method is called on some object, this target object (`currentObject`) is temporarily saved by the bridge, + * because DBus handles the invocation target separately from the method arguments. + * We then use the saved object inside the 'glue' method (e.g. BridgeValue::GetMinimum) + * to call the equivalent method on the respective C++ object (this could be ScrollBar::AccessibleImpl::GetMinimum in the example given). + */ + Dali::Accessibility::Accessible* FindCurrentObject() const; + + /** + * @brief Returns the target object of the currently executed DBus method call. + * + * This method tries to downcast the return value of FindCurrentObject() to the requested type, + * issuing an error reply to the DBus caller if the requested type is not implemented. Whether + * a given type is implemented is decided based on the return value of Accessible::GetInterfaces() + * for the current object. + * + * @tparam I The requested AT-SPI interface + * @return The Accessible object (cast to a more derived type) + * + * @see FindCurrentObject() + * @see Dali::Accessibility::AtspiInterface + * @see Dali::Accessibility::AtspiInterfaceType + * @see Dali::Accessibility::Accessible::GetInterfaces() + */ + template<Dali::Accessibility::AtspiInterface I> + auto* FindCurrentObjectWithInterface() const + { + using Type = Dali::Accessibility::AtspiInterfaceType<I>; + + Type* result; + auto* currentObject = FindCurrentObject(); + DALI_ASSERT_DEBUG(currentObject); // FindCurrentObject() throws domain_error + + if(!(result = Dali::Accessibility::Accessible::DownCast<I>(currentObject))) + { + std::stringstream s; + + s << "Object " << currentObject->GetAddress().ToString(); + s << " does not implement "; + s << Dali::Accessibility::Accessible::GetInterfaceName(I); + + throw std::domain_error{s.str()}; + } + + return result; + } + + void AddKnownObject(const Accessible* object); + + void RemoveKnownObject(const Accessible* object); + + /** + * @brief Sets the global extents offset. + * + * This offset will be added during serialization of GetExtents() return value to D-Bus. + * Local calls to GetExtents() are unaffected. + * + * @param[in] x Horizontal offset + * @param[in] y Vertical offset + * + * @see Dali::Accessibility::Bridge::SetSocketOffset() + * @see Dali::Accessibility::Component::GetExtents() + */ + using OffsetType = pair::<int32_t, int32_t>; + void SetExtentsOffset(int32_t x, int32_t y); + + OffsetType GetExtentsOffset() const; + +private: + std::unordered_set<const Accessible*> mKnownObjects; + OffsetType mExtentsOffset{0, 0}; +}; + +#endif // DALI_INTERNAL_ACCESSIBILITY_ACCESSIBLE_OBJECTS_HOLDER_H diff --git a/dali/internal/accessibility/bridge/bridge-editable-text.cpp b/dali/internal/accessibility/bridge/bridge-editable-text.cpp index 7053c7705..d2f5f3ad4 100644 --- a/dali/internal/accessibility/bridge/bridge-editable-text.cpp +++ b/dali/internal/accessibility/bridge/bridge-editable-text.cpp @@ -23,7 +23,10 @@ using namespace Dali::Accessibility; -void BridgeEditableText::RegisterInterfaces() +BridgeEditableText::BridgeEditableText(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeEditableText::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::EDITABLE_TEXT)}; AddFunctionToInterface(desc, "CopyText", &BridgeEditableText::CopyText); @@ -32,12 +35,13 @@ void BridgeEditableText::RegisterInterfaces() AddFunctionToInterface(desc, "InsertText", &BridgeEditableText::InsertText); AddFunctionToInterface(desc, "PasteText", &BridgeEditableText::PasteText); AddFunctionToInterface(desc, "SetTextContents", &BridgeEditableText::SetTextContents); - mDbusServer.addInterface("/", desc, true); + + return desc; } EditableText* BridgeEditableText::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::EDITABLE_TEXT>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::EDITABLE_TEXT>(); } DBus::ValueOrError<bool> BridgeEditableText::CopyText(int32_t startPosition, int32_t endPosition) diff --git a/dali/internal/accessibility/bridge/bridge-hyperlink.cpp b/dali/internal/accessibility/bridge/bridge-hyperlink.cpp index 992d2cf5d..1fa7f2ed2 100644 --- a/dali/internal/accessibility/bridge/bridge-hyperlink.cpp +++ b/dali/internal/accessibility/bridge/bridge-hyperlink.cpp @@ -23,7 +23,10 @@ using namespace Dali::Accessibility; -void BridgeHyperlink::RegisterInterfaces() +BridgeHyperlink::BridgeHyperlink(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeHyperlink::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::HYPERLINK)}; AddGetPropertyToInterface(desc, "NAnchors", &BridgeHyperlink::GetAnchorCount); @@ -32,12 +35,13 @@ void BridgeHyperlink::RegisterInterfaces() AddFunctionToInterface(desc, "GetObject", &BridgeHyperlink::GetAnchorAccessible); AddFunctionToInterface(desc, "GetURI", &BridgeHyperlink::GetAnchorUri); AddFunctionToInterface(desc, "IsValid", &BridgeHyperlink::IsValid); - mDbusServer.addInterface("/", desc, true); + + return desc; } Hyperlink* BridgeHyperlink::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::HYPERLINK>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::HYPERLINK>(); } DBus::ValueOrError<int32_t> BridgeHyperlink::GetEndIndex() diff --git a/dali/internal/accessibility/bridge/bridge-hypertext.cpp b/dali/internal/accessibility/bridge/bridge-hypertext.cpp index b5f3b3b46..c89c1c504 100644 --- a/dali/internal/accessibility/bridge/bridge-hypertext.cpp +++ b/dali/internal/accessibility/bridge/bridge-hypertext.cpp @@ -23,18 +23,22 @@ using namespace Dali::Accessibility; -void BridgeHypertext::RegisterInterfaces() +BridgeHypertext::BridgeHypertext(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeHypertext::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::HYPERTEXT)}; AddFunctionToInterface(desc, "GetNLinks", &BridgeHypertext::GetLinkCount); AddFunctionToInterface(desc, "GetLink", &BridgeHypertext::GetLink); AddFunctionToInterface(desc, "GetLinkIndex", &BridgeHypertext::GetLinkIndex); - mDbusServer.addInterface("/", desc, true); + + return desc; } Hypertext* BridgeHypertext::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::HYPERTEXT>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::HYPERTEXT>(); } DBus::ValueOrError<int32_t> BridgeHypertext::GetLinkCount() diff --git a/dali/internal/accessibility/bridge/bridge-impl.cpp b/dali/internal/accessibility/bridge/bridge-impl.cpp index 5dba57950..da55bdebc 100644 --- a/dali/internal/accessibility/bridge/bridge-impl.cpp +++ b/dali/internal/accessibility/bridge/bridge-impl.cpp @@ -43,6 +43,7 @@ #include <dali/internal/accessibility/bridge/bridge-text.h> #include <dali/internal/accessibility/bridge/bridge-value.h> #include <dali/internal/accessibility/bridge/dummy/dummy-atspi.h> +#include <dali/internal/accessibility/bridge/accessible-objects-holder.h> #include <dali/internal/adaptor/common/adaptor-impl.h> #include <dali/internal/system/common/environment-variables.h> @@ -58,26 +59,300 @@ std::string GetAccessiblePath(Accessible* accessible) return address ? ATSPI_PREFIX_PATH + address.GetPath() : ATSPI_NULL_PATH; } +Dali::Timer tickTimer; // TODO: 1) move this to member variable; 2) should Reset + +/** + * @brief Enumeration for CoalescableMessages. + */ +enum class CoalescableMessages +{ + BOUNDS_CHANGED, ///< Bounds changed + SET_OFFSET, ///< Set offset + POST_RENDER, ///< Post render +}; + +using CoalescableMessagesKey = std::pair<CoalescableMessages, Dali::Accessibility::Accessible*>; +using CoalescableMessagesEntry = std::tuple<unsigned int, unsigned int, std::function<void()>>; +using CoalescableMessagesMap = std::unordered_map<CoalescableMessagesKey, CoalescableMessagesEntry>; + +using CacheElementType = std::tuple< + Dali::Accessibility::Address, + Dali::Accessibility::Address, + Dali::Accessibility::Address, + std::vector<Dali::Accessibility::Address>, + std::vector<std::string>, + std::string, + Dali::Accessibility::Role, + std::string, + std::array<uint32_t, 2>>; + } // unnamed namespace +// Custom specialization of std::hash +namespace std +{ +template<> +struct hash<std::pair<CoalescableMessages, Dali::Accessibility::Accessible*>> +{ + size_t operator()(std::pair<CoalescableMessages, Dali::Accessibility::Accessible*> value) const + { + return (static_cast<size_t>(value.first) * 131) ^ reinterpret_cast<size_t>(value.second); + } +}; +} // namespace std + +/** + * @brief The ApplicationAccessible class is to define Accessibility Application. + */ +class ApplicationAccessible : public virtual Dali::Accessibility::Accessible, + public virtual Dali::Accessibility::Application, + public virtual Dali::Accessibility::Collection, + public virtual Dali::Accessibility::Component, + public virtual Dali::Accessibility::Socket +{ + std::shared_ptr<BridgeData> mBridgeData; +public: + Dali::Accessibility::ProxyAccessible mParent; + std::vector<Dali::Accessibility::Accessible*> mChildren; + std::string mName; + std::string mToolkitName{"dali"}; + bool mIsEmbedded{false}; + + ApplicationAccessible(std::shared_ptr<BridgeData> bridgeData) + : mBridgeData{std::move(bridgeData)} {} + + std::string GetName() const override + { + return mName; + } + + std::string GetDescription() const override + { + return ""; + } + + Dali::Accessibility::Accessible* GetParent() override + { + return &mParent; + } + + size_t GetChildCount() const override + { + return mChildren.size(); + } + + std::vector<Dali::Accessibility::Accessible*> GetChildren() override + { + return mChildren; + } + + Dali::Accessibility::Accessible* GetChildAtIndex(size_t index) override + { + auto size = mChildren.size(); + if(index >= size) + { + throw std::domain_error{"invalid index " + std::to_string(index) + " for object with " + std::to_string(size) + " children"}; + } + return mChildren[index]; + } + + size_t GetIndexInParent() override + { + if(mIsEmbedded) + { + return 0u; + } + + throw std::domain_error{"can't call GetIndexInParent on application object"}; + } + + Dali::Accessibility::Role GetRole() const override + { + return Dali::Accessibility::Role::APPLICATION; + } + + Dali::Accessibility::States GetStates() override + { + Dali::Accessibility::States result; + + for(auto* child : mChildren) + { + result = result | child->GetStates(); + } + + // The Application object should never have the SENSITIVE state + result[Dali::Accessibility::State::SENSITIVE] = false; + + return result; + } + + Dali::Accessibility::Attributes GetAttributes() const override + { + return {}; + } + + /** + * @brief Gets the Accessible object from the window. + * + * @param[in] window The window to find + * @return Null if mChildren is empty, otherwise the Accessible object + * @note Currently, the default window would be returned when mChildren is not empty. + */ + Dali::Accessibility::Accessible* GetWindowAccessible(Dali::Window window) + { + if(mChildren.empty()) + { + return nullptr; + } + + Dali::Layer rootLayer = window.GetRootLayer(); + + // Find a child which is related to the window. + for(auto i = 0u; i < mChildren.size(); ++i) + { + if(rootLayer == mChildren[i]->GetInternalActor()) + { + return mChildren[i]; + } + } + + // If can't find its children, return the default window. + return mChildren[0]; + } + + bool DoGesture(const Dali::Accessibility::GestureInfo& gestureInfo) override + { + return false; + } + + std::vector<Dali::Accessibility::Relation> GetRelationSet() override + { + return {}; + } + + Dali::Actor GetInternalActor() override + { + return Dali::Actor{}; + } + + Dali::Accessibility::Address GetAddress() const override + { + return {"", "root"}; + } + + // Application + + std::string GetToolkitName() const override + { + return mToolkitName; + } + + std::string GetVersion() const override + { + return std::to_string(Dali::ADAPTOR_MAJOR_VERSION) + "." + std::to_string(Dali::ADAPTOR_MINOR_VERSION); + } + + // Socket + + Dali::Accessibility::Address Embed(Dali::Accessibility::Address plug) override + { + mIsEmbedded = true; + mParent.SetAddress(plug); + + return GetAddress(); + } + + void Unembed(Dali::Accessibility::Address plug) override + { + if(mParent.GetAddress() == plug) + { + mIsEmbedded = false; + mParent.SetAddress({}); + mBridgeData->SetExtentsOffset(0, 0); + } + } + + void SetOffset(std::int32_t x, std::int32_t y) override + { + if(!mIsEmbedded) + { + return; + } + + mBridgeData->SetExtentsOffset(x, y); + } + + // Component + + Dali::Rect<> GetExtents(Dali::Accessibility::CoordinateType type) const override + { + using limits = std::numeric_limits<float>; + + float minX = limits::max(); + float minY = limits::max(); + float maxX = limits::min(); + float maxY = limits::min(); + + for(Dali::Accessibility::Accessible* child : mChildren) + { + auto* component = Dali::Accessibility::Component::DownCast(child); + if(!component) + { + continue; + } + + auto extents = component->GetExtents(type); + + minX = std::min(minX, extents.x); + minY = std::min(minY, extents.y); + maxX = std::max(maxX, extents.x + extents.width); + maxY = std::max(maxY, extents.y + extents.height); + } + + return {minX, minY, maxX - minX, maxY - minY}; + } + + Dali::Accessibility::ComponentLayer GetLayer() const override + { + return Dali::Accessibility::ComponentLayer::WINDOW; + } + + std::int16_t GetMdiZOrder() const override + { + return 0; + } + + bool GrabFocus() override + { + return false; + } + + double GetAlpha() const override + { + return 0.0; + } + + bool GrabHighlight() override + { + return false; + } + + bool ClearHighlight() override + { + return false; + } + + bool IsScrollable() const override + { + return false; + } +}; + /** * @brief The BridgeImpl class is to implement some Bridge functions. */ -class BridgeImpl : public virtual BridgeBase, - public BridgeAccessible, - public BridgeComponent, - public BridgeCollection, - public BridgeAction, - public BridgeValue, - public BridgeText, - public BridgeEditableText, - public BridgeSelection, - public BridgeApplication, - public BridgeHypertext, - public BridgeHyperlink, - public BridgeSocket, - public BridgeTable, - public BridgeTableCell +class BridgeImpl : public Dali::Accessibility::Bridge, + public Dali::ConnectionTracker { DBus::DBusClient mAccessibilityStatusClient{}; DBus::DBusClient mRegistryClient{}; @@ -93,9 +368,43 @@ class BridgeImpl : public virtual BridgeBase, Dali::Timer mReadScreenReaderEnabledTimer; Dali::Timer mForceUpTimer; std::string mPreferredBusName; + CoalescableMessagesMap mCoalescableMessages; + std::shared_ptr<BridgeData> mBridgeData; + const ApplicationAccessible mApplication; + const std::vector<BridgeBase> mBridgeObjects; + const BridgeAccessible& mBridgeAccessibleRef; + bool mIsScreenReaderSuppressed = false; + + DBus::DBusServer mDbusServer; + DBusWrapper::ConnectionPtr mConnectionPtr; + int mId = 0; + DBus::DBusClient mRegistry; + bool IsBoundsChangedEventAllowed{false}; public: - BridgeImpl() = default; + BridgeImpl() + : mBridgeData{std::make_shared<BridgeData>()} + , mApplication{mBridgeData} + { + mBridgeObjects = + { + BridgeAccessible{mBridgeData}, + BridgeComponent{mBridgeData}, + BridgeCollection{mBridgeData}, + BridgeAction{mBridgeData}, + BridgeValue{mBridgeData}, + BridgeText{mBridgeData}, + BridgeEditableText{mBridgeData}, + BridgeSelection{mBridgeData}, + BridgeApplication{mBridgeData}, + BridgeHypertext{mBridgeData}, + BridgeHyperlink{mBridgeData}, + BridgeSocket{mBridgeData}, + BridgeTable{mBridgeData}, + BridgeTableCell{mBridgeData} + } + mBridgeAccessibleRef = mBridgeObjects[0]; + } /** * @copydoc Dali::Accessibility::Bridge::EmitKeyEvent() @@ -239,12 +548,7 @@ public: mHighlightedActor = {}; mHighlightClearAction = {}; - BridgeAccessible::ForceDown(); - mRegistryClient = {}; - mDirectReadingClient = {}; - mDirectReadingCallbacks.clear(); - mApplication.mChildren.clear(); - ClearTimer(); + ResetResources(); } void ClearTimer() @@ -313,12 +617,15 @@ public: */ ForceUpResult ForceUp() override { - auto forceUpResult = BridgeAccessible::ForceUp(); - if(forceUpResult == ForceUpResult::ALREADY_UP) + //TODO: checking mBusName is enough? or a new variable to check bridge state? + auto forceUpResult = InitializeData(); + if(forceUpResult == ForceUpResult::ALREADY_UP && !GetBusName().empty()) { return forceUpResult; } - else if(forceUpResult == ForceUpResult::FAILED) + + forceUpResult = InitializeDbus(); + if(forceUpResult == ForceUpResult::FAILED) { if(!mForceUpTimer) { @@ -329,20 +636,10 @@ public: return forceUpResult; } - BridgeAccessible::RegisterInterfaces(); - BridgeComponent::RegisterInterfaces(); - BridgeCollection::RegisterInterfaces(); - BridgeAction::RegisterInterfaces(); - BridgeValue::RegisterInterfaces(); - BridgeText::RegisterInterfaces(); - BridgeEditableText::RegisterInterfaces(); - BridgeSelection::RegisterInterfaces(); - BridgeApplication::RegisterInterfaces(); - BridgeHypertext::RegisterInterfaces(); - BridgeHyperlink::RegisterInterfaces(); - BridgeSocket::RegisterInterfaces(); - BridgeTable::RegisterInterfaces(); - BridgeTableCell::RegisterInterfaces(); + for (const auto& bridgeObject : mBridgeObjects) + { + RegisterInterfaces(bridgeObject); + } RegisterAccessible(&mApplication); @@ -370,6 +667,11 @@ public: return ForceUpResult::JUST_STARTED; } + bool IsUp() const override + { + return bool(mData); + } + /** * @brief Sends a signal to dbus that the window is created. * @@ -841,14 +1143,6 @@ public: }); } - void SetExtentsOffset(std::int32_t x, std::int32_t y) override - { - if(mData) - { - mData->mExtentsOffset = {x, y}; - } - } - void SetPreferredBusName(std::string_view preferredBusName) override { if(preferredBusName == mPreferredBusName) @@ -1181,7 +1475,351 @@ public: {"", "root"}); } + const std::string& GetBusName() const override + { + static std::string empty; + return mData ? mData->mBusName : empty; + } + + void AddTopLevelWindow(Dali::Accessibility::Accessible* windowAccessible) override + { + if(windowAccessible->GetInternalActor() == nullptr) + { + return; + } + + // Prevent adding the default window twice. + if(!mApplication.mChildren.empty() && + mApplication.mChildren[0]->GetInternalActor() == windowAccessible->GetInternalActor()) + { + return; + } + + // Adds Window to a list of Windows. + mApplication.mChildren.push_back(windowAccessible); + windowAccessible->SetIsOnRootLevel(); + } + + void RemoveTopLevelWindow(Dali::Accessibility::Accessible* windowAccessible) override + { + for(auto i = 0u; i < mApplication.mChildren.size(); ++i) + { + if(mApplication.mChildren[i] == windowAccessible) + { + mApplication.mChildren.erase(mApplication.mChildren.begin() + i); + break; + } + } + } + + void RegisterDefaultLabel(Dali::Accessibility::Accessible* object) override + { + mBridgeAccessibleRef.RegisterDefaultLabel(object); + } + + void UnregisterDefaultLabel(Dali::Accessibility::Accessible* object) override + { + mBridgeAccessibleRef.UnregisterDefaultLabel(object); + } + + Dali::Accessibility::Accessible* GetApplication() const override + { + return &mApplication; + } + + void SetApplicationName(std::string name) override + { + mApplication.mName = std::move(name); + } + + void SetToolkitName(std::string_view toolkitName) override + { + mApplication.mToolkitName = std::string{toolkitName}; + } + + void RegisterAccessible(const Accessible* object) override + { + assert(mBridgeData); + mBridgeData->AddKnownObject(object); + } + + void UnregisterAccessible(const Accessible* object) override + { + assert(mBridgeData); + mBridgeData->RemoveKnownObject(object); + } + + Accessible* FindByPath(const std::string& name) const override + { + try + { + return mBridgeData->Find(name); + } + catch(std::domain_error&) + { + return nullptr; + } + } + private: + + /** + * @brief Adds CoalescableMessages, Accessible, and delay time to mCoalescableMessages. + * + * @param[in] kind CoalescableMessages enum value + * @param[in] obj Accessible object + * @param[in] delay The delay time + * @param[in] functor The function to be called // NEED TO UPDATE! + */ + void AddCoalescableMessage(CoalescableMessages kind, Dali::Accessibility::Accessible* obj, float delay, std::function<void()> functor) + { + if(delay < 0) + { + delay = 0; + } + auto countdownBase = static_cast<unsigned int>(delay * 10); + + auto it = mCoalescableMessages.insert({{kind, obj}, {countdownBase, countdownBase, {}}}); + if(it.second) + { + functor(); + } + else + { + std::get<1>(it.first->second) = countdownBase; + std::get<2>(it.first->second) = std::move(functor); + } + + if(!tickTimer) + { + tickTimer = Dali::Timer::New(100); + tickTimer.TickSignal().Connect(this, &BridgeImpl::TickCoalescableMessages); + } + + if(!tickTimer.IsRunning()) + { + tickTimer.Start(); + } + } + + /** + * @brief Removes all CoalescableMessages using Tick signal. + * + * @return False if mCoalescableMessages is empty, otherwise true. + */ + bool TickCoalescableMessages() + { + for(auto it = mCoalescableMessages.begin(); it != mCoalescableMessages.end();) + { + auto& countdown = std::get<0>(it->second); + auto countdownBase = std::get<1>(it->second); + auto& functor = std::get<2>(it->second); + if(countdown) + { + --countdown; + } + else + { + if(functor) + { + functor(); + functor = {}; + countdown = countdownBase; + } + else + { + it = mCoalescableMessages.erase(it); + continue; + } + } + ++it; + } + return !mCoalescableMessages.empty(); + } + + /** + * @brief Update registered events. + */ + void 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; + } + } + }); + } + + ForceUpResult InitializeData() + { + if(mData) + { + return ForceUpResult::ALREADY_UP; + } + mData = std::make_shared<Data>(); + return ForceUpResult::JUST_STARTED; + } + + ForceUpResult InitializeDbus() + { + 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(); + + if(!addr) + { + DALI_LOG_ERROR("failed at call '%s': %s\n", dbusLocators::atspi::GET_ADDRESS, addr.getError().message.c_str()); + return ForceUpResult::FAILED; + } + + mConnectionPtr = DBusWrapper::Installed()->eldbus_address_connection_get_impl(std::get<0>(addr)); + mData->mBusName = DBus::getConnectionName(mConnectionPtr); + mDbusServer = {mConnectionPtr}; + + { + DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::CACHE)}; + AddFunctionToInterface(desc, "GetItems", &BridgeImpl::GetItems); + mDbusServer.addInterface(AtspiDbusPathCache, desc); + } + { + DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::APPLICATION)}; + AddGetSetPropertyToInterface(desc, "Id", &BridgeImpl::GetId, &BridgeImpl::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 ResetResources() + { + ClearHighlightData(); + mRegistry = {}; + mDbusServer = {}; + mConnectionPtr = {}; + mRegistryClient = {}; + mDirectReadingClient = {}; + mDirectReadingCallbacks.clear(); + mApplication.mChildren.clear(); + ClearTimer(); + } + + void ClearHighlightData() + { + if(mData && mData->mCurrentlyHighlightedActor) + { + auto component = dynamic_cast<Component*>(Accessible::Get(mData->mCurrentlyHighlightedActor)); + if(component) + { + component->ClearHighlight(); + } + } + mData = {}; + } + + /** + * @brief Sets an ID. + * @param[in] id An ID (integer value) + */ + void SetId(int id) + { + mId = id; + } + + /** + * @brief Gets the ID. + * @return The ID to be set + */ + int GetId() + { + return mId; + } + + /** + * @brief Gets Items // NEED TO UPDATE! + * + * @return + */ + auto GetItems() -> DBus::ValueOrError<std::vector<CacheElementType>> + { + auto root = &mApplication; + + std::vector<CacheElementType> res; + + // TODO: make this usable + 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)); + } + }; + + return res; + } + + /** + * @brief Creates CacheElement. + * + * CreateCacheElement method works for GetItems which is a part of ATSPI protocol. + * ATSPI client library (libatspi from at-spi2-core) depending on cacheing policy configuration uses GetItems + * to pre-load entire accessible tree from application to its own cache in single dbus call. + * Otherwise the particular nodes in a tree are cached lazily when client library tries to access them. + * @param item Accessible to get information + * @return The elements to be cached + */ + auto CreateCacheElement(Accessible* item) -> CacheElementType + { + if(!item) + { + return {}; + } + + auto root = &mApplication; + auto parent = item->GetParent(); + + std::vector<Address> children; + for(auto i = 0u; i < item->GetChildCount(); ++i) + { + children.emplace_back(item->GetChildAtIndex(i)->GetAddress()); + } + + return std::make_tuple( + item->GetAddress(), + root->GetAddress(), + parent ? parent->GetAddress() : Address{}, + children, + item->GetInterfacesAsStrings(), + item->GetName(), + item->GetRole(), + item->GetDescription(), + item->GetStates().GetRawData()); + } + DBus::DBusClient CreateSocketClient(const Address& socket) { return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr}; @@ -1206,6 +1844,12 @@ private: DBus::releaseBusName(mConnectionPtr, busName); } + + void RegisterInterfaces(const BridgeBase& bridgeObject) + { + mDbusServer.addInterface("/", bridgeObject.GetInterface(), true); + } + }; // BridgeImpl namespace // unnamed namespace diff --git a/dali/internal/accessibility/bridge/bridge-selection.cpp b/dali/internal/accessibility/bridge/bridge-selection.cpp index 071367084..4d45b4d23 100644 --- a/dali/internal/accessibility/bridge/bridge-selection.cpp +++ b/dali/internal/accessibility/bridge/bridge-selection.cpp @@ -20,7 +20,10 @@ using namespace Dali::Accessibility; -void BridgeSelection::RegisterInterfaces() +BridgeSelection::BridgeSelection(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeSelection::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::SELECTION)}; AddGetPropertyToInterface(desc, "NSelectedChildren", &BridgeSelection::GetSelectedChildrenCount); @@ -31,12 +34,13 @@ void BridgeSelection::RegisterInterfaces() AddFunctionToInterface(desc, "SelectAll", &BridgeSelection::SelectAll); AddFunctionToInterface(desc, "ClearSelection", &BridgeSelection::ClearSelection); AddFunctionToInterface(desc, "DeselectChild", &BridgeSelection::DeselectChild); - mDbusServer.addInterface("/", desc, true); + + return desc; } Selection* BridgeSelection::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::SELECTION>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::SELECTION>(); } DBus::ValueOrError<int32_t> BridgeSelection::GetSelectedChildrenCount() diff --git a/dali/internal/accessibility/bridge/bridge-socket.cpp b/dali/internal/accessibility/bridge/bridge-socket.cpp index 621a43335..af99a0ac3 100644 --- a/dali/internal/accessibility/bridge/bridge-socket.cpp +++ b/dali/internal/accessibility/bridge/bridge-socket.cpp @@ -20,7 +20,10 @@ using namespace Dali::Accessibility; -void BridgeSocket::RegisterInterfaces() +BridgeSocket::BridgeSocket(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeSocket::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::SOCKET)}; @@ -28,12 +31,12 @@ void BridgeSocket::RegisterInterfaces() AddFunctionToInterface(desc, "Unembed", &BridgeSocket::Unembed); AddFunctionToInterface(desc, "SetOffset", &BridgeSocket::SetOffset); - mDbusServer.addInterface("/", desc, true); + return desc; } Socket* BridgeSocket::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::SOCKET>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::SOCKET>(); } DBus::ValueOrError<Address> BridgeSocket::Embed(Address plug) diff --git a/dali/internal/accessibility/bridge/bridge-table-cell.cpp b/dali/internal/accessibility/bridge/bridge-table-cell.cpp index b4680f8a7..09133e4d1 100644 --- a/dali/internal/accessibility/bridge/bridge-table-cell.cpp +++ b/dali/internal/accessibility/bridge/bridge-table-cell.cpp @@ -23,7 +23,10 @@ using namespace Dali::Accessibility; -void BridgeTableCell::RegisterInterfaces() +BridgeTableCell::BridgeTableCell(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeTableCell::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::TABLE_CELL)}; @@ -33,12 +36,12 @@ void BridgeTableCell::RegisterInterfaces() AddGetPropertyToInterface(desc, "ColumnSpan", &BridgeTableCell::GetCellColumnSpan); AddFunctionToInterface(desc, "GetRowColumnSpan", &BridgeTableCell::GetCellRowColumnSpan); - mDbusServer.addInterface("/", desc, true); + return desc; } TableCell* BridgeTableCell::FindSelf() const { - return FindCurrentObjectWithInterface<AtspiInterface::TABLE_CELL>(); + return mBridgeData->FindCurrentObjectWithInterface<AtspiInterface::TABLE_CELL>(); } DBus::ValueOrError<Accessible*> BridgeTableCell::GetTable() diff --git a/dali/internal/accessibility/bridge/bridge-table.cpp b/dali/internal/accessibility/bridge/bridge-table.cpp index 81ec834cc..df56bd899 100644 --- a/dali/internal/accessibility/bridge/bridge-table.cpp +++ b/dali/internal/accessibility/bridge/bridge-table.cpp @@ -23,7 +23,10 @@ using namespace Dali::Accessibility; -void BridgeTable::RegisterInterfaces() +BridgeTable::BridgeTable(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeTable::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::TABLE)}; @@ -54,12 +57,12 @@ void BridgeTable::RegisterInterfaces() AddFunctionToInterface(desc, "RemoveColumnSelection", &BridgeTable::RemoveColumnSelection); AddFunctionToInterface(desc, "GetRowColumnExtentsAtIndex", &BridgeTable::GetRowColumnSpan); - mDbusServer.addInterface("/", desc, true); + return desc; } Table* BridgeTable::FindSelf() const { - return FindCurrentObjectWithInterface<AtspiInterface::TABLE>(); + return mBridgeData->FindCurrentObjectWithInterface<AtspiInterface::TABLE>(); } DBus::ValueOrError<std::int32_t> BridgeTable::GetRowCount() diff --git a/dali/internal/accessibility/bridge/bridge-text.cpp b/dali/internal/accessibility/bridge/bridge-text.cpp index 2707e4415..4276d52d6 100644 --- a/dali/internal/accessibility/bridge/bridge-text.cpp +++ b/dali/internal/accessibility/bridge/bridge-text.cpp @@ -23,7 +23,10 @@ using namespace Dali::Accessibility; -void BridgeText::RegisterInterfaces() +BridgeText::BridgeText(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} + +DBus::DBusInterfaceDescription BridgeText::GetInterfaces() const { // The second arguments below are the names (or signatures) of DBus methods. // Screen Reader will call the methods with the exact names as specified in the AT-SPI Text interface: @@ -39,12 +42,13 @@ void BridgeText::RegisterInterfaces() AddFunctionToInterface(desc, "SetSelection", &BridgeText::SetRangeOfSelection); AddFunctionToInterface(desc, "RemoveSelection", &BridgeText::RemoveSelection); AddFunctionToInterface(desc, "GetRangeExtents", &BridgeText::GetRangeExtents); - mDbusServer.addInterface("/", desc, true); + + return desc; } Text* BridgeText::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::TEXT>(); + return mAccessibleObjectsHolder->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::TEXT>(); } DBus::ValueOrError<std::string> BridgeText::GetText(int startOffset, int endOffset) diff --git a/dali/internal/accessibility/bridge/bridge-text.h b/dali/internal/accessibility/bridge/bridge-text.h index cc50c3f27..46a33e7ba 100644 --- a/dali/internal/accessibility/bridge/bridge-text.h +++ b/dali/internal/accessibility/bridge/bridge-text.h @@ -27,14 +27,6 @@ */ class BridgeText : public virtual BridgeBase { -protected: - BridgeText() = default; - - /** - * @brief Registers Text functions to dbus interfaces. - */ - void RegisterInterfaces(); - /** * @brief Returns the Text object of the currently executed DBus method call. * @@ -43,6 +35,13 @@ protected: Dali::Accessibility::Text* FindSelf() const; public: + explicit BridgeText(std::shared_ptr<BridgeData> bridgeData); + + /** + * @brief Gets dbus interface description for Text type. + */ + DBus::DBusInterfaceDescription GetInterfaces() override; + /** * @copydoc Dali::Accessibility::Text::GetText() */ diff --git a/dali/internal/accessibility/bridge/bridge-value.cpp b/dali/internal/accessibility/bridge/bridge-value.cpp index 37e752be2..fad773def 100644 --- a/dali/internal/accessibility/bridge/bridge-value.cpp +++ b/dali/internal/accessibility/bridge/bridge-value.cpp @@ -20,11 +20,10 @@ using namespace Dali::Accessibility; -BridgeValue::BridgeValue() -{ -} +BridgeValue::BridgeValue(std::shared_ptr<BridgeData> bridgeData) +: BridgeBase(std::move(bridgeData)) {} -void BridgeValue::RegisterInterfaces() +DBus::DBusInterfaceDescription BridgeValue::GetInterfaces() const { DBus::DBusInterfaceDescription desc{Accessible::GetInterfaceName(AtspiInterface::VALUE)}; AddGetSetPropertyToInterface(desc, "CurrentValue", &BridgeValue::GetCurrentValue, &BridgeValue::SetCurrentValue); @@ -32,12 +31,13 @@ void BridgeValue::RegisterInterfaces() AddGetPropertyToInterface(desc, "MaximumValue", &BridgeValue::GetMaximumValue); AddGetPropertyToInterface(desc, "MinimumIncrement", &BridgeValue::GetMinimumIncrement); AddGetPropertyToInterface(desc, "MinimumValue", &BridgeValue::GetMinimumValue); - mDbusServer.addInterface("/", desc, true); + + return desc; } Value* BridgeValue::FindSelf() const { - return FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::VALUE>(); + return mBridgeData->FindCurrentObjectWithInterface<Dali::Accessibility::AtspiInterface::VALUE>(); } double BridgeValue::GetCurrentValue() diff --git a/dali/internal/accessibility/bridge/dummy/dummy-atspi.h b/dali/internal/accessibility/bridge/dummy/dummy-atspi.h index a5ed28962..031a36705 100644 --- a/dali/internal/accessibility/bridge/dummy/dummy-atspi.h +++ b/dali/internal/accessibility/bridge/dummy/dummy-atspi.h @@ -54,11 +54,6 @@ struct DummyBridge : Dali::Accessibility::Bridge { } - Dali::Accessibility::Accessible* GetDefaultLabel(Dali::Accessibility::Accessible* root) const override - { - return nullptr; - } - void SetApplicationName(std::string name) override { } @@ -126,6 +121,11 @@ struct DummyBridge : Dali::Accessibility::Bridge { } + bool IsUp() const override + { + return false; + } + void EmitCursorMoved(Accessibility::Accessible* obj, unsigned int cursorPosition) override { } @@ -226,11 +226,15 @@ struct DummyBridge : Dali::Accessibility::Bridge { } - void SetExtentsOffset(std::int32_t x, std::int32_t y) override + void SetPreferredBusName(std::string_view preferredBusName) override { } - void SetPreferredBusName(std::string_view preferredBusName) override + void RegisterAccessible(const Accessible* object) override + { + } + + void UnregisterAccessible(const Accessible* object) override { } }; |