2 * Copyright (c) 2021 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/bridge-accessible.h>
31 #include <dali/internal/accessibility/bridge/bridge-action.h>
32 #include <dali/internal/accessibility/bridge/bridge-collection.h>
33 #include <dali/internal/accessibility/bridge/bridge-component.h>
34 #include <dali/internal/accessibility/bridge/bridge-editable-text.h>
35 #include <dali/internal/accessibility/bridge/bridge-hypertext.h>
36 #include <dali/internal/accessibility/bridge/bridge-hyperlink.h>
37 #include <dali/internal/accessibility/bridge/bridge-object.h>
38 #include <dali/internal/accessibility/bridge/bridge-selection.h>
39 #include <dali/internal/accessibility/bridge/bridge-text.h>
40 #include <dali/internal/accessibility/bridge/bridge-value.h>
41 #include <dali/internal/accessibility/bridge/bridge-application.h>
42 #include <dali/internal/accessibility/bridge/dummy-atspi.h>
43 #include <dali/internal/adaptor/common/adaptor-impl.h>
44 #include <dali/internal/system/common/environment-variables.h>
46 using namespace Dali::Accessibility;
48 namespace // unnamed namespace
51 const int RETRY_INTERVAL = 1000;
53 } // unnamed namespace
56 * @brief The BridgeImpl class is to implement some Bridge functions.
58 class BridgeImpl : public virtual BridgeBase,
59 public BridgeAccessible,
61 public BridgeComponent,
62 public BridgeCollection,
66 public BridgeEditableText,
67 public BridgeSelection,
68 public BridgeApplication,
69 public BridgeHypertext,
70 public BridgeHyperlink
72 DBus::DBusClient mAccessibilityStatusClient;
73 DBus::DBusClient mRegistryClient;
74 DBus::DBusClient mDirectReadingClient;
75 bool mIsScreenReaderEnabled = false;
76 bool mIsEnabled = false;
77 bool mIsShown = false;
78 std::unordered_map<int32_t, std::function<void(std::string)>> mDirectReadingCallbacks;
79 Dali::Actor mHighlightedActor;
80 std::function<void(Dali::Actor)> mHighlightClearAction;
81 Dali::CallbackBase* mIdleCallback = NULL;
82 Dali::Timer mInitializeTimer;
83 Dali::Timer mReadIsEnabledTimer;
84 Dali::Timer mReadScreenReaderEnabledTimer;
92 * @copydoc Dali::Accessibility::Bridge::Emit()
94 Consumed Emit(KeyEventType type, unsigned int keyCode, const std::string& keyName, unsigned int timeStamp, bool isText) override
101 unsigned int keyType = 0;
105 case KeyEventType::KEY_PRESSED:
110 case KeyEventType::KEY_RELEASED:
121 auto methodObject = mRegistryClient.method<bool(std::tuple<uint32_t, int32_t, int32_t, int32_t, int32_t, std::string, bool>)>("NotifyListenersSync");
122 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});
125 LOG() << result.getError().message;
128 return std::get<0>(result) ? Consumed::YES : Consumed::NO;
132 * @copydoc Dali::Accessibility::Bridge::Pause()
134 void Pause() override
141 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
144 LOG() << "Direct reading command failed (" << msg.getError().message << ")";
151 * @copydoc Dali::Accessibility::Bridge::Resume()
153 void Resume() override
160 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
163 LOG() << "Direct reading command failed (" << msg.getError().message << ")";
170 * @copydoc Dali::Accessibility::Bridge::StopReading()
172 void StopReading(bool alsoNonDiscardable) override
179 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
182 LOG() << "Direct reading command failed (" << msg.getError().message << ")";
189 * @copydoc Dali::Accessibility::Bridge::Say()
191 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
198 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
201 LOG() << "Direct reading command failed (" << msg.getError().message << ")";
205 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
213 * @copydoc Dali::Accessibility::Bridge::ForceDown()
215 void ForceDown() override
219 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
221 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
223 mData->mCurrentlyHighlightedActor = {};
224 mData->mHighlightActor = {};
226 mDisabledSignal.Emit();
228 mHighlightedActor = {};
229 mHighlightClearAction = {};
230 BridgeAccessible::ForceDown();
231 mRegistryClient = {};
232 mDirectReadingClient = {};
233 mDirectReadingCallbacks.clear();
234 mApplication.mChildren.clear();
235 mApplication.mWindows.clear();
242 mInitializeTimer.Stop();
245 if(mReadIsEnabledTimer)
247 mReadIsEnabledTimer.Stop();
250 if(mReadScreenReaderEnabledTimer)
252 mReadScreenReaderEnabledTimer.Stop();
257 * @copydoc Dali::Accessibility::Bridge::Terminate()
259 void Terminate() override
263 mData->mCurrentlyHighlightedActor = {};
264 mData->mHighlightActor = {};
268 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
270 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
272 mAccessibilityStatusClient = {};
278 * @copydoc Dali::Accessibility::Bridge::ForceUp()
280 ForceUpResult ForceUp() override
282 if(BridgeAccessible::ForceUp() == ForceUpResult::ALREADY_UP)
284 return ForceUpResult::ALREADY_UP;
287 BridgeObject::RegisterInterfaces();
288 BridgeAccessible::RegisterInterfaces();
289 BridgeComponent::RegisterInterfaces();
290 BridgeCollection::RegisterInterfaces();
291 BridgeAction::RegisterInterfaces();
292 BridgeValue::RegisterInterfaces();
293 BridgeText::RegisterInterfaces();
294 BridgeEditableText::RegisterInterfaces();
295 BridgeSelection::RegisterInterfaces();
296 BridgeApplication::RegisterInterfaces();
297 BridgeHypertext::RegisterInterfaces();
298 BridgeHyperlink::RegisterInterfaces();
300 RegisterOnBridge(&mApplication);
302 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, AtspiDbusInterfaceDec, mConnectionPtr};
303 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
305 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
306 auto it = mDirectReadingCallbacks.find(id);
307 if(it != mDirectReadingCallbacks.end())
309 it->second(readingState);
310 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
312 mDirectReadingCallbacks.erase(it);
317 auto proxy = DBus::DBusClient{AtspiDbusNameRegistry, AtspiDbusPathRoot, AtspiDbusInterfaceSocket, mConnectionPtr};
318 Address root{"", "root"};
319 auto res = proxy.method<Address(Address)>("Embed").call(root);
322 LOG() << "Call to Embed failed: " << res.getError().message;
326 mApplication.mParent.SetAddress(std::move(std::get<0>(res)));
328 mEnabledSignal.Emit();
332 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer();
333 auto window = Dali::DevelWindow::Get(rootLayer);
334 EmitActivate(window); // Currently, sends a signal that the default window is activated here.
337 return ForceUpResult::JUST_STARTED;
341 * @brief Sends a signal to dbus that the window is shown.
343 * @param[in] window The window to be shown
344 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
346 void EmitShown(Dali::Window window)
348 auto windowAccessible = mApplication.GetWindowAccessible(window);
351 windowAccessible->EmitShowing(true);
356 * @brief Sends a signal to dbus that the window is hidden.
358 * @param[in] window The window to be hidden
359 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
361 void EmitHidden(Dali::Window window)
363 auto windowAccessible = mApplication.GetWindowAccessible(window);
366 windowAccessible->EmitShowing(false);
371 * @brief Sends a signal to dbus that the window is activated.
373 * @param[in] window The window to be activated
374 * @see BridgeObject::Emit()
376 void EmitActivate(Dali::Window window)
378 auto windowAccessible = mApplication.GetWindowAccessible(window);
381 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
386 * @brief Sends a signal to dbus that the window is deactivated.
388 * @param[in] window The window to be deactivated
389 * @see BridgeObject::Emit()
391 void EmitDeactivate(Dali::Window window)
393 auto windowAccessible = mApplication.GetWindowAccessible(window);
396 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
401 * @copydoc Dali::Accessibility::Bridge::WindowShown()
403 void WindowShown(Dali::Window window) override
405 if(!mIsShown && IsUp())
413 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
415 void WindowHidden(Dali::Window window) override
417 if(mIsShown && IsUp())
425 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
427 void WindowFocused(Dali::Window window) override
429 if(mIsShown && IsUp())
431 EmitActivate(window);
436 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
438 void WindowUnfocused(Dali::Window window) override
440 if(mIsShown && IsUp())
442 EmitDeactivate(window);
446 bool ReadIsEnabledTimerCallback()
448 ReadIsEnabledProperty();
452 void ReadIsEnabledProperty()
454 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
457 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
458 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
460 if(!mReadIsEnabledTimer)
462 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
463 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
465 mReadIsEnabledTimer.Start();
469 mIsEnabled = std::get<0>(msg);
477 void ListenIsEnabledProperty()
479 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
481 if(mIsScreenReaderEnabled || mIsEnabled)
492 bool ReadScreenReaderEnabledTimerCallback()
494 ReadScreenReaderEnabledProperty();
498 void ReadScreenReaderEnabledProperty()
500 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
503 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
504 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
506 if(!mReadScreenReaderEnabledTimer)
508 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
509 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
511 mReadScreenReaderEnabledTimer.Start();
515 mIsScreenReaderEnabled = std::get<0>(msg);
516 if(mIsScreenReaderEnabled)
523 void ListenScreenReaderEnabledProperty()
525 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
526 mIsScreenReaderEnabled = res;
527 if(mIsScreenReaderEnabled || mIsEnabled)
538 void ReadAndListenProperties()
540 ReadIsEnabledProperty();
541 ListenIsEnabledProperty();
543 ReadScreenReaderEnabledProperty();
544 ListenScreenReaderEnabledProperty();
547 bool InitializeAccessibilityStatusClient()
549 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
551 if (!mAccessibilityStatusClient)
553 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
560 bool InitializeTimerCallback()
562 if ( InitializeAccessibilityStatusClient() )
564 ReadAndListenProperties();
572 if ( InitializeAccessibilityStatusClient() )
574 ReadAndListenProperties();
575 mIdleCallback = NULL;
579 if(!mInitializeTimer)
581 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
582 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
584 mInitializeTimer.Start();
586 mIdleCallback = NULL;
591 * @copydoc Dali::Accessibility::Bridge::Initialize()
593 void Initialize() override
595 if ( InitializeAccessibilityStatusClient() )
597 ReadAndListenProperties();
601 // Initialize failed. Try it again on Idle
602 if( Dali::Adaptor::IsAvailable() )
604 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
605 if( NULL == mIdleCallback )
607 mIdleCallback = MakeCallback( this, &BridgeImpl::OnIdleSignal );
608 adaptor.AddIdle( mIdleCallback, true );
614 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
616 bool GetScreenReaderEnabled() override
618 return mIsScreenReaderEnabled;
622 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
624 bool IsEnabled() override
630 namespace // unnamed namespace
633 bool INITIALIZED_BRIDGE = false;
636 * @brief Creates BridgeImpl instance.
638 * @return The BridgeImpl instance
639 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
641 Bridge* CreateBridge()
643 INITIALIZED_BRIDGE = true;
647 /* check environment variable first */
648 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
649 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
651 return Dali::Accessibility::DummyBridge::GetInstance();
654 return new BridgeImpl;
656 catch(const std::exception&)
658 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
659 return Dali::Accessibility::DummyBridge::GetInstance();
663 } // unnamed namespace
665 // Dali::Accessibility::Bridge class implementation
667 Bridge* Bridge::GetCurrentBridge()
669 static Bridge* bridge;
675 else if(mAutoInitState == AutoInitState::ENABLED)
677 bridge = CreateBridge();
679 /* check environment variable for suppressing screen-reader */
680 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
681 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
683 bridge->SuppressScreenReader(true);
689 return Dali::Accessibility::DummyBridge::GetInstance();
692 void Bridge::DisableAutoInit()
694 if(INITIALIZED_BRIDGE)
696 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
699 mAutoInitState = AutoInitState::DISABLED;
702 void Bridge::EnableAutoInit()
704 mAutoInitState = AutoInitState::ENABLED;
706 if(INITIALIZED_BRIDGE)
711 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
712 auto window = Dali::DevelWindow::Get(rootLayer);
713 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
715 auto accessible = Accessibility::Accessible::Get(rootLayer, true);
717 auto* bridge = Bridge::GetCurrentBridge();
718 bridge->AddTopLevelWindow(accessible);
719 bridge->SetApplicationName(applicationName);
720 bridge->Initialize();
722 if(window && window.IsVisible())
724 bridge->WindowShown(window);