Search code examples
c++linuxdbus

sdbus-c++: client doesn't react to signals from server


I'm trying to create the daemon without UI and GUI-app that will configure it. This app should to send commands to daemon and display updated info. I'm trying to use DBus for it and sdbus-c++ library.

The problem that client doesn't react to the server's signals. It ignores PropertiesChanged signal and also my custom signals. But I see it in the dbus-monitor:

signal time=1712935168.329878 sender=:1.25 -> destination=(null destination) serial=20 path=/test/sdbuscpp/TestApp/TestObj/Obj2; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged
   string "test.sdbuscpp.TestApp.TestObj"
   array [
      dict entry(
         string "RandomNumber"
         variant             uint64 1881716734
      )
   ]
   array [
   ]
signal time=1712935168.329894 sender=:1.25 -> destination=(null destination) serial=21 path=/test/sdbuscpp/TestApp/TestObj/Obj2; interface=test.sdbuscpp.TestApp.TestObj; member=Updated
   string "RandomNumber"

so look like the server sends signals but client ignores them.

Only InterfaceAdded and InterfaceRemoved of ManagerProxy are catched but each signal of ObjectProxy is ignored.

So, what am I doing wrong?

Generate bindings XML:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/test/sdbuscpp/TestApp/TestObj">
    <interface name="test.sdbuscpp.TestApp.TestObj">
        <property name="StringProperty" type="s" access="read"/>

    <property name="RandomNumber" type="t" access="readwrite">
        <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
    </property>

    <signal name="Updated">
        <arg name="Property" type="s" />
    </signal>
    </interface>
</node>

Server's code:

#include "daemon-glue.h"

#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <signal.h>
#include <syslog.h>
#include <sys/stat.h>


class ManagerAdaptor : public sdbus::AdaptorInterfaces< sdbus::ObjectManager_adaptor >
{
public:
    ManagerAdaptor(sdbus::IConnection& connection, std::string path)
    : AdaptorInterfaces(connection, std::move(path))
    {
    
        registerAdaptor();
    }

    ~ManagerAdaptor()
    {
        unregisterAdaptor();
    }
};

class TestObjAdaptor final : public sdbus::AdaptorInterfaces< test::sdbuscpp::TestApp::TestObj_adaptor,
                                                sdbus::ManagedObject_adaptor,
                                                sdbus::Properties_adaptor >
{
public:
    TestObjAdaptor(sdbus::IConnection& connection, std::string path, std::string someString, uint64_t someRandomNumber)
    : AdaptorInterfaces(connection, std::move(path))
    , someString(std::move(someString))
    , someRandomNumber(someRandomNumber)
    {
    registerAdaptor();
        emitInterfacesAddedSignal({test::sdbuscpp::TestApp::TestObj_adaptor::INTERFACE_NAME});
    }

    ~TestObjAdaptor()
    {
        emitInterfacesRemovedSignal({test::sdbuscpp::TestApp::TestObj_adaptor::INTERFACE_NAME});
        unregisterAdaptor();
    }

    uint64_t RandomNumber() override
    {
        return someRandomNumber;
    }

    void RandomNumber(const uint64_t& number) override
    {
    someRandomNumber = number;
    
    emitPropertiesChangedSignal(test::sdbuscpp::TestApp::TestObj_adaptor::INTERFACE_NAME, {"RandomNumber"});
    emitUpdated("RandomNumber");
    }

    std::string StringProperty() override
    {
        return someString;
    }

private:
    std::string someString;
    uint64_t someRandomNumber;
};

