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>
51 using namespace Dali::Accessibility;
53 namespace // unnamed namespace
55 const int RETRY_INTERVAL = 1000;
57 } // unnamed namespace
60 * @brief The BridgeImpl class is to implement some Bridge functions.
62 class BridgeImpl : public virtual BridgeBase,
63 public BridgeAccessible,
65 public BridgeComponent,
66 public BridgeCollection,
70 public BridgeEditableText,
71 public BridgeSelection,
72 public BridgeApplication,
73 public BridgeHypertext,
74 public BridgeHyperlink,
77 public BridgeTableCell
79 DBus::DBusClient mAccessibilityStatusClient{};
80 DBus::DBusClient mRegistryClient{};
81 DBus::DBusClient mDirectReadingClient{};
82 bool mIsScreenReaderEnabled{false};
83 bool mIsEnabled{false};
84 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks{};
85 Dali::Actor mHighlightedActor;
86 std::function<void(Dali::Actor)> mHighlightClearAction{nullptr};
87 Dali::CallbackBase* mIdleCallback{};
88 Dali::Timer mInitializeTimer;
89 Dali::Timer mReadIsEnabledTimer;
90 Dali::Timer mReadScreenReaderEnabledTimer;
91 Dali::Timer mForceUpTimer;
92 std::string mPreferredBusName;
95 BridgeImpl() = default;
98 * @copydoc Dali::Accessibility::Bridge::Emit()
100 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
107 unsigned int keyType = 0;
111 case KeyEventType::KEY_PRESSED:
116 case KeyEventType::KEY_RELEASED:
131 * @copydoc Dali::Accessibility::Bridge::Pause()
133 void Pause() override
140 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
143 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
150 * @copydoc Dali::Accessibility::Bridge::Resume()
152 void Resume() override
159 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
162 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
169 * @copydoc Dali::Accessibility::Bridge::StopReading()
171 void StopReading(bool alsoNonDiscardable) override
178 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
181 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
188 * @copydoc Dali::Accessibility::Bridge::Say()
190 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
197 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
200 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
204 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
212 * @copydoc Dali::Accessibility::Bridge::ForceDown()
214 void ForceDown() override
218 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
220 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
222 mData->mCurrentlyHighlightedActor = {};
223 mData->mHighlightActor = {};
225 mDisabledSignal.Emit();
226 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
227 ReleaseBusName(mPreferredBusName);
230 mHighlightedActor = {};
231 mHighlightClearAction = {};
232 BridgeAccessible::ForceDown();
233 mRegistryClient = {};
234 mDirectReadingClient = {};
235 mDirectReadingCallbacks.clear();
236 mApplication.mChildren.clear();
244 mInitializeTimer.Stop();
245 mInitializeTimer.Reset();
248 if(mReadIsEnabledTimer)
250 mReadIsEnabledTimer.Stop();
251 mReadIsEnabledTimer.Reset();
254 if(mReadScreenReaderEnabledTimer)
256 mReadScreenReaderEnabledTimer.Stop();
257 mReadScreenReaderEnabledTimer.Reset();
262 mForceUpTimer.Stop();
263 mForceUpTimer.Reset();
268 * @copydoc Dali::Accessibility::Bridge::Terminate()
270 void Terminate() override
274 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
275 for(auto windowAccessible : mApplication.mChildren)
277 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
279 mData->mCurrentlyHighlightedActor = {};
280 mData->mHighlightActor = {};
283 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
285 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
287 mAccessibilityStatusClient = {};
292 bool ForceUpTimerCallback()
294 if(ForceUp() != ForceUpResult::FAILED)
302 * @copydoc Dali::Accessibility::Bridge::ForceUp()
304 ForceUpResult ForceUp() override
306 auto forceUpResult = BridgeAccessible::ForceUp();
307 if(forceUpResult == ForceUpResult::ALREADY_UP)
309 return forceUpResult;
311 else if(forceUpResult == ForceUpResult::FAILED)
315 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
316 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
317 mForceUpTimer.Start();
319 return forceUpResult;
322 BridgeObject::RegisterInterfaces();
323 BridgeAccessible::RegisterInterfaces();
324 BridgeComponent::RegisterInterfaces();
325 BridgeCollection::RegisterInterfaces();
326 BridgeAction::RegisterInterfaces();
327 BridgeValue::RegisterInterfaces();
328 BridgeText::RegisterInterfaces();
329 BridgeEditableText::RegisterInterfaces();
330 BridgeSelection::RegisterInterfaces();
331 BridgeApplication::RegisterInterfaces();
332 BridgeHypertext::RegisterInterfaces();
333 BridgeHyperlink::RegisterInterfaces();
334 BridgeSocket::RegisterInterfaces();
335 BridgeTable::RegisterInterfaces();
336 BridgeTableCell::RegisterInterfaces();
338 RegisterOnBridge(&mApplication);
340 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
341 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
343 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
344 auto it = mDirectReadingCallbacks.find(id);
345 if(it != mDirectReadingCallbacks.end())
347 it->second(readingState);
348 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
350 mDirectReadingCallbacks.erase(it);
355 RequestBusName(mPreferredBusName);
357 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
358 mApplication.mParent.SetAddress(std::move(parentAddress));
359 mEnabledSignal.Emit();
361 return ForceUpResult::JUST_STARTED;
365 * @brief Sends a signal to dbus that the window is shown.
367 * @param[in] window The window to be shown
368 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
370 void EmitShown(Dali::Window window)
372 auto windowAccessible = mApplication.GetWindowAccessible(window);
375 windowAccessible->EmitShowing(true);
380 * @brief Sends a signal to dbus that the window is hidden.
382 * @param[in] window The window to be hidden
383 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
385 void EmitHidden(Dali::Window window)
387 auto windowAccessible = mApplication.GetWindowAccessible(window);
390 windowAccessible->EmitShowing(false);
395 * @brief Sends a signal to dbus that the window is activated.
397 * @param[in] window The window to be activated
398 * @see BridgeObject::Emit()
400 void EmitActivate(Dali::Window window)
402 auto windowAccessible = mApplication.GetWindowAccessible(window);
405 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
410 * @brief Sends a signal to dbus that the window is deactivated.
412 * @param[in] window The window to be deactivated
413 * @see BridgeObject::Emit()
415 void EmitDeactivate(Dali::Window window)
417 auto windowAccessible = mApplication.GetWindowAccessible(window);
420 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
425 * @brief Sends a signal to dbus that the window is minimized.
427 * @param[in] window The window to be minimized
428 * @see BridgeObject::Emit()
430 void EmitMinimize(Dali::Window window)
432 auto windowAccessible = mApplication.GetWindowAccessible(window);
435 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
440 * @brief Sends a signal to dbus that the window is restored.
442 * @param[in] window The window to be restored
443 * @param[in] detail Restored window state
444 * @see BridgeObject::Emit()
446 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
448 auto windowAccessible = mApplication.GetWindowAccessible(window);
451 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
456 * @brief Sends a signal to dbus that the window is maximized.
458 * @param[in] window The window to be maximized
459 * @see BridgeObject::Emit()
461 void EmitMaximize(Dali::Window window)
463 auto windowAccessible = mApplication.GetWindowAccessible(window);
466 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
471 * @copydoc Dali::Accessibility::Bridge::WindowShown()
473 void WindowShown(Dali::Window window) override
482 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
484 void WindowHidden(Dali::Window window) override
493 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
495 void WindowFocused(Dali::Window window) override
499 EmitActivate(window);
504 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
506 void WindowUnfocused(Dali::Window window) override
510 EmitDeactivate(window);
515 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
517 void WindowMinimized(Dali::Window window) override
521 EmitMinimize(window);
526 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
528 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
532 EmitRestore(window, detail);
537 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
539 void WindowMaximized(Dali::Window window) override
543 EmitMaximize(window);
548 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
550 void SuppressScreenReader(bool suppress) override
552 if(mIsScreenReaderSuppressed == suppress)
556 mIsScreenReaderSuppressed = suppress;
557 ReadScreenReaderEnabledProperty();
562 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
572 bool ReadIsEnabledTimerCallback()
574 ReadIsEnabledProperty();
578 void ReadIsEnabledProperty()
580 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
583 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
584 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
586 if(!mReadIsEnabledTimer)
588 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
589 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
591 mReadIsEnabledTimer.Start();
596 if(mReadIsEnabledTimer)
598 mReadIsEnabledTimer.Stop();
599 mReadIsEnabledTimer.Reset();
602 mIsEnabled = std::get<0>(msg);
607 void ListenIsEnabledProperty()
609 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
615 bool ReadScreenReaderEnabledTimerCallback()
617 ReadScreenReaderEnabledProperty();
621 void ReadScreenReaderEnabledProperty()
623 // can be true because of SuppressScreenReader before init
624 if(!mAccessibilityStatusClient)
629 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
632 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
633 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
635 if(!mReadScreenReaderEnabledTimer)
637 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
638 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
640 mReadScreenReaderEnabledTimer.Start();
645 if(mReadScreenReaderEnabledTimer)
647 mReadScreenReaderEnabledTimer.Stop();
648 mReadScreenReaderEnabledTimer.Reset();
651 mIsScreenReaderEnabled = std::get<0>(msg);
656 void EmitScreenReaderEnabledSignal()
658 if(mIsScreenReaderEnabled)
660 mScreenReaderEnabledSignal.Emit();
664 mScreenReaderDisabledSignal.Emit();
668 void ListenScreenReaderEnabledProperty()
670 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
671 mIsScreenReaderEnabled = res;
672 EmitScreenReaderEnabledSignal();
677 void ReadAndListenProperties()
679 ReadIsEnabledProperty();
680 ListenIsEnabledProperty();
682 ReadScreenReaderEnabledProperty();
683 ListenScreenReaderEnabledProperty();
686 bool InitializeAccessibilityStatusClient()
688 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
690 if(!mAccessibilityStatusClient)
692 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
699 bool InitializeTimerCallback()
701 if(InitializeAccessibilityStatusClient())
703 ReadAndListenProperties();
711 if(InitializeAccessibilityStatusClient())
713 ReadAndListenProperties();
714 mIdleCallback = NULL;
718 if(!mInitializeTimer)
720 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
721 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
723 mInitializeTimer.Start();
725 mIdleCallback = NULL;
730 * @copydoc Dali::Accessibility::Bridge::Initialize()
732 void Initialize() override
734 if(InitializeAccessibilityStatusClient())
736 ReadAndListenProperties();
740 // Initialize failed. Try it again on Idle
741 if(Dali::Adaptor::IsAvailable())
743 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
744 if(NULL == mIdleCallback)
746 mIdleCallback = MakeCallback(this, &BridgeImpl::OnIdleSignal);
747 adaptor.AddIdle(mIdleCallback, true);
753 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
755 bool GetScreenReaderEnabled() override
757 return mIsScreenReaderEnabled;
761 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
763 bool IsEnabled() override
768 Address EmbedSocket(const Address& plug, const Address& socket) override
770 auto client = CreateSocketClient(socket);
771 auto reply = client.method<Address(Address)>("Embed").call(plug);
775 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
779 return std::get<0>(reply.getValues());
782 void EmbedAtkSocket(const Address& plug, const Address& socket) override
784 auto client = CreateSocketClient(socket);
786 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
789 void UnembedSocket(const Address& plug, const Address& socket) override
791 auto client = CreateSocketClient(socket);
793 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
796 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
798 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
799 auto client = CreateSocketClient(socket->GetAddress());
801 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
805 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
809 mData->mExtentsOffset = {x, y};
813 void SetPreferredBusName(std::string_view preferredBusName) override
815 if(preferredBusName == mPreferredBusName)
820 std::string oldPreferredBusName = std::move(mPreferredBusName);
821 mPreferredBusName = std::string{preferredBusName};
825 ReleaseBusName(oldPreferredBusName);
826 RequestBusName(mPreferredBusName);
828 // else: request/release will be handled by ForceUp/ForceDown, respectively
832 DBus::DBusClient CreateSocketClient(const Address& socket)
834 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
837 void RequestBusName(const std::string& busName)
844 DBus::requestBusName(mConnectionPtr, busName);
847 void ReleaseBusName(const std::string& busName)
854 DBus::releaseBusName(mConnectionPtr, busName);
858 namespace // unnamed namespace
860 bool INITIALIZED_BRIDGE = false;
863 * @brief Creates BridgeImpl instance.
865 * @return The BridgeImpl instance
866 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
868 std::shared_ptr<Bridge> CreateBridge()
870 INITIALIZED_BRIDGE = true;
874 /* Check environment variable first */
875 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
876 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
878 return Dali::Accessibility::DummyBridge::GetInstance();
881 // Check if the image is either release or perf mode
882 if((access("/etc/release", F_OK) == 0) || (access("/etc/perf", F_OK) == 0))
884 return Dali::Accessibility::DummyBridge::GetInstance();
887 return std::make_shared<BridgeImpl>();
889 catch(const std::exception&)
891 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
892 return Dali::Accessibility::DummyBridge::GetInstance();
896 } // unnamed namespace
898 // Dali::Accessibility::Bridge class implementation
900 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
902 static std::shared_ptr<Bridge> bridge;
908 else if(mAutoInitState == AutoInitState::ENABLED)
910 bridge = CreateBridge();
912 /* check environment variable for suppressing screen-reader */
913 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
914 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
916 bridge->SuppressScreenReader(true);
922 return Dali::Accessibility::DummyBridge::GetInstance();
925 void Bridge::DisableAutoInit()
927 if(INITIALIZED_BRIDGE)
929 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
932 mAutoInitState = AutoInitState::DISABLED;
935 void Bridge::EnableAutoInit()
937 mAutoInitState = AutoInitState::ENABLED;
939 if(INITIALIZED_BRIDGE)
944 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
945 auto window = Dali::DevelWindow::Get(rootLayer);
946 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
948 auto accessible = Accessibility::Accessible::Get(rootLayer);
950 auto bridge = Bridge::GetCurrentBridge();
951 bridge->AddTopLevelWindow(accessible);
952 bridge->SetApplicationName(applicationName);
953 bridge->Initialize();
955 if(window && window.IsVisible())
957 bridge->WindowShown(window);
961 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
963 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
964 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
966 static const char prefix[] = "com.samsung.dali.widget_";
967 static const char underscore = '_';
969 std::stringstream tmp;
973 for(char ch : widgetInstanceId)
975 tmp << (std::isalnum(ch) ? ch : underscore);