diff --git a/include/sdbus-c++/IObject.h b/include/sdbus-c++/IObject.h index d109986..5196665 100644 --- a/include/sdbus-c++/IObject.h +++ b/include/sdbus-c++/IObject.h @@ -187,6 +187,25 @@ namespace sdbus { */ virtual void emitSignal(const sdbus::Signal& message) = 0; + /*! + * @brief Emits PropertyChanged signal for specified properties under a given interface + * + * @param[in] interfaceName Name of an interface that properties belong to + * @param[in] propNames Names of properties that will be included in the PropertiesChanged signal + * + * @throws sdbus::Error in case of failure + */ + virtual void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) = 0; + + /*! + * @brief Emits PropertyChanged signal for all properties of a given interface + * + * @param[in] interfaceName Name of an interface + * + * @throws sdbus::Error in case of failure + */ + virtual void emitPropertiesChangedSignal(const std::string& interfaceName) = 0; + /*! * @brief Provides D-Bus connection used by the object * diff --git a/include/sdbus-c++/StandardInterfaces.h b/include/sdbus-c++/StandardInterfaces.h index a4016f2..7d006e6 100644 --- a/include/sdbus-c++/StandardInterfaces.h +++ b/include/sdbus-c++/StandardInterfaces.h @@ -178,8 +178,34 @@ namespace sdbus { }; // Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality - // is provided automatically for each D-Bus object by underlying libsystemd (with the exception of - // object manager which is provided but needs to be added explicitly, see IObject::addObjectManager). + // is provided by underlying libsystemd implementation. The exception is Properties_adaptor and + // ObjectManager_adaptor, which provide convenience functionality to emit signals. + + class Properties_adaptor + { + static constexpr const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties"; + + protected: + Properties_adaptor(sdbus::IObject& object) + : object_(object) + { + } + + public: + void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& properties) + { + object_.emitPropertiesChangedSignal(interfaceName, properties); + } + + void emitPropertiesChangedSignal(const std::string& interfaceName) + { + object_.emitPropertiesChangedSignal(interfaceName); + } + + private: + sdbus::IObject& object_; + }; + } #endif /* SDBUS_CXX_STANDARDINTERFACES_H_ */ diff --git a/src/Connection.cpp b/src/Connection.cpp index bf01ef1..b041662 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -33,6 +33,19 @@ #include #include +namespace { + +std::vector to_strv(const std::vector& strings) +{ + std::vector strv; + for (auto& str : strings) + strv.push_back(const_cast(str.c_str())); + strv.push_back(nullptr); + return strv; +} + +} + namespace sdbus { namespace internal { Connection::Connection(Connection::BusType type, std::unique_ptr&& interface) @@ -176,6 +189,20 @@ Signal Connection::createSignal( const std::string& objectPath return Signal{sdbusSignal, iface_.get(), adopt_message}; } +void Connection::emitPropertiesChangedSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::vector& propNames ) +{ + auto names = to_strv(propNames); + + auto r = iface_->sd_bus_emit_properties_changed_strv( bus_.get() + , objectPath.c_str() + , interfaceName.c_str() + , propNames.empty() ? nullptr : &names[0] ); + + SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit PropertiesChanged signal", -r); +} + SlotPtr Connection::registerSignalHandler( const std::string& objectPath , const std::string& interfaceName , const std::string& signalName diff --git a/src/Connection.h b/src/Connection.h index 990bb8e..1451a40 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -76,6 +76,9 @@ namespace sdbus { namespace internal { Signal createSignal( const std::string& objectPath , const std::string& interfaceName , const std::string& signalName ) const override; + void emitPropertiesChangedSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::vector& propNames ) override; SlotPtr registerSignalHandler( const std::string& objectPath , const std::string& interfaceName diff --git a/src/IConnection.h b/src/IConnection.h index 8bf5f7a..37c6892 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -68,6 +68,9 @@ namespace internal { virtual Signal createSignal( const std::string& objectPath , const std::string& interfaceName , const std::string& signalName ) const = 0; + virtual void emitPropertiesChangedSignal( const std::string& objectPath + , const std::string& interfaceName + , const std::vector& propNames ) = 0; virtual SlotPtr registerSignalHandler( const std::string& objectPath , const std::string& interfaceName diff --git a/src/ISdBus.h b/src/ISdBus.h index 3961614..ca4710c 100644 --- a/src/ISdBus.h +++ b/src/ISdBus.h @@ -53,6 +53,8 @@ namespace sdbus { namespace internal { virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) = 0; virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) = 0; + virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) = 0; + virtual int sd_bus_open_user(sd_bus **ret) = 0; virtual int sd_bus_open_system(sd_bus **ret) = 0; virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0; diff --git a/src/Object.cpp b/src/Object.cpp index 8186589..9ed7561 100644 --- a/src/Object.cpp +++ b/src/Object.cpp @@ -135,6 +135,16 @@ void Object::emitSignal(const sdbus::Signal& message) message.send(); } +void Object::emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) +{ + connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames); +} + +void Object::emitPropertiesChangedSignal(const std::string& interfaceName) +{ + Object::emitPropertiesChangedSignal(interfaceName, {}); +} + sdbus::IConnection& Object::getConnection() const { return dynamic_cast(connection_); diff --git a/src/Object.h b/src/Object.h index abf0932..ca921d5 100644 --- a/src/Object.h +++ b/src/Object.h @@ -77,6 +77,8 @@ namespace internal { sdbus::Signal createSignal(const std::string& interfaceName, const std::string& signalName) override; void emitSignal(const sdbus::Signal& message) override; + void emitPropertiesChangedSignal(const std::string& interfaceName, const std::vector& propNames) override; + void emitPropertiesChangedSignal(const std::string& interfaceName) override; sdbus::IConnection& getConnection() const override; diff --git a/src/SdBus.cpp b/src/SdBus.cpp index ab7409f..0e4d665 100644 --- a/src/SdBus.cpp +++ b/src/SdBus.cpp @@ -91,6 +91,13 @@ int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message return ::sd_bus_message_new_method_error(call, m, e); } +int SdBus::sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) +{ + std::unique_lock lock(sdbusMutex_); + + return ::sd_bus_emit_properties_changed_strv(bus, path, interface, names); +} + int SdBus::sd_bus_open_user(sd_bus **ret) { return ::sd_bus_open_user(ret); diff --git a/src/SdBus.h b/src/SdBus.h index e5c7f78..052e5c2 100644 --- a/src/SdBus.h +++ b/src/SdBus.h @@ -47,6 +47,8 @@ public: virtual int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m) override; virtual int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e) override; + virtual int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names) override; + virtual int sd_bus_open_user(sd_bus **ret) override; virtual int sd_bus_open_system(sd_bus **ret) override; virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override; diff --git a/tests/integrationtests/AdaptorAndProxy_test.cpp b/tests/integrationtests/AdaptorAndProxy_test.cpp index de394dd..de7642d 100644 --- a/tests/integrationtests/AdaptorAndProxy_test.cpp +++ b/tests/integrationtests/AdaptorAndProxy_test.cpp @@ -469,22 +469,39 @@ TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); } -// TODO: Uncomment once we have support for PropertiesChanged signals -//TEST_F(SdbusTestObject, GetsSignalOnChangedPropertiesViaPropertiesInterface) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onPropertiesChangedHandler = [&signalReceived](const std::string& interfaceName, const std::map& changedProperties, const std::vector& invalidatedProperties) -// { -// EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); -// EXPECT_THAT(changedProperties, SizeIs(2)); -// EXPECT_THAT(changedProperties.at("blocking").get(), Eq(false)); -// EXPECT_THAT(invalidatedProperties, SizeIs(0)); -// signalReceived = true; -// }; -// -// m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); -// waitUntil(signalReceived); -//} +TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) +{ + std::atomic signalReceived{false}; + m_proxy->m_onPropertiesChangedHandler = [&signalReceived](const std::string& interfaceName, const std::map& changedProperties, const std::vector& invalidatedProperties) + { + EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); + EXPECT_THAT(changedProperties, SizeIs(1)); + EXPECT_THAT(changedProperties.at("blocking").get(), Eq(!DEFAULT_BLOCKING_VALUE)); + signalReceived = true; + }; + + m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); + m_proxy->action(DEFAULT_ACTION_VALUE*2); + m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); + waitUntil(signalReceived); +} + +TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) +{ + std::atomic signalReceived{false}; + m_proxy->m_onPropertiesChangedHandler = [&signalReceived](const std::string& interfaceName, const std::map& changedProperties, const std::vector& invalidatedProperties) + { + EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); + EXPECT_THAT(changedProperties, SizeIs(1)); + EXPECT_THAT(changedProperties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + ASSERT_THAT(invalidatedProperties, SizeIs(1)); + EXPECT_THAT(invalidatedProperties[0], Eq("action")); + signalReceived = true; + }; + + m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); + waitUntil(signalReceived); +} TEST_F(SdbusTestObject, DoesNotProvideObjectManagerInterfaceByDefault) { diff --git a/tests/integrationtests/TestingAdaptor.h b/tests/integrationtests/TestingAdaptor.h index 4eb0d49..b3b4233 100644 --- a/tests/integrationtests/TestingAdaptor.h +++ b/tests/integrationtests/TestingAdaptor.h @@ -31,7 +31,8 @@ #include #include -class TestingAdaptor : public sdbus::AdaptorInterfaces +class TestingAdaptor : public sdbus::AdaptorInterfaces< testing_adaptor + , sdbus::Properties_adaptor > { public: TestingAdaptor(sdbus::IConnection& connection) : diff --git a/tests/unittests/mocks/SdBusMock.h b/tests/unittests/mocks/SdBusMock.h index da37595..c099ccf 100644 --- a/tests/unittests/mocks/SdBusMock.h +++ b/tests/unittests/mocks/SdBusMock.h @@ -46,6 +46,8 @@ public: MOCK_METHOD2(sd_bus_message_new_method_return, int(sd_bus_message *call, sd_bus_message **m)); MOCK_METHOD3(sd_bus_message_new_method_error, int(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e)); + MOCK_METHOD4(sd_bus_emit_properties_changed_strv, int(sd_bus *bus, const char *path, const char *interface, char **names)); + MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret)); MOCK_METHOD1(sd_bus_open_system, int(sd_bus **ret)); MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));