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-collection.h>
34 #include <dali/internal/accessibility/bridge/bridge-component.h>
35 #include <dali/internal/accessibility/bridge/bridge-editable-text.h>
36 #include <dali/internal/accessibility/bridge/bridge-hypertext.h>
37 #include <dali/internal/accessibility/bridge/bridge-hyperlink.h>
38 #include <dali/internal/accessibility/bridge/bridge-object.h>
39 #include <dali/internal/accessibility/bridge/bridge-selection.h>
40 #include <dali/internal/accessibility/bridge/bridge-socket.h>
41 #include <dali/internal/accessibility/bridge/bridge-text.h>
42 #include <dali/internal/accessibility/bridge/bridge-value.h>
43 #include <dali/internal/accessibility/bridge/bridge-application.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
53 const int RETRY_INTERVAL = 1000;
55 } // unnamed namespace
58 * @brief The BridgeImpl class is to implement some Bridge functions.
60 class BridgeImpl : public virtual BridgeBase,
61 public BridgeAccessible,
63 public BridgeComponent,
64 public BridgeCollection,
68 public BridgeEditableText,
69 public BridgeSelection,
70 public BridgeApplication,
71 public BridgeHypertext,
72 public BridgeHyperlink,
75 DBus::DBusClient mAccessibilityStatusClient;
76 DBus::DBusClient mRegistryClient;
77 DBus::DBusClient mDirectReadingClient;
78 bool mIsScreenReaderEnabled = false;
79 bool mIsEnabled = false;
80 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks;
81 Dali::Actor mHighlightedActor;
82 std::function<void(Dali::Actor)> mHighlightClearAction;
83 Dali::CallbackBase* mIdleCallback = NULL;
84 Dali::Timer mInitializeTimer;
85 Dali::Timer mReadIsEnabledTimer;
86 Dali::Timer mReadScreenReaderEnabledTimer;
87 Dali::Timer mForceUpTimer;
88 std::string mPreferredBusName;
96 * @copydoc Dali::Accessibility::Bridge::Emit()
98 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
105 unsigned int keyType = 0;
109 case KeyEventType::KEY_PRESSED:
114 case KeyEventType::KEY_RELEASED:
125 auto methodObject = mRegistryClient.method<bool(std::tuple<uint32_t, int32_t, int32_t, int32_t, int32_t, std::string, bool>)>("NotifyListenersSync");
126 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});
129 LOG() << result.getError().message;
132 return std::get<0>(result) ? Consumed::YES : Consumed::NO;
136 * @copydoc Dali::Accessibility::Bridge::Pause()
138 void Pause() override
145 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
148 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
155 * @copydoc Dali::Accessibility::Bridge::Resume()
157 void Resume() override
164 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
167 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
174 * @copydoc Dali::Accessibility::Bridge::StopReading()
176 void StopReading(bool alsoNonDiscardable) override
183 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
186 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
193 * @copydoc Dali::Accessibility::Bridge::Say()
195 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
202 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
205 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
209 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
217 * @copydoc Dali::Accessibility::Bridge::ForceDown()
219 void ForceDown() override
223 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
225 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
227 mData->mCurrentlyHighlightedActor = {};
228 mData->mHighlightActor = {};
230 mDisabledSignal.Emit();
231 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
232 ReleaseBusName(mPreferredBusName);
235 mHighlightedActor = {};
236 mHighlightClearAction = {};
237 BridgeAccessible::ForceDown();
238 mRegistryClient = {};
239 mDirectReadingClient = {};
240 mDirectReadingCallbacks.clear();
241 mApplication.mChildren.clear();
249 mInitializeTimer.Stop();
250 mInitializeTimer.Reset();
253 if(mReadIsEnabledTimer)
255 mReadIsEnabledTimer.Stop();
256 mReadIsEnabledTimer.Reset();
259 if(mReadScreenReaderEnabledTimer)
261 mReadScreenReaderEnabledTimer.Stop();
262 mReadScreenReaderEnabledTimer.Reset();
267 mForceUpTimer.Stop();
268 mForceUpTimer.Reset();
273 * @copydoc Dali::Accessibility::Bridge::Terminate()
275 void Terminate() override
279 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
280 for(auto windowAccessible : mApplication.mChildren)
282 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
284 mData->mCurrentlyHighlightedActor = {};
285 mData->mHighlightActor = {};
288 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
290 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
292 mAccessibilityStatusClient = {};
297 bool ForceUpTimerCallback()
299 if(ForceUp() != ForceUpResult::FAILED)
307 * @copydoc Dali::Accessibility::Bridge::ForceUp()
309 ForceUpResult ForceUp() override
311 auto forceUpResult = BridgeAccessible::ForceUp();
312 if(forceUpResult == ForceUpResult::ALREADY_UP)
314 return forceUpResult;
316 else if(forceUpResult == ForceUpResult::FAILED)
320 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
321 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
322 mForceUpTimer.Start();
324 return forceUpResult;
327 BridgeObject::RegisterInterfaces();
328 BridgeAccessible::RegisterInterfaces();
329 BridgeComponent::RegisterInterfaces();
330 BridgeCollection::RegisterInterfaces();
331 BridgeAction::RegisterInterfaces();
332 BridgeValue::RegisterInterfaces();
333 BridgeText::RegisterInterfaces();
334 BridgeEditableText::RegisterInterfaces();
335 BridgeSelection::RegisterInterfaces();
336 BridgeApplication::RegisterInterfaces();
337 BridgeHypertext::RegisterInterfaces();
338 BridgeHyperlink::RegisterInterfaces();
339 BridgeSocket::RegisterInterfaces();
341 RegisterOnBridge(&mApplication);
343 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
344 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
346 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
347 auto it = mDirectReadingCallbacks.find(id);
348 if(it != mDirectReadingCallbacks.end())
350 it->second(readingState);
351 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
353 mDirectReadingCallbacks.erase(it);
358 RequestBusName(mPreferredBusName);
360 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
361 mApplication.mParent.SetAddress(std::move(parentAddress));
362 mEnabledSignal.Emit();
364 return ForceUpResult::JUST_STARTED;
368 * @brief Sends a signal to dbus that the window is shown.
370 * @param[in] window The window to be shown
371 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
373 void EmitShown(Dali::Window window)
375 auto windowAccessible = mApplication.GetWindowAccessible(window);
378 windowAccessible->EmitShowing(true);
383 * @brief Sends a signal to dbus that the window is hidden.
385 * @param[in] window The window to be hidden
386 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
388 void EmitHidden(Dali::Window window)
390 auto windowAccessible = mApplication.GetWindowAccessible(window);
393 windowAccessible->EmitShowing(false);
398 * @brief Sends a signal to dbus that the window is activated.
400 * @param[in] window The window to be activated
401 * @see BridgeObject::Emit()
403 void EmitActivate(Dali::Window window)
405 auto windowAccessible = mApplication.GetWindowAccessible(window);
408 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
413 * @brief Sends a signal to dbus that the window is deactivated.
415 * @param[in] window The window to be deactivated
416 * @see BridgeObject::Emit()
418 void EmitDeactivate(Dali::Window window)
420 auto windowAccessible = mApplication.GetWindowAccessible(window);
423 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
428 * @brief Sends a signal to dbus that the window is minimized.
430 * @param[in] window The window to be minimized
431 * @see BridgeObject::Emit()
433 void EmitMinimize(Dali::Window window)
435 auto windowAccessible = mApplication.GetWindowAccessible(window);
438 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
443 * @brief Sends a signal to dbus that the window is restored.
445 * @param[in] window The window to be restored
446 * @param[in] detail Restored window state
447 * @see BridgeObject::Emit()
449 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
451 auto windowAccessible = mApplication.GetWindowAccessible(window);
454 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
459 * @brief Sends a signal to dbus that the window is maximized.
461 * @param[in] window The window to be maximized
462 * @see BridgeObject::Emit()
464 void EmitMaximize(Dali::Window window)
466 auto windowAccessible = mApplication.GetWindowAccessible(window);
469 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
474 * @copydoc Dali::Accessibility::Bridge::WindowShown()
476 void WindowShown(Dali::Window window) override
485 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
487 void WindowHidden(Dali::Window window) override
496 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
498 void WindowFocused(Dali::Window window) override
502 EmitActivate(window);
507 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
509 void WindowUnfocused(Dali::Window window) override
513 EmitDeactivate(window);
518 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
520 void WindowMinimized(Dali::Window window) override
524 EmitMinimize(window);
529 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
531 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
535 EmitRestore(window, detail);
540 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
542 void WindowMaximized(Dali::Window window) override
546 EmitMaximize(window);
551 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
553 void SuppressScreenReader(bool suppress) override
555 if(mIsScreenReaderSuppressed == suppress)
559 mIsScreenReaderSuppressed = suppress;
560 ReadScreenReaderEnabledProperty();
565 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
575 bool ReadIsEnabledTimerCallback()
577 ReadIsEnabledProperty();
581 void ReadIsEnabledProperty()
583 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
586 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
587 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
589 if(!mReadIsEnabledTimer)
591 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
592 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
594 mReadIsEnabledTimer.Start();
599 if(mReadIsEnabledTimer)
601 mReadIsEnabledTimer.Stop();
602 mReadIsEnabledTimer.Reset();
605 mIsEnabled = std::get<0>(msg);
610 void ListenIsEnabledProperty()
612 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
618 bool ReadScreenReaderEnabledTimerCallback()
620 ReadScreenReaderEnabledProperty();
624 void ReadScreenReaderEnabledProperty()
626 // can be true because of SuppressScreenReader before init
627 if (!mAccessibilityStatusClient)
632 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
635 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
636 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
638 if(!mReadScreenReaderEnabledTimer)
640 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
641 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
643 mReadScreenReaderEnabledTimer.Start();
648 if(mReadScreenReaderEnabledTimer)
650 mReadScreenReaderEnabledTimer.Stop();
651 mReadScreenReaderEnabledTimer.Reset();
654 mIsScreenReaderEnabled = std::get<0>(msg);
659 void EmitScreenReaderEnabledSignal()
661 if (mIsScreenReaderEnabled)
663 mScreenReaderEnabledSignal.Emit();
667 mScreenReaderDisabledSignal.Emit();
671 void ListenScreenReaderEnabledProperty()
673 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
674 mIsScreenReaderEnabled = res;
675 EmitScreenReaderEnabledSignal();
680 void ReadAndListenProperties()
682 ReadIsEnabledProperty();
683 ListenIsEnabledProperty();
685 ReadScreenReaderEnabledProperty();
686 ListenScreenReaderEnabledProperty();
689 bool InitializeAccessibilityStatusClient()
691 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
693 if (!mAccessibilityStatusClient)
695 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
702 bool InitializeTimerCallback()
704 if ( InitializeAccessibilityStatusClient() )
706 ReadAndListenProperties();
714 if ( InitializeAccessibilityStatusClient() )
716 ReadAndListenProperties();
717 mIdleCallback = NULL;
721 if(!mInitializeTimer)
723 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
724 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
726 mInitializeTimer.Start();
728 mIdleCallback = NULL;
733 * @copydoc Dali::Accessibility::Bridge::Initialize()
735 void Initialize() override
737 if ( InitializeAccessibilityStatusClient() )
739 ReadAndListenProperties();
743 // Initialize failed. Try it again on Idle
744 if( Dali::Adaptor::IsAvailable() )
746 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
747 if( NULL == mIdleCallback )
749 mIdleCallback = MakeCallback( this, &BridgeImpl::OnIdleSignal );
750 adaptor.AddIdle( mIdleCallback, true );
756 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
758 bool GetScreenReaderEnabled() override
760 return mIsScreenReaderEnabled;
764 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
766 bool IsEnabled() override
771 Address EmbedSocket(const Address& plug, const Address& socket) override
773 auto client = CreateSocketClient(socket);
774 auto reply = client.method<Address(Address)>("Embed").call(plug);
778 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
782 return std::get<0>(reply.getValues());
785 void EmbedAtkSocket(const Address& plug, const Address& socket) override
787 auto client = CreateSocketClient(socket);
789 client.method<void(std::string)>("Embedded").call(ATSPI_PREFIX_PATH + plug.GetPath());
792 void UnembedSocket(const Address& plug, const Address& socket) override
794 auto client = CreateSocketClient(socket);
796 client.method<void(Address)>("Unembed").call(plug);
799 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
801 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
802 auto client = CreateSocketClient(socket->GetAddress());
804 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
808 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
812 mData->mExtentsOffset = {x, y};
816 void SetPreferredBusName(std::string_view preferredBusName) override
818 if(preferredBusName == mPreferredBusName)
823 std::string oldPreferredBusName = std::move(mPreferredBusName);
824 mPreferredBusName = std::string{preferredBusName};
828 ReleaseBusName(oldPreferredBusName);
829 RequestBusName(mPreferredBusName);
831 // else: request/release will be handled by ForceUp/ForceDown, respectively
835 DBus::DBusClient CreateSocketClient(const Address& socket)
837 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
840 void RequestBusName(const std::string& busName)
847 DBus::requestBusName(mConnectionPtr, busName);
850 void ReleaseBusName(const std::string& busName)
857 DBus::releaseBusName(mConnectionPtr, busName);
861 namespace // unnamed namespace
864 bool INITIALIZED_BRIDGE = false;
867 * @brief Creates BridgeImpl instance.
869 * @return The BridgeImpl instance
870 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
872 std::shared_ptr<Bridge> CreateBridge()
874 INITIALIZED_BRIDGE = true;
878 /* check environment variable first */
879 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
880 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
882 return Dali::Accessibility::DummyBridge::GetInstance();
885 return std::make_shared<BridgeImpl>();
887 catch(const std::exception&)
889 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
890 return Dali::Accessibility::DummyBridge::GetInstance();
894 } // unnamed namespace
896 // Dali::Accessibility::Bridge class implementation
898 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
900 static std::shared_ptr<Bridge> bridge;
906 else if(mAutoInitState == AutoInitState::ENABLED)
908 bridge = CreateBridge();
910 /* check environment variable for suppressing screen-reader */
911 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
912 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
914 bridge->SuppressScreenReader(true);
920 return Dali::Accessibility::DummyBridge::GetInstance();
923 void Bridge::DisableAutoInit()
925 if(INITIALIZED_BRIDGE)
927 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
930 mAutoInitState = AutoInitState::DISABLED;
933 void Bridge::EnableAutoInit()
935 mAutoInitState = AutoInitState::ENABLED;
937 if(INITIALIZED_BRIDGE)
942 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
943 auto window = Dali::DevelWindow::Get(rootLayer);
944 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
946 auto accessible = Accessibility::Accessible::Get(rootLayer);
948 auto bridge = Bridge::GetCurrentBridge();
949 bridge->AddTopLevelWindow(accessible);
950 bridge->SetApplicationName(applicationName);
951 bridge->Initialize();
953 if(window && window.IsVisible())
955 bridge->WindowShown(window);
959 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
961 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
962 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
964 static const char prefix[] = "com.samsung.dali.widget_";
965 static const char underscore = '_';
967 std::stringstream tmp;
971 for(char ch : widgetInstanceId)
973 tmp << (std::isalnum(ch) ? ch : underscore);