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-text.h>
43 #include <dali/internal/accessibility/bridge/bridge-value.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>
48 using namespace Dali::Accessibility;
50 namespace // unnamed namespace
52 const int RETRY_INTERVAL = 1000;
54 } // unnamed namespace
57 * @brief The BridgeImpl class is to implement some Bridge functions.
59 class BridgeImpl : public virtual BridgeBase,
60 public BridgeAccessible,
62 public BridgeComponent,
63 public BridgeCollection,
67 public BridgeEditableText,
68 public BridgeSelection,
69 public BridgeApplication,
70 public BridgeHypertext,
71 public BridgeHyperlink,
74 DBus::DBusClient mAccessibilityStatusClient{};
75 DBus::DBusClient mRegistryClient{};
76 DBus::DBusClient mDirectReadingClient{};
77 bool mIsScreenReaderEnabled{false};
78 bool mIsEnabled{false};
79 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks{};
80 Dali::Actor mHighlightedActor;
81 std::function<void(Dali::Actor)> mHighlightClearAction{nullptr};
82 Dali::CallbackBase* mIdleCallback{};
83 Dali::Timer mInitializeTimer;
84 Dali::Timer mReadIsEnabledTimer;
85 Dali::Timer mReadScreenReaderEnabledTimer;
86 Dali::Timer mForceUpTimer;
87 std::string mPreferredBusName;
90 BridgeImpl() = default;
93 * @copydoc Dali::Accessibility::Bridge::Emit()
95 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
102 unsigned int keyType = 0;
106 case KeyEventType::KEY_PRESSED:
111 case KeyEventType::KEY_RELEASED:
126 * @copydoc Dali::Accessibility::Bridge::Pause()
128 void Pause() override
135 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
138 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
145 * @copydoc Dali::Accessibility::Bridge::Resume()
147 void Resume() override
154 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
157 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
164 * @copydoc Dali::Accessibility::Bridge::StopReading()
166 void StopReading(bool alsoNonDiscardable) override
173 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
176 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
183 * @copydoc Dali::Accessibility::Bridge::Say()
185 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
192 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
195 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
199 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
207 * @copydoc Dali::Accessibility::Bridge::ForceDown()
209 void ForceDown() override
213 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
215 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
217 mData->mCurrentlyHighlightedActor = {};
218 mData->mHighlightActor = {};
220 mDisabledSignal.Emit();
221 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
222 ReleaseBusName(mPreferredBusName);
225 mHighlightedActor = {};
226 mHighlightClearAction = {};
227 BridgeAccessible::ForceDown();
228 mRegistryClient = {};
229 mDirectReadingClient = {};
230 mDirectReadingCallbacks.clear();
231 mApplication.mChildren.clear();
239 mInitializeTimer.Stop();
240 mInitializeTimer.Reset();
243 if(mReadIsEnabledTimer)
245 mReadIsEnabledTimer.Stop();
246 mReadIsEnabledTimer.Reset();
249 if(mReadScreenReaderEnabledTimer)
251 mReadScreenReaderEnabledTimer.Stop();
252 mReadScreenReaderEnabledTimer.Reset();
257 mForceUpTimer.Stop();
258 mForceUpTimer.Reset();
263 * @copydoc Dali::Accessibility::Bridge::Terminate()
265 void Terminate() override
269 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
270 for(auto windowAccessible : mApplication.mChildren)
272 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
274 mData->mCurrentlyHighlightedActor = {};
275 mData->mHighlightActor = {};
278 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
280 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
282 mAccessibilityStatusClient = {};
287 bool ForceUpTimerCallback()
289 if(ForceUp() != ForceUpResult::FAILED)
297 * @copydoc Dali::Accessibility::Bridge::ForceUp()
299 ForceUpResult ForceUp() override
301 auto forceUpResult = BridgeAccessible::ForceUp();
302 if(forceUpResult == ForceUpResult::ALREADY_UP)
304 return forceUpResult;
306 else if(forceUpResult == ForceUpResult::FAILED)
310 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
311 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
312 mForceUpTimer.Start();
314 return forceUpResult;
317 BridgeObject::RegisterInterfaces();
318 BridgeAccessible::RegisterInterfaces();
319 BridgeComponent::RegisterInterfaces();
320 BridgeCollection::RegisterInterfaces();
321 BridgeAction::RegisterInterfaces();
322 BridgeValue::RegisterInterfaces();
323 BridgeText::RegisterInterfaces();
324 BridgeEditableText::RegisterInterfaces();
325 BridgeSelection::RegisterInterfaces();
326 BridgeApplication::RegisterInterfaces();
327 BridgeHypertext::RegisterInterfaces();
328 BridgeHyperlink::RegisterInterfaces();
329 BridgeSocket::RegisterInterfaces();
331 RegisterOnBridge(&mApplication);
333 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
334 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
336 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
337 auto it = mDirectReadingCallbacks.find(id);
338 if(it != mDirectReadingCallbacks.end())
340 it->second(readingState);
341 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
343 mDirectReadingCallbacks.erase(it);
348 RequestBusName(mPreferredBusName);
350 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
351 mApplication.mParent.SetAddress(std::move(parentAddress));
352 mEnabledSignal.Emit();
354 return ForceUpResult::JUST_STARTED;
358 * @brief Sends a signal to dbus that the window is shown.
360 * @param[in] window The window to be shown
361 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
363 void EmitShown(Dali::Window window)
365 auto windowAccessible = mApplication.GetWindowAccessible(window);
368 windowAccessible->EmitShowing(true);
373 * @brief Sends a signal to dbus that the window is hidden.
375 * @param[in] window The window to be hidden
376 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
378 void EmitHidden(Dali::Window window)
380 auto windowAccessible = mApplication.GetWindowAccessible(window);
383 windowAccessible->EmitShowing(false);
388 * @brief Sends a signal to dbus that the window is activated.
390 * @param[in] window The window to be activated
391 * @see BridgeObject::Emit()
393 void EmitActivate(Dali::Window window)
395 auto windowAccessible = mApplication.GetWindowAccessible(window);
398 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
403 * @brief Sends a signal to dbus that the window is deactivated.
405 * @param[in] window The window to be deactivated
406 * @see BridgeObject::Emit()
408 void EmitDeactivate(Dali::Window window)
410 auto windowAccessible = mApplication.GetWindowAccessible(window);
413 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
418 * @brief Sends a signal to dbus that the window is minimized.
420 * @param[in] window The window to be minimized
421 * @see BridgeObject::Emit()
423 void EmitMinimize(Dali::Window window)
425 auto windowAccessible = mApplication.GetWindowAccessible(window);
428 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
433 * @brief Sends a signal to dbus that the window is restored.
435 * @param[in] window The window to be restored
436 * @param[in] detail Restored window state
437 * @see BridgeObject::Emit()
439 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
441 auto windowAccessible = mApplication.GetWindowAccessible(window);
444 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
449 * @brief Sends a signal to dbus that the window is maximized.
451 * @param[in] window The window to be maximized
452 * @see BridgeObject::Emit()
454 void EmitMaximize(Dali::Window window)
456 auto windowAccessible = mApplication.GetWindowAccessible(window);
459 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
464 * @copydoc Dali::Accessibility::Bridge::WindowShown()
466 void WindowShown(Dali::Window window) override
475 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
477 void WindowHidden(Dali::Window window) override
486 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
488 void WindowFocused(Dali::Window window) override
492 EmitActivate(window);
497 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
499 void WindowUnfocused(Dali::Window window) override
503 EmitDeactivate(window);
508 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
510 void WindowMinimized(Dali::Window window) override
514 EmitMinimize(window);
519 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
521 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
525 EmitRestore(window, detail);
530 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
532 void WindowMaximized(Dali::Window window) override
536 EmitMaximize(window);
541 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
543 void SuppressScreenReader(bool suppress) override
545 if(mIsScreenReaderSuppressed == suppress)
549 mIsScreenReaderSuppressed = suppress;
550 ReadScreenReaderEnabledProperty();
555 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
565 bool ReadIsEnabledTimerCallback()
567 ReadIsEnabledProperty();
571 void ReadIsEnabledProperty()
573 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
576 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
577 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
579 if(!mReadIsEnabledTimer)
581 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
582 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
584 mReadIsEnabledTimer.Start();
589 if(mReadIsEnabledTimer)
591 mReadIsEnabledTimer.Stop();
592 mReadIsEnabledTimer.Reset();
595 mIsEnabled = std::get<0>(msg);
600 void ListenIsEnabledProperty()
602 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
608 bool ReadScreenReaderEnabledTimerCallback()
610 ReadScreenReaderEnabledProperty();
614 void ReadScreenReaderEnabledProperty()
616 // can be true because of SuppressScreenReader before init
617 if(!mAccessibilityStatusClient)
622 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
625 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
626 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
628 if(!mReadScreenReaderEnabledTimer)
630 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
631 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
633 mReadScreenReaderEnabledTimer.Start();
638 if(mReadScreenReaderEnabledTimer)
640 mReadScreenReaderEnabledTimer.Stop();
641 mReadScreenReaderEnabledTimer.Reset();
644 mIsScreenReaderEnabled = std::get<0>(msg);
649 void EmitScreenReaderEnabledSignal()
651 if(mIsScreenReaderEnabled)
653 mScreenReaderEnabledSignal.Emit();
657 mScreenReaderDisabledSignal.Emit();
661 void ListenScreenReaderEnabledProperty()
663 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
664 mIsScreenReaderEnabled = res;
665 EmitScreenReaderEnabledSignal();
670 void ReadAndListenProperties()
672 ReadIsEnabledProperty();
673 ListenIsEnabledProperty();
675 ReadScreenReaderEnabledProperty();
676 ListenScreenReaderEnabledProperty();
679 bool InitializeAccessibilityStatusClient()
681 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
683 if(!mAccessibilityStatusClient)
685 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
692 bool InitializeTimerCallback()
694 if(InitializeAccessibilityStatusClient())
696 ReadAndListenProperties();
704 if(InitializeAccessibilityStatusClient())
706 ReadAndListenProperties();
707 mIdleCallback = NULL;
711 if(!mInitializeTimer)
713 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
714 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
716 mInitializeTimer.Start();
718 mIdleCallback = NULL;
723 * @copydoc Dali::Accessibility::Bridge::Initialize()
725 void Initialize() override
727 if(InitializeAccessibilityStatusClient())
729 ReadAndListenProperties();
733 // Initialize failed. Try it again on Idle
734 if(Dali::Adaptor::IsAvailable())
736 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
737 if(NULL == mIdleCallback)
739 mIdleCallback = MakeCallback(this, &BridgeImpl::OnIdleSignal);
740 adaptor.AddIdle(mIdleCallback, true);
746 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
748 bool GetScreenReaderEnabled() override
750 return mIsScreenReaderEnabled;
754 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
756 bool IsEnabled() override
761 Address EmbedSocket(const Address& plug, const Address& socket) override
763 auto client = CreateSocketClient(socket);
764 auto reply = client.method<Address(Address)>("Embed").call(plug);
768 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
772 return std::get<0>(reply.getValues());
775 void EmbedAtkSocket(const Address& plug, const Address& socket) override
777 auto client = CreateSocketClient(socket);
779 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
782 void UnembedSocket(const Address& plug, const Address& socket) override
784 auto client = CreateSocketClient(socket);
786 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
789 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
791 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
792 auto client = CreateSocketClient(socket->GetAddress());
794 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
798 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
802 mData->mExtentsOffset = {x, y};
806 void SetPreferredBusName(std::string_view preferredBusName) override
808 if(preferredBusName == mPreferredBusName)
813 std::string oldPreferredBusName = std::move(mPreferredBusName);
814 mPreferredBusName = std::string{preferredBusName};
818 ReleaseBusName(oldPreferredBusName);
819 RequestBusName(mPreferredBusName);
821 // else: request/release will be handled by ForceUp/ForceDown, respectively
825 DBus::DBusClient CreateSocketClient(const Address& socket)
827 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
830 void RequestBusName(const std::string& busName)
837 DBus::requestBusName(mConnectionPtr, busName);
840 void ReleaseBusName(const std::string& busName)
847 DBus::releaseBusName(mConnectionPtr, busName);
851 namespace // unnamed namespace
853 bool INITIALIZED_BRIDGE = false;
856 * @brief Creates BridgeImpl instance.
858 * @return The BridgeImpl instance
859 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
861 std::shared_ptr<Bridge> CreateBridge()
863 INITIALIZED_BRIDGE = true;
867 /* check environment variable first */
868 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
869 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
871 return Dali::Accessibility::DummyBridge::GetInstance();
874 return std::make_shared<BridgeImpl>();
876 catch(const std::exception&)
878 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
879 return Dali::Accessibility::DummyBridge::GetInstance();
883 } // unnamed namespace
885 // Dali::Accessibility::Bridge class implementation
887 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
889 static std::shared_ptr<Bridge> bridge;
895 else if(mAutoInitState == AutoInitState::ENABLED)
897 bridge = CreateBridge();
899 /* check environment variable for suppressing screen-reader */
900 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
901 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
903 bridge->SuppressScreenReader(true);
909 return Dali::Accessibility::DummyBridge::GetInstance();
912 void Bridge::DisableAutoInit()
914 if(INITIALIZED_BRIDGE)
916 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
919 mAutoInitState = AutoInitState::DISABLED;
922 void Bridge::EnableAutoInit()
924 mAutoInitState = AutoInitState::ENABLED;
926 if(INITIALIZED_BRIDGE)
931 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
932 auto window = Dali::DevelWindow::Get(rootLayer);
933 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
935 auto accessible = Accessibility::Accessible::Get(rootLayer);
937 auto bridge = Bridge::GetCurrentBridge();
938 bridge->AddTopLevelWindow(accessible);
939 bridge->SetApplicationName(applicationName);
940 bridge->Initialize();
942 if(window && window.IsVisible())
944 bridge->WindowShown(window);
948 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
950 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
951 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
953 static const char prefix[] = "com.samsung.dali.widget_";
954 static const char underscore = '_';
956 std::stringstream tmp;
960 for(char ch : widgetInstanceId)
962 tmp << (std::isalnum(ch) ? ch : underscore);