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-application.h>
34 #include <dali/internal/accessibility/bridge/bridge-collection.h>
35 #include <dali/internal/accessibility/bridge/bridge-component.h>
36 #include <dali/internal/accessibility/bridge/bridge-editable-text.h>
37 #include <dali/internal/accessibility/bridge/bridge-hyperlink.h>
38 #include <dali/internal/accessibility/bridge/bridge-hypertext.h>
39 #include <dali/internal/accessibility/bridge/bridge-object.h>
40 #include <dali/internal/accessibility/bridge/bridge-selection.h>
41 #include <dali/internal/accessibility/bridge/bridge-socket.h>
42 #include <dali/internal/accessibility/bridge/bridge-table.h>
43 #include <dali/internal/accessibility/bridge/bridge-table-cell.h>
44 #include <dali/internal/accessibility/bridge/bridge-text.h>
45 #include <dali/internal/accessibility/bridge/bridge-value.h>
46 #include <dali/internal/accessibility/bridge/dummy/dummy-atspi.h>
47 #include <dali/internal/adaptor/common/adaptor-impl.h>
48 #include <dali/internal/system/common/environment-variables.h>
50 using namespace Dali::Accessibility;
52 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 public BridgeTableCell
78 DBus::DBusClient mAccessibilityStatusClient{};
79 DBus::DBusClient mRegistryClient{};
80 DBus::DBusClient mDirectReadingClient{};
81 bool mIsScreenReaderEnabled{false};
82 bool mIsEnabled{false};
83 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks{};
84 Dali::Actor mHighlightedActor;
85 std::function<void(Dali::Actor)> mHighlightClearAction{nullptr};
86 Dali::CallbackBase* mIdleCallback{};
87 Dali::Timer mInitializeTimer;
88 Dali::Timer mReadIsEnabledTimer;
89 Dali::Timer mReadScreenReaderEnabledTimer;
90 Dali::Timer mForceUpTimer;
91 std::string mPreferredBusName;
94 BridgeImpl() = default;
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();
334 BridgeTable::RegisterInterfaces();
335 BridgeTableCell::RegisterInterfaces();
337 RegisterOnBridge(&mApplication);
339 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
340 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
342 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
343 auto it = mDirectReadingCallbacks.find(id);
344 if(it != mDirectReadingCallbacks.end())
346 it->second(readingState);
347 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
349 mDirectReadingCallbacks.erase(it);
354 RequestBusName(mPreferredBusName);
356 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
357 mApplication.mParent.SetAddress(std::move(parentAddress));
358 mEnabledSignal.Emit();
360 return ForceUpResult::JUST_STARTED;
364 * @brief Sends a signal to dbus that the window is created.
366 * @param[in] window The window to be created
367 * @see BridgeObject::Emit()
369 void EmitCreated(Dali::Window window)
371 auto windowAccessible = mApplication.GetWindowAccessible(window);
374 windowAccessible->Emit(WindowEvent::CREATE, 0);
379 * @brief Sends a signal to dbus that the window is shown.
381 * @param[in] window The window to be shown
382 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
384 void EmitShown(Dali::Window window)
386 auto windowAccessible = mApplication.GetWindowAccessible(window);
389 windowAccessible->EmitShowing(true);
394 * @brief Sends a signal to dbus that the window is hidden.
396 * @param[in] window The window to be hidden
397 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
399 void EmitHidden(Dali::Window window)
401 auto windowAccessible = mApplication.GetWindowAccessible(window);
404 windowAccessible->EmitShowing(false);
409 * @brief Sends a signal to dbus that the window is activated.
411 * @param[in] window The window to be activated
412 * @see BridgeObject::Emit()
414 void EmitActivate(Dali::Window window)
416 auto windowAccessible = mApplication.GetWindowAccessible(window);
419 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
424 * @brief Sends a signal to dbus that the window is deactivated.
426 * @param[in] window The window to be deactivated
427 * @see BridgeObject::Emit()
429 void EmitDeactivate(Dali::Window window)
431 auto windowAccessible = mApplication.GetWindowAccessible(window);
434 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
439 * @brief Sends a signal to dbus that the window is minimized.
441 * @param[in] window The window to be minimized
442 * @see BridgeObject::Emit()
444 void EmitMinimize(Dali::Window window)
446 auto windowAccessible = mApplication.GetWindowAccessible(window);
449 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
454 * @brief Sends a signal to dbus that the window is restored.
456 * @param[in] window The window to be restored
457 * @param[in] detail Restored window state
458 * @see BridgeObject::Emit()
460 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
462 auto windowAccessible = mApplication.GetWindowAccessible(window);
465 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
470 * @brief Sends a signal to dbus that the window is maximized.
472 * @param[in] window The window to be maximized
473 * @see BridgeObject::Emit()
475 void EmitMaximize(Dali::Window window)
477 auto windowAccessible = mApplication.GetWindowAccessible(window);
480 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
485 * @copydoc Dali::Accessibility::Bridge::WindowCreated()
487 void WindowCreated(Dali::Window window) override
496 * @copydoc Dali::Accessibility::Bridge::WindowShown()
498 void WindowShown(Dali::Window window) override
507 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
509 void WindowHidden(Dali::Window window) override
518 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
520 void WindowFocused(Dali::Window window) override
524 EmitActivate(window);
529 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
531 void WindowUnfocused(Dali::Window window) override
535 EmitDeactivate(window);
540 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
542 void WindowMinimized(Dali::Window window) override
546 EmitMinimize(window);
551 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
553 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
557 EmitRestore(window, detail);
562 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
564 void WindowMaximized(Dali::Window window) override
568 EmitMaximize(window);
573 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
575 void SuppressScreenReader(bool suppress) override
577 if(mIsScreenReaderSuppressed == suppress)
581 mIsScreenReaderSuppressed = suppress;
582 ReadScreenReaderEnabledProperty();
587 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
597 bool ReadIsEnabledTimerCallback()
599 ReadIsEnabledProperty();
603 void ReadIsEnabledProperty()
605 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
608 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
609 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
611 if(!mReadIsEnabledTimer)
613 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
614 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
616 mReadIsEnabledTimer.Start();
621 if(mReadIsEnabledTimer)
623 mReadIsEnabledTimer.Stop();
624 mReadIsEnabledTimer.Reset();
627 mIsEnabled = std::get<0>(msg);
632 void ListenIsEnabledProperty()
634 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
640 bool ReadScreenReaderEnabledTimerCallback()
642 ReadScreenReaderEnabledProperty();
646 void ReadScreenReaderEnabledProperty()
648 // can be true because of SuppressScreenReader before init
649 if(!mAccessibilityStatusClient)
654 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
657 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
658 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
660 if(!mReadScreenReaderEnabledTimer)
662 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
663 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
665 mReadScreenReaderEnabledTimer.Start();
670 if(mReadScreenReaderEnabledTimer)
672 mReadScreenReaderEnabledTimer.Stop();
673 mReadScreenReaderEnabledTimer.Reset();
676 mIsScreenReaderEnabled = std::get<0>(msg);
681 void EmitScreenReaderEnabledSignal()
683 if(mIsScreenReaderEnabled)
685 mScreenReaderEnabledSignal.Emit();
689 mScreenReaderDisabledSignal.Emit();
693 void ListenScreenReaderEnabledProperty()
695 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
696 mIsScreenReaderEnabled = res;
697 EmitScreenReaderEnabledSignal();
702 void ReadAndListenProperties()
704 ReadIsEnabledProperty();
705 ListenIsEnabledProperty();
707 ReadScreenReaderEnabledProperty();
708 ListenScreenReaderEnabledProperty();
711 bool InitializeAccessibilityStatusClient()
713 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
715 if(!mAccessibilityStatusClient)
717 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
724 bool InitializeTimerCallback()
726 if(InitializeAccessibilityStatusClient())
728 ReadAndListenProperties();
736 if(InitializeAccessibilityStatusClient())
738 ReadAndListenProperties();
739 mIdleCallback = NULL;
743 if(!mInitializeTimer)
745 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
746 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
748 mInitializeTimer.Start();
750 mIdleCallback = NULL;
755 * @copydoc Dali::Accessibility::Bridge::Initialize()
757 void Initialize() override
759 if(InitializeAccessibilityStatusClient())
761 ReadAndListenProperties();
765 // Initialize failed. Try it again on Idle
766 if(Dali::Adaptor::IsAvailable())
768 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
769 if(NULL == mIdleCallback)
771 mIdleCallback = MakeCallback(this, &BridgeImpl::OnIdleSignal);
772 adaptor.AddIdle(mIdleCallback, true);
778 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
780 bool GetScreenReaderEnabled() override
782 return mIsScreenReaderEnabled;
786 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
788 bool IsEnabled() override
793 Address EmbedSocket(const Address& plug, const Address& socket) override
795 auto client = CreateSocketClient(socket);
796 auto reply = client.method<Address(Address)>("Embed").call(plug);
800 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
804 return std::get<0>(reply.getValues());
807 void EmbedAtkSocket(const Address& plug, const Address& socket) override
809 auto client = CreateSocketClient(socket);
811 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
814 void UnembedSocket(const Address& plug, const Address& socket) override
816 auto client = CreateSocketClient(socket);
818 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
821 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
823 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
824 auto client = CreateSocketClient(socket->GetAddress());
826 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
830 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
834 mData->mExtentsOffset = {x, y};
838 void SetPreferredBusName(std::string_view preferredBusName) override
840 if(preferredBusName == mPreferredBusName)
845 std::string oldPreferredBusName = std::move(mPreferredBusName);
846 mPreferredBusName = std::string{preferredBusName};
850 ReleaseBusName(oldPreferredBusName);
851 RequestBusName(mPreferredBusName);
853 // else: request/release will be handled by ForceUp/ForceDown, respectively
857 DBus::DBusClient CreateSocketClient(const Address& socket)
859 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
862 void RequestBusName(const std::string& busName)
869 DBus::requestBusName(mConnectionPtr, busName);
872 void ReleaseBusName(const std::string& busName)
879 DBus::releaseBusName(mConnectionPtr, busName);
883 namespace // unnamed namespace
885 bool INITIALIZED_BRIDGE = false;
888 * @brief Creates BridgeImpl instance.
890 * @return The BridgeImpl instance
891 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
893 std::shared_ptr<Bridge> CreateBridge()
895 INITIALIZED_BRIDGE = true;
899 /* check environment variable first */
900 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
901 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
903 return Dali::Accessibility::DummyBridge::GetInstance();
906 return std::make_shared<BridgeImpl>();
908 catch(const std::exception&)
910 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
911 return Dali::Accessibility::DummyBridge::GetInstance();
915 } // unnamed namespace
917 // Dali::Accessibility::Bridge class implementation
919 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
921 static std::shared_ptr<Bridge> bridge;
927 else if(mAutoInitState == AutoInitState::ENABLED)
929 bridge = CreateBridge();
931 /* check environment variable for suppressing screen-reader */
932 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
933 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
935 bridge->SuppressScreenReader(true);
941 return Dali::Accessibility::DummyBridge::GetInstance();
944 void Bridge::DisableAutoInit()
946 if(INITIALIZED_BRIDGE)
948 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
951 mAutoInitState = AutoInitState::DISABLED;
954 void Bridge::EnableAutoInit()
956 mAutoInitState = AutoInitState::ENABLED;
958 if(INITIALIZED_BRIDGE)
963 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
964 auto window = Dali::DevelWindow::Get(rootLayer);
965 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
967 auto accessible = Accessibility::Accessible::Get(rootLayer);
969 auto bridge = Bridge::GetCurrentBridge();
970 bridge->AddTopLevelWindow(accessible);
971 bridge->SetApplicationName(applicationName);
972 bridge->Initialize();
974 if(window && window.IsVisible())
976 bridge->WindowShown(window);
980 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
982 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
983 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
985 static const char prefix[] = "com.samsung.dali.widget_";
986 static const char underscore = '_';
988 std::stringstream tmp;
992 for(char ch : widgetInstanceId)
994 tmp << (std::isalnum(ch) ? ch : underscore);