- Notifications
You must be signed in to change notification settings - Fork2
A new C++17 dbus library will come to existence here where users can define a remote interface and use it as if it were implemented in the program itself.
License
5cript/dbus-glue
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
The DBus Glue library wants to make interfacing with a DBus Service / Interface almost as simple as defining a C++ interface.The DBus protocol is almost entirely hidden from the user.This library only enables interfacing with existant DBus APIs. Creating one yourself with this libraryisnot possible!
Example interfaces / Predefined interfaces can be found in my repository dbus-glue-system.
https://5cript.github.io/dbus-glue/
Here is a checkbox list of all the tings to come and all that are done so far.
- A bus object for all necessary bus related tasks.
- Handle the bus lifetime
- Calling methods
- Reading properties
- Writing properties
- Listening for signals
- Starting an event loop
- A Macro for declaring DBus interfaces as C++ interfaces.
- Attaching or creating a (simple) event loop to the bus object.(Note: If you use the sd_event* object system, you still have to setup and teardown the event stuff yourself, this is not wrapped by this library.)
- A rudamentary generator for interfaces. Not really necessary, since writing a simple class declaration is easy and fast, but can be used to get a quick start for big interfaces.
On a created interface, linked to a given DBus interface, you can:
- Call methods.
- Read and Write Properties.
- Connect to slots and listen for signals.
- Call methods and get the results asnchronously.
- Read and Write Properties asynchronously.
With your own registered interface you can:
- Declare your interface
- Expose the interface
- Expose a method
- Expose a property (read, read/write)
- Expose a signal
- Make exposed signal emitable
This project uses cmake.
- cd dbus-glue
- mkdir -p build
- cmake ..
- make -j4 (or more/less cores)
- make install (if you want)
- libsystemd, because the systemd sd-bus library is used underneath.
- boost preprocessor
This is a short tutorial for declaring external interfaces, attaching to them and using them.
- clone the library using git clone
- create a building directory somewhere and build the library, see the build chapter.
- now you can add the "include" directory to your projects search path and link the lib. Alternatively use make install. I personally advocate for a project structure where all git dependecies are parallel in the filesystem to the dependent project.
A very simple program to start off with.First the includes required for basics:
#include<dbus-glue/dbus_interface.hpp>
Here we see a very basic interface. You can use d-feet toinspect and try interfaces provided by services, daemons and other programs.
namespaceorg::freedesktop{// Declare an interface. This one is just reduced to one function, which is enough for this exampleclassIDBus {public:// We want to make this function callablevirtualautoListNames() -> std::vector <std::string> = 0;// Silences a warning, but IDBus is never really used polymorphicly.virtual~IDBus() =default; };}
Mirror the dbus interface in C++ code.When you dont provide any Methods, Properties or Signals, aspecial placeholder macro has to be used called "DBUS_DECLARE_NO_*"
DBUS_DECLARE_NAMESPACE( (org)(freedesktop), IDBus,DBUS_DECLARE_METHODS(ListNames), DBUS_DECLARE_NO_PROPERTIES, DBUS_DECLARE_NO_SIGNALS)This shows how to use our set up interface:
intmain(){usingnamespaceDBusGlue;// open the system busauto bus =open_system_bus();// bind the interface to the remote dbus interface:auto dbusInterface = create_interface <org::freedesktop::IDBus>( bus,"org.freedesktop.DBus","/org/freedesktop/DBus","org.freedesktop.DBus" );// you can now call list names:auto services = dbusInterface->ListNames();// print all to console, which aren't stupidly named / numbered, therefore unnamedfor (autoconst& service : services)if (service.front() !=':') std::cout << service <<"\n";// -> org.freedesktop.DBus// org.freedesktop.Accounts// ...return0;}
Now lets change a little bit of the program. We now dont want to do the call synchronously, but asynchronously. Note that as soon as an event handling loop is attached to the bus, even the synchronous calls get processed by the loop and block while its not their turn to be executed, which, in bad cases, can result in "long" wait times at these functions, which may be undersirable.I therefore recommend to switch to an entirely asynchronous architecture when you use any asynchronous methods/signals on the bus.
Asynchronous functions use "continuation" style. Which means that whenever an asynchronous function finishes, a callback is called from which execution can be resumed.
Properties can also be read and written asynchronously, by calling get/set on them with the async flag, just the same as with methods.
Only showing relevant differences to before:
// new required header for the event loop#include<dbus-glue/bindings/busy_loop.hpp>intmain(){usingnamespaceDBusGlue;usingnamespacestd::chrono_literals;// open the system busauto bus =open_system_bus();// create an event loop and attach it to the bus.// This is the default implementation, you can provide your own. For instance by using sd_event.make_busy_loop(&bus);// bind the interface to the remote dbus interface:auto dbusInterface = create_interface <org::freedesktop::IDBus>( bus,"org.freedesktop.DBus","/org/freedesktop/DBus","org.freedesktop.DBus" );// call ListNames asynchronously with async_flag overload:// (always the first parameter, if there are any) dbusInterface->ListNames(async_flag)// if you omit the then, you wont get a result / response.// Which can be fine, but is usually not intended .then([](autoconst& services) {// print all to console, which aren't stupidly named / numbered, therefore unnamedfor (autoconst& service : services)if (service.front() !=':') std::cout << service <<"\n"; })// This is called when something goes wrong// Can be omited .error([](auto&,autoconst& errorMessage) {// first parameter is the result message,// It probably does not contain anything useful. std::cerr << errorMessage <<"\n"; })// A timeout for completion, optional.// default is 10 seconds .timeout(1s) ;return0;}
Asynchronous calls return a proxy object that can be usedto bind callback, error callback and timeout before the call is made. The execution is made when the proxy object gets destroyed, which is after the end of the statement.
So you should ignore the return, and not pass it to a variable, like so:
// DONT!auto&& temp = dbusInterface.ListNames(async_flag);
unless you actually want that like so:
{// artifical scopeauto&& temp = dbusInterface.ListNames(async_flag); temp.then([](autoconst&){ std::cout <<"callback!\n"; });}// asynchronous call is made here.Variants are containers that can contain a number of types.In order to read out of a variant, you have to know its stored type first. Luckily sdbus can deliver this information.One small note: due to implementation restrictions, loading and storing a value from a variant can only be done by a free function and not by a member function.This would look like follows:
// Example for reading a variant.#include<dbus-glue/dbus_interface.hpp>#include<dbus-glue/bindings/variant_helpers.hpp>#include<iostream>intmain(){auto bus =open_system_bus();// a variant dictionary is a map of variants. variant_dictionary<std::map> dict; bus.read_properties ("org.freedesktop.Accounts","/org/freedesktop/Accounts","org.freedesktop.Accounts", dict );// In case that you dont know the type beforehand, but have to test first:auto descriptor = dict["DaemonVersion"].type();// print it in a somewhat readable way. std::cout << descriptor.string() <<"\n"; std::string ver, ver2; variant_load<std::string>(dict["DaemonVersion"], ver); std::cout << ver <<"\n";// You do NOT need to rewind a variant before rereading it, this is done for you.// dict["DaemonVersion"].rewind(); variant_load<std::string>(dict["DaemonVersion"], ver2); std::cout << ver2 <<"\n";}
// Example for writing to a variant.#include<dbus-glue/dbus_interface.hpp>#include<dbus-glue/bindings/variant_helpers.hpp>#include<iostream>usingnamespacestd::string_literals;intmain(){auto bus =open_system_bus(); variant var;// has to be non-member unfortunatelyvariant_store(bus, var,"hello"s); std::string val;variant_load(var, val); std::cout << val <<"\n";// -> hello std::cout << std::flush;return0;}
This is the tutorial for declaring your own dbus interface.
On contrary to interfaces that are externally supplied, thereis no adapt macro to use. The reason is that a macro to provide allthe parameter and return value names on top of method names etc. would be very verbose and hard to debug on errors.
Here we have an interface that we want to expose to the world:
#include<dbus-glue/bindings/exposable_interface.hpp>// Your interface to export has to derive from exposable_interface.classMyInterface :publicDBusGlue::exposable_interface{public:// these members determine the path and service name// for registration, do not need to be const literals. std::stringpath()constoverride {return"/bluetooth"; } std::stringservice()constoverride {return"com.bla.my"; }public:// MethodsautoDisplayText(std::stringconst& text) -> void {}autoMultiply(int lhs,int rhs) -> int {return lhs * rhs;}public:// Propertiesbool IsThisCool;public:// Signals// this is called emitable, not signal as in adapted interfaces. emitable <void(*)(int)> FireMe{this,"FireMe"};};
#include<dbus-glue/interface_builder.hpp>intmain(){auto bus =open_user_bus();usingnamespaceDBusGlue;usingnamespaceExposeHelpers;// creates an instance of MyInterface that can be used.auto shared_ptr_to_interface = make_interface <MyInterface>(// Multiply Method DBusGlue::exposable_method_factory{} <<name("Multiply") <<// Method nameresult("Product") <<// Name can only be a single word!parameter(0,"a") <<// Name can only be a single word!parameter(1,"b") <<// Parameter number is optional, but if supplied, all should have it supplied.as(&MyInterface::Multiply),// Display Text Method DBusGlue::exposable_method_factory{} <<name("DisplayText") <<result("Nothing") <<parameter("text") <<as(&MyInterface::DisplayText),// IsThisCool Property DBusGlue::exposable_property_factory{} <<name("IsThisCool") <<writeable(true) <<as(&MyInterface::IsThisCool),// FireMe Signal DBusGlue::exposable_signal_factory{} <<// name is in emitable constructor, not needed here.// d-feet does not show signal parameter namesparameter("integral") <<as(&MyInterface::FireMe) );// The bus takes a share to hold the interface and exposes it on the bus.auto result = bus.expose_interface(shared_ptr_to_interface);if (result <0) { std::cerr <<strerror(-result);return1; } result =sd_bus_request_name(static_cast<sd_bus*> (bus),"com.bla.my",0);if (result <0) { std::cerr <<strerror(-result);return1; }make_busy_loop(&bus, 200ms); bus.loop <busy_loop>()->error_callback([](int, std::stringconst& msg){ std::cerr << msg << std::endl;returntrue; });// emit signal exposed->FireMe.emit(5);// prevent immediate exit here however you like. std::cin.get();}
More examples are in the example directory
Here is the first example, to show a basis of what this library wants to do.
#include<dbus-glue/dbus_interface.hpp>#include<iostream>#include<vector>#include<string>usingnamespaceDBusGlue;/** * @brief The IAccounts interface. Its the provided interface (org.freedesktop.Accounts) as a C++ class.*/classIAccounts{public:virtual~IAccounts() =default;virtualautoCacheUser(std::stringconst& name) -> object_path = 0;virtualautoCreateUser(std::stringconst& name, std::stringconst& fullname,int32_t accountType) -> object_path = 0;virtualautoDeleteUser(int64_t id,bool removeFiles) -> void = 0;virtualautoFindUserById(int64_t id) -> object_path = 0;virtualautoListCachedUsers() -> std::vector <object_path> = 0;virtualautoUncacheUser(std::stringconst& user) -> void = 0;public:// Properties readable <std::vector <object_path>> AutomaticLoginUsers; readable <bool> HasMultipleUsers; readable <bool> HasNoUsers; readable <std::string> DaemonVersion;public:// signals DBusGlue::signal <void(object_path)> UserAdded; DBusGlue::signal <void(object_path)> UserDeleted;};//----------------------------------------------------------------------------------------// This step is necessary to enable interface auto-implementation.// There is a limit to how many properterties and methods are possible. (currently either 64 or 255 each, haven't tried, assume 64)// This limit can be circumvented by DBUS_DECLARE_N. Which allows to make the same interface more than once.// A successory call to DBUS_DECLARE_ZIP merges them all together.DBUS_DECLARE( IAccounts,DBUS_DECLARE_METHODS(CacheUser, CreateUser, DeleteUser, FindUserById, ListCachedUsers, UncacheUser), DBUS_DECLARE_PROPERTIES(AutomaticLoginUsers, HasMultipleUsers, HasNoUsers, DaemonVersion), DBUS_DECLARE_SIGNALS(UserAdded, UserDeleted))//----------------------------------------------------------------------------------------int main(){// open the system bus.auto bus =open_system_bus();try {// attach interface to remote interface.auto user_control = create_interface <IAccounts> (bus,"org.freedesktop.Accounts","/org/freedesktop/Accounts","org.freedesktop.Accounts");// calling a method with parameters//user_control.CreateUser("hello", "hello", 0);// calling a method and getting the resultauto cachedUsers = user_control->ListCachedUsers();for (autoconst& user : cachedUsers) {// the object_path type has a stream operator for output std::cout << user <<"\n"; }// reading a property std::cout << user_control->DaemonVersion <<"\n"; }catch (std::exceptionconst& exc)// catch all possible exceptions. { std::cout << exc.what() <<"\n"; }return0;}
Here is an example on how to listen to emitted signals.Note that signal handling requires an event loop.
#include<dbus-glue/dbus_interface.hpp>#include<dbus-glue/bindings/bus.hpp>#include<dbus-glue/bindings/busy_loop.hpp>#include<iostream>#include<string>#include<thread>#include<chrono>usingnamespaceDBusGlue;usingnamespacestd::chrono_literals;/** * @brief The IAccounts interface. Its the provided interface (org.freedesktop.Accounts) as a C++ class.*/classIAccounts{public:virtual~IAccounts() =default;public:// MethodsvirtualautoCreateUser(std::stringconst& name, std::stringconst& fullname,int32_t accountType) -> object_path = 0;virtualautoDeleteUser(int64_t id,bool removeFiles) -> void = 0;public:// Propertiespublic:// signals DBusGlue::signal <void(object_path)> UserAdded; DBusGlue::signal <void(object_path)> UserDeleted;};//----------------------------------------------------------------------------------------// This step is necessary to enable interface auto-implementation.// There is a limit to how many properterties and methods are possible. (currently either 64 or 255 each, haven't tried, assume 64)// This limit can be circumvented by DBUS_DECLARE_N. Which allows to make the same interface more than once.// A successory call to DBUS_DECLARE_ZIP merges them all together.DBUS_DECLARE( IAccounts,DBUS_DECLARE_METHODS(CreateUser, DeleteUser), DBUS_DECLARE_NO_PROPERTIES, DBUS_DECLARE_SIGNALS(UserAdded, UserDeleted))int main(){auto bus =open_system_bus();try { bus.install_event_loop(std::unique_ptr <event_loop> (newbusy_loop(&bus, 50ms)));// wrapped interface for creating / deleting accounts.auto accountControl = create_interface <IAccounts>( bus,"org.freedesktop.Accounts","/org/freedesktop/Accounts","org.freedesktop.Accounts" ); accountControl->UserAdded.listen( [](object_pathconst& p) {// success callback std::cout <<"callback - create:" << p << std::endl; }, [](message&, std::stringconst& str) {// failure callback std::cerr <<"oh no something gone wrong:" << str <<"\n"; } );// WARNING! Passing "release_slot" forces you to manage the slots lifetime yourself!// You can use this variation to manage lifetimes of your observed signals. std::unique_ptr<void,void(*)(void*)> slot = accountControl->UserDeleted.listen( [&](object_pathconst& p) {// this is called from the dbus system. std::cout <<"callback - delete:" << p << std::endl;// create a user from here.auto path = accountControl->CreateUser("tempus","tempus",0); }, [](message&, std::stringconst& str) {// this is called when an error got signaled into our callback. std::cerr <<"oh no something gone wrong:" << str <<"\n"; }, DBusGlue::release_slot );// try to delete a user with id 1001. WARNING, DONT JUST DELETE SOME USER ON YOUR SYSTEM. obviously...try {// commented out in case you just run this example// you should get the id from the name first.//accountControl->DeleteUser(1001, false); }catch (std::exceptionconst& exc) {// Create the user if he doesn't exist accountControl->CreateUser("tempus","tempus",0); std::cout << exc.what() << std::endl; }// just wait here so we dont exit directly std::cin.get(); }catch (std::exceptionconst& exc) { std::cout << exc.what() <<"\n"; }return0;}
About
A new C++17 dbus library will come to existence here where users can define a remote interface and use it as if it were implemented in the program itself.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.