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 shown.
366 * @param[in] window The window to be shown
367 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
369 void EmitShown(Dali::Window window)
371 auto windowAccessible = mApplication.GetWindowAccessible(window);
374 windowAccessible->EmitShowing(true);
379 * @brief Sends a signal to dbus that the window is hidden.
381 * @param[in] window The window to be hidden
382 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
384 void EmitHidden(Dali::Window window)
386 auto windowAccessible = mApplication.GetWindowAccessible(window);
389 windowAccessible->EmitShowing(false);
394 * @brief Sends a signal to dbus that the window is activated.
396 * @param[in] window The window to be activated
397 * @see BridgeObject::Emit()
399 void EmitActivate(Dali::Window window)
401 auto windowAccessible = mApplication.GetWindowAccessible(window);
404 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
409 * @brief Sends a signal to dbus that the window is deactivated.
411 * @param[in] window The window to be deactivated
412 * @see BridgeObject::Emit()
414 void EmitDeactivate(Dali::Window window)
416 auto windowAccessible = mApplication.GetWindowAccessible(window);
419 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
424 * @brief Sends a signal to dbus that the window is minimized.
426 * @param[in] window The window to be minimized
427 * @see BridgeObject::Emit()
429 void EmitMinimize(Dali::Window window)
431 auto windowAccessible = mApplication.GetWindowAccessible(window);
434 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
439 * @brief Sends a signal to dbus that the window is restored.
441 * @param[in] window The window to be restored
442 * @param[in] detail Restored window state
443 * @see BridgeObject::Emit()
445 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
447 auto windowAccessible = mApplication.GetWindowAccessible(window);
450 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
455 * @brief Sends a signal to dbus that the window is maximized.
457 * @param[in] window The window to be maximized
458 * @see BridgeObject::Emit()
460 void EmitMaximize(Dali::Window window)
462 auto windowAccessible = mApplication.GetWindowAccessible(window);
465 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
470 * @copydoc Dali::Accessibility::Bridge::WindowShown()
472 void WindowShown(Dali::Window window) override
481 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
483 void WindowHidden(Dali::Window window) override
492 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
494 void WindowFocused(Dali::Window window) override
498 EmitActivate(window);
503 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
505 void WindowUnfocused(Dali::Window window) override
509 EmitDeactivate(window);
514 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
516 void WindowMinimized(Dali::Window window) override
520 EmitMinimize(window);
525 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
527 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
531 EmitRestore(window, detail);
536 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
538 void WindowMaximized(Dali::Window window) override
542 EmitMaximize(window);
547 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
549 void SuppressScreenReader(bool suppress) override
551 if(mIsScreenReaderSuppressed == suppress)
555 mIsScreenReaderSuppressed = suppress;
556 ReadScreenReaderEnabledProperty();
561 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
571 bool ReadIsEnabledTimerCallback()
573 ReadIsEnabledProperty();
577 void ReadIsEnabledProperty()
579 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
582 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
583 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
585 if(!mReadIsEnabledTimer)
587 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
588 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
590 mReadIsEnabledTimer.Start();
595 if(mReadIsEnabledTimer)
597 mReadIsEnabledTimer.Stop();
598 mReadIsEnabledTimer.Reset();
601 mIsEnabled = std::get<0>(msg);
606 void ListenIsEnabledProperty()
608 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
614 bool ReadScreenReaderEnabledTimerCallback()
616 ReadScreenReaderEnabledProperty();
620 void ReadScreenReaderEnabledProperty()
622 // can be true because of SuppressScreenReader before init
623 if(!mAccessibilityStatusClient)
628 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
631 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
632 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
634 if(!mReadScreenReaderEnabledTimer)
636 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
637 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
639 mReadScreenReaderEnabledTimer.Start();
644 if(mReadScreenReaderEnabledTimer)
646 mReadScreenReaderEnabledTimer.Stop();
647 mReadScreenReaderEnabledTimer.Reset();
650 mIsScreenReaderEnabled = std::get<0>(msg);
655 void EmitScreenReaderEnabledSignal()
657 if(mIsScreenReaderEnabled)
659 mScreenReaderEnabledSignal.Emit();
663 mScreenReaderDisabledSignal.Emit();
667 void ListenScreenReaderEnabledProperty()
669 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
670 mIsScreenReaderEnabled = res;
671 EmitScreenReaderEnabledSignal();
676 void ReadAndListenProperties()
678 ReadIsEnabledProperty();
679 ListenIsEnabledProperty();
681 ReadScreenReaderEnabledProperty();
682 ListenScreenReaderEnabledProperty();
685 bool InitializeAccessibilityStatusClient()
687 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
689 if(!mAccessibilityStatusClient)
691 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
698 bool InitializeTimerCallback()
700 if(InitializeAccessibilityStatusClient())
702 ReadAndListenProperties();
710 if(InitializeAccessibilityStatusClient())
712 ReadAndListenProperties();
713 mIdleCallback = NULL;
717 if(!mInitializeTimer)
719 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
720 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
722 mInitializeTimer.Start();
724 mIdleCallback = NULL;
729 * @copydoc Dali::Accessibility::Bridge::Initialize()
731 void Initialize() override
733 if(InitializeAccessibilityStatusClient())
735 ReadAndListenProperties();
739 // Initialize failed. Try it again on Idle
740 if(Dali::Adaptor::IsAvailable())
742 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
743 if(NULL == mIdleCallback)
745 mIdleCallback = MakeCallback(this, &BridgeImpl::OnIdleSignal);
746 adaptor.AddIdle(mIdleCallback, true);
752 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
754 bool GetScreenReaderEnabled() override
756 return mIsScreenReaderEnabled;
760 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
762 bool IsEnabled() override
767 Address EmbedSocket(const Address& plug, const Address& socket) override
769 auto client = CreateSocketClient(socket);
770 auto reply = client.method<Address(Address)>("Embed").call(plug);
774 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
778 return std::get<0>(reply.getValues());
781 void EmbedAtkSocket(const Address& plug, const Address& socket) override
783 auto client = CreateSocketClient(socket);
785 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
788 void UnembedSocket(const Address& plug, const Address& socket) override
790 auto client = CreateSocketClient(socket);
792 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
795 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
797 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
798 auto client = CreateSocketClient(socket->GetAddress());
800 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
804 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
808 mData->mExtentsOffset = {x, y};
812 void SetPreferredBusName(std::string_view preferredBusName) override
814 if(preferredBusName == mPreferredBusName)
819 std::string oldPreferredBusName = std::move(mPreferredBusName);
820 mPreferredBusName = std::string{preferredBusName};
824 ReleaseBusName(oldPreferredBusName);
825 RequestBusName(mPreferredBusName);
827 // else: request/release will be handled by ForceUp/ForceDown, respectively
831 DBus::DBusClient CreateSocketClient(const Address& socket)
833 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
836 void RequestBusName(const std::string& busName)
843 DBus::requestBusName(mConnectionPtr, busName);
846 void ReleaseBusName(const std::string& busName)
853 DBus::releaseBusName(mConnectionPtr, busName);
857 namespace // unnamed namespace
859 bool INITIALIZED_BRIDGE = false;
862 * @brief Creates BridgeImpl instance.
864 * @return The BridgeImpl instance
865 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
867 std::shared_ptr<Bridge> CreateBridge()
869 INITIALIZED_BRIDGE = true;
873 /* check environment variable first */
874 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
875 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
877 return Dali::Accessibility::DummyBridge::GetInstance();
880 return std::make_shared<BridgeImpl>();
882 catch(const std::exception&)
884 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
885 return Dali::Accessibility::DummyBridge::GetInstance();
889 } // unnamed namespace
891 // Dali::Accessibility::Bridge class implementation
893 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
895 static std::shared_ptr<Bridge> bridge;
901 else if(mAutoInitState == AutoInitState::ENABLED)
903 bridge = CreateBridge();
905 /* check environment variable for suppressing screen-reader */
906 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
907 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
909 bridge->SuppressScreenReader(true);
915 return Dali::Accessibility::DummyBridge::GetInstance();
918 void Bridge::DisableAutoInit()
920 if(INITIALIZED_BRIDGE)
922 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
925 mAutoInitState = AutoInitState::DISABLED;
928 void Bridge::EnableAutoInit()
930 mAutoInitState = AutoInitState::ENABLED;
932 if(INITIALIZED_BRIDGE)
937 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
938 auto window = Dali::DevelWindow::Get(rootLayer);
939 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
941 auto accessible = Accessibility::Accessible::Get(rootLayer);
943 auto bridge = Bridge::GetCurrentBridge();
944 bridge->AddTopLevelWindow(accessible);
945 bridge->SetApplicationName(applicationName);
946 bridge->Initialize();
948 if(window && window.IsVisible())
950 bridge->WindowShown(window);
954 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
956 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
957 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
959 static const char prefix[] = "com.samsung.dali.widget_";
960 static const char underscore = '_';
962 std::stringstream tmp;
966 for(char ch : widgetInstanceId)
968 tmp << (std::isalnum(ch) ? ch : underscore);