Compare commits

...

421 Commits

Author SHA1 Message Date
7fbfcec455 feat: add async overloads of GetManagedObjects() (#480)
This implements three variants of `GetManagedObjectsAsync()` API into the standard `ObjectManager` client interface.

---------

Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2025-05-05 13:31:44 +02:00
0430ae0ad9 fix: fix the return type of the slot-returning overloads (#494) 2025-05-05 11:18:00 +02:00
6212b12159 feat: add cookie-related API to Message (#491)
Add `Message::getCookie()` and `MethodReply::getReplyCookie()` functions, based on underlying sd-bus cookie functions. They can be useful when pairing method call messages and method call reply messages.

---------

Co-authored-by: Dylan Howey <dylan.howey@evgo.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2025-05-05 08:04:20 +02:00
4389ea39bf chore(ci): do necessary updates and adjustments (#493)
* Bump to latest versions of upload-artifact and checkout GitHub scripts
* Remove obsolete Ubuntu-20.04 image
* Introduce Ubuntu-24.04 image
* Other related updates and fixes in the CI
2025-05-05 07:51:21 +02:00
48ea775531 feat: add createLightWeightProxy overload for convenience (#474) 2025-01-08 13:55:38 +01:00
fafe8487ff fix(codegen): generate correct annotation code (#473)
Before the patch, generating an adaptor from https://gitlab.freedesktop.org/hadess/mpris-spec/-/blob/master/spec/org.mpris.MediaPlayer2.xml
would produce incorrect code like this:
```
        m_object.addVTable( sdbus::setInterfaceFlags().withPropertyUpdateBehavior(sdbus::Flags::EMITS_CHANGE_SIGNAL);
                          , sdbus::registerMethod("Raise").implementedAs([this](){ return this->Raise(); })
                          , sdbus::registerMethod("Quit").implementedAs([this](){ return this->Quit(); })
...

```

With this patch, the code produced is:
```
        m_object.addVTable( sdbus::setInterfaceFlags().withPropertyUpdateBehavior(sdbus::Flags::EMITS_CHANGE_SIGNAL)
                          , sdbus::registerMethod("Raise").implementedAs([this](){ return this->Raise(); })
                          , sdbus::registerMethod("Quit").implementedAs([this](){ return this->Quit(); })
...

```
2025-01-02 17:48:48 +01:00
0261d0ec60 chore: release version v2.1.0 2024-11-20 23:47:56 +01:00
a1419ee45d fix: add missing c-tor for nesting Variants (#467)
This allows nesting variants, i.e. allows a variant to contain another variant as its value. Until now, there was no such possibility, since the default generated copy constructor would be invoked which would create a copy of source variant instead of embed the source variant as a value in the destination variant. The default generated copy constructor is kept, for it makes sense too, but a new tag-based overload is added for embedding the source variant into the destination variant.
2024-11-20 23:40:30 +01:00
02ca7212d1 feat: introduce support for struct-as-dict serialization (#459)
This extends the functionality of SDBUSCPP_REGISTER_STRUCT macro.

It now provides functionality for serializing a user-defined struct as an a{sv} dictionary, and for deserializing an a{sv} dictionary into a user-defined struct. The former one is achieved by decorating the struct with sdbus::as_dictionary(struct), the latter one is an automatic behavior -- when sdbus-c++ is asked to deserialize into a struct but the data in the message is of type a{sv}, then the dict-to-struct deserialization is performed automatically.

There are some aspects of behavior in the serialization/deserialization functionality that can be customized by the client. Newly introduced SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION and SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION macros serve the purpose.
2024-11-20 23:25:13 +01:00
8a117f8b42 feat: add version parameter to xml2cpp codegen tool (#463)
* Added -v, --version xml2cpp command line tool options

* Update tools/xml2cpp-codegen/xml2cpp.cpp

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>

* Updated tools/CMakeLists.txt

---------

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2024-11-20 19:54:49 +01:00
84130b1406 fix(codegen): check fstream status and report in case of failure (#462) 2024-10-31 12:48:19 +01:00
c55907069b refactor: send larger messages properly (#455)
Until now, the solution to ensure that even large messages are fully sent out has been to flush the connection queues after each sending of a message, which is likely an unnecessary call (with unnecessary cost) in vast majority of cases, and which may block the connection from doing other work until the large message is fully sent out. This was a rather quick, hacky workaround.

Now, after the sending the message we check whether it has been sent out fully or not. If not (outbound queues are non-empty), then we send a wake-up signal to the connection event loop. The event loop thread then fetches new sd-bus timeouts and events and will see that there are pending outbound messages to process, and will process them together with any other prospective pending events, until there is nothing to process (i.e., the outbound message has been fully dispatched).
2024-10-10 20:01:22 +02:00
107c6a1a97 refactor: invert dependency between Message and Connection (#457)
This reorganizes the layers of abstraction in the sense how `Message` depends on `Connection` and vice versa. Now, `Message` has a link to the `Connection`. This replaces the shortcut link to the low-level `SdBus` interface that the `Message` kept. The interactions from `Message` now go through `Connection` which forwards them to `SdBus`. `Connection` is now a sole owner of the low-level `SdBus` interface. This allows for future changes around `SdBus` (e.g. a change from virtual functions back to non-virtual functions) without affecting the rest of the library. `Proxy`s and `Object`s can now send messages directly without having to go through `Connection`. The `Connection` no more depends on `Message` business-logic methods; it serves only as a factory for messages.

The flow for creating messages: `Proxy`/`Object` -> `Connection` -> `SdBus`
The flow for sending messages: (`Proxy`/`Object` ->) `Message` -> `Connection` -> `SdBus`

This also better reflects how dependencies are managed in the underlying sd-bus library.

Additionally, `getSdBusInterface()` methods are removed which was anyway planned, and improves the design by "Tell, don't ask" principle.

This refactoring is the necessary enabler for other upcoming improvements (regarding sending long messages, or creds refactoring, for example).
2024-10-02 21:22:02 +02:00
1b7acaa735 fix: cleanup for older libsystemd versions (#454)
Fix inconsistencies in documentation and CMake files regarding minimum required libsystemd version (which is v238 for sdbus-c++ v2.0). Also fix compiler error bug in sdbus-c++ code when compiled against older libsystemd versions. Plus some extra related cleanups.
2024-09-15 14:13:49 +02:00
b7a038f11f refactor: make createPlainMessage public (#448)
Library clients may need to manually create plain sdbus-c++ message to be able to unit test their code.
2024-08-18 10:09:53 +02:00
c6705faf2f fix: const enum signature_of ambiguity error (#444)
This fixes the template specialization ambiguity error when a signature_of applied upon a const (and/or volatile) enum type would render the const specialization and the enum specialization as two equally-ranked candidates.
2024-05-15 15:46:24 +02:00
e6b87b106c docs: fix examples in using-sdbus-c++.md (#441)
Update in using documentation to version v2.0 syntax making examples work out of the box
2024-05-13 23:51:59 +02:00
50cc636058 fix: make REGISTER_STRUCT functions inline
This makes that you can use these macros in headers as well as
source files.
Else you will get a warning about multiple definitions of the same
function.
2024-05-13 23:47:53 +02:00
3d3aa26255 chore: update gtest default version to 1.14.0
this prevents compiler errors on newer compilers
2024-05-13 23:47:53 +02:00
14942db075 feat: add macro to teach sdbus-c++ about user-defined structs (#440)
This introduces SDBUSCPP_REGISTER_STRUCT macro that helps clients conveniently, in one line, teach sdbus-c++ to recognize and accept their custom C++ struct types wherever D-Bus structs are expected in a D-Bus API.

The macro saves some boilerplate that would otherwise be needed on client side for every registered struct.
2024-05-06 20:14:32 +02:00
e62472b210 chore: fix partially renamed BUILD_DOXYGEN_DOC CMake option 2024-04-25 01:23:15 +02:00
b7d85f936d chore: release version v2.0.0 2024-04-24 21:00:02 +02:00
798eaf8626 refactor: remove floating_slot_t tag and use return_slot_t instead (#439)
Make all the API consistent by using return_slot_t-based overloads for returning the slots to clients, and overloads without that tag for "floating" slots. floating_slot_t tag was previously added for API backwards compatibility reasons, but now is a (counter-)duplicate to the return_slot_t.
2024-04-24 20:20:29 +02:00
2bc9d3ebb3 refactor: object manager API (#438)
This makes the signature of addObjectManager() function overloads consistent with common API design of the library.
2024-04-24 20:20:29 +02:00
26c6da11be refactor: reorganize public API of main abstract classes (#437) 2024-04-24 20:20:29 +02:00
0c75ad2b71 docs: fix generated class examples in the tutorial (#436) 2024-04-24 20:20:29 +02:00
84a6fbbf86 fix: disable move in generated adaptor and proxy classes (#435)
Moving adaptor or proxy instances changes their `this` pointer. But `this` is captured by value in closures used by those instances, and this remains unchanged on move, leading to accessing an invalid instance when a lambda expression executes. Supporting move semantics would require unsubscribing/unregistering vtable, handlers, etc. and re-subscribing and re-registering all that, which is too complicated and may have side effects. Hence it has been decided that these classes are not moveable. One may use an indirection with e.g. `std::unique_ptr` to get move semantics.
2024-04-24 20:20:29 +02:00
83ece48ab0 feat: add Slot-returning overloads of async method calls (#433) 2024-04-24 20:20:29 +02:00
310161207a refactor: improve and make more efficient some Message API (#432) 2024-04-24 20:20:29 +02:00
ef552ec089 chore: update years in header comments 2024-04-24 20:20:29 +02:00
7894cda577 perf: provide also const char* overloads for convenience functions 2024-04-24 20:20:29 +02:00
d41a176c2a refactor: use string_view in Property convenience classes 2024-04-24 20:20:29 +02:00
f15c260750 feat: add support for string_view as D-Bus type 2024-04-24 20:20:29 +02:00
d856969051 refactor: enable compile-time D-Bus signature generation (#425)
Since sdbus-c++ knows types of D-Bus call/signal/property input/output parameters at compile time, why not assemble the D-Bus signature strings at compile time, too, instead of assembling them at run time as std::string with likely cost of (unnecessary) heap allocations...

std::array is well supported constexpr type in C++20, so we harness it to build the D-Bus signature string and store it into the binary at compile time.
2024-04-24 20:20:29 +02:00
32a97f214f fix: make Variant conversion operator explicit (#428)
Having explicit conversion operator is a good practice according to the C++ core guidelines, as it makes the code safer and better follows the principle of least astonishment. Also, it is consistent with the standard library style, where wrappers like std::variant, std::any, std::optional... also do not provide an implicit conversion to the underlying type. Last but not least, it paves the way for the upcoming std::variant <-> sdbus::Variant implicit conversions without surprising behavior in some edge cases.
2024-04-24 20:20:29 +02:00
42f0bd07c0 refactor: add strong types to public API (#414)
This introduces strong types for `std::string`-based D-Bus types. This facilitates safer, less error-prone and more expressive API.

What previously was `auto proxy = createProxy("org.sdbuscpp.concatenator", "/org/sdbuscpp/concatenator");` is now written like `auto proxy = createProxy(ServiceName{"org.sdbuscpp.concatenator"}, ObjectPath{"/org/sdbuscpp/concatenator"});`.

These types are:
  * `ObjectPath` type for the object path (the type has been around already but now is also used consistently in sdbus-c++ API for object path strings),
  * `InterfaceName` type for D-Bus interface names,
  * `BusName` (and its aliases `ServiceName` and `ConnectionName`) type for bus/service/connection names,
  * `MemberName` (and its aliases `MethodName`, `SignalName` and `PropertyName`) type for D-Bus method, signal and property names,
  * `Signature` type for the D-Bus signature (the type has been around already but now is also used consistently in sdbus-c++ API for signature strings),
  * `Error::Name` type for D-Bus error names.
2024-04-24 20:20:29 +02:00
fe21ee9656 fix(tests): fix intermittently failing integration tests (#412) 2024-04-24 20:20:29 +02:00
0dc0c87cc9 chore: use C++20 standard (#410)
As of now sdbus-c++ supports C++20 but does not require it, and the used C++20 features are conditionally compiled depending on whether they are available or not.
2024-04-24 20:20:29 +02:00
29e94c3b68 fix(cmake): rename forgotten CMake variables 2024-04-24 20:20:29 +02:00
c433d26176 chore(cmake): implement more flexible searching for sd-bus libs (#409)
A new CMake configuration variable SDBUSCPP_SDBUS_LIB has been introduced that enables clients to specify which sd-bus implementation library shall be used by sdbus-c++.
2024-04-24 20:20:29 +02:00
abc4d755f7 fix: conditional use and tests of span (#411) 2024-04-24 20:20:29 +02:00
4c35e98668 fix: pass correctly underlying lib to intg tests 2024-04-24 20:20:29 +02:00
87500ad9ad docs: add v2 migration notes to the tutorial (#407) 2024-04-24 20:20:29 +02:00
9a5616a87a refactor: rename connection creation methods (#406)
This PR makes things around connection factories a little more consistent and more intuitive:

    * createConnection() has been removed. One shall call more expressive createSystemConnection() instead to get a connection to the system bus.
    * createDefaultBusConnection() has been renamed to createBusConnection(), so as not to be confused with libsystemd's default_bus, which is a different thing (a reusable thread-local bus).

Proxies still by default call createBusConnection() to get a connection when the connection is not provided explicitly by the caller, but now createBusConnection() does a different thing, so now the proxies connect to either session bus or system bus depending on the context (as opposed to always to system bus like before).

The integration tests were modified to use createBusConnection().
2024-04-24 20:20:29 +02:00
4a90f80933 chore: update ChangeLog for v2.0.0 (#405) 2024-04-24 20:20:29 +02:00
d177d2e365 chore: reorder some API functions (#404)
In sdbus-c++ v1, new virtual functions (e.g. overloads of existing virtual functions) were always placed at the end of the class to keep backwards ABI compatibility. Now, with v2, these functions are reordered and functions forming a logical group are together.
2024-04-24 20:20:29 +02:00
c205178fc0 chore: rename COMPONENTs in CMake (#402) 2024-04-24 20:20:29 +02:00
280fcfb067 refactor: remove deprecated API stuff (#403) 2024-04-24 20:20:29 +02:00
0074c79e7f refactor: rename and re-organize CMake options (#401)
This improves usability of sdbus-c++ in downstream CMake projects. CMake options now have `SDBUSCPP_` prefix so potential conflicts with downstream options/variables are minimized. Also, all configurable options and variables are placed in a single place in the root CMake file.
2024-04-24 20:20:29 +02:00
3de2812a60 refactor: use optional for passing potential call errors (#396)
This switches from a raw pointer to std::optional type to pass prospective call errors to the client (using std::optional was not possible years back when sdbus-c++ was based on C++14). This makes the API a little clearer, safer, idiomatically more expressive, and removes potential confusion associated with raw pointers (like ownership, lifetime questions, etc.).
2024-04-24 20:20:29 +02:00
914d133d10 refactor: add nodiscard attribute for more functions 2024-04-24 20:20:29 +02:00
4721060d76 refactor: use correct header include order 2024-04-24 20:20:29 +02:00
1c72f1ce52 refactor: add Async suffix to async callMethod functions (#394)
This makes naming consistent, so all asynchronously working functions have Async suffix
2024-04-24 20:20:29 +02:00
b65461c404 refactor: add nodiscard attribute for some functions 2024-04-24 20:20:29 +02:00
24776f2047 chore: remove legacy comments 2024-04-24 20:20:29 +02:00
f1b9226491 refactor: remove deprecated dont_request_slot_t tag 2024-04-24 20:20:29 +02:00
dc0c2562b8 refactor: rename request_slot tag to return_slot 2024-04-24 20:20:29 +02:00
84932a6985 refactor: improve Proxy signal subscription (#389)
This makes D-Bus proxy signal registration more flexible, more dynamic, and less error-prone since no `finishRegistration()` call is needed. A proxy can register to a signal at any time during its lifetime, and can unregister freely by simply destroying the associated slot.
2024-04-24 20:20:29 +02:00
bdf313bc60 refactor: improve Object vtable registration (#388)
This improves the D-Bus object API registration/unregistration by making it more flexible, more dynamic, closer to sd-bus API design but still on high abstraction level, and -- most importantly -- less error-prone since no `finishRegistration()` call is needed anymore.
2024-04-24 20:20:29 +02:00
e76d38c317 fix: add missing header to ScopeGuard.h 2024-04-24 20:20:29 +02:00
c92f5722c7 refactor: use sd_bus_match_signal() for signal registration (#372)
sd_bus_match_signal() is more convenient than more general sd_bus_add_match() for signal registration, and requires less code on our side. Plus, systemd of at least v238 is required for sdbus-c++ v2, and this version already ships with sd_bus_match_signal().
2024-04-24 20:20:29 +02:00
20a13eeafb refactor: make Variant constructor explicit (#370)
This makes the library more robust and prone to user's errors when the user writes an extension for their custom type. In case they forget to implement a serialization function for that type and yet insert an object of that type into sdbus::Message, the current behavior is that, surprisingly, the library masks the error as it resolves the call to the Variant overload, because Variant provides an implicit template converting constructor, so the library tries to construct first the Variant object from the object of custom type, and then inserting into the message that Variant object. Variant constructor serializes the underlying object into its internal message object, which resolves to the same message insertion overload, creating an infinite recursion and ultimately the stack overflow. This is undesired and plain wrong. Marking this Variant converting constructor solves these problems, plus in overall it makes the code a little safer and more verbose. With explicit Variant constructor, when the user forgets to implement a serialization function for their type, the call of such function will fail with an expressive compilation error, and will produce no undesired, surprising results.
2024-04-24 20:20:29 +02:00
4fd09d4e81 fix(tests): enable skipped test for Clang and FreeBSD (#369)
The test now works with Clang, libc++ and -O2 optimization, since the underlying implementation has been completely re-designed and doesn't suffer from that problem anymore.
2024-04-24 20:20:29 +02:00
35fd3ff5ce refactor: simplify async call state flag (#368)
Since synchronous D-Bus calls are simplified in v2.0 and no more implemented in terms of an async call, the issue reported in #362 is no more relevant, and we can return to the simple boolean flag for indicating a finished call.
2024-04-24 20:20:29 +02:00
6f35f00fcf refactor: let callbacks take message objects by value (#367)
Signatures of callbacks async_reply_handler, signal_handler, message_handler and property_set_callback were modified to take input message objects by value, as opposed to non-const ref.

The callee assumes ownership of the message. This API is more idiomatic, more expressive, cleaner and safer. Move semantics is used to pass messages to the callback handlers. In some cases, this also improves performance.
2024-04-24 20:20:29 +02:00
9412940d1e refactor: use sd-bus API to get current message 2024-04-24 20:20:29 +02:00
c6afa26541 fix: update cmake version for library to 2.0.0
This will also install the library as `libsdbus-c++.so.2.0.0`, which
represents the actual version of this library
2024-04-24 20:20:29 +02:00
b4d036c503 fix: prevent installing a time event, when there's no need for a timeout
See #324 for discussion
2024-04-24 20:20:29 +02:00
0bfda9ab92 chore: require cmake v3.14 and use FetchContent_MakeAvailable 2024-04-24 20:20:29 +02:00
64337e545d test: support new googletest git tags 2024-04-24 20:20:29 +02:00
788168eded ci: build googletest manually on older ubuntus 2024-04-24 20:20:29 +02:00
b9aa770f58 feat: introduce sd-event integration 2024-04-24 20:20:29 +02:00
2efb6909b5 chore: enable actions on release/v2.0 branch 2024-04-24 20:20:29 +02:00
3e20fc639e refactor: simplify async D-Bus connection handling 2024-04-24 20:20:29 +02:00
7450515d0b chore: release version v1.6.0 2024-04-24 20:04:34 +02:00
c769637f3b chore: update years in header comments 2024-04-16 22:48:34 +02:00
334fcb8833 feat: add std::variant constructor and conversion operator to sdbus::Variant (#429)
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2024-04-04 20:16:27 +02:00
b9088cc801 test: add integration tests for std::variant (#427)
This also introduces `always_false` technique instead of `sizeof` trick for unsupported D-Bus type representation static assert. This one is more expressive and leads to more specific, more revealing compiler error messages.
2024-04-02 16:44:45 +02:00
a73eb9b8c1 feat: add support for std::variant as D-Bus variant representation (#415)
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2024-04-01 13:23:08 +02:00
700a011b80 fix: add missing #include <exception> (#421) 2024-03-25 20:16:23 +01:00
8a9cfc1f19 feat: support enums in D-Bus serialization and signatures (#416) 2024-03-07 17:49:05 +01:00
30d9f1d462 chore: release v1.5.0 2024-02-26 08:29:35 +01:00
28921ad424 fix: request name signal handling issue (#392)
In case a signal arrives during the `RequestName` or `ReleaseName` D-Bus call (which is a synchronous call), the signal may not be processed immediately, which is a bug. This is solved now by waking up the event loop.
2023-12-30 17:37:00 +01:00
721f583db1 refactor: handle exceptions correctly also in match callback handlers (#386)
This is a follow-up to #375. It covers match callback handlers that were missed in #375.
2023-12-05 19:06:41 +01:00
47a84ab889 feat: implement Overload pattern in utils (#385) 2023-12-05 18:16:59 +01:00
d80483cdc0 feat: extend ScopeGuard with success and failure overloads (#384) 2023-12-05 18:16:45 +01:00
934d51fa8a chore: add INSTALL_TESTS CMake option (#382)
* Change default test installation path to tests/sdbus-c++ , while also respecting CMAKE_INSTALL_PREFIX
* Introduce INSTALL_TESTS CMake option, so BUILD_TESTS is now split into BUILD_TESTS just for building and INSTALL_TESTS for installing the test binaries

Per discussion in #358 (comment), the change in the default settings is fine a for a minor release.
2023-11-23 21:05:44 +01:00
fb9e4ae371 fix: correctly add libsystemd dependency to pkgconfig (#378)
* fix: correctly add libsystemd dependency to pkgconfig

* refactor: solve sd-bus dependencies uniformly

---------

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-11-20 14:41:59 +01:00
6e348f3910 refactor: minor UnixFd cleanups (#376)
* chore: Use std::exchange in UnixFd

This was suggested in code review for #376 .

* fix: Protect against UnixFd self-assignment

While self-assignment is rare, it is expected to be safe.  Add a check
to prevent putting the object in an invalid state.

* fix: Improve hygiene around dup system call

- Don't try to call dup on a negative value.
- Check dup return code and throw if it fails, rather than returning an
  empty UnixFd object.

* chore: Move UnixFd::close to Types.cpp

Minor convenience for applications: unistd.h doesn't have to be included
in the public header.

---------

Co-authored-by: David Reiss <dreiss@meta.com>
2023-11-18 18:11:00 +01:00
f50e4676fe fix: add missing algorithm header include (#380)
* https://gcc.gnu.org/gcc-14/porting_to.html

Using gcc 14 uncovers a missing include in Message.h

Signed-off-by: Alfred Wingate <parona@protonmail.com>
2023-11-16 22:26:42 +01:00
1aa30e3a20 chore(ci): run freebsd job on ubuntu 2023-11-10 16:01:33 +01:00
e2b3e98374 refactor: improve handling of exceptions from callback handlers (#375)
* Catch and process all exceptions (not just sdbus::Error) from callback handlers
* Unify handling of exceptions from all types of callbacks -- always set sd_bus_error and return a negative result number in case of exception

Although libsystemd logs (with DEBUG severity) all errors from such callback handlers (except method callback handler), it seems to be out of our control. One of handy sdbus-c++ features could be the ability for clients to install a log callback, which sdbus-c++ would call in case of exceptions flying from callback handlers. In case something doesn't work for clients (especially novices), they can first look into these logs.

This may be handy in common situations like ignored signals on client side because of the inadvertent mismatch between real signal signature and signal handler signature. Like here: #373. (Although in this specific case of signals, there is a solution with an additional const sdbus::Error* argument that would reveal such an error.)
2023-11-03 18:03:01 +01:00
9490b3351f feat: add support for async registration of matches (#374) 2023-11-03 17:57:52 +01:00
9da18aec25 chore: remove obsolete TODO comments 2023-10-31 15:54:49 +01:00
b7b454ba38 doc: add section on bus connection factories 2023-10-16 19:28:27 +02:00
f420b216aa docs: update documentation 2023-10-16 15:30:23 +02:00
b482cd6d08 chore: version 1.4.0 2023-10-10 19:26:21 +02:00
aac7e590ea docs: add recommendation on destroying direct D-Bus connections 2023-10-10 19:15:21 +02:00
0ad2553417 fix: use-after-return in synchronous calls (#362)
* fix: Use-after-return in synchronous calls

This bug was introduced by c39bc637b8 and can be reproduced by
configuring with

  cmake -S . -B build -DBUILD_TESTS=yes -DCMAKE_CXX_COMPILER=clang++ \
    -DCMAKE_CXX_FLAGS="-Wno-error=deprecated-copy -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope"

and running `cmake --buid build && cmake --build build -t test` or
`build/tests/sdbus-c++-integration-tests --gtest_filter=SdbusTestObject.HandlesCorrectlyABulkOfParallelServerSideAsyncMethods`

The issue is that `sdbus_async_reply_handler` can call `removeCall`, which
writes to `data->finished`, but `data` can point to the stack of
`sendMethodCallMessageAndWaitForReply`, which can return as soon as
`asyncCallData->callback` is called.

As a fix, I restored some of the logic removed in c39bc637b8.
Specifically, in `sdbus_async_reply_handler`, I make a copy of some data
from `asyncCallData` (a new `state` field instead of `slot`), and in the
`SCOPE_GUARD`, I don't call `removeCall` if the call was actually
synchronous.

* refactor: use enum class instead of int

---------

Co-authored-by: David Reiss <dreiss@meta.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-10-09 20:01:45 +02:00
621b3d0862 feat: enable creation of IConnection from sd_bus object (#363) 2023-10-09 15:46:39 +02:00
189fd23744 fix(tests): fix ETIMEDOUT message on FreeBSD (#365)
[ RUN      ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut
tests/integrationtests/DBusMethodsTests.cpp:181: Failure
Value of: e.getMessage()
Expected: (is equal to "Connection timed out") or (is equal to "Method call timed out")
  Actual: "Operation timed out"

[  FAILED  ] SdbusTestObject/0.ThrowsTimeoutErrorWhenMethodTimesOut, where TypeParam = sdbus::test::SdBusCppLoop (3 ms)
2023-10-04 15:48:51 +02:00
cfb71bd6cf feat: add support for direct connections (#350)
* feat: add support for direct connections

* refactor: simplify a bit, change comments, extend tests

* fix: compiler warning about unused variable

* docs: add section on direct connections to the tutorial

---------

Co-authored-by: Maksim Fedyarov <m.fedyarov@omp.ru>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-09-25 20:12:34 +02:00
c437b4d508 fix: honor CMAKE_POSITION_INDEPENDENT_CODE when building (#361) 2023-09-25 08:53:24 +02:00
1e2d13a04a feat: add FreeBSD support (#358)
* chore: don't use systemd headers with elogind

In file included from src/VTableUtils.c:27:
src/VTableUtils.h:30:10: fatal error: 'systemd/sd-bus.h' file not found
 #include <systemd/sd-bus.h>
          ^~~~~~~~~~~~~~~~~~

* chore: add basu support

Similar to elogind but also supported on non-Linux.

* chore(tests): permit /var/lib/machine-id on non-systemd

https://github.com/elogind/elogind/commit/84fdc0fc61c1
https://git.sr.ht/~emersion/basu/commit/8324e6729231

* chore(ci): add simple freebsd job

Mainly to cover libc++ and basu.

* chore(ci): explicitly pass CMAKE_INSTALL_PREFIX

Some sdbus-cpp tests require configuring system bus. However, Linux
testing relies on writing outside of prefix in order to affect current
system bus instance instead of launching a dedicated one.

* chore(tests): respect CMAKE_INSTALL_PREFIX for system bus config

DBus isn't part of base system on BSDs, so may not use /etc for configs.
Also, testing installation failed as non-root:

$ cmake -DBUILD_TESTS=1 -DCMAKE_INSTALL_PREFIX=/tmp/sdbus-cpp_prefix -DTESTS_INSTALL_PATH=/tmp/sdbus-cpp_prefix/tests
$ cmake --build .
$ cmake --install .
[...]
CMake Error at tests/cmake_install.cmake:105 (file):
  file cannot create directory: /etc/dbus-1/system.d.  Maybe need
  administrative privileges.

* chore(tests): temporarily skip 1 test on FreeBSD to keep CI happy

* chore(ci): run tests in freebsd job
2023-09-18 11:35:23 +02:00
290078d6af feat: add support for async property get/set on client-side (#354)
* feat: add async property get/set convenience support classes

* feat: add no-reply and async overloads to Properties_proxy

* feat: add convenience functions for GetAll functionality

* test: add tests for new functionality

* add codegen IDL support and documentation
2023-09-14 10:54:57 +02:00
0eda855745 chore: version 1.3.0 2023-08-20 11:45:44 +02:00
2a992ca84d fix: remove explicit from Variant ctor to avoid potential breaking client code 2023-08-20 11:17:10 +02:00
3717e63c64 feat: support std::future-based async methods in codegen tool (#353) 2023-08-19 20:57:32 +02:00
8113bf88ad chore(cmake): add support for libelogind (#352)
* CMakeLists.txt: Fallback to elogind when libsystemd could not be
found.  Set LIBSYSTEMD variable.
* pkgconfig/sdbus-c++.pc.in (Description): Parameterize with above
LIBSYSTEMD variable.

Co-authored-by: Sven Eden <sven.eden@prydeworx.com>
2023-08-18 12:08:37 +02:00
3e84b254e9 refactor: improve type extensibility and its safety (#348) 2023-08-09 12:39:16 +02:00
8728653359 test: add tests for type extensibility (#347) 2023-08-09 12:14:33 +02:00
6620a447d1 docs: add tutorial on extending sdbus-c++ types (#346) 2023-08-09 12:13:47 +02:00
24a3d83c3f chore: fix file permissions 2023-08-04 13:30:34 +02:00
dcd9d46b9c style: remove trailing whitespace (#345)
* style: restore correct 644 file permission

* style: remove trailing whitespace
2023-08-04 13:26:45 +02:00
605fbe48c0 fix: moving instead of copying std::string argument 2023-08-03 14:25:33 +02:00
fb61420bf0 feat: support serialization of array, span and unordered_map (#342)
* feat: support serialization of array, span and unordered_map

* fix some spelling mistakes

* docs: update table of valid c++ types

---------

Co-authored-by: Marcel Hellwig <github@cookiesoft.de>
2023-08-03 13:55:37 +02:00
0a2bda9c67 feat: make Struct tuple-like class (#343) 2023-08-03 13:00:01 +02:00
f6e597a583 fix: add cast to void to silence compiler warning in case of empty parameter list 2023-08-03 12:59:48 +02:00
29c877a89a perf: optimize serialization of arrays of trivial D-Bus types (#340)
* Improve performance of std::vector<> per Issue #339.

* refactor: improve performance of vector serialization

---------

Co-authored-by: Plinio Andrade <plinio.andrade@oracle.com>
Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2023-07-27 18:31:49 +02:00
98f4929337 fix: pseudo-connection static lifetime issue 2023-07-26 08:57:21 +02:00
8d0d9b0d40 chore(ci): remove ubuntu-18.04 container image 2023-07-26 08:46:00 +02:00
c39bc637b8 fix: race condition in async Proxy::callMethod
Signed-off-by: Anthony Brandon <anthony@amarulasolutions.com>
2023-02-07 22:23:35 +01:00
737f04abc7 feat: add support for std::future-based async calls 2023-02-07 12:31:31 +01:00
3a56113422 fix: fix namespace name in ScopeGuard 2023-01-21 01:34:34 +01:00
f332f46087 fix: use correct runtime component in CMake file 2023-01-17 09:58:21 +01:00
c9e157e3e1 fix: flush long messages after sending 2023-01-05 15:12:39 +01:00
8ca3fdd5ce chore: update issue templates 2023-01-04 22:05:56 +01:00
55c306ce05 docs: strip absolute paths from doxygen documentation 2023-01-04 21:51:28 +01:00
6c5e72326c refactor(ci): use newer github actions and ubuntu v22 image 2023-01-04 21:51:13 +01:00
8bbeeeb4ce fix: integration tests in release mode 2023-01-03 16:39:54 +01:00
7a09e9bcc8 chore: install namelink to dev component 2023-01-03 15:24:17 +01:00
c812d03bc7 fix: integration tests for libsystemd v251 2023-01-03 15:20:30 +01:00
e7d4e07926 fix: broken D-Bus specs link in README (#297)
- D-Bus specification broken url link is corrected

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>

Signed-off-by: Bhavith C <bhavithc.acharya@gmail.com>
2023-01-02 15:31:08 +01:00
031f4687ca fix: compilation warnings 2022-09-21 15:37:57 +02:00
aeae79003a refactor: support move semantics in generated adaptor and proxy classes 2022-09-20 17:05:59 +02:00
74d849d933 feat: add support for proxy with no event loop thread 2022-09-05 17:25:37 +02:00
f336811fc7 docs: add section on D-Bus types to C++ types mapping (#285)
* Added D-Bus type C++ type table

* docs: do corrections and rewordings in the proposed chapter

Co-authored-by: Stanislav Angelovič <stanislav.angelovic@protonmail.com>
2022-09-05 13:51:05 +02:00
35bffd0f25 build: don't include libsystemd as shared pkg-config dep when statically linked (#283)
This works whether `BUILD_LIBSYSTEMD` is `ON` or the separate build of
libsystemd picked up by pkg-config is static. The Gentoo Linux package
requires the latter.

This change also handles `BUILD_SHARED_LIBS` being `OFF`. In this case,
the libsystemd dependency does need to be included, even though it is
static.

CMake does not allow you to build shared and static libraries
simultaneously (with a single `add_library` call), but this change
declares the libsystemd dependency as `Requires.private` rather than
omitting it entirely. This is in case someone builds and installs both a
static and shared build and then calls `pkg-config --static sdbus-c++`.
The static build needs to be installed first so that the pkg-config
file from the shared build takes precedence.
2022-09-03 22:54:06 +02:00
e33a890ce7 docs: fix send_destination property (#284) 2022-08-26 16:49:51 +02:00
751c1addc4 chore: version 1.2.0 2022-08-09 09:50:33 +02:00
2991fa4960 refactor: use pseudo D-Bus connection for plain messages 2022-08-09 08:55:29 +02:00
0f2362d8c3 feat: add support for session bus connection at custom address (#273)
* Add methods to initiate custom session bus connection

The new function helper `createSessionBusConnectionWithAddress` allows to create connection to session bus with custom address.

Signed-off-by: Alexander Livenets <a.livenets@gmail.com>

* feat: add support for session bus connection at custom address

Co-authored-by: Stanislav Angelovic <stanislav.angelovic@siemens.com>
2022-08-08 13:54:09 +02:00
e07c1f3981 chore: update doxygen header info 2022-07-05 18:10:05 +02:00
58426966f4 feat: add CMake variable for extra libsystemd config options 2022-06-27 15:01:06 +02:00
4e105081c9 fix: remove executable flag from source files 2022-06-27 12:17:18 +02:00
5ec6027d5f feat: add support for match rules 2022-06-27 12:14:57 +02:00
d864e1dfa4 refactor: rename dont_request_slot tag to floating_slot 2022-06-27 12:05:06 +02:00
b7f3d7c876 refactor: little fixes and reorganizations around Connection class 2022-06-22 18:12:42 +02:00
2a4c241303 docs: add more info on D-Bus security policy file 2022-06-22 18:12:13 +02:00
5e933c3f17 Detects missing type after array declaration. Fixes #253. (#255)
Co-authored-by: Michael Binz <michael.binz@daimler.com>
2022-06-19 23:02:54 +02:00
b5aee6d019 docs: add information about Buildroot package (#252) 2022-06-07 08:45:34 +02:00
5caea3b72b fix: invalid assert on event fd (#243) 2022-02-13 18:48:50 +01:00
b7a9c63ff0 refactor: add validity checks for names and paths (#242) 2022-02-11 21:53:37 +01:00
7f437a6e06 fix(tests): printer for std::chrono in googletest v1.11.0 2022-02-09 11:43:12 +01:00
f492472e9f Enable move for ObjectPath and Signature.
* Since ObjectPath and Signature have a user-declared copy-ctor and copy
  assignment operator the implicit declaration of corresponding move
  operations is disabled. Explicitly add defaulted versions so that
  move operations actually move instead of copy.

* See: https://github.com/Kistler-Group/sdbus-cpp/issues/230
2022-01-11 19:02:37 +01:00
f673e57a47 Fix potential UB in creation of sdbus::Error.
See https://github.com/Kistler-Group/sdbus-cpp/issues/231
2022-01-11 19:01:22 +01:00
bca8e81037 build: version 1.1.0 2021-12-22 13:17:31 +01:00
33ff69ecd2 chore: remove unnecessary googletest CMake file 2021-12-22 13:10:30 +01:00
b8eb0e8ceb fix: minor fixes for async timeout handling 2021-12-21 13:52:14 +01:00
23fdd0ce8f fix: use non-mutating find in signal unregistration 2021-12-20 10:05:24 +01:00
bb0f3f0242 Fix #88: Timeout handling. (#91)
fix timeout handling

* Despite what is documented in sd_bus_get_timeout(3), the timeout
  returned is actually an absolute time point of Linux's CLOCK_MONOTONIC
  clock. Hence, we first have to subtract the current time from the
  timeout in order to get a relative time that can be passed to poll.

* For async call timeouts to reliably work, we need a way to notify the
  event loop of a connection that is currently blocked waiting in poll.
  I.e. assume the event loop thread entered poll with a timeout set to
  T1. Afterwards, the main thread starts an async call C with a timeout
  T2 < T1. In order for C to be canceled after its timeout T1 has
  elapsed, we have to be able to notify the event loop so that it can
  update its poll data.

Co-authored-by: Urs Ritzmann <ursritzmann@protonmail.ch>
Co-authored-by: Lukasz Marcul <lukasz.marcul@onemeter.com>
2021-12-20 10:00:29 +01:00
0b8f2d9752 fix: use correct path to README file for CPack 2021-12-15 12:46:05 +01:00
9b8a15339e test: delete forgotten file 2021-12-14 21:59:46 +01:00
ef4d9bcba2 chore: add note on libsystemd-dev when libsystemd pkgconfig file is not found 2021-12-14 21:35:11 +01:00
41d33117cc Fix #214: Add means to unregister signal handler 2021-12-14 16:48:50 +01:00
442670ec18 codegen: Support chrono literal timeout by ProxyGenerator
Allow to use human readable chrono literals to specify method call
timeout. The change is backward compatbile - if no unit is provided,
the fallback is "us".

Example:
<annotation name="org.freedesktop.DBus.Method.Timeout" value="500ms"/>
2021-12-14 16:47:01 +01:00
b01db13ff7 fix method name in example 2021-12-01 16:18:58 +01:00
dc0f487751 Reword a few tutorial statements slightly 2021-11-22 14:27:28 +01:00
55310659e8 googletest-download: replace master with main 2021-11-16 17:47:25 +01:00
65782bbf43 tools version 1.0.0 2021-10-25 09:02:37 +02:00
125cb1616c version 1.0.0 2021-10-25 09:02:37 +02:00
f025b92d76 ci: pack only the g++ build 2021-10-25 09:00:30 +02:00
33aa5768a5 ci: create and upload deb package 2021-10-25 09:00:30 +02:00
0703324015 ci: update make commands to cmake 2021-10-25 09:00:30 +02:00
f05f63cd48 cpack: remove test component
If -DBUILD_TESTS=ON, then creating debian packages fails
because dpkg-shlibdeps obiously can't find the sdbus-c++ dependency
for the tests package.

We could disable CPACK_DEBIAN_PACKAGE_SHLIBDEPS for the tests package.
Nevertheless I don't think we want a tests package at all.
2021-10-25 09:00:30 +02:00
de9cd46d8a update ChangeLog: v0.8.4 was never released 2021-10-18 14:16:30 +02:00
ca05b1541f fix non-virtual-dtor warning 2021-10-18 11:58:07 +02:00
35176c4988 integrationtests: differentiate BUS_NAME from INTERFACE_NAME
Even though they have the same value, they are something fundamentally different.
Therefore it is extremely confusing if the constant INTERFACE_NAME is passed
where actually a well-known BUS_NAME (destination) should go.
2021-10-18 11:58:07 +02:00
a5ecbbfcec add examples for the ObjectManager API 2021-10-18 11:58:07 +02:00
4e908612ed introduce new ObjectManager API on generated stubs layer
ATTENTION: Breaking Change!
2021-10-18 11:58:07 +02:00
1d930f324e update changelog: v0.9.0 2021-10-15 15:20:46 +02:00
a341754533 add dependency to libsytemd-dev to sdbus-c++-dev package 2021-09-23 20:18:14 +02:00
9cb8b89a01 add cpack to build debian packages, split the packages by components 2021-09-23 20:18:14 +02:00
c422de641a Support Error parameter in signal handlers 2021-09-17 19:25:39 +02:00
d77bb6b869 Fix potential race condition in Object destruction 2021-09-17 19:03:15 +02:00
8320429ef7 chore: add gperf dependency for libsystemd build 2021-09-17 18:31:04 +02:00
a72e17b932 doc: add gperf dependency for libsystemd 2021-09-17 18:28:41 +02:00
b4f5c0f46c Catch sdbus-c++ exceptions flying from Proxy callbacks to libsystemd 2021-07-29 12:37:15 +02:00
6433b38ed1 Add specific sections for tips and notes in the tutorial 2021-07-22 16:01:42 +02:00
a95fcf5693 Make resetting loop thread ID exception-safe 2021-07-22 13:48:43 +02:00
022831b8c3 Avoid propagating msg unpack exceptions to event loop
This change addresses conditions where an exception is thrown by the library upon receipt of a malformed message from an external
source, and propagated up to the event loop with no chance of
interception by the application. This issue is only experienced by
proxy convenience APIs, as low-level APIs allow the application to
unpack the message.

Strategy:
1. For malformed signals received by proxies: ignore the signal.
2. For malformed async method responses, translate the unpack
  exception into an sdbus::Error, and pass it to the caller as expected.
2021-07-20 18:39:36 +02:00
e16ffb1288 Provide access to D-Bus message in high-level API 2021-06-22 11:31:08 +02:00
75ea127374 connection: add createDefaultBusConnection()
This internally calls sd_bus_open(), which automatically selects the
system or session bus connection based on the presence and 
content of a DBUS_STARTER_BUS_TYPE environment variable and
whether the calling process has root privileges.

This option is very helpful when creating services and clients that will use the system bus in production, but connect to a session
for testing.

Additional changes:
* Removed assertions null-checking make_unique() return values.
  make_unique() calls new, and new is expected to throw or abort
  on failure, making the assertions unhelpful.
* Corrected a typo in the ClosesAndUnrefsBusWhenDestructed
  unit test for the system bus (tested the wrong function).
2021-06-21 15:55:23 +02:00
d74365c535 docs: Add information about conan recipe 2021-06-15 15:27:29 +02:00
a5e94f07bf Fix issue #135: Segfault in Message::peekType()
* Add missing nullptr check in Message::peekType().

* According to its specification, sd_bus_message_peek_type() sets a
  given contents parameter to NULL if the next element in a message is
  not a container. Since assigning a nullptr to a std::string has
  undefined behaviour (typically resulting in an invalid memory access),
  Message::peekType() must not assign contentsSig unconditionally.
2021-06-15 15:26:10 +02:00
fa9569fdd9 Issue 133 race in proxy destruct (#177)
* fix construct/destruct order of mutex/data in AsnycCalls

Destroying a mutex which might still be owned leads to undefined behaviour.
It wasn't the case here but using the right order is more resistant to future bugs.

* proxy AsnycCalls: don't leave map in unspecified state

* fix 133: use thread-safe container for proxy interfaces list

* proxy callback payload uses concrete signalData

* proxy: revert adding the wrapper class InterfaceContainer

It's no longer required and simplifies the locking logic.

* Proxy: avoid additional lambda wrapper around signal callbacks

As proposed in
https://github.com/Kistler-Group/sdbus-cpp/pull/177#issuecomment-834439707
option 3.

Still TODO: Avoid relying on std::map's non-invalidatable by adding just
std::unique_ptr<Proxy::InterfaceData::SignalData> to the container.

* proxy: add missing underscore prefix

* proxy: put InterfaceData::SignalData into a unique_ptr

This ways, we avoid relying on std::map's non-invalidatable references.

* proxy: code style: get raw pointer directly

* style: fix code style

Co-authored-by: Stanislav Angelovic <stanislav.angelovic@siemens.com>
2021-06-03 18:53:38 +02:00
118faa58f6 Add API to get message path and message destination (#167)
* Add API to get message path

* Add API to get message destination

* Handle NULL message fields interface and member

Functions sd_bus_message_get_interface() and sd_bus_message_get_member()
can return null in case the message does not use those fields or does
not have them set.
2021-05-07 15:24:21 +02:00
d65744b1fc Enable default construction of PendingAsyncCall (#180)
This is helpful in use cases where a user defined class wants to
store a PendingAsyncCall as a member variable, or in a STL
container.
2021-05-07 15:22:07 +02:00
6df67469ad proxy: add IProxy::getConnection() (#179)
This provides access to the proxy's bus connection so code using
the proxy does not need to store an external reference to it.

A matching function is already available in IObject.
2021-05-06 16:52:02 +02:00
b0a72cbe92 Make Message's setDestination() thread safe 2021-04-29 15:30:56 +00:00
5ee4c61a1b Add API to set signal destination (#168)
With this patch it is possible to create unicast signals.
2021-04-29 17:18:04 +02:00
d46cbba23c Add README and sdbus-c++ tutorial as additional pages in doxydocs (#153)
Co-authored-by: Craig Spannring <craig.spannring@milnxpd001.mti.local>
Co-authored-by: Stanislav Angelovič <angelovic.s@gmail.com>
2021-04-29 16:46:36 +02:00
70778bfae0 Add note on BUILD_SHARED_LIBS CMake flag to README 2021-04-29 16:07:47 +02:00
5f271abc0c Add note on system bus security policy file to the tutorial 2021-04-29 15:52:06 +02:00
bbffcbf49e fix: minor documentation and test improvements (#166)
* fix: minor documentation and test improvements

* doc: add link to tests in standard interfaces tutorial secion

* Update README.md

Co-authored-by: Urs Ritzmann <urs.ritzmann@kistler.com>

* Update docs/using-sdbus-c++.md

Co-authored-by: Urs Ritzmann <urs.ritzmann@kistler.com>

* Use cmake instead of make in build instructions

Co-authored-by: Stanislav Angelovic <stanislav.angelovic@siemens.com>
Co-authored-by: Urs Ritzmann <urs.ritzmann@kistler.com>
2021-04-28 12:05:14 +02:00
dc6d55a282 Provide CMake config and PkgConfig files for tools (#172) 2021-04-28 11:15:47 +02:00
3f54b5e762 comment sd_bus_match_signal() requires libsystemd v237 2021-04-13 15:59:35 +02:00
fe8cdce107 update changelog: v0.8.4 2021-04-13 15:59:35 +02:00
d47e9d1834 fix issue 145, signals are not filtered by sender 2021-04-13 15:59:35 +02:00
b9723850b8 add integrationtest for issue 145 2021-04-13 15:59:35 +02:00
e1008dd8cf reformat CMake linking 2021-04-13 11:02:20 +02:00
fc9f770512 Find and link against pthread
Building on SUSE fails, as std::thread usage requires linking
against pthread:

/usr/bin/c++ -fPIC -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -DNDEBUG -O3 -DNDEBUG -flto=auto -Wl,--as-needed -Wl,--no-undefined -Wl,-z,now -shared -Wl,-soname,libsdbus-c++.so.0 -o libsdbus-c++.so.0.8.3 CMakeFiles/sdbus-c++-objlib.dir/src/Connection.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Error.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Message.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Object.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Proxy.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Types.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/Flags.cpp.o CMakeFiles/sdbus-c++-objlib.dir/src/VTableUtils.c.o CMakeFiles/sdbus-c++-objlib.dir/src/SdBus.cpp.o  /usr/lib64/libsystemd.so
/usr/lib64/gcc/x86_64-suse-linux/10/../../../../x86_64-suse-linux/bin/ld: /tmp/libsdbus-c++.so.0.8.3.uWoUml.ltrans0.ltrans.o: in function `sdbus::internal::Connection::enterEventLoopAsync()':
<artificial>:(.text+0x2bb): undefined reference to `pthread_create'
/usr/lib64/gcc/x86_64-suse-linux/10/../../../../x86_64-suse-linux/bin/ld: /tmp/libsdbus-c++.so.0.8.3.uWoUml.ltrans0.ltrans.o: in function `non-virtual thunk to sdbus::internal::Connection::enterEventLoopAsync()':
<artificial>:(.text+0x37a): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
2021-04-13 10:56:43 +02:00
5e03e78451 feat: add API to get message credentials (#151)
* sdbus-cpp: Add API to get message credentials

Signed-off-by: Alexander Livenets <a.livenets@gmail.com>

* fix: add <sys/types.h> include for gid_t and other types

Co-authored-by: Stanislav Angelovič <angelovic.s@gmail.com>
2021-03-12 14:14:23 +01:00
3f74512f8e cmake: fix building with BUILD_LIBSYSTEMD and ninja generator (#138)
The ninja generator requires the BUILD_BYPRODUCTS specifier to work out the dependencies.
2021-03-12 14:07:10 +01:00
0090ca97ee fix: missing copy assignment operator (#120)
Explicitly list default copy assignment operator otherwise it is deleted in gcc 8.3.
2021-03-12 14:03:25 +01:00
a649a0225e style: fix indentation in CMakeLists.txt 2021-03-12 14:01:02 +01:00
cfb9956de6 Fixes for floating point systemd version such as 243.4 (#128)
```
~/package $ pkg-config --modversion libsystemd
243.4
~/package $
```
Split it to the list and use the first one
2021-03-12 13:26:21 +01:00
fb008445b1 xml2cpp: Add missing EXPAT include dirs (#136)
This patch is required if EXPAT library is installed in non-standard location.
Without 'target_include_directories' cmake will find EXPAT library:
  ...
  -- Found EXPAT: .../lib/libexpat.a (found version "2.2.10")
  ...

But 'xml.cpp' compilation will fail with error:
  ...
  tools/xml2cpp-codegen/xml.cpp:7:10: fatal error: expat.h: No such file or directory
    7 | #include <expat.h>
  ...
2021-03-12 13:17:55 +01:00
1e2a09cccf docs: note expat dependency (#131)
* docs: note expat dependency

* Add description for expat dependency

Co-authored-by: Stanislav Angelovič <angelovic.s@gmail.com>
2021-03-12 13:15:07 +01:00
a9f2043daa Fix examples in the tutorial
Remove sdbus:: namespace in injected-class-name of the template base class in c-tor initialization list
2021-03-10 09:49:25 +01:00
96b2dfff69 Update using-sdbus-c++.md
Fix name of class in destructor
2021-03-09 17:04:43 +01:00
d6fdacafbe Try to first find googletest in the system before downloading it (#125) 2020-11-16 17:05:36 +01:00
b190646aa5 Make a note in README about an exception for LGPL license 2020-10-11 14:27:14 +02:00
1c56f069dd Add license exception for template code in header files 2020-10-11 14:25:14 +02:00
6e8e5aadb6 Bump up to v0.8.3 2020-09-11 22:29:15 +02:00
3b735bf1aa Introduce CI workflow based on GitHub Actions (#114)
This introduces GitHub CI which runs builds and tests in a matrix configuration. It also changes the systemd repo to stable one. And puts the CI badge (plus a few more badges) to the README page.

Fixes #44
2020-07-22 16:35:42 +02:00
2f7b35c5a8 Fix unused variable warnings for release builds 2020-07-21 15:44:16 +02:00
d5867e1197 Fix unused variable warning for release builds 2020-07-21 15:32:31 +02:00
250aa2bbe3 Add additional messages to CMake build for optional parts 2020-07-21 12:12:14 +02:00
e63357b222 Remove non-virtual-dtor warnings by making classes final 2020-07-21 11:12:57 +02:00
2c6be0307f Update CMake configuration flag names 2020-07-19 18:23:19 +02:00
138a437b22 Fix #112: Address a few inconsistencies and make code more idiomatic 2020-07-18 20:47:05 +02:00
cc8d88cc64 Fix GetObjectPath() in integration tests 2020-07-18 20:30:51 +02:00
bded067496 Fix #43: Clean up integration tests 2020-07-18 20:21:47 +02:00
c1c4512f9f Fix build with gcc8.3
sdbus::ObjectPath and sdbus::Signature copy constructors are implicitly
declared as deleted when compiling with Linaro's ARM toolchain [1] gcc8.3.
Explicitly listing the copy constructors for affected classes fixes the problem.

[1] https://www.linaro.org/
2020-07-16 18:53:18 +02:00
175c43ec53 Bump revision up to v0.8.2 2020-06-17 15:29:46 +02:00
c137dfa213 Fix potential data race in Proxy's condition variable 2020-06-17 09:13:30 +02:00
a0dadcc6fe Fix integration tests after getObjectPath() introduction failed them 2020-06-16 17:25:01 +02:00
0d010440c5 Fix build with clang 9.0.1 and libcxx
This should not be required in C++17 because there is an appropriate
class template deduction rule [1] which infers that it's going to be a
weak_ptr<T> when constructing from a shared_ptr<T>. However, in
clang/LLVM's libcxx C++ STL implementation this only got implemented in
May 2020 [2].

[1] https://en.cppreference.com/w/cpp/memory/weak_ptr/deduction_guides
[2] https://reviews.llvm.org/D69603
2020-06-16 16:51:53 +02:00
ae8849e545 Implement #104: add getObjectPath() for classes (#105)
* Implement #104: add getObjectPath() for classes

* Implement #104: changes requested in review

Co-authored-by: Christian Schneider <cschneider@radiodata.biz>
2020-05-28 15:36:58 +02:00
fb35a9a196 Fix integration test cases failing in specific situations 2020-05-17 15:06:29 +02:00
6cbd49ddaa Install proper public sd-bus headers in internal libsystemd build 2020-05-17 15:05:24 +02:00
fb0a70a831 Fix #101: sanitize names of namespaces/methods/signals/properties/arguments (#102)
- add a list of c++ keywords and common defines
- sanitize names so that there are no functions/args with names that are reserved in c++

Co-authored-by: Christian Schneider <cschneider@radiodata.biz>
2020-05-16 22:57:37 +02:00
9af20af001 Fixed integration tests for libsystemd older than 242 2020-05-09 22:02:57 +02:00
63bbb07ef0 Fixup for 00d0837d98
* Add PendingAsyncCall return value to all relevant overloads of
  IProxy::callMethod().
2020-04-08 16:41:29 +02:00
00d0837d98 Introduce support for cancellable async calls 2020-04-04 16:30:56 +02:00
e91bedd4cb Fix #92: CallData race condition in Proxy::callMethod 2020-04-02 20:46:38 +02:00
dc66efbbcb Fix #93: Get signals working for multiple proxies.
* Proxy::sdbus_signal_handler() needs to return 0 instead of 1 in
  order to allow multiple proxies listening to a signal all being
  triggered.
* Add test for emitting a signal to multiple proxies on same
  connection.
2020-03-26 21:19:18 +01:00
a23aadbe5e Fix required CMake version to v3.13
Fixes #86
2020-02-14 14:31:14 +01:00
ae57c6760b sdbus-c++-xml2cpp: fixed file existence condition
fixes issue: https://github.com/Kistler-Group/sdbus-cpp/issues/83
2020-02-04 12:18:50 +01:00
21820f7529 Update using-sdbus-c++.md 2020-02-02 23:03:24 +01:00
3a4f343fb9 Provide better names to event loop-related IConnection methods 2020-02-02 22:22:26 +01:00
dee6adce02 Bump revision up to v0.8.1 2020-02-02 02:31:39 +01:00
3e68fee4cd Switch to more modern CMake 2020-02-02 02:29:32 +01:00
8dfd29b0f0 Fix clang-9 warnings 2020-02-01 23:26:27 +01:00
db71707be4 Update tutorial for for full use of C++17 2020-02-01 23:03:44 +01:00
c9583f2887 Update README for full usage of C++17 2020-02-01 23:01:09 +01:00
975f1bf07f Switch to fully supported C++17 2020-02-01 22:58:34 +01:00
d591b69f92 Bump revision up to 0.8.0 2020-02-01 13:56:13 +01:00
49586001d6 Try to better explain the design and how to use connections in objects and proxies 2020-02-01 13:38:51 +01:00
aa8e9123de Update the tutorial for new method parameter names feature 2020-02-01 12:38:39 +01:00
eade6a0e44 Add support for method and signal parameter names in introspection 2020-02-01 12:38:39 +01:00
10977c6137 Bump revision up to 0.7.8 2020-01-25 23:38:54 +01:00
1e455b8ef3 Fix inline variable since in v0.7 version line we still support gcc 6 2020-01-25 22:31:41 +01:00
75709e31f1 Fix sporadic race condition between Variant and underlying bus
The underlying bus was thread_local, but the design assumption that Variants built on top of that instance won't outlive the thread was incorrect. In stress tests, Variants were moved (and this is completely legal) to a different thread.
2020-01-25 22:31:41 +01:00
245db893b8 Remove obsolete section on Yocto recipes for sdbus-c++ 2020-01-23 16:58:48 +01:00
477c5dd714 Update section on Yocto recipes for sdbus-c++ 2020-01-23 16:57:42 +01:00
b25534013f Fix lock_guard variable declaration (don't yet use newer C++17 features) 2020-01-19 19:17:04 +01:00
68b5eac9e9 Bump revision up to 0.7.7 2020-01-19 18:59:36 +01:00
4310a3bd17 Little fix of order of destruction in stress tests 2020-01-19 18:57:14 +01:00
f41d9bc395 Fix issue of event loop thread and synchronous method call thread polling on the same D-Bus connection
Synchronous D-Bus method calls are now done in terms of blocking asynchronous calls.
2020-01-19 18:51:19 +01:00
5121d46eed Bump revision up to 0.7.6 2020-01-05 22:49:26 +01:00
121ed1a975 Fix clang-8 compilation errors and warnings 2020-01-05 12:52:37 +01:00
cc495811f9 Bump revision up to 0.7.5 2019-11-10 17:43:14 +01:00
839bc13625 Make Connection const-correct and fix integration tests build error 2019-11-10 17:40:33 +01:00
5fe0f503ca Add a method to retrieve the unique name of a connection 2019-11-10 17:34:57 +01:00
d50a15b2a2 Move C++17 uncaught_exceptions to public API 2019-11-10 17:31:58 +01:00
3a76e9c120 Bump revision up to 0.7.4 2019-11-03 20:40:13 +01:00
304b69dd8b Use tag dispatching to construct various types of Connection, refactor Connection unit tests 2019-11-03 20:30:52 +01:00
Jay
099bc857ad Add support for opening a connection to a remote system bus using ssh (#77) 2019-11-03 20:21:39 +01:00
c139110112 Add support for custom timeout value for D-Bus method calls (#72) 2019-11-03 13:54:13 +01:00
e7155c5506 Bump revision up to 0.7.3 2019-10-13 15:39:32 +02:00
0f7de608ac Little code cleanup 2019-10-11 16:04:37 +02:00
c6d4d2710f Skip GetMachineId test when /etc/machine-id is not available 2019-10-11 15:56:00 +02:00
0440dcb15b Added ability to integrate with foreign event loops 2019-10-08 22:09:05 +02:00
e30ce194ab Add getSenderName method to Message 2019-10-06 11:28:16 +02:00
8dea11bac6 Add note on solving potential getent-related Yocto errors 2019-07-09 18:29:58 +02:00
750dab3927 Bump revision up to 0.7.2 2019-07-08 10:01:11 +02:00
bf35157a4a Comment out unused parameter 2019-07-08 09:58:22 +02:00
a09362f79a Switch from plain UnixFd to owning UnixFd (#69) 2019-07-08 09:53:53 +02:00
c264f83e83 Fix Yocto chapter level in the tutorial 2019-06-12 15:03:26 +02:00
71adb5cf30 Add notes on sdbus-c++ Yocto recipes to the tutorial 2019-06-12 15:02:01 +02:00
00177a7e4c Bump revision up to 0.7.1 2019-06-12 09:18:55 +02:00
9826d28f51 Add missing Factory friend to Message subclasses 2019-06-11 20:29:45 +02:00
ab34b0ae50 Update header doxy comments in source files 2019-06-11 20:18:37 +02:00
ff944c9e95 Add protected non-virtual destructor in generated classes 2019-06-10 22:54:16 +02:00
7049d00a78 Remove unnecessary std::move of parameters (thanks to @ardazishvili)
Fixes part of #52
2019-06-10 22:03:02 +02:00
236c10ff56 Resolve a few clang-tidy suggestions and warnings (thanks to @ardazishvili)
Fixes part of #52.
2019-06-10 21:54:02 +02:00
dcad208ffe Redesign inheritance from Message (#62)
... so that the code is more idiomatic, clear and expressive about its intended use
2019-06-10 21:38:30 +02:00
57c840637c Add support for Unix fd D-Bus type 2019-06-10 21:19:56 +02:00
efe799ef3f Update section on standard D-Bus interfaces in the tutorial 2019-06-05 12:34:43 +02:00
5c0a8d5ab4 Add object manager automatically in ObjectManager_adaptor constructor 2019-06-05 12:18:04 +02:00
65b3e7ba00 Update README 2019-06-05 11:43:06 +02:00
b2b0bddf02 Fix Variant signal test in integration tests 2019-06-04 23:45:45 +02:00
11f0edf7b8 Fix fragile time-based waiting in integration tests 2019-06-04 22:48:54 +02:00
946cc8d0cd Fix indentation of doxygen comments 2019-06-04 22:21:49 +02:00
07625a435b Bump revision up to 0.7.0 2019-06-04 21:39:29 +02:00
fbb5242729 Add emit prefix to generated signal emitting methods 2019-06-04 21:30:09 +02:00
dbeaf87208 Remove obsolete section from the tutorial 2019-06-03 23:58:29 +02:00
45176c9eb7 Update the tutorial with standard D-Bus interfaces info 2019-06-03 23:57:14 +02:00
38b51bddc6 Add support for ObjectManager's InterfaceAdded/Removed signals on server side 2019-06-03 23:47:27 +02:00
01e2a7a570 Add support for PropertyChanged signal on server side 2019-06-03 22:02:15 +02:00
38552483ca Update ChangeLog 2019-05-29 22:41:59 +02:00
91fa35140b Add support for ObjectManager and other standard D-Bus interfaces (#55)
Fixes #50
2019-05-29 22:28:15 +02:00
4b0c23204d Add object manager support to IConnection interface (#54) 2019-05-27 20:51:42 +02:00
c13ee60b7e Have no update step in external googletest build
This enables (re)building sdbus-c++ with already built googletest without the need for Internet connection
2019-05-26 15:38:58 +02:00
6ee66dfc47 Update ChangeLog for incoming new release 2019-05-26 15:37:32 +02:00
ed5c7a1fd5 Update INSTALL 2019-05-26 15:36:14 +02:00
6629d31733 Update CMake usage example in tutorial 2019-05-23 22:04:51 +02:00
0014bb0b6e Go a step further towards modern CMake and cleaner project structure 2019-05-23 21:40:59 +02:00
ad3749f2c2 Add libmount as dependency for libsystemd 2019-05-20 23:28:28 +02:00
0045e8fcdc Do shallow clone of googletest 2019-05-20 23:08:20 +02:00
e12a9c3914 Move the license to the parent tools directory 2019-05-14 14:42:00 +02:00
19d852e1b9 Add licence to the codegen tool 2019-05-14 14:38:22 +02:00
8da3e312bc Make tools a sdbus-c++-tools project, not codegen specifically 2019-05-14 14:38:05 +02:00
81b5a67f35 Use shallow clone for libsystemd external project 2019-05-13 19:21:23 +02:00
b87b0c9dd9 Fix the way of handling thread-local system bus 2019-05-12 10:23:26 +02:00
76414ff09e Add pkgconfig to Dependencies section in README (#51) 2019-05-09 19:37:56 +02:00
4998895f41 Hide some internal googletest-specific CMake options 2019-05-09 19:17:32 +02:00
7763c66513 Add support for libsystemd versions up to the newest 242, and set 242 as default version 2019-05-09 19:16:16 +02:00
7c3f91310f Improve docs on steps needed to build libsystemd 2019-05-08 23:54:32 +02:00
2256adf707 Add section on automatically provided standard D-Bus interfaces 2019-05-08 23:44:22 +02:00
2c218ab3ba Minor update of tutorial -- building libsystemd as part of sdbus-c++ 2019-04-28 17:20:11 +02:00
0cffed4574 Add option to build libsystemd automatically as integral part of sdbus-c++ 2019-04-28 17:09:37 +02:00
36269897fd Remove unwanted sdbus-c++-internal includes from integration tests 2019-04-28 15:59:29 +02:00
1b1b9ae8ae Move code generator to tools subdirectory for higher consistency with OSS standards 2019-04-26 00:03:46 +02:00
1b02c604d8 Rename test subdirectory to tests for higher consistency with OSS standards 2019-04-25 23:54:51 +02:00
981206fa8c Rename doc subdirectory to docs for higher consistency with OSS standards 2019-04-25 23:48:11 +02:00
b0dfea041d Use googletest from master because 1.8.1 causes THREADS_PTHREAD_ARG issue when cross-compiling 2019-04-25 17:15:35 +02:00
824aaa711e Separate doxygen doc building from other docs 2019-04-25 13:34:33 +02:00
882262bc2f Switch back to support also CMake 3.5 2019-04-25 12:51:50 +02:00
4ede37d6a3 Install additional documentation files 2019-04-25 12:42:08 +02:00
58647c6e7d Update AUTHORS file 2019-04-25 12:41:12 +02:00
a23d88a628 README: Add minimum versions of needed compilers 2019-04-23 20:35:52 +02:00
29c438b3bb README: Add link on solving systemd dependency on tutorial 2019-04-23 20:34:01 +02:00
8b7b9197eb Add info on solving libssystemd dependency 2019-04-23 20:30:57 +02:00
2d27f99b32 Add note on dependency on systemd to README 2019-04-23 17:21:27 +02:00
9bde7c7b68 Add more intro description of what sdbus-c++ is into README 2019-04-23 17:12:49 +02:00
62a546c9d3 Bump revision up to 0.6.0 2019-04-15 21:05:54 +02:00
5b99658f36 Turn on compiler warnings 2019-04-14 20:56:47 +02:00
d3749741d1 Add preliminary ChangeLog 2019-04-13 21:38:22 +02:00
1d44d8b37f Add loops in stress tests to test adaptor/proxy initialization/deinitialization 2019-04-13 21:28:43 +02:00
e3a74a3ff2 Add unregister function to IObject and IProxy API 2019-04-13 21:17:37 +02:00
99160156fe Fix all gcc warnings 2019-04-12 22:39:14 +02:00
ee30375cfc Use official release of googletest (v1.8.1) rather than master 2019-04-12 21:42:33 +02:00
06ca6539f3 Make sure googletest is always built as a static lib and never installed as part of sdbus-c++ 2019-04-12 21:30:07 +02:00
ed0745c83a Only install doxy docs if they were generated 2019-04-12 20:27:54 +02:00
93b6e5237a Clean up some names (rename classes, methods, files)
* ConvenienceClasses.h/.inl/.cpp -> ConvenienceApiClasses.h/.inl/.cpp
  * IObjectProxy class -> IProxy
  * Interfaces class -> AdaptorInterfaces
  * Interfaces.h -> split into AdaptorInterfaces.h and ProxyInterfaces.h
  * createObjectProxy() method -> createProxy()
2019-04-09 21:28:07 +02:00
e7c78460cf Bump revision up to 0.5.3 2019-04-09 20:56:33 +02:00
f5da0dabcb Fix race condition between worker threads and adaptor destructor in stress tests 2019-04-08 21:16:32 +02:00
c9ef1849cd Add two missing headers in test/CMakeLists.txt 2019-04-07 21:16:47 +02:00
d154022205 Extend stress tests with dynamic object creation and destruction in multiple threads 2019-04-04 20:39:31 +02:00
94fd3c88d8 Add getConnection() method to IObject so we ask Object about its connection 2019-04-04 20:39:03 +02:00
a919058d13 Bump up revision to 0.5.2 2019-04-03 00:17:31 +02:00
08945acbc4 Simplify and unify callback design for both sync and async methods 2019-04-03 00:05:20 +02:00
5673a9bcf2 Update section on async D-Bus methods in sdbus-c++ tutorial 2019-03-30 10:10:55 +01:00
b46fb170ea Bump up revision to 0.5.1 2019-03-29 22:28:58 +01:00
878ce6fa5c Update doxygen documentation as well as tutorial 2019-03-29 22:23:25 +01:00
461ac241c8 Introduce doxygen documentation 2019-03-29 21:50:08 +01:00
a5692c08ea Rename sdbus-c++ stub code generator to more consistent sdbus-c++-xml2cpp 2019-03-28 19:15:48 +01:00
4fd2479b06 Don't build tests by default 2019-03-28 19:11:37 +01:00
581e849534 Update README: Add documentation of sdbus-c++ CMake flags 2019-03-28 19:10:30 +01:00
63637b639f Make CMakeLists.txt cleaner and more flexible 2019-03-28 18:47:49 +01:00
fc60700e1b Rename test executables for consistency 2019-03-28 18:25:31 +01:00
a04ab9f445 Bump revision up to 0.5.0, a big step in maturing sdbus-c++ 2019-03-28 16:08:58 +01:00
1c4abab3e4 Remove executable bit erroneously set on source files 2019-03-27 17:53:31 +01:00
d489eee9c0 Revise and update the tutorial for redesigned sdbus-c++ parts 2019-03-27 17:34:41 +01:00
cbf2218301 Remove unnecessary forward declarations from Message.h 2019-03-27 14:41:30 +01:00
6f79c5bf14 Add stress tests for sdbus-c++ 2019-03-26 08:59:50 +01:00
7c968e78cb Fix missing <algorithm> include for std::generate_n in performance tests 2019-03-25 20:30:37 +01:00
fd7be39dd4 Re-design sdbus-c++ approach to connections (#47)
Fixes #33 , among others
2019-03-25 16:28:31 +01:00
26c6ea8730 Fix gcc 6.3 issue in Connection unit test 2019-03-25 16:08:43 +01:00
663df31398 Introduce support for asynchronous D-Bus method calls on the client side (#42)
Fixes #32
2019-03-25 14:45:48 +01:00
004f158817 Bump up revision to 0.4.3 2019-03-24 22:18:48 +01:00
ab407aa8c8 Fix interface names and object paths in integration tests 2019-03-24 20:18:29 +01:00
bb2bf5811b Add SdBus interface to proper namespace 2019-03-20 18:52:05 +01:00
41a10d644f Make code a bit cleaner and more consistent 2019-03-19 20:11:18 +01:00
b9ce1ca3ce Remove unnecessary copy-construction when making SdBus 2019-03-18 21:28:17 +01:00
850e211dca Merge pull request #39 from ardazishvili/testing
Add separation layer from sd-bus to improve isolation in unit testing
2019-03-18 21:05:07 +01:00
2b83d7ca2d Mock sdbus lib, add unit tests of Connection class.
Introduce mock of sdbus library through extracting its interface. Set up unit tests of Connection class through injection of sdbusMock to constructor. Clients of Connections class should use fabrics instead.
2019-03-17 18:02:47 +03:00
8c76e3ef8b Bump up revision to 0.4.2 2019-03-15 12:09:55 +01:00
a6d0b62ff5 Add sdbus-c++ performance tests (#41)
* Introduce simple method call and signal-based manual performance tests

* Put perftests in proper place

* Remove unnecessary CMakeLists file
2019-03-15 11:34:25 +01:00
757cc44381 Merge pull request #40 from Kistler-Group/generate-config-version-file
cmake: generate config version file
2019-03-15 11:26:37 +01:00
6dbcbbfa98 cmake: generate config version file 2019-03-14 11:08:42 +01:00
b813680192 Allow ObjectPath and Signature be created from std::string 2019-02-19 08:57:24 +01:00
84b15776a3 Add systemd and dbus configuration, e.g. to run samples in 'Using sdbus-c++ library' 2019-02-04 21:22:49 +01:00
8c5c774727 Minor fix 2019-01-25 20:02:58 +01:00
cd1efd66a5 Add essential information to doxy comments of ProxyInterfaces constructors 2019-01-25 19:59:09 +01:00
fad81e7659 Be more clear on the different behavior of proxy factory overloads 2019-01-25 19:50:06 +01:00
1dafd6262c Add essential information to doxy comments of createObjectProxy 2019-01-25 19:24:20 +01:00
0b27f222c4 Fix stub generator C++ standard back to 14 2019-01-16 20:55:17 +01:00
58895d2730 Bump revision up to 0.4.1 2019-01-16 20:01:05 +01:00
d957948274 Transform constexpr member to a getter method because of different odr-usage rules in different compilers 2019-01-16 19:58:26 +01:00
47fad7dd63 Remove obsolete autotools stuff from the tutorial 2019-01-10 13:59:05 +01:00
7378cea833 Bump up project version 2019-01-10 13:54:02 +01:00
2526546432 Remove Eclipse project file 2019-01-10 13:52:35 +01:00
9c0e98c580 Introduce support for some common D-Bus annotations (#30)
* Add ability to declare property behavior on PropertyChanged signal

* Add support for Method.NoReply annotation (WIP)

* Add support for common annotations/flags
2019-01-10 08:47:59 +01:00
2c78e08d19 Remove warnings-related compiler options from CMakeLists (#31) 2019-01-06 22:16:13 +01:00
97372155a6 Update ChangeLog for v0.3.3 2018-12-29 01:02:36 +01:00
1def4e247a Add note on thread-safety of Variant and its const methods 2018-12-29 00:59:31 +01:00
d764afec93 Little fixes in tutorial code samples
Fixes #24
2018-12-24 15:43:01 +01:00
eb58d2fa52 - Rewind Message prior to access for peekValueType.
Fixes #8

(cherry picked from commit f8bed4b0faa2c0a2bc7037f3a55105060d56dbdb)
2018-12-24 15:30:55 +01:00
a6bb8c070e Switch from autotools to CMake build system (#23)
* Switch from autotools to CMake

* CMake: require at least cmake 3.8

* cmake: updates for tests
2018-09-26 09:28:10 +02:00
108c33faac stub-generator: fixed issue when <arg> in <method> does not have "name" property 2018-08-24 16:39:31 +02:00
ec06462713 Add missing constructor overload for ProxyInterfaces 2018-08-06 22:58:36 +02:00
234145cade Clarify proxy and connection stuff in case of ProxyInterfaces 2018-08-06 22:52:38 +02:00
e971f95bad Clarify system/session connection on proxy creation 2018-08-06 22:34:08 +02:00
4f5dfbc301 Updated changelog for v0.3.1 2018-07-24 13:03:53 +02:00
fa878e594c Be explicit on apply function from sdbus namespace (#17) 2018-07-24 12:55:33 +02:00
d3d698f02a Fix CPU hog on async methods: Clear the event descriptor by reading from it (#16) 2018-07-24 12:54:31 +02:00
d8fd053714 Introduce support for asynchronous server-side methods (#12)
* Add preliminary changes for async server methods

* Refactor the Message concept and break it into distinctive types

* Continue working on async server methods (high-level API mainly)

* Continue developing support for async server

* Finishing async server methods

* Finishing async server methods (fixing tests & cleaning up)

* A little code cleaning

* Add unit tests for type traits of free functions

* Support for generating async server methods in stub headers

* Update ChangeLog for v0.3.0

* Update the tutorial with how to use async server-side methods

* Update the TOC in sdbus-c++ tutorial

* Update numbering in TOC

* Remove unnecessary code

* Final cleanups
2018-07-02 11:22:00 +02:00
152 changed files with 24169 additions and 5981 deletions

23
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Real (buggy) behavior**
A clear and concise description of what really happened in contrast to your expectation.
**Additional context**
Add any other context about the problem here, like version of sdbus-c++ library, version of systemd used, OS used.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

105
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,105 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches:
- master
- release/v2.0
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
compiler: [g++, clang]
build: [shared-libsystemd, embedded-static-libsystemd]
steps:
- uses: actions/checkout@v4
- name: install-libsystemd-toolchain
if: matrix.build == 'embedded-static-libsystemd'
run: |
sudo apt-get update -y
sudo apt-get install -y meson ninja-build libcap-dev libmount-dev m4 gperf
- name: install-libsystemd-dev
if: matrix.build == 'shared-libsystemd'
run: |
sudo apt-get update -y
sudo apt-get install -y libsystemd-dev
- name: install-clang
if: matrix.compiler == 'clang'
run: |
sudo apt-get install -y clang libc++-dev
sudo update-alternatives --remove-all cc
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 10
sudo update-alternatives --remove-all c++
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 10
# We need to use libc++ with Clang because there is a bug in libstdc++ chrono headers that Clang has issues with
echo "SDBUSCPP_EXTRA_CXX_FLAGS=-stdlib=libc++" >> $GITHUB_ENV
# We don't install googletest but we let it be built within sdbus-c++ builds below, since it needs to be built against libc++ for Clang jobs to pass
# - name: install-googletest
# run: |
# sudo apt-get install -y libgmock-dev
- name: configure-debug-gcc11 # For gcc 11, turn off the annoying deprecated-copy warning
if: matrix.build == 'shared-libsystemd' && matrix.compiler == 'g++' && matrix.os == 'ubuntu-22.04'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Wno-deprecated-copy -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 ..
- name: configure-debug
if: matrix.build == 'shared-libsystemd' && (matrix.compiler != 'g++' || matrix.os != 'ubuntu-22.04')
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror $SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 ..
- name: configure-release-with-embedded-libsystemd
if: matrix.build == 'embedded-static-libsystemd'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="$SDBUSCPP_EXTRA_CXX_FLAGS" -DCMAKE_VERBOSE_MAKEFILE=ON -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_LIBSYSTEMD=ON -DSDBUSCPP_LIBSYSTEMD_VERSION=252 -DSDBUSCPP_GOOGLETEST_VERSION=1.14.0 ..
- name: make
run: |
cd build
cmake --build . -j4
- name: verify
run: |
cd build
sudo cmake --build . --target install
ctest --output-on-failure
- name: pack
if: matrix.build == 'shared-libsystemd'
run: |
cd build
cpack -G DEB
- name: 'Upload Artifact'
if: matrix.build == 'shared-libsystemd' && matrix.compiler == 'g++'
uses: actions/upload-artifact@v4
with:
name: "debian-packages-${{ matrix.os }}-${{ matrix.compiler }}"
path: |
build/sdbus-c++*.deb
build/sdbus-c++*.ddeb
retention-days: 10
freebsd-build:
name: build (freebsd, clang/libc++, basu)
runs-on: ubuntu-22.04 # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v4
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v1
with:
copyback: false
usesh: true
prepare: |
pkg install -y cmake ninja pkgconf basu expat googletest
run: |
cmake -B _build -G Ninja -DSDBUSCPP_INSTALL=ON -DSDBUSCPP_BUILD_CODEGEN=ON -DSDBUSCPP_BUILD_TESTS=ON -DSDBUSCPP_BUILD_PERF_TESTS=ON -DSDBUSCPP_BUILD_STRESS_TESTS=ON
cmake --build _build
cmake --install _build
pkg install -y dbus
service dbus onestart
ctest --output-on-failure --test-dir _build

6
.gitignore vendored
View File

@ -7,9 +7,9 @@ build/
.deps/
.dirstamp
.libs/
test/libsdbus-c++_unittests
test/libsdbus-c++_integrationtests
test/run-test-on-device.sh
tests/libsdbus-c++_unittests
tests/libsdbus-c++_integrationtests
tests/run-test-on-device.sh
#eclipse
.cproject

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>sdbus-cpp</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.autotools.core.genmakebuilderV2</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.eclipse.cdt.core.ccnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
<nature>org.yocto.sdk.ide.YoctoSDKNature</nature>
<nature>org.eclipse.cdt.autotools.core.autotoolsNatureV2</nature>
<nature>org.yocto.sdk.ide.YoctoSDKAutotoolsNature</nature>
</natures>
</projectDescription>

10
AUTHORS
View File

@ -1,5 +1,7 @@
The library:
Stanislav Angelovic (stanislav.angelovic[at]kistler.com)
sdbus-c++ library:
Stanislav Angelovic (https://github.com/sangelovic)
Stub generator:
Lukas Durfina (lukas.durfina[at]kistler.com)
Code generator:
Lukas Durfina (https://github.com/lukasdurfina)
... and other contributors :)

333
CMakeLists.txt Normal file
View File

@ -0,0 +1,333 @@
#-------------------------------
# PROJECT INFORMATION
#-------------------------------
cmake_minimum_required(VERSION 3.14)
project(sdbus-c++ VERSION 2.1.0 LANGUAGES CXX C)
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
# -------------------------------
# CONFIGURATION OPTIONS
# -------------------------------
option(SDBUSCPP_BUILD_LIBSYSTEMD "Fetch & build libsystemd static library and make it part of libsdbus-c++, instead of searching for it in the system" OFF)
if(NOT SDBUSCPP_BUILD_LIBSYSTEMD)
set(SDBUSCPP_SDBUS_LIB "default" CACHE STRING "sd-bus implementation library to search for and use (default, systemd, elogind, or basu)")
set_property(CACHE SDBUSCPP_SDBUS_LIB PROPERTY STRINGS default systemd elogind basu)
else()
set(SDBUSCPP_LIBSYSTEMD_VERSION "252" CACHE STRING "libsystemd version (>=239) to build and incorporate into libsdbus-c++")
set(SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS "" CACHE STRING "Additional configuration options to be passed as-is to libsystemd build system")
endif()
option(SDBUSCPP_INSTALL "Enable installation of sdbus-c++ (downstream projects embedding sdbus-c++ may want to turn this OFF)" ON)
option(SDBUSCPP_BUILD_TESTS "Build tests" OFF)
if (SDBUSCPP_BUILD_TESTS)
option(SDBUSCPP_BUILD_PERF_TESTS "Build also sdbus-c++ performance tests" OFF)
option(SDBUSCPP_BUILD_STRESS_TESTS "Build also sdbus-c++ stress tests" OFF)
set(SDBUSCPP_TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed")
set(SDBUSCPP_GOOGLETEST_VERSION 1.14.0 CACHE STRING "Version of gmock library to use")
set(SDBUSCPP_GOOGLETEST_GIT_REPO "https://github.com/google/googletest.git" CACHE STRING "A git repo to clone and build googletest from if gmock is not found in the system")
endif()
option(SDBUSCPP_BUILD_CODEGEN "Build generator tool for C++ native bindings" OFF)
option(SDBUSCPP_BUILD_EXAMPLES "Build example programs" OFF)
option(SDBUSCPP_BUILD_DOCS "Build documentation for sdbus-c++" ON)
if(SDBUSCPP_BUILD_DOCS)
option(SDBUSCPP_BUILD_DOXYGEN_DOCS "Build doxygen documentation for sdbus-c++ API" OFF)
endif()
#option(SDBUSCPP_CLANG_TIDY "Co-compile with clang-tidy static analyzer" OFF)
#option(SDBUSCPP_COVERAGE "Build sdbus-c++ with code coverage instrumentation" OFF)
# We promote the BUILD_SHARED_LIBS flag to a (global) option only if we are the main project
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON)
endif()
message(STATUS "")
message(STATUS " *******************************************")
message(STATUS " * SDBUS-C++ CONFIGURATION *")
message(STATUS " *******************************************")
message(STATUS "")
message(STATUS " SDBUSCPP_BUILD_LIBSYSTEMD: ${SDBUSCPP_BUILD_LIBSYSTEMD}")
if(SDBUSCPP_BUILD_LIBSYSTEMD)
message(STATUS " SDBUSCPP_LIBSYSTEMD_VERSION: ${SDBUSCPP_LIBSYSTEMD_VERSION}")
message(STATUS " SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS: ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS}")
endif()
message(STATUS " SDBUSCPP_INSTALL: ${SDBUSCPP_INSTALL}")
message(STATUS " SDBUSCPP_BUILD_TESTS: ${SDBUSCPP_BUILD_TESTS}")
if(SDBUSCPP_BUILD_TESTS)
message(STATUS " SDBUSCPP_BUILD_PERF_TESTS: ${SDBUSCPP_BUILD_PERF_TESTS}")
message(STATUS " SDBUSCPP_BUILD_STRESS_TESTS: ${SDBUSCPP_BUILD_STRESS_TESTS}")
message(STATUS " SDBUSCPP_TESTS_INSTALL_PATH: ${SDBUSCPP_TESTS_INSTALL_PATH}")
message(STATUS " SDBUSCPP_GOOGLETEST_VERSION: ${SDBUSCPP_GOOGLETEST_VERSION}")
message(STATUS " SDBUSCPP_GOOGLETEST_GIT_REPO: ${SDBUSCPP_GOOGLETEST_GIT_REPO}")
endif()
message(STATUS " SDBUSCPP_BUILD_CODEGEN: ${SDBUSCPP_BUILD_CODEGEN}")
message(STATUS " SDBUSCPP_BUILD_EXAMPLES: ${SDBUSCPP_BUILD_EXAMPLES}")
message(STATUS " SDBUSCPP_BUILD_DOCS: ${SDBUSCPP_BUILD_DOCS}")
if(SDBUSCPP_BUILD_DOCS)
message(STATUS " SDBUSCPP_BUILD_DOXYGEN_DOCS: ${SDBUSCPP_BUILD_DOXYGEN_DOCS}")
endif()
message(STATUS " BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}")
message(STATUS "")
#-------------------------------
# PERFORMING CHECKS & PREPARING THE DEPENDENCIES
#-------------------------------
if(SDBUSCPP_BUILD_LIBSYSTEMD)
# Build static libsystemd library as an external project
include(cmake/LibsystemdExternalProject.cmake)
set(SDBUS_IMPL "systemd")
set(SDBUS_LIB "libsystemd")
else()
# Search for sd-bus implementations in the system as per user's configuration
set(SDBUS_LIBS ${SDBUSCPP_SDBUS_LIB})
if(SDBUSCPP_SDBUS_LIB STREQUAL "default")
set(SDBUS_LIBS systemd elogind basu) # This is the default search order
endif()
set(MINIMUM_SDBUS_VERSION 238)
find_package(PkgConfig REQUIRED)
foreach(LIB ${SDBUS_LIBS})
if(LIB STREQUAL "systemd")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libsystemd>=${MINIMUM_SDBUS_VERSION})
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "systemd")
set(SDBUS_LIB "libsystemd")
break()
endif()
elseif(LIB STREQUAL "elogind")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=${MINIMUM_SDBUS_VERSION})
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "elogind")
set(SDBUS_LIB "libelogind")
string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION})
list(GET VERSION_LIST 0 Systemd_VERSION)
break()
endif()
elseif(LIB STREQUAL "basu")
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL basu)
if(TARGET PkgConfig::Systemd)
set(SDBUS_IMPL "basu")
set(SDBUS_LIB "basu")
set(Systemd_VERSION "240") # https://git.sr.ht/~emersion/basu/commit/d4d185d29a26
break()
endif()
else()
message(FATAL_ERROR "Unrecognized sd-bus implementation library ${LIB}")
endif()
endforeach()
if(NOT TARGET PkgConfig::Systemd)
message(FATAL_ERROR "None of ${SDBUS_LIBS} libraries implementing sd-bus was found (Are their dev packages installed?)")
endif()
add_library(Systemd::Libsystemd ALIAS PkgConfig::Systemd)
string(REGEX MATCHALL "([0-9]+)" SYSTEMD_VERSION_LIST "${Systemd_VERSION}")
list(GET SYSTEMD_VERSION_LIST 0 SDBUSCPP_LIBSYSTEMD_VERSION)
message(STATUS "Building with libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}")
endif()
find_package(Threads REQUIRED)
#-------------------------------
# SOURCE FILES CONFIGURATION
#-------------------------------
set(SDBUSCPP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
set(SDBUSCPP_INCLUDE_SUBDIR sdbus-c++)
set(SDBUSCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/${SDBUSCPP_INCLUDE_SUBDIR})
set(SDBUSCPP_CPP_SRCS
${SDBUSCPP_SOURCE_DIR}/Connection.cpp
${SDBUSCPP_SOURCE_DIR}/Error.cpp
${SDBUSCPP_SOURCE_DIR}/Message.cpp
${SDBUSCPP_SOURCE_DIR}/Object.cpp
${SDBUSCPP_SOURCE_DIR}/Proxy.cpp
${SDBUSCPP_SOURCE_DIR}/Types.cpp
${SDBUSCPP_SOURCE_DIR}/Flags.cpp
${SDBUSCPP_SOURCE_DIR}/VTableUtils.c
${SDBUSCPP_SOURCE_DIR}/SdBus.cpp)
set(SDBUSCPP_HDR_SRCS
${SDBUSCPP_SOURCE_DIR}/Connection.h
${SDBUSCPP_SOURCE_DIR}/IConnection.h
${SDBUSCPP_SOURCE_DIR}/MessageUtils.h
${SDBUSCPP_SOURCE_DIR}/Utils.h
${SDBUSCPP_SOURCE_DIR}/Object.h
${SDBUSCPP_SOURCE_DIR}/Proxy.h
${SDBUSCPP_SOURCE_DIR}/ScopeGuard.h
${SDBUSCPP_SOURCE_DIR}/VTableUtils.h
${SDBUSCPP_SOURCE_DIR}/SdBus.h
${SDBUSCPP_SOURCE_DIR}/ISdBus.h)
set(SDBUSCPP_PUBLIC_HDRS
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.h
${SDBUSCPP_INCLUDE_DIR}/ConvenienceApiClasses.inl
${SDBUSCPP_INCLUDE_DIR}/VTableItems.h
${SDBUSCPP_INCLUDE_DIR}/VTableItems.inl
${SDBUSCPP_INCLUDE_DIR}/Error.h
${SDBUSCPP_INCLUDE_DIR}/IConnection.h
${SDBUSCPP_INCLUDE_DIR}/AdaptorInterfaces.h
${SDBUSCPP_INCLUDE_DIR}/ProxyInterfaces.h
${SDBUSCPP_INCLUDE_DIR}/StandardInterfaces.h
${SDBUSCPP_INCLUDE_DIR}/IObject.h
${SDBUSCPP_INCLUDE_DIR}/IProxy.h
${SDBUSCPP_INCLUDE_DIR}/Message.h
${SDBUSCPP_INCLUDE_DIR}/MethodResult.h
${SDBUSCPP_INCLUDE_DIR}/Types.h
${SDBUSCPP_INCLUDE_DIR}/TypeTraits.h
${SDBUSCPP_INCLUDE_DIR}/Flags.h
${SDBUSCPP_INCLUDE_DIR}/sdbus-c++.h)
set(SDBUSCPP_SRCS ${SDBUSCPP_CPP_SRCS} ${SDBUSCPP_HDR_SRCS} ${SDBUSCPP_PUBLIC_HDRS})
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
set(CMAKE_CXX_STANDARD 20)
#----------------------------------
# LIBRARY BUILD INFORMATION
#----------------------------------
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
# Having an object target allows unit tests to reuse already built sources without re-building
add_library(sdbus-c++-objlib OBJECT ${SDBUSCPP_SRCS})
target_compile_definitions(sdbus-c++-objlib PRIVATE
BUILD_LIB=1
LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL}
SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>)
target_include_directories(sdbus-c++-objlib PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>)
if(BUILD_SHARED_LIBS)
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
if(SDBUSCPP_BUILD_LIBSYSTEMD)
add_dependencies(sdbus-c++-objlib LibsystemdBuildProject)
endif()
target_link_libraries(sdbus-c++-objlib
PUBLIC
Systemd::Libsystemd
Threads::Threads)
add_library(sdbus-c++)
target_include_directories(sdbus-c++ PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
set_target_properties(sdbus-c++
PROPERTIES PUBLIC_HEADER "${SDBUSCPP_PUBLIC_HDRS}"
VERSION "${SDBUSCPP_VERSION}"
SOVERSION "${SDBUSCPP_VERSION_MAJOR}"
OUTPUT_NAME "sdbus-c++")
target_link_libraries(sdbus-c++ PRIVATE sdbus-c++-objlib)
#----------------------------------
# TESTS
#----------------------------------
if(SDBUSCPP_BUILD_TESTS)
message(STATUS "Building with tests")
enable_testing()
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tests")
endif()
#----------------------------------
# UTILS
#----------------------------------
if(SDBUSCPP_BUILD_CODEGEN)
message(STATUS "Building with code generator tool")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/tools")
endif()
#----------------------------------
# EXAMPLES
#----------------------------------
if(SDBUSCPP_BUILD_EXAMPLES)
message(STATUS "Building with examples")
add_subdirectory(examples)
endif()
#----------------------------------
# DOCUMENTATION
#----------------------------------
if(SDBUSCPP_BUILD_DOCS)
message(STATUS "Building with documentation")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/docs")
endif()
#----------------------------------
# CMAKE CONFIG & PACKAGE CONFIG
#----------------------------------
include(CMakePackageConfigHelpers)
configure_package_config_file(cmake/sdbus-c++-config.cmake.in cmake/sdbus-c++-config.cmake
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++)
write_basic_package_version_file(cmake/sdbus-c++-config-version.cmake COMPATIBILITY SameMajorVersion)
if(BUILD_SHARED_LIBS AND (SDBUSCPP_BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/libsystemd\.a(;|$)"))
set(PKGCONFIG_REQS ".private")
else()
set(PKGCONFIG_REQS "")
endif()
set(PKGCONFIG_DEPS ${SDBUS_LIB})
configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY)
#----------------------------------
# INSTALLATION
#----------------------------------
if(SDBUSCPP_INSTALL)
set(EXPORT_SET sdbus-c++)
if(NOT BUILD_SHARED_LIBS)
list(APPEND EXPORT_SET "sdbus-c++-objlib")
endif()
install(TARGETS ${EXPORT_SET}
EXPORT sdbus-c++-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-runtime
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-runtime NAMELINK_COMPONENT sdbus-c++-dev
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT sdbus-c++-dev
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${SDBUSCPP_INCLUDE_SUBDIR} COMPONENT sdbus-c++-dev)
install(EXPORT sdbus-c++-targets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
NAMESPACE SDBusCpp::
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config.cmake
${CMAKE_CURRENT_BINARY_DIR}/cmake/sdbus-c++-config-version.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
COMPONENT sdbus-c++-dev)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT sdbus-c++-dev)
if(SDBUSCPP_BUILD_DOCS)
install(FILES README README.md NEWS COPYING ChangeLog AUTHORS DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc)
endif()
endif()
#----------------------------------
# CPACK
#----------------------------------
set(CPACK_PACKAGE_VENDOR "Kistler")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "High-level C++ D-Bus library")
set(CPACK_PACKAGE_CONTACT "info@kistler.com")
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_COMPONENTS_ALL runtime dev doc)
set(CPACK_COMPONENT_DEV_DEPENDS "runtime")
# specific for DEB generator
set(CPACK_DEB_COMPONENT_INSTALL ON)
set(CPACK_DEBIAN_RUNTIME_DEBUGINFO_PACKAGE ON)
set(CPACK_DEBIAN_RUNTIME_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
set(CPACK_DEBIAN_PACKAGE_SECTION "libs")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)
set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "libsystemd-dev (>=${MINIMUM_SDBUS_VERSION})")
include(CPack)

22
COPYING-LGPL-Exception Normal file
View File

@ -0,0 +1,22 @@
sdbus-c++ LGPL Exception version 1.0
As an additional permission to the GNU Lesser General Public License version
2.1, the object code form of a "work that uses the Library" may incorporate
material from a header file that is part of the Library. You may distribute
such object code under terms of your choice, provided that:
(i) the header files of the Library have not been modified; and
(ii) the incorporated material is limited to numerical parameters, data
structure layouts, accessors, macros, inline functions and
templates; and
(iii) you comply with the terms of Section 6 of the GNU Lesser General
Public License version 2.1.
Moreover, you may apply this exception to a modified version of the Library,
provided that such modification does not involve copying material from the
Library into the modified Library's header files unless such material is
limited to (i) numerical parameters; (ii) data structure layouts;
(iii) accessors; and (iv) small macros, templates and inline functions of
five lines or less in length.
Furthermore, you are not required to apply this additional permission to a
modified version of the Library.

320
ChangeLog
View File

@ -1,17 +1,303 @@
v0.2.3
- Initially published version
v0.2.4
- Fixed closing of file descriptor of event loop's semaphore on exec
- Fixed interrupt handling when polling
- Improved tutorial
- Fixed issue with googlemock
- Added object proxy factory overload that takes unique_ptr to a connection
- Workaround: Clang compilation error when compiling sdbus::Struct (seems like an issue of Clang)
v0.2.5
- Real fix for issue with sdbus::Struct inherited constructors
- Little code refactorings and improvements
v0.2.6
- Fixed memory leak in Message copy operations
v0.2.3
- Initially published version
v0.2.4
- Fixed closing of file descriptor of event loop's semaphore on exec
- Fixed interrupt handling when polling
- Improved tutorial
- Fixed issue with googlemock
- Added object proxy factory overload that takes unique_ptr to a connection
- Workaround: Clang compilation error when compiling sdbus::Struct (seems like an issue of Clang)
v0.2.5
- Real fix for issue with sdbus::Struct inherited constructors
- Little code refactorings and improvements
v0.2.6
- Fixed memory leak in Message copy operations
v0.3.0
- Introduced support for asynchronous server-side methods
- Refactored the concept of Message into distinctive concrete message types
- As this release comes with breaking API changes:
* if you're using lower-level API, please rename 'Message' to whatever concrete message type (MethodCall, MethodReply, Signal) is needed there,
* if you're using higher-level API, please re-compile and re-link your application against sdbus-c++,
* if you're using generated stub headers, please re-compile and re-link your application against sdbus-c++.
v0.3.1
- Fixed hogging the CPU by server with async methods (issue #15)
v0.3.2
- Switched from autotools to CMake build system
v0.3.3
- Minor fixes in tutorial examples
- Add comment on threading traits of Variant and its const methods
- Fix broken invariant of const Variant::peekValueType() method
v0.4.0
- Introduce support and implementation of common D-Bus annotations
- Remove hard-coded warning-related compiler options from the build system
v0.4.1
- Change constexpr member into a getter method to be compatible across odr-usage rules of various compiler versions
v0.4.2
- Improve documentation
- Minor code improvements
- Introduce sdbus-c++ performance tests with measurements
v0.4.3
- Add another abstraction layer for sd-bus to improve isolation for unit testing
- Minor code improvements for consistency and clarity
- Not-existing interface name and object path fixes in integration tests
v0.5.0
- Redesign of the Connection model for scalability, thread-safety, simplicity and clarity
- [[Breaking ABI change]] Introduce support for asynchronous invocation of D-Bus methods on client-side
- Revise and extend sdbus-c++ tutorial
- Introduce stress tests that involve a D-Bus server and client interacting in dense communication in both sync and async ways
- Compilation error fixes for older gcc versions
v0.5.1
- Cleanups of CMakeLists.txt
- Rename sdbus-c++ test executables for consistency
- Rename sdbus-c++ stub code generator executable
- Introduce generation of doxygen documentation
- Improve documentation
v0.5.2
- Simplify and unify basic API level callbacks for both sync and async methods
v0.5.3
- Extend stress tests with dynamic object and proxy creation and destruction from multiple threads
- [[Breaking ABI change]] Add getConnection() method to IObject
- A few minor fixes
v0.6.0
- This release comes with a number of API and ABI breaking changes (that hopefully lead to a more mature and stable API for near future):
- Some constructs have been renamed (see below for details), you'll have to adapt your sources whether you're using the basic or the convenience sdbus-c++ API.
- You'll also have to re-generate adaptor/proxy stubs with the new stub code generator if you're using them.
- And you'll have to take care of manual registeration/deregistration in the constructors/destructors of your final adaptor and proxy classes. See updated using sdbus-c++ tutorial.
- Cleaning up some names (of class, methods, files):
* ConvenienceClasses.h/.inl/.cpp -> ConvenienceApiClasses.h/.inl/.cpp
* IObjectProxy class -> IProxy
* Interfaces class -> AdaptorInterfaces
* Interfaces.h -> split into AdaptorInterfaces.h and ProxyInterfaces.h
* createObjectProxy() method -> createProxy()
- Add new unregister() virtual function to IObject and IProxy to allow for safe construction and destructions of D-Bus ojects and proxies hooked to live D-Bus connections.
- Extend stress tests to allow testing safe initialization/deinitialization of objects and proxies
- Fix gcc warnings
- Use release v1.8.1 of googletest for tests
v0.7.0
- [[Breaking ABI change]] Added full support for ObjectManager, Properties and other standard D-Bus interfaces (Reordering virtual functions in interface classes)
- sdbus-c++ now can automatically download, build and incorporate libsystemd static library, which makes things pretty easy in non-systemd environments
- Minor changes in generated proxy/adaptor code:
* renamed interfaceName to INTERFACE_NAME to avoid potential naming clashes
* signal emitting methods now begin with "emit" prefix and capitalized first letter of signal name
- sdbus-c++ file organization has been adjusted to comply to de-facto OSS project standards
- Build system improvements -- moving towards more modern CMake
- Using googletest from master branch instead of v1.8.1 which has THREADS_PTHREAD_ARG issue when cross-compiling
- Documentation improvements
- Minor changes (renamings) in the code generated by sdbus-c++-xml2cpp generator
- Other minor fixes and internal design improvements
v0.7.1
- Fixed waiting in integration tests
- Added object manager automatically in ObjectManager_adaptor constructor
- Introduced support for unix fd type
- Redesigned Message class hierarchy to be more idiomatic, clean and intent-revealing
- Resolved a few clang-tidy warnings and suggestions
- Extended the tutorial with info on standard D-Bus interfaces
- Added protected non-virtual destructor in generated *_proxy/*_adaptor classes
v0.7.2
- Rewrite UnixFd implementation from plain UnixFd struct to full-ownership-semantics UnixFd class
v0.7.3
- Add ability to integrate with external event loops
- Add getSenderName() method to Message
- Skip GetMachineId integration test case when /etc/machine-id is not available
v0.7.4
- Add support for custom timeout of D-Bus method calls
- Add support for opening a connection to a remote system bus using ssh
- Internal refactoring: Use tag dispatching to construct various types of Connection
v0.7.5
- [[Breaking ABI change]] No more hiding from C++17: Move API code containing C++17 uncaught_exceptions calls from within library to public API
- Add a method to retrieve the unique name of a connection
v0.7.6
- Fixes of clang-8 errors and warnings
v0.7.7
- Fix race condition of polling on D-Bus fd from two threads (event loop thread and sync D-Bus call thread)
- Little ordering fix in stress tests
v0.7.8
- Switch from thread_local to global bus instance that is used to create Variant instances (thread_local caused issues with Variant in very special inter-thread situations)
v0.8.0
- [[Breaking ABI change]] Implement support for input & output parameter names for D-Bus methods and signals, which are used in introspection
- Explain better in tutorial the design and how to use connections in relation to objects and proxies
v0.8.1
- Switch to full C++17 support
- Switch to more modern CMake (>=3.12)
- Provide better names to event loop-related IConnection methods, keep old ones marked as deprecated for backwards compatibility
v0.8.2
- Introduce support for cancellable async calls
- Add getObjectPath() for proxy and object classes
- Sanitize names of namespaces/methods/signals/properties/arguments in sdbus-c++-xml2cpp
- Fix delivery of signals to multiple proxies subscribed to them
- Fix file existence condition in sdbus-c++-xml2cpp
- Fix CallData race condition in Proxy::callMethod
- Fix integration tests for libsystemd older than 242
- Fix installation of public sd-bus headers in internal libsystemd build
- Fix integration test cases failing in specific situations
- Fix build with clang 9.0.1 and libcxx
- Fix potential data race in Proxy's condition variable
v0.8.3
- Fix build with gcc 8.3
- Address a few inconsistencies and make code more idiomatic
- Clean up integration tests
- Remove non-virtual-dtor warnings by making classes final
- Update CMake configuration flag names
- Fix unused variable warning for release builds
- Introduce CI workflow based on GitHub Actions
v0.9.0
- Provide CMake config and PkgConfig files for tools
- Provide access to D-Bus message in high-level API
- Add API to set signal destination
- Add IProxy::getConnection() method
- Add README and sdbus-c++ tutorial as additional pages in doxydocs
- Enable default construction of PendingAsyncCall
- Add API to get message path and message destination
- Avoid propagating msg unpack exceptions to the event loop
- Fix issue #145: signals are not filtered by sender
- Fix race condition in Proxy and Object destructor
- Fix seg fault in Message::peekType()
- Add information to documentation about conan recipe
- Add cpack to build debian packages, split the packages by components
- Catch sdbus-c++ exceptions flying from Proxy callbacks to libsystemd
- Add createDefaultBusConnection() method
- Make resetting loop thread ID exception-safe
- Support Error parameter in signal handlers
- Add specific sections for tips and notes in the tutorial
- A few additional documentation and test updates and improvements
v1.0.0
- [[Breaking API change]] Fixed the API to send org.freedesktop.DBus.ObjectManager.InterfacesAdded and org.freedesktop.DBus.ObjectManager.InterfacesRemoved signals via the generated stubs layer.
- StandardInterfaces.h: Split ObjectManager_adaptor and ManagedObject_adaptor.
- New examples directory. First example covers the object manager. Further examples might follow.
v1.1.0
- Fix timeout handling for asynchronous method calls
- Add support for unregistering signal handler
- Add support for chrono literals in sdbus-c++-xml2cpp generator
- Additional little fixes and improvements in code, build system, and documentation
v1.2.0
- Add support for match rules
- Add support for session bus connection at custom address
- Add CMake variable for extra libsystemd config options
- Use pseudo D-Bus connection for plain messages
- Rename dont_request_slot tag to floating_slot
- Add validity checks for names and paths
- Remove executable flag from source files
- Detect missing type after array declaration
- Fix invalid assert on event fd
- Enable move for ObjectPath and Signature
- Add printer for std::chrono in googletest v1.11.0
- Fix potential undefined behavior in creation of sdbus::Error
- Additional little fixes and improvements in code, build system, and documentation
v1.3.0
- Add support for light-weight proxies (proxies without own event loop threads)
- Extend documentation with explicit mapping between D-Bus and corresponding C++ types
- Support move semantics in generated adaptor and proxy classes
- Adaptations for libsystemd v251
- Fix for proper complete sending of long D-Bus messages by explicitly flushing them
- Add support for std::future-based async calls
- Fix race condition in async Proxy::callMethod
- Fix pseudo-connection static lifetime issue with Phoenix pattern
- Speed up performance of of serialization of arrays of trivial D-Bus types
- Make sdbus::Struct a tuple-like class, so it's usable wherever std::tuple is
- Add support for std::array, std::span and std::unordered_map as additional C++ types for D-Bus array types
- Add support for libelogind as an addition to libsystemd
- Add support for std::future-based async methods in codegen tool
- Additional little fixes and improvements in code, build system, CI, and documentation
v1.4.0
- Implement API for convenient asynchronous property get/set on the client-side
- Add support for FreeBSD systems (including support for basu implementation of sd-bus on non-systemd machines)
- Add support for direct, peer-to-peer connections
- Add option to create IConnection directly from an underlying sd_bus instance
- Some additional fixes
v1.5.0
- Improve handling of exceptions from callback handlers
- Add support for async registration of matches
- Correctly add libsystemd dependency to pkgconfi
- Fix request name signal handling issue
- Add INSTALL_TESTS CMake option
- Minor UnixFd cleanups
- Additional little fixes and updates in code, build system, CI, and documentation
v1.6.0
- Add support for enums in D-Bus serialization and signatures
- Add support for std::variant as an alternative C++ type for D-Bus Variant
- Add support for implicit conversions between std::variant and sdbus::Variant
- Fix missing includes
v2.0.0
- A new major version with revamped API, bringing numerous new features, simplifications, fixes and breaking changes improving not only API consistency, safety and expressiveness, but also performance
- Add section 'Migrating to sdbus-c++ v2' to the 'Using sdbus-c++' document providing the complete list of breaking API/ABI/behavior changes and migration notes
- Switch to C++20 standard (but the API is backwards-compatible with C++17, skipping C++20 features in such a case)
- Add strong types to public API
- Add support for string_view as a D-Bus type representation
- Enable compile-time D-Bus signature generation
- Redesign Object vtable registration API
- Improve Proxy signal subscription API
- Refactor object manager API for consistency
- Introduce native sd-event integration
- Let callbacks take message objects by value
- Simplify async D-Bus connection handling
- Simplify async call state flag
- Make Variant constructor explicit
- Use optional for passing potential call errors
- Rename and re-organize CMake options
- Rename connection creation methods
- Have all async function names finish with *Async for consistency
- Implement more flexible searching for sd-bus libs
- Add new `SDBUSCPP_SDBUS_LIB` CMake configuration variable determining which sd-bus library shall be picked
- Make Variant conversion operator explicit
- Use sd-bus API to get the current message
- Use sd_bus_match_signal() for signal registration
- Provide also const char* overloads for convenience functions
- Disable move in generated adaptor and proxy classes
- Improve and make more efficient some Message API
- Add Slot-returning overloads of async method calls
- Remove floating_slot_t tag and use return_slot_t instead
- Add [[nodiscard]] to many relevant API functions
- Add proper fix for timeout handling
- Fix for external event loops in which the event loop thread ID was not correctly initialized (now fixed and simplified by not needing the thread ID anymore)
- Remove deprecated stuff
- Add `SDBUSCPP_` prefix to CMake configuration variables to avoid conflicts with downstream projects
- Require systemd of at least v238
- Many other fixes and updates in code, tests, build system, CI, and documentation
v2.1.0
- Add SDBUSCPP_REGISTER_STRUCT macro to conveniently teach sdbus-c++ about user-defined structs
- Extend the SDBUSCPP_REGISTER_STRUCT macro with serialization of user-defined structs as dicts, and deserialization of dicts into user-defined structs
- Make createPlainMessage() function public
- Solve the problem of sending large D-Bus messages properly (through the event loop thread)
- Fix partially renamed BUILD_DOXYGEN_DOC CMake option
- Change googletest to default version 1.14.0
- Add version parameter to the xml2cpp codegen tool
- A few other internal refactorings and improvements

View File

@ -1,5 +1,7 @@
Building:
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release ${OTHER_CONFIG_FLAGS}
$ make
Installing:

View File

@ -1,22 +0,0 @@
EXTRA_DIST = autogen.sh
SUBDIRS = include src test
DISTCHECK_CONFIGURE_FLAGS=
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = sdbus-c++.pc
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
DISTCLEANFILES = \
*libtool* \
aclocal.m4 \
compile config.* configure \
depcomp install-sh \
ltmain.sh \
Makefile Makefile.in \
missing \
stamp-h1 \
sdbus-c++.pc

View File

@ -1,37 +1,114 @@
sdbus-c++
=========
sdbus-c++ is a C++ API library for D-Bus IPC, based on sd-bus implementation.
![ci](https://github.com/Kistler-Group/sdbus-cpp/workflows/CI/badge.svg)
![license](https://img.shields.io/github/license/Kistler-Group/sdbus-cpp)
![release](https://img.shields.io/github/v/release/Kistler-Group/sdbus-cpp)
sdbus-c++ is a high-level C++ D-Bus library for Linux designed to provide expressive, easy-to-use API in modern C++. It adds another layer of abstraction on top of sd-bus, a nice, fresh C D-Bus implementation by systemd.
sdbus-c++ has been written primarily as a replacement of dbus-c++, which currently suffers from a number of (unresolved) bugs, concurrency issues and inherent design complexities and limitations. sdbus-c++ has learned from dbus-c++ and has chosen a different path, a path of simple yet powerful design that is intuitive and friendly to the user and inherently free of those bugs.
Even though sdbus-c++ uses sd-bus library, it is not necessarily constrained to systemd and can perfectly be used in non-systemd environments as well.
Building and installing the library
-----------------------------------
The library is built using CMake:
```bash
$ ./autogen.sh ${CONFIGURE_FLAGS}
$ make
$ sudo make install
$ mkdir build
$ cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release ${OTHER_CONFIG_FLAGS}
$ cmake --build .
$ sudo cmake --build . --target install
```
Use `--disable-tests` flag when configuring to disable building unit and integration tests for the library.
### CMake configuration flags for sdbus-c++
* `SDBUSCPP_BUILD_CODEGEN` [boolean]
Build the codegen tool `sdbus-c++-xml2cpp` for generating the high level C++ bindings out of the D-Bus IDL XML description. Default value: `OFF`. Use `-DSDBUSCPP_BUILD_CODEGEN=ON` flag to turn on building the code gen.
* `SDBUSCPP_BUILD_DOCS` [boolean]
Include sdbus-c++ documentation files and tutorials. Default value: `ON`. With this option turned on, you may also enable/disable the following option:
* `SDBUSCPP_BUILD_DOXYGEN_DOCS` [boolean]
Build Doxygen documentation of sdbus-c++ API. If enabled, the documentation must still be built explicitly through `cmake --build . --target doc`. Default value: `OFF`. Use `-DSDBUSCPP_BUILD_DOXYGEN_DOCS=OFF` to disable searching for Doxygen and building Doxygen documentation of sdbus-c++ API.
* `SDBUSCPP_BUILD_TESTS` [boolean]
Build sdbus-c++ unit and integration tests, invokable by `cmake --build . --target test` (Note: before invoking `cmake --build . --target test`, make sure you copy `tests/integrationtests/files/org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory). That incorporates downloading and building static libraries of Google Test. Default value: `OFF`. Use `-DBUILD_TESTS=ON` to enable building the tests. With this option turned on, you may also enable/disable the following options:
* `SDBUSCPP_BUILD_PERF_TESTS` [boolean]
Build sdbus-c++ performance tests. Default value: `OFF`.
* `SDBUSCPP_BUILD_STRESS_TESTS` [boolean]
Build sdbus-c++ stress tests. Default value: `OFF`.
* `SDBUSCPP_TESTS_INSTALL_PATH` [string]
Path where the test binaries shall get installed. Default value: `${CMAKE_INSTALL_PREFIX}/tests/sdbus-c++` (previously: `/opt/test/bin`).
* `SDBUSCPP_BUILD_EXAMPLES` [boolean]
Build example programs which are located in the _example_ directory. Examples are not installed. Default value: `OFF`.
* `SDBUSCPP_BUILD_LIBSYSTEMD` [boolean]
Build sd-bus (libsystemd library) instead of searching for it in the system, and make it part of sdbus-c++ library. Default value: `OFF`, which means that the sd-bus implementation library (`libsystemd`, `libelogind`, or `basu`) will be searched via `pkg-config` in the system.
This option may be very helpful in environments where sd-bus implementation library is unavailable (see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information).
With this option turned off, you may provide the following additional configuration flag:
* `SDBUSCPP_SDBUS_LIB` [string]
Defines which sd-bus implementation library to search for and use. Allowed values: `default`, `systemd`, `elogind`, `basu`. Default value: `default`, which means that sdbus-c++ will try to find any of `systemd`, `elogind`, `basu` in the order as listed here.
With this option turned on, you may provide the following additional configuration flag:
* `SDBUSCPP_LIBSYSTEMD_VERSION` [string]
Defines version of systemd to be downloaded, built and integrated into sdbus-c++. Default value: `252`, minimum value: `239`.
* `SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS` [string]
Additional options to be passed as-is to the libsystemd build system in its configure step. Can be used for passing e.g. toolchain file path in case of cross builds. Default value: empty.
* `CMAKE_BUILD_TYPE` [string]
CMake-builtin option. Set to `Release` to build sdbus-c++ for production use. Set to `Debug` if you want to help further develop (and debug) the library :)
* `BUILD_SHARED_LIBS` [boolean]
Global CMake flag, promoted in sdbus-c++ project to a CMake option. Use this to control whether sdbus-c++ is built as either a shared or static library. Default value: `ON`.
Dependencies
------------
* `C++17` - the library uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature.
* `libsystemd` - systemd library containing sd-bus implementation. Systemd v236 at least is needed for sdbus-c++ to compile.
* `googletest` - google unit testing framework, only necessary when building tests
* `C++20/C++17` - the library uses C++20 features, but its public API is backwards compatible with C++17 and provides optional extra features when C++20 features are available
* `libsystemd`/`libelogind`/`basu` - libraries containing sd-bus implementation that sdbus-c++ is written around. In case of `libsystemd` and `libelogind`, version >= 238 is needed. (In case you have you're missing any of those sd-bus implementations, don't worry, see [Solving sd-bus dependency](docs/using-sdbus-c++.md#solving-sd-bus-dependency) for more information.)
* `googletest` - google unit testing framework, only necessary when building tests, will be downloaded and built automatically.
* `pkgconfig` - required for sdbus-c++ to be able to find some dependency packages.
* `expat` - necessary when building the xml2cpp binding code generator (`SDBUSCPP_BUILD_CODEGEN` option is `ON`).
Licensing
---------
The library is distributed under LGPLv2.1 license.
The library is distributed under LGPLv2.1 license, with a specific exception for macro/template/inline code in library header files.
References/documentation
------------------------
* [Using sdbus-c++](docs/using-sdbus-c++.md) - *the* main, comprehensive tutorial on sdbus-c++
* [Systemd and dbus configuration](docs/systemd-dbus-config.md)
* [D-Bus Specification](https://dbus.freedesktop.org/doc/dbus-specification.html)
* [sd-bus Overview](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html)
* [Tutorial: Using sdbus-c++](doc/using-sdbus-c++.md)
Contributing
------------
@ -41,4 +118,4 @@ Contributions that increase the library quality, functionality, or fix issues ar
Contact
-------
stanislav.angelovic[at]kistler.com
https://github.com/Kistler-Group/sdbus-cpp

View File

@ -1,10 +0,0 @@
#! /bin/sh
[ -e config.cache ] && rm -f config.cache
libtoolize --automake
aclocal ${OECORE_ACLOCAL_OPTS}
autoconf
autoheader
automake -a
./configure $@
exit

View File

@ -0,0 +1,62 @@
find_program(MESON meson)
find_program(NINJA ninja)
find_program(GPERF gperf)
if((NOT MESON) OR (NOT NINJA))
message(FATAL_ERROR "Meson and Ninja are required to build libsystemd")
endif()
if(NOT GPERF)
message(WARNING "gperf was not found, libsystemd configuration may fail")
endif()
find_library(GLIBC_RT_LIBRARY rt)
find_package(PkgConfig REQUIRED)
pkg_check_modules(MOUNT mount)
pkg_check_modules(CAP REQUIRED libcap)
if (NOT CAP_FOUND)
find_library(CAP_LIBRARIES cap) # Compat with Ubuntu 14.04 which ships libcap w/o .pc file
endif()
if(NOT CMAKE_BUILD_TYPE)
set(LIBSYSTEMD_BUILD_TYPE "plain")
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LIBSYSTEMD_BUILD_TYPE "debug")
else()
set(LIBSYSTEMD_BUILD_TYPE "release")
endif()
if(SDBUSCPP_LIBSYSTEMD_VERSION LESS "239")
message(FATAL_ERROR "Only libsystemd version >=239 can be built as static part of sdbus-c++")
endif()
if(SDBUSCPP_LIBSYSTEMD_VERSION GREATER "240")
set(BUILD_VERSION_H ${NINJA} -C <BINARY_DIR> version.h)
endif()
message(STATUS "Building with embedded libsystemd v${SDBUSCPP_LIBSYSTEMD_VERSION}")
include(ExternalProject)
ExternalProject_Add(LibsystemdBuildProject
PREFIX libsystemd-v${SDBUSCPP_LIBSYSTEMD_VERSION}
GIT_REPOSITORY https://github.com/systemd/systemd-stable.git
GIT_TAG v${SDBUSCPP_LIBSYSTEMD_VERSION}-stable
GIT_SHALLOW 1
UPDATE_COMMAND ""
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E remove <BINARY_DIR>/*
COMMAND ${MESON} --prefix=<INSTALL_DIR> --buildtype=${LIBSYSTEMD_BUILD_TYPE} -Drootprefix=<INSTALL_DIR> -Dstatic-libsystemd=pic -Dselinux=false <SOURCE_DIR> <BINARY_DIR> ${SDBUSCPP_LIBSYSTEMD_EXTRA_CONFIG_OPTS}
BUILD_COMMAND ${BUILD_VERSION_H}
COMMAND ${NINJA} -C <BINARY_DIR> libsystemd.a
BUILD_ALWAYS 0
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/src/systemd <INSTALL_DIR>/include/systemd
LOG_DOWNLOAD 1 LOG_UPDATE 1 LOG_CONFIGURE 1 LOG_BUILD 1
BUILD_BYPRODUCTS <BINARY_DIR>/libsystemd.a)
ExternalProject_Get_property(LibsystemdBuildProject SOURCE_DIR)
ExternalProject_Get_property(LibsystemdBuildProject BINARY_DIR)
ExternalProject_Get_property(LibsystemdBuildProject INSTALL_DIR)
add_library(Systemd::Libsystemd STATIC IMPORTED)
set_target_properties(Systemd::Libsystemd PROPERTIES IMPORTED_LOCATION ${BINARY_DIR}/libsystemd.a)
file(MAKE_DIRECTORY ${INSTALL_DIR}/include/systemd) # Trick for CMake to stop complaining about non-existent ${INSTALL_DIR}/include directory
target_include_directories(Systemd::Libsystemd INTERFACE ${INSTALL_DIR}/include)
target_link_libraries(Systemd::Libsystemd INTERFACE ${CAP_LIBRARIES} ${GLIBC_RT_LIBRARY} ${MOUNT_LIBRARIES})

View File

@ -0,0 +1,4 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake")
check_required_components("@PROJECT_NAME@")

View File

@ -1,62 +0,0 @@
AC_PREREQ(2.61)
# package version number (not shared library version)
# odd micro numbers indicate in-progress development
# even micro numbers indicate released versions
m4_define(sdbus_cpp_version_major, 0)
m4_define(sdbus_cpp_version_minor, 2)
m4_define(sdbus_cpp_version_micro, 6)
m4_define([sdbus_cpp_version],
[sdbus_cpp_version_major.sdbus_cpp_version_minor.sdbus_cpp_version_micro])
m4_define([sdbus_cpp_api_version],
[sdbus_cpp_version_major.sdbus_cpp_version_minor])
AC_INIT(libsdbus-c++, sdbus_cpp_version)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS(config.h)
# Checks for programs.
AC_PROG_LIBTOOL
AC_PROG_CXX
AC_PROG_CC
AC_PROG_INSTALL
# enable pkg-config
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(SYSTEMD, [libsystemd >= 0.10.1],,
AC_MSG_ERROR([You need the libsystemd library (version 0.10.1 or better)]
[https://www.freedesktop.org/wiki/Software/systemd/])
)
# Checks for library functions.
#AC_CHECK_FUNCS([memset])
AC_SUBST(libsdbus_cpp_CFLAGS)
AC_SUBST(libsdbus_cpp_LIBS)
AC_ARG_ENABLE([tests],
[AS_HELP_STRING([--enable-tests],
[build and install tests @<:@default=yes@:>@])],
[],
[enable_tests=yes])
AM_CONDITIONAL([ENABLE_TESTS], [test x$enable_tests = xyes])
#icondir=${datadir}/icons/hicolor/32x32/apps
#AC_SUBST(icondir)
AC_OUTPUT([
Makefile
include/Makefile
src/Makefile
test/Makefile
sdbus-c++.pc
])
echo ""
echo " sdbus-cpp $VERSION"
echo " ====================="
echo ""
echo " To build the project, run \"make\""
echo ""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,748 +0,0 @@
Using sdbus-c++ library
=======================
**Table of contents**
1. [Introduction](#introduction)
2. [Integrating sdbus-c++ into your project](#integrating-sdbus-c-into-your-project)
3. [Header files and namespaces](#header-files-and-namespaces)
4. [Error signalling and propagation](#error-signalling-and-propagation)
5. [Multiple layers of sdbus-c++ API](#multiple-layers-of-sdbus-c-api)
6. [An example: Number concatenator](#an-example-number-concatenator)
7. [Implementing the Concatenator example using basic sdbus-c++ API layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer)
8. [Implementing the Concatenator example using convenience sdbus-c++ API layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer)
9. [Implementing the Concatenator example using sdbus-c++-generated stubs](#implementing-the-concatenator-example-using-sdbus-c-generated-stubs)
10. [Using D-Bus properties](#using-d-bus-properties)
11. [Conclusion](#conclusion)
Introduction
------------
sdbus-c++ is a C++ wrapper library built on top of [sd-bus](http://0pointer.net/blog/the-new-sd-bus-api-of-systemd.html), a lightweight D-Bus client
library implemented within systemd project. It provides D-Bus functionality on a higher level of abstraction, trying to employ C++ type system
to shift as much work as possible from the developer to the compiler.
sdbus-c++ does not cover the entire sd-bus API, but provides tools for implementing the most common functionality - RPC
method calls, signals and properties. There is room for additions and improvements, as needed and when needed.
Integrating sdbus-c++ into your project
---------------------------------------
The library build system is based on Autotools. The library supports `pkg-config`, so integrating it into your autotools project
is a two step process.
1. Add `PKG_CHECK_MODULES` macro into your `configure.ac`:
```bash
PKG_CHECK_MODULES(SDBUSCPP, [sdbus-c++ >= 0.1],,
AC_MSG_ERROR([You need the sdbus-c++ library (version 0.1 or better)]
[http://www.kistler.com/])
)
```
2. Update `*_CFLAGS` and `*_LDFLAGS` in Makefiles of modules that use *sdbus-c++*, for example like this:
```bash
AM_CXXFLAGS = -std=c++17 -pedantic -W -Wall @SDBUSCPP_CFLAGS@ ...
AM_LDFLAGS = @SDBUSCPP_LIBS@ ...
```
Note: sdbus-c++ library depends on C++17, since it uses C++17 `std::uncaught_exceptions()` feature. When building sdbus-c++ manually, make sure you use a compiler that supports that feature. To use the library, make sure you have a C++ standard library that supports the feature. The feature is supported by e.g. gcc >= 6, and clang >= 3.7.
Header files and namespaces
---------------------------
All sdbus-c++ header files reside in the `sdbus-c++` subdirectory within the standard include directory. Users can either include
individual header files, like so:
```cpp
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IObjectProxy.h>
```
or just include the global header file that pulls in everything:
```cpp
#include <sdbus-c++/sdbus-c++.h>
```
All public types and functions of sdbus-c++ reside in the `sdbus` namespace.
Error signalling and propagation
--------------------------------
`sdbus::Error` exception is used to signal errors in sdbus-c++. There are two types of errors:
* D-Bus related errors, like call timeouts, failed socket allocation, etc.
* user errors, i.e. errors signalled and propagated from remote methods back to the caller.
The exception object carries the error name and error message with it.
sdbus-c++ design
----------------
The following diagram illustrates the major entities in sdbus-c++.
![class](sdbus-c++-class-diagram.png)
`IConnection` represents the concept of a D-Bus connection. You can connect to either the system bus or a session bus. Services can assign unique service names to those connections. A processing loop can be run on the connection.
`IObject` represents the concept of an object that exposes its methods, signals and properties. Its responsibilities are:
* registering (possibly multiple) interfaces and methods, signals, properties on those interfaces,
* emitting signals.
`IObjectProxy` represents the concept of the proxy, which is a view of the `Object` from the client side. Its responsibilities are:
* invoking remote methods of the corresponding object,
* registering handlers for signals.
`Message` class represents a message, which is the fundamental DBus concept. The message can be
* a method call (with serialized parameters),
* a method reply (with serialized return values),
* or a signal (with serialized parameters).
Multiple layers of sdbus-c++ API
-------------------------------
sdbus-c++ API comes in two layers:
* [the basic layer](#implementing-the-concatenator-example-using-basic-sdbus-c-api-layer), which is a simple wrapper layer on top of sd-bus, using mechanisms that are native to C++ (e.g. serialization/deserialization of data from messages),
* [the convenience layer](#implementing-the-concatenator-example-using-convenience-sdbus-c-api-layer), building on top of the basic layer, which aims at alleviating users from unnecessary details and enables them to write shorter, safer, and more expressive code.
sdbus-c++ also ships with a stub generator tool that converts D-Bus IDL in XML format into stub code for the adaptor as well as proxy part. Hierarchically, these stubs provide yet another layer of convenience (the "stubs layer"), making it possible for D-Bus RPC calls to completely look like native C++ calls on a local object.
An example: Number concatenator
-------------------------------
Let's have an object `/org/sdbuscpp/concatenator` that implements the `org.sdbuscpp.concatenator` interface. The interface exposes the following:
* a `concatenate` method that takes a collection of integers and a separator string and returns a string that is the concatenation of all
integers from the collection using given separator,
* a `concatenated` signal that is emitted at the end of each successful concatenation.
In the following sections, we will elaborate on the ways of implementing such an object on both the server and the client side.
Implementing the Concatenator example using basic sdbus-c++ API layer
---------------------------------------------------------------------
In the basic API layer, we already have abstractions for D-Bus connections, objects and object proxies, with which we can interact via
interfaces. However, we still work with the concept of messages. To issue a method call for example, we have to go through several steps:
we have to create a method call message first, serialize method arguments into the message, and send the message at last. We get the reply
message (if applicable) in return, so we have to deserialize the return values from it manually.
Overloaded versions of C++ insertion/extraction operators are used for serialization/deserialization. That makes the client code much simpler.
### Server side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
// Yeah, global variable is ugly, but this is just an example and we want to access
// the concatenator instance from within the concatenate method handler to be able
// to emit signals.
sdbus::IObject* g_concatenator{};
void concatenate(sdbus::Message& msg, sdbus::Message& reply)
{
// Deserialize the collection of numbers from the message
std::vector<int> numbers;
msg >> numbers;
// Deserialize separator from the message
std::string separator;
msg >> separator;
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Serialize resulting string to the reply
reply << result;
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
auto signalMsg = g_concatenator->createSignal(interfaceName, "concatenated");
signalMsg << result;
g_concatenator->emitSignal(signalMsg);
}
int main(int argc, char *argv[])
{
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenator = sdbus::createObject(*connection, objectPath);
g_concatenator = concatenator.get();
// Register D-Bus methods and signals on the concatenator object, and exports the object.
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenator->registerMethod(interfaceName, "concatenate", "ais", "s", &concatenate);
concatenator->registerSignal(interfaceName, "concatenated", "s");
concatenator->finishRegistration();
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
### Client side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
void onConcatenated(sdbus::Message& signalMsg)
{
std::string concatenatedString;
msg >> concatenatedString;
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side. Since we don't pass
// the D-Bus connection object to the proxy constructor, the proxy will internally create
// its own connection to the system bus.
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->registerSignalHandler(interfaceName, "concatenated", &onConcatenated);
concatenatorProxy->finishRegistration();
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate on given interface of the object
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << numbers << separator;
auto reply = concatenatorProxy->callMethod(method);
std::string result;
reply >> result;
assert(result == "1:2:3");
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
auto method = concatenatorProxy->createMethodCall(interfaceName, "concatenate");
method << std::vector<int>() << separator;
try
{
auto reply = concatenatorProxy->callMethod(method);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
The object proxy is created without explicitly providing a D-Bus connection as an argument in its factory function. In that case, the proxy
will create its own connection to the *system* bus and listen to signals on it in a separate thread. That means the `onConcatenated` method is invoked always
in the context of a thread different from the main thread.
Implementing the Concatenator example using convenience sdbus-c++ API layer
---------------------------------------------------------------------------
One of the major sdbus-c++ design goals is to make the sdbus-c++ API easy to use correctly, and hard to use incorrectly.
The convenience API layer abstracts the concept of underlying D-Bus messages away completely. It abstracts away D-Bus signatures. And it tries
to provide an interface that uses small, focused functions, with one or zero parameters, to form a chained function statement that reads like
a sentence to a human reading the code. To achieve that, sdbus-c++ utilizes the power of the C++ type system, so many issues are resolved at
compile time, and the run-time performance cost compared to the basic layer is close to zero.
Thus, in the end of the day, the code written using the convenience API is:
- more expressive,
- closer to the abstraction level of the problem being solved,
- shorter,
- almost as fast (if not equally fast) as one written using the basic API layer.
Rather than *how*, the code written using this layer expresses *what* it does. Let's look at code samples to see if you agree :)
### Server side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
// Yeah, global variable is ugly, but this is just an example and we want to access
// the concatenator instance from within the concatenate method handler to be able
// to emit signals.
sdbus::IObject* g_concatenator{};
std::string concatenate(const std::vector<int> numbers, const std::string& separator)
{
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Emit 'concatenated' signal
const char* interfaceName = "org.sdbuscpp.Concatenator";
g_concatenator->emitSignal("concatenated").onInterface(interfaceName).withArguments(result);
return result;
}
int main(int argc, char *argv[])
{
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenator = sdbus::createObject(*connection, objectPath);
g_concatenator = concatenator.get();
// Register D-Bus methods and signals on the concatenator object, and exports the object.
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenator->registerMethod("concatenate").onInterface(interfaceName).implementedAs(&concatenate);
concatenator->registerSignal("concatenated").onInterface(interfaceName).withParameters<std::string>();
concatenator->finishRegistration();
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
### Client side
```c++
#include <sdbus-c++/sdbus-c++.h>
#include <vector>
#include <string>
#include <iostream>
#include <unistd.h>
void onConcatenated(const std::string& concatenatedString)
{
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
auto concatenatorProxy = sdbus::createObjectProxy(destinationName, objectPath);
// Let's subscribe for the 'concatenated' signals
const char* interfaceName = "org.sdbuscpp.Concatenator";
concatenatorProxy->uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& str){ onConcatenated(str); });
concatenatorProxy->finishRegistration();
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate on given interface of the object
{
std::string concatenatedString;
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(concatenatedString);
assert(concatenatedString == "1:2:3");
}
// Invoke concatenate again, this time with no numbers and we shall get an error
{
try
{
concatenatorProxy->callMethod("concatenate").onInterface(interfaceName).withArguments(std::vector<int>(), separator);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
Several lines of code have shrunk into one-liners when registering/calling methods or signals. D-Bus signatures and the serialization/deserialization
of arguments from the messages is generated at compile time, by introspecting signatures of provided callbacks or deducing types of provided arguments.
Implementing the Concatenator example using sdbus-c++-generated stubs
---------------------------------------------------------------------
sdbus-c++ ships with the native stub generator tool called sdbuscpp-xml2cpp. The tool is very similar to dbusxx-xml2cpp tool that comes from
dbus-c++ project.
The generator tool takes D-Bus XML IDL description of D-Bus interfaces on its input, and can be instructed to generate one or both of these:
an adaptor header file for use at server side, and a proxy header file for use at client side. Like this:
```bash
sdbuscpp-xml2cpp database-bindings.xml --adaptor=database-server-glue.h --proxy=database-client-glue.h
```
The adaptor header file contains classes that can be used to implement described interfaces. The proxy header file contains classes that can be used
to make calls to remote objects.
### XML description of the Concatenator interface
As an example, let's look at an XML description of our Concatenator's interfaces.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/concatenator">
<interface name="org.sdbuscpp.Concatenator">
<method name="concatenate">
<arg type="ai" name="numbers" direction="in" />
<arg type="s" name="separator" direction="in" />
<arg type="s" name="concatenatedString" direction="out" />
</method>
<signal name="concatenated">
<arg type="s" name="concatenatedString" />
</signal>
</interface>
</node>
```
After running this through the stubs generator, we get the stub code that is described in the following two subsections.
### concatenator-server-glue.h
There is specific class for each interface in the XML IDL file. The class is de facto an interface which shall be implemented by inheriting from it.
The class' constructor takes care of registering all methods, signals and properties. For each D-Bus method there is a pure virtual member function.
These pure virtual functions must be implemented in the child class. For each signal, there is a public function member that emits this signal.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_server_glue_h__adaptor__H__
#define __sdbuscpp__concatenator_server_glue_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class Concatenator_adaptor
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator";
protected:
Concatenator_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerMethod("concatenate").onInterface(interfaceName).implementedAs([this](const std::vector<int32_t>& numbers, const std::string& separator){ return this->concatenate(numbers, separator); });
object_.registerSignal("concatenated").onInterface(interfaceName).withParameters<std::string>();
}
public:
void concatenated(const std::string& concatenatedString)
{
object_.emitSignal("concatenated").onInterface(interfaceName).withArguments(concatenatedString);
}
private:
virtual std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator) = 0;
private:
sdbus::IObject& object_;
};
}} // namespaces
#endif
```
### concatenator-client-glue.h
Analogously to the adaptor classes described above, there is specific class for each interface in the XML IDL file. The class is de facto a proxy
to the concrete interface of a remote object. For each D-Bus signal there is a pure virtual member function whose body must be provided in a child
class. For each method, there is a public function member that calls the method remotely.
```cpp
/*
* This file was automatically generated by sdbuscpp-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__concatenator_client_glue_h__proxy__H__
#define __sdbuscpp__concatenator_client_glue_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
class Concatenator_proxy
{
public:
static constexpr const char* interfaceName = "org.sdbuscpp.Concatenator";
protected:
Concatenator_proxy(sdbus::IObjectProxy& object)
: object_(object)
{
object_.uponSignal("concatenated").onInterface(interfaceName).call([this](const std::string& concatenatedString){ this->onConcatenated(concatenatedString); });
}
virtual void onConcatenated(const std::string& concatenatedString) = 0;
public:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator)
{
std::string result;
object_.callMethod("concatenate").onInterface(interfaceName).withArguments(numbers, separator).storeResultsTo(result);
return result;
}
private:
sdbus::IObjectProxy& object_;
};
}} // namespaces
#endif
```
### Providing server implementation based on generated adaptors
To implement a D-Bus object that implements all its D-Bus interfaces, we shall create a class representing the object that inherits from all
corresponding `*_adaptor` classes and implements all pure virtual member functions. Specifically, the object class shall inherit from the
`Interfaces` template class, the template arguments of which are individual adaptor classes. The `Interfaces` is just a convenience class that
hides a few boiler-plate details. For example, in its constructor, it creates an `Object` instance, it takes care of proper initialization of
all adaptor superclasses, and exports the object finally.
```cpp
#include <sdbus-c++/sdbus-c++.h>
#include "concatenator-server-glue.h"
class Concatenator : public sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor /*, more adaptor classes if there are more interfaces*/>
{
public:
Concatenator(sdbus::IConnection& connection, std::string objectPath)
: sdbus::Interfaces<org::sdbuscpp::Concatenator_adaptor>(connection, std::move(objectPath))
{
}
protected:
std::string concatenate(const std::vector<int32_t>& numbers, const std::string& separator) override
{
// Return error if there are no numbers in the collection
if (numbers.empty())
throw sdbus::Error("org.sdbuscpp.Concatenator.Error", "No numbers provided");
// Concatenate the numbers
std::string result;
for (auto number : numbers)
{
result += (result.empty() ? std::string() : separator) + std::to_string(number);
}
// Emit the 'concatenated' signal with the resulting string
concatenated(result);
// Return the resulting string
return result;
}
};
```
That's it. We now have an implementation of a D-Bus object implementing `org.sdbuscpp.Concatenator` interface. Let's now create a service
publishing the object.
```cpp
#include "Concatenator.h"
int main(int argc, char *argv[])
{
// Create D-Bus connection to the system bus and requests name on it.
const char* serviceName = "org.sdbuscpp.concatenator";
auto connection = sdbus::createSystemBusConnection(serviceName);
// Create concatenator D-Bus object.
const char* objectPath = "/org/sdbuscpp/concatenator";
Concatenator concatenator(*connection, objectPath);
// Run the loop on the connection.
connection->enterProcessingLoop();
}
```
It's that simple!
### Providing client implementation based on generated proxies
To implement a proxy for a remote D-Bus object, we shall create a class representing the object proxy that inherits from all corresponding
`*_proxy` classes and -- if applicable -- implements all pure virtual member functions. Specifically, the object proxy class shall inherit
from the `ProxyInterfaces` template class. As its template arguments we shall provide all proxy classes. The `ProxyInterfaces` is just a
convenience class that hides a few boiler-plate details. For example, in its constructor, it creates an `ObjectProxy` instance, and it takes
care of proper initialization of all proxy superclasses.
```cpp
#include <sdbus-c++/sdbus-c++.h>
#include "concatenator-client-glue.h"
class ConcatenatorProxy : public sdbus::ProxyInterfaces<org::sdbuscpp::Concatenator_proxy /*, more proxy classes if there are more interfaces*/>
{
public:
ConcatenatorProxy(std::string destination, std::string objectPath)
: sdbus::ProxyInterfaces<org::sdbuscpp::Concatenator_proxy>(std::move(destination), std::move(objectPath))
{
}
protected:
void onConcatenated(const std::string& concatenatedString) override
{
std::cout << "Received signal with concatenated string " << concatenatedString << std::endl;
}
};
```
Now let's use this proxy to make remote calls and listen to signals in a real application.
```cpp
#include "ConcatenatorProxy.h"
int main(int argc, char *argv[])
{
// Create proxy object for the concatenator object on the server side
const char* destinationName = "org.sdbuscpp.concatenator";
const char* objectPath = "/org/sdbuscpp/concatenator";
ConcatenatorProxy concatenatorProxy(destinationName, objectPath);
std::vector<int> numbers = {1, 2, 3};
std::string separator = ":";
// Invoke concatenate with some numbers
auto concatenatedString = concatenatorProxy.concatenate(numbers, separator);
assert(concatenatedString == "1:2:3");
// Invoke concatenate again, this time with no numbers and we shall get an error
try
{
auto concatenatedString = concatenatorProxy.concatenate(std::vector<int>(), separator);
assert(false);
}
catch(const sdbus::Error& e)
{
std::cerr << "Got concatenate error " << e.getName() << " with message " << e.getMessage() << std::endl;
}
// Give sufficient time to receive 'concatenated' signal from the first concatenate invocation
sleep(1);
return 0;
}
```
Using D-Bus properties
----------------------
Defining and working with D-Bus properties using XML description is quite easy.
### Defining a property in the XML
A property element has no arg child element. It just has the attributes name, type and access, which are all mandatory. The access attribute allows the values readwrite, read, and write.
An example of a read-write property `status`:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<node name="/org/sdbuscpp/propertyprovider">
<interface name="org.sdbuscpp.PropertyProvider">
<!--...-->
<property name="status" type="u" access="readwrite"/>
<!--...-->
</interface>
</node>
```
### Generated stubs
This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor:
```cpp
class PropertyProvider_adaptor
{
/*...*/
public:
PropertyProvider_adaptor(sdbus::IObject& object)
: object_(object)
{
object_.registerProperty("status").onInterface(INTERFACE_NAME).withGetter([this](){ return this->status(); }).withSetter([this](const uint32_t& value){ this->status(value); });
}
private:
// property getter
virtual uint32_t status() = 0;
// property setter
virtual void status(const uint32_t& value) = 0;
/*...*/
};
#endif
```
The proxy:
```cpp
class PropertyProvider_proxy
{
/*...*/
public:
// getting the property value
uint32_t status()
{
return object_.getProperty("status").onInterface(INTERFACE_NAME);
}
// setting the property value
void status(const uint32_t& value)
{
object_.setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
}
/*...*/
};
```
When implementing the adaptor, we simply need to provide the body for `status` getter and setter method by overriding them. Then in the proxy, we just call them.
Conclusion
----------
There is no conclusion. Happy journeys by D-Bus with sdbus-c++!

33
docs/CMakeLists.txt Normal file
View File

@ -0,0 +1,33 @@
# Building doxygen documentation
find_package(Doxygen)
if(SDBUSCPP_BUILD_DOXYGEN_DOCS)
if(DOXYGEN_FOUND)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
add_custom_target(doc
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM)
# workaround bug https://github.com/doxygen/doxygen/pull/6787
add_custom_command(TARGET doc POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/sdbus-c++-class-diagram.png ${CMAKE_CURRENT_BINARY_DIR}/html/.)
else()
message(WARNING "Documentation enabled, but Doxygen cannot be found")
endif()
endif()
if(SDBUSCPP_INSTALL)
install(FILES sdbus-c++-class-diagram.png
sdbus-c++-class-diagram.uml
systemd-dbus-config.md
using-sdbus-c++.md
DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT sdbus-c++-doc)
if (SDBUSCPP_BUILD_DOXYGEN_DOCS AND DOXYGEN_FOUND)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR} OPTIONAL COMPONENT sdbus-c++-doc)
endif()
endif()

2496
docs/Doxyfile.in Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -12,9 +12,10 @@ interface IObject {
+emitSignal()
}
interface IObjectProxy {
interface IProxy {
+callMethod()
+subscribeToSignal()
+get/setProperty()
}
class Message {
@ -39,21 +40,24 @@ class Object {
string objectPath
List interfaces
List methods
List signals
List properties
}
class ObjectProxy {
class Proxy {
IConnectionInternal& connection
string destination
string objectPath
List signalHandlers
}
IConnection <|-- Connection
IConnectionInternal <|- Connection
IObject <|-- Object
IObjectProxy <|-- ObjectProxy
IProxy <|-- Proxy
Connection <-- Object : "use"
Connection <-- ObjectProxy : "use"
Connection <-- Proxy : "use"
Message <.. Object : "send/receive"
Message <.. ObjectProxy : "send/receive"
Message <.. Proxy : "send/receive"
@enduml

View File

@ -0,0 +1,50 @@
Systemd and D-Bus configuration
=======================
**Table of contents**
1. [Introduction](#introduction)
2. [Systemd configuration](#systemd-configuration)
3. [Dbus configuration](#dbus-configuration)
Introduction
------------
To run executable as a systemd service you may need some additional setup. For example, you may need explicitly allow the usage of your service. Following chapters contain template configurations.
Systemd configuration
---------------------------------------
Filename should use `.service` extension. It also must be placed in configuration directory (/etc/systemd/system in Ubuntu 18.04.1 LTS)
```
[Unit]
Description=nameOfService
[Service]
ExecStart=/path/to/executable
[Install]
WantedBy=multi-user.target
```
D-Bus configuration
------------------
Typical default D-Bus configuration does not allow to register services except explicitly allowed. To allow a service to register its D-Bus API, we must place an appropriate conf file in `/etc/dbus-1/system.d/` directory. The conf file name must be `<service-name>.conf`. I.e., full file path for Concatenator example from sdbus-c++ tutorial would be `/etc/dbus-1/system.d/org.sdbuscpp.concatenator.conf`. And here is template configuration to use its D-Bus interface under root:
```
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.sdbuscpp.concatenator"/>
<allow send_destination="org.sdbuscpp.concatenator"/>
<allow send_interface="org.sdbuscpp.concatenator"/>
</policy>
</busconfig>
```
If you need access from other user then `root` should be substituted by desired username. Or you can simply use policy `<policy context="default">` like [conf file](/tests/integrationtests/files/org.sdbuscpp.integrationtests.conf) for sdbus-c++ integration tests is doing it. For more information refer to `man dbus-daemon`.

1894
docs/using-sdbus-c++.md Normal file

File diff suppressed because it is too large Load Diff

12
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
# Building examples
add_executable(obj-manager-server org.freedesktop.DBus.ObjectManager/obj-manager-server.cpp)
target_link_libraries(obj-manager-server sdbus-c++)
add_executable(obj-manager-client org.freedesktop.DBus.ObjectManager/obj-manager-client.cpp)
target_link_libraries(obj-manager-client sdbus-c++)
if(SDBUSCPP_INSTALL)
install(TARGETS obj-manager-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples)
install(TARGETS obj-manager-client DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT sdbus-c++-examples)
endif()

View File

@ -0,0 +1,59 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__examplemanager_planet1_client_glue_h__proxy__H__
#define __sdbuscpp__examplemanager_planet1_client_glue_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace ExampleManager {
class Planet1_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1";
protected:
Planet1_proxy(sdbus::IProxy& proxy)
: m_proxy(proxy)
{
}
Planet1_proxy(const Planet1_proxy&) = delete;
Planet1_proxy& operator=(const Planet1_proxy&) = delete;
Planet1_proxy(Planet1_proxy&&) = delete;
Planet1_proxy& operator=(Planet1_proxy&&) = delete;
~Planet1_proxy() = default;
void registerProxy()
{
}
public:
uint64_t GetPopulation()
{
uint64_t result;
m_proxy.callMethod("GetPopulation").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
public:
std::string Name()
{
return m_proxy.getProperty("Name").onInterface(INTERFACE_NAME).get<std::string>();
}
private:
sdbus::IProxy& m_proxy;
};
}}} // namespaces
#endif

View File

@ -0,0 +1,54 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__examplemanager_planet1_server_glue_h__adaptor__H__
#define __sdbuscpp__examplemanager_planet1_server_glue_h__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace sdbuscpp {
namespace ExampleManager {
class Planet1_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.sdbuscpp.ExampleManager.Planet1";
protected:
Planet1_adaptor(sdbus::IObject& object)
: m_object(object)
{
}
Planet1_adaptor(const Planet1_adaptor&) = delete;
Planet1_adaptor& operator=(const Planet1_adaptor&) = delete;
Planet1_adaptor(Planet1_adaptor&&) = delete;
Planet1_adaptor& operator=(Planet1_adaptor&&) = delete;
~Planet1_adaptor() = default;
void registerAdaptor()
{
m_object.addVTable( sdbus::registerMethod("GetPopulation").withOutputParamNames("population").implementedAs([this](){ return this->GetPopulation(); })
, sdbus::registerProperty("Name").withGetter([this](){ return this->Name(); })
).forInterface(INTERFACE_NAME);
}
private:
virtual uint64_t GetPopulation() = 0;
private:
virtual std::string Name() = 0;
private:
sdbus::IObject& m_object;
};
}}} // namespaces
#endif

View File

@ -0,0 +1,111 @@
/**
* Example of a D-Bus client which implements org.freedesktop.DBus.ObjectManager
*
* The example uses the generated stub API layer to listen to interfaces added to new objects under
* "org.sdbuscpp.examplemanager". If added, we access "org.sdbuscpp.ExampleManager.Planet1" to print
* info like this:
* /org/sdbuscpp/examplemanager/Planet1/Earth added: org.sdbuscpp.ExampleManager.Planet1
* Earth has a population of 7874965825.
*
*/
#include "examplemanager-planet1-client-glue.h"
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <thread>
class PlanetProxy final : public sdbus::ProxyInterfaces< org::sdbuscpp::ExampleManager::Planet1_proxy >
{
public:
PlanetProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path)
: ProxyInterfaces(connection, std::move(destination), std::move(path))
{
registerProxy();
}
~PlanetProxy()
{
unregisterProxy();
}
};
class ManagerProxy final : public sdbus::ProxyInterfaces<sdbus::ObjectManager_proxy>
{
public:
ManagerProxy(sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path)
: ProxyInterfaces(connection, destination, std::move(path))
, m_connection(connection)
, m_destination(destination)
{
registerProxy();
}
~ManagerProxy()
{
unregisterProxy();
}
void handleExistingObjects()
{
auto objectsInterfacesAndProperties = GetManagedObjects();
for (const auto& [object, interfacesAndProperties] : objectsInterfacesAndProperties) {
onInterfacesAdded(object, interfacesAndProperties);
}
}
private:
void onInterfacesAdded( const sdbus::ObjectPath& objectPath
, const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties) override
{
std::cout << objectPath << " added:\t";
for (const auto& [interface, _] : interfacesAndProperties) {
std::cout << interface << " ";
}
std::cout << std::endl;
// Parse and print some more info
auto planetInterface = interfacesAndProperties.find(sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_proxy::INTERFACE_NAME});
if (planetInterface == interfacesAndProperties.end()) {
return;
}
const auto& properties = planetInterface->second;
// get a property which was passed as part of the signal.
const auto& name = properties.at(sdbus::PropertyName{"Name"}).get<std::string>();
// or create a proxy instance to the newly added object.
PlanetProxy planet(m_connection, m_destination, objectPath);
std::cout << name << " has a population of " << planet.GetPopulation() << ".\n" << std::endl;
}
void onInterfacesRemoved( const sdbus::ObjectPath& objectPath
, const std::vector<sdbus::InterfaceName>& interfaces) override
{
std::cout << objectPath << " removed:\t";
for (const auto& interface : interfaces) {
std::cout << interface << " ";
}
std::cout << std::endl;
}
sdbus::IConnection& m_connection;
sdbus::ServiceName m_destination;
};
int main()
{
auto connection = sdbus::createSessionBusConnection();
sdbus::ServiceName destination{"org.sdbuscpp.examplemanager"};
sdbus::ObjectPath objectPath{"/org/sdbuscpp/examplemanager"};
auto managerProxy = std::make_unique<ManagerProxy>(*connection, std::move(destination), std::move(objectPath));
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;
}
}
connection->enterEventLoop();
return 0;
}

View File

@ -0,0 +1,111 @@
/**
* Example of a D-Bus server which implements org.freedesktop.DBus.ObjectManager
*
* The example uses the generated stub API layer to register an object manager under "org.sdbuscpp.examplemanager"
* and add objects underneath which implement "org.sdbuscpp.ExampleManager.Planet1".
*
* We add and remove objects after a few seconds and print info like this:
* Creating PlanetAdaptor in 5 4 3 2 1
* Creating PlanetAdaptor in 5 4 3 2 1
* Creating PlanetAdaptor in 5 4 3 2 1
* Removing PlanetAdaptor in 5 4 3 2 1
* Removing PlanetAdaptor in 5 4 3 2 1
*/
#include "examplemanager-planet1-server-glue.h"
#include <sdbus-c++/sdbus-c++.h>
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
using sdbus::ObjectPath;
class ManagerAdaptor : public sdbus::AdaptorInterfaces<sdbus::ObjectManager_adaptor>
{
public:
ManagerAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path)
: AdaptorInterfaces(connection, std::move(path))
{
registerAdaptor();
}
~ManagerAdaptor()
{
unregisterAdaptor();
}
};
class PlanetAdaptor final : public sdbus::AdaptorInterfaces< org::sdbuscpp::ExampleManager::Planet1_adaptor
, sdbus::ManagedObject_adaptor
, sdbus::Properties_adaptor >
{
public:
PlanetAdaptor(sdbus::IConnection& connection, sdbus::ObjectPath path, std::string name, uint64_t population)
: AdaptorInterfaces(connection, std::move(path))
, m_name(std::move(name))
, m_population(population)
{
registerAdaptor();
emitInterfacesAddedSignal({sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}});
}
~PlanetAdaptor()
{
emitInterfacesRemovedSignal({sdbus::InterfaceName{org::sdbuscpp::ExampleManager::Planet1_adaptor::INTERFACE_NAME}});
unregisterAdaptor();
}
uint64_t GetPopulation() override
{
return m_population;
}
std::string Name() override
{
return m_name;
}
private:
std::string m_name;
uint64_t m_population;
};
void printCountDown(const std::string& message, int seconds)
{
std::cout << message << std::flush;
for (int i = seconds; i > 0; i--) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << i << " " << std::flush;
}
std::cout << std::endl;
}
int main()
{
auto connection = sdbus::createSessionBusConnection();
sdbus::ServiceName serviceName{"org.sdbuscpp.examplemanager"};
connection->requestName(serviceName);
connection->enterEventLoopAsync();
auto manager = std::make_unique<ManagerAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager"});
while (true)
{
printCountDown("Creating PlanetAdaptor in ", 5);
auto earth = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Earth"}, "Earth", 7'874'965'825);
printCountDown("Creating PlanetAdaptor in ", 5);
auto trantor = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Trantor"}, "Trantor", 40'000'000'000);
printCountDown("Creating PlanetAdaptor in ", 5);
auto laconia = std::make_unique<PlanetAdaptor>(*connection, ObjectPath{"/org/sdbuscpp/examplemanager/Planet1/Laconia"}, "Laconia", 231'721);
printCountDown("Removing PlanetAdaptor in ", 5);
earth.reset();
printCountDown("Removing PlanetAdaptor in ", 5);
trantor.reset();
printCountDown("Removing PlanetAdaptor in ", 5);
laconia.reset();
}
connection->releaseName(serviceName);
connection->leaveEventLoop();
return 0;
}

View File

@ -0,0 +1,20 @@
<?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>
<interface name="org.sdbuscpp.ExampleManager.Planet1">
<!--
@brief get the population of this planet
@param [out] population of the planet
-->
<method name="GetPopulation">
<arg name="population" type="t" direction="out" />
</method>
<!--
@brief This planet's name
-->
<property name="Name" type="s" access="read"/>
</interface>
</node>

View File

@ -1,23 +0,0 @@
# Distribution
# Headers will be installed to $(includedir)/sdbus-c++ directory
HEADER_DIR = sdbus-c++
libsdbuscppdir = $(includedir)/$(HEADER_DIR)
libsdbuscpp_HEADERS = \
$(HEADER_DIR)/ConvenienceClasses.h \
$(HEADER_DIR)/ConvenienceClasses.inl \
$(HEADER_DIR)/Error.h \
$(HEADER_DIR)/IConnection.h \
$(HEADER_DIR)/Interfaces.h \
$(HEADER_DIR)/Introspection.h \
$(HEADER_DIR)/IObject.h \
$(HEADER_DIR)/IObjectProxy.h \
$(HEADER_DIR)/Message.h \
$(HEADER_DIR)/Types.h \
$(HEADER_DIR)/TypeTraits.h \
$(HEADER_DIR)/sdbus-c++.h
EXTRA_DIST = ($libsdbuscpp_HEADERS)
DISTCLEANFILES = Makefile Makefile.in

View File

@ -0,0 +1,152 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file AdaptorInterfaces.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ADAPTORINTERFACES_H_
#define SDBUS_CXX_ADAPTORINTERFACES_H_
#include <sdbus-c++/IObject.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class ObjectHolder
*
* ObjectHolder is a helper that simply owns and provides
* access to an object to other classes in the inheritance
* hierarchy of an object based on generated interface classes.
*
***********************************************/
class ObjectHolder
{
protected:
ObjectHolder(std::unique_ptr<IObject>&& object)
: object_(std::move(object))
{
}
const IObject& getObject() const
{
assert(object_ != nullptr);
return *object_;
}
IObject& getObject()
{
assert(object_ != nullptr);
return *object_;
}
private:
std::unique_ptr<IObject> object_;
};
/********************************************//**
* @class AdaptorInterfaces
*
* AdaptorInterfaces is a helper template class that joins all interface classes of a remote
* D-Bus object generated by sdbus-c++-xml2cpp to be used on the server (the adaptor) side,
* including some auxiliary classes. AdaptorInterfaces is the class that native-like object
* implementation classes written by users should inherit from and implement all pure virtual
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* adaptor-side interface classes representing interfaces (with methods, signals and properties)
* of the D-Bus object.
*
* In the final adaptor class inherited from AdaptorInterfaces, one needs to make sure:
* 1. to call `registerAdaptor();` in the class constructor, and, conversely,
* 2. to call `unregisterAdaptor();` in the class destructor,
* so that the object API vtable is registered and unregistered at the proper time.
*
***********************************************/
template <typename... _Interfaces>
class AdaptorInterfaces
: protected ObjectHolder
, public _Interfaces...
{
public:
/*!
* @brief Creates object instance
*
* @param[in] connection D-Bus connection where the object will publish itself
* @param[in] objectPath Path of the D-Bus object
*
* For more information, consult @ref createObject(sdbus::IConnection&,std::string)
*/
AdaptorInterfaces(IConnection& connection, ObjectPath objectPath)
: ObjectHolder(createObject(connection, std::move(objectPath)))
, _Interfaces(getObject())...
{
}
/*!
* @brief Adds object vtable (i.e. D-Bus API) definitions for all interfaces it implements
*
* This function must be called in the constructor of the final adaptor class that implements AdaptorInterfaces.
*
* See also @ref IObject::addVTable()
*/
void registerAdaptor()
{
(_Interfaces::registerAdaptor(), ...);
}
/*!
* @brief Unregisters adaptors's API and removes it from the bus
*
* This function must be called in the destructor of the final adaptor class that implements AdaptorInterfaces.
*
* For more information, see underlying @ref IObject::unregister()
*/
void unregisterAdaptor()
{
getObject().unregister();
}
/*!
* @brief Returns reference to the underlying IObject instance
*/
using ObjectHolder::getObject;
protected:
using base_type = AdaptorInterfaces;
AdaptorInterfaces(const AdaptorInterfaces&) = delete;
AdaptorInterfaces& operator=(const AdaptorInterfaces&) = delete;
AdaptorInterfaces(AdaptorInterfaces&&) = delete;
AdaptorInterfaces& operator=(AdaptorInterfaces&&) = delete;
~AdaptorInterfaces() = default;
};
}
#endif /* SDBUS_CXX_ADAPTORINTERFACES_H_ */

View File

@ -0,0 +1,290 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ConvenienceApiClasses.h
*
* Created on: Jan 19, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_CONVENIENCEAPICLASSES_H_
#define SDBUS_CXX_CONVENIENCEAPICLASSES_H_
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/VTableItems.h>
#include <chrono>
#include <cstdint>
#include <future>
#include <map>
#include <string>
#include <string_view>
#include <vector>
// Forward declarations
namespace sdbus {
class IObject;
class IProxy;
class Error;
class PendingAsyncCall;
}
namespace sdbus {
class VTableAdder
{
public:
void forInterface(InterfaceName interfaceName);
void forInterface(std::string interfaceName);
[[nodiscard]] Slot forInterface(InterfaceName interfaceName, return_slot_t);
[[nodiscard]] Slot forInterface(std::string interfaceName, return_slot_t);
private:
friend IObject;
VTableAdder(IObject& object, std::vector<VTableItem> vtable);
private:
IObject& object_;
std::vector<VTableItem> vtable_;
};
class SignalEmitter
{
public:
SignalEmitter& onInterface(const InterfaceName& interfaceName);
SignalEmitter& onInterface(const std::string& interfaceName);
SignalEmitter& onInterface(const char* interfaceName);
template <typename... _Args> void withArguments(_Args&&... args);
SignalEmitter(SignalEmitter&& other) = default;
~SignalEmitter() noexcept(false);
private:
friend IObject;
SignalEmitter(IObject& object, const SignalName& signalName);
SignalEmitter(IObject& object, const char* signalName);
private:
IObject& object_;
const char* signalName_;
Signal signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
};
class MethodInvoker
{
public:
MethodInvoker& onInterface(const InterfaceName& interfaceName);
MethodInvoker& onInterface(const std::string& interfaceName);
MethodInvoker& onInterface(const char* interfaceName);
MethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period>
MethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
void dontExpectReply();
MethodInvoker(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
private:
friend IProxy;
MethodInvoker(IProxy& proxy, const MethodName& methodName);
MethodInvoker(IProxy& proxy, const char* methodName);
private:
IProxy& proxy_;
const char* methodName_;
uint64_t timeout_{};
MethodCall method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{};
};
class AsyncMethodInvoker
{
public:
AsyncMethodInvoker& onInterface(const InterfaceName& interfaceName);
AsyncMethodInvoker& onInterface(const std::string& interfaceName);
AsyncMethodInvoker& onInterface(const char* interfaceName);
AsyncMethodInvoker& withTimeout(uint64_t usec);
template <typename _Rep, typename _Period>
AsyncMethodInvoker& withTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
template <typename... _Args> AsyncMethodInvoker& withArguments(_Args&&... args);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
// Returned future will be std::future<void> for no (void) D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
template <typename... _Args> std::future<future_return_t<_Args...>> getResultAsFuture();
private:
friend IProxy;
AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName);
AsyncMethodInvoker(IProxy& proxy, const char* methodName);
template <typename _Function> async_reply_handler makeAsyncReplyHandler(_Function&& callback);
private:
IProxy& proxy_;
const char* methodName_;
uint64_t timeout_{};
MethodCall method_;
};
class SignalSubscriber
{
public:
SignalSubscriber& onInterface(const InterfaceName& interfaceName);
SignalSubscriber& onInterface(const std::string& interfaceName);
SignalSubscriber& onInterface(const char* interfaceName);
template <typename _Function> void call(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot call(_Function&& callback, return_slot_t);
private:
friend IProxy;
SignalSubscriber(IProxy& proxy, const SignalName& signalName);
SignalSubscriber(IProxy& proxy, const char* signalName);
template <typename _Function> signal_handler makeSignalHandler(_Function&& callback);
private:
IProxy& proxy_;
const char* signalName_;
const char* interfaceName_{};
};
class PropertyGetter
{
public:
Variant onInterface(std::string_view interfaceName);
private:
friend IProxy;
PropertyGetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
};
class AsyncPropertyGetter
{
public:
AsyncPropertyGetter& onInterface(std::string_view interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<Variant> getResultAsFuture();
private:
friend IProxy;
AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
std::string_view interfaceName_;
};
class PropertySetter
{
public:
PropertySetter& onInterface(std::string_view interfaceName);
template <typename _Value> void toValue(const _Value& value);
template <typename _Value> void toValue(const _Value& value, dont_expect_reply_t);
void toValue(const Variant& value);
void toValue(const Variant& value, dont_expect_reply_t);
private:
friend IProxy;
PropertySetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
std::string_view interfaceName_;
};
class AsyncPropertySetter
{
public:
AsyncPropertySetter& onInterface(std::string_view interfaceName);
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
AsyncPropertySetter& toValue(Variant value);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<void> getResultAsFuture();
private:
friend IProxy;
AsyncPropertySetter(IProxy& proxy, std::string_view propertyName);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view propertyName_;
std::string_view interfaceName_;
Variant value_;
};
class AllPropertiesGetter
{
public:
std::map<PropertyName, Variant> onInterface(std::string_view interfaceName);
private:
friend IProxy;
AllPropertiesGetter(IProxy& proxy);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
};
class AsyncAllPropertiesGetter
{
public:
AsyncAllPropertiesGetter& onInterface(std::string_view interfaceName);
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
template <typename _Function> [[nodiscard]] Slot uponReplyInvoke(_Function&& callback, return_slot_t);
std::future<std::map<PropertyName, Variant>> getResultAsFuture();
private:
friend IProxy;
AsyncAllPropertiesGetter(IProxy& proxy);
static constexpr const char* DBUS_PROPERTIES_INTERFACE_NAME = "org.freedesktop.DBus.Properties";
private:
IProxy& proxy_;
std::string_view interfaceName_;
};
} // namespace sdbus
#endif /* SDBUS_CXX_CONVENIENCEAPICLASSES_H_ */

View File

@ -0,0 +1,718 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ConvenienceApiClasses.inl
*
* Created on: Dec 19, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
#include <sdbus-c++/Error.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <cassert>
#include <exception>
#include <string>
#include <tuple>
#include <type_traits>
namespace sdbus {
/*** ------------- ***/
/*** VTableAdder ***/
/*** ------------- ***/
inline VTableAdder::VTableAdder(IObject& object, std::vector<VTableItem> vtable)
: object_(object)
, vtable_(std::move(vtable))
{
}
inline void VTableAdder::forInterface(InterfaceName interfaceName)
{
object_.addVTable(std::move(interfaceName), std::move(vtable_));
}
inline void VTableAdder::forInterface(std::string interfaceName)
{
forInterface(InterfaceName{std::move(interfaceName)});
}
[[nodiscard]] inline Slot VTableAdder::forInterface(InterfaceName interfaceName, return_slot_t)
{
return object_.addVTable(std::move(interfaceName), std::move(vtable_), return_slot);
}
[[nodiscard]] inline Slot VTableAdder::forInterface(std::string interfaceName, return_slot_t)
{
return forInterface(InterfaceName{std::move(interfaceName)}, return_slot);
}
/*** ------------- ***/
/*** SignalEmitter ***/
/*** ------------- ***/
inline SignalEmitter::SignalEmitter(IObject& object, const SignalName& signalName)
: SignalEmitter(object, signalName.c_str())
{
}
inline SignalEmitter::SignalEmitter(IObject& object, const char* signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
// emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow emitSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.emitSignal(signal_);
}
inline SignalEmitter& SignalEmitter::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalEmitter& SignalEmitter::onInterface(const char* interfaceName)
{
signal_ = object_.createSignal(interfaceName, signalName_);
return *this;
}
template <typename... _Args>
inline void SignalEmitter::withArguments(_Args&&... args)
{
assert(signal_.isValid()); // onInterface() must be placed/called prior to withArguments()
detail::serialize_pack(signal_, std::forward<_Args>(args)...);
}
/*** ------------- ***/
/*** MethodInvoker ***/
/*** ------------- ***/
inline MethodInvoker::MethodInvoker(IProxy& proxy, const MethodName& methodName)
: MethodInvoker(proxy, methodName.c_str())
{
}
inline MethodInvoker::MethodInvoker(IProxy& proxy, const char* methodName)
: proxy_(proxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions())
{
}
inline MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't call the method if it has been called already or if MethodInvoker
// threw an exception in one of its methods
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
return;
// callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
proxy_.callMethod(method_, timeout_);
}
inline MethodInvoker& MethodInvoker::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline MethodInvoker& MethodInvoker::onInterface(const char* interfaceName)
{
method_ = proxy_.createMethodCall(interfaceName, methodName_);
return *this;
}
inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec)
{
timeout_ = usec;
return *this;
}
template <typename _Rep, typename _Period>
inline MethodInvoker& MethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return withTimeout(microsecs.count());
}
template <typename... _Args>
inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename... _Args>
inline void MethodInvoker::storeResultsTo(_Args&... args)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
auto reply = proxy_.callMethod(method_, timeout_);
methodCalled_ = true;
detail::deserialize_pack(reply, args...);
}
inline void MethodInvoker::dontExpectReply()
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
method_.dontExpectReply();
}
/*** ------------------ ***/
/*** AsyncMethodInvoker ***/
/*** ------------------ ***/
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const MethodName& methodName)
: AsyncMethodInvoker(proxy, methodName.c_str())
{
}
inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const char* methodName)
: proxy_(proxy)
, methodName_(methodName)
{
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const char* interfaceName)
{
method_ = proxy_.createMethodCall(interfaceName, methodName_);
return *this;
}
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec)
{
timeout_ = usec;
return *this;
}
template <typename _Rep, typename _Period>
inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return withTimeout(microsecs.count());
}
template <typename... _Args>
inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync(method_, makeAsyncReplyHandler(std::forward<_Function>(callback)), timeout_);
}
template <typename _Function>
[[nodiscard]] Slot AsyncMethodInvoker::uponReplyInvoke(_Function&& callback, return_slot_t)
{
assert(method_.isValid()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync( method_
, makeAsyncReplyHandler(std::forward<_Function>(callback))
, timeout_
, return_slot );
}
template <typename _Function>
inline async_reply_handler AsyncMethodInvoker::makeAsyncReplyHandler(_Function&& callback)
{
return [callback = std::forward<_Function>(callback)](MethodReply reply, std::optional<Error> error)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> args;
// Deserialize input arguments from the message into the tuple (if no error occurred).
if (!error)
{
try
{
reply >> args;
}
catch (const Error& e)
{
// Pass message deserialization exceptions to the client via callback error parameter,
// instead of propagating them up the message loop call stack.
sdbus::apply(callback, e, args);
return;
}
}
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, std::move(error), args);
};
}
template <typename... _Args>
std::future<future_return_t<_Args...>> AsyncMethodInvoker::getResultAsFuture()
{
auto promise = std::make_shared<std::promise<future_return_t<_Args...>>>();
auto future = promise->get_future();
uponReplyInvoke([promise = std::move(promise)](std::optional<Error> error, _Args... args)
{
if (!error)
if constexpr (!std::is_void_v<future_return_t<_Args...>>)
promise->set_value({std::move(args)...});
else
promise->set_value();
else
promise->set_exception(std::make_exception_ptr(*std::move(error)));
});
// Will be std::future<void> for no D-Bus method return value
// or std::future<T> for single D-Bus method return value
// or std::future<std::tuple<...>> for multiple method return values
return future;
}
/*** ---------------- ***/
/*** SignalSubscriber ***/
/*** ---------------- ***/
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const SignalName& signalName)
: SignalSubscriber(proxy, signalName.c_str())
{
}
inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const char* signalName)
: proxy_(proxy)
, signalName_(signalName)
{
}
inline SignalSubscriber& SignalSubscriber::onInterface(const InterfaceName& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName)
{
return onInterface(interfaceName.c_str());
}
inline SignalSubscriber& SignalSubscriber::onInterface(const char* interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Function>
inline void SignalSubscriber::call(_Function&& callback)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
proxy_.registerSignalHandler( interfaceName_
, signalName_
, makeSignalHandler(std::forward<_Function>(callback)) );
}
template <typename _Function>
[[nodiscard]] inline Slot SignalSubscriber::call(_Function&& callback, return_slot_t)
{
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
return proxy_.registerSignalHandler( interfaceName_
, signalName_
, makeSignalHandler(std::forward<_Function>(callback))
, return_slot );
}
template <typename _Function>
inline signal_handler SignalSubscriber::makeSignalHandler(_Function&& callback)
{
return [callback = std::forward<_Function>(callback)](Signal signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.
tuple_of_function_input_arg_types_t<_Function> signalArgs;
// The signal handler can take pure signal parameters only, or an additional `std::optional<Error>` as its first
// parameter. In the former case, if the deserialization fails (e.g. due to signature mismatch),
// the failure is ignored (and signal simply dropped). In the latter case, the deserialization failure
// will be communicated to the client's signal handler as a valid Error object inside the std::optional parameter.
if constexpr (has_error_param_v<_Function>)
{
// Deserialize input arguments from the signal message into the tuple
try
{
signal >> signalArgs;
}
catch (const sdbus::Error& e)
{
// Pass message deserialization exceptions to the client via callback error parameter,
// instead of propagating them up the message loop call stack.
sdbus::apply(callback, e, signalArgs);
return;
}
// Invoke callback with no error and input arguments from the tuple.
sdbus::apply(callback, {}, signalArgs);
}
else
{
// Deserialize input arguments from the signal message into the tuple
signal >> signalArgs;
// Invoke callback with input arguments from the tuple.
sdbus::apply(callback, signalArgs);
}
};
}
/*** -------------- ***/
/*** PropertyGetter ***/
/*** -------------- ***/
inline PropertyGetter::PropertyGetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(std::move(propertyName))
{
}
inline Variant PropertyGetter::onInterface(std::string_view interfaceName)
{
Variant var;
proxy_.callMethod("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
/*** ------------------- ***/
/*** AsyncPropertyGetter ***/
/*** ------------------- ***/
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(std::move(propertyName))
{
}
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncPropertyGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, Variant>
, "Property get callback function must accept std::optional<Error> and property value as Variant" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Get")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_)
.getResultAsFuture<Variant>();
}
/*** -------------- ***/
/*** PropertySetter ***/
/*** -------------- ***/
inline PropertySetter::PropertySetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(std::move(propertyName))
{
}
inline PropertySetter& PropertySetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
PropertySetter::toValue(Variant{value});
}
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value, dont_expect_reply_t)
{
PropertySetter::toValue(Variant{value}, dont_expect_reply);
}
inline void PropertySetter::toValue(const Variant& value)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, value);
}
inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t)
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
proxy_.callMethod("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, value)
.dontExpectReply();
}
/*** ------------------- ***/
/*** AsyncPropertySetter ***/
/*** ------------------- ***/
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, std::string_view propertyName)
: proxy_(proxy)
, propertyName_(propertyName)
{
}
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Value>
inline AsyncPropertySetter& AsyncPropertySetter::toValue(_Value&& value)
{
return AsyncPropertySetter::toValue(Variant{std::forward<_Value>(value)});
}
inline AsyncPropertySetter& AsyncPropertySetter::toValue(Variant value)
{
value_ = std::move(value);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncPropertySetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>>
, "Property set callback function must accept std::optional<Error> only" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncPropertySetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>>
, "Property set callback function must accept std::optional<Error> only" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("Set")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_, propertyName_, std::move(value_))
.getResultAsFuture<>();
}
/*** ------------------- ***/
/*** AllPropertiesGetter ***/
/*** ------------------- ***/
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline std::map<PropertyName, Variant> AllPropertiesGetter::onInterface(std::string_view interfaceName)
{
std::map<PropertyName, Variant> props;
proxy_.callMethod("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(std::move(interfaceName))
.storeResultsTo(props);
return props;
}
/*** ------------------------ ***/
/*** AsyncAllPropertiesGetter ***/
/*** ------------------------ ***/
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
: proxy_(proxy)
{
}
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(std::string_view interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename _Function>
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, std::map<PropertyName, Variant>>
, "All properties get callback function must accept std::optional<Error> and a map of property names to their values" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback, return_slot_t)
{
static_assert( std::is_invocable_r_v<void, _Function, std::optional<Error>, std::map<PropertyName, Variant>>
, "All properties get callback function must accept std::optional<Error> and a map of property names to their values" );
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
inline std::future<std::map<PropertyName, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
{
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
return proxy_.callMethodAsync("GetAll")
.onInterface(DBUS_PROPERTIES_INTERFACE_NAME)
.withArguments(interfaceName_)
.getResultAsFuture<std::map<PropertyName, Variant>>();
}
} // namespace sdbus
#endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */

View File

@ -1,169 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.h
*
* Created on: Jan 19, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_CONVENIENCECLASSES_H_
#define SDBUS_CXX_CONVENIENCECLASSES_H_
#include <sdbus-c++/Message.h>
#include <string>
// Forward declarations
namespace sdbus {
class IObject;
class IObjectProxy;
class Variant;
}
namespace sdbus {
class MethodRegistrator
{
public:
MethodRegistrator(IObject& object, const std::string& methodName);
MethodRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> void implementedAs(_Function&& callback);
private:
IObject& object_;
const std::string& methodName_;
std::string interfaceName_;
};
class SignalRegistrator
{
public:
SignalRegistrator(IObject& object, const std::string& signalName);
SignalRegistrator(SignalRegistrator&& other) = default;
SignalRegistrator& operator=(SignalRegistrator&& other) = default;
~SignalRegistrator() noexcept(false);
SignalRegistrator& onInterface(std::string interfaceName);
template <typename... _Args> void withParameters();
private:
IObject& object_;
const std::string& signalName_;
std::string interfaceName_;
std::string signalSignature_;
int exceptions_{}; // Number of active exceptions when SignalRegistrator is constructed
};
class PropertyRegistrator
{
public:
PropertyRegistrator(IObject& object, const std::string& propertyName);
PropertyRegistrator(PropertyRegistrator&& other) = default;
PropertyRegistrator& operator=(PropertyRegistrator&& other) = default;
~PropertyRegistrator() noexcept(false);
PropertyRegistrator& onInterface(const std::string& interfaceName);
template <typename _Function> PropertyRegistrator& withGetter(_Function&& callback);
template <typename _Function> PropertyRegistrator& withSetter(_Function&& callback);
private:
IObject& object_;
const std::string& propertyName_;
std::string interfaceName_;
std::string propertySignature_;
property_get_callback getter_;
property_set_callback setter_;
int exceptions_{}; // Number of active exceptions when PropertyRegistrator is constructed
};
class SignalEmitter
{
public:
SignalEmitter(IObject& object, const std::string& signalName);
SignalEmitter(SignalEmitter&& other) = default;
SignalEmitter& operator=(SignalEmitter&& other) = default;
~SignalEmitter() noexcept(false);
SignalEmitter& onInterface(const std::string& interfaceName);
template <typename... _Args> void withArguments(_Args&&... args);
private:
IObject& object_;
const std::string& signalName_;
Message signal_;
int exceptions_{}; // Number of active exceptions when SignalEmitter is constructed
};
class MethodInvoker
{
public:
MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName);
MethodInvoker(MethodInvoker&& other) = default;
MethodInvoker& operator=(MethodInvoker&& other) = default;
~MethodInvoker() noexcept(false);
MethodInvoker& onInterface(const std::string& interfaceName);
template <typename... _Args> MethodInvoker& withArguments(_Args&&... args);
template <typename... _Args> void storeResultsTo(_Args&... args);
private:
IObjectProxy& objectProxy_;
const std::string& methodName_;
Message method_;
int exceptions_{}; // Number of active exceptions when MethodInvoker is constructed
bool methodCalled_{};
};
class SignalSubscriber
{
public:
SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName);
SignalSubscriber& onInterface(const std::string& interfaceName);
template <typename _Function> void call(_Function&& callback);
private:
IObjectProxy& objectProxy_;
std::string signalName_;
std::string interfaceName_;
};
class PropertyGetter
{
public:
PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName);
sdbus::Variant onInterface(const std::string& interfaceName);
private:
IObjectProxy& objectProxy_;
std::string propertyName_;
};
class PropertySetter
{
public:
PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName);
PropertySetter& onInterface(const std::string& interfaceName);
template <typename _Value> void toValue(const _Value& value);
private:
IObjectProxy& objectProxy_;
const std::string& propertyName_;
std::string interfaceName_;
};
}
#endif /* SDBUS_CXX_CONVENIENCECLASSES_H_ */

View File

@ -1,401 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.inl
*
* Created on: Dec 19, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_CONVENIENCECLASSES_INL_
#define SDBUS_CPP_CONVENIENCECLASSES_INL_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
#include <string>
#include <tuple>
/*#include <exception>*/
namespace sdbus {
inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
: object_(object)
, methodName_(methodName)
{
}
inline MethodRegistrator& MethodRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline void MethodRegistrator::implementedAs(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus method", EINVAL);
object_.registerMethod( interfaceName_
, methodName_
, signature_of_function_input_arguments<_Function>::str()
, signature_of_function_output_arguments<_Function>::str()
, [callback = std::forward<_Function>(callback)](Message& msg, Message& reply)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple
msg >> inputArgs;
// Invoke callback with input arguments from the tuple.
// For callbacks returning a non-void value, `apply' also returns that value.
// For callbacks returning void, `apply' returns an empty tuple.
auto ret = apply(callback, inputArgs); // We don't yet have C++17's std::apply :-(
// The return value is stored to the reply message.
// In case of void functions, ret is an empty tuple and thus nothing is stored.
reply << ret;
});
}
// Moved into the library to isolate from C++17 dependency
/*
inline SignalRegistrator::SignalRegistrator(IObject& object, std::string signalName)
: object_(object)
, signalName_(std::move(signalName))
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the signal if SignalRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
if (interfaceName_.empty())
throw sdbus::Exception("DBus interface not specified when registering a DBus signal");
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal(interfaceName_, signalName_, signalSignature_);
}
*/
inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName)
{
interfaceName_ = std::move(interfaceName);
return *this;
}
template <typename... _Args>
inline void SignalRegistrator::withParameters()
{
signalSignature_ = signature_of_function_input_arguments<void(_Args...)>::str();
}
// Moved into the library to isolate from C++17 dependency
/*
inline PropertyRegistrator::PropertyRegistrator(IObject& object, std::string propertyName)
: object_(object)
, propertyName_(std::move(propertyName))
, exceptions_(std::uncaught_exceptions())
{
}
inline PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the property if PropertyRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL);
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerProperty() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerProperty( std::move(interfaceName_)
, std::move(propertyName_)
, std::move(propertySignature_)
, std::move(getter_)
, std::move(setter_) );
}
*/
inline PropertyRegistrator& PropertyRegistrator::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_output_arguments<_Function>::str();
getter_ = [callback = std::forward<_Function>(callback)](Message& msg)
{
// Get the propety value and serialize it into the message
msg << callback();
};
return *this;
}
template <typename _Function>
inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
{
static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (propertySignature_.empty())
propertySignature_ = signature_of_function_input_arguments<_Function>::str();
setter_ = [callback = std::forward<_Function>(callback)](Message& msg)
{
// Default-construct property value
using property_type = function_argument_t<_Function, 0>;
std::decay_t<property_type> property;
// Deserialize property value from the message
msg >> property;
// Invoke setter with the value
callback(property);
};
return *this;
}
// Moved into the library to isolate from C++17 dependency
/*
inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions())
{
}
inline SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
if (!signal_.isValid())
throw sdbus::Exception("DBus interface not specified when emitting a DBus signal");
// emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow emitSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.emitSignal(signal_);
}
*/
inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
{
signal_ = object_.createSignal(interfaceName, signalName_);
return *this;
}
template <typename... _Args>
inline void SignalEmitter::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
detail::serialize_pack(signal_, std::forward<_Args>(args)...);
}
// Moved into the library to isolate from C++17 dependency
/*
inline MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions())
{
}
inline MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't call the method if it has been called already or if MethodInvoker
// threw an exception in one of its methods
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
return;
if (!method_.isValid())
throw sdbus::Exception("DBus interface not specified when calling a DBus method");
// callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
}
*/
inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
{
method_ = objectProxy_.createMethodCall(interfaceName, methodName_);
return *this;
}
template <typename... _Args>
inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
detail::serialize_pack(method_, std::forward<_Args>(args)...);
return *this;
}
template <typename... _Args>
inline void MethodInvoker::storeResultsTo(_Args&... args)
{
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
auto reply = objectProxy_.callMethod(method_);
methodCalled_ = true;
detail::deserialize_pack(reply, args...);
}
inline SignalSubscriber::SignalSubscriber(IObjectProxy& objectProxy, const std::string& signalName)
: objectProxy_(objectProxy)
, signalName_(signalName)
{
}
inline SignalSubscriber& SignalSubscriber::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Function>
inline void SignalSubscriber::call(_Function&& callback)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when subscribing to a signal", EINVAL);
objectProxy_.registerSignalHandler( interfaceName_
, signalName_
, [callback = std::forward<_Function>(callback)](Message& signal)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the signal message.
tuple_of_function_input_arg_types_t<_Function> signalArgs;
// Deserialize input arguments from the signal message into the tuple
signal >> signalArgs;
// Invoke callback with input arguments from the tuple.
apply(callback, signalArgs); // We don't yet have C++17's std::apply :-(
});
}
inline PropertyGetter::PropertyGetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
, propertyName_(propertyName)
{
}
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
{
sdbus::Variant var;
objectProxy_
.callMethod("Get")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName, propertyName_)
.storeResultsTo(var);
return var;
}
inline PropertySetter::PropertySetter(IObjectProxy& objectProxy, const std::string& propertyName)
: objectProxy_(objectProxy)
, propertyName_(propertyName)
{
}
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
{
interfaceName_ = interfaceName;
return *this;
}
template <typename _Value>
inline void PropertySetter::toValue(const _Value& value)
{
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when setting a property", EINVAL);
objectProxy_
.callMethod("Set")
.onInterface("org.freedesktop.DBus.Properties")
.withArguments(interfaceName_, propertyName_, sdbus::Variant{value});
}
}
#endif /* SDBUS_CPP_CONVENIENCECLASSES_INL_ */

58
include/sdbus-c++/Error.h Executable file → Normal file
View File

@ -1,7 +1,8 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ConvenienceClasses.h
* @file Error.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
@ -26,7 +27,9 @@
#ifndef SDBUS_CXX_ERROR_H_
#define SDBUS_CXX_ERROR_H_
#include <errno.h>
#include <stdexcept>
#include <string>
namespace sdbus {
@ -40,37 +43,64 @@ namespace sdbus {
: public std::runtime_error
{
public:
Error(const std::string& name, const std::string& message)
: std::runtime_error("[" + name + "] " + message)
, name_(name)
, message_(message)
// Strong type representing the D-Bus error name
class Name : public std::string
{
public:
Name() = default;
explicit Name(std::string value)
: std::string(std::move(value))
{}
explicit Name(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
explicit Error(Name name, const char* message = nullptr)
: Error(std::move(name), std::string(message ? message : ""))
{
}
const std::string& getName() const
Error(Name name, std::string message)
: std::runtime_error("[" + name + "] " + message)
, name_(std::move(name))
, message_(std::move(message))
{
}
[[nodiscard]] const Name& getName() const
{
return name_;
}
const std::string& getMessage() const
[[nodiscard]] const std::string& getMessage() const
{
return message_;
}
[[nodiscard]] bool isValid() const
{
return !getName().empty();
}
private:
std::string name_;
Name name_;
std::string message_;
};
sdbus::Error createError(int errNo, const std::string& customMsg);
Error createError(int errNo, std::string customMsg);
inline const Error::Name SDBUSCPP_ERROR_NAME{"org.sdbuscpp.Error"};
}
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
throw sdbus::createError((_ERRNO), (_MSG)) \
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
throw sdbus::createError((_ERRNO), (_MSG)) \
/**/
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
if (_COND) SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
#define SDBUS_THROW_ERROR_IF(_COND, _MSG, _ERRNO) \
if (!(_COND)) ; else SDBUS_THROW_ERROR((_MSG), (_ERRNO)) \
/**/
#endif /* SDBUS_CXX_ERROR_H_ */

99
include/sdbus-c++/Flags.h Normal file
View File

@ -0,0 +1,99 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Flags.h
*
* Created on: Dec 31, 2018
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_FLAGS_H_
#define SDBUS_CXX_FLAGS_H_
#include <bitset>
#include <cstdint>
namespace sdbus {
// D-Bus interface, method, signal or property flags
class Flags
{
public:
enum GeneralFlags : uint8_t
{ DEPRECATED = 0
, METHOD_NO_REPLY = 1
, PRIVILEGED = 2
};
enum PropertyUpdateBehaviorFlags : uint8_t
{ EMITS_CHANGE_SIGNAL = 3
, EMITS_INVALIDATION_SIGNAL = 4
, EMITS_NO_SIGNAL = 5
, CONST_PROPERTY_VALUE = 6
};
enum : uint8_t
{ FLAG_COUNT = 7
};
Flags()
{
// EMITS_CHANGE_SIGNAL is on by default
flags_.set(EMITS_CHANGE_SIGNAL, true);
}
void set(GeneralFlags flag, bool value = true)
{
flags_.set(flag, value);
}
void set(PropertyUpdateBehaviorFlags flag, bool value = true)
{
flags_.set(EMITS_CHANGE_SIGNAL, false);
flags_.set(EMITS_INVALIDATION_SIGNAL, false);
flags_.set(EMITS_NO_SIGNAL, false);
flags_.set(CONST_PROPERTY_VALUE, false);
flags_.set(flag, value);
}
[[nodiscard]] bool test(GeneralFlags flag) const
{
return flags_.test(flag);
}
[[nodiscard]] bool test(PropertyUpdateBehaviorFlags flag) const
{
return flags_.test(flag);
}
[[nodiscard]] uint64_t toSdBusInterfaceFlags() const;
[[nodiscard]] uint64_t toSdBusMethodFlags() const;
[[nodiscard]] uint64_t toSdBusSignalFlags() const;
[[nodiscard]] uint64_t toSdBusPropertyFlags() const;
[[nodiscard]] uint64_t toSdBusWritablePropertyFlags() const;
private:
std::bitset<FLAG_COUNT> flags_;
};
}
#endif /* SDBUS_CXX_FLAGS_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file IConnection.h
*
@ -26,9 +27,23 @@
#ifndef SDBUS_CXX_ICONNECTION_H_
#define SDBUS_CXX_ICONNECTION_H_
//#include <cstdint>
#include <string>
#include <sdbus-c++/TypeTraits.h>
#include <chrono>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
// Forward declarations
struct sd_bus;
struct sd_event;
namespace sdbus {
class Message;
class ObjectPath;
class BusName;
using ServiceName = BusName;
}
namespace sdbus {
@ -36,122 +51,534 @@ namespace sdbus {
* @class IConnection
*
* An interface to D-Bus bus connection. Incorporates implementation
* of both synchronous and asynchronous processing loop.
* of both synchronous and asynchronous D-Bus I/O event loop.
*
* All methods throw @sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
* All methods throw sdbus::Error in case of failure. All methods in
* this class are thread-aware, but not thread-safe.
*
***********************************************/
class IConnection
{
public:
/*!
* @brief Requests D-Bus name on the connection
*
* @param[in] name Name to request
*
* @throws sdbus::Error in case of failure
*/
virtual void requestName(const std::string& name) = 0;
struct PollData;
virtual ~IConnection() = default;
/*!
* @brief Releases D-Bus name on the connection
*
* @param[in] name Name to release
*
* @throws sdbus::Error in case of failure
*/
virtual void releaseName(const std::string& name) = 0;
* @brief Enters I/O event loop on this bus connection
*
* The incoming D-Bus messages are processed in the loop. The method
* blocks indefinitely, until unblocked through leaveEventLoop().
*
* @throws sdbus::Error in case of failure
*/
virtual void enterEventLoop() = 0;
/*!
* @brief Enters the D-Bus processing loop
*
* The incoming D-Bus messages are processed in the loop. The method
* blocks indefinitely, until unblocked via @leaveProcessingLoop.
*
* @throws sdbus::Error in case of failure
*/
virtual void enterProcessingLoop() = 0;
* @brief Enters I/O event loop on this bus connection in a separate thread
*
* The same as enterEventLoop, except that it doesn't block
* because it runs the loop in a separate, internally managed thread.
*/
virtual void enterEventLoopAsync() = 0;
/*!
* @brief Enters the D-Bus processing loop in a separate thread
*
* The same as @enterProcessingLoop, except that it doesn't block
* because it runs the loop in a separate thread managed internally.
*/
virtual void enterProcessingLoopAsync() = 0;
* @brief Leaves the I/O event loop running on this bus connection
*
* This causes the loop to exit and frees the thread serving the loop
*
* @throws sdbus::Error in case of failure
*/
virtual void leaveEventLoop() = 0;
/*!
* @brief Leaves the D-Bus processing loop
*
* Ends the previously started processing loop.
*
* @throws sdbus::Error in case of failure
*/
virtual void leaveProcessingLoop() = 0;
* @brief Attaches the bus connection to an sd-event event loop
*
* @param[in] event sd-event event loop object
* @param[in] priority Specified priority
*
* @throws sdbus::Error in case of failure
*
* See `man sd_bus_attach_event'.
*/
virtual void attachSdEventLoop(sd_event *event, int priority = 0) = 0;
inline virtual ~IConnection() = 0;
/*!
* @brief Detaches the bus connection from an sd-event event loop
*
* @throws sdbus::Error in case of failure
*/
virtual void detachSdEventLoop() = 0;
/*!
* @brief Gets current sd-event event loop for the bus connection
*
* @return Pointer to event loop object if attached, nullptr otherwise
*/
virtual sd_event *getSdEventLoop() = 0;
/*!
* @brief Returns fd's, I/O events and timeout data to be used in an external event loop
*
* This function is useful to hook up a bus connection object with an
* external (like GMainLoop, boost::asio, etc.) or manual event loop
* involving poll() or a similar I/O polling call.
*
* Before **each** invocation of the I/O polling call, this function
* should be invoked. Returned PollData::fd file descriptor should
* be polled for the events indicated by PollData::events, and the I/O
* call should block for that up to the returned PollData::timeout.
*
* Additionally, returned PollData::eventFd should be polled for POLLIN
* events.
*
* After each I/O polling call the bus connection needs to process
* incoming or outgoing data, by invoking processPendingEvent().
*
* Note that the returned timeout should be considered only a maximum
* sleeping time. It is permissible (and even expected) that shorter
* timeouts are used by the calling program, in case other event sources
* are polled in the same event loop. Note that the returned time-value
* is absolute, based of CLOCK_MONOTONIC and specified in microseconds.
* Use PollData::getPollTimeout() to have the timeout value converted
* in a form that can be passed to poll(2).
*
* The bus connection conveniently integrates sd-event event loop.
* To attach the bus connection to an sd-event event loop, use
* attachSdEventLoop() function.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual PollData getEventLoopPollData() const = 0;
/*!
* @brief Processes a pending event
*
* @returns True if an event was processed, false if no operations were pending
*
* This function drives the D-Bus connection. It processes pending I/O events.
* Queued outgoing messages (or parts thereof) are sent out. Queued incoming
* messages are dispatched to registered callbacks. Timeouts are recalculated.
*
* It returns false when no operations were pending and true if a message was
* processed. When false is returned the caller should synchronously poll for
* I/O events before calling into processPendingEvent() again.
* Don't forget to call getEventLoopPollData() each time before the next poll.
*
* You don't need to directly call this method or getEventLoopPollData() method
* when using convenient, internal bus connection event loops through
* enterEventLoop() or enterEventLoopAsync() calls, or when the bus is
* connected to an sd-event event loop through attachSdEventLoop().
* It is invoked automatically when necessary.
*
* @throws sdbus::Error in case of failure
*/
virtual bool processPendingEvent() = 0;
/*!
* @brief Provides access to the currently processed D-Bus message
*
* This method provides access to the currently processed incoming D-Bus message.
* "Currently processed" means that the registered callback handler(s) for that message
* are being invoked. This method is meant to be called from within a callback handler
* (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is
* guaranteed to return a valid D-Bus message instance for which the handler is called.
* If called from other contexts/threads, it may return a valid or invalid message, depending
* on whether a message was processed or not at the time of the call.
*
* @return Currently processed D-Bus message
*/
[[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Sets general method call timeout
*
* @param[in] timeout Timeout value in microseconds
*
* General method call timeout is used for all method calls upon this connection.
* Method call-specific timeout overrides this general setting.
*
* Supported by libsystemd>=v240.
*
* @throws sdbus::Error in case of failure
*/
virtual void setMethodCallTimeout(uint64_t timeout) = 0;
/*!
* @copydoc IConnection::setMethodCallTimeout(uint64_t)
*/
template <typename _Rep, typename _Period>
void setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Gets general method call timeout
*
* @return Timeout value in microseconds
*
* Supported by libsystemd>=v240.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual uint64_t getMethodCallTimeout() const = 0;
/*!
* @brief Adds an ObjectManager at the specified D-Bus object path
* @param[in] objectPath Object path at which the ObjectManager interface shall be installed
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* This call creates a floating registration. The ObjectManager will
* be there for the object path until the connection is destroyed.
*
* Another, recommended way to add object managers is directly through IObject API.
*
* @throws sdbus::Error in case of failure
*/
virtual void addObjectManager(const ObjectPath& objectPath) = 0;
/*!
* @brief Adds an ObjectManager at the specified D-Bus object path
* @param[in] objectPath Object path at which the ObjectManager interface shall be installed
* @return Slot handle owning the registration
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* This call returns an owning slot. The lifetime of the ObjectManager
* interface is bound to the lifetime of the returned slot instance.
*
* Another, recommended way to add object managers is directly through IObject API.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) = 0;
/*!
* @brief Installs a floating match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* The method installs a match rule for messages received on the specified bus connection.
* The syntax of the match rule expression passed in match is described in the D-Bus specification.
* The specified handler function callback is called for each incoming message matching the specified
* expression. The match is installed synchronously when connected to a bus broker, i.e. the call
* sends a control message requesting the match to be added to the broker and waits until the broker
* confirms the match has been installed successfully.
*
* The method installs a floating match rule for messages received on the specified bus connection.
* Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule
* is bound to the lifetime of the bus connection.
*
* For more information, consult `man sd_bus_add_match`.
*
* @throws sdbus::Error in case of failure
*/
virtual void addMatch(const std::string& match, message_handler callback) = 0;
/*!
* @brief Installs a match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @return RAII-style slot handle representing the ownership of the subscription
*
* The method installs a match rule for messages received on the specified bus connection.
* The syntax of the match rule expression passed in match is described in the D-Bus specification.
* The specified handler function callback is called for each incoming message matching the specified
* expression. The match is installed synchronously when connected to a bus broker, i.e. the call
* sends a control message requesting the match to be added to the broker and waits until the broker
* confirms the match has been installed successfully.
*
* The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying
* the slot instance implies uninstalling of the match rule from the bus connection.
*
* For more information, consult `man sd_bus_add_match`.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback, return_slot_t) = 0;
/*!
* @brief Asynchronously installs a floating match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule
*
* This method operates the same as `addMatch()` above, just that it installs the match rule asynchronously,
* in a non-blocking fashion. A request is sent to the broker, but the call does not wait for a response.
* The `installCallback' callable is called when the response is later received, with the response message
* from the broker as parameter. If it's an empty function object, a default implementation is used that
* terminates the bus connection should installing the match fail.
*
* The method installs a floating match rule for messages received on the specified bus connection.
* Floating means that the bus connection object owns the match rule, i.e. lifetime of the match rule
* is bound to the lifetime of the bus connection.
*
* For more information, consult `man sd_bus_add_match_async`.
*
* @throws sdbus::Error in case of failure
*/
virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 0;
/*!
* @brief Asynchronously installs a match rule for messages received on this bus connection
*
* @param[in] match Match expression to filter incoming D-Bus message
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @param[in] installCallback Callback handler to be called upon processing an inbound D-Bus message matching the rule
* @return RAII-style slot handle representing the ownership of the subscription
*
* This method operates the same as `addMatch()` above, just that it installs the match rule asynchronously,
* in a non-blocking fashion. A request is sent to the broker, but the call does not wait for a response.
* The `installCallback' callable is called when the response is later received, with the response message
* from the broker as parameter. If it's an empty function object, a default implementation is used that
* terminates the bus connection should installing the match fail.
*
* The lifetime of the match rule is bound to the lifetime of the returned slot instance. Destroying
* the slot instance implies the uninstalling of the match rule from the bus connection.
*
* For more information, consult `man sd_bus_add_match_async`.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addMatchAsync( const std::string& match
, message_handler callback
, message_handler installCallback
, return_slot_t ) = 0;
/*!
* @brief Retrieves the unique name of a connection. E.g. ":1.xx"
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual BusName getUniqueName() const = 0;
/*!
* @brief Requests a well-known D-Bus service name on a bus
*
* @param[in] name Name to request
*
* @throws sdbus::Error in case of failure
*/
virtual void requestName(const ServiceName& name) = 0;
/*!
* @brief Releases an acquired well-known D-Bus service name on a bus
*
* @param[in] name Name to release
*
* @throws sdbus::Error in case of failure
*/
virtual void releaseName(const ServiceName& name) = 0;
/*!
* @struct PollData
*
* Carries poll data needed for integration with external event loop implementations.
*
* See getEventLoopPollData() for more info.
*/
struct PollData
{
/*!
* The read fd to be monitored by the event loop.
*/
int fd;
/*!
* The events to use for poll(2) alongside fd.
*/
short int events;
/*!
* Absolute timeout value in microseconds, based of CLOCK_MONOTONIC.
*
* Call getPollTimeout() to get timeout recalculated to relative timeout that can be passed to poll(2).
*/
std::chrono::microseconds timeout;
/*!
* An additional event fd to be monitored by the event loop for POLLIN events.
*/
int eventFd;
/*!
* Returns the timeout as relative value from now.
*
* Returned value is std::chrono::microseconds::max() if the timeout is indefinite.
*
* @return Relative timeout as a time duration
*/
[[nodiscard]] std::chrono::microseconds getRelativeTimeout() const;
/*!
* Returns relative timeout in the form which can be passed as argument 'timeout' to poll(2)
*
* @return -1 if the timeout is indefinite. 0 if the poll(2) shouldn't block.
* An integer in milliseconds otherwise.
*/
[[nodiscard]] int getPollTimeout() const;
};
};
IConnection::~IConnection() {}
template <typename _Rep, typename _Period>
inline void IConnection::setMethodCallTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return setMethodCallTimeout(microsecs.count());
}
/*!
* @brief Creates/opens D-Bus system connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createConnection();
* @brief Creates/opens D-Bus session bus connection when in a user context, and a system bus connection, otherwise.
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection();
/*!
* @brief Creates/opens D-Bus system connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createConnection(const std::string& name);
* @brief Creates/opens D-Bus session bus connection with a name when in a user context, and a system bus connection with a name, otherwise.
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection(const ServiceName& name);
/*!
* @brief Creates/opens D-Bus system connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSystemBusConnection();
* @brief Creates/opens D-Bus system bus connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSystemBusConnection();
/*!
* @brief Creates/opens D-Bus system connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const std::string& name);
* @brief Creates/opens D-Bus system bus connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSystemBusConnection(const ServiceName& name);
/*!
* @brief Creates/opens D-Bus session connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSessionBusConnection();
* @brief Creates/opens D-Bus session bus connection
*
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSessionBusConnection();
/*!
* @brief Creates/opens D-Bus session connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string& name);
* @brief Creates/opens D-Bus session bus connection with a name
*
* @param[in] name Name to request on the connection after its opening
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const ServiceName& name);
/*!
* @brief Creates/opens D-Bus session bus connection at a custom address
*
* @param[in] address ";"-separated list of addresses of bus brokers to try to connect
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*
* Consult manual pages for `sd_bus_set_address` of the underlying sd-bus library for more information.
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createSessionBusConnectionWithAddress(const std::string& address);
/*!
* @brief Creates/opens D-Bus system connection on a remote host using ssh
*
* @param[in] host Name of the host to connect
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::string& host);
/*!
* @brief Opens direct D-Bus connection at a custom address
*
* @param[in] address ";"-separated list of addresses of bus brokers to try to connect to
* @return Connection instance
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address);
/*!
* @brief Opens direct D-Bus connection at the given file descriptor
*
* @param[in] fd File descriptor used to communicate directly from/to a D-Bus server
* @return Connection instance
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd);
/*!
* @brief Opens direct D-Bus connection at fd as a server
*
* @param[in] fd File descriptor to use for server DBus connection
* @return Server connection instance
*
* This creates a new, custom bus object in server mode. One can then call createDirectBusConnection()
* on client side to connect to this bus.
*
* The underlying sdbus-c++ connection instance takes over ownership of fd, so the caller can let it go.
* If, however, the call throws an exception, the ownership of fd remains with the caller.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createServerBus(int fd);
/*!
* @brief Creates sdbus-c++ bus connection representation out of underlying sd_bus instance
*
* @param[in] bus File descriptor to use for server DBus connection
* @return Connection instance
*
* This functions is helpful in cases where clients need a custom, tweaked configuration of their
* bus object. Since sdbus-c++ does not provide C++ API for all bus connection configuration
* functions of the underlying sd-bus library, clients can use these sd-bus functions themselves
* to create and configure their sd_bus object, and create sdbus-c++ IConnection on top of it.
*
* The IConnection instance assumes unique ownership of the provided bus object. The bus object
* must have been started by the client before this call.
* The bus object will get flushed, closed, and unreffed when the IConnection instance is destroyed.
*
* @throws sdbus::Error in case of failure
*
* Code example:
* @code
* sd_bus* bus{};
* ::sd_bus_new(&bus);
* ::sd_bus_set_address(bus, address);
* ::sd_bus_set_anonymous(bus, true);
* ::sd_bus_start(bus);
* auto con = sdbus::createBusConnection(bus); // IConnection consumes sd_bus object
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus);
}
#endif /* SDBUS_CXX_ICONNECTION_H_ */

601
include/sdbus-c++/IObject.h Executable file → Normal file
View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file IObject.h
*
@ -26,16 +27,21 @@
#ifndef SDBUS_CXX_IOBJECT_H_
#define SDBUS_CXX_IOBJECT_H_
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/ConvenienceApiClasses.h>
#include <sdbus-c++/Flags.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/VTableItems.h>
#include <functional>
#include <string>
#include <memory>
#include <string>
#include <vector>
// Forward declarations
namespace sdbus {
class Message;
class Signal;
class IConnection;
class ObjectPath;
}
namespace sdbus {
@ -43,237 +49,436 @@ namespace sdbus {
/********************************************//**
* @class IObject
*
* An interface to D-Bus object. Provides API for registration
* of methods, signals, properties, and for emitting signals.
* IObject class represents a D-Bus object instance identified by a specific object path.
* D-Bus object provides its interfaces, methods, signals and properties on a bus
* identified by a specific bus name.
*
* All methods throw @c sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
* All IObject member methods throw @c sdbus::Error in case of D-Bus or sdbus-c++ error.
* The IObject class has been designed as thread-aware. However, the operation of
* creating and sending asynchronous method replies, as well as creating and emitting
* signals, is thread-safe by design.
*
***********************************************/
class IObject
{
public:
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the method will belong to
* @param[in] methodName Name of the method
* @param[in] inputSignature D-Bus signature of method input parameters
* @param[in] outputSignature D-Bus signature of method output parameters
* @param[in] methodCallback Callback that implements the body of the method
*
* @throws sdbus::Error in case of failure
*/
virtual void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) = 0;
public: // High-level, convenience API
virtual ~IObject() = default;
/*!
* @brief Registers signal that the object will emit on D-Bus
*
* @param[in] interfaceName Name of an interface that the signal will fall under
* @param[in] signalName Name of the signal
* @param[in] signature D-Bus signature of signal parameters
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) = 0;
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] vtable Individual instances of VTable item structures stored in a vector
* @return VTableAdder high-level helper class
*
* This method is used to declare attributes for the object under the given interface.
* Parameter `vtable' represents a vtable definition that may contain method declarations
* (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime.
*
* When called like `addVTable(vtable).forInterface(interface)`, then an internal registration
* slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance.
* When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal
* registration slot is created for the vtable and is returned to the caller. Keeping the slot means
* keep the registration "alive". Destroying the slot means that the vtable is not needed anymore,
* and the vtable gets removed from the object. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] VTableAdder addVTable(std::vector<VTableItem> vtable);
/*!
* @brief Registers read-only property that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback ) = 0;
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] items Individual instances of VTable item structures
* @return VTableAdder high-level helper class
*
* This method is used to declare attributes for the object under the given interface.
* Parameter pack contains vtable definition that may contain method declarations
* (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime.
*
* When called like `addVTable(items...).forInterface(interface)`, then an internal registration
* slot is created for that vtable and its lifetime is tied to the lifetime of the Object instance.
* When called like `addVTable(items...).forInterface(interface, sdbus::return_slot)`, then an internal
* registration slot is created for the vtable and is returned to the caller. Keeping the slot means
* keep the registration "alive". Destroying the slot means that the vtable is not needed anymore,
* and the vtable gets removed from the object. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
template < typename... VTableItems
, typename = std::enable_if_t<(is_one_of_variants_types<VTableItem, std::decay_t<VTableItems>> && ...)> >
[[nodiscard]] VTableAdder addVTable(VTableItems&&... items);
/*!
* @brief Registers read/write property that the object will provide on D-Bus
*
* @param[in] interfaceName Name of an interface that the property will fall under
* @param[in] propertyName Name of the property
* @param[in] signature D-Bus signature of property parameters
* @param[in] getCallback Callback that implements the body of the property getter
* @param[in] setCallback Callback that implements the body of the property setter
*
* @throws sdbus::Error in case of failure
*/
virtual void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) = 0;
* @brief Emits signal on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient emission of signals
*
* This is a high-level, convenience way of emitting D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments.
*
* Example of use:
* @code
* int arg1 = ...;
* double arg2 = ...;
* SignalName fooSignal{"fooSignal"};
* object_.emitSignal(fooSignal).onInterface("com.kistler.foo").withArguments(arg1, arg2);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] SignalEmitter emitSignal(const SignalName& signalName);
/*!
* @brief Finishes the registration and exports object API on D-Bus
*
* The method exports all up to now registered methods, signals and properties on D-Bus.
* Must be called only once, after all methods, signals and properties have been registered.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
* @copydoc IObject::emitSignal(const SignalName&)
*/
[[nodiscard]] SignalEmitter emitSignal(const std::string& signalName);
/*!
* @brief Creates a signal message
*
* @param[in] interfaceName Name of an interface that the signal belongs under
* @param[in] signalName Name of the signal
* @return A signal message
*
* Serialize signal arguments into the returned message and emit the signal by passing
* the message with serialized arguments to the @c emitSignal function.
* Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message createSignal(const std::string& interfaceName, const std::string& signalName) = 0;
* @copydoc IObject::emitSignal(const SignalName&)
*/
[[nodiscard]] SignalEmitter emitSignal(const char* signalName);
/*!
* @brief Emits signal on D-Bus
*
* @param[in] message Signal message to be sent out
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Message& message) = 0;
* @brief Emits PropertyChanged signal for specified properties under a given interface of this object path
*
* @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 InterfaceName& interfaceName, const std::vector<PropertyName>& propNames) = 0;
/*!
* @brief Registers method that the object will provide on D-Bus
*
* @param[in] methodName Name of the method
* @return A helper object for convenient registration of the method
*
* This is a high-level, convenience way of registering D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the parameters and return type
* of the provided native method implementation callback.
*
* Example of use:
* @code
* object.registerMethod("doFoo").onInterface("com.kistler.foo").implementedAs([this](int value){ return this->doFoo(value); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
MethodRegistrator registerMethod(const std::string& methodName);
* @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&,const std::vector<PropertyName>&)
*/
virtual void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames) = 0;
/*!
* @brief Registers signal that the object will provide on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal
*
* This is a high-level, convenience way of registering D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native parameters.
*
* Example of use:
* @code
* object.registerSignal("paramChange").onInterface("com.kistler.foo").withParameters<std::map<int32_t, std::string>>();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalRegistrator registerSignal(const std::string& signalName);
* @brief Emits PropertyChanged signal for all properties on a given interface of this object path
*
* @param[in] interfaceName Name of an interface
*
* @throws sdbus::Error in case of failure
*/
virtual void emitPropertiesChangedSignal(const InterfaceName& interfaceName) = 0;
/*!
* @brief Registers property that the object will provide on D-Bus
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient registration of the property
*
* This is a high-level, convenience way of registering D-Bus properties that abstracts
* from the D-Bus message concept. Property arguments are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native callbacks.
*
* Example of use:
* @code
* object_.registerProperty("state").onInterface("com.kistler.foo").withGetter([this](){ return this->state(); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertyRegistrator registerProperty(const std::string& propertyName);
* @copydoc IObject::emitPropertiesChangedSignal(const InterfaceName&)
*/
virtual void emitPropertiesChangedSignal(const char* interfaceName) = 0;
/*!
* @brief Emits signal on D-Bus
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient emission of signals
*
* This is a high-level, convenience way of emitting D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments.
*
* Example of use:
* @code
* int arg1 = ...;
* double arg2 = ...;
* object_.emitSignal("fooSignal").onInterface("com.kistler.foo").withArguments(arg1, arg2);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalEmitter emitSignal(const std::string& signalName);
* @brief Emits InterfacesAdded signal on this object path
*
* This emits an InterfacesAdded signal on this object path, by iterating all registered
* interfaces on the path. All properties are queried and included in the signal.
* This call is equivalent to emitInterfacesAddedSignal() with an explicit list of
* registered interfaces. However, unlike emitInterfacesAddedSignal(interfaces), this
* call can figure out the list of supported interfaces itself. Furthermore, it properly
* adds the builtin org.freedesktop.DBus.* interfaces.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitInterfacesAddedSignal() = 0;
virtual ~IObject() = 0;
/*!
* @brief Emits InterfacesAdded signal on this object path
*
* This emits an InterfacesAdded signal on this object path with explicitly provided list
* of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable
* object interfaces and their vtables, so this method now makes more sense.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces) = 0;
/*!
* @brief Emits InterfacesRemoved signal on this object path
*
* This is like sd_bus_emit_object_added(), but emits an InterfacesRemoved signal on this
* object path. This only includes any registered interfaces but skips the properties.
* This function shall be called (just) before destroying the object.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitInterfacesRemovedSignal() = 0;
/*!
* @brief Emits InterfacesRemoved signal on this object path
*
* This emits an InterfacesRemoved signal on the given path with explicitly provided list
* of registered interfaces. Since v2.0, sdbus-c++ supports dynamically addable/removable
* object interfaces and their vtables, so this method now makes more sense.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces) = 0;
/*!
* @brief Adds an ObjectManager interface at the path of this D-Bus object
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* This call creates a so-called floating registration. This means that
* the ObjectManager interface stays there for the lifetime of the object.
*
* @throws sdbus::Error in case of failure
*/
virtual void addObjectManager() = 0;
/*!
* @brief Adds an ObjectManager interface at the path of this D-Bus object
*
* @return Slot handle owning the registration
*
* Creates an ObjectManager interface at the specified object path on
* the connection. This is a convenient way to interrogate a connection
* to see what objects it has.
*
* The lifetime of the ObjectManager interface is bound to the lifetime
* of the returned slot instance.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addObjectManager(return_slot_t) = 0;
/*!
* @brief Provides D-Bus connection used by the object
*
* @return Reference to the D-Bus connection
*/
[[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0;
/*!
* @brief Returns object path of the underlying DBus object
*/
[[nodiscard]] virtual const ObjectPath& getObjectPath() const = 0;
/*!
* @brief Provides access to the currently processed D-Bus message
*
* This method provides access to the currently processed incoming D-Bus message.
* "Currently processed" means that the registered callback handler(s) for that message
* are being invoked. This method is meant to be called from within a callback handler
* (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is
* guaranteed to return a valid D-Bus message instance for which the handler is called.
* If called from other contexts/threads, it may return a valid or invalid message, depending
* on whether a message was processed or not at the time of the call.
*
* @return Currently processed D-Bus message
*/
[[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Unregisters object's API and removes object from the bus
*
* This method unregisters the object, its interfaces, methods, signals and properties
* from the bus. Unregistration is done automatically also in object's destructor. This
* method makes sense if, in the process of object removal, we need to make sure that
* callbacks are unregistered explicitly before the final destruction of the object instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
public: // Lower-level, message-based API
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] items Individual instances of VTable item structures
*
* This method is used to declare attributes for the object under the given interface.
* Parameter `items' represents a vtable definition that may contain method declarations
* (using MethodVTableItem struct), property declarations (using PropertyVTableItem
* struct), signal declarations (using SignalVTableItem struct), or global interface
* flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and its lifetime is tied to the lifetime of the Object instance.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
template < typename... VTableItems
, typename = std::enable_if_t<(is_one_of_variants_types<VTableItem, std::decay_t<VTableItems>> && ...)> >
void addVTable(InterfaceName interfaceName, VTableItems&&... items);
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] vtable A list of individual descriptions in the form of VTable item instances
*
* This method is used to declare attributes for the object under the given interface.
* The `vtable' parameter may contain method declarations (using MethodVTableItem struct),
* property declarations (using PropertyVTableItem struct), signal declarations (using
* SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and its lifetime is tied to the lifetime of the Object instance.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
virtual void addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable) = 0;
/*!
* @brief Adds a declaration of methods, properties and signals of the object at a given interface
*
* @param[in] interfaceName Name of an interface the the vtable is registered for
* @param[in] vtable A list of individual descriptions in the form of VTable item instances
*
* This method is used to declare attributes for the object under the given interface.
* The `vtable' parameter may contain method declarations (using MethodVTableItem struct),
* property declarations (using PropertyVTableItem struct), signal declarations (using
* SignalVTableItem struct), or global interface flags (using InterfaceFlagsVTableItem struct).
*
* An interface can have any number of vtables attached to it.
*
* Consult manual pages for the underlying `sd_bus_add_object_vtable` function for more information.
*
* The method can be called at any time during object's lifetime. For each vtable an internal
* registration slot is created and is returned to the caller. The returned slot should be destroyed
* when the vtable is not needed anymore. This allows for "dynamic" object API where vtables
* can be added or removed by the user at runtime.
*
* The function provides strong exception guarantee. The state of the object remains
* unmodified in face of an exception.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t) = 0;
/*!
* @brief Creates a signal message
*
* @param[in] interfaceName Name of an interface that the signal belongs under
* @param[in] signalName Name of the signal
* @return A signal message
*
* Serialize signal arguments into the returned message and emit the signal by passing
* the message with serialized arguments to the @c emitSignal function.
* Alternatively, use higher-level API @c emitSignal(const std::string& signalName) defined below.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) const = 0;
/*!
* @brief Emits signal for this object path
*
* @param[in] message Signal message to be sent out
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual void emitSignal(const sdbus::Signal& message) = 0;
protected: // Internal API for efficiency reasons used by high-level API helper classes
friend SignalEmitter;
[[nodiscard]] virtual Signal createSignal(const char* interfaceName, const char* signalName) const = 0;
};
inline MethodRegistrator IObject::registerMethod(const std::string& methodName)
{
return MethodRegistrator(*this, methodName);
}
// Out-of-line member definitions
inline SignalRegistrator IObject::registerSignal(const std::string& signalName)
{
return SignalRegistrator(*this, std::move(signalName));
}
inline PropertyRegistrator IObject::registerProperty(const std::string& propertyName)
{
return PropertyRegistrator(*this, std::move(propertyName));
}
inline SignalEmitter IObject::emitSignal(const std::string& signalName)
inline SignalEmitter IObject::emitSignal(const SignalName& signalName)
{
return SignalEmitter(*this, signalName);
}
inline IObject::~IObject() {}
inline SignalEmitter IObject::emitSignal(const std::string& signalName)
{
return SignalEmitter(*this, signalName.c_str());
}
inline SignalEmitter IObject::emitSignal(const char* signalName)
{
return SignalEmitter(*this, signalName);
}
template <typename... VTableItems, typename>
void IObject::addVTable(InterfaceName interfaceName, VTableItems&&... items)
{
addVTable(std::move(interfaceName), {std::forward<VTableItems>(items)...});
}
template <typename... VTableItems, typename>
VTableAdder IObject::addVTable(VTableItems&&... items)
{
return addVTable(std::vector<VTableItem>{std::forward<VTableItems>(items)...});
}
inline VTableAdder IObject::addVTable(std::vector<VTableItem> vtable)
{
return VTableAdder(*this, std::move(vtable));
}
/*!
* @brief Creates instance representing a D-Bus object
*
* @param[in] connection D-Bus connection to be used by the object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object representation instance
*
* The provided connection will be used by the object to export methods,
* issue signals and provide properties.
*
* Code example:
* @code
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath);
* @brief Creates instance representing a D-Bus object
*
* @param[in] connection D-Bus connection to be used by the object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object representation instance
*
* The provided connection will be used by the object to export methods,
* issue signals and provide properties.
*
* Creating a D-Bus object instance is (thread-)safe even upon the connection
* which is already running its I/O event loop.
*
* Code example:
* @code
* auto proxy = sdbus::createObject(connection, "/com/kistler/foo");
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, ObjectPath objectPath);
}
#include <sdbus-c++/ConvenienceClasses.inl>
#include <sdbus-c++/ConvenienceApiClasses.inl>
#include <sdbus-c++/VTableItems.inl>
#endif /* SDBUS_CXX_IOBJECT_H_ */

View File

@ -1,270 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file IObjectProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_IOBJECTPROXY_H_
#define SDBUS_CXX_IOBJECTPROXY_H_
#include <sdbus-c++/ConvenienceClasses.h>
#include <string>
#include <memory>
#include <functional>
// Forward declarations
namespace sdbus {
class Message;
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class IObjectProxy
*
* An interface to D-Bus object proxy. Provides API for calling
* methods, getting/setting properties, and for registering to signals.
*
* All methods throw @c sdbus::Error in case of failure. The class is
* thread-aware, but not thread-safe.
*
***********************************************/
class IObjectProxy
{
public:
/*!
* @brief Creates a method call message
*
* @param[in] interfaceName Name of an interface that the method is defined under
* @param[in] methodName Name of the method
* @return A method call message message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethod(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] message Message representing a method call
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual Message callMethod(const sdbus::Message& message) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) = 0;
/*!
* @brief Finishes the registration of signal handlers
*
* The method physically subscribes to the desired signals.
* Must be called only once, after all signals have been registered already.
*
* @throws sdbus::Error in case of failure
*/
virtual void finishRegistration() = 0;
/*!
* @brief Calls method on the proxied D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int result, a = ..., b = ...;
* object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
MethodInvoker callMethod(const std::string& methodName);
/*!
* @brief Registers signal handler for a given signal of the proxied D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
*
* This is a high-level, convenience way of registering to D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the parameters
* of the provided native signal callback.
*
* Example of use:
* @code
* object_.uponSignal("fooSignal").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onFooSignal(arg1, arg2); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @brief Gets value of a property of the proxied D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept. sdbus::Variant is returned which shall then be converted
* to the real property type (implicit conversion is supported).
*
* Example of use:
* @code
* int state = object.getProperty("state").onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertyGetter getProperty(const std::string& propertyName);
/*!
* @brief Sets value of a property of the proxied D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
PropertySetter setProperty(const std::string& propertyName);
virtual ~IObjectProxy() = 0;
};
inline MethodInvoker IObjectProxy::callMethod(const std::string& methodName)
{
return MethodInvoker(*this, methodName);
}
inline SignalSubscriber IObjectProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName);
}
inline PropertyGetter IObjectProxy::getProperty(const std::string& propertyName)
{
return PropertyGetter(*this, propertyName);
}
inline PropertySetter IObjectProxy::setProperty(const std::string& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline IObjectProxy::~IObjectProxy() {}
/*!
* @brief Creates object proxy instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection.
*
* Code example:
* @code
* auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( sdbus::IConnection& connection
, std::string destination
, std::string objectPath );
/*!
* @brief Creates object proxy instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection. Object proxy becomes
* an exclusive owner of this connection.
*
* Code example:
* @code
* auto proxy = sdbus::createObjectProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::unique_ptr<sdbus::IConnection>&& connection
, std::string destination
, std::string objectPath );
/*!
* @brief Creates object proxy instance that uses its own D-Bus connection
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
* @return Pointer to the object proxy instance
*
* This factory overload creates a proxy that manages its own D-Bus connection(s).
*
* Code example:
* @code
* auto proxy = sdbus::createObjectProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::string destination
, std::string objectPath );
}
#include <sdbus-c++/ConvenienceClasses.inl>
#endif /* SDBUS_CXX_IOBJECTPROXY_H_ */

981
include/sdbus-c++/IProxy.h Normal file
View File

@ -0,0 +1,981 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file IProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_IPROXY_H_
#define SDBUS_CXX_IPROXY_H_
#include <sdbus-c++/ConvenienceApiClasses.h>
#include <sdbus-c++/TypeTraits.h>
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <string>
#include <string_view>
// Forward declarations
namespace sdbus {
class MethodCall;
class MethodReply;
class IConnection;
class ObjectPath;
class PendingAsyncCall;
namespace internal {
class Proxy;
}
}
namespace sdbus {
/********************************************//**
* @class IProxy
*
* IProxy class represents a proxy object, which is a convenient local object created
* to represent a remote D-Bus object in another process.
* The proxy enables calling methods on remote objects, receiving signals from remote
* objects, and getting/setting properties of remote objects.
*
* All IProxy member methods throw @c sdbus::Error in case of D-Bus or sdbus-c++ error.
* The IProxy class has been designed as thread-aware. However, the operation of
* creating and sending method calls (both synchronously and asynchronously) is
* thread-safe by design.
*
***********************************************/
class IProxy
{
public: // High-level, convenience API
virtual ~IProxy() = default;
/*!
* @brief Calls method on the D-Bus object
*
* @param[in] methodName Name of the method
* @return A helper object for convenient invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int result, a = ..., b = ...;
* MethodName multiply{"multiply"};
* object_.callMethod(multiply).onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] MethodInvoker callMethod(const MethodName& methodName);
/*!
* @copydoc IProxy::callMethod(const MethodName&)
*/
[[nodiscard]] MethodInvoker callMethod(const std::string& methodName);
/*!
* @copydoc IProxy::callMethod(const MethodName&)
*/
[[nodiscard]] MethodInvoker callMethod(const char* methodName);
/*!
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] methodName Name of the method
* @return A helper object for convenient asynchronous invocation of the method
*
* This is a high-level, convenience way of calling D-Bus methods that abstracts
* from the D-Bus message concept. Method arguments/return value are automatically (de)serialized
* in a message and D-Bus signatures automatically deduced from the provided native arguments
* and return values.
*
* Example of use:
* @code
* int a = ..., b = ...;
* MethodName multiply{"multiply"};
* object_.callMethodAsync(multiply).onInterface(INTERFACE_NAME).withArguments(a, b).uponReplyInvoke([](int result)
* {
* std::cout << "Got result of multiplying " << a << " and " << b << ": " << result << std::endl;
* });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const MethodName& methodName);
/*!
* @copydoc IProxy::callMethodAsync(const MethodName&)
*/
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName);
/*!
* @copydoc IProxy::callMethodAsync(const MethodName&)
*/
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const char* methodName);
/*!
* @brief Registers signal handler for a given signal of the D-Bus object
*
* @param[in] signalName Name of the signal
* @return A helper object for convenient registration of the signal handler
*
* This is a high-level, convenience way of registering to D-Bus signals that abstracts
* from the D-Bus message concept. Signal arguments are automatically serialized
* in a message and D-Bus signatures automatically deduced from the parameters
* of the provided native signal callback.
*
* A signal can be subscribed to and unsubscribed from at any time during proxy
* lifetime. The subscription is active immediately after the call.
*
* Example of use:
* @code
* object_.uponSignal("stateChanged").onInterface("com.kistler.foo").call([this](int arg1, double arg2){ this->onStateChanged(arg1, arg2); });
* sdbus::InterfaceName foo{"com.kistler.foo"};
* sdbus::SignalName levelChanged{"levelChanged"};
* object_.uponSignal(levelChanged).onInterface(foo).call([this](uint16_t level){ this->onLevelChanged(level); });
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] SignalSubscriber uponSignal(const SignalName& signalName);
/*!
* @copydoc IProxy::uponSignal(const SignalName&)
*/
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
/*!
* @copydoc IProxy::uponSignal(const SignalName&)
*/
[[nodiscard]] SignalSubscriber uponSignal(const char* signalName);
/*!
* @brief Gets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept. sdbus::Variant is returned which shall then be converted
* to the real property type (implicit conversion is supported).
*
* Example of use:
* @code
* int state = object.getProperty("state").onInterface("com.kistler.foo");
* sdbus::InterfaceName foo{"com.kistler.foo"};
* sdbus::PropertyName level{"level"};
* int level = object.getProperty(level).onInterface(foo);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertyGetter getProperty(const PropertyName& propertyName);
/*!
* @copydoc IProxy::getProperty(const PropertyName&)
*/
[[nodiscard]] PropertyGetter getProperty(std::string_view propertyName);
/*!
* @brief Gets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous getting of property value
*
* This is a high-level, convenience way of reading D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* std::future<sdbus::Variant> state = object.getPropertyAsync("state").onInterface("com.kistler.foo").getResultAsFuture();
* auto callback = [](std::optional<sdbus::Error> err, const sdbus::Variant& value){ ... };
* object.getPropertyAsync("state").onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncPropertyGetter getPropertyAsync(const PropertyName& propertyName);
/*!
* @copydoc IProxy::getPropertyAsync(const PropertyName&)
*/
[[nodiscard]] AsyncPropertyGetter getPropertyAsync(std::string_view propertyName);
/*!
* @brief Sets value of a property of the D-Bus object
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
* Setting property value with NoReply flag is also supported.
*
* Example of use:
* @code
* int state = ...;
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state);
* // Or we can just send the set message call without waiting for the reply
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(state, dont_expect_reply);
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] PropertySetter setProperty(const PropertyName& propertyName);
/*!
* @copydoc IProxy::setProperty(const PropertyName&)
*/
[[nodiscard]] PropertySetter setProperty(std::string_view propertyName);
/*!
* @brief Sets value of a property of the D-Bus object asynchronously
*
* @param[in] propertyName Name of the property
* @return A helper object for convenient asynchronous setting of property value
*
* This is a high-level, convenience way of writing D-Bus property values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* int state = ...;
* // We can wait until the set operation finishes by waiting on the future
* std::future<void> res = object_.setPropertyAsync("state").onInterface("com.kistler.foo").toValue(state).getResultAsFuture();
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncPropertySetter setPropertyAsync(const PropertyName& propertyName);
/*!
* @copydoc IProxy::setPropertyAsync(const PropertyName&)
*/
[[nodiscard]] AsyncPropertySetter setPropertyAsync(std::string_view propertyName);
/*!
* @brief Gets values of all properties of the D-Bus object
*
* @return A helper object for convenient getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto props = object.getAllProperties().onInterface("com.kistler.foo");
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AllPropertiesGetter getAllProperties();
/*!
* @brief Gets values of all properties of the D-Bus object asynchronously
*
* @return A helper object for convenient asynchronous getting of properties' values
*
* This is a high-level, convenience way of reading D-Bus properties' values that abstracts
* from the D-Bus message concept.
*
* Example of use:
* @code
* auto callback = [](std::optional<sdbus::Error> err, const std::map<PropertyName, Variant>>& properties){ ... };
* auto props = object.getAllPropertiesAsync().onInterface("com.kistler.foo").uponReplyInvoke(std::move(callback));
* @endcode
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] AsyncAllPropertiesGetter getAllPropertiesAsync();
/*!
* @brief Provides D-Bus connection used by the proxy
*
* @return Reference to the D-Bus connection
*/
[[nodiscard]] virtual sdbus::IConnection& getConnection() const = 0;
/*!
* @brief Returns object path of the underlying DBus object
*/
[[nodiscard]] virtual const ObjectPath& getObjectPath() const = 0;
/*!
* @brief Provides access to the currently processed D-Bus message
*
* This method provides access to the currently processed incoming D-Bus message.
* "Currently processed" means that the registered callback handler(s) for that message
* are being invoked. This method is meant to be called from within a callback handler
* (e.g. from a D-Bus signal handler, or async method reply handler, etc.). In such a case it is
* guaranteed to return a valid D-Bus message instance for which the handler is called.
* If called from other contexts/threads, it may return a valid or invalid message, depending
* on whether a message was processed or not at the time of the call.
*
* @return Currently processed D-Bus message
*/
[[nodiscard]] virtual Message getCurrentlyProcessedMessage() const = 0;
/*!
* @brief Unregisters proxy's signal handlers and stops receiving replies to pending async calls
*
* Unregistration is done automatically also in proxy's destructor. This method makes
* sense if, in the process of proxy removal, we need to make sure that callbacks
* are unregistered explicitly before the final destruction of the proxy instance.
*
* @throws sdbus::Error in case of failure
*/
virtual void unregister() = 0;
public: // Lower-level, message-based API
/*!
* @brief Creates a method call message
*
* @param[in] interfaceName Name of an interface that provides a given method
* @param[in] methodName Name of the method
* @return A method call message
*
* Serialize method arguments into the returned message and invoke the method by passing
* the message with serialized arguments to the @c callMethod function.
* Alternatively, use higher-level API @c callMethod(const std::string& methodName) defined below.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) const = 0;
/*!
* @brief Calls method on the remote D-Bus object
*
* @param[in] message Message representing a method call
* @return A method reply message
*
* The call does not block if the method call has dont-expect-reply flag set. In that case,
* the call returns immediately and the return value is an empty, invalid method reply.
*
* The call blocks otherwise, waiting for the remote peer to send back a reply or an error,
* or until the call times out.
*
* While blocking, other concurrent operations (in other threads) on the underlying bus
* connection are stalled until the call returns. This is not an issue in vast majority of
* (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving
* shared bus connections, this may be an issue. It is advised to instead use an asynchronous
* callMethod() function overload, which does not block the bus connection, or do the synchronous
* call from another Proxy instance created just before the call and then destroyed (which is
* anyway quite a typical approach in D-Bus implementations). Such proxy instance must have
* its own bus connection. So-called light-weight proxies (ones running without an event loop thread)
* tag are designed for exactly that purpose.
*
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure (also in case the remote function returned an error)
*/
virtual MethodReply callMethod(const MethodCall& message) = 0;
/*!
* @brief Calls method on the remote D-Bus object
*
* @param[in] message Message representing a method call
* @param[in] timeout Method call timeout (in microseconds)
* @return A method reply message
*
* The call does not block if the method call has dont-expect-reply flag set. In that case,
* the call returns immediately and the return value is an empty, invalid method reply.
*
* The call blocks otherwise, waiting for the remote peer to send back a reply or an error,
* or until the call times out.
*
* While blocking, other concurrent operations (in other threads) on the underlying bus
* connection are stalled until the call returns. This is not an issue in vast majority of
* (simple, single-threaded) applications. In asynchronous, multi-threaded designs involving
* shared bus connections, this may be an issue. It is advised to instead use an asynchronous
* callMethod() function overload, which does not block the bus connection, or do the synchronous
* call from another Proxy instance created just before the call and then destroyed (which is
* anyway quite a typical approach in D-Bus implementations). Such proxy instance must have
* its own bus connection. So-called light-weight proxies (ones running without an event loop thread)
* tag are designed for exactly that purpose.
*
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure (also in case the remote function returned an error)
*/
virtual MethodReply callMethod(const MethodCall& message, uint64_t timeout) = 0;
/*!
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t)
*/
template <typename _Rep, typename _Period>
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
/*!
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
* @return Observing handle for the the pending asynchronous call
*
* This is a callback-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the bus
* connection I/O event loop thread.
*
* An non-owning, observing async call handle is returned that can be used to query call status or cancel the call.
*
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) = 0;
/*!
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
* @return RAII-style slot handle representing the ownership of the async call
*
* This is a callback-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the bus
* connection I/O event loop thread.
*
* A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot.
* The slot can be used to cancel the method call at a later time by simply destroying it.
*
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, return_slot_t ) = 0;
/*!
* @brief Calls method on the D-Bus object asynchronously, with custom timeout
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
* @param[in] timeout Method call timeout (in microseconds)
* @return Observing handle for the the pending asynchronous call
*
* This is a callback-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the bus
* connection I/O event loop thread.
*
* An non-owning, observing async call handle is returned that can be used to query call status or cancel the call.
*
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual PendingAsyncCall callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout ) = 0;
/*!
* @brief Calls method on the D-Bus object asynchronously, with custom timeout
*
* @param[in] message Message representing an async method call
* @param[in] asyncReplyCallback Handler for the async reply
* @param[in] timeout Method call timeout (in microseconds)
* @return RAII-style slot handle representing the ownership of the async call
*
* This is a callback-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided async reply handler will get invoked from the context of the bus
* connection I/O event loop thread.
*
* A slot (an owning handle) is returned for the async call. Lifetime of the call is bound to the lifetime of the slot.
* The slot can be used to cancel the method call at a later time by simply destroying it.
*
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use API on a higher level of abstraction defined below.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout
, return_slot_t ) = 0;
/*!
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t)
*/
template <typename _Rep, typename _Period>
PendingAsyncCall callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, const std::chrono::duration<_Rep, _Period>& timeout );
/*!
* @copydoc IProxy::callMethod(const MethodCall&,async_reply_handler,uint64_t,return_slot_t)
*/
template <typename _Rep, typename _Period>
[[nodiscard]] Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, const std::chrono::duration<_Rep, _Period>& timeout
, return_slot_t );
/*!
* @brief Calls method on the D-Bus object asynchronously
*
* @param[in] message Message representing an async method call
* @param[in] Tag denoting a std::future-based overload
* @return Future object providing access to the future method reply message
*
* This is a std::future-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided future object will be set to contain the reply (or sdbus::Error
* in case the remote method threw an exception).
*
* The default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual std::future<MethodReply> callMethodAsync(const MethodCall& message, with_future_t) = 0;
/*!
* @brief Calls method on the D-Bus object asynchronously, with custom timeout
*
* @param[in] message Message representing an async method call
* @param[in] timeout Method call timeout
* @param[in] Tag denoting a std::future-based overload
* @return Future object providing access to the future method reply message
*
* This is a std::future-based way of asynchronously calling a remote D-Bus method.
*
* The call itself is non-blocking. It doesn't wait for the reply. Once the reply arrives,
* the provided future object will be set to contain the reply (or sdbus::Error
* in case the remote method threw an exception, or the call timed out).
*
* If timeout is zero, the default D-Bus method call timeout is used. See IConnection::getMethodCallTimeout().
*
* Note: To avoid messing with messages, use higher-level API defined below.
*
* @throws sdbus::Error in case of failure
*/
virtual std::future<MethodReply> callMethodAsync( const MethodCall& message
, uint64_t timeout
, with_future_t ) = 0;
/*!
* @copydoc IProxy::callMethod(const MethodCall&,uint64_t,with_future_t)
*/
template <typename _Rep, typename _Period>
std::future<MethodReply> callMethodAsync( const MethodCall& message
, const std::chrono::duration<_Rep, _Period>& timeout
, with_future_t );
/*!
* @brief Registers a handler for the desired signal emitted by the D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* A signal can be subscribed to at any time during proxy lifetime. The subscription
* is active immediately after the call, and stays active for the entire lifetime
* of the Proxy object.
*
* To be able to unsubscribe from the signal at a later time, use the registerSignalHandler()
* overload with request_slot tag.
*
* @throws sdbus::Error in case of failure
*/
virtual void registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler ) = 0;
/*!
* @brief Registers a handler for the desired signal emitted by the D-Bus object
*
* @param[in] interfaceName Name of an interface that the signal belongs to
* @param[in] signalName Name of the signal
* @param[in] signalHandler Callback that implements the body of the signal handler
*
* @return RAII-style slot handle representing the ownership of the subscription
*
* A signal can be subscribed to and unsubscribed from at any time during proxy
* lifetime. The subscription is active immediately after the call. The lifetime
* of the subscription is bound to the lifetime of the slot object. The subscription
* is unregistered by letting go of the slot object.
*
* @throws sdbus::Error in case of failure
*/
[[nodiscard]] virtual Slot registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler
, return_slot_t ) = 0;
protected: // Internal API for efficiency reasons used by high-level API helper classes
friend MethodInvoker;
friend AsyncMethodInvoker;
friend SignalSubscriber;
[[nodiscard]] virtual MethodCall createMethodCall(const char* interfaceName, const char* methodName) const = 0;
virtual void registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler ) = 0;
[[nodiscard]] virtual Slot registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler
, return_slot_t ) = 0;
};
/********************************************//**
* @class PendingAsyncCall
*
* PendingAsyncCall represents a simple handle type to cancel the delivery
* of the asynchronous D-Bus call result to the application.
*
* The handle is lifetime-independent from the originating Proxy object.
* It's safe to call its methods even after the Proxy has gone.
*
***********************************************/
class PendingAsyncCall
{
public:
PendingAsyncCall() = default;
/*!
* @brief Cancels the delivery of the pending asynchronous call result
*
* This function effectively removes the callback handler registered to the
* async D-Bus method call result delivery. Does nothing if the call was
* completed already, or if the originating Proxy object has gone meanwhile.
*/
void cancel();
/*!
* @brief Answers whether the asynchronous call is still pending
*
* @return True if the call is pending, false if the call has been fully completed
*
* Pending call in this context means a call whose results have not arrived, or
* have arrived and are currently being processed by the callback handler.
*/
[[nodiscard]] bool isPending() const;
private:
friend internal::Proxy;
PendingAsyncCall(std::weak_ptr<void> callInfo);
private:
std::weak_ptr<void> callInfo_;
};
// Out-of-line member definitions
template <typename _Rep, typename _Period>
inline MethodReply IProxy::callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout)
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethod(message, microsecs.count());
}
template <typename _Rep, typename _Period>
inline PendingAsyncCall IProxy::callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, const std::chrono::duration<_Rep, _Period>& timeout )
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count());
}
template <typename _Rep, typename _Period>
inline Slot IProxy::callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, const std::chrono::duration<_Rep, _Period>& timeout
, return_slot_t )
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethodAsync(message, std::move(asyncReplyCallback), microsecs.count(), return_slot);
}
template <typename _Rep, typename _Period>
inline std::future<MethodReply> IProxy::callMethodAsync( const MethodCall& message
, const std::chrono::duration<_Rep, _Period>& timeout
, with_future_t )
{
auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
return callMethodAsync(message, microsecs.count(), with_future);
}
inline MethodInvoker IProxy::callMethod(const MethodName& methodName)
{
return MethodInvoker(*this, methodName);
}
inline MethodInvoker IProxy::callMethod(const std::string& methodName)
{
return MethodInvoker(*this, methodName.c_str());
}
inline MethodInvoker IProxy::callMethod(const char* methodName)
{
return MethodInvoker(*this, methodName);
}
inline AsyncMethodInvoker IProxy::callMethodAsync(const MethodName& methodName)
{
return AsyncMethodInvoker(*this, methodName);
}
inline AsyncMethodInvoker IProxy::callMethodAsync(const std::string& methodName)
{
return AsyncMethodInvoker(*this, methodName.c_str());
}
inline AsyncMethodInvoker IProxy::callMethodAsync(const char* methodName)
{
return AsyncMethodInvoker(*this, methodName);
}
inline SignalSubscriber IProxy::uponSignal(const SignalName& signalName)
{
return SignalSubscriber(*this, signalName);
}
inline SignalSubscriber IProxy::uponSignal(const std::string& signalName)
{
return SignalSubscriber(*this, signalName.c_str());
}
inline SignalSubscriber IProxy::uponSignal(const char* signalName)
{
return SignalSubscriber(*this, signalName);
}
inline PropertyGetter IProxy::getProperty(const PropertyName& propertyName)
{
return PropertyGetter(*this, propertyName);
}
inline PropertyGetter IProxy::getProperty(std::string_view propertyName)
{
return PropertyGetter(*this, std::move(propertyName));
}
inline AsyncPropertyGetter IProxy::getPropertyAsync(const PropertyName& propertyName)
{
return AsyncPropertyGetter(*this, propertyName);
}
inline AsyncPropertyGetter IProxy::getPropertyAsync(std::string_view propertyName)
{
return AsyncPropertyGetter(*this, std::move(propertyName));
}
inline PropertySetter IProxy::setProperty(const PropertyName& propertyName)
{
return PropertySetter(*this, propertyName);
}
inline PropertySetter IProxy::setProperty(std::string_view propertyName)
{
return PropertySetter(*this, std::move(propertyName));
}
inline AsyncPropertySetter IProxy::setPropertyAsync(const PropertyName& propertyName)
{
return AsyncPropertySetter(*this, propertyName);
}
inline AsyncPropertySetter IProxy::setPropertyAsync(std::string_view propertyName)
{
return AsyncPropertySetter(*this, std::move(propertyName));
}
inline AllPropertiesGetter IProxy::getAllProperties()
{
return AllPropertiesGetter(*this);
}
inline AsyncAllPropertiesGetter IProxy::getAllPropertiesAsync()
{
return AsyncAllPropertiesGetter(*this);
}
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the proxy object instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection. The caller still
* remains the owner of the connection (the proxy just keeps a reference to it), and
* should make sure that an I/O event loop is running on that connection, so the proxy
* may receive incoming signals and asynchronous method replies.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(connection, "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( sdbus::IConnection& connection
, ServiceName destination
, ObjectPath objectPath );
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object,
* and signals, if any, will be subscribed to on this connection. The Object proxy becomes
* an exclusive owner of this connection, and will automatically start a procesing loop
* upon that connection in a separate internal thread. Handlers for incoming signals and
* asynchronous method replies will be executed in the context of that thread.
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<sdbus::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath );
/*!
* @brief Creates a light-weight proxy object for a specific remote D-Bus object
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* The provided connection will be used by the proxy to issue calls against the object.
* The Object proxy becomes an exclusive owner of this connection, but will not start
* an event loop thread on this connection. This is cheap construction and is suitable
* for short-lived proxies created just to execute simple synchronous D-Bus calls and
* then destroyed. Such blocking request-reply calls will work without an event loop
* (but signals, async calls, etc. won't).
*
* The destination parameter may be an empty string (useful e.g. in case of direct
* D-Bus connections to a custom server bus).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy(std::move(connection), "com.kistler.foo", "/com/kistler/foo", sdbus::dont_run_event_loop_thread);
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<sdbus::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t );
/*!
* @brief Creates a light-weight proxy object for a specific remote D-Bus object
*
* Does the same thing as createProxy(std::unique_ptr<sdbus::IConnection>&&, ServiceName, ObjectPath, dont_run_event_loop_thread_t);
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createLightWeightProxy( std::unique_ptr<sdbus::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath );
/*!
* @brief Creates a proxy object for a specific remote D-Bus object
*
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* No D-Bus connection is provided here, so the object proxy will create and manage
* his own connection, and will automatically start an event loop upon that connection
* in a separate internal thread. Handlers for incoming signals and asynchronous
* method replies will be executed in the context of that thread.
*
* Code example:
* @code
* auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo");
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath );
/*!
* @brief Creates a light-weight proxy object for a specific remote D-Bus object
*
* @param[in] destination Bus name that provides the remote D-Bus object
* @param[in] objectPath Path of the remote D-Bus object
* @return Pointer to the object proxy instance
*
* No D-Bus connection is provided here, so the object proxy will create and manage
* his own connection, but it will not start an event loop thread. This is cheap
* construction and is suitable for short-lived proxies created just to execute simple
* synchronous D-Bus calls and then destroyed. Such blocking request-reply calls
* will work without an event loop (but signals, async calls, etc. won't).
*
* Code example:
* @code
* auto proxy = sdbus::createProxy("com.kistler.foo", "/com/kistler/foo", sdbus::dont_run_event_loop_thread );
* @endcode
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t );
/*!
* @brief Creates a light-weight proxy object for a specific remote D-Bus object
*
* Does the same thing as createProxy(ServiceName, ObjectPath, dont_run_event_loop_thread_t);
*/
[[nodiscard]] std::unique_ptr<sdbus::IProxy> createLightWeightProxy(ServiceName destination, ObjectPath objectPath);
}
#include <sdbus-c++/ConvenienceApiClasses.inl>
#endif /* SDBUS_CXX_IPROXY_H_ */

View File

@ -1,120 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Interfaces.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERFACES_H_
#define SDBUS_CXX_INTERFACES_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
template <typename _Object>
class ObjectHolder
{
protected:
ObjectHolder(std::unique_ptr<_Object>&& object)
: object_(std::move(object))
{
}
const _Object& getObject() const
{
assert(object_ != nullptr);
return *object_;
}
_Object& getObject()
{
assert(object_ != nullptr);
return *object_;
}
private:
std::unique_ptr<_Object> object_;
};
/********************************************//**
* @class Interfaces
*
* A helper template class that a user class representing a D-Bus object
* should inherit from, providing as template arguments the adaptor
* classes representing D-Bus interfaces that the object implements.
*
***********************************************/
template <typename... _Interfaces>
class Interfaces
: private ObjectHolder<IObject>
, public _Interfaces...
{
public:
Interfaces(IConnection& connection, std::string objectPath)
: ObjectHolder<IObject>(createObject(connection, std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
};
/********************************************//**
* @class Interfaces
*
* A helper template class that a user class representing a proxy of a
* D-Bus object should inherit from, providing as template arguments the proxy
* classes representing object D-Bus interfaces that the object implements.
*
***********************************************/
template <typename... _Interfaces>
class ProxyInterfaces
: private ObjectHolder<IObjectProxy>
, public _Interfaces...
{
public:
ProxyInterfaces(std::string destination, std::string objectPath)
: ObjectHolder<IObjectProxy>(createObjectProxy(std::move(destination), std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
ProxyInterfaces(IConnection& connection, std::string destination, std::string objectPath)
: ObjectHolder<IObjectProxy>(createObjectProxy(connection, std::move(destination), std::move(objectPath)))
, _Interfaces(getObject())...
{
getObject().finishRegistration();
}
};
}
#endif /* SDBUS_CXX_INTERFACES_H_ */

View File

@ -1,84 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Introspection.h
*
* Created on: Dec 13, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTROSPECTION_H_
#define SDBUS_CXX_INTROSPECTION_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <string>
namespace sdbus {
// Proxy for introspection
class introspectable_proxy
{
static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable";
protected:
introspectable_proxy(sdbus::IObjectProxy& object)
: object_(object)
{
}
public:
std::string Introspect()
{
std::string xml;
object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml);
return xml;
}
private:
sdbus::IObjectProxy& object_;
};
// Adaptor is not necessary if we want to rely on sdbus-provided introspection
// class introspectable_adaptor
// {
// static constexpr const char* interfaceName = "org.freedesktop.DBus.Introspectable";
//
// protected:
// introspectable_adaptor(sdbus::IObject& object)
// : object_(object)
// {
// object_.registerMethod("Introspect").onInterface(interfaceName).implementedAs([this](){ return object_.introspect(); });
// }
//
// public:
// std::string introspect()
// {
// std::string xml;
// object_.callMethod("Introspect").onInterface(interfaceName).storeResultsTo(xml);
// return xml;
// }
//
// private:
// sdbus::IObject& object_;
// };
}
#endif /* SDBUS_CXX_INTROSPECTION_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Message.h
*
@ -26,17 +27,27 @@
#ifndef SDBUS_CXX_MESSAGE_H_
#define SDBUS_CXX_MESSAGE_H_
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Error.h>
#include <string>
#include <vector>
#include <map>
#include <memory>
#include <utility>
#include <cstdint>
#include <cassert>
#include <sdbus-c++/TypeTraits.h>
#include <iostream>
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <functional>
#include <map>
#ifdef __has_include
# if __has_include(<span>)
# include <span>
# endif
#endif
#include <string>
#include <sys/types.h>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
// Forward declarations
namespace sdbus {
@ -44,6 +55,11 @@ namespace sdbus {
class ObjectPath;
class Signature;
template <typename... _ValueTypes> class Struct;
class UnixFd;
class MethodReply;
namespace internal {
class IConnection;
}
}
namespace sdbus {
@ -52,29 +68,18 @@ namespace sdbus {
* @class Message
*
* Message represents a D-Bus message, which can be either method call message,
* method reply message, signal message, or a plain message serving as a storage
* for serialized data.
* method reply message, signal message, or a plain message.
*
* Serialization and deserialization functions are provided for types supported
* by D-Bus.
*
* You don't need to work with this class directly if you use high-level APIs
* of @c IObject and @c IObjectProxy.
* You mostly don't need to work with this class directly if you use high-level
* APIs of @c IObject and @c IProxy.
*
***********************************************/
class Message
class [[nodiscard]] Message
{
public:
enum class Type
{
ePlainMessage
, eMethodCall
, eMethodReply
, eSignal
};
Message() = default;
Message(void *msg, Type type = Type::ePlainMessage) noexcept;
Message(const Message&) noexcept;
Message& operator=(const Message&) noexcept;
Message(Message&& other) noexcept;
@ -92,9 +97,33 @@ namespace sdbus {
Message& operator<<(double item);
Message& operator<<(const char *item);
Message& operator<<(const std::string &item);
Message& operator<<(std::string_view item);
Message& operator<<(const Variant &item);
template <typename ...Elements>
Message& operator<<(const std::variant<Elements...>& value);
Message& operator<<(const ObjectPath &item);
Message& operator<<(const Signature &item);
Message& operator<<(const UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator<<(const std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator<<(const std::array<_Element, _Size>& items);
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
Message& operator<<(const std::span<_Element, _Extent>& items);
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator<<(const _Enum& item);
template <typename _Key, typename _Value>
Message& operator<<(const DictEntry<_Key, _Value>& value);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator<<(const Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator<<(const std::tuple<_ValueTypes...>& item);
Message& operator>>(bool& item);
Message& operator>>(int16_t& item);
@ -108,82 +137,322 @@ namespace sdbus {
Message& operator>>(char*& item);
Message& operator>>(std::string &item);
Message& operator>>(Variant &item);
template <typename ...Elements>
Message& operator>>(std::variant<Elements...>& value);
Message& operator>>(ObjectPath &item);
Message& operator>>(Signature &item);
Message& operator>>(UnixFd &item);
template <typename _Element, typename _Allocator>
Message& operator>>(std::vector<_Element, _Allocator>& items);
template <typename _Element, std::size_t _Size>
Message& operator>>(std::array<_Element, _Size>& items);
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
Message& operator>>(std::span<_Element, _Extent>& items);
#endif
template <typename _Enum, typename = std::enable_if_t<std::is_enum_v<_Enum>>>
Message& operator>>(_Enum& item);
template <typename _Key, typename _Value>
Message& operator>>(DictEntry<_Key, _Value>& value);
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
Message& operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items);
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
Message& operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items);
template <typename... _ValueTypes>
Message& operator>>(Struct<_ValueTypes...>& item);
template <typename... _ValueTypes>
Message& operator>>(std::tuple<_ValueTypes...>& item);
Message& openContainer(const std::string& signature);
template <typename _ElementType>
Message& openContainer();
Message& openContainer(const char* signature);
Message& closeContainer();
Message& openDictEntry(const std::string& signature);
template <typename _KeyType, typename _ValueType>
Message& openDictEntry();
Message& openDictEntry(const char* signature);
Message& closeDictEntry();
Message& openVariant(const std::string& signature);
template <typename _ValueType>
Message& openVariant();
Message& openVariant(const char* signature);
Message& closeVariant();
Message& openStruct(const std::string& signature);
template <typename... _ValueTypes>
Message& openStruct();
Message& openStruct(const char* signature);
Message& closeStruct();
Message& enterContainer(const std::string& signature);
template <typename _ElementType>
Message& enterContainer();
Message& enterContainer(const char* signature);
Message& exitContainer();
Message& enterDictEntry(const std::string& signature);
template <typename _KeyType, typename _ValueType>
Message& enterDictEntry();
Message& enterDictEntry(const char* signature);
Message& exitDictEntry();
Message& enterVariant(const std::string& signature);
template <typename _ValueType>
Message& enterVariant();
Message& enterVariant(const char* signature);
Message& exitVariant();
Message& enterStruct(const std::string& signature);
template <typename... _ValueTypes>
Message& enterStruct();
Message& enterStruct(const char* signature);
Message& exitStruct();
operator bool() const;
Message& appendArray(char type, const void *ptr, size_t size);
Message& readArray(char type, const void **ptr, size_t *size);
template <typename _Key, typename _Value, typename _Callback>
Message& serializeDictionary(const _Callback& callback);
template <typename _Key, typename _Value>
Message& serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& dictEntries);
template <typename _Key, typename _Value, typename _Callback>
Message& deserializeDictionary(const _Callback& callback);
explicit operator bool() const;
void clearFlags();
std::string getInterfaceName() const;
std::string getMemberName() const;
void peekType(std::string& type, std::string& contents) const;
const char* getInterfaceName() const;
const char* getMemberName() const;
const char* getSender() const;
const char* getPath() const;
const char* getDestination() const;
uint64_t getCookie() const;
// TODO: short docs in whole Message API
std::pair<char, const char*> peekType() const;
bool isValid() const;
bool isEmpty() const;
Type getType() const;
bool isAtEnd(bool complete) const;
void copyTo(Message& destination, bool complete) const;
void seal();
void rewind(bool complete);
Message createReply() const;
Message send() const;
pid_t getCredsPid() const;
uid_t getCredsUid() const;
uid_t getCredsEuid() const;
gid_t getCredsGid() const;
gid_t getCredsEgid() const;
std::vector<gid_t> getCredsSupplementaryGids() const;
std::string getSELinuxContext() const;
class Factory;
private:
template <typename _Array>
void serializeArray(const _Array& items);
template <typename _Array>
void deserializeArray(_Array& items);
template <typename _Array>
void deserializeArrayFast(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArrayFast(std::vector<_Element, _Allocator>& items);
template <typename _Array>
void deserializeArraySlow(_Array& items);
template <typename _Element, typename _Allocator>
void deserializeArraySlow(std::vector<_Element, _Allocator>& items);
protected:
Message() = default;
explicit Message(internal::IConnection* connection) noexcept;
Message(void *msg, internal::IConnection* connection) noexcept;
Message(void *msg, internal::IConnection* connection, adopt_message_t) noexcept;
friend Factory;
protected:
void* msg_{};
Type type_{Type::ePlainMessage};
internal::IConnection* connection_{};
mutable bool ok_{true};
};
template <typename _Element>
inline Message& operator<<(Message& msg, const std::vector<_Element>& items)
class MethodCall : public Message
{
msg.openContainer(signature_of<_Element>::str());
using Message::Message;
friend Factory;
for (const auto& item : items)
msg << item;
public:
MethodCall() = default;
msg.closeContainer();
MethodReply send(uint64_t timeout) const;
[[nodiscard]] Slot send(void* callback, void* userData, uint64_t timeout, return_slot_t) const;
return msg;
MethodReply createReply() const;
MethodReply createErrorReply(const sdbus::Error& error) const;
void dontExpectReply();
bool doesntExpectReply() const;
protected:
MethodCall(void *msg, internal::IConnection* connection, adopt_message_t) noexcept;
private:
MethodReply sendWithReply(uint64_t timeout = 0) const;
MethodReply sendWithNoReply() const;
};
class MethodReply : public Message
{
using Message::Message;
friend Factory;
public:
MethodReply() = default;
void send() const;
uint64_t getReplyCookie() const;
};
class Signal : public Message
{
using Message::Message;
friend Factory;
public:
Signal() = default;
void setDestination(const std::string& destination);
void setDestination(const char* destination);
void send() const;
};
class PropertySetCall : public Message
{
using Message::Message;
friend Factory;
public:
PropertySetCall() = default;
};
class PropertyGetReply : public Message
{
using Message::Message;
friend Factory;
public:
PropertyGetReply() = default;
};
// Represents any of the above message types, or just a message that serves as a container for data
class PlainMessage : public Message
{
using Message::Message;
friend Factory;
public:
PlainMessage() = default;
};
PlainMessage createPlainMessage();
template <typename ...Elements>
inline Message& Message::operator<<(const std::variant<Elements...>& value)
{
std::visit([this](const auto& inner)
{
openVariant<decltype(inner)>();
*this << inner;
closeVariant();
}, value);
return *this;
}
template <typename _Element, typename _Allocator>
inline Message& Message::operator<<(const std::vector<_Element, _Allocator>& items)
{
serializeArray(items);
return *this;
}
template <typename _Element, std::size_t _Size>
inline Message& Message::operator<<(const std::array<_Element, _Size>& items)
{
serializeArray(items);
return *this;
}
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator<<(const std::span<_Element, _Extent>& items)
{
serializeArray(items);
return *this;
}
#endif
template <typename _Enum, typename>
inline Message& Message::operator<<(const _Enum &item)
{
return operator<<(static_cast<std::underlying_type_t<_Enum>>(item));
}
template <typename _Array>
inline void Message::serializeArray(const _Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step serialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step serialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
constexpr auto signature = as_null_terminated(signature_of_v<ElementType>);
appendArray(*signature.data(), items.data(), items.size() * sizeof(ElementType));
}
else
{
openContainer<ElementType>();
for (const auto& item : items)
*this << item;
closeContainer();
}
}
template <typename _Key, typename _Value>
inline Message& operator<<(Message& msg, const std::map<_Key, _Value>& items)
inline Message& Message::operator<<(const DictEntry<_Key, _Value>& value)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
openDictEntry<_Key, _Value>();
*this << value.first;
*this << value.second;
closeDictEntry();
msg.openContainer(arraySignature);
return *this;
}
for (const auto& item : items)
{
msg.openDictEntry(dictEntrySignature);
msg << item.first;
msg << item.second;
msg.closeDictEntry();
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator<<(const std::map<_Key, _Value, _Compare, _Allocator>& items)
{
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
msg.closeContainer();
return *this;
}
return msg;
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator<<(const std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
serializeDictionary<_Key, _Value>([&items](Message& msg){ for (const auto& item : items) msg << item; });
return *this;
}
template <typename _Key, typename _Value>
inline Message& Message::serializeDictionary(const std::initializer_list<DictEntry<_Key, _Value>>& items)
{
serializeDictionary<_Key, _Value>([&](Message& msg){ for (const auto& item : items) msg << item; });
return *this;
}
template <typename _Key, typename _Value, typename _Callback>
inline Message& Message::serializeDictionary(const _Callback& callback)
{
openContainer<DictEntry<_Key, _Value>>();
callback(*this);
closeContainer();
return *this;
}
namespace detail
@ -191,10 +460,7 @@ namespace sdbus {
template <typename... _Args>
void serialize_pack(Message& msg, _Args&&... args)
{
// Use initializer_list because it guarantees left to right order, and can be empty
using _ = std::initializer_list<int>;
// We are not interested in the list itself, but in the side effects
(void)_{(void(msg << std::forward<_Args>(args)), 0)...};
(void)(msg << ... << args);
}
template <class _Tuple, std::size_t... _Is>
@ -207,78 +473,216 @@ namespace sdbus {
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const Struct<_ValueTypes...>& item)
inline Message& Message::operator<<(const Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::str();
assert(structSignature.size() > 2);
// Remove opening and closing parenthesis from the struct signature to get contents signature
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
openStruct<_ValueTypes...>();
detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
closeStruct();
msg.openStruct(structContentSignature);
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
msg.closeStruct();
return msg;
return *this;
}
template <typename... _ValueTypes>
inline Message& operator<<(Message& msg, const std::tuple<_ValueTypes...>& item)
inline Message& Message::operator<<(const std::tuple<_ValueTypes...>& item)
{
detail::serialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
detail::serialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return *this;
}
template <typename _Element>
inline Message& operator>>(Message& msg, std::vector<_Element>& items)
namespace detail
{
if(!msg.enterContainer(signature_of<_Element>::str()))
return msg;
template <typename _Element, typename... _Elements>
bool deserialize_variant(Message& msg, std::variant<_Elements...>& value, const char* signature)
{
constexpr auto elemSignature = as_null_terminated(sdbus::signature_of_v<_Element>);
if (std::strcmp(signature, elemSignature.data()) != 0)
return false;
_Element temp;
msg.enterVariant(signature);
msg >> temp;
msg.exitVariant();
value = std::move(temp);
return true;
}
}
template <typename... Elements>
inline Message& Message::operator>>(std::variant<Elements...>& value)
{
auto [type, contents] = peekType();
bool result = (detail::deserialize_variant<Elements>(*this, value, contents) || ...);
SDBUS_THROW_ERROR_IF(!result, "Failed to deserialize variant: signature did not match any of the variant types", EINVAL);
return *this;
}
template <typename _Element, typename _Allocator>
inline Message& Message::operator>>(std::vector<_Element, _Allocator>& items)
{
deserializeArray(items);
return *this;
}
template <typename _Element, std::size_t _Size>
inline Message& Message::operator>>(std::array<_Element, _Size>& items)
{
deserializeArray(items);
return *this;
}
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
inline Message& Message::operator>>(std::span<_Element, _Extent>& items)
{
deserializeArray(items);
return *this;
}
#endif
template <typename _Enum, typename>
inline Message& Message::operator>>(_Enum& item)
{
std::underlying_type_t<_Enum> val;
*this >> val;
item = static_cast<_Enum>(val);
return *this;
}
template <typename _Array>
inline void Message::deserializeArray(_Array& items)
{
using ElementType = typename _Array::value_type;
// Use faster, one-step deserialization of contiguous array of elements of trivial D-Bus types except bool,
// otherwise use step-by-step deserialization of individual elements.
if constexpr (signature_of<ElementType>::is_trivial_dbus_type && !std::is_same_v<ElementType, bool>)
{
deserializeArrayFast(items);
}
else
{
deserializeArraySlow(items);
}
}
template <typename _Array>
inline void Message::deserializeArrayFast(_Array& items)
{
using ElementType = typename _Array::value_type;
size_t arraySize{};
const ElementType* arrayPtr{};
constexpr auto signature = as_null_terminated(sdbus::signature_of_v<ElementType>);
readArray(*signature.data(), (const void**)&arrayPtr, &arraySize);
size_t elementsInMsg = arraySize / sizeof(ElementType);
bool notEnoughSpace = items.size() < elementsInMsg;
SDBUS_THROW_ERROR_IF(notEnoughSpace, "Failed to deserialize array: not enough space in destination sequence", EINVAL);
std::copy_n(arrayPtr, elementsInMsg, items.begin());
}
template <typename _Element, typename _Allocator>
void Message::deserializeArrayFast(std::vector<_Element, _Allocator>& items)
{
size_t arraySize{};
const _Element* arrayPtr{};
constexpr auto signature = as_null_terminated(sdbus::signature_of_v<_Element>);
readArray(*signature.data(), (const void**)&arrayPtr, &arraySize);
items.insert(items.end(), arrayPtr, arrayPtr + (arraySize / sizeof(_Element)));
}
template <typename _Array>
inline void Message::deserializeArraySlow(_Array& items)
{
using ElementType = typename _Array::value_type;
if(!enterContainer<ElementType>())
return;
for (auto& elem : items)
if (!(*this >> elem))
break; // Keep the rest in the destination sequence untouched
SDBUS_THROW_ERROR_IF(!isAtEnd(false), "Failed to deserialize array: not enough space in destination sequence", EINVAL);
clearFlags();
exitContainer();
}
template <typename _Element, typename _Allocator>
void Message::deserializeArraySlow(std::vector<_Element, _Allocator>& items)
{
if(!enterContainer<_Element>())
return;
while (true)
{
_Element elem;
if (msg >> elem)
if (*this >> elem)
items.emplace_back(std::move(elem));
else
break;
}
msg.clearFlags();
clearFlags();
msg.exitContainer();
return msg;
exitContainer();
}
template <typename _Key, typename _Value>
inline Message& operator>>(Message& msg, std::map<_Key, _Value>& items)
inline Message& Message::operator>>(DictEntry<_Key, _Value>& value)
{
const std::string dictEntrySignature = signature_of<_Key>::str() + signature_of<_Value>::str();
const std::string arraySignature = "{" + dictEntrySignature + "}";
if (!enterDictEntry<_Key, _Value>())
return *this;
*this >> value.first >> value.second;
exitDictEntry();
if (!msg.enterContainer(arraySignature))
return msg;
return *this;
}
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
inline Message& Message::operator>>(std::map<_Key, _Value, _Compare, _Allocator>& items)
{
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
return *this;
}
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
inline Message& Message::operator>>(std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>& items)
{
deserializeDictionary<_Key, _Value>([&items](auto dictEntry){ items.insert(std::move(dictEntry)); });
return *this;
}
template <typename _Key, typename _Value, typename _Callback>
inline Message& Message::deserializeDictionary(const _Callback& callback)
{
if (!enterContainer<DictEntry<_Key, _Value>>())
return *this;
while (true)
{
if (!msg.enterDictEntry(dictEntrySignature))
DictEntry<_Key, _Value> dictEntry;
*this >> dictEntry;
if (!*this)
break;
_Key key;
_Value value;
msg >> key >> value;
items.emplace(std::move(key), std::move(value));
msg.exitDictEntry();
callback(std::move(dictEntry));
}
clearFlags();
msg.clearFlags();
exitContainer();
msg.exitContainer();
return msg;
return *this;
}
namespace detail
@ -286,10 +690,7 @@ namespace sdbus {
template <typename... _Args>
void deserialize_pack(Message& msg, _Args&... args)
{
// Use initializer_list because it guarantees left to right order, and can be empty
using _ = std::initializer_list<int>;
// We are not interested in the list itself, but in the side effects
(void)_{(void(msg >> args), 0)...};
(void)(msg >> ... >> args);
}
template <class _Tuple, std::size_t... _Is>
@ -302,27 +703,79 @@ namespace sdbus {
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, Struct<_ValueTypes...>& item)
inline Message& Message::operator>>(Struct<_ValueTypes...>& item)
{
auto structSignature = signature_of<Struct<_ValueTypes...>>::str();
// Remove opening and closing parenthesis from the struct signature to get contents signature
auto structContentSignature = structSignature.substr(1, structSignature.size()-2);
if (!enterStruct<_ValueTypes...>())
return *this;
if (!msg.enterStruct(structContentSignature))
return msg;
detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
exitStruct();
msg.exitStruct();
return msg;
return *this;
}
template <typename... _ValueTypes>
inline Message& operator>>(Message& msg, std::tuple<_ValueTypes...>& item)
inline Message& Message::operator>>(std::tuple<_ValueTypes...>& item)
{
detail::deserialize_tuple(msg, item, std::index_sequence_for<_ValueTypes...>{});
return msg;
detail::deserialize_tuple(*this, item, std::index_sequence_for<_ValueTypes...>{});
return *this;
}
template <typename _ElementType>
inline Message& Message::openContainer()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>);
return openContainer(signature.data());
}
template <typename _KeyType, typename _ValueType>
inline Message& Message::openDictEntry()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_KeyType, _ValueType>>);
return openDictEntry(signature.data());
}
template <typename _ValueType>
inline Message& Message::openVariant()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>);
return openVariant(signature.data());
}
template <typename... _ValueTypes>
inline Message& Message::openStruct()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_ValueTypes...>>);
return openStruct(signature.data());
}
template <typename _ElementType>
inline Message& Message::enterContainer()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ElementType>);
return enterContainer(signature.data());
}
template <typename _KeyType, typename _ValueType>
inline Message& Message::enterDictEntry()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_KeyType, _ValueType>>);
return enterDictEntry(signature.data());
}
template <typename _ValueType>
inline Message& Message::enterVariant()
{
constexpr auto signature = as_null_terminated(signature_of_v<_ValueType>);
return enterVariant(signature.data());
}
template <typename... _ValueTypes>
inline Message& Message::enterStruct()
{
constexpr auto signature = as_null_terminated(signature_of_v<std::tuple<_ValueTypes...>>);
return enterStruct(signature.data());
}
}

View File

@ -0,0 +1,93 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file MethodResult.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_METHODRESULT_H_
#define SDBUS_CXX_METHODRESULT_H_
#include <sdbus-c++/Message.h>
#include <cassert>
// Forward declarations
namespace sdbus {
class Error;
}
namespace sdbus {
/********************************************//**
* @class Result
*
* Represents result of an asynchronous server-side method.
* An instance is provided to the method and shall be set
* by the method to either method return value or an error.
*
***********************************************/
template <typename... _Results>
class Result
{
public:
Result() = default;
Result(MethodCall call);
Result(const Result&) = delete;
Result& operator=(const Result&) = delete;
Result(Result&& other) = default;
Result& operator=(Result&& other) = default;
void returnResults(const _Results&... results) const;
void returnError(const Error& error) const;
private:
MethodCall call_;
};
template <typename... _Results>
inline Result<_Results...>::Result(MethodCall call)
: call_(std::move(call))
{
}
template <typename... _Results>
inline void Result<_Results...>::returnResults(const _Results&... results) const
{
assert(call_.isValid());
auto reply = call_.createReply();
(void)(reply << ... << results);
reply.send();
}
template <typename... _Results>
inline void Result<_Results...>::returnError(const Error& error) const
{
auto reply = call_.createErrorReply(error);
reply.send();
}
}
#endif /* SDBUS_CXX_METHODRESULT_H_ */

View File

@ -0,0 +1,217 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ProxyInterfaces.h
*
* Created on: Apr 8, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_PROXYINTERFACES_H_
#define SDBUS_CXX_PROXYINTERFACES_H_
#include <sdbus-c++/IProxy.h>
#include <cassert>
#include <string>
#include <memory>
// Forward declarations
namespace sdbus {
class IConnection;
}
namespace sdbus {
/********************************************//**
* @class ProxyObjectHolder
*
* ProxyObjectHolder is a helper that simply owns and provides
* access to a proxy object to other classes in the inheritance
* hierarchy of a native-like proxy object based on generated
* interface classes.
*
***********************************************/
class ProxyObjectHolder
{
protected:
ProxyObjectHolder(std::unique_ptr<IProxy>&& proxy)
: proxy_(std::move(proxy))
{
assert(proxy_ != nullptr);
}
const IProxy& getProxy() const
{
assert(proxy_ != nullptr);
return *proxy_;
}
IProxy& getProxy()
{
assert(proxy_ != nullptr);
return *proxy_;
}
private:
std::unique_ptr<IProxy> proxy_;
};
/********************************************//**
* @class ProxyInterfaces
*
* ProxyInterfaces is a helper template class that joins all interface classes of a remote
* D-Bus object generated by sdbus-c++-xml2cpp to be used on the client (the proxy) side,
* including some auxiliary classes. ProxyInterfaces is the class that native-like proxy
* implementation classes written by users should inherit from and implement all pure virtual
* methods. So the _Interfaces template parameter is a list of sdbus-c++-xml2cpp-generated
* proxy-side interface classes representing interfaces of the corresponding remote D-Bus object.
*
* In the final adaptor class inherited from ProxyInterfaces, one needs to make sure:
* 1. to call `registerProxy();` in the class constructor, and, conversely,
* 2. to call `unregisterProxy();` in the class destructor,
* so that the signals are subscribed to and unsubscribed from at a proper time.
*
***********************************************/
template <typename... _Interfaces>
class ProxyInterfaces
: protected ProxyObjectHolder
, public _Interfaces...
{
public:
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* This constructor overload creates a proxy that manages its own D-Bus connection(s).
* For more information on its behavior, consult @ref createProxy(std::string,std::string)
*/
ProxyInterfaces(ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* This constructor overload creates a proxy that manages its own D-Bus connection(s).
* For more information on its behavior, consult @ref createProxy(std::string,std::string,sdbus::dont_run_event_loop_thread_t)
*/
ProxyInterfaces(ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t)
: ProxyObjectHolder(createProxy(std::move(destination), std::move(objectPath), dont_run_event_loop_thread))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* The proxy created this way just references a D-Bus connection owned and managed by the user.
* For more information on its behavior, consult @ref createProxy(IConnection&,std::string,std::string)
*/
ProxyInterfaces(IConnection& connection, ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(connection, std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* The proxy created this way becomes an owner of the connection.
* For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string)
*/
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, ServiceName destination, ObjectPath objectPath)
: ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath)))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Creates native-like proxy object instance
*
* @param[in] connection D-Bus connection to be used by the proxy object
* @param[in] destination Bus name that provides a D-Bus object
* @param[in] objectPath Path of the D-Bus object
*
* The proxy created this way becomes an owner of the connection.
* For more information on its behavior, consult @ref createProxy(std::unique_ptr<sdbus::IConnection>&&,std::string,std::string,sdbus::dont_run_event_loop_thread_t)
*/
ProxyInterfaces(std::unique_ptr<sdbus::IConnection>&& connection, ServiceName destination, ObjectPath objectPath, dont_run_event_loop_thread_t)
: ProxyObjectHolder(createProxy(std::move(connection), std::move(destination), std::move(objectPath), dont_run_event_loop_thread))
, _Interfaces(getProxy())...
{
}
/*!
* @brief Registers handlers for D-Bus signals of the remote object
*
* This function must be called in the constructor of the final proxy class that implements ProxyInterfaces.
*
* See also @ref IProxy::registerSignalHandler()
*/
void registerProxy()
{
(_Interfaces::registerProxy(), ...);
}
/*!
* @brief Unregisters the proxy so it no more receives signals and async call replies
*
* This function must be called in the destructor of the final proxy class that implements ProxyInterfaces.
*
* See underlying @ref IProxy::unregister()
*/
void unregisterProxy()
{
getProxy().unregister();
}
/*!
* @brief Returns reference to the underlying IProxy instance
*/
using ProxyObjectHolder::getProxy;
protected:
using base_type = ProxyInterfaces;
ProxyInterfaces(const ProxyInterfaces&) = delete;
ProxyInterfaces& operator=(const ProxyInterfaces&) = delete;
ProxyInterfaces(ProxyInterfaces&&) = delete;
ProxyInterfaces& operator=(ProxyInterfaces&&) = delete;
~ProxyInterfaces() = default;
};
}
#endif /* SDBUS_CXX_INTERFACES_H_ */

View File

@ -0,0 +1,530 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file StandardInterfaces.h
*
* Created on: Dec 13, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_STANDARDINTERFACES_H_
#define SDBUS_CXX_STANDARDINTERFACES_H_
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/Types.h>
#include <string>
#include <string_view>
#include <map>
#include <vector>
namespace sdbus {
// Proxy for peer
class Peer_proxy
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Peer";
protected:
Peer_proxy(sdbus::IProxy& proxy)
: m_proxy(proxy)
{
}
Peer_proxy(const Peer_proxy&) = delete;
Peer_proxy& operator=(const Peer_proxy&) = delete;
Peer_proxy(Peer_proxy&&) = delete;
Peer_proxy& operator=(Peer_proxy&&) = delete;
~Peer_proxy() = default;
void registerProxy()
{
}
public:
void Ping()
{
m_proxy.callMethod("Ping").onInterface(INTERFACE_NAME);
}
std::string GetMachineId()
{
std::string machineUUID;
m_proxy.callMethod("GetMachineId").onInterface(INTERFACE_NAME).storeResultsTo(machineUUID);
return machineUUID;
}
private:
sdbus::IProxy& m_proxy;
};
// Proxy for introspection
class Introspectable_proxy
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Introspectable";
protected:
Introspectable_proxy(sdbus::IProxy& proxy)
: m_proxy(proxy)
{
}
Introspectable_proxy(const Introspectable_proxy&) = delete;
Introspectable_proxy& operator=(const Introspectable_proxy&) = delete;
Introspectable_proxy(Introspectable_proxy&&) = delete;
Introspectable_proxy& operator=(Introspectable_proxy&&) = delete;
~Introspectable_proxy() = default;
void registerProxy()
{
}
public:
std::string Introspect()
{
std::string xml;
m_proxy.callMethod("Introspect").onInterface(INTERFACE_NAME).storeResultsTo(xml);
return xml;
}
private:
sdbus::IProxy& m_proxy;
};
// Proxy for properties
class Properties_proxy
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties";
protected:
Properties_proxy(sdbus::IProxy& proxy)
: m_proxy(proxy)
{
}
Properties_proxy(const Properties_proxy&) = delete;
Properties_proxy& operator=(const Properties_proxy&) = delete;
Properties_proxy(Properties_proxy&&) = delete;
Properties_proxy& operator=(Properties_proxy&&) = delete;
~Properties_proxy() = default;
void registerProxy()
{
m_proxy
.uponSignal("PropertiesChanged")
.onInterface(INTERFACE_NAME)
.call([this]( const InterfaceName& interfaceName
, const std::map<PropertyName, sdbus::Variant>& changedProperties
, const std::vector<PropertyName>& invalidatedProperties )
{
this->onPropertiesChanged(interfaceName, changedProperties, invalidatedProperties);
});
}
virtual void onPropertiesChanged( const InterfaceName& interfaceName
, const std::map<PropertyName, sdbus::Variant>& changedProperties
, const std::vector<PropertyName>& invalidatedProperties ) = 0;
public:
sdbus::Variant Get(const InterfaceName& interfaceName, const PropertyName& propertyName)
{
return m_proxy.getProperty(propertyName).onInterface(interfaceName);
}
sdbus::Variant Get(std::string_view interfaceName, std::string_view propertyName)
{
return m_proxy.getProperty(propertyName).onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, _Function&& callback, return_slot_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<sdbus::Variant> GetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, with_future_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
template <typename _Function>
PendingAsyncCall GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetAsync(std::string_view interfaceName, std::string_view propertyName, _Function&& callback, return_slot_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<sdbus::Variant> GetAsync(std::string_view interfaceName, std::string_view propertyName, with_future_t)
{
return m_proxy.getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
}
void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value);
}
void Set(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
void Set(std::string_view interfaceName, const std::string_view propertyName, const sdbus::Variant& value, dont_expect_reply_t)
{
m_proxy.setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
}
template <typename _Function>
PendingAsyncCall SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<void> SetAsync(const InterfaceName& interfaceName, const PropertyName& propertyName, const sdbus::Variant& value, with_future_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
template <typename _Function>
PendingAsyncCall SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, _Function&& callback, return_slot_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<void> SetAsync(std::string_view interfaceName, std::string_view propertyName, const sdbus::Variant& value, with_future_t)
{
return m_proxy.setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
}
std::map<PropertyName, sdbus::Variant> GetAll(const InterfaceName& interfaceName)
{
return m_proxy.getAllProperties().onInterface(interfaceName);
}
std::map<PropertyName, sdbus::Variant> GetAll(std::string_view interfaceName)
{
return m_proxy.getAllProperties().onInterface(interfaceName);
}
template <typename _Function>
PendingAsyncCall GetAllAsync(const InterfaceName& interfaceName, _Function&& callback)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetAllAsync(const InterfaceName& interfaceName, _Function&& callback, return_slot_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(const InterfaceName& interfaceName, with_future_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
template <typename _Function>
PendingAsyncCall GetAllAsync(std::string_view interfaceName, _Function&& callback)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetAllAsync(std::string_view interfaceName, _Function&& callback, return_slot_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<std::map<PropertyName, sdbus::Variant>> GetAllAsync(std::string_view interfaceName, with_future_t)
{
return m_proxy.getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
}
private:
sdbus::IProxy& m_proxy;
};
// Proxy for object manager
class ObjectManager_proxy
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager";
protected:
ObjectManager_proxy(sdbus::IProxy& proxy)
: m_proxy(proxy)
{
}
ObjectManager_proxy(const ObjectManager_proxy&) = delete;
ObjectManager_proxy& operator=(const ObjectManager_proxy&) = delete;
ObjectManager_proxy(ObjectManager_proxy&&) = delete;
ObjectManager_proxy& operator=(ObjectManager_proxy&&) = delete;
~ObjectManager_proxy() = default;
void registerProxy()
{
m_proxy
.uponSignal("InterfacesAdded")
.onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath
, const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>& interfacesAndProperties )
{
this->onInterfacesAdded(objectPath, interfacesAndProperties);
});
m_proxy
.uponSignal("InterfacesRemoved")
.onInterface(INTERFACE_NAME)
.call([this]( const sdbus::ObjectPath& objectPath
, const std::vector<sdbus::InterfaceName>& interfaces )
{
this->onInterfacesRemoved(objectPath, interfaces);
});
}
virtual void onInterfacesAdded( const sdbus::ObjectPath& objectPath
, const std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>& interfacesAndProperties) = 0;
virtual void onInterfacesRemoved( const sdbus::ObjectPath& objectPath
, const std::vector<sdbus::InterfaceName>& interfaces) = 0;
public:
std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>> GetManagedObjects()
{
std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>> objectsInterfacesAndProperties;
m_proxy.callMethod("GetManagedObjects").onInterface(INTERFACE_NAME).storeResultsTo(objectsInterfacesAndProperties);
return objectsInterfacesAndProperties;
}
template <typename _Function>
PendingAsyncCall GetManagedObjectsAsync(_Function&& callback)
{
return m_proxy.callMethodAsync("GetManagedObjects").onInterface(INTERFACE_NAME).uponReplyInvoke(std::forward<_Function>(callback));
}
template <typename _Function>
[[nodiscard]] Slot GetManagedObjectsAsync(_Function&& callback, return_slot_t)
{
return m_proxy.callMethodAsync("GetManagedObjects").onInterface(INTERFACE_NAME).uponReplyInvoke(std::forward<_Function>(callback), return_slot);
}
std::future<std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>>> GetManagedObjectsAsync(with_future_t)
{
return m_proxy.callMethodAsync("GetManagedObjects").onInterface(INTERFACE_NAME).getResultAsFuture<std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<PropertyName, sdbus::Variant>>>>();
}
private:
sdbus::IProxy& m_proxy;
};
// Adaptors for the above-listed standard D-Bus interfaces are not necessary because the functionality
// is provided by underlying libsystemd implementation. The exception is Properties_adaptor,
// ObjectManager_adaptor and ManagedObject_adaptor, which provide convenience functionality to emit signals.
// Adaptor for properties
class Properties_adaptor
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.Properties";
protected:
Properties_adaptor(sdbus::IObject& object) : m_object(object)
{
}
Properties_adaptor(const Properties_adaptor&) = delete;
Properties_adaptor& operator=(const Properties_adaptor&) = delete;
Properties_adaptor(Properties_adaptor&&) = delete;
Properties_adaptor& operator=(Properties_adaptor&&) = delete;
~Properties_adaptor() = default;
void registerAdaptor()
{
}
public:
void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& properties)
{
m_object.emitPropertiesChangedSignal(interfaceName, properties);
}
void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& properties)
{
m_object.emitPropertiesChangedSignal(interfaceName, properties);
}
void emitPropertiesChangedSignal(const InterfaceName& interfaceName)
{
m_object.emitPropertiesChangedSignal(interfaceName);
}
void emitPropertiesChangedSignal(const char* interfaceName)
{
m_object.emitPropertiesChangedSignal(interfaceName);
}
private:
sdbus::IObject& m_object;
};
/*!
* @brief Object Manager Convenience Adaptor
*
* Adding this class as _Interfaces.. template parameter of class AdaptorInterfaces
* implements the *GetManagedObjects()* method of the [org.freedesktop.DBus.ObjectManager.GetManagedObjects](https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager)
* interface.
*
* Note that there can be multiple object managers in a path hierarchy. InterfacesAdded/InterfacesRemoved
* signals are sent from the closest object manager at either the same path or the closest parent path of an object.
*/
class ObjectManager_adaptor
{
static inline const char* INTERFACE_NAME = "org.freedesktop.DBus.ObjectManager";
protected:
explicit ObjectManager_adaptor(sdbus::IObject& object) : m_object(object)
{
}
ObjectManager_adaptor(const ObjectManager_adaptor&) = delete;
ObjectManager_adaptor& operator=(const ObjectManager_adaptor&) = delete;
ObjectManager_adaptor(ObjectManager_adaptor&&) = delete;
ObjectManager_adaptor& operator=(ObjectManager_adaptor&&) = delete;
~ObjectManager_adaptor() = default;
void registerAdaptor()
{
m_object.addObjectManager();
}
private:
sdbus::IObject& m_object;
};
/*!
* @brief Managed Object Convenience Adaptor
*
* Adding this class as _Interfaces.. template parameter of class AdaptorInterfaces
* will extend the resulting object adaptor with emitInterfacesAddedSignal()/emitInterfacesRemovedSignal()
* according to org.freedesktop.DBus.ObjectManager.InterfacesAdded/.InterfacesRemoved.
*
* Note that objects which implement this adaptor require an object manager (e.g via ObjectManager_adaptor) to be
* instantiated on one of it's parent object paths or the same path. InterfacesAdded/InterfacesRemoved
* signals are sent from the closest object manager at either the same path or the closest parent path of an object.
*/
class ManagedObject_adaptor
{
protected:
explicit ManagedObject_adaptor(sdbus::IObject& object)
: m_object(object)
{
}
ManagedObject_adaptor(const ManagedObject_adaptor&) = delete;
ManagedObject_adaptor& operator=(const ManagedObject_adaptor&) = delete;
ManagedObject_adaptor(ManagedObject_adaptor&&) = delete;
ManagedObject_adaptor& operator=(ManagedObject_adaptor&&) = delete;
~ManagedObject_adaptor() = default;
void registerAdaptor()
{
}
public:
/*!
* @brief Emits InterfacesAdded signal for this object path
*
* See IObject::emitInterfacesAddedSignal().
*/
void emitInterfacesAddedSignal()
{
m_object.emitInterfacesAddedSignal();
}
/*!
* @brief Emits InterfacesAdded signal for this object path
*
* See IObject::emitInterfacesAddedSignal().
*/
void emitInterfacesAddedSignal(const std::vector<sdbus::InterfaceName>& interfaces)
{
m_object.emitInterfacesAddedSignal(interfaces);
}
/*!
* @brief Emits InterfacesRemoved signal for this object path
*
* See IObject::emitInterfacesRemovedSignal().
*/
void emitInterfacesRemovedSignal()
{
m_object.emitInterfacesRemovedSignal();
}
/*!
* @brief Emits InterfacesRemoved signal for this object path
*
* See IObject::emitInterfacesRemovedSignal().
*/
void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces)
{
m_object.emitInterfacesRemovedSignal(interfaces);
}
private:
sdbus::IObject& m_object;
};
}
#endif /* SDBUS_CXX_STANDARDINTERFACES_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file TypeTraits.h
*
@ -26,12 +27,27 @@
#ifndef SDBUS_CXX_TYPETRAITS_H_
#define SDBUS_CXX_TYPETRAITS_H_
#include <string>
#include <vector>
#include <map>
#include <sdbus-c++/Error.h>
#include <array>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#ifdef __has_include
# if __has_include(<span>)
# include <span>
# endif
#endif
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
// Forward declarations
namespace sdbus {
@ -39,295 +55,371 @@ namespace sdbus {
template <typename... _ValueTypes> class Struct;
class ObjectPath;
class Signature;
class UnixFd;
template<typename _T1, typename _T2> using DictEntry = std::pair<_T1, _T2>;
class BusName;
class InterfaceName;
class MemberName;
class MethodCall;
class MethodReply;
class Signal;
class Message;
class PropertySetCall;
class PropertyGetReply;
template <typename... _Results> class Result;
class Error;
template <typename _T, typename _Enable = void> struct signature_of;
}
namespace sdbus {
using method_callback = std::function<void(Message& msg, Message& reply)>;
using signal_handler = std::function<void(Message& signal)>;
using property_set_callback = std::function<void(Message& msg)>;
using property_get_callback = std::function<void(Message& reply)>;
// Callbacks from sdbus-c++
using method_callback = std::function<void(MethodCall msg)>;
using async_reply_handler = std::function<void(MethodReply reply, std::optional<Error> error)>;
using signal_handler = std::function<void(Signal signal)>;
using message_handler = std::function<void(Message msg)>;
using property_set_callback = std::function<void(PropertySetCall msg)>;
using property_get_callback = std::function<void(PropertyGetReply& reply)>;
// Primary template
// Type-erased RAII-style handle to callbacks/subscriptions registered to sdbus-c++
using Slot = std::unique_ptr<void, std::function<void(void*)>>;
// Tag specifying that an owning handle (so-called slot) of the logical resource shall be provided to the client
struct return_slot_t { explicit return_slot_t() = default; };
inline constexpr return_slot_t return_slot{};
// Tag specifying that the library shall own the slot resulting from the call of the function (so-called floating slot)
struct floating_slot_t { explicit floating_slot_t() = default; };
inline constexpr floating_slot_t floating_slot{};
// Tag denoting the assumption that the caller has already obtained message ownership
struct adopt_message_t { explicit adopt_message_t() = default; };
inline constexpr adopt_message_t adopt_message{};
// Tag denoting the assumption that the caller has already obtained fd ownership
struct adopt_fd_t { explicit adopt_fd_t() = default; };
inline constexpr adopt_fd_t adopt_fd{};
// Tag specifying that the proxy shall not run an event loop thread on its D-Bus connection.
// Such proxies are typically created to carry out a simple synchronous D-Bus call(s) and then are destroyed.
struct dont_run_event_loop_thread_t { explicit dont_run_event_loop_thread_t() = default; };
inline constexpr dont_run_event_loop_thread_t dont_run_event_loop_thread{};
// Tag denoting an asynchronous call that returns std::future as a handle
struct with_future_t { explicit with_future_t() = default; };
inline constexpr with_future_t with_future{};
// Tag denoting a call where the reply shouldn't be waited for
struct dont_expect_reply_t { explicit dont_expect_reply_t() = default; };
inline constexpr dont_expect_reply_t dont_expect_reply{};
// Tag denoting that the variant shall embed the other variant as its value, instead of creating a copy
struct embed_variant_t { explicit embed_variant_t() = default; };
inline constexpr embed_variant_t embed_variant{};
// Helper for static assert
template <class... _T> constexpr bool always_false = false;
// Helper operator+ for concatenation of `std::array`s
template <typename _T, std::size_t _N1, std::size_t _N2>
constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs);
// Template specializations for getting D-Bus signatures from C++ types
template <typename _T>
constexpr auto signature_of_v = signature_of<_T>::value;
template <typename _T, typename _Enable>
struct signature_of
{
static constexpr bool is_valid = false;
static constexpr bool is_trivial_dbus_type = false;
static const std::string str()
static constexpr void* value = []
{
// sizeof(_T) < 0 is here to make compiler not being able to figure out
// the assertion expression before the template instantiation takes place.
static_assert(sizeof(_T) < 0, "Unknown DBus type");
return "";
}
// See using-sdbus-c++.md, section "Extending sdbus-c++ type system",
// on how to teach sdbus-c++ about your custom types
static_assert(always_false<_T>, "Unsupported D-Bus type (specialize `signature_of` for your custom types)");
};
};
template <typename _T>
struct signature_of<const _T> : signature_of<_T>
{};
template <typename _T>
struct signature_of<volatile _T> : signature_of<_T>
{};
template <typename _T>
struct signature_of<const volatile _T> : signature_of<_T>
{};
template <typename _T>
struct signature_of<_T&> : signature_of<_T>
{};
template <>
struct signature_of<void>
{
static constexpr std::array<char, 0> value{};
static constexpr bool is_valid = true;
static const std::string str()
{
return "";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <>
struct signature_of<bool>
{
static constexpr std::array value{'b'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "b";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<uint8_t>
{
static constexpr std::array value{'y'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "y";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<int16_t>
{
static constexpr std::array value{'n'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "n";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<uint16_t>
{
static constexpr std::array value{'q'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "q";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<int32_t>
{
static constexpr std::array value{'i'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "i";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<uint32_t>
{
static constexpr std::array value{'u'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "u";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<int64_t>
{
static constexpr std::array value{'x'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "x";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<uint64_t>
{
static constexpr std::array value{'t'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "t";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<double>
{
static constexpr std::array value{'d'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "d";
}
};
template <>
struct signature_of<char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <>
struct signature_of<const char*>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
};
template <std::size_t _N>
struct signature_of<const char[_N]>
{
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
static constexpr bool is_trivial_dbus_type = true;
};
template <>
struct signature_of<std::string>
{
static constexpr std::array value{'s'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "s";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <>
struct signature_of<std::string_view> : signature_of<std::string>
{};
template <>
struct signature_of<char*> : signature_of<std::string>
{};
template <>
struct signature_of<const char*> : signature_of<std::string>
{};
template <std::size_t _N>
struct signature_of<char[_N]> : signature_of<std::string>
{};
template <std::size_t _N>
struct signature_of<const char[_N]> : signature_of<std::string>
{};
template <>
struct signature_of<BusName> : signature_of<std::string>
{};
template <>
struct signature_of<InterfaceName> : signature_of<std::string>
{};
template <>
struct signature_of<MemberName> : signature_of<std::string>
{};
template <typename... _ValueTypes>
struct signature_of<Struct<_ValueTypes...>>
{
static constexpr std::array contents = (signature_of_v<_ValueTypes> + ...);
static constexpr std::array value = std::array{'('} + contents + std::array{')'};
static constexpr char type_value{'r'}; /* Not actually used in signatures on D-Bus, see specs */
static constexpr bool is_valid = true;
static const std::string str()
{
std::initializer_list<std::string> signatures{signature_of<_ValueTypes>::str()...};
std::string signature;
signature += "(";
for (const auto& item : signatures)
signature += item;
signature += ")";
return signature;
}
static constexpr bool is_trivial_dbus_type = false;
};
template <>
struct signature_of<Variant>
{
static constexpr std::array value{'v'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "v";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <typename... Elements>
struct signature_of<std::variant<Elements...>> : signature_of<Variant>
{};
template <>
struct signature_of<ObjectPath>
{
static constexpr std::array value{'o'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "o";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <>
struct signature_of<Signature>
{
static constexpr std::array value{'g'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "g";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Element>
struct signature_of<std::vector<_Element>>
template <>
struct signature_of<UnixFd>
{
static constexpr std::array value{'h'};
static constexpr bool is_valid = true;
static const std::string str()
{
return "a" + signature_of<_Element>::str();
}
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Key, typename _Value>
struct signature_of<std::map<_Key, _Value>>
template <typename _T1, typename _T2>
struct signature_of<DictEntry<_T1, _T2>>
{
static constexpr std::array value = std::array{'{'} + signature_of_v<std::tuple<_T1, _T2>> + std::array{'}'};
static constexpr char type_value{'e'}; /* Not actually used in signatures on D-Bus, see specs */
static constexpr bool is_valid = true;
static const std::string str()
{
return "a{" + signature_of<_Key>::str() + signature_of<_Value>::str() + "}";
}
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Type>
struct function_traits
: public function_traits<decltype(&_Type::operator())>
template <typename _Element, typename _Allocator>
struct signature_of<std::vector<_Element, _Allocator>>
{
static constexpr std::array value = std::array{'a'} + signature_of_v<_Element>;
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Element, std::size_t _Size>
struct signature_of<std::array<_Element, _Size>> : signature_of<std::vector<_Element>>
{
};
#ifdef __cpp_lib_span
template <typename _Element, std::size_t _Extent>
struct signature_of<std::span<_Element, _Extent>> : signature_of<std::vector<_Element>>
{
};
#endif
template <typename _Enum> // is_const_v and is_volatile_v to avoid ambiguity conflicts with const and volatile specializations of signature_of
struct signature_of<_Enum, typename std::enable_if_t<std::is_enum_v<_Enum> && !std::is_const_v<_Enum> && !std::is_volatile_v<_Enum>>>
: signature_of<std::underlying_type_t<_Enum>>
{};
template <typename _Type>
struct function_traits<const _Type>
: public function_traits<_Type>
{};
template <typename _Key, typename _Value, typename _Compare, typename _Allocator>
struct signature_of<std::map<_Key, _Value, _Compare, _Allocator>>
{
static constexpr std::array value = std::array{'a'} + signature_of_v<DictEntry<_Key, _Value>>;
static constexpr bool is_valid = true;
static constexpr bool is_trivial_dbus_type = false;
};
template <typename _Type>
struct function_traits<_Type&>
: public function_traits<_Type>
{};
template <typename _Key, typename _Value, typename _Hash, typename _KeyEqual, typename _Allocator>
struct signature_of<std::unordered_map<_Key, _Value, _Hash, _KeyEqual, _Allocator>>
: signature_of<std::map<_Key, _Value>>
{
};
template <typename... _Types>
struct signature_of<std::tuple<_Types...>> // A simple concatenation of signatures of _Types
{
static constexpr std::array value = (std::array<char, 0>{} + ... + signature_of_v<_Types>);
static constexpr bool is_valid = false;
static constexpr bool is_trivial_dbus_type = false;
};
// To simplify conversions of arrays to C strings
template <typename _T, std::size_t _N>
constexpr auto as_null_terminated(std::array<_T, _N> arr)
{
return arr + std::array<_T, 1>{0};
}
// Function traits implementation inspired by (c) kennytm,
// https://github.com/kennytm/utils/blob/master/traits.hpp
template <typename _Type>
struct function_traits : function_traits<decltype(&_Type::operator())>
{};
template <typename _Type>
struct function_traits<const _Type> : function_traits<_Type>
{};
template <typename _Type>
struct function_traits<_Type&> : function_traits<_Type>
{};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_Args...)>
struct function_traits_base
{
typedef _ReturnType result_type;
typedef std::tuple<_Args...> arguments_type;
typedef std::tuple<std::decay_t<_Args>...> decayed_arguments_type;
typedef _ReturnType function_type(_Args...);
static constexpr std::size_t arity = sizeof...(_Args);
// template <size_t _Idx, typename _Enabled = void>
// struct arg;
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<(_Idx < arity)>>
// {
// typedef std::tuple_element_t<_Idx, arguments_type> type;
// };
//
// template <size_t _Idx>
// struct arg<_Idx, std::enable_if_t<!(_Idx < arity)>>
// {
// typedef void type;
// };
template <size_t _Idx>
struct arg
{
@ -339,134 +431,214 @@ namespace sdbus {
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
struct function_traits<_ReturnType(_Args...)> : function_traits_base<_ReturnType, _Args...>
{
static constexpr bool is_async = false;
static constexpr bool has_error_param = false;
};
template <typename... _Args>
struct function_traits<void(std::optional<Error>, _Args...)> : function_traits_base<void, _Args...>
{
static constexpr bool has_error_param = true;
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>, _Args...)> : function_traits_base<std::tuple<_Results...>, _Args...>
{
static constexpr bool is_async = true;
using async_result_t = Result<_Results...>;
};
template <typename... _Args, typename... _Results>
struct function_traits<void(Result<_Results...>&&, _Args...)> : function_traits_base<std::tuple<_Results...>, _Args...>
{
static constexpr bool is_async = true;
using async_result_t = Result<_Results...>;
};
template <typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(*)(_Args...)> : function_traits<_ReturnType(_Args...)>
{};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...)>
: public function_traits<_ReturnType(_Args...)>
struct function_traits<_ReturnType(_ClassType::*)(_Args...)> : function_traits<_ReturnType(_Args...)>
{
typedef _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const>
: public function_traits<_ReturnType(_Args...)>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const> : function_traits<_ReturnType(_Args...)>
{
typedef const _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile>
: public function_traits<_ReturnType(_Args...)>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) volatile> : function_traits<_ReturnType(_Args...)>
{
typedef volatile _ClassType& owner_type;
};
template <typename _ClassType, typename _ReturnType, typename... _Args>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile>
: public function_traits<_ReturnType(_Args...)>
struct function_traits<_ReturnType(_ClassType::*)(_Args...) const volatile> : function_traits<_ReturnType(_Args...)>
{
typedef const volatile _ClassType& owner_type;
};
template <typename FunctionType>
struct function_traits<std::function<FunctionType>>
: public function_traits<FunctionType>
struct function_traits<std::function<FunctionType>> : function_traits<FunctionType>
{};
template <class _Function>
constexpr auto is_async_method_v = function_traits<_Function>::is_async;
template <class _Function>
constexpr auto has_error_param_v = function_traits<_Function>::has_error_param;
template <typename _FunctionType>
using function_arguments_t = typename function_traits<_FunctionType>::arguments_type;
template <typename _FunctionType, size_t _Idx>
using function_argument_t = typename function_traits<_FunctionType>::template arg_t<_Idx>;
template <typename _FunctionType>
constexpr auto function_argument_count_v = function_traits<_FunctionType>::arity;
template <typename _FunctionType>
using function_result_t = typename function_traits<_FunctionType>::result_type;
template <typename _Type>
struct aggregate_signature
{
static const std::string str()
{
return signature_of<std::decay_t<_Type>>::str();
}
};
template <typename... _Types>
struct aggregate_signature<std::tuple<_Types...>>
{
static const std::string str()
{
std::initializer_list<std::string> signatures{signature_of<std::decay_t<_Types>>::str()...};
std::string signature;
for (const auto& item : signatures)
signature += item;
return signature;
}
};
// Get a tuple of function input argument types from function signature.
// But first, convert provided function signature to the standardized form `out(in...)'.
template <typename _Function>
struct tuple_of_function_input_arg_types
: public tuple_of_function_input_arg_types<typename function_traits<_Function>::function_type>
{};
// Get a tuple of function input argument types from function signature.
// Function signature is expected in the standardized form `out(in...)'.
template <typename _ReturnType, typename... _Args>
struct tuple_of_function_input_arg_types<_ReturnType(_Args...)>
{
// Arguments may be cv-qualified and may be references, so we have to strip cv and references
// with decay_t in order to get real 'naked' types.
// Example: for a function with signature void(const int i, const std::vector<float> v, double d)
// the `type' will be `std::tuple<int, std::vector<float>, double>'.
typedef std::tuple<std::decay_t<_Args>...> type;
typedef typename function_traits<_Function>::decayed_arguments_type type;
};
template <typename _Function>
using tuple_of_function_input_arg_types_t = typename tuple_of_function_input_arg_types<_Function>::type;
template <typename _Function>
struct signature_of_function_input_arguments
struct tuple_of_function_output_arg_types
{
static const std::string str()
typedef typename function_traits<_Function>::result_type type;
};
template <typename _Function>
using tuple_of_function_output_arg_types_t = typename tuple_of_function_output_arg_types<_Function>::type;
template <typename _Function>
struct signature_of_function_input_arguments : signature_of<tuple_of_function_input_arg_types_t<_Function>>
{
static std::string value_as_string()
{
return aggregate_signature<tuple_of_function_input_arg_types_t<_Function>>::str();
constexpr auto signature = as_null_terminated(signature_of_v<tuple_of_function_input_arg_types_t<_Function>>);
return signature.data();
}
};
template <typename _Function>
struct signature_of_function_output_arguments
inline auto signature_of_function_input_arguments_v = signature_of_function_input_arguments<_Function>::value_as_string();
template <typename _Function>
struct signature_of_function_output_arguments : signature_of<tuple_of_function_output_arg_types_t<_Function>>
{
static const std::string str()
static std::string value_as_string()
{
return aggregate_signature<function_result_t<_Function>>::str();
constexpr auto signature = as_null_terminated(signature_of_v<tuple_of_function_output_arg_types_t<_Function>>);
return signature.data();
}
};
template <typename _Function>
inline auto signature_of_function_output_arguments_v = signature_of_function_output_arguments<_Function>::value_as_string();
// std::future stuff for return values of async calls
template <typename... _Args> struct future_return
{
typedef std::tuple<_Args...> type;
};
template <> struct future_return<>
{
typedef void type;
};
template <typename _Type> struct future_return<_Type>
{
typedef _Type type;
};
template <typename... _Args>
using future_return_t = typename future_return<_Args...>::type;
// Credit: Piotr Skotnicki (https://stackoverflow.com/a/57639506)
template <typename, typename>
constexpr bool is_one_of_variants_types = false;
template <typename... _VariantTypes, typename _QueriedType>
constexpr bool is_one_of_variants_types<std::variant<_VariantTypes...>, _QueriedType>
= (std::is_same_v<_QueriedType, _VariantTypes> || ...);
// Wrapper (tag) denoting we want to serialize user-defined struct
// into a D-Bus message as a dictionary of strings to variants.
template <typename _Struct>
struct as_dictionary
{
explicit as_dictionary(const _Struct& s) : m_struct(s) {}
const _Struct& m_struct;
};
template <typename _Type>
const _Type& as_dictionary_if_struct(const _Type& object)
{
return object; // identity in case _Type is not struct (user-defined structs shall provide an overload)
}
// By default, the dict-as-struct deserialization strategy is strict.
// Strict means that every key of the deserialized dictionary must have its counterpart member in the struct, otherwise an exception is thrown.
// Relaxed means that a key that does not have a matching struct member is silently ignored.
// The behavior can be overridden for user-defined struct by specializing this variable template.
template <typename _Struct>
constexpr auto strict_dict_as_struct_deserialization_v = true;
// By default, the struct-as-dict serialization strategy is single-level only (as opposed to nested).
// Single-level means that only the specific struct is serialized as a dictionary, serializing members that are structs always as structs.
// Nested means that the struct *and* its members that are structs are all serialized as a dictionary. If nested strategy is also
// defined for the nested struct, then the same behavior applies for that struct, recursively.
// The behavior can be overridden for user-defined struct by specializing this variable template.
template <typename _Struct>
constexpr auto nested_struct_as_dict_serialization_v = false;
namespace detail
{
// Version of apply_impl for functions returning non-void values.
// In this case just forward function return value.
template <class _Function, class _Tuple, std::size_t... _I>
template <class _Function, class _Tuple, typename... _Args, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, Result<_Args...>&& r
, _Tuple&& t
, std::index_sequence<_I...>
, std::enable_if_t<!std::is_void<function_result_t<_Function>>::value>* = nullptr)
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...);
return std::forward<_Function>(f)(std::move(r), std::get<_I>(std::forward<_Tuple>(t))...);
}
// Version of apply_impl for functions returning void.
// In this case, to have uniform code on the caller side, return empty tuple, our synonym for `void'.
template <class _Function, class _Tuple, std::size_t... _I>
decltype(auto) apply_impl( _Function&& f
, std::optional<Error> e
, _Tuple&& t
, std::index_sequence<_I...> )
{
return std::forward<_Function>(f)(std::move(e), std::get<_I>(std::forward<_Tuple>(t))...);
}
// For non-void returning functions, apply_impl simply returns function return value (a tuple of values).
// For void-returning functions, apply_impl returns an empty tuple.
template <class _Function, class _Tuple, std::size_t... _I>
constexpr decltype(auto) apply_impl( _Function&& f
, _Tuple&& t
, std::index_sequence<_I...>
, std::enable_if_t<std::is_void<function_result_t<_Function>>::value>* = nullptr)
, std::index_sequence<_I...> )
{
std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...);
return std::tuple<>{};
if constexpr (!std::is_void_v<function_result_t<_Function>>)
return std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...);
else
return std::forward<_Function>(f)(std::get<_I>(std::forward<_Tuple>(t))...), std::tuple<>{};
}
}
@ -480,6 +652,47 @@ namespace sdbus {
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple, typename... _Args>
constexpr decltype(auto) apply(_Function&& f, Result<_Args...>&& r, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, std::move(r)
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convert tuple `t' of values into a list of arguments
// and invoke function `f' with those arguments.
template <class _Function, class _Tuple>
decltype(auto) apply(_Function&& f, std::optional<Error> e, _Tuple&& t)
{
return detail::apply_impl( std::forward<_Function>(f)
, std::move(e)
, std::forward<_Tuple>(t)
, std::make_index_sequence<std::tuple_size<std::decay_t<_Tuple>>::value>{} );
}
// Convenient concatenation of arrays
template <typename _T, std::size_t _N1, std::size_t _N2>
constexpr std::array<_T, _N1 + _N2> operator+(std::array<_T, _N1> lhs, std::array<_T, _N2> rhs)
{
std::array<_T, _N1 + _N2> result{};
std::size_t index = 0;
for (auto& el : lhs) {
result[index] = std::move(el);
++index;
}
for (auto& el : rhs) {
result[index] = std::move(el);
++index;
}
return result;
}
}
#endif /* SDBUS_CXX_TYPETRAITS_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Types.h
*
@ -28,11 +29,15 @@
#include <sdbus-c++/Message.h>
#include <sdbus-c++/TypeTraits.h>
#include <cstring>
#include <cstddef>
#include <memory>
#include <string>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <memory>
#include <tuple>
#include <utility>
namespace sdbus {
@ -41,6 +46,12 @@ namespace sdbus {
*
* Variant can hold value of any D-Bus-supported type.
*
* Note: Even though thread-aware, Variant objects are not thread-safe.
* Some const methods are conceptually const, but not physically const,
* thus are not thread-safe. This is by design: normally, clients
* should process a single Variant object in a single thread at a time.
* Otherwise they need to take care of synchronization by themselves.
*
***********************************************/
class Variant
{
@ -48,21 +59,46 @@ namespace sdbus {
Variant();
template <typename _ValueType>
Variant(const _ValueType& value)
: Variant()
explicit Variant(const _ValueType& value) : Variant()
{
msg_.openVariant(signature_of<_ValueType>::str());
msg_.openVariant<_ValueType>();
msg_ << value;
msg_.closeVariant();
msg_.seal();
}
Variant(const Variant& value, embed_variant_t) : Variant()
{
msg_.openVariant<Variant>();
msg_ << value;
msg_.closeVariant();
msg_.seal();
}
template <typename _Struct>
explicit Variant(const as_dictionary<_Struct>& value) : Variant()
{
msg_.openVariant<std::map<std::string, Variant>>();
msg_ << as_dictionary(value.m_struct);
msg_.closeVariant();
msg_.seal();
}
template <typename... _Elements>
Variant(const std::variant<_Elements...>& value)
: Variant()
{
msg_ << value;
msg_.seal();
}
template <typename _ValueType>
_ValueType get() const
{
_ValueType val;
msg_.rewind(false);
msg_.enterVariant(signature_of<_ValueType>::str());
msg_.enterVariant<_ValueType>();
_ValueType val;
msg_ >> val;
msg_.exitVariant();
return val;
@ -70,27 +106,47 @@ namespace sdbus {
// Only allow conversion operator for true D-Bus type representations in C++
template <typename _ValueType, typename = std::enable_if_t<signature_of<_ValueType>::is_valid>>
operator _ValueType() const
explicit operator _ValueType() const
{
return get<_ValueType>();
}
template <typename... _Elements>
operator std::variant<_Elements...>() const
{
std::variant<_Elements...> result;
msg_.rewind(false);
msg_ >> result;
return result;
}
template <typename _Type>
bool containsValueOfType() const
{
return signature_of<_Type>::str() == peekValueType();
constexpr auto signature = as_null_terminated(signature_of_v<_Type>);
return std::strcmp(signature.data(), peekValueType()) == 0;
}
bool isEmpty() const;
void serializeTo(Message& msg) const;
void deserializeFrom(Message& msg);
std::string peekValueType() const;
const char* peekValueType() const;
private:
mutable Message msg_{};
mutable PlainMessage msg_{};
};
/********************************************//**
* @class Struct
*
* Representation of struct D-Bus type
*
* Struct implements tuple protocol, i.e. it's a tuple-like class.
* It can be used with std::get<>(), std::tuple_element,
* std::tuple_size and in structured bindings.
*
***********************************************/
template <typename... _ValueTypes>
class Struct
: public std::tuple<_ValueTypes...>
@ -98,15 +154,12 @@ namespace sdbus {
public:
using std::tuple<_ValueTypes...>::tuple;
// Disable constructor if an older then 7.1.0 version of GCC is used
#if !((defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !(__GNUC__ > 7 || (__GNUC__ == 7 && (__GNUC_MINOR__ > 1 || (__GNUC_MINOR__ == 1 && __GNUC_PATCHLEVEL__ > 0)))))
Struct() = default;
explicit Struct(const std::tuple<_ValueTypes...>& t)
: std::tuple<_ValueTypes...>(t)
{
}
#endif
template <std::size_t _I>
auto& get()
@ -121,6 +174,9 @@ namespace sdbus {
}
};
template <typename... _Elements>
Struct(_Elements...) -> Struct<_Elements...>;
template<typename... _Elements>
constexpr Struct<std::decay_t<_Elements>...>
make_struct(_Elements&&... args)
@ -129,20 +185,413 @@ namespace sdbus {
return result_type(std::forward<_Elements>(args)...);
}
/********************************************//**
* @class ObjectPath
*
* Strong type representing the D-Bus object path
*
***********************************************/
class ObjectPath : public std::string
{
public:
using std::string::string;
ObjectPath() = default;
explicit ObjectPath(std::string value)
: std::string(std::move(value))
{}
explicit ObjectPath(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
/********************************************//**
* @class BusName
*
* Strong type representing the D-Bus bus/service/connection name
*
***********************************************/
class BusName : public std::string
{
public:
BusName() = default;
explicit BusName(std::string value)
: std::string(std::move(value))
{}
explicit BusName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
using ServiceName = BusName;
using ConnectionName = BusName;
/********************************************//**
* @class InterfaceName
*
* Strong type representing the D-Bus interface name
*
***********************************************/
class InterfaceName : public std::string
{
public:
InterfaceName() = default;
explicit InterfaceName(std::string value)
: std::string(std::move(value))
{}
explicit InterfaceName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
/********************************************//**
* @class MemberName
*
* Strong type representing the D-Bus member name
*
***********************************************/
class MemberName : public std::string
{
public:
MemberName() = default;
explicit MemberName(std::string value)
: std::string(std::move(value))
{}
explicit MemberName(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
/********************************************//**
* @class Signature
*
* Strong type representing the D-Bus object path
*
***********************************************/
class Signature : public std::string
{
public:
using std::string::string;
Signature() = default;
explicit Signature(std::string value)
: std::string(std::move(value))
{}
explicit Signature(const char* value)
: std::string(value)
{}
using std::string::operator=;
};
/********************************************//**
* @struct UnixFd
*
* UnixFd is a representation of file descriptor D-Bus type that owns
* the underlying fd, provides access to it, and closes the fd when
* the UnixFd goes out of scope.
*
* UnixFd can be default constructed (owning invalid fd), or constructed from
* an explicitly provided fd by either duplicating or adopting that fd as-is.
*
***********************************************/
class UnixFd
{
public:
UnixFd() = default;
explicit UnixFd(int fd)
: fd_(checkedDup(fd))
{
}
UnixFd(int fd, adopt_fd_t)
: fd_(fd)
{
}
UnixFd(const UnixFd& other)
{
*this = other;
}
UnixFd& operator=(const UnixFd& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = checkedDup(other.fd_);
return *this;
}
UnixFd(UnixFd&& other)
{
*this = std::move(other);
}
UnixFd& operator=(UnixFd&& other)
{
if (this == &other)
{
return *this;
}
close();
fd_ = std::exchange(other.fd_, -1);
return *this;
}
~UnixFd()
{
close();
}
[[nodiscard]] int get() const
{
return fd_;
}
void reset(int fd = -1)
{
*this = UnixFd{fd};
}
void reset(int fd, adopt_fd_t)
{
*this = UnixFd{fd, adopt_fd};
}
int release()
{
return std::exchange(fd_, -1);
}
[[nodiscard]] bool isValid() const
{
return fd_ >= 0;
}
private:
/// Closes file descriptor, but does not set it to -1.
void close();
/// Returns negative argument unchanged.
/// Otherwise, call ::dup and throw on failure.
static int checkedDup(int fd);
int fd_ = -1;
};
/********************************************//**
* @typedef DictEntry
*
* DictEntry is implemented as std::pair, a standard
* value_type in STL(-like) associative containers.
*
***********************************************/
template<typename _T1, typename _T2>
using DictEntry = std::pair<_T1, _T2>;
}
// Making sdbus::Struct implement the tuple-protocol, i.e. be a tuple-like type
template <size_t _I, typename... _ValueTypes>
struct std::tuple_element<_I, sdbus::Struct<_ValueTypes...>>
: std::tuple_element<_I, std::tuple<_ValueTypes...>>
{};
template <typename... _ValueTypes>
struct std::tuple_size<sdbus::Struct<_ValueTypes...>>
: std::tuple_size<std::tuple<_ValueTypes...>>
{};
/********************************************//**
* @name SDBUSCPP_REGISTER_STRUCT
*
* A convenient way to extend sdbus-c++ type system with user-defined structs.
*
* The macro teaches sdbus-c++ to recognize the user-defined struct
* as a valid C++ representation of a D-Bus Struct type, and enables
* clients to use their struct conveniently instead of the (too
* generic and less expressive) `sdbus::Struct<...>` in sdbus-c++ API.
*
* It also enables to serialize a user-defined struct as an a{sv} dictionary,
* and to deserialize an a{sv} dictionary into the user-defined struct.
*
* The first argument is the struct type name and the remaining arguments
* are names of struct members. Members must be of types supported by
* sdbus-c++ (or of user-defined types that sdbus-c++ was taught to support).
* Members can be other structs (nesting is supported).
* The macro must be placed in the global namespace.
*
* For example, given the user-defined struct `ABC`:
*
* namespace foo {
* struct ABC
* {
* int number;
* std::string name;
* std::vector<double> data;
* };
* }
*
* one can teach sdbus-c++ about the contents of this struct simply with:
*
* SDBUSCPP_REGISTER_STRUCT(foo::ABC, number, name, data);
*
* Up to 16 struct members are supported by the macro.
*
***********************************************/
#define SDBUSCPP_REGISTER_STRUCT(STRUCT, ...) \
namespace sdbus { \
static_assert(SDBUSCPP_PP_NARG(__VA_ARGS__) <= 16, \
"Not more than 16 struct members are supported, please open an issue if you need more"); \
\
template <> \
struct signature_of<STRUCT> \
: signature_of<sdbus::Struct<SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, __VA_ARGS__)>> \
{}; \
\
inline auto as_dictionary_if_struct(const STRUCT& object) \
{ \
return as_dictionary<STRUCT>(object); \
} \
\
inline sdbus::Message& operator<<(sdbus::Message& msg, const STRUCT& items) \
{ \
return msg << sdbus::Struct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(items, __VA_ARGS__))}; \
} \
\
inline Message& operator<<(Message& msg, const as_dictionary<STRUCT>& s) \
{ \
if constexpr (!nested_struct_as_dict_serialization_v<STRUCT>) \
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
else \
return msg.serializeDictionary<std::string, Variant>({SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(s.m_struct, __VA_ARGS__)}); \
} \
\
inline Message& operator>>(Message& msg, STRUCT& s) \
{ \
/* First, try to deserialize as a struct */ \
if (msg.peekType().first == signature_of<STRUCT>::type_value) \
{ \
Struct sdbusStruct{std::forward_as_tuple(SDBUSCPP_STRUCT_MEMBERS(s, __VA_ARGS__))}; \
return msg >> sdbusStruct; \
} \
\
/* Otherwise try to deserialize as a dictionary of strings to variants */ \
\
return msg.deserializeDictionary<std::string, Variant>([&s](const auto& dictEntry) \
{ \
const std::string& key = dictEntry.first; /* Intentionally not using structured bindings */ \
const Variant& value = dictEntry.second; \
\
using namespace std::string_literals; \
/* This also handles members which are structs serialized as dict of strings to variants, recursively */ \
SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(s, __VA_ARGS__) \
SDBUS_THROW_ERROR_IF( strict_dict_as_struct_deserialization_v<STRUCT> \
, ("Failed to deserialize struct from a dictionary: could not find field '"s += key) += "' in struct 'my::Struct'" \
, EINVAL ); \
}); \
} \
} \
/**/
/********************************************//**
* @name SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION
*
* Enables relaxed deserialization of an a{sv} dictionary into a user-defined struct STRUCT.
*
* The default (strict) deserialization mode is that if there are entries in the dictionary
* which do not have a corresponding field in the struct, an exception is thrown.
* In the relaxed mode, such entries are silently skipped.
*
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
***********************************************/
#define SDBUSCPP_ENABLE_RELAXED_DICT2STRUCT_DESERIALIZATION(STRUCT) \
template <> \
constexpr auto sdbus::strict_dict_as_struct_deserialization_v<STRUCT> = false; \
/**/
/********************************************//**
* @name SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION
*
* Enables nested serialization of user-defined struct STRUCT as an a{sv} dictionary.
*
* By default, STRUCT fields which are structs themselves are serialized as D-Bus structs.
* This macro tells sdbus-c++ to also serialize nested structs, in a recursive fashion,
* as a{sv} dictionaries.
*
* The macro can only be used in combination with SDBUSCPP_REGISTER_STRUCT macro,
* and must be placed before SDBUSCPP_REGISTER_STRUCT macro.
***********************************************/
#define SDBUSCPP_ENABLE_NESTED_STRUCT2DICT_SERIALIZATION(STRUCT) \
template <> \
constexpr auto sdbus::nested_struct_as_dict_serialization_v<STRUCT> = true \
/**/
/*!
* @cond SDBUSCPP_INTERNAL
*
* Internal helper preprocessor facilities
*/
#define SDBUSCPP_STRUCT_MEMBERS(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER(STRUCT, MEMBER) STRUCT.MEMBER
#define SDBUSCPP_STRUCT_MEMBER_TYPES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_TYPE, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_TYPE(STRUCT, MEMBER) decltype(STRUCT::MEMBER)
#define SDBUSCPP_STRUCT_MEMBERS_AS_DICT_ENTRIES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_AS_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{STRUCT.MEMBER}}
#define SDBUSCPP_STRUCT_MEMBERS_AS_NESTED_DICT_ENTRIES(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY, SDBUSCPP_PP_COMMA, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_STRUCT_MEMBER_AS_NESTED_DICT_ENTRY(STRUCT, MEMBER) {#MEMBER, Variant{as_dictionary_if_struct(STRUCT.MEMBER)}}
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBERS(STRUCT, ...) \
SDBUSCPP_PP_CAT(SDBUSCPP_FOR_EACH_, SDBUSCPP_PP_NARG(__VA_ARGS__))(SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER, SDBUSCPP_PP_SPACE, STRUCT, __VA_ARGS__) \
/**/
#define SDBUSCPP_FIND_AND_DESERIALIZE_STRUCT_MEMBER(STRUCT, MEMBER) if (key == #MEMBER) STRUCT.MEMBER = value.get<decltype(STRUCT.MEMBER)>(); else
#define SDBUSCPP_FOR_EACH_1(M, D, S, M1) M(S, M1)
#define SDBUSCPP_FOR_EACH_2(M, D, S, M1, M2) M(S, M1) D M(S, M2)
#define SDBUSCPP_FOR_EACH_3(M, D, S, M1, M2, M3) M(S, M1) D M(S, M2) D M(S, M3)
#define SDBUSCPP_FOR_EACH_4(M, D, S, M1, M2, M3, M4) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4)
#define SDBUSCPP_FOR_EACH_5(M, D, S, M1, M2, M3, M4, M5) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5)
#define SDBUSCPP_FOR_EACH_6(M, D, S, M1, M2, M3, M4, M5, M6) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6)
#define SDBUSCPP_FOR_EACH_7(M, D, S, M1, M2, M3, M4, M5, M6, M7) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7)
#define SDBUSCPP_FOR_EACH_8(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8)
#define SDBUSCPP_FOR_EACH_9(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9)
#define SDBUSCPP_FOR_EACH_10(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10)
#define SDBUSCPP_FOR_EACH_11(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11)
#define SDBUSCPP_FOR_EACH_12(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12)
#define SDBUSCPP_FOR_EACH_13(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13)
#define SDBUSCPP_FOR_EACH_14(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14)
#define SDBUSCPP_FOR_EACH_15(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15)
#define SDBUSCPP_FOR_EACH_16(M, D, S, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15, M16) M(S, M1) D M(S, M2) D M(S, M3) D M(S, M4) D M(S, M5) D M(S, M6) D M(S, M7) D M(S, M8) D M(S, M9) D M(S, M10) D M(S, M11) D M(S, M12) D M(S, M13) D M(S, M14) D M(S, M15) D M(S, M16)
#define SDBUSCPP_PP_CAT(X, Y) SDBUSCPP_PP_CAT_IMPL(X, Y)
#define SDBUSCPP_PP_CAT_IMPL(X, Y) X##Y
#define SDBUSCPP_PP_NARG(...) SDBUSCPP_PP_NARG_IMPL(__VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define SDBUSCPP_PP_NARG_IMPL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _N, ...) _N
#define SDBUSCPP_PP_COMMA ,
#define SDBUSCPP_PP_SPACE
#endif /* SDBUS_CXX_TYPES_H_ */

View File

@ -0,0 +1,112 @@
/**
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableItems.h
*
* Created on: Dec 14, 2023
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_VTABLEITEMS_H_
#define SDBUS_CXX_VTABLEITEMS_H_
#include <sdbus-c++/Flags.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <variant>
#include <vector>
namespace sdbus {
struct MethodVTableItem
{
template <typename _Function> MethodVTableItem& implementedAs(_Function&& callback);
MethodVTableItem& withInputParamNames(std::vector<std::string> names);
template <typename... _String> MethodVTableItem& withInputParamNames(_String... names);
MethodVTableItem& withOutputParamNames(std::vector<std::string> names);
template <typename... _String> MethodVTableItem& withOutputParamNames(_String... names);
MethodVTableItem& markAsDeprecated();
MethodVTableItem& markAsPrivileged();
MethodVTableItem& withNoReply();
MethodName name;
Signature inputSignature;
std::vector<std::string> inputParamNames;
Signature outputSignature;
std::vector<std::string> outputParamNames;
method_callback callbackHandler;
Flags flags;
};
MethodVTableItem registerMethod(MethodName methodName);
MethodVTableItem registerMethod(std::string methodName);
struct SignalVTableItem
{
template <typename... _Args> SignalVTableItem& withParameters();
template <typename... _Args> SignalVTableItem& withParameters(std::vector<std::string> names);
template <typename... _Args, typename... _String> SignalVTableItem& withParameters(_String... names);
SignalVTableItem& markAsDeprecated();
SignalName name;
Signature signature;
std::vector<std::string> paramNames;
Flags flags;
};
SignalVTableItem registerSignal(SignalName signalName);
SignalVTableItem registerSignal(std::string signalName);
struct PropertyVTableItem
{
template <typename _Function> PropertyVTableItem& withGetter(_Function&& callback);
template <typename _Function> PropertyVTableItem& withSetter(_Function&& callback);
PropertyVTableItem& markAsDeprecated();
PropertyVTableItem& markAsPrivileged();
PropertyVTableItem& withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
PropertyName name;
Signature signature;
property_get_callback getter;
property_set_callback setter;
Flags flags;
};
PropertyVTableItem registerProperty(PropertyName propertyName);
PropertyVTableItem registerProperty(std::string propertyName);
struct InterfaceFlagsVTableItem
{
InterfaceFlagsVTableItem& markAsDeprecated();
InterfaceFlagsVTableItem& markAsPrivileged();
InterfaceFlagsVTableItem& withNoReplyMethods();
InterfaceFlagsVTableItem& withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior);
Flags flags;
};
InterfaceFlagsVTableItem setInterfaceFlags();
using VTableItem = std::variant<MethodVTableItem, SignalVTableItem, PropertyVTableItem, InterfaceFlagsVTableItem>;
} // namespace sdbus
#endif /* SDBUS_CXX_VTABLEITEMS_H_ */

View File

@ -0,0 +1,301 @@
/**
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableItems.inl
*
* Created on: Dec 14, 2023
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_VTABLEITEMS_INL_
#define SDBUS_CPP_VTABLEITEMS_INL_
#include <sdbus-c++/Error.h>
#include <sdbus-c++/TypeTraits.h>
#include <string>
#include <type_traits>
#include <vector>
namespace sdbus {
/*** -------------------- ***/
/*** Method VTable Item ***/
/*** -------------------- ***/
template <typename _Function>
MethodVTableItem& MethodVTableItem::implementedAs(_Function&& callback)
{
inputSignature = signature_of_function_input_arguments_v<_Function>;
outputSignature = signature_of_function_output_arguments_v<_Function>;
callbackHandler = [callback = std::forward<_Function>(callback)](MethodCall call)
{
// Create a tuple of callback input arguments' types, which will be used
// as a storage for the argument values deserialized from the message.
tuple_of_function_input_arg_types_t<_Function> inputArgs;
// Deserialize input arguments from the message into the tuple.
call >> inputArgs;
if constexpr (!is_async_method_v<_Function>)
{
// Invoke callback with input arguments from the tuple.
auto ret = sdbus::apply(callback, inputArgs);
// Store output arguments to the reply message and send it back.
auto reply = call.createReply();
reply << ret;
reply.send();
}
else
{
// Invoke callback with input arguments from the tuple and with result object to be set later
using AsyncResult = typename function_traits<_Function>::async_result_t;
sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs));
}
};
return *this;
}
inline MethodVTableItem& MethodVTableItem::withInputParamNames(std::vector<std::string> names)
{
inputParamNames = std::move(names);
return *this;
}
template <typename... _String>
inline MethodVTableItem& MethodVTableItem::withInputParamNames(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
return withInputParamNames({names...});
}
inline MethodVTableItem& MethodVTableItem::withOutputParamNames(std::vector<std::string> names)
{
outputParamNames = std::move(names);
return *this;
}
template <typename... _String>
inline MethodVTableItem& MethodVTableItem::withOutputParamNames(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
return withOutputParamNames({names...});
}
inline MethodVTableItem& MethodVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline MethodVTableItem& MethodVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline MethodVTableItem& MethodVTableItem::withNoReply()
{
flags.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline MethodVTableItem registerMethod(MethodName methodName)
{
return {std::move(methodName), {}, {}, {}, {}, {}, {}};
}
inline MethodVTableItem registerMethod(std::string methodName)
{
return registerMethod(MethodName{std::move(methodName)});
}
/*** -------------------- ***/
/*** Signal VTable Item ***/
/*** -------------------- ***/
template <typename... _Args>
inline SignalVTableItem& SignalVTableItem::withParameters()
{
signature = signature_of_function_input_arguments_v<void(_Args...)>;
return *this;
}
template <typename... _Args>
inline SignalVTableItem& SignalVTableItem::withParameters(std::vector<std::string> names)
{
paramNames = std::move(names);
return withParameters<_Args...>();
}
template <typename... _Args, typename... _String>
inline SignalVTableItem& SignalVTableItem::withParameters(_String... names)
{
static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match");
return withParameters<_Args...>({names...});
}
inline SignalVTableItem& SignalVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline SignalVTableItem registerSignal(SignalName signalName)
{
return {std::move(signalName), {}, {}, {}};
}
inline SignalVTableItem registerSignal(std::string signalName)
{
return registerSignal(SignalName{std::move(signalName)});
}
/*** -------------------- ***/
/*** Property VTable Item ***/
/*** -------------------- ***/
template <typename _Function>
inline PropertyVTableItem& PropertyVTableItem::withGetter(_Function&& callback)
{
static_assert(function_argument_count_v<_Function> == 0, "Property getter function must not take any arguments");
static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
if (signature.empty())
signature = signature_of_function_output_arguments_v<_Function>;
getter = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply)
{
// Get the propety value and serialize it into the pre-constructed reply message
reply << callback();
};
return *this;
}
template <typename _Function>
inline PropertyVTableItem& PropertyVTableItem::withSetter(_Function&& callback)
{
static_assert(function_argument_count_v<_Function> == 1, "Property setter function must take one parameter - the property value");
static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
if (signature.empty())
signature = signature_of_function_input_arguments_v<_Function>;
setter = [callback = std::forward<_Function>(callback)](PropertySetCall call)
{
// Default-construct property value
using property_type = function_argument_t<_Function, 0>;
std::decay_t<property_type> property;
// Deserialize property value from the incoming call message
call >> property;
// Invoke setter with the value
callback(property);
};
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline PropertyVTableItem& PropertyVTableItem::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags.set(behavior);
return *this;
}
inline PropertyVTableItem registerProperty(PropertyName propertyName)
{
return {std::move(propertyName), {}, {}, {}, {}};
}
inline PropertyVTableItem registerProperty(std::string propertyName)
{
return registerProperty(PropertyName{std::move(propertyName)});
}
/*** --------------------------- ***/
/*** Interface Flags VTable Item ***/
/*** --------------------------- ***/
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsDeprecated()
{
flags.set(Flags::DEPRECATED);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::markAsPrivileged()
{
flags.set(Flags::PRIVILEGED);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withNoReplyMethods()
{
flags.set(Flags::METHOD_NO_REPLY);
return *this;
}
inline InterfaceFlagsVTableItem& InterfaceFlagsVTableItem::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
{
flags.set(behavior);
return *this;
}
inline InterfaceFlagsVTableItem setInterfaceFlags()
{
return {};
}
} // namespace sdbus
#endif /* SDBUS_CPP_VTABLEITEMS_INL_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file sdbus-c++.h
*
@ -25,10 +26,13 @@
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <sdbus-c++/Interfaces.h>
#include <sdbus-c++/IProxy.h>
#include <sdbus-c++/AdaptorInterfaces.h>
#include <sdbus-c++/ProxyInterfaces.h>
#include <sdbus-c++/StandardInterfaces.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/MethodResult.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Introspection.h>
#include <sdbus-c++/Error.h>
#include <sdbus-c++/Flags.h>

11
pkgconfig/sdbus-c++.pc.in Normal file
View File

@ -0,0 +1,11 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
Name: @PROJECT_NAME@
Description: C++ library on top of sd-bus, a systemd D-Bus library
Requires@PKGCONFIG_REQS@: @PKGCONFIG_DEPS@
Version: @SDBUSCPP_VERSION@
Libs: -L${libdir} -l@PROJECT_NAME@
Cflags: -I${includedir}

View File

@ -1,11 +0,0 @@
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: @PACKAGE@
Description: C++ bindings library for sd-bus
Requires: libsystemd
Version: @VERSION@
Libs: -L${libdir} -lsdbus-c++
Cflags: -I${includedir}

997
src/Connection.cpp Executable file → Normal file

File diff suppressed because it is too large Load Diff

274
src/Connection.h Executable file → Normal file
View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Connection.h
*
@ -26,81 +27,230 @@
#ifndef SDBUS_CXX_INTERNAL_CONNECTION_H_
#define SDBUS_CXX_INTERNAL_CONNECTION_H_
#include <sdbus-c++/IConnection.h>
#include <systemd/sd-bus.h>
#include <memory>
#include <atomic>
#include <thread>
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Message.h"
#include "IConnection.h"
#include "ISdBus.h"
#include "ScopeGuard.h"
namespace sdbus { namespace internal {
#include <memory>
#include <string>
#include SDBUS_HEADER
#include <thread>
#include <vector>
class Connection
: public sdbus::IConnection // External, public interface
, public sdbus::internal::IConnection // Internal, private interface
// Forward declarations
struct sd_event_source;
namespace sdbus {
class ObjectPath;
class InterfaceName;
class BusName;
using ServiceName = BusName;
class MemberName;
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
}
namespace sdbus::internal {
class Connection final
: public sdbus::internal::IConnection
{
public:
enum class BusType
// Bus type tags
struct default_bus_t{};
inline static constexpr default_bus_t default_bus{};
struct system_bus_t{};
inline static constexpr system_bus_t system_bus{};
struct session_bus_t{};
inline static constexpr session_bus_t session_bus{};
struct custom_session_bus_t{};
inline static constexpr custom_session_bus_t custom_session_bus{};
struct remote_system_bus_t{};
inline static constexpr remote_system_bus_t remote_system_bus{};
struct private_bus_t{};
inline static constexpr private_bus_t private_bus{};
struct server_bus_t{};
inline static constexpr server_bus_t server_bus{};
struct sdbus_bus_t{}; // A bus connection created directly from existing sd_bus instance
inline static constexpr sdbus_bus_t sdbus_bus{};
struct pseudo_bus_t{}; // A bus connection that is not really established with D-Bus daemon
inline static constexpr pseudo_bus_t pseudo_bus{};
Connection(std::unique_ptr<ISdBus>&& interface, default_bus_t);
Connection(std::unique_ptr<ISdBus>&& interface, system_bus_t);
Connection(std::unique_ptr<ISdBus>&& interface, session_bus_t);
Connection(std::unique_ptr<ISdBus>&& interface, custom_session_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t, const std::string& host);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, const std::string& address);
Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd);
Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus);
Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t);
~Connection() override;
void requestName(const ServiceName & name) override;
void releaseName(const ServiceName& name) override;
[[nodiscard]] BusName getUniqueName() const override;
void enterEventLoop() override;
void enterEventLoopAsync() override;
void leaveEventLoop() override;
[[nodiscard]] PollData getEventLoopPollData() const override;
bool processPendingEvent() override;
Message getCurrentlyProcessedMessage() const override;
void addObjectManager(const ObjectPath& objectPath) override;
Slot addObjectManager(const ObjectPath& objectPath, return_slot_t) override;
void setMethodCallTimeout(uint64_t timeout) override;
[[nodiscard]] uint64_t getMethodCallTimeout() const override;
void addMatch(const std::string& match, message_handler callback) override;
[[nodiscard]] Slot addMatch(const std::string& match, message_handler callback, return_slot_t) override;
void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override;
[[nodiscard]] Slot addMatchAsync( const std::string& match
, message_handler callback
, message_handler installCallback
, return_slot_t ) override;
void attachSdEventLoop(sd_event *event, int priority) override;
void detachSdEventLoop() override;
sd_event *getSdEventLoop() override;
Slot addObjectVTable( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const sd_bus_vtable* vtable
, void* userData
, return_slot_t ) override;
[[nodiscard]] PlainMessage createPlainMessage() const override;
[[nodiscard]] MethodCall createMethodCall( const ServiceName& destination
, const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const MethodName& methodName ) const override;
[[nodiscard]] MethodCall createMethodCall( const char* destination
, const char* objectPath
, const char* interfaceName
, const char* methodName ) const override;
[[nodiscard]] Signal createSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const SignalName& signalName ) const override;
[[nodiscard]] Signal createSignal( const char* objectPath
, const char* interfaceName
, const char* signalName ) const override;
void emitPropertiesChangedSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const std::vector<PropertyName>& propNames ) override;
void emitPropertiesChangedSignal( const char* objectPath
, const char* interfaceName
, const std::vector<PropertyName>& propNames ) override;
void emitInterfacesAddedSignal(const ObjectPath& objectPath) override;
void emitInterfacesAddedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) override;
void emitInterfacesRemovedSignal(const ObjectPath& objectPath) override;
void emitInterfacesRemovedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) override;
Slot registerSignalHandler( const char* sender
, const char* objectPath
, const char* interfaceName
, const char* signalName
, sd_bus_message_handler_t callback
, void* userData
, return_slot_t ) override;
sd_bus_message* incrementMessageRefCount(sd_bus_message* sdbusMsg) override;
sd_bus_message* decrementMessageRefCount(sd_bus_message* sdbusMsg) override;
int querySenderCredentials(sd_bus_message* sdbusMsg, uint64_t mask, sd_bus_creds **creds) override;
sd_bus_creds* incrementCredsRefCount(sd_bus_creds* creds) override;
sd_bus_creds* decrementCredsRefCount(sd_bus_creds* creds) override;
sd_bus_message* callMethod(sd_bus_message* sdbusMsg, uint64_t timeout) override;
Slot callMethodAsync(sd_bus_message* sdbusMsg, sd_bus_message_handler_t callback, void* userData, uint64_t timeout, return_slot_t) override;
void sendMessage(sd_bus_message* sdbusMsg) override;
sd_bus_message* createMethodReply(sd_bus_message* sdbusMsg) override;
sd_bus_message* createErrorReplyMessage(sd_bus_message* sdbusMsg, const Error& error) override;
private:
using BusFactory = std::function<int(sd_bus**)>;
using BusPtr = std::unique_ptr<sd_bus, std::function<sd_bus*(sd_bus*)>>;
Connection(std::unique_ptr<ISdBus>&& interface, const BusFactory& busFactory);
BusPtr openBus(const std::function<int(sd_bus**)>& busFactory);
BusPtr openPseudoBus();
void finishHandshake(sd_bus* bus);
bool waitForNextEvent();
[[nodiscard]] bool arePendingMessagesInQueues() const;
void notifyEventLoopToExit();
void notifyEventLoopToWakeUpFromPoll();
void wakeUpEventLoopIfMessagesInQueue();
void joinWithEventLoop();
template <typename StringBasedType>
static std::vector</*const */char*> to_strv(const std::vector<StringBasedType>& strings);
static int sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
#ifndef SDBUS_basu // sd_event integration is not supported if instead of libsystemd we are based on basu
Slot createSdEventSlot(sd_event *event);
Slot createSdTimeEventSourceSlot(sd_event *event, int priority);
Slot createSdIoEventSourceSlot(sd_event *event, int fd, int priority);
Slot createSdInternalEventSourceSlot(sd_event *event, int fd, int priority);
static void deleteSdEventSource(sd_event_source *s);
static int onSdTimerEvent(sd_event_source *s, uint64_t usec, void *userdata);
static int onSdIoEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata);
static int onSdInternalEvent(sd_event_source *s, int fd, uint32_t revents, void *userdata);
static int onSdEventPrepare(sd_event_source *s, void *userdata);
#endif
struct EventFd
{
eSystem,
eSession
EventFd();
~EventFd();
void notify();
bool clear();
int fd{-1};
};
Connection(BusType type);
~Connection();
struct MatchInfo
{
message_handler callback;
message_handler installCallback;
Connection& connection;
Slot slot;
};
void requestName(const std::string& name) override;
void releaseName(const std::string& name) override;
void enterProcessingLoop() override;
void enterProcessingLoopAsync() override;
void leaveProcessingLoop() override;
void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) override;
void removeObjectVTable(void* vtableHandle) override;
sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const override;
sdbus::Message createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const override;
void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) override;
void unregisterSignalHandler(void* handlerCookie) override;
std::unique_ptr<sdbus::internal::IConnection> clone() const override;
// sd-event integration
struct SdEvent
{
Slot sdEvent;
Slot sdTimeEventSource;
Slot sdIoEventSource;
Slot sdInternalEventSource;
};
private:
static sd_bus* openBus(Connection::BusType type);
static void finishHandshake(sd_bus* bus);
static int createProcessingExitDescriptor();
static void closeProcessingExitDescriptor(int fd);
static bool processPendingRequest(sd_bus* bus);
static bool waitForNextRequest(sd_bus* bus, int exitFd);
static std::string composeSignalMatchFilter( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName );
void notifyProcessingLoopToExit();
void joinWithProcessingLoop();
private:
std::unique_ptr<sd_bus, decltype(&sd_bus_flush_close_unref)> bus_{nullptr, &sd_bus_flush_close_unref};
std::unique_ptr<ISdBus> sdbus_;
BusPtr bus_;
std::thread asyncLoopThread_;
std::atomic<int> exitLoopFd_{-1};
BusType busType_;
static constexpr const uint64_t POLL_TIMEOUT_USEC = 500000;
EventFd loopExitFd_; // To wake up event loop I/O polling to exit
EventFd eventFd_; // To wake up event loop I/O polling to re-enter poll with fresh PollData values
std::vector<Slot> floatingMatchRules_;
std::unique_ptr<SdEvent> sdEvent_; // Integration of systemd sd-event event loop implementation
};
}}
}
#endif /* SDBUS_CXX_INTERNAL_CONNECTION_H_ */

View File

@ -1,150 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ConvenienceClasses.cpp
*
* Created on: Jan 19, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/ConvenienceClasses.h>
#include <sdbus-c++/IObject.h>
#include <sdbus-c++/IObjectProxy.h>
#include <string>
#include <exception>
namespace sdbus {
SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the signal if SignalRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus signal", EINVAL);
// registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerSignal(interfaceName_, signalName_, signalSignature_);
}
PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
: object_(object)
, propertyName_(propertyName)
, exceptions_(std::uncaught_exceptions())
{
}
PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't register the property if PropertyRegistrator threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(interfaceName_.empty(), "DBus interface not specified when registering a DBus property", EINVAL);
// registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow registerProperty() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.registerProperty( std::move(interfaceName_)
, std::move(propertyName_)
, std::move(propertySignature_)
, std::move(getter_)
, std::move(setter_) );
}
SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
: object_(object)
, signalName_(signalName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't emit the signal if SignalEmitter threw an exception in one of its methods
if (std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(!signal_.isValid(), "DBus interface not specified when emitting a DBus signal", EINVAL);
// emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow emitSignal() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
object_.emitSignal(signal_);
}
MethodInvoker::MethodInvoker(IObjectProxy& objectProxy, const std::string& methodName)
: objectProxy_(objectProxy)
, methodName_(methodName)
, exceptions_(std::uncaught_exceptions()) // Needs C++17
{
}
MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
{ // explicitly be allowed to throw
// Don't call the method if it has been called already or if MethodInvoker
// threw an exception in one of its methods
if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
return;
SDBUS_THROW_ERROR_IF(!method_.isValid(), "DBus interface not specified when calling a DBus method", EINVAL);
// callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed,
// temporary object, i.e. not as a stack-allocated object, the double-exception situation
// shall never happen. I.e. it should not happen that this destructor is directly called
// in the stack-unwinding process of another flying exception (which would lead to immediate
// termination). It can be called indirectly in the destructor of another object, but that's
// fine and safe provided that the caller catches exceptions thrown from here.
// Therefore, we can allow callMethod() to throw even if we are in the destructor.
// Bottomline is, to be on the safe side, the caller must take care of catching and reacting
// to the exception thrown from here if the caller is a destructor itself.
objectProxy_.callMethod(method_);
}
}

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Error.cpp
*
@ -23,20 +24,26 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Error.h>
#include <systemd/sd-bus.h>
#include "sdbus-c++/Error.h"
#include "ScopeGuard.h"
#include SDBUS_HEADER
namespace sdbus
{
sdbus::Error createError(int errNo, const std::string& customMsg)
sdbus::Error createError(int errNo, std::string customMsg)
{
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
sd_bus_error_set_errno(&sdbusError, errNo);
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
std::string name(sdbusError.name);
std::string message(customMsg + " (" + sdbusError.message + ")");
return sdbus::Error(name, message);
Error::Name name(sdbusError.name);
std::string message(std::move(customMsg));
message.append(" (");
message.append(sdbusError.message);
message.append(")");
return Error(std::move(name), std::move(message));
}
}
} // namespace sdbus

112
src/Flags.cpp Normal file
View File

@ -0,0 +1,112 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Flags.cpp
*
* Created on: Dec 31, 2018
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Flags.h>
#include SDBUS_HEADER
namespace sdbus
{
uint64_t Flags::toSdBusInterfaceFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::EMITS_CHANGE_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
else if (flags_.test(Flags::CONST_PROPERTY_VALUE))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0;
return sdbusFlags;
}
uint64_t Flags::toSdBusMethodFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::METHOD_NO_REPLY))
sdbusFlags |= SD_BUS_VTABLE_METHOD_NO_REPLY;
return sdbusFlags;
}
uint64_t Flags::toSdBusSignalFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
return sdbusFlags;
}
uint64_t Flags::toSdBusPropertyFlags() const
{
uint64_t sdbusFlags{};
using namespace sdbus;
if (flags_.test(Flags::DEPRECATED))
sdbusFlags |= SD_BUS_VTABLE_DEPRECATED;
//if (!flags_.test(Flags::PRIVILEGED))
// sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
if (flags_.test(Flags::EMITS_CHANGE_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
else if (flags_.test(Flags::EMITS_INVALIDATION_SIGNAL))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
else if (flags_.test(Flags::CONST_PROPERTY_VALUE))
sdbusFlags |= SD_BUS_VTABLE_PROPERTY_CONST;
else if (flags_.test(Flags::EMITS_NO_SIGNAL))
sdbusFlags |= 0;
return sdbusFlags;
}
uint64_t Flags::toSdBusWritablePropertyFlags() const
{
auto sdbusFlags = toSdBusPropertyFlags();
using namespace sdbus;
if (!flags_.test(Flags::PRIVILEGED))
sdbusFlags |= SD_BUS_VTABLE_UNPRIVILEGED;
return sdbusFlags;
}
}

120
src/IConnection.h Executable file → Normal file
View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file IConnection.h
*
@ -26,52 +27,109 @@
#ifndef SDBUS_CXX_INTERNAL_ICONNECTION_H_
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_
#include <systemd/sd-bus.h>
#include <string>
#include <memory>
#include "sdbus-c++/IConnection.h"
// Forward declaration
#include "sdbus-c++/TypeTraits.h"
#include <functional>
#include <memory>
#include <string>
#include SDBUS_HEADER
#include <vector>
// Forward declarations
namespace sdbus {
class Message;
class MethodCall;
class MethodReply;
class Signal;
class PlainMessage;
class ObjectPath;
class InterfaceName;
class BusName;
using ServiceName = BusName;
class MemberName;
using MethodName = MemberName;
using SignalName = MemberName;
using PropertyName = MemberName;
class Error;
namespace internal {
class ISdBus;
}
}
namespace sdbus {
namespace internal {
namespace sdbus::internal {
class IConnection
: public ::sdbus::IConnection
{
public:
virtual void* addObjectVTable( const std::string& objectPath
, const std::string& interfaceName
, const void* vtable
, void* userData ) = 0;
virtual void removeObjectVTable(void* vtableHandle) = 0;
~IConnection() override = default;
virtual sdbus::Message createMethodCall( const std::string& destination
, const std::string& objectPath
, const std::string& interfaceName
, const std::string& methodName ) const = 0;
[[nodiscard]] virtual Slot addObjectVTable( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const sd_bus_vtable* vtable
, void* userData
, return_slot_t ) = 0;
virtual sdbus::Message createSignal( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName ) const = 0;
[[nodiscard]] virtual PlainMessage createPlainMessage() const = 0;
[[nodiscard]] virtual MethodCall createMethodCall( const ServiceName& destination
, const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const MethodName& methodName ) const = 0;
[[nodiscard]] virtual MethodCall createMethodCall( const char* destination
, const char* objectPath
, const char* interfaceName
, const char* methodName ) const = 0;
[[nodiscard]] virtual Signal createSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const SignalName& signalName ) const = 0;
[[nodiscard]] virtual Signal createSignal( const char* objectPath
, const char* interfaceName
, const char* signalName ) const = 0;
virtual void* registerSignalHandler( const std::string& objectPath
, const std::string& interfaceName
, const std::string& signalName
, sd_bus_message_handler_t callback
, void* userData ) = 0;
virtual void unregisterSignalHandler(void* handlerCookie) = 0;
virtual void emitPropertiesChangedSignal( const ObjectPath& objectPath
, const InterfaceName& interfaceName
, const std::vector<PropertyName>& propNames ) = 0;
virtual void emitPropertiesChangedSignal( const char* objectPath
, const char* interfaceName
, const std::vector<PropertyName>& propNames ) = 0;
virtual void emitInterfacesAddedSignal(const ObjectPath& objectPath) = 0;
virtual void emitInterfacesAddedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) = 0;
virtual void emitInterfacesRemovedSignal(const ObjectPath& objectPath) = 0;
virtual void emitInterfacesRemovedSignal( const ObjectPath& objectPath
, const std::vector<InterfaceName>& interfaces ) = 0;
virtual void enterProcessingLoopAsync() = 0;
virtual void leaveProcessingLoop() = 0;
[[nodiscard]] virtual Slot registerSignalHandler( const char* sender
, const char* objectPath
, const char* interfaceName
, const char* signalName
, sd_bus_message_handler_t callback
, void* userData
, return_slot_t ) = 0;
virtual std::unique_ptr<sdbus::internal::IConnection> clone() const = 0;
virtual sd_bus_message* incrementMessageRefCount(sd_bus_message* sdbusMsg) = 0;
virtual sd_bus_message* decrementMessageRefCount(sd_bus_message* sdbusMsg) = 0;
virtual ~IConnection() = default;
// TODO: Refactor to higher level (Creds class will ownership handling and getters)
virtual int querySenderCredentials(sd_bus_message* sdbusMsg, uint64_t mask, sd_bus_creds **creds) = 0;
virtual sd_bus_creds* incrementCredsRefCount(sd_bus_creds* creds) = 0;
virtual sd_bus_creds* decrementCredsRefCount(sd_bus_creds* creds) = 0;
virtual sd_bus_message* callMethod(sd_bus_message* sdbusMsg, uint64_t timeout) = 0;
[[nodiscard]] virtual Slot callMethodAsync( sd_bus_message* sdbusMsg
, sd_bus_message_handler_t callback
, void* userData
, uint64_t timeout
, return_slot_t ) = 0;
virtual void sendMessage(sd_bus_message* sdbusMsg) = 0;
virtual sd_bus_message* createMethodReply(sd_bus_message* sdbusMsg) = 0;
virtual sd_bus_message* createErrorReplyMessage(sd_bus_message* sdbusMsg, const Error& error) = 0;
};
}
[[nodiscard]] std::unique_ptr<sdbus::internal::IConnection> createPseudoConnection();
}
#endif /* SDBUS_CXX_INTERNAL_ICONNECTION_H_ */

115
src/ISdBus.h Normal file
View File

@ -0,0 +1,115 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ISdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 12, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_ISDBUS_H
#define SDBUS_CXX_ISDBUS_H
#include SDBUS_HEADER
namespace sdbus::internal {
class ISdBus
{
public:
struct PollData
{
int fd;
short int events;
uint64_t timeout_usec;
};
virtual ~ISdBus() = default;
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) = 0;
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) = 0;
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) = 0;
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) = 0;
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) = 0;
virtual int sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type) = 0;
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) = 0;
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) = 0;
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_set_method_call_timeout(sd_bus *bus, uint64_t usec) = 0;
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) = 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_emit_object_added(sd_bus *bus, const char *path) = 0;
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) = 0;
virtual int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) = 0;
virtual int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) = 0;
virtual int sd_bus_open(sd_bus **ret) = 0;
virtual int sd_bus_open_system(sd_bus **ret) = 0;
virtual int sd_bus_open_user(sd_bus **ret) = 0;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* host) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) = 0;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) = 0;
virtual int sd_bus_open_server(sd_bus **ret, int fd) = 0;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) = 0;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) = 0;
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) = 0;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) = 0;
virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) = 0;
virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) = 0;
virtual int sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) = 0;
virtual int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) = 0;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
virtual int sd_bus_new(sd_bus **ret) = 0;
virtual int sd_bus_start(sd_bus *bus) = 0;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) = 0;
virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) = 0;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) = 0;
virtual int sd_bus_get_n_queued(sd_bus *bus, uint64_t *read, uint64_t* write) = 0;
virtual int sd_bus_flush(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) = 0;
virtual sd_bus *sd_bus_close_unref(sd_bus *bus) = 0;
virtual int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) = 0;
virtual int sd_bus_query_sender_creds(sd_bus_message *m, uint64_t mask, sd_bus_creds **c) = 0;
virtual sd_bus_creds* sd_bus_creds_ref(sd_bus_creds *c) = 0;
virtual sd_bus_creds* sd_bus_creds_unref(sd_bus_creds *c) = 0;
virtual int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) = 0;
virtual int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) = 0;
virtual int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *uid) = 0;
virtual int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) = 0;
virtual int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) = 0;
virtual int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) = 0;
virtual int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **label) = 0;
};
}
#endif //SDBUS_CXX_ISDBUS_H

View File

@ -1,26 +0,0 @@
lib_LTLIBRARIES = libsdbus-c++.la
libsdbus_c___la_SOURCES = \
Connection.cpp \
ConvenienceClasses.cpp \
Message.cpp \
Object.cpp \
ObjectProxy.cpp \
Types.cpp \
Error.cpp \
VTableUtils.c
libsdbus_c___la_LIBADD = @SYSTEMD_LIBS@
# Setting per-file flags
AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -std=c++17 -pipe -pedantic -W -Wall
AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@
libsdbus_c___la_LDFLAGS = -version-info 0:0:0
# Cleaning
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
DISTCLEANFILES = Makefile Makefile.in

511
src/Message.cpp Executable file → Normal file
View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Message.cpp
*
@ -23,22 +24,42 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "sdbus-c++/Message.h"
#include "sdbus-c++/Error.h"
#include "sdbus-c++/Types.h"
#include "IConnection.h"
#include "MessageUtils.h"
#include "ScopeGuard.h"
#include <systemd/sd-bus.h>
#include <cassert>
#include <cstring>
#include SDBUS_HEADER
namespace sdbus { /*namespace internal {*/
namespace sdbus {
Message::Message(void *msg, Type type) noexcept
Message::Message(internal::IConnection* connection) noexcept
: connection_(connection)
{
assert(connection_ != nullptr);
}
Message::Message(void *msg, internal::IConnection* connection) noexcept
: msg_(msg)
, type_(type)
, connection_(connection)
{
assert(msg_ != nullptr);
sd_bus_message_ref((sd_bus_message*)msg_);
assert(connection_ != nullptr);
connection_->incrementMessageRefCount((sd_bus_message*)msg_);
}
Message::Message(void *msg, internal::IConnection* connection, adopt_message_t) noexcept
: msg_(msg)
, connection_(connection)
{
assert(msg_ != nullptr);
assert(connection_ != nullptr);
}
Message::Message(const Message& other) noexcept
@ -49,13 +70,13 @@ Message::Message(const Message& other) noexcept
Message& Message::operator=(const Message& other) noexcept
{
if (msg_)
sd_bus_message_unref((sd_bus_message*)msg_);
connection_->decrementMessageRefCount((sd_bus_message*)msg_);
msg_ = other.msg_;
type_ = other.type_;
connection_ = other.connection_;
ok_ = other.ok_;
sd_bus_message_ref((sd_bus_message*)msg_);
connection_->incrementMessageRefCount((sd_bus_message*)msg_);
return *this;
}
@ -68,12 +89,12 @@ Message::Message(Message&& other) noexcept
Message& Message::operator=(Message&& other) noexcept
{
if (msg_)
sd_bus_message_unref((sd_bus_message*)msg_);
connection_->decrementMessageRefCount((sd_bus_message*)msg_);
msg_ = other.msg_;
other.msg_ = nullptr;
type_ = other.type_;
other.type_ = {};
connection_ = other.connection_;
other.connection_ = nullptr;
ok_ = other.ok_;
other.ok_ = true;
@ -83,13 +104,17 @@ Message& Message::operator=(Message&& other) noexcept
Message::~Message()
{
if (msg_)
sd_bus_message_unref((sd_bus_message*)msg_);
connection_->decrementMessageRefCount((sd_bus_message*)msg_);
}
Message& Message::operator<<(bool item)
{
int intItem = item;
// Direct sd-bus method, bypassing SdBus mutex, are called here, since Message serialization/deserialization,
// as well as getter/setter methods are not thread safe by design. Additionally, they are called frequently,
// so some overhead is spared. What is thread-safe in Message class is Message constructors, copy/move operations
// and the destructor, so the Message instance can be passed from one thread to another safely.
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_BOOLEAN, &intItem);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a bool value", -r);
@ -176,6 +201,17 @@ Message& Message::operator<<(const std::string& item)
return *this;
}
Message& Message::operator<<(std::string_view item)
{
char* destPtr{};
auto r = sd_bus_message_append_string_space((sd_bus_message*)msg_, item.length(), &destPtr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a string_view value", -r);
std::memcpy(destPtr, item.data(), item.length());
return *this;
}
Message& Message::operator<<(const Variant &item)
{
item.serializeTo(*this);
@ -194,11 +230,27 @@ Message& Message::operator<<(const ObjectPath &item)
Message& Message::operator<<(const Signature &item)
{
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_SIGNATURE, item.c_str());
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an Signature value", -r);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a Signature value", -r);
return *this;
}
Message& Message::operator<<(const UnixFd &item)
{
auto fd = item.get();
auto r = sd_bus_message_append_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &fd);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize a UnixFd value", -r);
return *this;
}
Message& Message::appendArray(char type, const void *ptr, size_t size)
{
auto r = sd_bus_message_append_array((sd_bus_message*)msg_, type, ptr, size);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to serialize an array", -r);
return *this;
}
Message& Message::operator>>(bool& item)
{
@ -291,6 +343,17 @@ Message& Message::operator>>(uint64_t& item)
return *this;
}
Message& Message::readArray(char type, const void **ptr, size_t *size)
{
auto r = sd_bus_message_read_array((sd_bus_message*)msg_, type, ptr, size);
if (r == 0)
ok_ = false;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize an array", -r);
return *this;
}
Message& Message::operator>>(double& item)
{
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_DOUBLE, &item);
@ -367,10 +430,23 @@ Message& Message::operator>>(Signature &item)
return *this;
}
Message& Message::openContainer(const std::string& signature)
Message& Message::operator>>(UnixFd &item)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
int fd = -1;
auto r = sd_bus_message_read_basic((sd_bus_message*)msg_, SD_BUS_TYPE_UNIX_FD, &fd);
if (r == 0)
ok_ = false;
SDBUS_THROW_ERROR_IF(r < 0, "Failed to deserialize a UnixFd value", -r);
item.reset(fd);
return *this;
}
Message& Message::openContainer(const char* signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a container", -r);
return *this;
@ -384,9 +460,9 @@ Message& Message::closeContainer()
return *this;
}
Message& Message::openDictEntry(const std::string& signature)
Message& Message::openDictEntry(const char* signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a dictionary entry", -r);
return *this;
@ -400,9 +476,9 @@ Message& Message::closeDictEntry()
return *this;
}
Message& Message::openVariant(const std::string& signature)
Message& Message::openVariant(const char* signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a variant", -r);
return *this;
@ -416,9 +492,9 @@ Message& Message::closeVariant()
return *this;
}
Message& Message::openStruct(const std::string& signature)
Message& Message::openStruct(const char* signature)
{
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
auto r = sd_bus_message_open_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to open a struct", -r);
return *this;
@ -432,10 +508,9 @@ Message& Message::closeStruct()
return *this;
}
Message& Message::enterContainer(const std::string& signature)
Message& Message::enterContainer(const char* signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature.c_str());
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_ARRAY, signature);
if (r == 0)
ok_ = false;
@ -452,9 +527,9 @@ Message& Message::exitContainer()
return *this;
}
Message& Message::enterDictEntry(const std::string& signature)
Message& Message::enterDictEntry(const char* signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature.c_str());
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_DICT_ENTRY, signature);
if (r == 0)
ok_ = false;
@ -471,9 +546,9 @@ Message& Message::exitDictEntry()
return *this;
}
Message& Message::enterVariant(const std::string& signature)
Message& Message::enterVariant(const char* signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature.c_str());
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_VARIANT, signature);
if (r == 0)
ok_ = false;
@ -490,9 +565,9 @@ Message& Message::exitVariant()
return *this;
}
Message& Message::enterStruct(const std::string& signature)
Message& Message::enterStruct(const char* signature)
{
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature.c_str());
auto r = sd_bus_message_enter_container((sd_bus_message*)msg_, SD_BUS_TYPE_STRUCT, signature);
if (r == 0)
ok_ = false;
@ -540,109 +615,319 @@ void Message::rewind(bool complete)
SDBUS_THROW_ERROR_IF(r < 0, "Failed to rewind the message", -r);
}
Message Message::send() const
{
if (type_ == Type::eMethodCall)
{
sd_bus_message* sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
sd_bus_error sdbusError = SD_BUS_ERROR_NULL;
SCOPE_EXIT{ sd_bus_error_free(&sdbusError); };
auto r = sd_bus_call(nullptr, (sd_bus_message*)msg_, 0, &sdbusError, &sdbusReply);
if (sd_bus_error_is_set(&sdbusError))
{
throw sdbus::Error(sdbusError.name, sdbusError.message);
}
SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method", -r);
return Message(sdbusReply);
}
else if (type_ == Type::eMethodReply)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to send reply", -r);
return Message();
}
else if (type_ == Type::eSignal)
{
auto r = sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to emit signal", -r);
return Message();
}
else
{
assert(false);
return Message();
}
}
Message Message::createReply() const
{
sd_bus_message *sdbusReply{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusReply); }; // Returned message will become an owner of sdbusReply
auto r = sd_bus_message_new_method_return((sd_bus_message*)msg_, &sdbusReply);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create method reply", -r);
assert(sdbusReply != nullptr);
return Message(sdbusReply, Type::eMethodReply);
}
std::string Message::getInterfaceName() const
const char* Message::getInterfaceName() const
{
return sd_bus_message_get_interface((sd_bus_message*)msg_);
}
std::string Message::getMemberName() const
const char* Message::getMemberName() const
{
return sd_bus_message_get_member((sd_bus_message*)msg_);
}
void Message::peekType(std::string& type, std::string& contents) const
const char* Message::getSender() const
{
char typeSig;
const char* contentsSig;
auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSig, &contentsSig);
return sd_bus_message_get_sender((sd_bus_message*)msg_);
}
const char* Message::getPath() const
{
return sd_bus_message_get_path((sd_bus_message*)msg_);
}
const char* Message::getDestination() const
{
return sd_bus_message_get_destination((sd_bus_message*)msg_);
}
uint64_t Message::getCookie() const
{
uint64_t cookie;
auto r = sd_bus_message_get_cookie((sd_bus_message*)msg_, &cookie);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get cookie", -r);
return cookie;
}
std::pair<char, const char*> Message::peekType() const
{
char typeSignature{};
const char* contentsSignature{};
auto r = sd_bus_message_peek_type((sd_bus_message*)msg_, &typeSignature, &contentsSignature);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to peek message type", -r);
type = typeSig;
contents = contentsSig;
return {typeSignature, contentsSignature};
}
bool Message::isValid() const
{
return msg_ != nullptr;
return msg_ != nullptr && connection_ != nullptr;
}
bool Message::isEmpty() const
{
return sd_bus_message_is_empty((sd_bus_message*)msg_);
return sd_bus_message_is_empty((sd_bus_message*)msg_) != 0;
}
Message::Type Message::getType() const
bool Message::isAtEnd(bool complete) const
{
return type_;
return sd_bus_message_at_end((sd_bus_message*)msg_, complete) > 0;
}
Message createPlainMessage()
// TODO: Create a RAII ownership class for creds with copy&move semantics, doing ref()/unref() under the hood.
// Create a method Message::querySenderCreds() that will return an object of this class by value, through IConnection and SdBus mutex.
// The class will expose methods like getPid(), getUid(), etc. that will directly call sd_bus_creds_* functions, no need for mutex here.
pid_t Message::getCredsPid() const
{
int r;
uint64_t mask = SD_BUS_CREDS_PID | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
sd_bus* bus{};
SCOPE_EXIT{ sd_bus_unref(bus); }; // sdbusMsg will hold reference to the bus
r = sd_bus_default_system(&bus);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get default system bus", -r);
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
sd_bus_message* sdbusMsg{};
SCOPE_EXIT{ sd_bus_message_unref(sdbusMsg); }; // Returned message will become an owner of sdbusMsg
r = sd_bus_message_new(bus, &sdbusMsg, _SD_BUS_MESSAGE_TYPE_INVALID);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to create a new message", -r);
return Message(sdbusMsg, Message::Type::ePlainMessage);
pid_t pid = 0;
r = sd_bus_creds_get_pid(creds, &pid);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred pid", -r);
return pid;
}
/*}*/}
uid_t Message::getCredsUid() const
{
uint64_t mask = SD_BUS_CREDS_UID | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
uid_t uid = (uid_t)-1;
r = sd_bus_creds_get_uid(creds, &uid);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred uid", -r);
return uid;
}
uid_t Message::getCredsEuid() const
{
uint64_t mask = SD_BUS_CREDS_EUID | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
uid_t euid = (uid_t)-1;
r = sd_bus_creds_get_euid(creds, &euid);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred euid", -r);
return euid;
}
gid_t Message::getCredsGid() const
{
uint64_t mask = SD_BUS_CREDS_GID | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
gid_t gid = (gid_t)-1;
r = sd_bus_creds_get_gid(creds, &gid);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred gid", -r);
return gid;
}
gid_t Message::getCredsEgid() const
{
uint64_t mask = SD_BUS_CREDS_EGID | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
gid_t egid = (gid_t)-1;
r = sd_bus_creds_get_egid(creds, &egid);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred egid", -r);
return egid;
}
std::vector<gid_t> Message::getCredsSupplementaryGids() const
{
uint64_t mask = SD_BUS_CREDS_SUPPLEMENTARY_GIDS | SD_BUS_CREDS_AUGMENT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
const gid_t *cGids = nullptr;
r = sd_bus_creds_get_supplementary_gids(creds, &cGids);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred supplementary gids", -r);
std::vector<gid_t> gids{};
if (cGids != nullptr)
{
for (int i = 0; i < r; i++)
gids.push_back(cGids[i]);
}
return gids;
}
std::string Message::getSELinuxContext() const
{
uint64_t mask = SD_BUS_CREDS_AUGMENT | SD_BUS_CREDS_SELINUX_CONTEXT;
sd_bus_creds *creds = nullptr;
SCOPE_EXIT{ connection_->decrementCredsRefCount(creds); };
int r = connection_->querySenderCredentials((sd_bus_message*)msg_, mask, &creds);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus creds", -r);
const char *cLabel = nullptr;
r = sd_bus_creds_get_selinux_context(creds, &cLabel);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get bus cred selinux context", -r);
return cLabel;
}
MethodCall::MethodCall( void *msg
, internal::IConnection *connection
, adopt_message_t) noexcept
: Message(msg, connection, adopt_message)
{
}
void MethodCall::dontExpectReply()
{
auto r = sd_bus_message_set_expect_reply((sd_bus_message*)msg_, 0);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set the dont-expect-reply flag", -r);
}
bool MethodCall::doesntExpectReply() const
{
auto r = sd_bus_message_get_expect_reply((sd_bus_message*)msg_);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get the dont-expect-reply flag", -r);
return r == 0;
}
MethodReply MethodCall::send(uint64_t timeout) const
{
if (!doesntExpectReply())
return sendWithReply(timeout);
else
return sendWithNoReply();
}
MethodReply MethodCall::sendWithReply(uint64_t timeout) const
{
auto* sdbusReply = connection_->callMethod((sd_bus_message*)msg_, timeout);
return Factory::create<MethodReply>(sdbusReply, connection_, adopt_message);
}
MethodReply MethodCall::sendWithNoReply() const
{
connection_->sendMessage((sd_bus_message*)msg_);
return Factory::create<MethodReply>(); // No reply
}
Slot MethodCall::send(void* callback, void* userData, uint64_t timeout, return_slot_t) const
{
return connection_->callMethodAsync((sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout, return_slot);
}
MethodReply MethodCall::createReply() const
{
auto* sdbusReply = connection_->createMethodReply((sd_bus_message*)msg_);
return Factory::create<MethodReply>(sdbusReply, connection_, adopt_message);
}
MethodReply MethodCall::createErrorReply(const Error& error) const
{
sd_bus_message* sdbusErrorReply = connection_->createErrorReplyMessage((sd_bus_message*)msg_, error);
return Factory::create<MethodReply>(sdbusErrorReply, connection_, adopt_message);
}
void MethodReply::send() const
{
connection_->sendMessage((sd_bus_message*)msg_);
}
uint64_t MethodReply::getReplyCookie() const
{
uint64_t cookie;
auto r = sd_bus_message_get_reply_cookie((sd_bus_message*)msg_, &cookie);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to get cookie", -r);
return cookie;
}
void Signal::send() const
{
connection_->sendMessage((sd_bus_message*)msg_);
}
void Signal::setDestination(const std::string& destination)
{
return setDestination(destination.c_str());
}
void Signal::setDestination(const char* destination)
{
auto r = sd_bus_message_set_destination((sd_bus_message*)msg_, destination);
SDBUS_THROW_ERROR_IF(r < 0, "Failed to set signal destination", -r);
}
namespace {
// Pseudo-connection lifetime handling. In standard cases, we could do with simply function-local static pseudo
// connection instance below. However, it may happen that client's sdbus-c++ objects outlive this static connection
// instance (because they are used in global application objects that were created before this connection, and thus
// are destroyed later). This by itself sounds like a smell in client's application design, but it is downright bad
// in sdbus-c++ because it has no control over when client's dependent statics get destroyed. A "Phoenix" pattern
// (see Modern C++ Design - Generic Programming and Design Patterns Applied, by Andrei Alexandrescu) is applied to fix
// this by re-creating the connection again in such cases and keeping it alive until the next exit handler is invoked.
// Please note that the solution is NOT thread-safe.
// Another common solution is global sdbus-c++ startup/shutdown functions, but that would be an intrusive change.
#ifdef __cpp_constinit
constinit static bool pseudoConnectionDestroyed{};
#else
static bool pseudoConnectionDestroyed{};
#endif
std::unique_ptr<sdbus::internal::IConnection, void(*)(sdbus::internal::IConnection*)> createPseudoConnection()
{
auto deleter = [](sdbus::internal::IConnection* con)
{
delete con;
pseudoConnectionDestroyed = true;
};
return {internal::createPseudoConnection().release(), std::move(deleter)};
}
sdbus::internal::IConnection& getPseudoConnectionInstance()
{
static auto connection = createPseudoConnection();
if (pseudoConnectionDestroyed)
{
connection = createPseudoConnection(); // Phoenix rising from the ashes
atexit([](){ connection.~unique_ptr(); }); // We have to manually take care of deleting the phoenix
pseudoConnectionDestroyed = false;
}
assert(connection != nullptr);
return *connection;
}
}
PlainMessage createPlainMessage()
{
// Let's create a pseudo connection -- one that does not really connect to the real bus.
// This is a bit of a hack, but it enables use to work with D-Bus message locally without
// the need of D-Bus daemon. This is especially useful in unit tests of both sdbus-c++ and client code.
// Additionally, it's light-weight and fast solution.
const auto& connection = getPseudoConnectionInstance();
return connection.createPlainMessage();
}
}

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file MessageUtils.h
*
@ -30,7 +31,33 @@
namespace sdbus
{
Message createPlainMessage();
class Message::Factory
{
public:
template<typename _Msg>
static _Msg create()
{
return _Msg{};
}
template<typename _Msg>
static _Msg create(void *msg)
{
return _Msg{msg};
}
template<typename _Msg>
static _Msg create(void *msg, internal::IConnection* connection)
{
return _Msg{msg, connection};
}
template<typename _Msg>
static _Msg create(void *msg, internal::IConnection* connection, adopt_message_t)
{
return _Msg{msg, connection, adopt_message};
}
};
}
#endif /* SDBUS_CXX_INTERNAL_MESSAGEUTILS_H_ */

449
src/Object.cpp Executable file → Normal file
View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Object.cpp
*
@ -24,261 +25,375 @@
*/
#include "Object.h"
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Message.h>
#include <sdbus-c++/Error.h>
#include "sdbus-c++/Error.h"
#include "sdbus-c++/Flags.h"
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Message.h"
#include "IConnection.h"
#include "MessageUtils.h"
#include "ScopeGuard.h"
#include "Utils.h"
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include <utility>
#include <cassert>
#include SDBUS_HEADER
#include <utility>
namespace sdbus { namespace internal {
namespace sdbus::internal {
Object::Object(sdbus::internal::IConnection& connection, std::string objectPath)
Object::Object(sdbus::internal::IConnection& connection, ObjectPath objectPath)
: connection_(connection), objectPath_(std::move(objectPath))
{
SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
}
void Object::registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback )
void Object::addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable)
{
SDBUS_THROW_ERROR_IF(!methodCallback, "Invalid method callback provided", EINVAL);
auto slot = Object::addVTable(std::move(interfaceName), std::move(vtable), return_slot);
auto& interface = interfaces_[interfaceName];
InterfaceData::MethodData methodData{inputSignature, outputSignature, std::move(methodCallback)};
auto inserted = interface.methods_.emplace(methodName, std::move(methodData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register method: method already exists", EINVAL);
vtables_.push_back(std::move(slot));
}
void Object::registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature )
Slot Object::addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t)
{
auto& interface = interfaces_[interfaceName];
SDBUS_CHECK_INTERFACE_NAME(interfaceName.c_str());
InterfaceData::SignalData signalData{signature};
auto inserted = interface.signals_.emplace(signalName, std::move(signalData)).second;
// 1st pass -- create vtable structure for internal sdbus-c++ purposes
auto internalVTable = std::make_unique<VTable>(createInternalVTable(std::move(interfaceName), std::move(vtable)));
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal: signal already exists", EINVAL);
// 2nd pass -- from internal sdbus-c++ vtable, create vtable structure in format expected by underlying sd-bus library
internalVTable->sdbusVTable = createInternalSdBusVTable(*internalVTable);
// 3rd step -- register the vtable with sd-bus
internalVTable->slot = connection_.addObjectVTable( objectPath_
, internalVTable->interfaceName
, &internalVTable->sdbusVTable[0]
, internalVTable.get()
, return_slot );
// Return vtable wrapped in a Slot object
return {internalVTable.release(), [](void *ptr){ delete static_cast<VTable*>(ptr); }};
}
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback )
void Object::unregister()
{
registerProperty( interfaceName
, propertyName
, signature
, getCallback
, property_set_callback{} );
vtables_.clear();
objectManagerSlot_.reset();
}
void Object::registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback )
Signal Object::createSignal(const InterfaceName& interfaceName, const SignalName& signalName) const
{
SDBUS_THROW_ERROR_IF(!getCallback && !setCallback, "Invalid property callbacks provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::PropertyData propertyData{signature, std::move(getCallback), std::move(setCallback)};
auto inserted = interface.properties_.emplace(propertyName, std::move(propertyData)).second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register property: property already exists", EINVAL);
}
void Object::finishRegistration()
{
for (auto& item : interfaces_)
{
const auto& interfaceName = item.first;
auto& interfaceData = item.second;
const auto& vtable = createInterfaceVTable(interfaceData);
activateInterfaceVTable(interfaceName, interfaceData, vtable);
}
}
sdbus::Message Object::createSignal(const std::string& interfaceName, const std::string& signalName)
{
// Tell, don't ask
return connection_.createSignal(objectPath_, interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Message& message)
Signal Object::createSignal(const char* interfaceName, const char* signalName) const
{
return connection_.createSignal(objectPath_.c_str(), interfaceName, signalName);
}
void Object::emitSignal(const sdbus::Signal& message)
{
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid signal message provided", EINVAL);
message.send();
}
const std::vector<sd_bus_vtable>& Object::createInterfaceVTable(InterfaceData& interfaceData)
void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& propNames)
{
auto& vtable = interfaceData.vtable_;
assert(vtable.empty());
vtable.push_back(createVTableStartItem());
registerMethodsToVTable(interfaceData, vtable);
registerSignalsToVTable(interfaceData, vtable);
registerPropertiesToVTable(interfaceData, vtable);
vtable.push_back(createVTableEndItem());
return vtable;
connection_.emitPropertiesChangedSignal(objectPath_, interfaceName, propNames);
}
void Object::registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
void Object::emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames)
{
for (const auto& item : interfaceData.methods_)
connection_.emitPropertiesChangedSignal(objectPath_.c_str(), interfaceName, propNames);
}
void Object::emitPropertiesChangedSignal(const InterfaceName& interfaceName)
{
Object::emitPropertiesChangedSignal(interfaceName, {});
}
void Object::emitPropertiesChangedSignal(const char* interfaceName)
{
Object::emitPropertiesChangedSignal(interfaceName, {});
}
void Object::emitInterfacesAddedSignal()
{
connection_.emitInterfacesAddedSignal(objectPath_);
}
void Object::emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces)
{
connection_.emitInterfacesAddedSignal(objectPath_, interfaces);
}
void Object::emitInterfacesRemovedSignal()
{
connection_.emitInterfacesRemovedSignal(objectPath_);
}
void Object::emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces)
{
connection_.emitInterfacesRemovedSignal(objectPath_, interfaces);
}
void Object::addObjectManager()
{
objectManagerSlot_ = connection_.addObjectManager(objectPath_, return_slot);
}
Slot Object::addObjectManager(return_slot_t)
{
return connection_.addObjectManager(objectPath_, return_slot);
}
sdbus::IConnection& Object::getConnection() const
{
return connection_;
}
const ObjectPath& Object::getObjectPath() const
{
return objectPath_;
}
Message Object::getCurrentlyProcessedMessage() const
{
return connection_.getCurrentlyProcessedMessage();
}
Object::VTable Object::createInternalVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable)
{
VTable internalVTable;
internalVTable.interfaceName = std::move(interfaceName);
for (auto& vtableItem : vtable)
{
const auto& methodName = item.first;
const auto& methodData = item.second;
vtable.push_back(createVTableMethodItem( methodName.c_str()
, methodData.inputArgs_.c_str()
, methodData.outputArgs_.c_str()
, &Object::sdbus_method_callback ));
std::visit( overload{ [&](InterfaceFlagsVTableItem&& interfaceFlags){ writeInterfaceFlagsToVTable(std::move(interfaceFlags), internalVTable); }
, [&](MethodVTableItem&& method){ writeMethodRecordToVTable(std::move(method), internalVTable); }
, [&](SignalVTableItem&& signal){ writeSignalRecordToVTable(std::move(signal), internalVTable); }
, [&](PropertyVTableItem&& property){ writePropertyRecordToVTable(std::move(property), internalVTable); } }
, std::move(vtableItem) );
}
// Sort arrays so we can do fast searching for an item in sd-bus callback handlers
std::sort(internalVTable.methods.begin(), internalVTable.methods.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
std::sort(internalVTable.signals.begin(), internalVTable.signals.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
std::sort(internalVTable.properties.begin(), internalVTable.properties.end(), [](const auto& a, const auto& b){ return a.name < b.name; });
internalVTable.object = this;
return internalVTable;
}
void Object::registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
void Object::writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable)
{
for (const auto& item : interfaceData.signals_)
vtable.interfaceFlags = std::move(flags.flags);
}
void Object::writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(method.name.c_str());
SDBUS_THROW_ERROR_IF(!method.callbackHandler, "Invalid method callback provided", EINVAL);
vtable.methods.push_back({ std::move(method.name)
, std::move(method.inputSignature)
, std::move(method.outputSignature)
, paramNamesToString(method.inputParamNames) + paramNamesToString(method.outputParamNames)
, std::move(method.callbackHandler)
, std::move(method.flags) });
}
void Object::writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(signal.name.c_str());
vtable.signals.push_back({ std::move(signal.name)
, std::move(signal.signature)
, paramNamesToString(signal.paramNames)
, std::move(signal.flags) });
}
void Object::writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable)
{
SDBUS_CHECK_MEMBER_NAME(property.name.c_str());
SDBUS_THROW_ERROR_IF(!property.getter && !property.setter, "Invalid property callbacks provided", EINVAL);
vtable.properties.push_back({ std::move(property.name)
, std::move(property.signature)
, std::move(property.getter)
, std::move(property.setter)
, std::move(property.flags) });
}
std::vector<sd_bus_vtable> Object::createInternalSdBusVTable(const VTable& vtable)
{
std::vector<sd_bus_vtable> sdbusVTable;
startSdBusVTable(vtable.interfaceFlags, sdbusVTable);
for (const auto& methodItem : vtable.methods)
writeMethodRecordToSdBusVTable(methodItem, sdbusVTable);
for (const auto& signalItem : vtable.signals)
writeSignalRecordToSdBusVTable(signalItem, sdbusVTable);
for (const auto& propertyItem : vtable.properties)
writePropertyRecordToSdBusVTable(propertyItem, sdbusVTable);
finalizeSdBusVTable(sdbusVTable);
return sdbusVTable;
}
void Object::startSdBusVTable(const Flags& interfaceFlags, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableStartItem(interfaceFlags.toSdBusInterfaceFlags());
vtable.push_back(std::move(vtableItem));
}
void Object::writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableMethodItem( method.name.c_str()
, method.inputSignature.c_str()
, method.outputSignature.c_str()
, method.paramNames.c_str()
, &Object::sdbus_method_callback
, method.flags.toSdBusMethodFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = createSdBusVTableSignalItem( signal.name.c_str()
, signal.signature.c_str()
, signal.paramNames.c_str()
, signal.flags.toSdBusSignalFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector<sd_bus_vtable>& vtable)
{
auto vtableItem = !property.setCallback
? createSdBusVTableReadOnlyPropertyItem( property.name.c_str()
, property.signature.c_str()
, &Object::sdbus_property_get_callback
, property.flags.toSdBusPropertyFlags() )
: createSdBusVTableWritablePropertyItem( property.name.c_str()
, property.signature.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback
, property.flags.toSdBusWritablePropertyFlags() );
vtable.push_back(std::move(vtableItem));
}
void Object::finalizeSdBusVTable(std::vector<sd_bus_vtable>& vtable)
{
vtable.push_back(createSdBusVTableEndItem());
}
const Object::VTable::MethodItem* Object::findMethod(const VTable& vtable, std::string_view methodName)
{
auto it = std::lower_bound(vtable.methods.begin(), vtable.methods.end(), methodName, [](const auto& methodItem, const auto& methodName)
{
const auto& signalName = item.first;
const auto& signalData = item.second;
return methodItem.name < methodName;
});
vtable.push_back(createVTableSignalItem( signalName.c_str()
, signalData.signature_.c_str() ));
}
return it != vtable.methods.end() && it->name == methodName ? &*it : nullptr;
}
void Object::registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable)
const Object::VTable::PropertyItem* Object::findProperty(const VTable& vtable, std::string_view propertyName)
{
for (const auto& item : interfaceData.properties_)
auto it = std::lower_bound(vtable.properties.begin(), vtable.properties.end(), propertyName, [](const auto& propertyItem, const auto& propertyName)
{
const auto& propertyName = item.first;
const auto& propertyData = item.second;
return propertyItem.name < propertyName;
});
if (!propertyData.setCallback_)
vtable.push_back(createVTablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback ));
else
vtable.push_back(createVTableWritablePropertyItem( propertyName.c_str()
, propertyData.signature_.c_str()
, &Object::sdbus_property_get_callback
, &Object::sdbus_property_set_callback ));
}
return it != vtable.properties.end() && it->name == propertyName ? &*it : nullptr;
}
void Object::activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData
, const std::vector<sd_bus_vtable>& vtable )
std::string Object::paramNamesToString(const std::vector<std::string>& paramNames)
{
// Tell, don't ask
auto slot = (sd_bus_slot*) connection_.addObjectVTable(objectPath_, interfaceName, &vtable[0], this);
interfaceData.slot_.reset(slot);
interfaceData.slot_.get_deleter() = [this](void *slot){ connection_.removeObjectVTable(slot); };
std::string names;
for (const auto& name : paramNames)
names += name + '\0';
return names;
}
int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
Message message(sdbusMessage, Message::Type::eMethodCall);
auto* vtable = static_cast<VTable*>(userData);
assert(vtable != nullptr);
assert(vtable->object != nullptr);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].methods_[message.getMemberName()].callback_;
assert(callback);
auto message = Message::Factory::create<MethodCall>(sdbusMessage, &vtable->object->connection_);
auto reply = message.createReply();
const auto* methodItem = findMethod(*vtable, message.getMemberName());
assert(methodItem != nullptr);
assert(methodItem->callback);
try
{
callback(message, reply);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
auto ok = invokeHandlerAndCatchErrors([&](){ methodItem->callback(std::move(message)); }, retError);
reply.send();
return 1;
return ok ? 1 : -1;
}
int Object::sdbus_property_get_callback( sd_bus */*bus*/
, const char */*objectPath*/
, const char *interface
, const char */*interface*/
, const char *property
, sd_bus_message *sdbusReply
, void *userData
, sd_bus_error *retError )
{
Message reply(sdbusReply, Message::Type::ePlainMessage);
auto* vtable = static_cast<VTable*>(userData);
assert(vtable != nullptr);
assert(vtable->object != nullptr);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].getCallback_;
// Getter can be empty - the case of "write-only" property
if (!callback)
const auto* propertyItem = findProperty(*vtable, property);
assert(propertyItem != nullptr);
// Getter may be empty - the case of "write-only" property
if (!propertyItem->getCallback)
{
sd_bus_error_set(retError, "org.freedesktop.DBus.Error.Failed", "Cannot read property as it is write-only");
return 1;
}
try
{
callback(reply);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
}
auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &vtable->object->connection_);
return 1;
auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->getCallback(reply); }, retError);
return ok ? 1 : -1;
}
int Object::sdbus_property_set_callback( sd_bus */*bus*/
, const char */*objectPath*/
, const char *interface
, const char */*interface*/
, const char *property
, sd_bus_message *sdbusValue
, void *userData
, sd_bus_error *retError )
{
Message value(sdbusValue, Message::Type::ePlainMessage);
auto* vtable = static_cast<VTable*>(userData);
assert(vtable != nullptr);
assert(vtable->object != nullptr);
auto* object = static_cast<Object*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[interface].properties_[property].setCallback_;
assert(callback);
const auto* propertyItem = findProperty(*vtable, property);
assert(propertyItem != nullptr);
assert(propertyItem->setCallback);
try
{
callback(value);
}
catch (const sdbus::Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return 1;
}
auto value = Message::Factory::create<PropertySetCall>(sdbusValue, &vtable->object->connection_);
return 1;
auto ok = invokeHandlerAndCatchErrors([&](){ propertyItem->setCallback(std::move(value)); }, retError);
return ok ? 1 : -1;
}
}}
}
namespace sdbus {
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, std::string objectPath)
std::unique_ptr<sdbus::IObject> createObject(sdbus::IConnection& connection, ObjectPath objectPath)
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Object.h
*
@ -26,88 +27,121 @@
#ifndef SDBUS_CXX_INTERNAL_OBJECT_H_
#define SDBUS_CXX_INTERNAL_OBJECT_H_
#include <sdbus-c++/IObject.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <string>
#include <map>
#include <vector>
#include <memory>
#include <cassert>
#include "sdbus-c++/IObject.h"
namespace sdbus {
namespace internal {
#include "IConnection.h"
#include "sdbus-c++/Types.h"
#include <cassert>
#include <functional>
#include <list>
#include <memory>
#include <string>
#include <string_view>
#include SDBUS_HEADER
#include <vector>
namespace sdbus::internal {
class Object
: public IObject
{
public:
Object(sdbus::internal::IConnection& connection, std::string objectPath);
Object(sdbus::internal::IConnection& connection, ObjectPath objectPath);
void registerMethod( const std::string& interfaceName
, const std::string& methodName
, const std::string& inputSignature
, const std::string& outputSignature
, method_callback methodCallback ) override;
void addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable) override;
Slot addVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable, return_slot_t) override;
void unregister() override;
void registerSignal( const std::string& interfaceName
, const std::string& signalName
, const std::string& signature ) override;
Signal createSignal(const InterfaceName& interfaceName, const SignalName& signalName) const override;
Signal createSignal(const char* interfaceName, const char* signalName) const override;
void emitSignal(const sdbus::Signal& message) override;
void emitPropertiesChangedSignal(const InterfaceName& interfaceName, const std::vector<PropertyName>& propNames) override;
void emitPropertiesChangedSignal(const char* interfaceName, const std::vector<PropertyName>& propNames) override;
void emitPropertiesChangedSignal(const InterfaceName& interfaceName) override;
void emitPropertiesChangedSignal(const char* interfaceName) override;
void emitInterfacesAddedSignal() override;
void emitInterfacesAddedSignal(const std::vector<InterfaceName>& interfaces) override;
void emitInterfacesRemovedSignal() override;
void emitInterfacesRemovedSignal(const std::vector<InterfaceName>& interfaces) override;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback ) override;
void addObjectManager() override;
[[nodiscard]] Slot addObjectManager(return_slot_t) override;
void registerProperty( const std::string& interfaceName
, const std::string& propertyName
, const std::string& signature
, property_get_callback getCallback
, property_set_callback setCallback ) override;
void finishRegistration() override;
sdbus::Message createSignal(const std::string& interfaceName, const std::string& signalName) override;
void emitSignal(const sdbus::Message& message) override;
[[nodiscard]] sdbus::IConnection& getConnection() const override;
[[nodiscard]] const ObjectPath& getObjectPath() const override;
[[nodiscard]] Message getCurrentlyProcessedMessage() const override;
private:
using InterfaceName = std::string;
struct InterfaceData
// A vtable record comprising methods, signals, properties, flags.
// Once created, it cannot be modified. Only new vtables records can be added.
// An interface can have any number of vtables attached to it, not only one.
struct VTable
{
using MethodName = std::string;
struct MethodData
{
std::string inputArgs_;
std::string outputArgs_;
method_callback callback_;
};
std::map<MethodName, MethodData> methods_;
using SignalName = std::string;
struct SignalData
{
std::string signature_;
};
std::map<SignalName, SignalData> signals_;
using PropertyName = std::string;
struct PropertyData
{
std::string signature_;
property_get_callback getCallback_;
property_set_callback setCallback_;
};
std::map<PropertyName, PropertyData> properties_;
std::vector<sd_bus_vtable> vtable_;
InterfaceName interfaceName;
Flags interfaceFlags;
std::unique_ptr<void, std::function<void(void*)>> slot_;
struct MethodItem
{
MethodName name;
Signature inputSignature;
Signature outputSignature;
std::string paramNames;
method_callback callback;
Flags flags;
};
// Array of method records sorted by method name
std::vector<MethodItem> methods;
struct SignalItem
{
SignalName name;
Signature signature;
std::string paramNames;
Flags flags;
};
// Array of signal records sorted by signal name
std::vector<SignalItem> signals;
struct PropertyItem
{
PropertyName name;
Signature signature;
property_get_callback getCallback;
property_set_callback setCallback;
Flags flags;
};
// Array of signal records sorted by signal name
std::vector<PropertyItem> properties;
// VTable structure in format required by sd-bus API
std::vector<sd_bus_vtable> sdbusVTable;
// Back-reference to the owning object from sd-bus callback handlers
Object* object{};
// This is intentionally the last member, because it must be destructed first,
// releasing callbacks above before the callbacks themselves are destructed.
Slot slot;
};
static const std::vector<sd_bus_vtable>& createInterfaceVTable(InterfaceData& interfaceData);
static void registerMethodsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
static void registerSignalsToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
static void registerPropertiesToVTable(const InterfaceData& interfaceData, std::vector<sd_bus_vtable>& vtable);
void activateInterfaceVTable( const std::string& interfaceName
, InterfaceData& interfaceData
, const std::vector<sd_bus_vtable>& vtable );
VTable createInternalVTable(InterfaceName interfaceName, std::vector<VTableItem> vtable);
void writeInterfaceFlagsToVTable(InterfaceFlagsVTableItem flags, VTable& vtable);
void writeMethodRecordToVTable(MethodVTableItem method, VTable& vtable);
void writeSignalRecordToVTable(SignalVTableItem signal, VTable& vtable);
void writePropertyRecordToVTable(PropertyVTableItem property, VTable& vtable);
std::vector<sd_bus_vtable> createInternalSdBusVTable(const VTable& vtable);
static void startSdBusVTable(const Flags& interfaceFlags, std::vector<sd_bus_vtable>& vtable);
static void writeMethodRecordToSdBusVTable(const VTable::MethodItem& method, std::vector<sd_bus_vtable>& vtable);
static void writeSignalRecordToSdBusVTable(const VTable::SignalItem& signal, std::vector<sd_bus_vtable>& vtable);
static void writePropertyRecordToSdBusVTable(const VTable::PropertyItem& property, std::vector<sd_bus_vtable>& vtable);
static void finalizeSdBusVTable(std::vector<sd_bus_vtable>& vtable);
static const VTable::MethodItem* findMethod(const VTable& vtable, std::string_view methodName);
static const VTable::PropertyItem* findProperty(const VTable& vtable, std::string_view propertyName);
static std::string paramNamesToString(const std::vector<std::string>& paramNames);
static int sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_property_get_callback( sd_bus *bus
@ -127,11 +161,11 @@ namespace internal {
private:
sdbus::internal::IConnection& connection_;
std::string objectPath_;
std::map<InterfaceName, InterfaceData> interfaces_;
ObjectPath objectPath_;
std::vector<Slot> vtables_;
Slot objectManagerSlot_;
};
}
}
#endif /* SDBUS_CXX_INTERNAL_OBJECT_H_ */

View File

@ -1,196 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ObjectProxy.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ObjectProxy.h"
#include <sdbus-c++/Message.h>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/Error.h>
#include "IConnection.h"
#include <systemd/sd-bus.h>
#include <cassert>
namespace sdbus { namespace internal {
ObjectProxy::ObjectProxy(sdbus::internal::IConnection& connection, std::string destination, std::string objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, ownConnection_(false)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
}
ObjectProxy::ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath )
: connection_(std::move(connection))
, ownConnection_(true)
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
}
ObjectProxy::~ObjectProxy()
{
// If the dedicated connection for signals is used, we have to stop the processing loop
// upon this connection prior to unregistering signal slots in the interfaces_ container,
// otherwise we might have a race condition of two threads working upon one connection.
if (signalConnection_ != nullptr)
signalConnection_->leaveProcessingLoop();
}
Message ObjectProxy::createMethodCall(const std::string& interfaceName, const std::string& methodName)
{
// Tell, don't ask
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
Message ObjectProxy::callMethod(const Message& message)
{
return message.send();
}
void ObjectProxy::registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler )
{
SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL);
auto& interface = interfaces_[interfaceName];
InterfaceData::SignalData signalData{std::move(signalHandler), nullptr};
auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData));
auto inserted = insertionResult.second;
SDBUS_THROW_ERROR_IF(!inserted, "Failed to register signal handler: handler already exists", EINVAL);
}
void ObjectProxy::finishRegistration()
{
bool hasSignals = listensToSignals();
if (hasSignals && ownConnection_)
{
// Let's use dedicated signalConnection_ for signals,
// which will then be used by the processing loop thread.
signalConnection_ = connection_->clone();
registerSignalHandlers(*signalConnection_);
signalConnection_->enterProcessingLoopAsync();
}
else if (hasSignals)
{
// Let's used connection provided from the outside.
registerSignalHandlers(*connection_);
}
}
bool ObjectProxy::listensToSignals() const
{
for (auto& interfaceItem : interfaces_)
if (!interfaceItem.second.signals_.empty())
return true;
return false;
}
void ObjectProxy::registerSignalHandlers(sdbus::internal::IConnection& connection)
{
for (auto& interfaceItem : interfaces_)
{
const auto& interfaceName = interfaceItem.first;
auto& signalsOnInterface = interfaceItem.second.signals_;
for (auto& signalItem : signalsOnInterface)
{
const auto& signalName = signalItem.first;
auto& slot = signalItem.second.slot_;
auto* rawSlotPtr = connection.registerSignalHandler( objectPath_
, interfaceName
, signalName
, &ObjectProxy::sdbus_signal_callback
, this );
slot.reset(rawSlotPtr);
slot.get_deleter() = [&connection](void *slot){ connection.unregisterSignalHandler(slot); };
}
}
}
int ObjectProxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
{
Message message(sdbusMessage, Message::Type::eSignal);
auto* object = static_cast<ObjectProxy*>(userData);
// Note: The lookup can be optimized by using sorted vectors instead of associative containers
auto& callback = object->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_;
assert(callback);
callback(message);
return 1;
}
}}
namespace sdbus {
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( IConnection& connection
, std::string destination
, std::string objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
return std::make_unique<sdbus::internal::ObjectProxy>( *sdbusConnection
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::unique_ptr<IConnection>&& connection
, std::string destination
, std::string objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
connection.release();
return std::make_unique<sdbus::internal::ObjectProxy>( std::unique_ptr<sdbus::internal::IConnection>(sdbusConnection)
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IObjectProxy> createObjectProxy( std::string destination
, std::string objectPath )
{
auto connection = sdbus::createConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr);
return std::make_unique<sdbus::internal::ObjectProxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath) );
}
}

View File

@ -1,93 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ObjectProxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_OBJECTPROXY_H_
#define SDBUS_CXX_INTERNAL_OBJECTPROXY_H_
#include <sdbus-c++/IObjectProxy.h>
#include <systemd/sd-bus.h>
#include <string>
#include <memory>
#include <map>
// Forward declarations
namespace sdbus { namespace internal {
class IConnection;
}}
namespace sdbus {
namespace internal {
class ObjectProxy
: public IObjectProxy
{
public:
ObjectProxy( sdbus::internal::IConnection& connection
, std::string destination
, std::string objectPath );
ObjectProxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, std::string destination
, std::string objectPath );
~ObjectProxy();
Message createMethodCall(const std::string& interfaceName, const std::string& methodName) override;
Message callMethod(const Message& message) override;
void registerSignalHandler( const std::string& interfaceName
, const std::string& signalName
, signal_handler signalHandler ) override;
void finishRegistration() override;
private:
bool listensToSignals() const;
void registerSignalHandlers(sdbus::internal::IConnection& connection);
static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)>
> connection_;
bool ownConnection_{};
std::unique_ptr<sdbus::internal::IConnection> signalConnection_;
std::string destination_;
std::string objectPath_;
using InterfaceName = std::string;
struct InterfaceData
{
using SignalName = std::string;
struct SignalData
{
signal_handler callback_;
std::unique_ptr<void, std::function<void(void*)>> slot_;
};
std::map<SignalName, SignalData> signals_;
};
std::map<InterfaceName, InterfaceData> interfaces_;
};
}}
#endif /* SDBUS_CXX_INTERNAL_OBJECTPROXY_H_ */

443
src/Proxy.cpp Normal file
View File

@ -0,0 +1,443 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Proxy.cpp
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Proxy.h"
#include "sdbus-c++/Error.h"
#include "sdbus-c++/IConnection.h"
#include "sdbus-c++/Message.h"
#include "IConnection.h"
#include "MessageUtils.h"
#include "ScopeGuard.h"
#include "Utils.h"
#include <cassert>
#include <cstring>
#include SDBUS_HEADER
#include <utility>
namespace sdbus::internal {
Proxy::Proxy(sdbus::internal::IConnection& connection, ServiceName destination, ObjectPath objectPath)
: connection_(&connection, [](sdbus::internal::IConnection *){ /* Intentionally left empty */ })
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// The connection is not ours only, it is owned and managed by the user and we just reference
// it here, so we expect the client to manage the event loop upon this connection themselves.
}
Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath )
: connection_(std::move(connection))
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// The connection is ours only, i.e. it's us who has to manage the event loop upon this connection,
// in order that we get and process signals, async call replies, and other messages from D-Bus.
connection_->enterEventLoopAsync();
}
Proxy::Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
: connection_(std::move(connection))
, destination_(std::move(destination))
, objectPath_(std::move(objectPath))
{
SDBUS_CHECK_SERVICE_NAME(destination_.c_str());
SDBUS_CHECK_OBJECT_PATH(objectPath_.c_str());
// Even though the connection is ours only, we don't start an event loop thread.
// This proxy is meant to be created, used for simple synchronous D-Bus call(s) and then dismissed.
}
MethodCall Proxy::createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) const
{
return connection_->createMethodCall(destination_, objectPath_, interfaceName, methodName);
}
MethodCall Proxy::createMethodCall(const char* interfaceName, const char* methodName) const
{
return connection_->createMethodCall(destination_.c_str(), objectPath_.c_str(), interfaceName, methodName);
}
MethodReply Proxy::callMethod(const MethodCall& message)
{
return Proxy::callMethod(message, /*timeout*/ 0);
}
MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout)
{
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL);
return message.send(timeout);
}
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback)
{
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0);
}
Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, return_slot_t)
{
return Proxy::callMethodAsync(message, std::move(asyncReplyCallback), /*timeout*/ 0, return_slot);
}
PendingAsyncCall Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout)
{
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
auto asyncCallInfo = std::make_shared<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
, .proxy = *this
, .floating = false });
asyncCallInfo->slot = message.send((void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout, return_slot);
auto asyncCallInfoWeakPtr = std::weak_ptr{asyncCallInfo};
floatingAsyncCallSlots_.push_back(std::move(asyncCallInfo));
return {asyncCallInfoWeakPtr};
}
Slot Proxy::callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout, return_slot_t)
{
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
auto asyncCallInfo = std::make_unique<AsyncCallInfo>(AsyncCallInfo{ .callback = std::move(asyncReplyCallback)
, .proxy = *this
, .floating = true });
asyncCallInfo->slot = message.send((void*)&Proxy::sdbus_async_reply_handler, asyncCallInfo.get(), timeout, return_slot);
return {asyncCallInfo.release(), [](void *ptr){ delete static_cast<AsyncCallInfo*>(ptr); }};
}
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, with_future_t)
{
return Proxy::callMethodAsync(message, {}, with_future);
}
std::future<MethodReply> Proxy::callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t)
{
auto promise = std::make_shared<std::promise<MethodReply>>();
auto future = promise->get_future();
async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply reply, std::optional<Error> error) noexcept
{
if (!error)
promise->set_value(std::move(reply));
else
promise->set_exception(std::make_exception_ptr(*std::move(error)));
};
(void)Proxy::callMethodAsync(message, std::move(asyncReplyCallback), timeout);
return future;
}
void Proxy::registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler )
{
Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler));
}
void Proxy::registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler )
{
auto slot = Proxy::registerSignalHandler(interfaceName, signalName, std::move(signalHandler), return_slot);
floatingSignalSlots_.push_back(std::move(slot));
}
Slot Proxy::registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler
, return_slot_t )
{
return Proxy::registerSignalHandler(interfaceName.c_str(), signalName.c_str(), std::move(signalHandler), return_slot);
}
Slot Proxy::registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler
, return_slot_t )
{
SDBUS_CHECK_INTERFACE_NAME(interfaceName);
SDBUS_CHECK_MEMBER_NAME(signalName);
SDBUS_THROW_ERROR_IF(!signalHandler, "Invalid signal handler provided", EINVAL);
auto signalInfo = std::make_unique<SignalInfo>(SignalInfo{std::move(signalHandler), *this, {}});
signalInfo->slot = connection_->registerSignalHandler( destination_.c_str()
, objectPath_.c_str()
, interfaceName
, signalName
, &Proxy::sdbus_signal_handler
, signalInfo.get()
, return_slot );
return {signalInfo.release(), [](void *ptr){ delete static_cast<SignalInfo*>(ptr); }};
}
void Proxy::unregister()
{
floatingAsyncCallSlots_.clear();
floatingSignalSlots_.clear();
}
sdbus::IConnection& Proxy::getConnection() const
{
return *connection_;
}
const ObjectPath& Proxy::getObjectPath() const
{
return objectPath_;
}
Message Proxy::getCurrentlyProcessedMessage() const
{
return connection_->getCurrentlyProcessedMessage();
}
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* asyncCallInfo = static_cast<AsyncCallInfo*>(userData);
assert(asyncCallInfo != nullptr);
assert(asyncCallInfo->callback);
auto& proxy = asyncCallInfo->proxy;
// We are removing the CallData item at the complete scope exit, after the callback has been invoked.
// We can't do it earlier (before callback invocation for example), because CallBack data (slot release)
// is the synchronization point between callback invocation and Proxy::unregister.
SCOPE_EXIT
{
proxy.floatingAsyncCallSlots_.erase(asyncCallInfo);
};
auto message = Message::Factory::create<MethodReply>(sdbusMessage, proxy.connection_.get());
auto ok = invokeHandlerAndCatchErrors([&]
{
const auto* error = sd_bus_message_get_error(sdbusMessage);
if (error == nullptr)
{
asyncCallInfo->callback(std::move(message), {});
}
else
{
Error exception(Error::Name{error->name}, error->message);
asyncCallInfo->callback(std::move(message), std::move(exception));
}
}, retError);
return ok ? 0 : -1;
}
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
{
auto* signalInfo = static_cast<SignalInfo*>(userData);
assert(signalInfo != nullptr);
assert(signalInfo->callback);
auto message = Message::Factory::create<Signal>(sdbusMessage, signalInfo->proxy.connection_.get());
auto ok = invokeHandlerAndCatchErrors([&](){ signalInfo->callback(std::move(message)); }, retError);
return ok ? 0 : -1;
}
Proxy::FloatingAsyncCallSlots::~FloatingAsyncCallSlots()
{
clear();
}
void Proxy::FloatingAsyncCallSlots::push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo)
{
std::lock_guard lock(mutex_);
if (!asyncCallInfo->finished) // The call may have finished in the meantime
slots_.emplace_back(std::move(asyncCallInfo));
}
void Proxy::FloatingAsyncCallSlots::erase(AsyncCallInfo* info)
{
std::unique_lock lock(mutex_);
info->finished = true;
auto it = std::find_if(slots_.begin(), slots_.end(), [info](auto const& entry){ return entry.get() == info; });
if (it != slots_.end())
{
auto callInfo = std::move(*it);
slots_.erase(it);
lock.unlock();
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
// out of the `mutex_' critical section here, because if the `removeCall` is called by some
// thread and at the same time Proxy's async reply handler (which already holds global sd-bus
// mutex) is in progress in a different thread, we get double-mutex deadlock.
}
}
void Proxy::FloatingAsyncCallSlots::clear()
{
std::unique_lock lock(mutex_);
auto asyncCallSlots = std::move(slots_);
slots_ = {};
lock.unlock();
// Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release
// out of the `mutex_' critical section here, because if the `clear` is called by some thread
// and at the same time Proxy's async reply handler (which already holds global sd-bus
// mutex) is in progress in a different thread, we get double-mutex deadlock.
}
}
namespace sdbus {
PendingAsyncCall::PendingAsyncCall(std::weak_ptr<void> callInfo)
: callInfo_(std::move(callInfo))
{
}
void PendingAsyncCall::cancel()
{
if (auto ptr = callInfo_.lock(); ptr != nullptr)
{
auto* asyncCallInfo = static_cast<internal::Proxy::AsyncCallInfo*>(ptr.get());
asyncCallInfo->proxy.floatingAsyncCallSlots_.erase(asyncCallInfo);
// At this point, the callData item is being deleted, leading to the release of the
// sd-bus slot pointer. This release locks the global sd-bus mutex. If the async
// callback is currently being processed, the sd-bus mutex is locked by the event
// loop thread, thus access to the callData item is synchronized and thread-safe.
}
}
bool PendingAsyncCall::isPending() const
{
return !callInfo_.expired();
}
}
namespace sdbus {
std::unique_ptr<sdbus::IProxy> createProxy( IConnection& connection
, ServiceName destination
, ObjectPath objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(&connection);
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
return std::make_unique<sdbus::internal::Proxy>( *sdbusConnection
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
connection.release();
return std::make_unique<sdbus::internal::Proxy>( std::unique_ptr<sdbus::internal::IConnection>(sdbusConnection)
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( std::unique_ptr<IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
{
auto* sdbusConnection = dynamic_cast<sdbus::internal::IConnection*>(connection.get());
SDBUS_THROW_ERROR_IF(!sdbusConnection, "Connection is not a real sdbus-c++ connection", EINVAL);
connection.release();
return std::make_unique<sdbus::internal::Proxy>( std::unique_ptr<sdbus::internal::IConnection>(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
std::unique_ptr<sdbus::IProxy> createLightWeightProxy( std::unique_ptr<IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath )
{
return createProxy(std::move(connection), std::move(destination), std::move(objectPath), dont_run_event_loop_thread);
}
std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath )
{
auto connection = sdbus::createBusConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr);
return std::make_unique<sdbus::internal::Proxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath) );
}
std::unique_ptr<sdbus::IProxy> createProxy( ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t )
{
auto connection = sdbus::createBusConnection();
auto sdbusConnection = std::unique_ptr<sdbus::internal::IConnection>(dynamic_cast<sdbus::internal::IConnection*>(connection.release()));
assert(sdbusConnection != nullptr);
return std::make_unique<sdbus::internal::Proxy>( std::move(sdbusConnection)
, std::move(destination)
, std::move(objectPath)
, dont_run_event_loop_thread );
}
std::unique_ptr<sdbus::IProxy> createLightWeightProxy(ServiceName destination, ObjectPath objectPath)
{
return createProxy(std::move(destination), std::move(objectPath), dont_run_event_loop_thread);
}
}

147
src/Proxy.h Normal file
View File

@ -0,0 +1,147 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Proxy.h
*
* Created on: Nov 8, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_PROXY_H_
#define SDBUS_CXX_INTERNAL_PROXY_H_
#include "sdbus-c++/IProxy.h"
#include "IConnection.h"
#include "sdbus-c++/Types.h"
#include <deque>
#include <memory>
#include <mutex>
#include <string>
#include SDBUS_HEADER
#include <vector>
namespace sdbus::internal {
class Proxy
: public IProxy
{
public:
Proxy( sdbus::internal::IConnection& connection
, ServiceName destination
, ObjectPath objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath );
Proxy( std::unique_ptr<sdbus::internal::IConnection>&& connection
, ServiceName destination
, ObjectPath objectPath
, dont_run_event_loop_thread_t );
MethodCall createMethodCall(const InterfaceName& interfaceName, const MethodName& methodName) const override;
MethodCall createMethodCall(const char* interfaceName, const char* methodName) const override;
MethodReply callMethod(const MethodCall& message) override;
MethodReply callMethod(const MethodCall& message, uint64_t timeout) override;
PendingAsyncCall callMethodAsync(const MethodCall& message, async_reply_handler asyncReplyCallback) override;
Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, return_slot_t ) override;
PendingAsyncCall callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout ) override;
Slot callMethodAsync( const MethodCall& message
, async_reply_handler asyncReplyCallback
, uint64_t timeout
, return_slot_t ) override;
std::future<MethodReply> callMethodAsync(const MethodCall& message, with_future_t) override;
std::future<MethodReply> callMethodAsync(const MethodCall& message, uint64_t timeout, with_future_t) override;
void registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler ) override;
void registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler ) override;
Slot registerSignalHandler( const InterfaceName& interfaceName
, const SignalName& signalName
, signal_handler signalHandler
, return_slot_t ) override;
Slot registerSignalHandler( const char* interfaceName
, const char* signalName
, signal_handler signalHandler
, return_slot_t ) override;
void unregister() override;
[[nodiscard]] sdbus::IConnection& getConnection() const override;
[[nodiscard]] const ObjectPath& getObjectPath() const override;
[[nodiscard]] Message getCurrentlyProcessedMessage() const override;
private:
static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError);
private:
friend PendingAsyncCall;
std::unique_ptr< sdbus::internal::IConnection
, std::function<void(sdbus::internal::IConnection*)>
> connection_;
ServiceName destination_;
ObjectPath objectPath_;
std::vector<Slot> floatingSignalSlots_;
struct SignalInfo
{
signal_handler callback;
Proxy& proxy;
Slot slot;
};
struct AsyncCallInfo
{
async_reply_handler callback;
Proxy& proxy;
Slot slot{};
bool finished{false};
bool floating;
};
// Container keeping track of pending async calls
class FloatingAsyncCallSlots
{
public:
~FloatingAsyncCallSlots();
void push_back(std::shared_ptr<AsyncCallInfo> asyncCallInfo);
void erase(AsyncCallInfo* info);
void clear();
private:
std::mutex mutex_;
std::deque<std::shared_ptr<AsyncCallInfo>> slots_;
};
FloatingAsyncCallSlots floatingAsyncCallSlots_;
};
}
#endif /* SDBUS_CXX_INTERNAL_PROXY_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file ScopeGuard.h
*
@ -26,6 +27,7 @@
#ifndef SDBUS_CPP_INTERNAL_SCOPEGUARD_H_
#define SDBUS_CPP_INTERNAL_SCOPEGUARD_H_
#include <exception>
#include <utility>
// Straightforward, modern, easy-to-use RAII utility to perform work on scope exit in an exception-safe manner.
@ -54,78 +56,115 @@
// return; // exiting scope normally
// }
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
#define SCOPE_EXIT \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_SUCCESS_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitSuccessTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE \
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
#define SCOPE_EXIT_FAILURE_NAMED(NAME) \
auto NAME \
= ::sdbus::internal::ScopeGuardOnExitFailureTag{} + [&]() \
/**/
#define SCOPE_EXIT_NAMED(NAME) \
auto NAME \
= ::skybase::utils::detail::ScopeGuardOnExit() + [&]() \
/**/
namespace sdbus::internal {
namespace skybase {
namespace utils {
struct ScopeGuardOnExitTag
{
static bool holds(int /*originalExceptions*/)
{
return true; // Always holds
}
};
struct ScopeGuardOnExitSuccessTag
{
static bool holds(int originalExceptions)
{
return originalExceptions == std::uncaught_exceptions(); // Only holds when no exception occurred within the scope
}
};
struct ScopeGuardOnExitFailureTag
{
static bool holds(int originalExceptions)
{
return originalExceptions != std::uncaught_exceptions(); // Only holds when an exception occurred within the scope
}
};
template <class _Fun>
template <class _Fun, typename _Tag>
class ScopeGuard
{
_Fun fnc_;
bool active_;
public:
ScopeGuard(_Fun f)
: fnc_(std::move(f))
, active_(true)
ScopeGuard(_Fun f) : fnc_(std::move(f))
{
}
~ScopeGuard()
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs) : fnc_(std::move(rhs.fnc_)), active_(rhs.active_), exceptions_(rhs.exceptions_)
{
if (active_)
fnc_();
rhs.dismiss();
}
void dismiss()
{
active_ = false;
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs)
: fnc_(std::move(rhs.fnc_))
, active_(rhs.active_)
~ScopeGuard()
{
rhs.dismiss();
if (active_ && _Tag::holds(exceptions_))
fnc_();
}
private:
_Fun fnc_;
int exceptions_{std::uncaught_exceptions()};
bool active_{true};
};
namespace detail
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitTag> operator+(ScopeGuardOnExitTag, _Fun&& fnc)
{
enum class ScopeGuardOnExit
{
};
// Helper function to auto-deduce type of the callable entity
template <typename _Fun>
ScopeGuard<_Fun> operator+(ScopeGuardOnExit, _Fun&& fnc)
{
return ScopeGuard<_Fun>(std::forward<_Fun>(fnc));
}
return ScopeGuard<_Fun, ScopeGuardOnExitTag>(std::forward<_Fun>(fnc));
}
}}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag> operator+(ScopeGuardOnExitSuccessTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitSuccessTag>(std::forward<_Fun>(fnc));
}
template <typename _Fun>
ScopeGuard<_Fun, ScopeGuardOnExitFailureTag> operator+(ScopeGuardOnExitFailureTag, _Fun&& fnc)
{
return ScopeGuard<_Fun, ScopeGuardOnExitFailureTag>(std::forward<_Fun>(fnc));
}
}
#define CONCATENATE_IMPL(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __COUNTER__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) \
CONCATENATE(str, __LINE__) \
/**/
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#endif /* SDBUS_CPP_INTERNAL_SCOPEGUARD_H_ */

514
src/SdBus.cpp Normal file
View File

@ -0,0 +1,514 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file SdBus.cpp
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 3, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SdBus.h"
#include <sdbus-c++/Error.h>
#include <algorithm>
namespace sdbus::internal {
sd_bus_message* SdBus::sd_bus_message_ref(sd_bus_message *m)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_ref(m);
}
sd_bus_message* SdBus::sd_bus_message_unref(sd_bus_message *m)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_unref(m);
}
int SdBus::sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie)
{
std::lock_guard lock(sdbusMutex_);
auto r = ::sd_bus_send(bus, m, cookie);
if (r < 0)
return r;
return r;
}
int SdBus::sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_call(bus, m, usec, ret_error, reply);
}
int SdBus::sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec)
{
std::lock_guard lock(sdbusMutex_);
auto r = ::sd_bus_call_async(bus, slot, m, callback, userdata, usec);
if (r < 0)
return r;
return r;
}
int SdBus::sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_new(bus, m, type);
}
int SdBus::sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_new_method_call(bus, m, destination, path, interface, member);
}
int SdBus::sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_new_signal(bus, m, path, interface, member);
}
int SdBus::sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_new_method_return(call, m);
}
int SdBus::sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_new_method_error(call, m, e);
}
int SdBus::sd_bus_set_method_call_timeout(sd_bus *bus, uint64_t usec)
{
#if LIBSYSTEMD_VERSION>=240
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_set_method_call_timeout(bus, usec);
#else
(void)bus;
(void)usec;
throw Error(Error::Name{SD_BUS_ERROR_NOT_SUPPORTED}, "Setting general method call timeout not supported by underlying version of libsystemd");
#endif
}
int SdBus::sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret)
{
#if LIBSYSTEMD_VERSION>=240
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_get_method_call_timeout(bus, ret);
#else
(void)bus;
(void)ret;
throw Error(Error::Name{SD_BUS_ERROR_NOT_SUPPORTED}, "Getting general method call timeout not supported by underlying version of libsystemd");
#endif
}
int SdBus::sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_emit_properties_changed_strv(bus, path, interface, names);
}
int SdBus::sd_bus_emit_object_added(sd_bus *bus, const char *path)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_emit_object_added(bus, path);
}
int SdBus::sd_bus_emit_object_removed(sd_bus *bus, const char *path)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_emit_object_removed(bus, path);
}
int SdBus::sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_emit_interfaces_added_strv(bus, path, interfaces);
}
int SdBus::sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_emit_interfaces_removed_strv(bus, path, interfaces);
}
int SdBus::sd_bus_open(sd_bus **ret)
{
return ::sd_bus_open(ret);
}
int SdBus::sd_bus_open_system(sd_bus **ret)
{
return ::sd_bus_open_system(ret);
}
int SdBus::sd_bus_open_user(sd_bus **ret)
{
return ::sd_bus_open_user(ret);
}
int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = ::sd_bus_set_bus_client(bus, true);
if (r < 0)
return r;
// Copying behavior from
// https://github.com/systemd/systemd/blob/fee6441601c979165ebcbb35472036439f8dad5f/src/libsystemd/sd-bus/sd-bus.c#L1381
// Here, we make the bus as trusted
r = ::sd_bus_set_trusted(bus, true);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, const char* address)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_address(bus, address);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_direct(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_server(sd_bus **ret, int fd)
{
sd_bus* bus = nullptr;
int r = ::sd_bus_new(&bus);
if (r < 0)
return r;
r = ::sd_bus_set_fd(bus, fd, fd);
if (r < 0)
return r;
sd_id128_t id;
r = ::sd_id128_randomize(&id);
if (r < 0)
return r;
r = ::sd_bus_set_server(bus, true, id);
if (r < 0)
return r;
r = ::sd_bus_start(bus);
if (r < 0)
return r;
*ret = bus;
return 0;
}
int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
{
#ifndef SDBUS_basu
return ::sd_bus_open_system_remote(ret, host);
#else
(void)ret;
(void)host;
// https://git.sr.ht/~emersion/basu/commit/01d33b244eb6
return -EOPNOTSUPP;
#endif
}
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_request_name(bus, name, flags);
}
int SdBus::sd_bus_release_name(sd_bus *bus, const char *name)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_release_name(bus, name);
}
int SdBus::sd_bus_get_unique_name(sd_bus *bus, const char **name)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_get_unique_name(bus, name);
}
int SdBus::sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_object_vtable(bus, slot, path, interface, vtable, userdata);
}
int SdBus::sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_object_manager(bus, slot, path);
}
int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_match(bus, slot, match, callback, userdata);
}
int SdBus::sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_add_match_async(bus, slot, match, callback, install_callback, userdata);
}
int SdBus::sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_match_signal(bus, ret, sender, path, interface, member, callback, userdata);
}
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_slot_unref(slot);
}
int SdBus::sd_bus_new(sd_bus **ret)
{
return ::sd_bus_new(ret);
}
int SdBus::sd_bus_start(sd_bus *bus)
{
return ::sd_bus_start(bus);
}
int SdBus::sd_bus_process(sd_bus *bus, sd_bus_message **r)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_process(bus, r);
}
sd_bus_message* SdBus::sd_bus_get_current_message(sd_bus *bus)
{
return ::sd_bus_get_current_message(bus);
}
int SdBus::sd_bus_get_poll_data(sd_bus *bus, PollData* data)
{
std::lock_guard lock(sdbusMutex_);
auto r = ::sd_bus_get_fd(bus);
if (r < 0)
return r;
data->fd = r;
r = ::sd_bus_get_events(bus);
if (r < 0)
return r;
data->events = static_cast<short int>(r);
r = ::sd_bus_get_timeout(bus, &data->timeout_usec);
return r;
}
int SdBus::sd_bus_get_n_queued(sd_bus *bus, uint64_t *read, uint64_t* write)
{
std::lock_guard lock(sdbusMutex_);
auto r1 = ::sd_bus_get_n_queued_read(bus, read);
auto r2 = ::sd_bus_get_n_queued_write(bus, write);
return std::min(r1, r2);
}
int SdBus::sd_bus_flush(sd_bus *bus)
{
return ::sd_bus_flush(bus);
}
sd_bus* SdBus::sd_bus_flush_close_unref(sd_bus *bus)
{
return ::sd_bus_flush_close_unref(bus);
}
sd_bus* SdBus::sd_bus_close_unref(sd_bus *bus)
{
#if LIBSYSTEMD_VERSION>=241
return ::sd_bus_close_unref(bus);
#else
::sd_bus_close(bus);
return ::sd_bus_unref(bus);
#endif
}
int SdBus::sd_bus_message_set_destination(sd_bus_message *m, const char *destination)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_message_set_destination(m, destination);
}
int SdBus::sd_bus_query_sender_creds(sd_bus_message *m, uint64_t mask, sd_bus_creds **c)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_query_sender_creds(m, mask, c);
}
sd_bus_creds* SdBus::sd_bus_creds_ref(sd_bus_creds *c)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_ref(c);
}
sd_bus_creds* SdBus::sd_bus_creds_unref(sd_bus_creds *c)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_unref(c);
}
int SdBus::sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_pid(c, pid);
}
int SdBus::sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_uid(c, uid);
}
int SdBus::sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_euid(c, euid);
}
int SdBus::sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_gid(c, gid);
}
int SdBus::sd_bus_creds_get_egid(sd_bus_creds *c, uid_t *egid)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_egid(c, egid);
}
int SdBus::sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_supplementary_gids(c, gids);
}
int SdBus::sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **label)
{
std::lock_guard lock(sdbusMutex_);
return ::sd_bus_creds_get_selinux_context(c, label);
}
}

110
src/SdBus.h Normal file
View File

@ -0,0 +1,110 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file SdBus.h
* @author Ardazishvili Roman (ardazishvili.roman@yandex.ru)
*
* Created on: Mar 3, 2019
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_SDBUS_H
#define SDBUS_CXX_SDBUS_H
#include "ISdBus.h"
#include <mutex>
namespace sdbus::internal {
class SdBus final : public ISdBus
{
public:
virtual sd_bus_message* sd_bus_message_ref(sd_bus_message *m) override;
virtual sd_bus_message* sd_bus_message_unref(sd_bus_message *m) override;
virtual int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) override;
virtual int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply) override;
virtual int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec) override;
virtual int sd_bus_message_new(sd_bus *bus, sd_bus_message **m, uint8_t type) override;
virtual int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member) override;
virtual int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member) override;
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_set_method_call_timeout(sd_bus *bus, uint64_t usec) override;
virtual int sd_bus_get_method_call_timeout(sd_bus *bus, uint64_t *ret) 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_emit_object_added(sd_bus *bus, const char *path) override;
virtual int sd_bus_emit_object_removed(sd_bus *bus, const char *path) override;
virtual int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) override;
virtual int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) override;
virtual int sd_bus_open(sd_bus **ret) override;
virtual int sd_bus_open_system(sd_bus **ret) override;
virtual int sd_bus_open_user(sd_bus **ret) override;
virtual int sd_bus_open_user_with_address(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_system_remote(sd_bus **ret, const char* hsot) override;
virtual int sd_bus_open_direct(sd_bus **ret, const char* address) override;
virtual int sd_bus_open_direct(sd_bus **ret, int fd) override;
virtual int sd_bus_open_server(sd_bus **ret, int fd) override;
virtual int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) override;
virtual int sd_bus_release_name(sd_bus *bus, const char *name) override;
virtual int sd_bus_get_unique_name(sd_bus *bus, const char **name) override;
virtual int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata) override;
virtual int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) override;
virtual int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata) override;
virtual int sd_bus_add_match_async(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, sd_bus_message_handler_t install_callback, void *userdata) override;
virtual int sd_bus_match_signal(sd_bus *bus, sd_bus_slot **ret, const char *sender, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata) override;
virtual sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
virtual int sd_bus_new(sd_bus **ret) override;
virtual int sd_bus_start(sd_bus *bus) override;
virtual int sd_bus_process(sd_bus *bus, sd_bus_message **r) override;
virtual sd_bus_message* sd_bus_get_current_message(sd_bus *bus) override;
virtual int sd_bus_get_poll_data(sd_bus *bus, PollData* data) override;
virtual int sd_bus_get_n_queued(sd_bus *bus, uint64_t *read, uint64_t* write) override;
virtual int sd_bus_flush(sd_bus *bus) override;
virtual sd_bus *sd_bus_flush_close_unref(sd_bus *bus) override;
virtual sd_bus *sd_bus_close_unref(sd_bus *bus) override;
virtual int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) override;
virtual int sd_bus_query_sender_creds(sd_bus_message *m, uint64_t mask, sd_bus_creds **c) override;
virtual sd_bus_creds* sd_bus_creds_ref(sd_bus_creds *c) override;
virtual sd_bus_creds* sd_bus_creds_unref(sd_bus_creds *c) override;
virtual int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) override;
virtual int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) override;
virtual int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) override;
virtual int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) override;
virtual int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) override;
virtual int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) override;
virtual int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **label) override;
private:
std::recursive_mutex sdbusMutex_;
};
}
#endif //SDBUS_C_SDBUS_H

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Types.cpp
*
@ -23,13 +24,18 @@
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include <sdbus-c++/Error.h>
#include "MessageUtils.h"
#include <systemd/sd-bus.h>
#include <cassert>
#include "sdbus-c++/Types.h"
namespace sdbus { /*namespace internal {*/
#include "sdbus-c++/Error.h"
#include "MessageUtils.h"
#include <cerrno>
#include <system_error>
#include SDBUS_HEADER
#include <unistd.h>
namespace sdbus {
Variant::Variant()
: msg_(createPlainMessage())
@ -49,11 +55,10 @@ void Variant::deserializeFrom(Message& msg)
msg_.seal();
}
std::string Variant::peekValueType() const
const char* Variant::peekValueType() const
{
std::string type;
std::string contents;
msg_.peekType(type, contents);
msg_.rewind(false);
auto [type, contents] = msg_.peekType();
return contents;
}
@ -62,4 +67,27 @@ bool Variant::isEmpty() const
return msg_.isEmpty();
}
void UnixFd::close() // NOLINT(readability-make-member-function-const)
{
if (fd_ >= 0)
{
::close(fd_);
}
}
int UnixFd::checkedDup(int fd)
{
if (fd < 0)
{
return fd;
}
int ret = ::dup(fd); // NOLINT(android-cloexec-dup) // TODO: verify
if (ret < 0)
{
throw std::system_error(errno, std::generic_category(), "dup failed");
}
return ret;
}
} // namespace sdbus

98
src/Utils.h Normal file
View File

@ -0,0 +1,98 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file Utils.h
*
* Created on: Feb 9, 2022
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CXX_INTERNAL_UTILS_H_
#define SDBUS_CXX_INTERNAL_UTILS_H_
#include <sdbus-c++/Error.h>
#include SDBUS_HEADER
#if LIBSYSTEMD_VERSION>=246
#define SDBUS_CHECK_OBJECT_PATH(_PATH) \
SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH), std::string("Invalid object path '") + _PATH + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME), std::string("Invalid interface name '") + _NAME + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(*_NAME && !sd_bus_service_name_is_valid(_NAME), std::string("Invalid service name '") + _NAME + "' provided", EINVAL) \
/**/
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \
SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME), std::string("Invalid member name '") + _NAME + "' provided", EINVAL) \
/**/
#else
#define SDBUS_CHECK_OBJECT_PATH(_PATH)
#define SDBUS_CHECK_INTERFACE_NAME(_NAME)
#define SDBUS_CHECK_SERVICE_NAME(_NAME)
#define SDBUS_CHECK_MEMBER_NAME(_NAME)
#endif
namespace sdbus::internal {
template <typename _Callable>
bool invokeHandlerAndCatchErrors(_Callable callable, sd_bus_error *retError)
{
try
{
callable();
}
catch (const Error& e)
{
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
return false;
}
catch (const std::exception& e)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME.c_str(), e.what());
return false;
}
catch (...)
{
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME.c_str(), "Unknown error occurred");
return false;
}
return true;
}
// Returns time since epoch based of POSIX CLOCK_MONOTONIC,
// so we use the very same clock as underlying sd-bus library.
[[nodiscard]] inline auto now()
{
struct timespec ts{};
auto r = clock_gettime(CLOCK_MONOTONIC, &ts);
SDBUS_THROW_ERROR_IF(r < 0, "clock_gettime failed: ", -errno);
return std::chrono::nanoseconds(ts.tv_nsec) + std::chrono::seconds(ts.tv_sec);
}
// Implementation of the overload pattern for variant visitation
template <class... Ts> struct overload : Ts... { using Ts::operator()...; };
template <class... Ts> overload(Ts...) -> overload<Ts...>;
}
#endif /* SDBUS_CXX_INTERNAL_UTILS_H_ */

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableUtils.c
*
@ -24,48 +25,80 @@
*/
#include "VTableUtils.h"
#include <systemd/sd-bus.h>
#include SDBUS_HEADER
sd_bus_vtable createVTableStartItem()
sd_bus_vtable createSdBusVTableStartItem(uint64_t flags)
{
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(0);
struct sd_bus_vtable vtableStart = SD_BUS_VTABLE_START(flags);
return vtableStart;
}
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler )
sd_bus_vtable createSdBusVTableMethodItem( const char *member
, const char *signature
, const char *result
, const char *paramNames
, sd_bus_message_handler_t handler
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, SD_BUS_VTABLE_UNPRIVILEGED);
#if LIBSYSTEMD_VERSION>=242
// We have to expand macro SD_BUS_METHOD_WITH_NAMES manually here, because the macro expects literal char strings
/*struct sd_bus_vtable vtableItem = SD_BUS_METHOD_WITH_NAMES(member, signature, innames, result, outnames, handler, flags);*/
struct sd_bus_vtable vtableItem =
{
.type = _SD_BUS_VTABLE_METHOD,
.flags = flags,
.x = {
.method = {
.member = member,
.signature = signature,
.result = result,
.handler = handler,
.offset = 0,
.names = paramNames,
},
},
};
#else
(void)paramNames;
struct sd_bus_vtable vtableItem = SD_BUS_METHOD(member, signature, result, handler, flags);
#endif
return vtableItem;
}
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature )
sd_bus_vtable createSdBusVTableSignalItem( const char *member
, const char *signature
, const char *outnames
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, 0);
#if LIBSYSTEMD_VERSION>=242
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL_WITH_NAMES(member, signature, outnames, flags);
#else
(void)outnames;
struct sd_bus_vtable vtableItem = SD_BUS_SIGNAL(member, signature, flags);
#endif
return vtableItem;
}
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter )
sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, 0);
struct sd_bus_vtable vtableItem = SD_BUS_PROPERTY(member, signature, getter, 0, flags);
return vtableItem;
}
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter )
sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter
, uint64_t flags )
{
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, 0);
struct sd_bus_vtable vtableItem = SD_BUS_WRITABLE_PROPERTY(member, signature, getter, setter, 0, flags);
return vtableItem;
}
sd_bus_vtable createVTableEndItem()
sd_bus_vtable createSdBusVTableEndItem()
{
struct sd_bus_vtable vtableEnd = SD_BUS_VTABLE_END;
return vtableEnd;

View File

@ -1,5 +1,6 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file VTableUtils.h
*
@ -26,27 +27,34 @@
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
#include <systemd/sd-bus.h>
#include <stdbool.h>
#include SDBUS_HEADER
#ifdef __cplusplus
extern "C" {
#endif
sd_bus_vtable createVTableStartItem();
sd_bus_vtable createVTableMethodItem( const char *member
, const char *signature
, const char *result
, sd_bus_message_handler_t handler );
sd_bus_vtable createVTableSignalItem( const char *member
, const char *signature );
sd_bus_vtable createVTablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter );
sd_bus_vtable createVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter );
sd_bus_vtable createVTableEndItem();
sd_bus_vtable createSdBusVTableStartItem(uint64_t flags);
sd_bus_vtable createSdBusVTableMethodItem( const char *member
, const char *signature
, const char *result
, const char *paramNames
, sd_bus_message_handler_t handler
, uint64_t flags );
sd_bus_vtable createSdBusVTableSignalItem( const char *member
, const char *signature
, const char *outnames
, uint64_t flags );
sd_bus_vtable createSdBusVTableReadOnlyPropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, uint64_t flags );
sd_bus_vtable createSdBusVTableWritablePropertyItem( const char *member
, const char *signature
, sd_bus_property_get_t getter
, sd_bus_property_set_t setter
, uint64_t flags );
sd_bus_vtable createSdBusVTableEndItem();
#ifdef __cplusplus
}

View File

@ -1,244 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorGenerator.cpp
*
* Created on: Feb 1, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
// Own
#include "generator_utils.h"
#include "AdaptorGenerator.h"
// STL
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <algorithm>
#include <iterator>
using std::endl;
using sdbuscpp::xml::Document;
using sdbuscpp::xml::Node;
using sdbuscpp::xml::Nodes;
/**
* Generate adaptor code - server glue
*/
int AdaptorGenerator::transformXmlToFileImpl(const Document& doc, const char* filename) const
{
Node &root = *(doc.root);
Nodes interfaces = root["interface"];
std::ostringstream code;
code << createHeader(filename, StubType::ADAPTOR);
for (const auto& interface : interfaces)
{
code << processInterface(*interface);
}
code << "#endif" << endl;
return writeToFile(filename, code.str());
}
std::string AdaptorGenerator::processInterface(Node& interface) const
{
std::string ifaceName = interface.get("name");
std::cout << "Generating adaptor code for interface " << ifaceName << endl;
unsigned int namespacesCount = 0;
std::string namespacesStr;
std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
std::ostringstream body;
body << namespacesStr;
std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
+ "_adaptor";
body << "class " << className << endl
<< "{" << endl
<< "public:" << endl
<< tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl
<< tab << className << "(sdbus::IObject& object)" << endl
<< tab << tab << ": object_(object)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
Nodes properties = interface["property"];
std::string methodRegistration, methodDeclaration;
std::tie(methodRegistration, methodDeclaration) = processMethods(methods);
std::string signalRegistration, signalMethods;
std::tie(signalRegistration, signalMethods) = processSignals(signals);
std::string propertyRegistration, propertyAccessorDeclaration;
std::tie(propertyRegistration, propertyAccessorDeclaration) = processProperties(properties);
body << tab << "{" << endl
<< methodRegistration
<< signalRegistration
<< propertyRegistration
<< tab << "}" << endl << endl;
if (!signalMethods.empty())
{
body << "public:" << endl << signalMethods;
}
if (!methodDeclaration.empty())
{
body << "private:" << endl << methodDeclaration << endl;
}
if (!propertyAccessorDeclaration.empty())
{
body << "private:" << endl << propertyAccessorDeclaration << endl;
}
body << "private:" << endl
<< tab << "sdbus::IObject& object_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
return body.str();
}
std::tuple<std::string, std::string> AdaptorGenerator::processMethods(const Nodes& methods) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& method : methods)
{
auto methodName = method->get("name");
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
registrationSS << tab << tab << "object_.registerMethod(\""
<< methodName << "\")"
<< ".onInterface(interfaceName)"
<< ".implementedAs("
<< "[this]("
<< argTypeStr
<< "){ return this->" << methodName << "("
<< argStr << "); });" << endl;
declarationSS << tab
<< "virtual " << outArgsToType(outArgs) << " " << methodName
<< "(" << argTypeStr << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::tuple<std::string, std::string> AdaptorGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream signalRegistrationSS, signalMethodSS;
for (const auto& signal : signals)
{
auto name = signal->get("name");
Nodes args = (*signal)["arg"];
std::string argStr, argTypeStr, typeStr;;
std::tie(argStr, argTypeStr, typeStr) = argsToNamesAndTypes(args);
signalRegistrationSS << tab << tab
<< "object_.registerSignal(\"" << name << "\")"
".onInterface(interfaceName)";
if (args.size() > 0)
{
signalRegistrationSS << ".withParameters<" << typeStr << ">()";
}
signalRegistrationSS << ";" << endl;
signalMethodSS << tab << "void " << name << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl
<< tab << tab << "object_.emitSignal(\"" << name << "\")"
".onInterface(interfaceName)";
if (!argStr.empty())
{
signalMethodSS << ".withArguments(" << argStr << ")";
}
signalMethodSS << ";" << endl
<< tab << "}" << endl << endl;
}
return std::make_tuple(signalRegistrationSS.str(), signalMethodSS.str());
}
std::tuple<std::string, std::string> AdaptorGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
auto propertyAccess = property->get("access");
auto propertySignature = property->get("type");
auto propertyType = signature_to_type(propertySignature);
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
registrationSS << tab << tab << "object_.registerProperty(\""
<< propertyName << "\")"
<< ".onInterface(interfaceName)";
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
registrationSS << ".withGetter([this](){ return this->" << propertyName << "(); })";
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
registrationSS
<< ".withSetter([this](" << propertyTypeArg << ")"
"{ this->" << propertyName << "(" << propertyArg << "); })";
}
registrationSS << ";" << endl;
if (propertyAccess == "read" || propertyAccess == "readwrite")
declarationSS << tab << "virtual " << propertyType << " " << propertyName << "() = 0;" << endl;
if (propertyAccess == "readwrite" || propertyAccess == "write")
declarationSS << tab << "virtual void " << propertyName << "(" << propertyTypeArg << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}

View File

@ -1,13 +0,0 @@
cmake_minimum_required (VERSION 3.3)
add_definitions(-std=c++14)
project (sdbuscpp-xml2cpp)
add_executable(${PROJECT_NAME} xml2cpp.cpp xml.cpp generator_utils.cpp BaseGenerator.cpp AdaptorGenerator.cpp ProxyGenerator.cpp)
find_package (EXPAT REQUIRED)
target_link_libraries (${PROJECT_NAME} ${EXPAT_LIBRARIES})
install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME} DESTINATION bin)

View File

@ -1,224 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file ProxyGenerator.cpp
*
* Created on: Feb 1, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
// Own
#include "generator_utils.h"
#include "ProxyGenerator.h"
// STL
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <iterator>
using std::endl;
using sdbuscpp::xml::Document;
using sdbuscpp::xml::Node;
using sdbuscpp::xml::Nodes;
/**
* Generate proxy code - client glue
*/
int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const
{
Node &root = *(doc.root);
Nodes interfaces = root["interface"];
std::ostringstream code;
code << createHeader(filename, StubType::PROXY);
for (const auto& interface : interfaces)
{
code << processInterface(*interface);
}
code << "#endif" << endl;
return writeToFile(filename, code.str());
}
std::string ProxyGenerator::processInterface(Node& interface) const
{
std::string ifaceName = interface.get("name");
std::cout << "Generating proxy code for interface " << ifaceName << endl;
unsigned int namespacesCount = 0;
std::string namespacesStr;
std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName);
std::ostringstream body;
body << namespacesStr;
std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1)
+ "_proxy";
body << "class " << className << endl
<< "{" << endl
<< "public:" << endl
<< tab << "static constexpr const char* interfaceName = \"" << ifaceName << "\";" << endl << endl
<< "protected:" << endl
<< tab << className << "(sdbus::IObjectProxy& object)" << endl
<< tab << tab << ": object_(object)" << endl;
Nodes methods = interface["method"];
Nodes signals = interface["signal"];
Nodes properties = interface["property"];
std::string registration, declaration;
std::tie(registration, declaration) = processSignals(signals);
body << tab << "{" << endl
<< registration
<< tab << "}" << endl << endl
<< declaration << endl;
std::string methodDefinitions = processMethods(methods);
if (!methodDefinitions.empty())
{
body << "public:" << endl << methodDefinitions;
}
std::string propertyDefinitions = processProperties(properties);
if (!propertyDefinitions.empty())
{
body << "public:" << endl << propertyDefinitions;
}
body << "private:" << endl
<< tab << "sdbus::IObjectProxy& object_;" << endl
<< "};" << endl << endl
<< std::string(namespacesCount, '}') << " // namespaces" << endl << endl;
return body.str();
}
std::string ProxyGenerator::processMethods(const Nodes& methods) const
{
std::ostringstream methodSS;
for (const auto& method : methods)
{
auto name = method->get("name");
Nodes args = (*method)["arg"];
Nodes inArgs = args.select("direction" , "in");
Nodes outArgs = args.select("direction" , "out");
auto retType = outArgsToType(outArgs);
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(inArgs);
methodSS << tab << retType << " " << name << "(" << argTypeStr << ")" << endl
<< tab << "{" << endl;
if (outArgs.size() > 0)
{
methodSS << tab << tab << retType << " result;" << endl;
}
methodSS << tab << tab << "object_.callMethod(\"" << name << "\")"
".onInterface(interfaceName)";
if (inArgs.size() > 0)
{
methodSS << ".withArguments(" << argStr << ")";
}
if (outArgs.size() > 0)
{
methodSS << ".storeResultsTo(result);" << endl
<< tab << tab << "return result";
}
methodSS << ";" << endl << tab << "}" << endl << endl;
}
return methodSS.str();
}
std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes& signals) const
{
std::ostringstream registrationSS, declarationSS;
for (const auto& signal : signals)
{
auto name = signal->get("name");
Nodes args = (*signal)["arg"];
auto nameBigFirst = name;
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
std::string argStr, argTypeStr;
std::tie(argStr, argTypeStr, std::ignore) = argsToNamesAndTypes(args);
registrationSS << tab << tab << "object_"
".uponSignal(\"" << name << "\")"
".onInterface(interfaceName)"
".call([this](" << argTypeStr << ")"
"{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl;
declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl;
}
return std::make_tuple(registrationSS.str(), declarationSS.str());
}
std::string ProxyGenerator::processProperties(const Nodes& properties) const
{
std::ostringstream propertySS;
for (const auto& property : properties)
{
auto propertyName = property->get("name");
auto propertyAccess = property->get("access");
auto propertySignature = property->get("type");
auto propertyType = signature_to_type(propertySignature);
auto propertyArg = std::string("value");
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
if (propertyAccess == "read" || propertyAccess == "readwrite")
{
propertySS << tab << propertyType << " " << propertyName << "()" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "return object_.getProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)";
propertySS << ";" << endl << tab << "}" << endl << endl;
}
if (propertyAccess == "readwrite" || propertyAccess == "write")
{
propertySS << tab << "void " << propertyName << "(" << propertyTypeArg << ")" << endl
<< tab << "{" << endl;
propertySS << tab << tab << "object_.setProperty(\"" << propertyName << "\")"
".onInterface(interfaceName)"
".toValue(" << propertyArg << ")";
propertySS << ";" << endl << tab << "}" << endl << endl;
}
}
return propertySS.str();
}

View File

@ -1,68 +0,0 @@
# Defines how to build and install libsdbus-c++ tests
AUTOMAKE_OPTIONS = subdir-objects
# Target dirs for test binaries, scripts and files
testbindir = /opt/test/bin
dbusconfdir = $(sysconfdir)/dbus-1/system.d
# ENABLE_TESTS is defined by configure when user enables tests during configuration
if ENABLE_TESTS
testbin_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests
dbusconf_DATA = integrationtests/files/libsdbus-cpp-test.conf
endif
# Setting per-file flags
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/src
AM_CXXFLAGS = @libsdbus_cpp_CFLAGS@ @SYSTEMD_CFLAGS@ -W -Wall -Werror -pedantic -pipe -std=c++14
AM_LDFLAGS = @libsdbus_cpp_LIBS@ @SYSTEMD_LIBS@
CLEANFILES = *~ *.lo *.la
MOSTLYCLEANFILES = *.o
TESTS =
# Configuration for libsdbus-c++_unittests
libsdbus_c___unittests_SOURCES = \
unittests/libsdbus-c++_unittests.cpp \
unittests/TypeTraits_test.cpp \
unittests/Types_test.cpp \
unittests/Message_test.cpp
libsdbus_c___unittests_LDFLAGS = -L$(top_builddir)/src
libsdbus_c___unittests_LDADD = \
-lsdbus-c++ \
@libsdbus_cpp_LIBS@ \
@SYSTEMD_LIBS@ \
-lgmock
TESTS += libsdbus-c++_unittests
# Configuration for libsdbus-c++_integrationtests
libsdbus_c___integrationtests_SOURCES = \
integrationtests/libsdbus-c++_integrationtests.cpp \
integrationtests/Connection_test.cpp \
integrationtests/AdaptorAndProxy_test.cpp
libsdbus_c___integrationtests_LDFLAGS = -L$(top_builddir)/src
libsdbus_c___integrationtests_LDADD = \
-lsdbus-c++ \
@libsdbus_cpp_LIBS@ \
@SYSTEMD_LIBS@ \
-lgmock \
-lpthread
TESTS += libsdbus-c++_integrationtests
check_PROGRAMS = libsdbus-c++_unittests libsdbus-c++_integrationtests
DISTCLEANFILES = Makefile Makefile.in
# Post-build action: executing tests from the IDE
if ENABLE_TESTS
all-local: libsdbus-c++_unittests libsdbus-c++_integrationtests
if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_unittests; fi; exit 0
if [ "${UNIT_TESTS_RUNNER}" ]; then "${UNIT_TESTS_RUNNER}" --deviceip="${TEST_DEVICE_IP}" --testbin=.libs/libsdbus-c++_integrationtests; fi; exit 0
endif

View File

@ -1,289 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file AdaptorAndProxy_test.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
// Own
#include "Connection.h"
#include "TestingAdaptor.h"
#include "TestingProxy.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
// gmock
#include <gtest/gtest.h>
#include <gmock/gmock.h>
// STL
#include <string>
#include <thread>
#include <tuple>
using ::testing::Eq;
using ::testing::Gt;
namespace
{
class AdaptorAndProxyFixture : public ::testing::Test
{
public:
static void SetUpTestCase()
{
m_connection.requestName(INTERFACE_NAME);
m_connection.enterProcessingLoopAsync();
}
static void TearDownTestCase()
{
m_connection.leaveProcessingLoop();
m_connection.releaseName(INTERFACE_NAME);
}
private:
void SetUp() override
{
m_adaptor = std::make_unique<TestingAdaptor>(m_connection);
m_proxy = std::make_unique<TestingProxy>(INTERFACE_NAME, OBJECT_PATH);
usleep(50000); // Give time for the proxy to start listening to signals
}
void TearDown() override
{
m_proxy.reset();
m_adaptor.reset();
}
public:
static sdbus::internal::Connection m_connection;
std::unique_ptr<TestingAdaptor> m_adaptor;
std::unique_ptr<TestingProxy> m_proxy;
};
sdbus::internal::Connection AdaptorAndProxyFixture::m_connection{sdbus::internal::Connection::BusType::eSystem};
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AdaptorAndProxy, CanBeConstructedSuccesfully)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
ASSERT_NO_THROW(TestingAdaptor adaptor(*connection));
ASSERT_NO_THROW(TestingProxy proxy(INTERFACE_NAME, OBJECT_PATH));
connection->releaseName(INTERFACE_NAME);
}
// Methods
using SdbusTestObject = AdaptorAndProxyFixture;
TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully)
{
ASSERT_NO_THROW(m_proxy->noArgNoReturn());
}
TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully)
{
auto resInt = m_proxy->getInt();
ASSERT_THAT(resInt, Eq(INT32_VALUE));
auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully)
{
auto resTuple = m_proxy->getTuple();
ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE));
ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully)
{
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> a{};
auto vectorRes = m_proxy->getInts16FromStruct(a);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{0})); // because second item is by default initialized to 0
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> b{
UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE}
};
vectorRes = m_proxy->getInts16FromStruct(b);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{INT16_VALUE, INT16_VALUE, -INT16_VALUE}));
}
TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully)
{
sdbus::Variant v{DOUBLE_VALUE};
auto variantRes = m_proxy->processVariant(v);
ASSERT_THAT(variantRes.get<int32_t>(), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
}
TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully)
{
std::vector<int32_t> x{-2, 0, 2};
sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true};
auto mapOfVariants = m_proxy->getMapOfVariants(x, y);
decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}};
ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>()));
ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>()));
ASSERT_THAT(mapOfVariants[2].get<bool>(), Eq(res[2].get<bool>()));
}
TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully)
{
auto val = m_proxy->getStructInStruct();
ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE));
ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully)
{
auto val = m_proxy->sumStructItems({1, 2}, {3, 4});
ASSERT_THAT(val, Eq(1 + 2 + 3 + 4));
}
TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully)
{
auto val = m_proxy->sumVectorItems({1, 7}, {2, 3});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3));
}
TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully)
{
auto resSignature = m_proxy->getSignature();
ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully)
{
auto resObjectPath = m_proxy->getObjectPath();
ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE));
}
TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully)
{
auto resComplex = m_proxy->getComplex();
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TEST_F(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface)
{
ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination)
{
TestingProxy proxy("wrongDestination", OBJECT_PATH);
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
{
TestingProxy proxy(INTERFACE_NAME, "/wrong/path");
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
// Signals
TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully)
{
auto count = m_proxy->getSimpleCallCount();
m_adaptor->simpleSignal();
usleep(10000);
ASSERT_THAT(m_proxy->getSimpleCallCount(), Eq(count + 1));
}
TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully)
{
m_adaptor->signalWithMap({{0, "zero"}, {1, "one"}});
usleep(10000);
auto map = m_proxy->getMap();
ASSERT_THAT(map[0], Eq("zero"));
ASSERT_THAT(map[1], Eq("one"));
}
TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully)
{
double d = 3.14;
m_adaptor->signalWithVariant(3.14);
usleep(10000);
ASSERT_THAT(m_proxy->getVariantValue(), d);
}
TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully)
{
m_adaptor->signalWithoutRegistration({"platform", {"av"}});
usleep(10000);
auto signature = m_proxy->getSignatureFromSignal();
ASSERT_THAT(signature["platform"], Eq("av"));
}
TEST_F(SdbusTestObject, failsEmitingSignalOnNonexistentInterface)
{
ASSERT_THROW(m_adaptor->emitSignalOnNonexistentInterface(), sdbus::Error);
}
// Properties
TEST_F(SdbusTestObject, ReadsReadPropertySuccesfully)
{
ASSERT_THAT(m_proxy->state(), Eq(STRING_VALUE));
}
TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully)
{
auto x = 42;
ASSERT_NO_THROW(m_proxy->action(x));
ASSERT_THAT(m_proxy->action(), Eq(x));
}
TEST_F(SdbusTestObject, WritesToWritePropertySuccesfully)
{
auto x = true;
ASSERT_NO_THROW(m_proxy->blocking(x));
}
TEST_F(SdbusTestObject, CannotReadFromWriteProperty)
{
ASSERT_THROW(m_proxy->blocking(), sdbus::Error);
}

View File

@ -1,91 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Connection_test.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
// Own
#include "defs.h"
// sdbus
#include <sdbus-c++/Error.h>
#include <sdbus-c++/IConnection.h>
// gmock
#include <gtest/gtest.h>
#include <gmock/gmock.h>
// STL
#include <thread>
using ::testing::Eq;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(Connection, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::createConnection());
}
TEST(Connection, CanRequestRegisteredDbusName)
{
auto connection = sdbus::createConnection();
ASSERT_NO_THROW(connection->requestName(INTERFACE_NAME));
connection->releaseName(INTERFACE_NAME);
}
TEST(Connection, CannotRequestNonregisteredDbusName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->requestName("some_random_not_supported_dbus_name"), sdbus::Error);
}
TEST(Connection, CanReleasedRequestedName)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
ASSERT_NO_THROW(connection->releaseName(INTERFACE_NAME));
}
TEST(Connection, CannotReleaseNonrequestedName)
{
auto connection = sdbus::createConnection();
ASSERT_THROW(connection->releaseName("some_random_nonrequested_name"), sdbus::Error);
}
TEST(Connection, CanEnterAndLeaveProcessingLoop)
{
auto connection = sdbus::createConnection();
connection->requestName(INTERFACE_NAME);
std::thread t([&](){ connection->enterProcessingLoop(); });
connection->leaveProcessingLoop();
t.join();
connection->releaseName(INTERFACE_NAME);
}

View File

@ -1,145 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TestingAdaptor.h
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGADAPTOR_H_
#include "adaptor-glue.h"
class TestingAdaptor : public sdbus::Interfaces<testing_adaptor>
{
public:
TestingAdaptor(sdbus::IConnection& connection) :
sdbus::Interfaces<::testing_adaptor>(connection, OBJECT_PATH) { }
virtual ~TestingAdaptor() { }
protected:
void noArgNoReturn() const { }
int32_t getInt() const { return INT32_VALUE; }
std::tuple<uint32_t, std::string> getTuple() const { return std::make_tuple(UINT32_VALUE, STRING_VALUE); }
double multiply(const int64_t& a, const double& b) const { return a * b; }
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x) const
{
std::vector<int16_t> res{x.get<1>()};
auto y = std::get<std::vector<int16_t>>(x);
res.insert(res.end(), y.begin(), y.end());
return res;
}
sdbus::Variant processVariant(sdbus::Variant& v)
{
sdbus::Variant res = static_cast<int32_t>(v.get<double>());
return res;
}
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) const
{
std::map<int32_t, sdbus::Variant> res;
for (auto item : x)
{
res[item] = (item <= 0) ? std::get<0>(y) : std::get<1>(y);
}
return res;
}
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const
{
return sdbus::make_struct(STRING_VALUE, sdbus::make_struct(std::map<int32_t, int32_t>{{INT32_VALUE, INT32_VALUE}}));
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
{
int32_t res{0};
res += std::get<0>(a) + std::get<1>(a);
res += std::get<0>(b) + std::get<1>(b);
return res;
}
uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b)
{
uint32_t res{0};
for (auto x : a)
{
res += x;
}
for (auto x : b)
{
res += x;
}
return res;
}
sdbus::Signature getSignature() const { return SIGNATURE_VALUE; }
sdbus::ObjectPath getObjectPath() const { return OBJECT_PATH_VALUE; }
ComplexType getComplex() const
{
return { // map
{
0, // uint_64_t
{ // struct
{ // map
{
'a', // uint8_t
{ // vector
{ // struct
"/object/path", // object path
false,
3.14,
{ // map
{0, "zero"}
}
}
}
}
},
"a{t(a{ya(obva{is})}gs)}", // signature
""
}
}
};
}
std::string state() { return STRING_VALUE; }
uint32_t action() { return m_action; }
void action(const uint32_t& value) { m_action = value; }
bool blocking() { return m_blocking; }
void blocking(const bool& value) { m_blocking = value; }
private:
uint32_t m_action;
bool m_blocking;
};
#endif /* INTEGRATIONTESTS_TESTINGADAPTOR_H_ */

View File

@ -1,66 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TestingProxy.h
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_
#define SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_
#include "proxy-glue.h"
class TestingProxy : public sdbus::ProxyInterfaces<::testing_proxy>
{
public:
using sdbus::ProxyInterfaces<::testing_proxy>::ProxyInterfaces;
int getSimpleCallCount() const { return m_simpleCallCounter; }
std::map<int32_t, std::string> getMap() const { return m_map; }
double getVariantValue() const { return m_variantValue; }
std::map<std::string, std::string> getSignatureFromSignal() const { return m_signature; }
protected:
void onSimpleSignal() override { ++m_simpleCallCounter; }
void onSignalWithMap(const std::map<int32_t, std::string>& m) override { m_map = m; }
void onSignalWithVariant(const sdbus::Variant& v) override
{
m_variantValue = v.get<double>();
}
void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) override
{
m_signature[std::get<0>(s)] = static_cast<std::string>(std::get<0>(std::get<1>(s)));
}
private:
int m_simpleCallCounter{};
std::map<int32_t, std::string> m_map;
double m_variantValue;
std::map<std::string, std::string> m_signature;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTINGPROXY_H_ */

View File

@ -1,156 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file adaptor-glue.h
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_
#define SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_
#include "defs.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
sdbus::Variant,
std::map<int, std::string>
>
>
>,
sdbus::Signature,
std::string // char* leads to type and memory issues, std::string is best choice
>
>;
class testing_adaptor
{
protected:
testing_adaptor(sdbus::IObject& object) :
object_(object)
{
object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); });
object_.registerMethod("getInt").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getInt(); });
object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getTuple(); });
object_.registerMethod("multiply").onInterface(INTERFACE_NAME).implementedAs([this](const int64_t& a, const double& b){ return this->multiply(a, b); });
object_.registerMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).implementedAs([this](
const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x){ return this->getInts16FromStruct(x); });
object_.registerMethod("processVariant").onInterface(INTERFACE_NAME).implementedAs([this](sdbus::Variant& v){ return this->processVariant(v); });
object_.registerMethod("getMapOfVariants").onInterface(INTERFACE_NAME).implementedAs([this](
const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y){ return this->getMapOfVariants(x ,y); });
object_.registerMethod("getStructInStruct").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getStructInStruct(); });
object_.registerMethod("sumStructItems").onInterface(INTERFACE_NAME).implementedAs([this](
const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b){
return this->sumStructItems(a, b);
});
object_.registerMethod("sumVectorItems").onInterface(INTERFACE_NAME).implementedAs([this](
const std::vector<uint16_t>& a, const std::vector<uint64_t>& b){
return this->sumVectorItems(a, b);
});
object_.registerMethod("getSignature").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getSignature(); });
object_.registerMethod("getObjectPath").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getObjectPath(); });
object_.registerMethod("getComplex").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getComplex(); });
// registration of signals is optional, it is useful because of introspection
object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME);
object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters<std::map<int32_t, std::string>>();
object_.registerSignal("signalWithVariant").onInterface(INTERFACE_NAME).withParameters<sdbus::Variant>();
object_.registerProperty("state").onInterface(INTERFACE_NAME).withGetter([this](){ return this->state(); });
object_.registerProperty("action").onInterface(INTERFACE_NAME).withGetter([this](){ return this->action(); }).withSetter([this](const uint32_t& value){ this->action(value); });
object_.registerProperty("blocking").onInterface(INTERFACE_NAME)./*withGetter([this](){ return this->blocking(); }).*/withSetter([this](const bool& value){ this->blocking(value); });
}
public:
void simpleSignal()
{
object_.emitSignal("simpleSignal").onInterface(INTERFACE_NAME);
}
void signalWithMap(const std::map<int32_t, std::string>& map)
{
object_.emitSignal("signalWithMap").onInterface(INTERFACE_NAME).withArguments(map);
}
void signalWithVariant(const sdbus::Variant& v)
{
object_.emitSignal("signalWithVariant").onInterface(INTERFACE_NAME).withArguments(v);
}
void signalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s)
{
object_.emitSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).withArguments(s);
}
void emitSignalOnNonexistentInterface()
{
object_.emitSignal("simpleSignal").onInterface("interfaceThatDoesNotExists");
}
private:
sdbus::IObject& object_;
protected:
virtual void noArgNoReturn() const = 0;
virtual int32_t getInt() const = 0;
virtual std::tuple<uint32_t, std::string> getTuple() const = 0;
virtual double multiply(const int64_t& a, const double& b) const = 0;
virtual std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x) const = 0;
virtual sdbus::Variant processVariant(sdbus::Variant& v) = 0;
virtual std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y) const = 0;
virtual sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct() const = 0;
virtual int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b) = 0;
virtual uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b) = 0;
virtual sdbus::Signature getSignature() const = 0;
virtual sdbus::ObjectPath getObjectPath() const = 0;
virtual ComplexType getComplex() const = 0;
virtual std::string state() = 0;
virtual uint32_t action() = 0;
virtual void action(const uint32_t& value) = 0;
virtual bool blocking() = 0;
virtual void blocking(const bool& value) = 0;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_ADAPTOR_GLUE_H_ */

View File

@ -1,46 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file defs.h
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_
#define SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_
#include "sdbus-c++/Types.h"
const std::string INTERFACE_NAME{"com.kistler.testsdbuscpp"};
const std::string OBJECT_PATH{"/"};
constexpr const uint8_t UINT8_VALUE{1};
constexpr const int16_t INT16_VALUE{21};
constexpr const uint32_t UINT32_VALUE{42};
constexpr const int32_t INT32_VALUE{-42};
constexpr const int32_t INT64_VALUE{-1024};
const std::string STRING_VALUE{"sdbus-c++-testing"};
const sdbus::Signature SIGNATURE_VALUE{"a{is}"};
const sdbus::ObjectPath OBJECT_PATH_VALUE{"/"};
constexpr const double DOUBLE_VALUE{3.24L};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_DEFS_H_ */

View File

@ -1,190 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file proxy-glue.h
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_
#define SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_
#include "defs.h"
// sdbus
#include "sdbus-c++/sdbus-c++.h"
class testing_proxy
{
protected:
testing_proxy(sdbus::IObjectProxy& object) :
object_(object)
{
object_.uponSignal("simpleSignal").onInterface(INTERFACE_NAME).call([this](){ this->onSimpleSignal(); });
object_.uponSignal("signalWithMap").onInterface(INTERFACE_NAME).call([this](const std::map<int32_t, std::string>& map){ this->onSignalWithMap(map); });
object_.uponSignal("signalWithVariant").onInterface(INTERFACE_NAME).call([this](const sdbus::Variant& v){ this->onSignalWithVariant(v); });
object_.uponSignal("signalWithoutRegistration").onInterface(INTERFACE_NAME).call([this](const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s)
{ this->onSignalWithoutRegistration(s); });
}
virtual void onSimpleSignal() = 0;
virtual void onSignalWithMap(const std::map<int32_t, std::string>& map) = 0;
virtual void onSignalWithVariant(const sdbus::Variant& v) = 0;
virtual void onSignalWithoutRegistration(const sdbus::Struct<std::string, sdbus::Struct<sdbus::Signature>>& s) = 0;
public:
void noArgNoReturn()
{
object_.callMethod("noArgNoReturn").onInterface(INTERFACE_NAME);
}
int32_t getInt()
{
int32_t result;
object_.callMethod("getInt").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
std::tuple<uint32_t, std::string> getTuple()
{
std::tuple<uint32_t, std::string> result;
object_.callMethod("getTuple").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
double multiply(const int64_t& a, const double& b)
{
double result;
object_.callMethod("multiply").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
std::vector<int16_t> getInts16FromStruct(const sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>>& x)
{
std::vector<int16_t> result;
object_.callMethod("getInts16FromStruct").onInterface(INTERFACE_NAME).withArguments(x).storeResultsTo(result);
return result;
}
sdbus::Variant processVariant(const sdbus::Variant& v)
{
sdbus::Variant result;
object_.callMethod("processVariant").onInterface(INTERFACE_NAME).withArguments(v).storeResultsTo(result);
return result;
}
std::map<int32_t, sdbus::Variant> getMapOfVariants(const std::vector<int32_t>& x, const sdbus::Struct<sdbus::Variant, sdbus::Variant>& y)
{
std::map<int32_t, sdbus::Variant> result;
object_.callMethod("getMapOfVariants").onInterface(INTERFACE_NAME).withArguments(x, y).storeResultsTo(result);
return result;
}
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> getStructInStruct()
{
sdbus::Struct<std::string, sdbus::Struct<std::map<int32_t, int32_t>>> result;
object_.callMethod("getStructInStruct").onInterface(INTERFACE_NAME).withArguments().storeResultsTo(result);
return result;
}
int32_t sumStructItems(const sdbus::Struct<uint8_t, uint16_t>& a, const sdbus::Struct<int32_t, int64_t>& b)
{
int32_t result;
object_.callMethod("sumStructItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
uint32_t sumVectorItems(const std::vector<uint16_t>& a, const std::vector<uint64_t>& b)
{
uint32_t result;
object_.callMethod("sumVectorItems").onInterface(INTERFACE_NAME).withArguments(a, b).storeResultsTo(result);
return result;
}
sdbus::Signature getSignature()
{
sdbus::Signature result;
object_.callMethod("getSignature").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
sdbus::ObjectPath getObjectPath()
{
sdbus::ObjectPath result;
object_.callMethod("getObjectPath").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
ComplexType getComplex()
{
ComplexType result;
object_.callMethod("getComplex").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
int32_t callNonexistentMethod()
{
int32_t result;
object_.callMethod("callNonexistentMethod").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
int32_t callMethodOnNonexistentInterface()
{
int32_t result;
object_.callMethod("someMethod").onInterface("interfaceThatDoesNotExist").storeResultsTo(result);
return result;
}
std::string state()
{
return object_.getProperty("state").onInterface(INTERFACE_NAME);
}
uint32_t action()
{
return object_.getProperty("action").onInterface(INTERFACE_NAME);
}
void action(const uint32_t& value)
{
object_.setProperty("action").onInterface(INTERFACE_NAME).toValue(value);
}
bool blocking()
{
return object_.getProperty("blocking").onInterface(INTERFACE_NAME);
}
void blocking(const bool& value)
{
object_.setProperty("blocking").onInterface(INTERFACE_NAME).toValue(value);
}
private:
sdbus::IObjectProxy& object_;
};
#endif /* SDBUS_CPP_INTEGRATIONTESTS_PROXY_GLUE_H_ */

View File

@ -1,230 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Message_test.cpp
*
* Created on: Dec 3, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include "MessageUtils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
using ::testing::DoubleEq;
using namespace std::string_literals;
namespace
{
std::string deserializeString(sdbus::Message& msg)
{
std::string str;
msg >> str;
return str;
}
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AMessage, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::Message());
}
TEST(AMessage, IsInvalidAfterDefaultConstructed)
{
sdbus::Message msg;
ASSERT_FALSE(msg.isValid());
}
TEST(AMessage, IsValidWhenConstructedAsRealMessage)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_TRUE(msg.isValid());
}
TEST(AMessage, CreatesShallowCopyWhenCopyConstructed)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
msg.seal();
sdbus::Message msgCopy = msg;
std::string str;
msgCopy >> str;
ASSERT_THAT(str, Eq("I am a string"));
ASSERT_THROW(msgCopy >> str, sdbus::Error);
}
TEST(AMessage, CreatesDeepCopyWhenEplicitlyCopied)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
msg.seal();
sdbus::Message msgCopy{sdbus::createPlainMessage()};
msg.copyTo(msgCopy, true);
msgCopy.seal(); // Seal to be able to read from it subsequently
msg.rewind(true); // Rewind to the beginning after copying
ASSERT_THAT(deserializeString(msg), Eq("I am a string"));
ASSERT_THAT(deserializeString(msgCopy), Eq("I am a string"));
}
TEST(AMessage, IsEmptyWhenContainsNoValue)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_TRUE(msg.isEmpty());
}
TEST(AMessage, IsNotEmptyWhenContainsAValue)
{
sdbus::Message msg{sdbus::createPlainMessage()};
msg << "I am a string"s;
ASSERT_FALSE(msg.isEmpty());
}
TEST(AMessage, ReturnsItsTypeWhenAsked)
{
sdbus::Message msg{sdbus::createPlainMessage()};
ASSERT_THAT(msg.getType(), Eq(sdbus::Message::Type::ePlainMessage));
}
TEST(AMessage, CanCarryASimpleInteger)
{
sdbus::Message msg{sdbus::createPlainMessage()};
int dataWritten = 5;
msg << dataWritten;
msg.seal();
int dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryAVariant)
{
sdbus::Message msg{sdbus::createPlainMessage()};
auto dataWritten = sdbus::Variant((double)3.14);
msg << dataWritten;
msg.seal();
sdbus::Variant dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead.get<double>(), Eq(dataWritten.get<double>()));
}
TEST(AMessage, CanCarryACollectionOfEmbeddedVariants)
{
sdbus::Message msg{sdbus::createPlainMessage()};
auto value = std::vector<sdbus::Variant>{"hello"s, (double)3.14};
auto dataWritten = sdbus::Variant{value};
msg << dataWritten;
msg.seal();
sdbus::Variant dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead.get<decltype(value)>()[0].get<std::string>(), Eq(value[0].get<std::string>()));
ASSERT_THAT(dataRead.get<decltype(value)>()[1].get<double>(), Eq(value[1].get<double>()));
}
TEST(AMessage, CanCarryAnArray)
{
sdbus::Message msg{sdbus::createPlainMessage()};
std::vector<int64_t> dataWritten{3545342, 43643532, 324325};
msg << dataWritten;
msg.seal();
std::vector<int64_t> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryADictionary)
{
sdbus::Message msg{sdbus::createPlainMessage()};
std::map<int, std::string> dataWritten{{1, "one"}, {2, "two"}};
msg << dataWritten;
msg.seal();
std::map<int, std::string> dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}
TEST(AMessage, CanCarryAComplexType)
{
sdbus::Message msg{sdbus::createPlainMessage()};
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
int16_t,
/*sdbus::Variant,*/
std::map<int, std::string>
>
>
>,
sdbus::Signature,
double
>
>;
ComplexType dataWritten = { {1, {{{5, {{"/some/object", true, 45, {{6, "hello"}, {7, "world"}}}}}}, "av", 3.14}}};
msg << dataWritten;
msg.seal();
ComplexType dataRead;
msg >> dataRead;
ASSERT_THAT(dataRead, Eq(dataWritten));
}

View File

@ -1,129 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file TypeTraits_test.cpp
*
* Created on: Nov 27, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/TypeTraits.h>
#include <sdbus-c++/Types.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
namespace
{
// ---
// FIXTURE DEFINITION FOR TYPED TESTS
// ----
template <typename _T>
class Type2DBusTypeSignatureConversion
: public ::testing::Test
{
protected:
const std::string dbusTypeSignature_{getDBusTypeSignature()};
private:
static std::string getDBusTypeSignature();
};
#define TYPE(...) \
template <> \
std::string Type2DBusTypeSignatureConversion<__VA_ARGS__>::getDBusTypeSignature() \
/**/
#define HAS_DBUS_TYPE_SIGNATURE(_SIG) \
{ \
return (_SIG); \
} \
/**/
TYPE(bool)HAS_DBUS_TYPE_SIGNATURE("b")
TYPE(uint8_t)HAS_DBUS_TYPE_SIGNATURE("y")
TYPE(int16_t)HAS_DBUS_TYPE_SIGNATURE("n")
TYPE(uint16_t)HAS_DBUS_TYPE_SIGNATURE("q")
TYPE(int32_t)HAS_DBUS_TYPE_SIGNATURE("i")
TYPE(uint32_t)HAS_DBUS_TYPE_SIGNATURE("u")
TYPE(int64_t)HAS_DBUS_TYPE_SIGNATURE("x")
TYPE(uint64_t)HAS_DBUS_TYPE_SIGNATURE("t")
TYPE(double)HAS_DBUS_TYPE_SIGNATURE("d")
TYPE(const char*)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(std::string)HAS_DBUS_TYPE_SIGNATURE("s")
TYPE(sdbus::ObjectPath)HAS_DBUS_TYPE_SIGNATURE("o")
TYPE(sdbus::Signature)HAS_DBUS_TYPE_SIGNATURE("g")
TYPE(sdbus::Variant)HAS_DBUS_TYPE_SIGNATURE("v")
TYPE(sdbus::Struct<bool>)HAS_DBUS_TYPE_SIGNATURE("(b)")
TYPE(sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>)HAS_DBUS_TYPE_SIGNATURE("(qdsv)")
TYPE(std::vector<int16_t>)HAS_DBUS_TYPE_SIGNATURE("an")
TYPE(std::map<int32_t, int64_t>)HAS_DBUS_TYPE_SIGNATURE("a{ix}")
using ComplexType = std::map<
uint64_t,
sdbus::Struct<
std::map<
uint8_t,
std::vector<
sdbus::Struct<
sdbus::ObjectPath,
bool,
sdbus::Variant,
std::map<int, std::string>
>
>
>,
sdbus::Signature,
const char*
>
>;
TYPE(ComplexType)HAS_DBUS_TYPE_SIGNATURE("a{t(a{ya(obva{is})}gs)}")
typedef ::testing::Types< bool
, uint8_t
, int16_t
, uint16_t
, int32_t
, uint32_t
, int64_t
, uint64_t
, double
, const char*
, std::string
, sdbus::ObjectPath
, sdbus::Signature
, sdbus::Variant
, sdbus::Struct<bool>
, sdbus::Struct<uint16_t, double, std::string, sdbus::Variant>
, std::vector<int16_t>
, std::map<int32_t, int64_t>
, ComplexType
> DBusSupportedTypes;
TYPED_TEST_CASE(Type2DBusTypeSignatureConversion, DBusSupportedTypes);
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(Type2DBusTypeSignatureConversion, ConvertsTypeToProperDBusSignature)
{
ASSERT_THAT(sdbus::signature_of<TypeParam>::str(), Eq(this->dbusTypeSignature_));
}

View File

@ -1,214 +0,0 @@
/**
* (C) 2017 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
*
* @file Types_test.cpp
*
* Created on: Dec 3, 2016
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include <sdbus-c++/Types.h>
#include "MessageUtils.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
using ::testing::Eq;
using namespace std::string_literals;
namespace
{
constexpr const uint64_t ANY_UINT64 = 84578348354;
constexpr const double ANY_DOUBLE = 3.14;
}
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AVariant, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(sdbus::Variant());
}
TEST(AVariant, ContainsNoValueAfterDefaultConstructed)
{
sdbus::Variant v;
ASSERT_TRUE(v.isEmpty());
}
TEST(AVariant, CanBeConstructedFromASimpleValue)
{
int value = 5;
ASSERT_NO_THROW(sdbus::Variant{value});
}
TEST(AVariant, CanBeConstructedFromAComplexValue)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
ASSERT_NO_THROW(sdbus::Variant(value));
}
TEST(AVariant, CanBeCopied)
{
auto value = "hello"s;
sdbus::Variant variant(value);
auto variantCopy1{variant};
auto variantCopy2 = variantCopy1;
ASSERT_THAT(variantCopy1.get<std::string>(), Eq(value));
ASSERT_THAT(variantCopy2.get<std::string>(), Eq(value));
}
TEST(AVariant, IsNotEmptyWhenContainsAValue)
{
sdbus::Variant v("hello");
ASSERT_FALSE(v.isEmpty());
}
TEST(ASimpleVariant, ReturnsTheSimpleValueWhenAsked)
{
int value = 5;
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<int>(), Eq(value));
}
TEST(AComplexVariant, ReturnsTheComplexValueWhenAsked)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<decltype(value)>(), Eq(value));
}
TEST(AVariant, HasConceptuallyNonmutableGetMethodWhichCanBeCalledXTimes)
{
std::string value{"I am a string"};
sdbus::Variant variant(value);
ASSERT_THAT(variant.get<std::string>(), Eq(value));
ASSERT_THAT(variant.get<std::string>(), Eq(value));
ASSERT_THAT(variant.get<std::string>(), Eq(value));
}
TEST(AVariant, ReturnsTrueWhenAskedIfItContainsTheTypeItReallyContains)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
ASSERT_TRUE(variant.containsValueOfType<ComplexType>());
}
TEST(ASimpleVariant, ReturnsFalseWhenAskedIfItContainsTypeItDoesntReallyContain)
{
int value = 5;
sdbus::Variant variant(value);
ASSERT_FALSE(variant.containsValueOfType<double>());
}
TEST(AVariant, CanContainOtherEmbeddedVariants)
{
using TypeWithVariants = std::vector<sdbus::Struct<sdbus::Variant, double>>;
TypeWithVariants value;
value.emplace_back(sdbus::make_struct(sdbus::Variant("a string"), ANY_DOUBLE));
value.emplace_back(sdbus::make_struct(sdbus::Variant(ANY_UINT64), ANY_DOUBLE));
sdbus::Variant variant(value);
ASSERT_TRUE(variant.containsValueOfType<TypeWithVariants>());
}
TEST(ANonEmptyVariant, SerializesSuccessfullyToAMessage)
{
sdbus::Variant variant("a string");
sdbus::Message msg = sdbus::createPlainMessage();
ASSERT_NO_THROW(variant.serializeTo(msg));
}
TEST(AnEmptyVariant, ThrowsWhenBeingSerializedToAMessage)
{
sdbus::Variant variant;
sdbus::Message msg = sdbus::createPlainMessage();
ASSERT_THROW(variant.serializeTo(msg), sdbus::Error);
}
TEST(ANonEmptyVariant, SerializesToAndDeserializesFromAMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
sdbus::Message msg = sdbus::createPlainMessage();
variant.serializeTo(msg);
msg.seal();
sdbus::Variant variant2;
variant2.deserializeFrom(msg);
ASSERT_THAT(variant2.get<decltype(value)>(), Eq(value));
}
TEST(CopiesOfVariant, SerializeToAndDeserializeFromMessageSuccessfully)
{
using ComplexType = std::map<uint64_t, std::vector<sdbus::Struct<std::string, double>>>;
ComplexType value{ {ANY_UINT64, ComplexType::mapped_type{sdbus::make_struct("hello", ANY_DOUBLE), sdbus::make_struct("world", ANY_DOUBLE)}} };
sdbus::Variant variant(value);
auto variantCopy1{variant};
auto variantCopy2 = variant;
sdbus::Message msg = sdbus::createPlainMessage();
variant.serializeTo(msg);
variantCopy1.serializeTo(msg);
variantCopy2.serializeTo(msg);
msg.seal();
sdbus::Variant receivedVariant1, receivedVariant2, receivedVariant3;
receivedVariant1.deserializeFrom(msg);
receivedVariant2.deserializeFrom(msg);
receivedVariant3.deserializeFrom(msg);
ASSERT_THAT(receivedVariant1.get<decltype(value)>(), Eq(value));
ASSERT_THAT(receivedVariant2.get<decltype(value)>(), Eq(value));
ASSERT_THAT(receivedVariant3.get<decltype(value)>(), Eq(value));
}
TEST(AStruct, CreatesStructFromTuple)
{
std::tuple<int32_t, std::string> value{1234, "abcd"};
sdbus::Struct<int32_t, std::string> valueStruct{value};
ASSERT_THAT(std::get<0>(valueStruct), Eq(std::get<0>(value)));
ASSERT_THAT(std::get<1>(valueStruct), Eq(std::get<1>(value)));
}

175
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,175 @@
#-------------------------------
# DOWNLOAD AND BUILD OF GOOGLETEST
#-------------------------------
find_package(GTest ${SDBUSCPP_GOOGLETEST_VERSION} CONFIG)
if (NOT TARGET GTest::gmock)
# Try pkg-config if GTest was not found through CMake config
find_package(PkgConfig)
if (PkgConfig_FOUND)
pkg_check_modules(GMock IMPORTED_TARGET GLOBAL gmock>=${SDBUSCPP_GOOGLETEST_VERSION})
if(TARGET PkgConfig::GMock)
add_library(GTest::gmock ALIAS PkgConfig::GMock)
endif()
endif()
# GTest was not found in the system, build it on our own
if (NOT TARGET GTest::gmock)
include(FetchContent)
if (SDBUSCPP_GOOGLETEST_VERSION VERSION_GREATER_EQUAL 1.13.0)
set(GOOGLETEST_TAG "v${SDBUSCPP_GOOGLETEST_VERSION}")
else()
set(GOOGLETEST_TAG "release-${SDBUSCPP_GOOGLETEST_VERSION}")
endif()
message("Manually fetching & building googletest ${GOOGLETEST_TAG}...")
FetchContent_Declare(googletest
GIT_REPOSITORY ${SDBUSCPP_GOOGLETEST_GIT_REPO}
GIT_TAG ${GOOGLETEST_TAG}
GIT_SHALLOW 1
UPDATE_COMMAND "")
set(gtest_force_shared_crt ON CACHE INTERNAL "" FORCE)
set(INSTALL_GTEST OFF CACHE INTERNAL "" FORCE)
set(BUILD_SHARED_LIBS_BAK ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
FetchContent_MakeAvailable(googletest)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_BAK})
add_library(GTest::gmock ALIAS gmock)
endif()
endif()
#-------------------------------
# SOURCE FILES CONFIGURATION
#-------------------------------
set(UNITTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/unittests)
set(UNITTESTS_SRCS
${UNITTESTS_SOURCE_DIR}/sdbus-c++-unit-tests.cpp
${UNITTESTS_SOURCE_DIR}/Message_test.cpp
${UNITTESTS_SOURCE_DIR}/PollData_test.cpp
${UNITTESTS_SOURCE_DIR}/Types_test.cpp
${UNITTESTS_SOURCE_DIR}/TypeTraits_test.cpp
${UNITTESTS_SOURCE_DIR}/Connection_test.cpp
${UNITTESTS_SOURCE_DIR}/mocks/SdBusMock.h)
set(INTEGRATIONTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/integrationtests)
set(INTEGRATIONTESTS_SRCS
${INTEGRATIONTESTS_SOURCE_DIR}/DBusConnectionTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusGeneralTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusMethodsTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusAsyncMethodsTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusSignalsTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusPropertiesTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/DBusStandardInterfacesTests.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/Defs.h
${INTEGRATIONTESTS_SOURCE_DIR}/integrationtests-adaptor.h
${INTEGRATIONTESTS_SOURCE_DIR}/integrationtests-proxy.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestFixture.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestFixture.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/TestAdaptor.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestAdaptor.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/TestProxy.h
${INTEGRATIONTESTS_SOURCE_DIR}/TestProxy.cpp
${INTEGRATIONTESTS_SOURCE_DIR}/sdbus-c++-integration-tests.cpp)
set(PERFTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/perftests)
set(STRESSTESTS_CLIENT_SRCS
${PERFTESTS_SOURCE_DIR}/client.cpp
${PERFTESTS_SOURCE_DIR}/perftests-proxy.h)
set(STRESSTESTS_SERVER_SRCS
${PERFTESTS_SOURCE_DIR}/server.cpp
${PERFTESTS_SOURCE_DIR}/perftests-adaptor.h)
set(STRESSTESTS_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/stresstests)
set(STRESSTESTS_SRCS
${STRESSTESTS_SOURCE_DIR}/sdbus-c++-stress-tests.cpp
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-adaptor.h
${STRESSTESTS_SOURCE_DIR}/fahrenheit-thermometer-proxy.h
${STRESSTESTS_SOURCE_DIR}/celsius-thermometer-adaptor.h
${STRESSTESTS_SOURCE_DIR}/celsius-thermometer-proxy.h
${STRESSTESTS_SOURCE_DIR}/concatenator-adaptor.h
${STRESSTESTS_SOURCE_DIR}/concatenator-proxy.h)
#-------------------------------
# GENERAL COMPILER CONFIGURATION
#-------------------------------
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
#----------------------------------
# BUILD INFORMATION
#----------------------------------
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS})
target_compile_definitions(sdbus-c++-unit-tests PRIVATE
LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL}
SDBUS_HEADER=<${SDBUS_IMPL}/sd-bus.h>)
target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock)
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
target_compile_definitions(sdbus-c++-integration-tests PRIVATE
LIBSYSTEMD_VERSION=${SDBUSCPP_LIBSYSTEMD_VERSION}
SDBUS_${SDBUS_IMPL})
if(NOT SDBUS_IMPL STREQUAL "basu")
# Systemd::Libsystemd is included because integration tests use sd-event. Otherwise sdbus-c++ encapsulates and hides libsystemd.
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ Systemd::Libsystemd GTest::gmock)
else()
# sd-event implementation is not part of basu, so its integration tests will be skipped
target_link_libraries(sdbus-c++-integration-tests sdbus-c++ GTest::gmock)
endif()
if(SDBUSCPP_BUILD_PERF_TESTS OR SDBUSCPP_BUILD_STRESS_TESTS)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
if(SDBUSCPP_BUILD_PERF_TESTS)
message(STATUS "Building with performance tests")
add_executable(sdbus-c++-perf-tests-client ${STRESSTESTS_CLIENT_SRCS})
target_link_libraries(sdbus-c++-perf-tests-client sdbus-c++ Threads::Threads)
add_executable(sdbus-c++-perf-tests-server ${STRESSTESTS_SERVER_SRCS})
target_link_libraries(sdbus-c++-perf-tests-server sdbus-c++ Threads::Threads)
endif()
if(SDBUSCPP_BUILD_STRESS_TESTS)
message(STATUS "Building with stress tests")
add_executable(sdbus-c++-stress-tests ${STRESSTESTS_SRCS})
target_link_libraries(sdbus-c++-stress-tests sdbus-c++ Threads::Threads)
endif()
endif()
#----------------------------------
# INSTALLATION
#----------------------------------
if(SDBUSCPP_INSTALL)
include(GNUInstallDirs)
install(TARGETS sdbus-c++-unit-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(TARGETS sdbus-c++-integration-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
COMPONENT sdbus-c++-test)
if(SDBUSCPP_BUILD_PERF_TESTS)
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
COMPONENT sdbus-c++-test)
endif()
if(SDBUSCPP_BUILD_STRESS_TESTS)
install(TARGETS sdbus-c++-stress-tests DESTINATION ${SDBUSCPP_TESTS_INSTALL_PATH} COMPONENT sdbus-c++-test)
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf
DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d
COMPONENT sdbus-c++-test)
endif()
endif()
#----------------------------------
# RUNNING THE TESTS UPON BUILD
#----------------------------------
if(NOT CMAKE_CROSSCOMPILING)
add_test(NAME sdbus-c++-unit-tests COMMAND sdbus-c++-unit-tests)
add_test(NAME sdbus-c++-integration-tests COMMAND sdbus-c++-integration-tests)
endif()

View File

@ -0,0 +1,301 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusAsyncMethodsTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestFixture.h"
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
#include <unistd.h>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
using ::testing::Le;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::SizeIs;
using namespace std::chrono_literals;
using namespace sdbus::test;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(AsyncSdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut)
{
std::chrono::time_point<std::chrono::steady_clock> start;
try
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
start = std::chrono::steady_clock::now();
this->m_proxy->doOperationClientSideAsyncWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out
future.get();
FAIL() << "Expected sdbus::Error exception";
}
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out"));
auto measuredTimeout = std::chrono::steady_clock::now() - start;
ASSERT_THAT(measuredTimeout, Le(50ms));
}
catch(...)
{
FAIL() << "Expected sdbus::Error exception";
}
}
TYPED_TEST(AsyncSdbusTestObject, RunsServerSideAsynchronousMethodAsynchronously)
{
// Yeah, this is kinda timing-dependent test, but times should be safe...
std::mutex mtx;
std::vector<uint32_t> results;
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
auto call = [&](uint32_t param)
{
TestProxy proxy{SERVICE_NAME, OBJECT_PATH};
++startedCount;
while (!invoke) ;
auto result = proxy.doOperationAsync(param);
std::lock_guard<std::mutex> guard(mtx);
results.push_back(result);
};
std::thread invocations[]{std::thread{call, 1500}, std::thread{call, 1000}, std::thread{call, 500}};
while (startedCount != 3) ;
invoke = true;
std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); });
ASSERT_THAT(results, ElementsAre(500, 1000, 1500));
}
TYPED_TEST(AsyncSdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods)
{
std::atomic<size_t> resultCount{};
std::atomic<bool> invoke{};
std::atomic<int> startedCount{};
auto call = [&]()
{
TestProxy proxy{SERVICE_NAME, OBJECT_PATH};
++startedCount;
while (!invoke) ;
size_t localResultCount{};
for (size_t i = 0; i < 500; ++i)
{
auto result = proxy.doOperationAsync(i % 2);
if (result == (i % 2)) // Correct return value?
localResultCount++;
}
resultCount += localResultCount;
};
std::thread invocations[]{std::thread{call}, std::thread{call}, std::thread{call}};
while (startedCount != 3) ;
invoke = true;
std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); });
ASSERT_THAT(resultCount, Eq(1500));
}
TYPED_TEST(AsyncSdbusTestObject, RunsServerSideAsynchronousMethodWithLargeMessage)
{
std::map<int32_t, std::string> largeMap;
for (int32_t i = 0; i < 40'000; ++i)
largeMap.emplace(i, "This is string nr. " + std::to_string(i+1));
auto result1 = this->m_proxy->doOperationAsyncWithLargeData(0, largeMap); // Sends large map back in the context of the callback (event loop thread)
auto result2 = this->m_proxy->doOperationAsyncWithLargeData(500, largeMap); // Sends large map back outside the context of the event loop thread
ASSERT_THAT(result1, Eq(largeMap));
ASSERT_THAT(result2, Eq(largeMap));
}
TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(std::move(err)));
});
this->m_proxy->doOperationClientSideAsync(100);
ASSERT_THAT(future.get(), Eq(100));
}
TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFuture)
{
auto future = this->m_proxy->doOperationClientSideAsync(100, sdbus::with_future);
ASSERT_THAT(future.get(), Eq(100));
}
TYPED_TEST(AsyncSdbusTestObject, InvokesMethodAsynchronouslyOnClientSideWithFutureOnBasicAPILevel)
{
auto future = this->m_proxy->doOperationClientSideAsyncOnBasicAPILevel(100);
auto methodReply = future.get();
uint32_t returnValue{};
methodReply >> returnValue;
ASSERT_THAT(returnValue, Eq(100));
}
TYPED_TEST(AsyncSdbusTestObject, InvokesMethodWithLargeDataAsynchronouslyOnClientSideWithFuture)
{
std::map<int32_t, std::string> largeMap;
for (int32_t i = 0; i < 40'000; ++i)
largeMap.emplace(i, "This is string nr. " + std::to_string(i+1));
auto future = this->m_proxy->doOperationWithLargeDataClientSideAsync(largeMap, sdbus::with_future);
ASSERT_THAT(future.get(), Eq(largeMap));
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsPendingIfItIsInProgress)
{
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){});
auto call = this->m_proxy->doOperationClientSideAsync(100);
ASSERT_TRUE(call.isPending());
}
TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSide)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = this->m_proxy->doOperationClientSideAsync(100);
call.cancel();
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
}
TYPED_TEST(AsyncSdbusTestObject, CancelsPendingAsyncCallOnClientSideByDestroyingOwningSlot)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
{
auto slot = this->m_proxy->doOperationClientSideAsync(100, sdbus::return_slot);
// Now the slot is destroyed, cancelling the async call
}
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCancelled)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = this->m_proxy->doOperationClientSideAsync(100);
call.cancel();
ASSERT_FALSE(call.isPending());
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatAsyncCallIsNotPendingAfterItHasBeenCompleted)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t /*res*/, std::optional<sdbus::Error> /*err*/){ promise.set_value(1); });
auto call = this->m_proxy->doOperationClientSideAsync(0);
(void) future.get(); // Wait for the call to finish
ASSERT_TRUE(waitUntil([&call](){ return !call.isPending(); }));
}
TYPED_TEST(AsyncSdbusTestObject, AnswersThatDefaultConstructedAsyncCallIsNotPending)
{
sdbus::PendingAsyncCall call;
ASSERT_FALSE(call.isPending());
}
TYPED_TEST(AsyncSdbusTestObject, SupportsAsyncCallCopyAssignment)
{
sdbus::PendingAsyncCall call;
call = this->m_proxy->doOperationClientSideAsync(100);
ASSERT_TRUE(call.isPending());
}
TYPED_TEST(AsyncSdbusTestObject, ReturnsNonnullErrorWhenAsynchronousMethodCallFails)
{
std::promise<uint32_t> promise;
auto future = promise.get_future();
this->m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value(res);
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
this->m_proxy->doErroneousOperationClientSideAsync();
ASSERT_THROW(future.get(), sdbus::Error);
}
TYPED_TEST(AsyncSdbusTestObject, ThrowsErrorWhenClientSideAsynchronousMethodCallWithFutureFails)
{
auto future = this->m_proxy->doErroneousOperationClientSideAsync(sdbus::with_future);
ASSERT_THROW(future.get(), sdbus::Error);
}

View File

@ -0,0 +1,96 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusConnectionTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
// Own
#include "Defs.h"
// sdbus
#include <sdbus-c++/Error.h>
#include <sdbus-c++/IConnection.h>
// gmock
#include <gtest/gtest.h>
#include <gmock/gmock.h>
// STL
#include <thread>
#include <chrono>
using ::testing::Eq;
using namespace sdbus::test;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(Connection, CanBeDefaultConstructed)
{
ASSERT_NO_THROW(auto con = sdbus::createBusConnection());
}
TEST(Connection, CanRequestName)
{
auto connection = sdbus::createBusConnection();
// In case of system bus connection, requesting may throw as we need to allow that first through a config file in /etc/dbus-1/system.d
ASSERT_NO_THROW(connection->requestName(SERVICE_NAME))
<< "Perhaps you've forgotten to copy `org.sdbuscpp.integrationtests.conf` file to `/etc/dbus-1/system.d` directory before running the tests?";
}
TEST(SystemBusConnection, CannotRequestNonregisteredDbusName)
{
auto connection = sdbus::createSystemBusConnection();
sdbus::ServiceName notSupportedBusName{"some.random.not.supported.dbus.name"};
ASSERT_THROW(connection->requestName(notSupportedBusName), sdbus::Error);
}
TEST(Connection, CanReleaseRequestedName)
{
auto connection = sdbus::createBusConnection();
connection->requestName(SERVICE_NAME);
ASSERT_NO_THROW(connection->releaseName(SERVICE_NAME));
}
TEST(Connection, CannotReleaseNonrequestedName)
{
auto connection = sdbus::createBusConnection();
sdbus::ServiceName notAcquiredBusName{"some.random.unacquired.name"};
ASSERT_THROW(connection->releaseName(notAcquiredBusName), sdbus::Error);
}
TEST(Connection, CanEnterAndLeaveInternalEventLoop)
{
auto connection = sdbus::createBusConnection();
connection->requestName(SERVICE_NAME);
std::thread t([&](){ connection->enterEventLoop(); });
connection->leaveEventLoop();
t.join();
}

View File

@ -0,0 +1,184 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusGeneralTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "TestFixture.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
#include <unistd.h>
#include <variant>
using ::testing::ElementsAre;
using ::testing::Eq;
using namespace std::chrono_literals;
using namespace sdbus::test;
using namespace std::string_view_literals;
using ADirectConnection = TestFixtureWithDirectConnection;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TEST(AdaptorAndProxy, CanBeConstructedSuccessfully)
{
auto connection = sdbus::createBusConnection();
connection->requestName(SERVICE_NAME);
ASSERT_NO_THROW(TestAdaptor adaptor(*connection, OBJECT_PATH));
ASSERT_NO_THROW(TestProxy proxy(SERVICE_NAME, OBJECT_PATH));
connection->releaseName(SERVICE_NAME);
}
TEST(AProxy, DoesNotSupportMoveSemantics)
{
static_assert(!std::is_move_constructible_v<DummyTestProxy>);
static_assert(!std::is_move_assignable_v<DummyTestProxy>);
}
TEST(AnAdaptor, DoesNotSupportMoveSemantics)
{
static_assert(!std::is_move_constructible_v<DummyTestAdaptor>);
static_assert(!std::is_move_assignable_v<DummyTestAdaptor>);
}
TYPED_TEST(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
}, sdbus::return_slot);
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TYPED_TEST(AConnection, CanInstallMatchRuleAsynchronously)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
std::atomic<bool> matchRuleInstalled{false};
auto slot = this->s_proxyConnection->addMatchAsync( matchRule
, [&](sdbus::Message msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
}
, [&](sdbus::Message /*msg*/)
{
matchRuleInstalled = true;
}
, sdbus::return_slot );
EXPECT_TRUE(waitUntil(matchRuleInstalled));
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(matchingMessageReceived));
}
TYPED_TEST(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
}, sdbus::return_slot);
slot.reset();
this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s));
}
TYPED_TEST(AConnection, CanAddFloatingMatchRule)
{
auto matchRule = "sender='" + SERVICE_NAME + "',path='" + OBJECT_PATH + "'";
std::atomic<bool> matchingMessageReceived{false};
auto con = sdbus::createBusConnection();
con->enterEventLoopAsync();
auto callback = [&](sdbus::Message msg)
{
if(msg.getPath() == OBJECT_PATH)
matchingMessageReceived = true;
};
con->addMatch(matchRule, std::move(callback));
this->m_adaptor->emitSimpleSignal();
[[maybe_unused]] auto gotMessage = waitUntil(matchingMessageReceived, 2s);
assert(gotMessage);
matchingMessageReceived = false;
con.reset();
this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(matchingMessageReceived, 1s));
}
TYPED_TEST(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule)
{
auto matchRule = "type='signal',interface='" + INTERFACE_NAME + "',member='simpleSignal'";
std::atomic<size_t> numberOfMatchingMessages{};
auto slot = this->s_proxyConnection->addMatch(matchRule, [&](sdbus::Message msg)
{
if(msg.getMemberName() == "simpleSignal"sv)
numberOfMatchingMessages++;
}, sdbus::return_slot);
auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
this->m_adaptor->emitSignalWithMap({});
adaptor2->emitSimpleSignal();
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil([&](){ return numberOfMatchingMessages == 2; }));
ASSERT_FALSE(waitUntil([&](){ return numberOfMatchingMessages > 2; }, 1s));
}
// A simple direct connection test similar in nature to https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-bus/test-bus-server.c
TEST_F(ADirectConnection, CanBeUsedBetweenClientAndServer)
{
auto val = m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
m_adaptor->emitSimpleSignal();
// Make sure method call passes and emitted signal is received
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal));
}

View File

@ -0,0 +1,383 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusMethodsTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestFixture.h"
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
#include <unistd.h>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
using ::testing::Le;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::SizeIs;
using ::testing::NotNull;
using namespace std::chrono_literals;
using namespace std::string_literals;
using namespace sdbus::test;
namespace my {
struct Struct
{
int i;
std::string s;
std::vector<double> l;
friend bool operator==(const Struct &lhs, const Struct &rhs) = default;
};
}
SDBUSCPP_REGISTER_STRUCT(my::Struct, i, s, l);
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(SdbusTestObject, CallsEmptyMethodSuccessfully)
{
ASSERT_NO_THROW(this->m_proxy->noArgNoReturn());
}
TYPED_TEST(SdbusTestObject, CallsMethodsWithBaseTypesSuccessfully)
{
auto resInt = this->m_proxy->getInt();
ASSERT_THAT(resInt, Eq(INT32_VALUE));
auto multiplyRes = this->m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodsWithTuplesSuccessfully)
{
auto resTuple = this->m_proxy->getTuple();
ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE));
ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodsWithStructSuccessfully)
{
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> a{};
auto vectorRes = this->m_proxy->getInts16FromStruct(a);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{0})); // because second item is by default initialized to 0
sdbus::Struct<uint8_t, int16_t, double, std::string, std::vector<int16_t>> b{
UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE}
};
vectorRes = this->m_proxy->getInts16FromStruct(b);
ASSERT_THAT(vectorRes, Eq(std::vector<int16_t>{INT16_VALUE, INT16_VALUE, -INT16_VALUE}));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithVariantSuccessfully)
{
sdbus::Variant v{DOUBLE_VALUE};
sdbus::Variant variantRes = this->m_proxy->processVariant(v);
ASSERT_THAT(variantRes.get<int32_t>(), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithStdVariantSuccessfully)
{
std::variant<int32_t, double, std::string> v{DOUBLE_VALUE};
auto variantRes = this->m_proxy->processVariant(v);
ASSERT_THAT(std::get<int32_t>(variantRes), Eq(static_cast<int32_t>(DOUBLE_VALUE)));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccessfully)
{
std::vector<int32_t> x{-2, 0, 2};
sdbus::Struct<sdbus::Variant, sdbus::Variant> y{false, true};
std::map<int32_t, sdbus::Variant> mapOfVariants = this->m_proxy->getMapOfVariants(x, y);
decltype(mapOfVariants) res{ {-2, sdbus::Variant{false}}
, {0, sdbus::Variant{false}}
, {2, sdbus::Variant{true}}};
ASSERT_THAT(mapOfVariants[-2].get<bool>(), Eq(res[-2].get<bool>()));
ASSERT_THAT(mapOfVariants[0].get<bool>(), Eq(res[0].get<bool>()));
ASSERT_THAT(mapOfVariants[2].get<bool>(), Eq(res[2].get<bool>()));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithStructInStructSuccessfully)
{
auto val = this->m_proxy->getStructInStruct();
ASSERT_THAT(val.template get<0>(), Eq(STRING_VALUE));
ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithTwoStructsSuccessfully)
{
auto val = this->m_proxy->sumStructItems({1, 2}, {3, 4});
ASSERT_THAT(val, Eq(1 + 2 + 3 + 4));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithTwoVectorsSuccessfully)
{
auto val = this->m_proxy->sumArrayItems({1, 7}, {2, 3, 4});
ASSERT_THAT(val, Eq(1 + 7 + 2 + 3 + 4));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithSignatureSuccessfully)
{
auto resSignature = this->m_proxy->getSignature();
ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithObjectPathSuccessfully)
{
auto resObjectPath = this->m_proxy->getObjPath();
ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithUnixFdSuccessfully)
{
auto resUnixFd = this->m_proxy->getUnixFd();
ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithComplexTypeSuccessfully)
{
auto resComplex = this->m_proxy->getComplex();
ASSERT_THAT(resComplex.count(0), Eq(1));
}
TYPED_TEST(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag)
{
this->m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasMultiplyCalled));
ASSERT_THAT(this->m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully)
{
auto res = this->m_proxy->doOperationWithTimeout(500ms, (20ms).count()); // The operation will take 20ms, but the timeout is 500ms, so we are fine
ASSERT_THAT(res, Eq(20));
}
TYPED_TEST(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
{
auto start = std::chrono::steady_clock::now();
try
{
this->m_proxy->doOperationWithTimeout(1us, (1s).count()); // The operation will take 1s, but the timeout is 1us, so we should time out
FAIL() << "Expected sdbus::Error exception";
}
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply"));
ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Operation timed out", "Method call timed out"));
auto measuredTimeout = std::chrono::steady_clock::now() - start;
ASSERT_THAT(measuredTimeout, Le(50ms));
}
catch(...)
{
FAIL() << "Expected sdbus::Error exception";
}
}
TYPED_TEST(SdbusTestObject, CallsMethodThatThrowsError)
{
try
{
this->m_proxy->throwError();
FAIL() << "Expected sdbus::Error exception";
}
catch (const sdbus::Error& e)
{
ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.AccessDenied"));
ASSERT_THAT(e.getMessage(), Eq("A test error occurred (Operation not permitted)"));
}
catch(...)
{
FAIL() << "Expected sdbus::Error exception";
}
}
TYPED_TEST(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet)
{
ASSERT_NO_THROW(this->m_proxy->throwErrorWithNoReply());
ASSERT_TRUE(waitUntil(this->m_adaptor->m_wasThrowErrorCalled));
}
TYPED_TEST(SdbusTestObject, FailsCallingNonexistentMethod)
{
ASSERT_THROW(this->m_proxy->callNonexistentMethod(), sdbus::Error);
}
TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentInterface)
{
ASSERT_THROW(this->m_proxy->callMethodOnNonexistentInterface(), sdbus::Error);
}
TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentDestination)
{
TestProxy proxy(sdbus::ServiceName{"sdbuscpp.destination.that.does.not.exist"}, OBJECT_PATH);
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
TYPED_TEST(SdbusTestObject, FailsCallingMethodOnNonexistentObject)
{
TestProxy proxy(SERVICE_NAME, sdbus::ObjectPath{"/sdbuscpp/path/that/does/not/exist"});
ASSERT_THROW(proxy.getInt(), sdbus::Error);
}
TYPED_TEST(SdbusTestObject, CanReceiveSignalWhileMakingMethodCall)
{
this->m_proxy->emitTwoSimpleSignals();
EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
EXPECT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
}
TYPED_TEST(SdbusTestObject, CanSendAndReceiveDictionariesAsCustomStructsImplicitly)
{
// This test demonstrates that sdbus-c++ can send a SDBUSCPP_REGISTER_STRUCT-described struct as a dictionary of strings to variants,
// and that sdbus-c++ can automatically deserialize a dictionary of strings to variants into such a struct (instead of a map).
const my::Struct structSent{3545342, "hello"s, {3.14, 2.4568546}};
my::Struct structReceived;
this->m_proxy->getProxy().callMethod("returnDictionary")
.onInterface("org.sdbuscpp.integrationtests")
.withArguments(sdbus::as_dictionary(structSent))
.storeResultsTo(structReceived);
ASSERT_THAT(structReceived, Eq(structSent));
}
TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInMethodCallHandler)
{
this->m_proxy->doOperation(10); // This will save pointer to method call message on server side
ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull());
ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperation"));
}
TYPED_TEST(SdbusTestObject, ProvidesSerialInMethodCallAndMethodReplyMessage)
{
auto reply = this->m_proxy->doOperationOnBasicAPILevel(ANY_UNSIGNED_NUMBER);
ASSERT_THAT(this->m_proxy->m_methodCallMsg->getCookie(), Gt(0));
ASSERT_THAT(reply.getReplyCookie(), Eq(this->m_proxy->m_methodCallMsg->getCookie())); // Pairing method reply with method call message
}
TYPED_TEST(SdbusTestObject, CanAccessAssociatedMethodCallMessageInAsyncMethodCallHandler)
{
this->m_proxy->doOperationAsync(10); // This will save pointer to method call message on server side
ASSERT_THAT(this->m_adaptor->m_methodCallMsg, NotNull());
ASSERT_THAT(this->m_adaptor->m_methodName, Eq("doOperationAsync"));
}
TYPED_TEST(SdbusTestObject, CallsMethodWithLargeArgument)
{
std::map<int, std::string> collection;
//std::size_t totalSize{};
for (int i = 0; i < 400'000; i++)
{
collection[i] = ("This is a string of fifty characters. This is a string of fifty " + std::to_string(i));
//totalSize += sizeof(int) + collection[i].size();
}
//printf("Sending large message with collection of size %zu bytes\n", totalSize);
this->m_proxy->sendLargeMessage(collection);
}
TYPED_TEST(SdbusTestObject, CanSendCallsAndReceiveRepliesWithLargeData)
{
std::map<int32_t, std::string> largeMap;
for (int32_t i = 0; i < 40'000; ++i)
largeMap.emplace(i, "This is string nr. " + std::to_string(i+1));
auto returnedMap = this->m_proxy->doOperationWithLargeData(largeMap);
ASSERT_THAT(returnedMap, Eq(largeMap));
}
#if LIBSYSTEMD_VERSION>=240
TYPED_TEST(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239)
{
this->s_adaptorConnection->setMethodCallTimeout(5000000);
ASSERT_THAT(this->s_adaptorConnection->getMethodCallTimeout(), Eq(5000000));
}
#else
TYPED_TEST(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240)
{
ASSERT_THROW(this->s_adaptorConnection->setMethodCallTimeout(5000000), sdbus::Error);
ASSERT_THROW(this->s_adaptorConnection->getMethodCallTimeout(), sdbus::Error);
}
#endif
TYPED_TEST(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread)
{
auto proxy = std::make_unique<TestProxy>(SERVICE_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE));
}
TYPED_TEST(SdbusTestObject, CanRegisterAdditionalVTableDynamicallyAtAnyTime)
{
auto& object = this->m_adaptor->getObject();
sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"};
auto vtableSlot = object.addVTable( interfaceName
, { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; })
, sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) }
, sdbus::return_slot );
// The new remote vtable is registered as long as we keep vtableSlot, so remote method calls now should pass
auto proxy = sdbus::createLightWeightProxy(SERVICE_NAME, OBJECT_PATH);
int result{};
proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2).storeResultsTo(result);
ASSERT_THAT(result, Eq(8));
}
TYPED_TEST(SdbusTestObject, CanUnregisterAdditionallyRegisteredVTableAtAnyTime)
{
auto& object = this->m_adaptor->getObject();
sdbus::InterfaceName interfaceName{"org.sdbuscpp.integrationtests2"};
auto vtableSlot = object.addVTable( interfaceName
, { sdbus::registerMethod("add").implementedAs([](const int64_t& a, const double& b){ return a + b; })
, sdbus::registerMethod("subtract").implementedAs([](const int& a, const int& b){ return a - b; }) }
, sdbus::return_slot );
vtableSlot.reset(); // Letting the slot go means letting go the associated vtable registration
// No such remote D-Bus method under given interface exists anymore...
auto proxy = sdbus::createLightWeightProxy(SERVICE_NAME, OBJECT_PATH);
ASSERT_THROW(proxy->callMethod("subtract").onInterface(interfaceName).withArguments(10, 2), sdbus::Error);
}

View File

@ -0,0 +1,92 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusPropertiesTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestFixture.h"
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
#include <unistd.h>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::SizeIs;
using ::testing::NotNull;
using ::testing::Not;
using ::testing::IsEmpty;
using namespace std::chrono_literals;
using namespace sdbus::test;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(SdbusTestObject, ReadsReadOnlyPropertySuccessfully)
{
ASSERT_THAT(this->m_proxy->state(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, FailsWritingToReadOnlyProperty)
{
ASSERT_THROW(this->m_proxy->setStateProperty("new_value"), sdbus::Error);
}
TYPED_TEST(SdbusTestObject, WritesAndReadsReadWritePropertySuccessfully)
{
uint32_t newActionValue = 5678;
this->m_proxy->action(newActionValue);
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, CanAccessAssociatedPropertySetMessageInPropertySetHandler)
{
this->m_proxy->blocking(true); // This will save pointer to property get message on server side
ASSERT_THAT(this->m_adaptor->m_propertySetMsg, NotNull());
ASSERT_THAT(this->m_adaptor->m_propertySetSender, Not(IsEmpty()));
}
TYPED_TEST(SdbusTestObject, WritesAndReadsReadWriteVariantPropertySuccessfully)
{
sdbus::Variant newActionValue{5678};
this->m_proxy->actionVariant(newActionValue);
ASSERT_THAT(this->m_proxy->actionVariant().template get<int>(), Eq(5678));
}

View File

@ -0,0 +1,167 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file AdaptorAndProxy_test.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestFixture.h"
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <chrono>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::SizeIs;
using ::testing::NotNull;
using namespace std::chrono_literals;
using namespace sdbus::test;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(SdbusTestObject, EmitsSimpleSignalSuccessfully)
{
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
}
TYPED_TEST(SdbusTestObject, EmitsSimpleSignalToMultipleProxiesSuccessfully)
{
auto proxy1 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
auto proxy2 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal));
}
TYPED_TEST(SdbusTestObject, ProxyDoesNotReceiveSignalFromOtherBusName)
{
sdbus::ServiceName otherBusName{SERVICE_NAME + "2"};
auto connection2 = sdbus::createBusConnection(otherBusName);
auto adaptor2 = std::make_unique<TestAdaptor>(*connection2, OBJECT_PATH);
adaptor2->emitSimpleSignal();
ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
}
TYPED_TEST(SdbusTestObject, EmitsSignalWithMapSuccessfully)
{
this->m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}});
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("zero"));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("one"));
}
TYPED_TEST(SdbusTestObject, EmitsSignalWithLargeMapSuccessfully)
{
std::map<int32_t, std::string> largeMap;
for (int32_t i = 0; i < 20'000; ++i)
largeMap.emplace(i, "This is string nr. " + std::to_string(i+1));
this->m_adaptor->emitSignalWithMap(largeMap);
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithMap));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[0], Eq("This is string nr. 1"));
ASSERT_THAT(this->m_proxy->m_mapFromSignal[1], Eq("This is string nr. 2"));
}
TYPED_TEST(SdbusTestObject, EmitsSignalWithVariantSuccessfully)
{
double d = 3.14;
this->m_adaptor->emitSignalWithVariant(sdbus::Variant{d});
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithVariant));
ASSERT_THAT(this->m_proxy->m_variantFromSignal, DoubleEq(d));
}
TYPED_TEST(SdbusTestObject, EmitsSignalWithoutRegistrationSuccessfully)
{
this->m_adaptor->emitSignalWithoutRegistration({"platform", sdbus::Signature{"av"}});
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSignalWithSignature));
ASSERT_THAT(this->m_proxy->m_signatureFromSignal["platform"], Eq(sdbus::Signature{"av"}));
}
TYPED_TEST(SdbusTestObject, CanAccessAssociatedSignalMessageInSignalHandler)
{
this->m_adaptor->emitSimpleSignal();
waitUntil(this->m_proxy->m_gotSimpleSignal);
ASSERT_THAT(this->m_proxy->m_signalMsg, NotNull());
ASSERT_THAT(this->m_proxy->m_signalName, Eq(std::string("simpleSignal")));
}
TYPED_TEST(SdbusTestObject, UnregistersSignalHandler)
{
ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
}
TYPED_TEST(SdbusTestObject, UnregistersSignalHandlerForSomeProxies)
{
auto proxy1 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
auto proxy2 = std::make_unique<TestProxy>(*this->s_adaptorConnection, SERVICE_NAME, OBJECT_PATH);
ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(proxy1->m_gotSimpleSignal));
ASSERT_TRUE(waitUntil(proxy2->m_gotSimpleSignal));
ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
}
TYPED_TEST(SdbusTestObject, ReRegistersSignalHandler)
{
// unregister simple-signal handler
ASSERT_NO_THROW(this->m_proxy->unregisterSimpleSignalHandler());
this->m_adaptor->emitSimpleSignal();
ASSERT_FALSE(waitUntil(this->m_proxy->m_gotSimpleSignal, 1s));
// re-register simple-signal handler
ASSERT_NO_THROW(this->m_proxy->reRegisterSimpleSignalHandler());
this->m_adaptor->emitSimpleSignal();
ASSERT_TRUE(waitUntil(this->m_proxy->m_gotSimpleSignal));
}

View File

@ -0,0 +1,412 @@
/**
* (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland
* (C) 2016 - 2024 Stanislav Angelovic <stanislav.angelovic@protonmail.com>
*
* @file DBusStandardInterfacesTests.cpp
*
* Created on: Jan 2, 2017
* Project: sdbus-c++
* Description: High-level D-Bus IPC C++ library based on sd-bus
*
* This file is part of sdbus-c++.
*
* sdbus-c++ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* sdbus-c++ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with sdbus-c++. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestFixture.h"
#include "TestAdaptor.h"
#include "TestProxy.h"
#include "sdbus-c++/sdbus-c++.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <string>
#include <thread>
#include <tuple>
#include <chrono>
#include <fstream>
#include <future>
#include <unistd.h>
using ::testing::Eq;
using ::testing::DoubleEq;
using ::testing::Gt;
using ::testing::AnyOf;
using ::testing::ElementsAre;
using ::testing::SizeIs;
using namespace std::chrono_literals;
using namespace sdbus::test;
/*-------------------------------------*/
/* -- TEST CASES -- */
/*-------------------------------------*/
TYPED_TEST(SdbusTestObject, PingsViaPeerInterface)
{
ASSERT_NO_THROW(this->m_proxy->Ping());
}
TYPED_TEST(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
{
if (::access("/etc/machine-id", F_OK) == -1 &&
::access("/var/lib/dbus/machine-id", F_OK) == -1)
GTEST_SKIP() << "/etc/machine-id and /var/lib/dbus/machine-id files do not exist, GetMachineId() will not work";
ASSERT_NO_THROW(this->m_proxy->GetMachineId());
}
// TODO: Adjust expected xml and uncomment this test
//TYPED_TEST(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface)
//{
// ASSERT_THAT(this->m_proxy->Introspect(), Eq(this->m_adaptor->getExpectedXmlApiDescription()));
//}
TYPED_TEST(SdbusTestObject, GetsPropertyViaPropertiesInterface)
{
ASSERT_THAT(this->m_proxy->Get(INTERFACE_NAME, "state").template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface)
{
std::promise<std::string> promise;
auto future = promise.get_future();
this->m_proxy->GetAsync(INTERFACE_NAME, "state", [&](std::optional<sdbus::Error> err, sdbus::Variant value)
{
if (!err)
promise.set_value(value.get<std::string>());
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = this->m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future);
ASSERT_THAT(future.get().template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
}
TYPED_TEST(SdbusTestObject, SetsPropertyViaPropertiesInterface)
{
uint32_t newActionValue = 2345;
this->m_proxy->Set(INTERFACE_NAME, "action", sdbus::Variant{newActionValue});
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface)
{
uint32_t newActionValue = 2346;
std::promise<void> promise;
auto future = promise.get_future();
this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value();
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
ASSERT_NO_THROW(future.get());
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, CancelsAsynchronousPropertySettingViaPropertiesInterface)
{
uint32_t newActionValue = 2346;
std::promise<void> promise;
auto future = promise.get_future();
{
auto slot = this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](std::optional<sdbus::Error> err)
{
if (!err)
promise.set_value();
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
}, sdbus::return_slot);
// Now the slot is destroyed, cancelling the async call
}
ASSERT_THAT(future.wait_for(300ms), Eq(std::future_status::timeout));
}
TYPED_TEST(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
{
uint32_t newActionValue = 2347;
auto future = this->m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future);
ASSERT_NO_THROW(future.get());
ASSERT_THAT(this->m_proxy->action(), Eq(newActionValue));
}
TYPED_TEST(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
{
const auto properties = this->m_proxy->GetAll(INTERFACE_NAME);
ASSERT_THAT(properties, SizeIs(4));
EXPECT_THAT(properties.at(STATE_PROPERTY).template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at(ACTION_VARIANT_PROPERTY).template get<sdbus::Variant>().template get<std::string>(), Eq(DEFAULT_ACTION_VARIANT_VALUE));
EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface)
{
std::promise<std::map<sdbus::PropertyName, sdbus::Variant>> promise;
auto future = promise.get_future();
this->m_proxy->GetAllAsync(INTERFACE_NAME, [&](std::optional<sdbus::Error> err, std::map<sdbus::PropertyName, sdbus::Variant> value)
{
if (!err)
promise.set_value(std::move(value));
else
promise.set_exception(std::make_exception_ptr(*std::move(err)));
});
const auto properties = future.get();
ASSERT_THAT(properties, SizeIs(4));
EXPECT_THAT(properties.at(STATE_PROPERTY).get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at(ACTION_PROPERTY).get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at(ACTION_VARIANT_PROPERTY).template get<sdbus::Variant>().template get<std::string>(), Eq(DEFAULT_ACTION_VARIANT_VALUE));
EXPECT_THAT(properties.at(BLOCKING_PROPERTY).get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture)
{
auto future = this->m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future);
auto properties = future.get();
ASSERT_THAT(properties, SizeIs(4));
EXPECT_THAT(properties.at(STATE_PROPERTY).template get<std::string>(), Eq(DEFAULT_STATE_VALUE));
EXPECT_THAT(properties.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(properties.at(ACTION_VARIANT_PROPERTY).template get<sdbus::Variant>().template get<std::string>(), Eq(DEFAULT_ACTION_VARIANT_VALUE));
EXPECT_THAT(properties.at(BLOCKING_PROPERTY).template get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
}
TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
{
std::atomic<bool> signalReceived{false};
this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName
, const std::map<sdbus::PropertyName, sdbus::Variant>& changedProperties
, const std::vector<sdbus::PropertyName>& /*invalidatedProperties*/ )
{
EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME));
EXPECT_THAT(changedProperties, SizeIs(1));
EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get<bool>(), Eq(!DEFAULT_BLOCKING_VALUE));
signalReceived = true;
};
this->m_proxy->blocking(!DEFAULT_BLOCKING_VALUE);
this->m_proxy->action(DEFAULT_ACTION_VALUE*2);
this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {BLOCKING_PROPERTY});
ASSERT_TRUE(waitUntil(signalReceived));
}
TYPED_TEST(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties)
{
std::atomic<bool> signalReceived{false};
this->m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const sdbus::InterfaceName& interfaceName
, const std::map<sdbus::PropertyName, sdbus::Variant>& changedProperties
, const std::vector<sdbus::PropertyName>& invalidatedProperties )
{
EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME));
EXPECT_THAT(changedProperties, SizeIs(1));
EXPECT_THAT(changedProperties.at(BLOCKING_PROPERTY).get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
ASSERT_THAT(invalidatedProperties, SizeIs(1));
EXPECT_THAT(invalidatedProperties[0], Eq("action"));
signalReceived = true;
};
this->m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME);
ASSERT_TRUE(waitUntil(signalReceived));
}
TYPED_TEST(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects)
{
this->m_adaptor.reset();
const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects();
ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0));
}
TYPED_TEST(SdbusTestObject, GetsManagedObjectsSuccessfully)
{
auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
const auto objectsInterfacesAndProperties = this->m_objectManagerProxy->GetManagedObjects();
ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2));
EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH)
.at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME})
.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
EXPECT_THAT(objectsInterfacesAndProperties.at(OBJECT_PATH_2)
.at(sdbus::InterfaceName{org::sdbuscpp::integrationtests_adaptor::INTERFACE_NAME})
.at(ACTION_PROPERTY).template get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
}
TYPED_TEST(SdbusTestObject, GetsManagedObjectsAsynchronously)
{
std::promise<size_t> promise;
auto future = promise.get_future();
auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
this->m_objectManagerProxy->GetManagedObjectsAsync([&](std::optional<sdbus::Error> /*err*/, const std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>>& objectsInterfacesAndProperties)
{
promise.set_value(objectsInterfacesAndProperties.size());
});
ASSERT_THAT(future.get(), Eq(2));
}
TYPED_TEST(SdbusTestObject, GetsManagedObjectsAsynchronouslyViaSlotReturningOverload)
{
std::promise<size_t> promise;
auto future = promise.get_future();
auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
auto slot = this->m_objectManagerProxy->GetManagedObjectsAsync([&](std::optional<sdbus::Error> /*err*/, const std::map<sdbus::ObjectPath, std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>>& objectsInterfacesAndProperties)
{
promise.set_value(objectsInterfacesAndProperties.size());
}, sdbus::return_slot);
ASSERT_THAT(future.get(), Eq(2));
}
TYPED_TEST(SdbusTestObject, GetsManagedObjectsAsynchronouslyViaFutureOverload)
{
auto adaptor2 = std::make_unique<TestAdaptor>(*this->s_adaptorConnection, OBJECT_PATH_2);
auto future = this->m_objectManagerProxy->GetManagedObjectsAsync(sdbus::with_future);
ASSERT_THAT(future.get().size(), Eq(2));
}
TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces)
{
std::atomic<bool> signalReceived{false};
this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
EXPECT_THAT(interfacesAndProperties, SizeIs(1));
EXPECT_THAT(interfacesAndProperties.count(INTERFACE_NAME), Eq(1));
#if LIBSYSTEMD_VERSION<=244
// Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(4));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_VARIANT_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#else
// Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only
// for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal.
// So in this specific instance, `action' property is no more added to the list.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_VARIANT_PROPERTY));
#endif
signalReceived = true;
};
this->m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME});
ASSERT_TRUE(waitUntil(signalReceived));
}
TYPED_TEST(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces)
{
std::atomic<bool> signalReceived{false};
this->m_objectManagerProxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::map<sdbus::InterfaceName, std::map<sdbus::PropertyName, sdbus::Variant>>& interfacesAndProperties )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
EXPECT_THAT(interfacesAndProperties, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
#if LIBSYSTEMD_VERSION<=244
// Up to sd-bus v244, all properties are added to the list, i.e. `state', `action', and `blocking' in this case.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(4));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_VARIANT_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
#else
// Since v245 sd-bus does not add to the InterfacesAdded signal message the values of properties marked only
// for invalidation on change, which makes the behavior consistent with the PropertiesChangedSignal.
// So in this specific instance, `action' property is no more added to the list.
EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(STATE_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(BLOCKING_PROPERTY));
EXPECT_TRUE(interfacesAndProperties.at(INTERFACE_NAME).count(ACTION_VARIANT_PROPERTY));
#endif
signalReceived = true;
};
this->m_adaptor->emitInterfacesAddedSignal();
ASSERT_TRUE(waitUntil(signalReceived));
}
TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces)
{
std::atomic<bool> signalReceived{false};
this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::vector<sdbus::InterfaceName>& interfaces )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
ASSERT_THAT(interfaces, SizeIs(1));
EXPECT_THAT(interfaces[0], Eq(INTERFACE_NAME));
signalReceived = true;
};
this->m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME});
ASSERT_TRUE(waitUntil(signalReceived));
}
TYPED_TEST(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces)
{
std::atomic<bool> signalReceived{false};
this->m_objectManagerProxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath
, const std::vector<sdbus::InterfaceName>& interfaces )
{
EXPECT_THAT(objectPath, Eq(OBJECT_PATH));
#if LIBSYSTEMD_VERSION<=250
ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces
#else
// Since systemd v251, ObjectManager standard interface is not listed among the interfaces
// if the object does not have object manager functionality explicitly enabled.
ASSERT_THAT(interfaces, SizeIs(4)); // INTERFACE_NAME + 3 standard interfaces
#endif
signalReceived = true;
};
this->m_adaptor->emitInterfacesRemovedSignal();
ASSERT_TRUE(waitUntil(signalReceived));
}

Some files were not shown because too many files have changed in this diff Show More