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-table.h>
42 #include <dali/internal/accessibility/bridge/bridge-table-cell.h>
43 #include <dali/internal/accessibility/bridge/bridge-text.h>
44 #include <dali/internal/accessibility/bridge/bridge-value.h>
45 #include <dali/internal/accessibility/bridge/bridge-application.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
56 const int RETRY_INTERVAL = 1000;
58 } // unnamed namespace
61 * @brief The BridgeImpl class is to implement some Bridge functions.
63 class BridgeImpl : public virtual BridgeBase,
64 public BridgeAccessible,
66 public BridgeComponent,
67 public BridgeCollection,
71 public BridgeEditableText,
72 public BridgeSelection,
73 public BridgeApplication,
74 public BridgeHypertext,
75 public BridgeHyperlink,
78 public BridgeTableCell
80 DBus::DBusClient mAccessibilityStatusClient;
81 DBus::DBusClient mRegistryClient;
82 DBus::DBusClient mDirectReadingClient;
83 bool mIsScreenReaderEnabled = false;
84 bool mIsEnabled = false;
85 bool mIsApplicationRunning = false;
86 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks;
87 Dali::Actor mHighlightedActor;
88 std::function<void(Dali::Actor)> mHighlightClearAction;
89 Dali::CallbackBase* mIdleCallback = NULL;
90 Dali::Timer mInitializeTimer;
91 Dali::Timer mReadIsEnabledTimer;
92 Dali::Timer mReadScreenReaderEnabledTimer;
93 Dali::Timer mForceUpTimer;
94 std::string mPreferredBusName;
102 * @copydoc Dali::Accessibility::Bridge::Emit()
104 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
111 unsigned int keyType = 0;
115 case KeyEventType::KEY_PRESSED:
120 case KeyEventType::KEY_RELEASED:
135 * @copydoc Dali::Accessibility::Bridge::Pause()
137 void Pause() override
144 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
147 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
154 * @copydoc Dali::Accessibility::Bridge::Resume()
156 void Resume() override
163 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
166 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
173 * @copydoc Dali::Accessibility::Bridge::StopReading()
175 void StopReading(bool alsoNonDiscardable) override
182 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
185 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
192 * @copydoc Dali::Accessibility::Bridge::Say()
194 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
201 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
204 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
208 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
216 * @copydoc Dali::Accessibility::Bridge::ForceDown()
218 void ForceDown() override
222 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
224 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
226 mData->mCurrentlyHighlightedActor = {};
227 mData->mHighlightActor = {};
229 mDisabledSignal.Emit();
230 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
231 ReleaseBusName(mPreferredBusName);
234 mHighlightedActor = {};
235 mHighlightClearAction = {};
236 BridgeAccessible::ForceDown();
237 mRegistryClient = {};
238 mDirectReadingClient = {};
239 mDirectReadingCallbacks.clear();
240 mApplication.mChildren.clear();
248 mInitializeTimer.Stop();
249 mInitializeTimer.Reset();
252 if(mReadIsEnabledTimer)
254 mReadIsEnabledTimer.Stop();
255 mReadIsEnabledTimer.Reset();
258 if(mReadScreenReaderEnabledTimer)
260 mReadScreenReaderEnabledTimer.Stop();
261 mReadScreenReaderEnabledTimer.Reset();
266 mForceUpTimer.Stop();
267 mForceUpTimer.Reset();
272 * @copydoc Dali::Accessibility::Bridge::Terminate()
274 void Terminate() override
278 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
279 for(auto windowAccessible : mApplication.mChildren)
281 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
283 mData->mCurrentlyHighlightedActor = {};
284 mData->mHighlightActor = {};
287 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
289 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
291 mAccessibilityStatusClient = {};
296 bool ForceUpTimerCallback()
298 if(ForceUp() != ForceUpResult::FAILED)
306 * @copydoc Dali::Accessibility::Bridge::ForceUp()
308 ForceUpResult ForceUp() override
310 auto forceUpResult = BridgeAccessible::ForceUp();
311 if(forceUpResult == ForceUpResult::ALREADY_UP)
313 return forceUpResult;
315 else if(forceUpResult == ForceUpResult::FAILED)
319 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
320 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
321 mForceUpTimer.Start();
323 return forceUpResult;
326 BridgeObject::RegisterInterfaces();
327 BridgeAccessible::RegisterInterfaces();
328 BridgeComponent::RegisterInterfaces();
329 BridgeCollection::RegisterInterfaces();
330 BridgeAction::RegisterInterfaces();
331 BridgeValue::RegisterInterfaces();
332 BridgeText::RegisterInterfaces();
333 BridgeEditableText::RegisterInterfaces();
334 BridgeSelection::RegisterInterfaces();
335 BridgeApplication::RegisterInterfaces();
336 BridgeHypertext::RegisterInterfaces();
337 BridgeHyperlink::RegisterInterfaces();
338 BridgeSocket::RegisterInterfaces();
339 BridgeTable::RegisterInterfaces();
340 BridgeTableCell::RegisterInterfaces();
342 RegisterOnBridge(&mApplication);
344 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
345 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
347 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
348 auto it = mDirectReadingCallbacks.find(id);
349 if(it != mDirectReadingCallbacks.end())
351 it->second(readingState);
352 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
354 mDirectReadingCallbacks.erase(it);
359 RequestBusName(mPreferredBusName);
361 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
362 mApplication.mParent.SetAddress(std::move(parentAddress));
363 mEnabledSignal.Emit();
365 return ForceUpResult::JUST_STARTED;
369 * @brief Sends a signal to dbus that the window is created.
371 * @param[in] window The window to be created
372 * @see BridgeObject::Emit()
374 void EmitCreated(Dali::Window window)
376 auto windowAccessible = mApplication.GetWindowAccessible(window);
379 windowAccessible->Emit(WindowEvent::CREATE, 0);
384 * @brief Sends a signal to dbus that the window is shown.
386 * @param[in] window The window to be shown
387 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
389 void EmitShown(Dali::Window window)
391 auto windowAccessible = mApplication.GetWindowAccessible(window);
394 windowAccessible->EmitShowing(true);
399 * @brief Sends a signal to dbus that the window is hidden.
401 * @param[in] window The window to be hidden
402 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
404 void EmitHidden(Dali::Window window)
406 auto windowAccessible = mApplication.GetWindowAccessible(window);
409 windowAccessible->EmitShowing(false);
414 * @brief Sends a signal to dbus that the window is activated.
416 * @param[in] window The window to be activated
417 * @see BridgeObject::Emit()
419 void EmitActivate(Dali::Window window)
421 auto windowAccessible = mApplication.GetWindowAccessible(window);
424 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
429 * @brief Sends a signal to dbus that the window is deactivated.
431 * @param[in] window The window to be deactivated
432 * @see BridgeObject::Emit()
434 void EmitDeactivate(Dali::Window window)
436 auto windowAccessible = mApplication.GetWindowAccessible(window);
439 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
444 * @copydoc Dali::Accessibility::Bridge::WindowCreated()
446 void WindowCreated(Dali::Window window) override
455 * @copydoc Dali::Accessibility::Bridge::WindowShown()
457 void WindowShown(Dali::Window window) override
466 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
468 void WindowHidden(Dali::Window window) override
477 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
479 void WindowFocused(Dali::Window window) override
483 EmitActivate(window);
488 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
490 void WindowUnfocused(Dali::Window window) override
494 EmitDeactivate(window);
499 * @copydoc Dali::Accessibility::Bridge::ApplicationPaused()
501 void ApplicationPaused() override
503 mIsApplicationRunning = false;
508 * @copydoc Dali::Accessibility::Bridge::ApplicationResumed()
510 void ApplicationResumed() override
512 mIsApplicationRunning = true;
517 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
519 void SuppressScreenReader(bool suppress) override
521 if(mIsScreenReaderSuppressed == suppress)
525 mIsScreenReaderSuppressed = suppress;
526 ReadScreenReaderEnabledProperty();
531 bool isScreenReaderEnabled = mIsScreenReaderEnabled && !mIsScreenReaderSuppressed;
533 if((isScreenReaderEnabled || mIsEnabled) && mIsApplicationRunning)
543 bool ReadIsEnabledTimerCallback()
545 ReadIsEnabledProperty();
549 void ReadIsEnabledProperty()
551 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
554 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
555 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
557 if(!mReadIsEnabledTimer)
559 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
560 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
562 mReadIsEnabledTimer.Start();
567 if(mReadIsEnabledTimer)
569 mReadIsEnabledTimer.Stop();
570 mReadIsEnabledTimer.Reset();
573 mIsEnabled = std::get<0>(msg);
578 void ListenIsEnabledProperty()
580 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
586 bool ReadScreenReaderEnabledTimerCallback()
588 ReadScreenReaderEnabledProperty();
592 void ReadScreenReaderEnabledProperty()
594 // can be true because of SuppressScreenReader before init
595 if (!mAccessibilityStatusClient)
600 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
603 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
604 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
606 if(!mReadScreenReaderEnabledTimer)
608 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
609 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
611 mReadScreenReaderEnabledTimer.Start();
616 if(mReadScreenReaderEnabledTimer)
618 mReadScreenReaderEnabledTimer.Stop();
619 mReadScreenReaderEnabledTimer.Reset();
622 mIsScreenReaderEnabled = std::get<0>(msg);
627 void EmitScreenReaderEnabledSignal()
629 if (mIsScreenReaderEnabled)
631 mScreenReaderEnabledSignal.Emit();
635 mScreenReaderDisabledSignal.Emit();
639 void ListenScreenReaderEnabledProperty()
641 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
642 mIsScreenReaderEnabled = res;
643 EmitScreenReaderEnabledSignal();
648 void ReadAndListenProperties()
650 ReadIsEnabledProperty();
651 ListenIsEnabledProperty();
653 ReadScreenReaderEnabledProperty();
654 ListenScreenReaderEnabledProperty();
657 bool InitializeAccessibilityStatusClient()
659 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
661 if (!mAccessibilityStatusClient)
663 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
670 bool InitializeTimerCallback()
672 if ( InitializeAccessibilityStatusClient() )
674 ReadAndListenProperties();
682 if ( InitializeAccessibilityStatusClient() )
684 ReadAndListenProperties();
685 mIdleCallback = NULL;
689 if(!mInitializeTimer)
691 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
692 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
694 mInitializeTimer.Start();
696 mIdleCallback = NULL;
701 * @copydoc Dali::Accessibility::Bridge::Initialize()
703 void Initialize() override
705 if ( InitializeAccessibilityStatusClient() )
707 ReadAndListenProperties();
711 // Initialize failed. Try it again on Idle
712 if( Dali::Adaptor::IsAvailable() )
714 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
715 if( NULL == mIdleCallback )
717 mIdleCallback = MakeCallback( this, &BridgeImpl::OnIdleSignal );
718 adaptor.AddIdle( mIdleCallback, true );
724 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
726 bool GetScreenReaderEnabled() override
728 return mIsScreenReaderEnabled;
732 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
734 bool IsEnabled() override
739 Address EmbedSocket(const Address& plug, const Address& socket) override
741 auto client = CreateSocketClient(socket);
742 auto reply = client.method<Address(Address)>("Embed").call(plug);
746 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
750 return std::get<0>(reply.getValues());
753 void EmbedAtkSocket(const Address& plug, const Address& socket) override
755 auto client = CreateSocketClient(socket);
757 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
760 void UnembedSocket(const Address& plug, const Address& socket) override
762 auto client = CreateSocketClient(socket);
764 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
767 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
769 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
770 auto client = CreateSocketClient(socket->GetAddress());
772 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
776 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
780 mData->mExtentsOffset = {x, y};
784 void SetPreferredBusName(std::string_view preferredBusName) override
786 if(preferredBusName == mPreferredBusName)
791 std::string oldPreferredBusName = std::move(mPreferredBusName);
792 mPreferredBusName = std::string{preferredBusName};
796 ReleaseBusName(oldPreferredBusName);
797 RequestBusName(mPreferredBusName);
799 // else: request/release will be handled by ForceUp/ForceDown, respectively
803 DBus::DBusClient CreateSocketClient(const Address& socket)
805 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
808 void RequestBusName(const std::string& busName)
815 DBus::requestBusName(mConnectionPtr, busName);
818 void ReleaseBusName(const std::string& busName)
825 DBus::releaseBusName(mConnectionPtr, busName);
829 namespace // unnamed namespace
832 bool INITIALIZED_BRIDGE = false;
835 * @brief Creates BridgeImpl instance.
837 * @return The BridgeImpl instance
838 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
840 std::shared_ptr<Bridge> CreateBridge()
842 INITIALIZED_BRIDGE = true;
846 /* Check environment variable first */
847 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
848 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
850 return Dali::Accessibility::DummyBridge::GetInstance();
853 // Check if the image is either release or perf mode
854 if((access("/etc/release", F_OK) == 0) || (access("/etc/perf", F_OK) == 0))
856 return Dali::Accessibility::DummyBridge::GetInstance();
859 return std::make_shared<BridgeImpl>();
861 catch(const std::exception&)
863 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
864 return Dali::Accessibility::DummyBridge::GetInstance();
868 } // unnamed namespace
870 // Dali::Accessibility::Bridge class implementation
872 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
874 static std::shared_ptr<Bridge> bridge;
880 else if(mAutoInitState == AutoInitState::ENABLED)
882 bridge = CreateBridge();
884 /* check environment variable for suppressing screen-reader */
885 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
886 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
888 bridge->SuppressScreenReader(true);
894 return Dali::Accessibility::DummyBridge::GetInstance();
897 void Bridge::DisableAutoInit()
899 if(INITIALIZED_BRIDGE)
901 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
904 mAutoInitState = AutoInitState::DISABLED;
907 void Bridge::EnableAutoInit()
909 mAutoInitState = AutoInitState::ENABLED;
911 if(INITIALIZED_BRIDGE)
916 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
917 auto window = Dali::DevelWindow::Get(rootLayer);
918 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
920 auto accessible = Accessibility::Accessible::Get(rootLayer);
922 auto bridge = Bridge::GetCurrentBridge();
923 bridge->AddTopLevelWindow(accessible);
924 bridge->SetApplicationName(applicationName);
925 bridge->Initialize();
927 if(window && window.IsVisible())
929 bridge->WindowShown(window);
933 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
935 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
936 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
938 static const char prefix[] = "com.samsung.dali.widget_";
939 static const char underscore = '_';
941 std::stringstream tmp;
945 for(char ch : widgetInstanceId)
947 tmp << (std::isalnum(ch) ? ch : underscore);