int main()
{
    std::srand(std::time(nullptr));

    auto connection = sdbus::createSessionBusConnection();
    connection->requestName("test.sdbuscpp.TestApp");
    connection->enterEventLoopAsync();

    auto manager = std::make_unique<ManagerAdaptor>(*connection, "/test/sdbuscpp/TestApp");
    
    std::unique_ptr<TestObjAdaptor> ObjArray[] = {
    std::make_unique<TestObjAdaptor>(*connection, "/test/sdbuscpp/TestApp/TestObj/Obj1", "Obj1", std::rand()),
    std::make_unique<TestObjAdaptor>(*connection, "/test/sdbuscpp/TestApp/TestObj/Obj2", "Obj2", std::rand()),
    std::make_unique<TestObjAdaptor>(*connection, "/test/sdbuscpp/TestApp/TestObj/Obj3", "Obj3", std::rand())
    };

    while (true)
    {
        for (const auto& Obj : ObjArray)
    {
        Obj->RandomNumber(std::rand());
    }

    std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    for (auto& Obj : ObjArray)
    {
    Obj.reset();
    }

    connection->releaseName("test.sdbuscpp.TestApp");
    connection->leaveEventLoop();

    return EXIT_SUCCESS;
}

Client's code:

#include "gui-glue.h"
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <thread>

class TestObjProxy final : public sdbus::ProxyInterfaces< test::sdbuscpp::TestApp::TestObj_proxy, sdbus::Properties_proxy >
{
public:
    TestObjProxy(sdbus::IConnection& connection, std::string destination, std::string path)
    : ProxyInterfaces(connection, std::move(destination), std::move(path))
    {
        registerProxy();
    }

    ~TestObjProxy()
    {
        unregisterProxy();
    }

    virtual void onPropertiesChanged( const std::string& interface, const std::map<std::string, sdbus::Variant>& changed_properties, const std::vector<std::string>& invalidated_properties) override
    {
    std::cout << interface << " changed" << std::endl;

        // Parse and print some more info
        if (interface != test::sdbuscpp::TestApp::TestObj_proxy::INTERFACE_NAME)
    {
            return;
        }
        
    std::cout << "Changed properties:" << std::endl;
    for (const std::pair<std::string, sdbus::Variant>& property : changed_properties)
    {
        std::cout << property.first << std::endl;
    }
    
    std::cout << "Invalidated properties:" << std::endl;
    for (const std::string& property : invalidated_properties)
    {
        std::cout << property << std::endl;
    }
    }

    virtual void onUpdated(const std::string& Property) override
    {
    std::cout << "Property updated: " << Property << std::endl;
    }

};

class ManagerProxy final : public sdbus::ProxyInterfaces< sdbus::ObjectManager_proxy >
{
public:
    ManagerProxy(sdbus::IConnection& connection, const std::string& destination, std::string path)
    : ProxyInterfaces(connection, destination, std::move(path))
    , m_connection(connection)
    , m_destination(destination)
    {
    registerProxy();
    }

    ~ManagerProxy()
    {
        unregisterProxy();
    }

    void handleExistingObjects()
    {
        std::map<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>> objectsInterfacesAndProperties;
        objectsInterfacesAndProperties = GetManagedObjects();

    std::cout << objectsInterfacesAndProperties.size() << " objects awaits" << std::endl;

        for (const std::pair<sdbus::ObjectPath, std::map<std::string, std::map<std::string, sdbus::Variant>>>& objectPair : objectsInterfacesAndProperties) {
            onInterfacesAdded(objectPair.first, objectPair.second);
        }
    }

private:
    void onInterfacesAdded( const sdbus::ObjectPath& objectPath
            , const std::map<std::string, std::map<std::string, sdbus::Variant>>& interfacesAndProperties) override
    {
        std::cout << objectPath << " added:\t";
        for (const auto& interfacePair : interfacesAndProperties) {
            std::cout << interfacePair.first << " ";
        }
        std::cout << std::endl;

        // Parse and print some more info
        auto objInterface = interfacesAndProperties.find(test::sdbuscpp::TestApp::TestObj_proxy::INTERFACE_NAME);
        if (objInterface == interfacesAndProperties.end()) {
            return;
        }
        const auto& properties = objInterface->second;
        // get a property which was passed as part of the signal.
        const auto& name = properties.at("StringProperty").get<std::string>();
        // or create a proxy instance to the newly added object.
        TestObjProxy obj(m_connection, m_destination, objectPath);
        std::cout << "Object " << name << " num " << obj.RandomNumber() << std::endl;
    }

    void onInterfacesRemoved( const sdbus::ObjectPath& objectPath
            , const std::vector<std::string>& interfaces) override
    {
        std::cout << objectPath << " removed:\t";
        for (const auto& interface : interfaces) {
            std::cout << interface << " ";
        }
        std::cout << std::endl;
    }

    

    sdbus::IConnection& m_connection;
    std::string m_destination;
};

int main()
{
    auto connection = sdbus::createSessionBusConnection();

    auto managerProxy = std::make_unique<ManagerProxy>(*connection, "test.sdbuscpp.TestApp", "/test/sdbuscpp/TestApp");
    try {
        managerProxy->handleExistingObjects();
    }
    catch (const sdbus::Error& e) {
        if (e.getName() == "org.freedesktop.DBus.Error.ServiceUnknown") {
            std::cout << "Waiting for server to start ..." << std::endl;
        }
    }

    std::cout << "enter event loop" << std::endl;
    connection->enterEventLoopAsync();

    while(true);

    std::cout << "exit event loop" << std::endl;
    return 0;
}

Solution

  • I found the root of problem.

    The problem was caused by creating TestObjProxy on stack instead of heap in the ManagerProxy::onInterfacesAdded. So, the object is removed after this function returned and nobody listens the update signals.

    After creating object at dynamic allocated memory, the problem has gone.