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:
126 auto methodObject = mRegistryClient.method<bool(std::tuple<uint32_t, int32_t, int32_t, int32_t, int32_t, std::string, bool>)>("NotifyListenersSync");
127 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});
130 LOG() << result.getError().message;
133 return std::get<0>(result) ? Consumed::YES : Consumed::NO;
137 * @copydoc Dali::Accessibility::Bridge::Pause()
139 void Pause() override
146 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
149 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
156 * @copydoc Dali::Accessibility::Bridge::Resume()
158 void Resume() override
165 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("PauseResume").asyncCall([](DBus::ValueOrError<void> msg) {
168 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
175 * @copydoc Dali::Accessibility::Bridge::StopReading()
177 void StopReading(bool alsoNonDiscardable) override
184 mDirectReadingClient.method<DBus::ValueOrError<void>(bool)>("StopReading").asyncCall([](DBus::ValueOrError<void> msg) {
187 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
194 * @copydoc Dali::Accessibility::Bridge::Say()
196 void Say(const std::string& text, bool discardable, std::function<void(std::string)> callback) override
203 mDirectReadingClient.method<DBus::ValueOrError<std::string, bool, int32_t>(std::string, bool)>("ReadCommand").asyncCall([=](DBus::ValueOrError<std::string, bool, int32_t> msg) {
206 LOG() << "Direct reading command failed (" << msg.getError().message << ")\n";
210 mDirectReadingCallbacks.emplace(std::get<2>(msg), callback);
218 * @copydoc Dali::Accessibility::Bridge::ForceDown()
220 void ForceDown() override
224 if(mData->mCurrentlyHighlightedActor && mData->mHighlightActor)
226 mData->mCurrentlyHighlightedActor.Remove(mData->mHighlightActor);
228 mData->mCurrentlyHighlightedActor = {};
229 mData->mHighlightActor = {};
231 mDisabledSignal.Emit();
232 UnembedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
233 ReleaseBusName(mPreferredBusName);
236 mHighlightedActor = {};
237 mHighlightClearAction = {};
238 BridgeAccessible::ForceDown();
239 mRegistryClient = {};
240 mDirectReadingClient = {};
241 mDirectReadingCallbacks.clear();
242 mApplication.mChildren.clear();
250 mInitializeTimer.Stop();
251 mInitializeTimer.Reset();
254 if(mReadIsEnabledTimer)
256 mReadIsEnabledTimer.Stop();
257 mReadIsEnabledTimer.Reset();
260 if(mReadScreenReaderEnabledTimer)
262 mReadScreenReaderEnabledTimer.Stop();
263 mReadScreenReaderEnabledTimer.Reset();
268 mForceUpTimer.Stop();
269 mForceUpTimer.Reset();
274 * @copydoc Dali::Accessibility::Bridge::Terminate()
276 void Terminate() override
280 // The ~Window() after this point cannot emit DESTROY, because Bridge is not available. So emit DESTROY here.
281 for(auto windowAccessible : mApplication.mChildren)
283 BridgeObject::Emit(windowAccessible, WindowEvent::DESTROY);
285 mData->mCurrentlyHighlightedActor = {};
286 mData->mHighlightActor = {};
289 if((NULL != mIdleCallback) && Dali::Adaptor::IsAvailable())
291 Dali::Adaptor::Get().RemoveIdle(mIdleCallback);
293 mAccessibilityStatusClient = {};
298 bool ForceUpTimerCallback()
300 if(ForceUp() != ForceUpResult::FAILED)
308 * @copydoc Dali::Accessibility::Bridge::ForceUp()
310 ForceUpResult ForceUp() override
312 auto forceUpResult = BridgeAccessible::ForceUp();
313 if(forceUpResult == ForceUpResult::ALREADY_UP)
315 return forceUpResult;
317 else if(forceUpResult == ForceUpResult::FAILED)
321 mForceUpTimer = Dali::Timer::New(RETRY_INTERVAL);
322 mForceUpTimer.TickSignal().Connect(this, &BridgeImpl::ForceUpTimerCallback);
323 mForceUpTimer.Start();
325 return forceUpResult;
328 BridgeObject::RegisterInterfaces();
329 BridgeAccessible::RegisterInterfaces();
330 BridgeComponent::RegisterInterfaces();
331 BridgeCollection::RegisterInterfaces();
332 BridgeAction::RegisterInterfaces();
333 BridgeValue::RegisterInterfaces();
334 BridgeText::RegisterInterfaces();
335 BridgeEditableText::RegisterInterfaces();
336 BridgeSelection::RegisterInterfaces();
337 BridgeApplication::RegisterInterfaces();
338 BridgeHypertext::RegisterInterfaces();
339 BridgeHyperlink::RegisterInterfaces();
340 BridgeSocket::RegisterInterfaces();
341 BridgeTable::RegisterInterfaces();
342 BridgeTableCell::RegisterInterfaces();
344 RegisterOnBridge(&mApplication);
346 mRegistryClient = {AtspiDbusNameRegistry, AtspiDbusPathDec, Accessible::GetInterfaceName(AtspiInterface::DEVICE_EVENT_CONTROLLER), mConnectionPtr};
347 mDirectReadingClient = DBus::DBusClient{DirectReadingDBusName, DirectReadingDBusPath, DirectReadingDBusInterface, mConnectionPtr};
349 mDirectReadingClient.addSignal<void(int32_t, std::string)>("ReadingStateChanged", [=](int32_t id, std::string readingState) {
350 auto it = mDirectReadingCallbacks.find(id);
351 if(it != mDirectReadingCallbacks.end())
353 it->second(readingState);
354 if(readingState != "ReadingPaused" && readingState != "ReadingResumed" && readingState != "ReadingStarted")
356 mDirectReadingCallbacks.erase(it);
361 RequestBusName(mPreferredBusName);
363 auto parentAddress = EmbedSocket(mApplication.GetAddress(), {AtspiDbusNameRegistry, "root"});
364 mApplication.mParent.SetAddress(std::move(parentAddress));
365 mEnabledSignal.Emit();
367 return ForceUpResult::JUST_STARTED;
371 * @brief Sends a signal to dbus that the window is created.
373 * @param[in] window The window to be created
374 * @see BridgeObject::Emit()
376 void EmitCreated(Dali::Window window)
378 auto windowAccessible = mApplication.GetWindowAccessible(window);
381 windowAccessible->Emit(WindowEvent::CREATE, 0);
386 * @brief Sends a signal to dbus that the window is shown.
388 * @param[in] window The window to be shown
389 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
391 void EmitShown(Dali::Window window)
393 auto windowAccessible = mApplication.GetWindowAccessible(window);
396 windowAccessible->EmitShowing(true);
401 * @brief Sends a signal to dbus that the window is hidden.
403 * @param[in] window The window to be hidden
404 * @see Accessible::EmitShowing() and BridgeObject::EmitStateChanged()
406 void EmitHidden(Dali::Window window)
408 auto windowAccessible = mApplication.GetWindowAccessible(window);
411 windowAccessible->EmitShowing(false);
416 * @brief Sends a signal to dbus that the window is activated.
418 * @param[in] window The window to be activated
419 * @see BridgeObject::Emit()
421 void EmitActivate(Dali::Window window)
423 auto windowAccessible = mApplication.GetWindowAccessible(window);
426 windowAccessible->Emit(WindowEvent::ACTIVATE, 0);
431 * @brief Sends a signal to dbus that the window is deactivated.
433 * @param[in] window The window to be deactivated
434 * @see BridgeObject::Emit()
436 void EmitDeactivate(Dali::Window window)
438 auto windowAccessible = mApplication.GetWindowAccessible(window);
441 windowAccessible->Emit(WindowEvent::DEACTIVATE, 0);
446 * @brief Sends a signal to dbus that the window is minimized.
448 * @param[in] window The window to be minimized
449 * @see BridgeObject::Emit()
451 void EmitMinimize(Dali::Window window)
453 auto windowAccessible = mApplication.GetWindowAccessible(window);
456 windowAccessible->Emit(WindowEvent::MINIMIZE, 0);
461 * @brief Sends a signal to dbus that the window is restored.
463 * @param[in] window The window to be restored
464 * @param[in] detail Restored window state
465 * @see BridgeObject::Emit()
467 void EmitRestore(Dali::Window window, Dali::Accessibility::WindowRestoreType detail)
469 auto windowAccessible = mApplication.GetWindowAccessible(window);
472 windowAccessible->Emit(WindowEvent::RESTORE, static_cast<unsigned int>(detail));
477 * @brief Sends a signal to dbus that the window is maximized.
479 * @param[in] window The window to be maximized
480 * @see BridgeObject::Emit()
482 void EmitMaximize(Dali::Window window)
484 auto windowAccessible = mApplication.GetWindowAccessible(window);
487 windowAccessible->Emit(WindowEvent::MAXIMIZE, 0);
492 * @copydoc Dali::Accessibility::Bridge::WindowCreated()
494 void WindowCreated(Dali::Window window) override
503 * @copydoc Dali::Accessibility::Bridge::WindowShown()
505 void WindowShown(Dali::Window window) override
514 * @copydoc Dali::Accessibility::Bridge::WindowHidden()
516 void WindowHidden(Dali::Window window) override
525 * @copydoc Dali::Accessibility::Bridge::WindowFocused()
527 void WindowFocused(Dali::Window window) override
531 EmitActivate(window);
536 * @copydoc Dali::Accessibility::Bridge::WindowUnfocused()
538 void WindowUnfocused(Dali::Window window) override
542 EmitDeactivate(window);
547 * @copydoc Dali::Accessibility::Bridge::WindowMinimized()
549 void WindowMinimized(Dali::Window window) override
553 EmitMinimize(window);
558 * @copydoc Dali::Accessibility::Bridge::WindowRestored()
560 void WindowRestored(Dali::Window window, WindowRestoreType detail) override
564 EmitRestore(window, detail);
569 * @copydoc Dali::Accessibility::Bridge::WindowMaximized()
571 void WindowMaximized(Dali::Window window) override
575 EmitMaximize(window);
580 * @copydoc Dali::Accessibility::Bridge::SuppressScreenReader()
582 void SuppressScreenReader(bool suppress) override
584 if(mIsScreenReaderSuppressed == suppress)
588 mIsScreenReaderSuppressed = suppress;
589 ReadScreenReaderEnabledProperty();
594 if((!mIsScreenReaderSuppressed && mIsScreenReaderEnabled) || mIsEnabled)
604 bool ReadIsEnabledTimerCallback()
606 ReadIsEnabledProperty();
610 void ReadIsEnabledProperty()
612 mAccessibilityStatusClient.property<bool>("IsEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
615 DALI_LOG_ERROR("Get IsEnabled property error: %s\n", msg.getError().message.c_str());
616 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
618 if(!mReadIsEnabledTimer)
620 mReadIsEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
621 mReadIsEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadIsEnabledTimerCallback);
623 mReadIsEnabledTimer.Start();
628 if(mReadIsEnabledTimer)
630 mReadIsEnabledTimer.Stop();
631 mReadIsEnabledTimer.Reset();
634 mIsEnabled = std::get<0>(msg);
639 void ListenIsEnabledProperty()
641 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("IsEnabled", [this](bool res) {
647 bool ReadScreenReaderEnabledTimerCallback()
649 ReadScreenReaderEnabledProperty();
653 void ReadScreenReaderEnabledProperty()
655 // can be true because of SuppressScreenReader before init
656 if(!mAccessibilityStatusClient)
661 mAccessibilityStatusClient.property<bool>("ScreenReaderEnabled").asyncGet([this](DBus::ValueOrError<bool> msg) {
664 DALI_LOG_ERROR("Get ScreenReaderEnabled property error: %s\n", msg.getError().message.c_str());
665 if(msg.getError().errorType == DBus::ErrorType::INVALID_REPLY)
667 if(!mReadScreenReaderEnabledTimer)
669 mReadScreenReaderEnabledTimer = Dali::Timer::New(RETRY_INTERVAL);
670 mReadScreenReaderEnabledTimer.TickSignal().Connect(this, &BridgeImpl::ReadScreenReaderEnabledTimerCallback);
672 mReadScreenReaderEnabledTimer.Start();
677 if(mReadScreenReaderEnabledTimer)
679 mReadScreenReaderEnabledTimer.Stop();
680 mReadScreenReaderEnabledTimer.Reset();
683 mIsScreenReaderEnabled = std::get<0>(msg);
688 void EmitScreenReaderEnabledSignal()
690 if(mIsScreenReaderEnabled)
692 mScreenReaderEnabledSignal.Emit();
696 mScreenReaderDisabledSignal.Emit();
700 void ListenScreenReaderEnabledProperty()
702 mAccessibilityStatusClient.addPropertyChangedEvent<bool>("ScreenReaderEnabled", [this](bool res) {
703 mIsScreenReaderEnabled = res;
704 EmitScreenReaderEnabledSignal();
709 void ReadAndListenProperties()
711 ReadIsEnabledProperty();
712 ListenIsEnabledProperty();
714 ReadScreenReaderEnabledProperty();
715 ListenScreenReaderEnabledProperty();
718 bool InitializeAccessibilityStatusClient()
720 mAccessibilityStatusClient = DBus::DBusClient{A11yDbusName, A11yDbusPath, A11yDbusStatusInterface, DBus::ConnectionType::SESSION};
722 if(!mAccessibilityStatusClient)
724 DALI_LOG_ERROR("Accessibility Status DbusClient is not ready\n");
731 bool InitializeTimerCallback()
733 if(InitializeAccessibilityStatusClient())
735 ReadAndListenProperties();
743 if(InitializeAccessibilityStatusClient())
745 ReadAndListenProperties();
746 mIdleCallback = NULL;
750 if(!mInitializeTimer)
752 mInitializeTimer = Dali::Timer::New(RETRY_INTERVAL);
753 mInitializeTimer.TickSignal().Connect(this, &BridgeImpl::InitializeTimerCallback);
755 mInitializeTimer.Start();
757 mIdleCallback = NULL;
762 * @copydoc Dali::Accessibility::Bridge::Initialize()
764 void Initialize() override
766 if(InitializeAccessibilityStatusClient())
768 ReadAndListenProperties();
772 // Initialize failed. Try it again on Idle
773 if(Dali::Adaptor::IsAvailable())
775 Dali::Adaptor& adaptor = Dali::Adaptor::Get();
776 if(NULL == mIdleCallback)
778 mIdleCallback = MakeCallback(this, &BridgeImpl::OnIdleSignal);
779 adaptor.AddIdle(mIdleCallback, true);
785 * @copydoc Dali::Accessibility::Bridge::GetScreenReaderEnabled()
787 bool GetScreenReaderEnabled() override
789 return mIsScreenReaderEnabled;
793 * @copydoc Dali::Accessibility::Bridge::IsEnabled()
795 bool IsEnabled() override
800 Address EmbedSocket(const Address& plug, const Address& socket) override
802 auto client = CreateSocketClient(socket);
803 auto reply = client.method<Address(Address)>("Embed").call(plug);
807 DALI_LOG_ERROR("Failed to embed socket %s: %s", socket.ToString().c_str(), reply.getError().message.c_str());
811 return std::get<0>(reply.getValues());
814 void EmbedAtkSocket(const Address& plug, const Address& socket) override
816 auto client = CreateSocketClient(socket);
818 client.method<void(std::string)>("Embedded").asyncCall([](DBus::ValueOrError<void>) {}, ATSPI_PREFIX_PATH + plug.GetPath());
821 void UnembedSocket(const Address& plug, const Address& socket) override
823 auto client = CreateSocketClient(socket);
825 client.method<void(Address)>("Unembed").asyncCall([](DBus::ValueOrError<void>) {}, plug);
828 void SetSocketOffset(ProxyAccessible* socket, std::int32_t x, std::int32_t y) override
830 AddCoalescableMessage(CoalescableMessages::SET_OFFSET, socket, 1.0f, [=]() {
831 auto client = CreateSocketClient(socket->GetAddress());
833 client.method<void(std::int32_t, std::int32_t)>("SetOffset").asyncCall([](DBus::ValueOrError<void>) {}, x, y);
837 void SetExtentsOffset(std::int32_t x, std::int32_t y) override
841 mData->mExtentsOffset = {x, y};
845 void SetPreferredBusName(std::string_view preferredBusName) override
847 if(preferredBusName == mPreferredBusName)
852 std::string oldPreferredBusName = std::move(mPreferredBusName);
853 mPreferredBusName = std::string{preferredBusName};
857 ReleaseBusName(oldPreferredBusName);
858 RequestBusName(mPreferredBusName);
860 // else: request/release will be handled by ForceUp/ForceDown, respectively
864 DBus::DBusClient CreateSocketClient(const Address& socket)
866 return {socket.GetBus(), ATSPI_PREFIX_PATH + socket.GetPath(), Accessible::GetInterfaceName(AtspiInterface::SOCKET), mConnectionPtr};
869 void RequestBusName(const std::string& busName)
876 DBus::requestBusName(mConnectionPtr, busName);
879 void ReleaseBusName(const std::string& busName)
886 DBus::releaseBusName(mConnectionPtr, busName);
890 namespace // unnamed namespace
892 bool INITIALIZED_BRIDGE = false;
895 * @brief Creates BridgeImpl instance.
897 * @return The BridgeImpl instance
898 * @note This method is to check environment variable first. If ATSPI is disable using env, it returns dummy bridge instance.
900 std::shared_ptr<Bridge> CreateBridge()
902 INITIALIZED_BRIDGE = true;
906 /* check environment variable first */
907 const char* envAtspiDisabled = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_DISABLE_ATSPI);
908 if(envAtspiDisabled && std::atoi(envAtspiDisabled) != 0)
910 return Dali::Accessibility::DummyBridge::GetInstance();
913 return std::make_shared<BridgeImpl>();
915 catch(const std::exception&)
917 DALI_LOG_ERROR("Failed to initialize AT-SPI bridge");
918 return Dali::Accessibility::DummyBridge::GetInstance();
922 } // unnamed namespace
924 // Dali::Accessibility::Bridge class implementation
926 std::shared_ptr<Bridge> Bridge::GetCurrentBridge()
928 static std::shared_ptr<Bridge> bridge;
934 else if(mAutoInitState == AutoInitState::ENABLED)
936 bridge = CreateBridge();
938 /* check environment variable for suppressing screen-reader */
939 const char* envSuppressScreenReader = Dali::EnvironmentVariable::GetEnvironmentVariable(DALI_ENV_SUPPRESS_SCREEN_READER);
940 if(envSuppressScreenReader && std::atoi(envSuppressScreenReader) != 0)
942 bridge->SuppressScreenReader(true);
948 return Dali::Accessibility::DummyBridge::GetInstance();
951 void Bridge::DisableAutoInit()
953 if(INITIALIZED_BRIDGE)
955 DALI_LOG_ERROR("Bridge::DisableAutoInit() called after bridge auto-initialization");
958 mAutoInitState = AutoInitState::DISABLED;
961 void Bridge::EnableAutoInit()
963 mAutoInitState = AutoInitState::ENABLED;
965 if(INITIALIZED_BRIDGE)
970 auto rootLayer = Dali::Stage::GetCurrent().GetRootLayer(); // A root layer of the default window.
971 auto window = Dali::DevelWindow::Get(rootLayer);
972 auto applicationName = Dali::Internal::Adaptor::Adaptor::GetApplicationPackageName();
974 auto accessible = Accessibility::Accessible::Get(rootLayer);
976 auto bridge = Bridge::GetCurrentBridge();
977 bridge->AddTopLevelWindow(accessible);
978 bridge->SetApplicationName(applicationName);
979 bridge->Initialize();
981 if(window && window.IsVisible())
983 bridge->WindowShown(window);
987 std::string Bridge::MakeBusNameForWidget(std::string_view widgetInstanceId)
989 // The bus name should consist of dot-separated alphanumeric elements, e.g. "com.example.BusName123".
990 // Allowed characters in each element: "[A-Z][a-z][0-9]_", but no element may start with a digit.
992 static const char prefix[] = "com.samsung.dali.widget_";
993 static const char underscore = '_';
995 std::stringstream tmp;
999 for(char ch : widgetInstanceId)
1001 tmp << (std::isalnum(ch) ? ch : underscore);