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:
122 auto methodObject = mRegistryClient.method<bool(std::tuple<uint32_t, int32_t, int32_t, int32_t, int32_t, std::string, bool>)>("NotifyListenersSync");
123 auto result = methodObject.call(std::tuple<uint32_t, int32_t, int32_t, int32_t, int32_t, std::string, bool>{keyType, 0, static_cast<int32_t>(keyCode), 0, static_cast<int32_t>(timeStamp), keyName, isText ? 1 : 0});
126 LOG() << result.getError().message;
129 return std::get<0>(result) ? Consumed::YES : Consumed::NO;
133 * @copydoc Dali::Accessibility::Bridge::Pause()
135 void Pause() override
142 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
145 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
152 * @copydoc Dali::Accessibility::Bridge::Resume()
154 void Resume() override
161 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
164 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
171 * @copydoc Dali::Accessibility::Bridge::StopReading()
173 void StopReading(bool alsoNonDiscardable) override
180 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
183 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
190 * @copydoc Dali::Accessibility::Bridge::Say()
192 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
199 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
202 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
206 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
214 * @copydoc Dali::Accessibility::Bridge::ForceDown()
216 void ForceDown() override
220 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
222 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
224 mData->mCurrentlyHighlightedActor = {};
225 mData->mHighlightActor = {};
227 mDisabledSignal.Emit();
228 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
229 ReleaseBusName(mPreferredBusName);
232 mHighlightedActor = {};
233 mHighlightClearAction = {};
234 BridgeAccessible::ForceDown();
235 mRegistryClient = {};
236 mDirectReadingClient = {};
237 mDirectReadingCallbacks.clear();
238 mApplication.mChildren.clear();
246 mInitializeTimer.Stop();
247 mInitializeTimer.Reset();
250 if(mReadIsEnabledTimer)
252 mReadIsEnabledTimer.Stop();
253 mReadIsEnabledTimer.Reset();
256 if(mReadScreenReaderEnabledTimer)
258 mReadScreenReaderEnabledTimer.Stop();
259 mReadScreenReaderEnabledTimer.Reset();
264 mForceUpTimer.Stop();
265 mForceUpTimer.Reset();
270 * @copydoc Dali::Accessibility::Bridge::Terminate()
272 void Terminate() override
276 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
277 for(auto windowAccessible : mApplication.mChildren)
279 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
281 mData->mCurrentlyHighlightedActor = {};
282 mData->mHighlightActor = {};
285 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
287 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
289 mAccessibilityStatusClient = {};
294 bool ForceUpTimerCallback()
296 if(ForceUp() != ForceUpResult::FAILED)
304 * @copydoc Dali::Accessibility::Bridge::ForceUp()
306 ForceUpResult ForceUp() override
308 auto forceUpResult = BridgeAccessible::ForceUp();
309 if(forceUpResult == ForceUpResult::ALREADY_UP)
311 return forceUpResult;
313 else if(forceUpResult == ForceUpResult::FAILED)
317 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
318 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
319 mForceUpTimer.Start();
321 return forceUpResult;
324 BridgeObject::RegisterInterfaces();
325 BridgeAccessible::RegisterInterfaces();
326 BridgeComponent::RegisterInterfaces();
327 BridgeCollection::RegisterInterfaces();
328 BridgeAction::RegisterInterfaces();
329 BridgeValue::RegisterInterfaces();
330 BridgeText::RegisterInterfaces();
331 BridgeEditableText::RegisterInterfaces();
332 BridgeSelection::RegisterInterfaces();
333 BridgeApplication::RegisterInterfaces();
334 BridgeHypertext::RegisterInterfaces();
335 BridgeHyperlink::RegisterInterfaces();
336 BridgeSocket::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 return std::make_shared<BridgeImpl>();
883 catch(const std::exception&)
885 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
886 return Dali::Accessibility::DummyBridge::GetInstance();
890 } // unnamed namespace
892 // Dali::Accessibility::Bridge class implementation
894 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
896 static std::shared_ptr<Bridge> bridge;
902 else if(mAutoInitState == AutoInitState::ENABLED)
904 bridge = CreateBridge();
906 /* check environment variable for suppressing screen-reader */
907 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
908 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
910 bridge->SuppressScreenReader(true);
916 return Dali::Accessibility::DummyBridge::GetInstance();
919 void Bridge::DisableAutoInit()
921 if(INITIALIZED_BRIDGE)
923 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
926 mAutoInitState = AutoInitState::DISABLED;
929 void Bridge::EnableAutoInit()
931 mAutoInitState = AutoInitState::ENABLED;
933 if(INITIALIZED_BRIDGE)
938 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
939 auto window = Dali::DevelWindow::Get(rootLayer);
940 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
942 auto accessible = Accessibility::Accessible::Get(rootLayer);
944 auto bridge = Bridge::GetCurrentBridge();
945 bridge->AddTopLevelWindow(accessible);
946 bridge->SetApplicationName(applicationName);
947 bridge->Initialize();
949 if(window && window.IsVisible())
951 bridge->WindowShown(window);
955 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
957 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
958 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
960 static const char prefix[] = "com.samsung.dali.widget_";
961 static const char underscore = '_';
963 std::stringstream tmp;
967 for(char ch : widgetInstanceId)
969 tmp << (std::isalnum(ch) ? ch : underscore);