# gdbuspp **Repository Path**: GysName/gdbuspp ## Basic Information - **Project Name**: gdbuspp - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-10-01 - **Last Updated**: 2025-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README GDBus++ :: glib2 D-Bus C++ interface ==================================== This library provides a simpler C++ based interface to implement D-Bus into applications in a more C++ approach, based on the C++17 standard. How to build ------------ This project uses the [Meson Build System](https://mesonbuild.com/) to compile and install the GDBus++ library and header files. $ meson setup --prefix=/usr _builddir $ cd _builddir $ meson compile $ meson test $ meson install $ sudo ldconfig To see the configuration options available: $ meson configure See the [meson documentation](https://mesonbuild.com/Commands.html#configure) how to modify these settings. Basic concepts and the namespaces --------------------------------- The GDBus++ API is split up across multiple C++ namespaces, all using `DBus` as the root starting point. All objects created in the `DBus` namespace should return a `Ptr` based object. This is just a short form of a `std::shared_ptr<>`. The `DBus::Connection::Ptr` is the same as `std::shared_ptr`. It is not a requirement to use the `Ptr` style, this is more a code style aspect. The code excerpts used below can be found in [docs/example-service.cpp](docs/example-service.cpp) and [docs/example-proxy.cpp](docs/example-proxy.cpp). #### `DBus::Connection` This contains the class and declarations used to connect to the D-Bus on the running system. To create a connection to the session D-Bus: ```C++ auto connection = DBus::Connection::Create(DBus::BusType::Session); ``` To create a connection to the system D-Bus: ```C++ auto connection = DBus::Connection::Create(DBus::BusType::System); ``` No other bus connections are currently supported. The `Create()` method will return a `DBus::Connection::Ptr` object. This object is used both by the service providing side and the proxy client side. #### `DBus::Service` This provides the main building block to provide a service on the D-Bus on the system. This is a class which your own implementation must extend. The key elements for a service are: ```C++ class MyService : public DBus::Service { public: MyService(DBus::Connection::Ptr connection) : DBus::Service(con, "net.example.myservice") { } void BusNameAcquired(const std::string &busname) override { // Code run when the name of your service is accepted } void BusNameLost(const std::string &busname) override { // Code run when your service could not get or keep the bus name } }; ``` In the `main()` function of your program, you initialize the service like this; this example uses the Session Bus: ```C++ auto connection = DBus::Connection::Create(DBus::BusType::SESSION); auto my_service = DBus::Service::Create(connection); ``` #### `DBus::Object::Base` The `Base` class in the `DBus::Object` namespace provides the needed glue to bind your own C++ object into a D-Bus object. Inheritance is used here as well. In relation to `DBus::Object`, there is also `DBus::Object::Path` implementation available. This behaves just like `std::string`, but does have some additional checks to ensure it's a valid D-Bus object path. It is recommended to use this as container for object paths, since the GDBus++ APIs will also treat this as an object path instead of a string data type when passing the data over the D-Bus. Using `std::string` will in these cases require overriding the data type if the D-Bus interface expects an object path. ```C++ class MyObject : public DBus::Object::Base { public: MyObject() : DBus::Object::Base("/example/myobject", "net.example.myinterface") { } const bool Authorize(const DBus::Authz::Request::Ptr request) override { // code to authorize a D-Bus proxy client accessing this object } }; ``` All D-Bus services must have a root D-Bus object. This is referred to as the "service handler" in GDBus++. This service handler is a basic `DBus::Object::Base` based object and is added to the D-Bus service using the `DBus::Service::CreateServiceHandler<>()` method ```C++ auto connection = DBus::Connection::Create(DBus::BusType::SESSION); auto my_service = DBus::Service::Create(connection); my_service->CreateServiceHandler(connection); ``` ##### Adding D-Bus methods To add D-Bus methods, use the `DBus::Object::Base::AddMethod()` method, where you define a callback function and the arguments it will receive and return. This must be setup during the initialization of the object. ```C++ MyObject() : DBus::Object::Base("/example/myobject", "net.example.myinterface") { AddMethod("MyMethod", [](DBus::Object::Method::Arguments::Ptr args) { // Code performed on access } ); } ``` This adds a very simple method not receiving any input nor sending any output back to the calling D-Bus proxy client. To add arguments to a D-bus method, you use the `AddInput()` and `AddOutput()` methods in the object the `AddMethod()` method returns: ```C++ // ... auto args = AddMethod("MethodWithArgs", [](DBus::Object::Method::Arguments::Ptr args) { // Code performed on access } ); args->AddInput("string_1", glib2::DataType::DBus()); args->AddInput("string_2", glib2::DataType::DBus()); args->AddOutput("result", glib2::DataType::DBus()); ``` With these two methods declared, the D-Bus object introspection will look something like this: ``` $ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject node /example/myobject { // ... interface net.example.myinterface { methods: MyMethod(); MethodWithArgs(in s string_1, in s string_2, out s result); // ... }; }; ``` ##### Adding D-Bus object properties To add D-Bus properties (directly accessible variables), also set up via the constructor: ```C++ MyObject() : DBus::Object::Base("/example/myobject", "net.example.myinterface") { // ... bool readwrite = true; AddProperty("my_property", my_property, readwrite); } // ... private: std::string my_property{"My Initial Value"}; ``` This will add a direct mapping between a C++ variable and a D-Bus object property. In this case, the `readwrite` variable is `true`, which allows the D-Bus proxy client to modify this variable. The C++ variable is then updated automatically. If the C++ variable is modified by your program, a "Get Property" call to your D-Bus object will reflect that as well. The D-Bus data type is automatically identified via the C++ variable data type. The D-Bus introspection of this property will look something like this: ``` $ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject node /example/myobject { // ... interface net.example.myinterface { properties: readwrite s my_property = 'My Initial Value'; // ... }; }; ``` There are more ways to add more complex data types as well, by using the `AddPropertyBySpec()` method instead of the `AddProperty()`. Please see the example code for more details how do make use of that. ##### Adding D-Bus object signals - via `DBus::Signals::Group` The `Group` class in `DBus::Signals` is used to create an object sending various types of D-Bus signals from a `DBus::Object::Base` based object. This is done by creating your own "signal object": ```C++ class MySignalGroup : public DBus::Signals::Group { public: using Ptr = std::shared_ptr; MySignalGroup(DBus::Connection::Ptr connection) : DBus::Signals::Group(connection, "/example/myobject", "net.example.myinterface") { RegisterSignal("MySignal", {{"message", glib2::DataType::DBus()}}); } void MySignal(const std::string &message_content) { GVariant *message = glib2::Value::CreateTupleWrapped(message_content); SendGVariant("MySignal", message); } }; ``` This class can be extended with specialised functions to satisfy your need for sending D-Bus signals. All you need in your code is to instantiate this as an object in your `DBus::Object::Base` based constructor: ```C++ MyObject(DBus::Connection::Ptr connection) : DBus::Object::Base("/example/myobject", "net.example.myinterface") { // ... my_signals = DBus::Signals::Group::Create(connection); my_signals->AddTarget("") RegisterSignals(my_signals); } private: MySignals::Ptr signals = nullptr; ``` The `RegisterSignals()` call will add the signal details to the D-Bus object introspection: ``` $ gdbus introspect --session --dest net.example.myservice --object-path /example/myobject node /example/myobject { // ... interface net.example.myinterface { signals: MySignal(s message); // ... }; }; ``` The any of the code inside the `MyObject` object can now call `my_signals->MySignal("message");` to send the `MySignal` D-Bus signal. Since the `AddTarget()` used `""` as the destination target, this will be be sent to any application listening to the `MySignal signal on the session bus. ##### D-Bus method arguments - via `DBus::Object::Method::Arguments` A smart-pointer to an `Arguments` object will be returned when calling the `DBus::Object::Base::AddMethod()` method. This object is used to add input and output arguments to a D-Bus method you declare, as well as preparing for passing file descriptors via this D-Bus method call. In addition, this object is also passed to the D-Bus method callback as the only argument. This must be used to extract the arguments passed to the D-Bus method by the proxy client, as well as the response this method should return back to the caller. This happens currently via glib2's `GVariant` objects, which are generic value containers. To retrieve the arguments passed to the callback function: ```C++ [](DBus::Object::Method::Arguments::Ptr args) { GVariant *params = args->GetMethodParameters(); std::string string1 = glib2::Value::Extract(params, 0); std::string string2 = glib2::Value::Extract(params, 1); } ``` To return a value back to the caller: ```C++ [](DBus::Object::Method::Arguments::Ptr args) { // .... std::string result = string1 + " <=> " + string2; GVariant *ret = glib2::Value::CreateTupleWrapped(result); args->SetMethodReturn(ret); } ``` To return an error back to the caller, create an exception class based on the `DBus::Exception` class and throw that as an exception. #### D-Bus object management via `DBus::Object::Manager` A D-Bus service may want to dynamically add or remove D-Bus objects during its runtime. This is functionality provided via the `DBus::Object::Manager`. When creating the `DBus::Service`, an object manager is prepared for you. ```C++ auto my_service = DBus::Service::Create(connection); auto object_manager = my_service->GetObjectManager(); ``` This manager provides two primary methods, first to create a new D-Bus object in the D-Bus service the application is providing. The second call will remove the object from the service. ```C++ auto my_new_object = object_manager->CreateObject(MY_CLASS_CONSTRUCTOR_ARGUMENTS) object_manager->RemoveObject(DBus::Object::Path &object_path) ``` The D-Bus path this new object will use comes from the `DBus::Object::Base()` constructor; the first argument here is the D-Bus object path to use. The `DBus::Object::Manager::CreateObject()` call will return a `Ptr` (`std::shared_ptr`) to the newly created object. If you preserve a reference to this object, you can remove the object like this: ```C++ object_manager->RemoveObject(my_new_object->GetPath()); ``` A `DBus::Service` implementation wanting to provide access to the `DBus::Object::Manager` in its object, can pass this via the class constructor: ```C++ MyObject(DBus::Connection::Ptr connection, DBus::Object::Manager::Ptr obj_mgr) : DBus::Object::Base("/example/myobject", "net.example.myinterface"), object_manager(obj_mgr) { // ... private: DBus::Object::Manager::Ptr object_manager = nullptr; }; ``` See the [tests/simple-service.cpp](tests/simple-service.cpp) for a more thorough implementation how to use this functionality. #### `DBus::Proxy::Client` This is the main class used to connect to a D-Bus service. This can be used both as a stand-alone class or being implemented into your own class providing a more direct C++ API to a D-Bus service. To call a D-Bus method: ```C++ auto connection = DBus::Connection::Create(DBus::BusType::SESSION); auto proxy = Proxy::Client::Create(connection, "net.example.myservice"); auto preset = Proxy::TargetPreset::Create("/example/myobject", "net.example.myinterface"); GVariantBuilder *args_builder = glib2::Builder::Create("(ss)"); glib2::Builder::Add(args_builder, std::string("My first string")); glib2::Builder::Add(args_builder, std::string("My Second String")); GVariant *arguments = glib2::Builder::Finish(args_builder); GVariant *response = proxy->Call(preset, "MethodWithArgs", arguments); auto result = glib2::Value::Extract(response, 0); g_variant_unref(response); ``` This will call the `MethodWithArgs` D-Bus method with the required arguments and extract the result from the response. To retrieve a D-Bus object property: ```C++ std::string my_property = proxy->GetProperty(preset, "my_property"); ``` To modify a D-Bus object property: ```C++ std::string new_property_value = "A changed property"; proxy->SetProperty(preset, "my_property", new_property_value); ``` See the [example-proxy.cpp](docs/example-proxy.cpp) and [example-proxy2.cpp](docs/example-proxy2.cpp) for the a full example of the code snippets above. #### `DBus::MainLoop` This class contains the glib2 main loop logic. Unless there is a need to control the main loop directly, it is recommended to use the main loop built into the `DBus::Service` object. ```C++ auto connection = DBus::Connection::Create(DBus::BusType::SESSION); auto my_service = DBus::Service::Create(connection); my_service->Run(); ``` To set up an independent main loop: ```C++ auto mainloop = DBus::MainLoop::Create(); mainloop->Run(); ``` The advantage of using the `DBus::Service` provided main loop, is that the service itself can more easily shut itself down, by calling the `DBus::Service::Stop()` method. Alternatively, the `DBus::Service` implementation need to get the `DBus::MainLoop::Ptr` object provided via its constructor or another approach. #### `glib2` namespace - various low-level helper functions The `glib2` namespace contains a set of sub-namespaces to help glue the C based API with a more native C++ API. This uses C++ templates heavily and the type deduction happening at compile time. When working with `GVariant` based glib2 objects, the `glib2::Value` namespace is typically the most useful place to start looking. ##### `glib2::Value` Functions to extract or retrieve values from `GVariant` objects to C++ variables are found in this namespace. The `Extract<>()` group of functions will extract values from variable lists (when the D-Bus data type is encapsulated with `(` and `)`). The `Get<>()` group of functions will only read the value out of a `GVariant` object where the value is accessible directly. This namespace also contains the `ExtractVector()` function to convert a `GVariant` based array to a C++ `std::vector<>`. This can only be used if all the values in the `GVariant` array is of the same data type. The The `Create<>()` set of functions will convert a C++ variable into a `GVariant` object with the appropriate D-Bus data type. Similarly, the `CreateVector()` takes a `std::vector<>` C++ object and converts that to a `GVariant` based object. ##### `glib2::Builder` In this namespace there are functions to work with the `GVariantBuilder` API in glib2. This is most commonly used to construct more complex data types and processing data structures. It also contains methods to convert `std::vector<>` to `GVariantBuilder` objects. ##### `glib2::DataType` This namespace contains several functions to retrieve the D-Bus identifier for various C++ data types. Examples code ------------- Several test programs and a few example programs are shipped in this project. All the test programs are indirectly used when running `meson test` and is expected to always work. * [example-service.cpp](docs/example-service.cpp) A very simple D-Bus service. All the service example code excerpts in this file comes from this example. * [example-proxy.cpp](docs/example-proxy.cpp) A very simple D-Bus client proxy, accessing a D-Bus object provided by [example-service.cpp](docs/example-service.cpp). All the client proxy examples comes in this README from this example code. * [example-proxy2.cpp](docs/example-proxy2.cpp) An alternative `example-proxy.cpp` implementation, implementing the proxy as an object which is used directly from the `main()` function. This also uses the service implemented in [example-service.cpp](docs/example-service.cpp). * [simple-service.cpp](tests/simple-service.cpp) This implements a simple D-Bus test service using the Session Bus * [proxy-client.cpp](tests/proxy-client.cpp) This implements a generic client side D-Bus proxy, which has similar functionality to the `gdbus` program. * [signal-subscribe.cpp](tests/signal-subscribe.cpp) Implements a simple signal event handler, used for testing purposes. * [signal-emit.cpp](tests/signal-emit.cpp) Provides a simple program to send one or more signals with various data types * [signal-group.cpp](tests/signal-group.cpp) Similar to `signal-emit.cpp` but gives the possibility to wrap the signal emission of more signals into its own class. * [fd-receive-read.cpp](tests/fd-receive-read.cpp) An test program for receiving a file descriptor over the D-Bus. This requires the D-Bus service to prepare a file descriptor intended to be sent back to the caller. * [fd-send-fstat.cpp](tests/fd-send-fstat.cpp) Similar to `fd-receive-read.cpp`, but here the D-Bus method caller will send a file descriptor to the D-Bus service. License ------- This project is licensed under [AGPLv3](COPYRIGHT.md).