2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
21 #include <dali/devel-api/common/stage.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/actors/layer.h>
25 #include <unordered_map>
28 #include <dali/devel-api/adaptor-framework/environment-variable.h>
29 #include <dali/devel-api/adaptor-framework/window-devel.h>
30 #include <dali/internal/accessibility/bridge/accessibility-common.h>
31 #include <dali/internal/accessibility/bridge/bridge-accessible.h>
32 #include <dali/internal/accessibility/bridge/bridge-action.h>
33 #include <dali/internal/accessibility/bridge/bridge-collection.h>
34 #include <dali/internal/accessibility/bridge/bridge-component.h>
35 #include <dali/internal/accessibility/bridge/bridge-editable-text.h>
36 #include <dali/internal/accessibility/bridge/bridge-hypertext.h>
37 #include <dali/internal/accessibility/bridge/bridge-hyperlink.h>
38 #include <dali/internal/accessibility/bridge/bridge-object.h>
39 #include <dali/internal/accessibility/bridge/bridge-selection.h>
40 #include <dali/internal/accessibility/bridge/bridge-socket.h>
41 #include <dali/internal/accessibility/bridge/bridge-text.h>
42 #include <dali/internal/accessibility/bridge/bridge-value.h>
43 #include <dali/internal/accessibility/bridge/bridge-application.h>
44 #include <dali/internal/accessibility/bridge/dummy/dummy-atspi.h>
45 #include <dali/internal/adaptor/common/adaptor-impl.h>
46 #include <dali/internal/system/common/environment-variables.h>
49 using namespace Dali::Accessibility;
51 namespace // unnamed namespace
54 const int RETRY_INTERVAL = 1000;
56 } // unnamed namespace
59 * @brief The BridgeImpl class is to implement some Bridge functions.
61 class BridgeImpl : public virtual BridgeBase,
62 public BridgeAccessible,
64 public BridgeComponent,
65 public BridgeCollection,
69 public BridgeEditableText,
70 public BridgeSelection,
71 public BridgeApplication,
72 public BridgeHypertext,
73 public BridgeHyperlink,
76 DBus::DBusClient mAccessibilityStatusClient;
77 DBus::DBusClient mRegistryClient;
78 DBus::DBusClient mDirectReadingClient;
79 bool mIsScreenReaderEnabled = false;
80 bool mIsEnabled = false;
81 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks;
82 Dali::Actor mHighlightedActor;
83 std::function<void(Dali::Actor)> mHighlightClearAction;
84 Dali::CallbackBase* mIdleCallback = NULL;
85 Dali::Timer mInitializeTimer;
86 Dali::Timer mReadIsEnabledTimer;
87 Dali::Timer mReadScreenReaderEnabledTimer;
88 Dali::Timer mForceUpTimer;
89 std::string mPreferredBusName;
97 * @copydoc Dali::Accessibility::Bridge::Emit()
99 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
106 unsigned int keyType = 0;
110 case KeyEventType::KEY_PRESSED:
115 case KeyEventType::KEY_RELEASED:
130 * @copydoc Dali::Accessibility::Bridge::Pause()
132 void Pause() override
139 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
142 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
149 * @copydoc Dali::Accessibility::Bridge::Resume()
151 void Resume() override
158 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
161 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
168 * @copydoc Dali::Accessibility::Bridge::StopReading()
170 void StopReading(bool alsoNonDiscardable) override
177 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
180 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
187 * @copydoc Dali::Accessibility::Bridge::Say()
189 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
196 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
199 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
203 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
211 * @copydoc Dali::Accessibility::Bridge::ForceDown()
213 void ForceDown() override
217 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
219 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
221 mData->mCurrentlyHighlightedActor = {};
222 mData->mHighlightActor = {};
224 mDisabledSignal.Emit();
225 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
226 ReleaseBusName(mPreferredBusName);
229 mHighlightedActor = {};
230 mHighlightClearAction = {};
231 BridgeAccessible::ForceDown();
232 mRegistryClient = {};
233 mDirectReadingClient = {};
234 mDirectReadingCallbacks.clear();
235 mApplication.mChildren.clear();
243 mInitializeTimer.Stop();
244 mInitializeTimer.Reset();
247 if(mReadIsEnabledTimer)
249 mReadIsEnabledTimer.Stop();
250 mReadIsEnabledTimer.Reset();
253 if(mReadScreenReaderEnabledTimer)
255 mReadScreenReaderEnabledTimer.Stop();
256 mReadScreenReaderEnabledTimer.Reset();
261 mForceUpTimer.Stop();
262 mForceUpTimer.Reset();
267 * @copydoc Dali::Accessibility::Bridge::Terminate()
269 void Terminate() override
273 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
274 for(auto windowAccessible : mApplication.mChildren)
276 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
278 mData->mCurrentlyHighlightedActor = {};
279 mData->mHighlightActor = {};
282 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
284 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
286 mAccessibilityStatusClient = {};
291 bool ForceUpTimerCallback()
293 if(ForceUp() != ForceUpResult::FAILED)
301 * @copydoc Dali::Accessibility::Bridge::ForceUp()
303 ForceUpResult ForceUp() override
305 auto forceUpResult = BridgeAccessible::ForceUp();
306 if(forceUpResult == ForceUpResult::ALREADY_UP)
308 return forceUpResult;
310 else if(forceUpResult == ForceUpResult::FAILED)
314 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
315 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
316 mForceUpTimer.Start();
318 return forceUpResult;
321 BridgeObject::RegisterInterfaces();
322 BridgeAccessible::RegisterInterfaces();
323 BridgeComponent::RegisterInterfaces();
324 BridgeCollection::RegisterInterfaces();
325 BridgeAction::RegisterInterfaces();
326 BridgeValue::RegisterInterfaces();
327 BridgeText::RegisterInterfaces();
328 BridgeEditableText::RegisterInterfaces();
329 BridgeSelection::RegisterInterfaces();
330 BridgeApplication::RegisterInterfaces();
331 BridgeHypertext::RegisterInterfaces();
332 BridgeHyperlink::RegisterInterfaces();
333 BridgeSocket::RegisterInterfaces();
335 RegisterOnBridge(&mApplication);
337 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
338 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
340 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
341 auto it = mDirectReadingCallbacks.find(id);
342 if(it != mDirectReadingCallbacks.end())
344 it->second(readingState);
345 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
347 mDirectReadingCallbacks.erase(it);
352 RequestBusName(mPreferredBusName);
354 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
355 mApplication.mParent.SetAddress(std::move(parentAddress));
356 mEnabledSignal.Emit();
358 return ForceUpResult::JUST_STARTED;
362 * @brief Sends a signal to dbus that the window is shown.
364 * @param[in] window The window to be shown
365 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
367 void EmitShown(Dali::Window window)
369 auto windowAccessible = mApplication.GetWindowAccessible(window);
372 windowAccessible->EmitShowing(true);
377 * @brief Sends a signal to dbus that the window is hidden.
379 * @param[in] window The window to be hidden
380 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
382 void EmitHidden(Dali::Window window)
384 auto windowAccessible = mApplication.GetWindowAccessible(window);
387 windowAccessible->EmitShowing(false);
392 * @brief Sends a signal to dbus that the window is activated.
394 * @param[in] window The window to be activated
395 * @see BridgeObject::Emit()
397 void EmitActivate(Dali::Window window)
399 auto windowAccessible = mApplication.GetWindowAccessible(window);
402 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
407 * @brief Sends a signal to dbus that the window is deactivated.
409 * @param[in] window The window to be deactivated
410 * @see BridgeObject::Emit()
412 void EmitDeactivate(Dali::Window window)
414 auto windowAccessible = mApplication.GetWindowAccessible(window);
417 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
422 * @copydoc Dali::Accessibility::Bridge::WindowShown()
424 void WindowShown(Dali::Window window) override
433 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
435 void WindowHidden(Dali::Window window) override
444 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
446 void WindowFocused(Dali::Window window) override
450 EmitActivate(window);
455 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
457 void WindowUnfocused(Dali::Window window) override
461 EmitDeactivate(window);
466 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
468 void SuppressScreenReader(bool suppress) override
470 if(mIsScreenReaderSuppressed == suppress)
474 mIsScreenReaderSuppressed = suppress;
475 ReadScreenReaderEnabledProperty();
480 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
490 bool ReadIsEnabledTimerCallback()
492 ReadIsEnabledProperty();
496 void ReadIsEnabledProperty()
498 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
501 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
502 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
504 if(!mReadIsEnabledTimer)
506 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
507 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
509 mReadIsEnabledTimer.Start();
514 if(mReadIsEnabledTimer)
516 mReadIsEnabledTimer.Stop();
517 mReadIsEnabledTimer.Reset();
520 mIsEnabled = std::get<0>(msg);
525 void ListenIsEnabledProperty()
527 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
533 bool ReadScreenReaderEnabledTimerCallback()
535 ReadScreenReaderEnabledProperty();
539 void ReadScreenReaderEnabledProperty()
541 // can be true because of SuppressScreenReader before init
542 if (!mAccessibilityStatusClient)
547 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
550 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
551 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
553 if(!mReadScreenReaderEnabledTimer)
555 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
556 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
558 mReadScreenReaderEnabledTimer.Start();
563 if(mReadScreenReaderEnabledTimer)
565 mReadScreenReaderEnabledTimer.Stop();
566 mReadScreenReaderEnabledTimer.Reset();
569 mIsScreenReaderEnabled = std::get<0>(msg);
574 void EmitScreenReaderEnabledSignal()
576 if (mIsScreenReaderEnabled)
578 mScreenReaderEnabledSignal.Emit();
582 mScreenReaderDisabledSignal.Emit();
586 void ListenScreenReaderEnabledProperty()
588 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
589 mIsScreenReaderEnabled = res;
590 EmitScreenReaderEnabledSignal();
595 void ReadAndListenProperties()
597 ReadIsEnabledProperty();
598 ListenIsEnabledProperty();
600 ReadScreenReaderEnabledProperty();
601 ListenScreenReaderEnabledProperty();
604 bool InitializeAccessibilityStatusClient()
606 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
608 if (!mAccessibilityStatusClient)
610 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
617 bool InitializeTimerCallback()
619 if ( InitializeAccessibilityStatusClient() )
621 ReadAndListenProperties();
629 if ( InitializeAccessibilityStatusClient() )
631 ReadAndListenProperties();
632 mIdleCallback = NULL;
636 if(!mInitializeTimer)
638 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
639 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
641 mInitializeTimer.Start();
643 mIdleCallback = NULL;
648 * @copydoc Dali::Accessibility::Bridge::Initialize()
650 void Initialize() override
652 if ( InitializeAccessibilityStatusClient() )
654 ReadAndListenProperties();
658 // Initialize failed. Try it again on Idle
659 if( Dali::Adaptor::IsAvailable() )
661 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
662 if( NULL == mIdleCallback )
664 mIdleCallback = MakeCallback( this, &BridgeImpl::OnIdleSignal );
665 adaptor.AddIdle( mIdleCallback, true );
671 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
673 bool GetScreenReaderEnabled() override
675 return mIsScreenReaderEnabled;
679 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
681 bool IsEnabled() override
686 Address EmbedSocket(const Address& plug, const Address& socket) override
688 auto client = CreateSocketClient(socket);
689 auto reply = client.method<Address(Address)>("Embed").call(plug);
693 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
697 return std::get<0>(reply.getValues());
700 void EmbedAtkSocket(const Address& plug, const Address& socket) override
702 auto client = CreateSocketClient(socket);
704 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
707 void UnembedSocket(const Address& plug, const Address& socket) override
709 auto client = CreateSocketClient(socket);
711 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
714 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
716 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
717 auto client = CreateSocketClient(socket->GetAddress());
719 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
723 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
727 mData->mExtentsOffset = {x, y};
731 void SetPreferredBusName(std::string_view preferredBusName) override
733 if(preferredBusName == mPreferredBusName)
738 std::string oldPreferredBusName = std::move(mPreferredBusName);
739 mPreferredBusName = std::string{preferredBusName};
743 ReleaseBusName(oldPreferredBusName);
744 RequestBusName(mPreferredBusName);
746 // else: request/release will be handled by ForceUp/ForceDown, respectively
750 DBus::DBusClient CreateSocketClient(const Address& socket)
752 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
755 void RequestBusName(const std::string& busName)
762 DBus::requestBusName(mConnectionPtr, busName);
765 void ReleaseBusName(const std::string& busName)
772 DBus::releaseBusName(mConnectionPtr, busName);
776 namespace // unnamed namespace
779 bool INITIALIZED_BRIDGE = false;
782 * @brief Creates BridgeImpl instance.
784 * @return The BridgeImpl instance
785 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
787 std::shared_ptr<Bridge> CreateBridge()
789 INITIALIZED_BRIDGE = true;
793 /* check environment variable first */
794 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
795 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
797 return Dali::Accessibility::DummyBridge::GetInstance();
800 // check if debug mode
801 if(access("/etc/debug", F_OK) != 0)
803 return Dali::Accessibility::DummyBridge::GetInstance();
806 return std::make_shared<BridgeImpl>();
808 catch(const std::exception&)
810 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
811 return Dali::Accessibility::DummyBridge::GetInstance();
815 } // unnamed namespace
817 // Dali::Accessibility::Bridge class implementation
819 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
821 static std::shared_ptr<Bridge> bridge;
827 else if(mAutoInitState == AutoInitState::ENABLED)
829 bridge = CreateBridge();
831 /* check environment variable for suppressing screen-reader */
832 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
833 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
835 bridge->SuppressScreenReader(true);
841 return Dali::Accessibility::DummyBridge::GetInstance();
844 void Bridge::DisableAutoInit()
846 if(INITIALIZED_BRIDGE)
848 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
851 mAutoInitState = AutoInitState::DISABLED;
854 void Bridge::EnableAutoInit()
856 mAutoInitState = AutoInitState::ENABLED;
858 if(INITIALIZED_BRIDGE)
863 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
864 auto window = Dali::DevelWindow::Get(rootLayer);
865 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
867 auto accessible = Accessibility::Accessible::Get(rootLayer);
869 auto bridge = Bridge::GetCurrentBridge();
870 bridge->AddTopLevelWindow(accessible);
871 bridge->SetApplicationName(applicationName);
872 bridge->Initialize();
874 if(window && window.IsVisible())
876 bridge->WindowShown(window);
880 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
882 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
883 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
885 static const char prefix[] = "com.samsung.dali.widget_";
886 static const char underscore = '_';
888 std::stringstream tmp;
892 for(char ch : widgetInstanceId)
894 tmp << (std::isalnum(ch) ? ch : underscore);