forked from Kistler-Group/sdbus-cpp
Compare commits
24 Commits
v1.3.0
...
refactor/t
Author | SHA1 | Date | |
---|---|---|---|
e75337bd23 | |||
28921ad424 | |||
721f583db1 | |||
47a84ab889 | |||
d80483cdc0 | |||
934d51fa8a | |||
fb9e4ae371 | |||
6e348f3910 | |||
f50e4676fe | |||
1aa30e3a20 | |||
e2b3e98374 | |||
9490b3351f | |||
9da18aec25 | |||
b7b454ba38 | |||
f420b216aa | |||
b482cd6d08 | |||
aac7e590ea | |||
0ad2553417 | |||
621b3d0862 | |||
189fd23744 | |||
cfb71bd6cf | |||
c437b4d508 | |||
1e2d13a04a | |||
290078d6af |
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@ -48,19 +48,19 @@ jobs:
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O0 -g -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
|
||||
- name: configure-release
|
||||
if: matrix.build == 'shared-libsystemd' && matrix.os == 'ubuntu-22.04'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
|
||||
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_CXX_FLAGS="-O3 -DNDEBUG -W -Wextra -Wall -Wnon-virtual-dtor -Werror" -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON ..
|
||||
- name: configure-with-embedded-libsystemd
|
||||
if: matrix.build == 'embedded-static-libsystemd'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=244 ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_VERBOSE_MAKEFILE=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON -DBUILD_CODE_GEN=ON -DBUILD_LIBSYSTEMD=ON -DLIBSYSTEMD_VERSION=244 ..
|
||||
- name: make
|
||||
run: |
|
||||
cd build
|
||||
@ -84,3 +84,22 @@ jobs:
|
||||
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@v2
|
||||
- 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 -DBUILD_CODE_GEN=ON -DBUILD_TESTS=ON -DINSTALL_TESTS=ON -DENABLE_PERF_TESTS=ON -DENABLE_STRESS_TESTS=ON
|
||||
cmake --build _build
|
||||
cmake --install _build
|
||||
pkg install -y dbus
|
||||
service dbus onestart
|
||||
ctest --output-on-failure --test-dir _build
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
project(sdbus-c++ VERSION 1.3.0 LANGUAGES C CXX)
|
||||
project(sdbus-c++ VERSION 1.4.0 LANGUAGES C CXX)
|
||||
|
||||
include(GNUInstallDirs) # Installation directories for `install` command and pkgconfig file
|
||||
|
||||
@ -12,7 +12,8 @@ include(GNUInstallDirs) # Installation directories for `install` command and pkg
|
||||
# PERFORMING CHECKS & PREPARING THE DEPENDENCIES
|
||||
#-------------------------------
|
||||
|
||||
set(LIBSYSTEMD "libsystemd")
|
||||
set(LIBSYSTEMD_IMPL "systemd")
|
||||
set(LIBSYSTEMD_LIB "libsystemd")
|
||||
|
||||
option(BUILD_LIBSYSTEMD "Build libsystemd static library and incorporate it into libsdbus-c++" OFF)
|
||||
|
||||
@ -23,9 +24,17 @@ if(NOT BUILD_LIBSYSTEMD)
|
||||
message(WARNING "libsystemd not found, checking for libelogind instead")
|
||||
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL libelogind>=236)
|
||||
if(TARGET PkgConfig::Systemd)
|
||||
set(LIBSYSTEMD "libelogind")
|
||||
set(LIBSYSTEMD_IMPL "elogind")
|
||||
set(LIBSYSTEMD_LIB "libelogind")
|
||||
string(REPLACE "." ";" VERSION_LIST ${Systemd_VERSION})
|
||||
list(GET VERSION_LIST 0 Systemd_VERSION)
|
||||
else()
|
||||
message(WARNING "libelogind not found, checking for basu instead")
|
||||
pkg_check_modules(Systemd IMPORTED_TARGET GLOBAL basu)
|
||||
set(LIBSYSTEMD_IMPL "basu")
|
||||
set(LIBSYSTEMD_LIB "basu")
|
||||
# https://git.sr.ht/~emersion/basu/commit/d4d185d29a26
|
||||
set(Systemd_VERSION "240")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT TARGET PkgConfig::Systemd)
|
||||
@ -109,18 +118,22 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(SDBUSCPP_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
|
||||
set(SDBUSCPP_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
# We promote BUILD_SHARED_LIBS flags to (global) option only if we are the main project
|
||||
# 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()
|
||||
|
||||
# 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=${LIBSYSTEMD_VERSION})
|
||||
target_compile_definitions(sdbus-c++-objlib PRIVATE
|
||||
BUILD_LIB=1
|
||||
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
|
||||
SDBUS_${LIBSYSTEMD_IMPL}
|
||||
SDBUS_HEADER=<${LIBSYSTEMD_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(DEFINED BUILD_SHARED_LIBS)
|
||||
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS})
|
||||
if(BUILD_SHARED_LIBS)
|
||||
set_target_properties(sdbus-c++-objlib PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
if(BUILD_LIBSYSTEMD)
|
||||
add_dependencies(sdbus-c++-objlib LibsystemdBuildProject)
|
||||
@ -159,7 +172,7 @@ install(TARGETS ${EXPORT_SET}
|
||||
# TESTS
|
||||
#----------------------------------
|
||||
|
||||
option(BUILD_TESTS "Build and install tests (default OFF)" OFF)
|
||||
option(BUILD_TESTS "Build tests (default OFF)" OFF)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
message(STATUS "Building with tests")
|
||||
@ -208,6 +221,7 @@ endif()
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
|
||||
install(EXPORT sdbus-c++-targets
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/sdbus-c++
|
||||
NAMESPACE SDBusCpp::
|
||||
@ -226,6 +240,7 @@ if(BUILD_SHARED_LIBS AND (BUILD_LIBSYSTEMD OR Systemd_LINK_LIBRARIES MATCHES "/l
|
||||
else()
|
||||
set(PKGCONFIG_REQS "")
|
||||
endif()
|
||||
set(PKGCONFIG_DEPS ${LIBSYSTEMD_LIB})
|
||||
configure_file(pkgconfig/sdbus-c++.pc.in pkgconfig/sdbus-c++.pc @ONLY)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/sdbus-c++.pc
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT dev)
|
||||
|
@ -232,3 +232,10 @@ v1.3.0
|
||||
- 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
|
||||
|
@ -50,9 +50,13 @@ $ sudo cmake --build . --target install
|
||||
|
||||
Option for building sdbus-c++ stress tests. Default value: `OFF`.
|
||||
|
||||
* `INSTALL_TESTS` [boolean]
|
||||
|
||||
Option for installing tests that were built. Default value: `OFF`.
|
||||
|
||||
* `TESTS_INSTALL_PATH` [string]
|
||||
|
||||
Path where the test binaries shall get installed. Default value: `/opt/test/bin`.
|
||||
Path where the test binaries shall get installed. Default value: `${CMAKE_INSTALL_PREFIX}/tests/sdbus-c++` (previously: `/opt/test/bin`).
|
||||
|
||||
* `BUILD_LIBSYSTEMD` [boolean]
|
||||
|
||||
@ -82,7 +86,7 @@ Dependencies
|
||||
------------
|
||||
|
||||
* `C++17` - the library uses C++17 features.
|
||||
* `libsystemd` - systemd library containing sd-bus implementation. This library is part of systemd. Systemd at least v236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-dependency) for more information.)
|
||||
* `libsystemd`/`libelogind`/`basu` - libraries containing sd-bus implementation that sdbus-c++ is written around. In case of `libsystemd` and `libelogind`, version >= 236 is needed. (In case you have a non-systemd environment, don't worry, see [Solving libsystemd dependency](docs/using-sdbus-c++.md#solving-libsystemd-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 xml2cpp code generator (`BUILD_CODE_GEN` option is ON).
|
||||
|
@ -21,7 +21,8 @@ Using sdbus-c++ library
|
||||
16. [Standard D-Bus interfaces](#standard-d-bus-interfaces)
|
||||
17. [Representing D-Bus Types in sdbus-c++](#representing-d-bus-types-in-sdbus-c)
|
||||
18. [Support for match rules](#support-for-match-rules)
|
||||
19. [Conclusion](#conclusion)
|
||||
19. [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections)
|
||||
20. [Conclusion](#conclusion)
|
||||
|
||||
Introduction
|
||||
------------
|
||||
@ -74,13 +75,19 @@ add_custom_command(
|
||||
Solving libsystemd dependency
|
||||
-----------------------------
|
||||
|
||||
sdbus-c++ depends on libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd) and that contains underlying sd-bus implementation.
|
||||
sdbus-c++ depends on sd-bus API, which is implemented in libsystemd, a C library that is part of [systemd](https://github.com/systemd/systemd).
|
||||
|
||||
Minimum required libsystemd shared library version is 0.20.0 (which corresponds to minimum systemd version 236).
|
||||
|
||||
If your target Linux distribution is already based on systemd ecosystem of version 236 and higher, then there is no additional effort, just make sure you have corresponding systemd header files available (provided by `libsystemd-dev` package on Debian/Ubuntu, for example), and you may go on building sdbus-c++ seamlessly.
|
||||
|
||||
However, sdbus-c++ can perfectly be used in non-systemd environments as well. There are two ways to approach this:
|
||||
However, sdbus-c++ can perfectly be used in non-systemd environments as well. If `libsystemd` is not found in the system when configuring sdbus-c++, then
|
||||
|
||||
1. sdbus-c++ will try to find `libelogind`, which is an extracted logind from original systemd containing sd-bus implementation. If not found, then
|
||||
|
||||
2. sdbus-c++ will try to find `basu`, which is just sd-bus implementation extracted from systemd.
|
||||
|
||||
On systems where neither of these is available, we can build sd-bus as a shared lib manually or we can (conveniently) instruct sdbus-c++ to build and integrate sd-bus into itself for us.
|
||||
|
||||
### Building and distributing libsystemd as a shared library yourself
|
||||
|
||||
@ -100,7 +107,7 @@ $ ninja libsystemd.so.0.26.0 # or another version number depending which system
|
||||
|
||||
### Building and distributing libsystemd as part of sdbus-c++
|
||||
|
||||
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Be sure to check out the systemd documentation for the Also, when distributing, make sure these dependency libraries are installed on the production machine.
|
||||
sdbus-c++ provides `BUILD_LIBSYSTEMD` configuration option. When turned on, sdbus-c++ will automatically download and build libsystemd as a static library and make it an opaque part of sdbus-c++ shared library for you. This is the most convenient and effective approach to build, distribute and use sdbus-c++ as a self-contained, systemd-independent library in non-systemd environments. Just make sure your build machine has all dependencies needed by libsystemd build process. That includes, among others, `meson`, `ninja`, `git`, `gperf`, and -- primarily -- libraries and library headers for `libmount`, `libcap` and `librt` (part of glibc). Also, when distributing, make sure these dependency libraries are installed on the production machine.
|
||||
|
||||
You may additionally set the `LIBSYSTEMD_VERSION` configuration flag to fine-tune the version of systemd to be taken in. (The default value is 242).
|
||||
|
||||
@ -387,6 +394,27 @@ Subsequently, we invoke two RPC calls to object's `concatenate()` method. We cre
|
||||
|
||||
Please note that we can create and destroy D-Bus object proxies dynamically, at any time during runtime, even when they share a common D-Bus connection and there is an active event loop upon the connection. So managing D-Bus object proxies' lifecycle (creating and destroying D-Bus object proxies) is completely thread-safe.
|
||||
|
||||
### Opening bus connections in sdbus-c++
|
||||
|
||||
There are several factory methods to create a bus connection object in sdbus-c++:
|
||||
|
||||
* `createConnection()` - opens a connection to the system bus
|
||||
* `createConnection(const std::string& name)` - opens a connection with the given name to the system bus
|
||||
* `createDefaultBusConnection()` - opens a connection to the session bus when in a user context, and a connection to the system bus, otherwise
|
||||
* `createDefaultBusConnection(const std::string& name)` - opens a connection with the given name to the session bus when in a user context, and a connection with the given name to the system bus, otherwise
|
||||
* `createSystemBusConnection()` - opens a connection to the system bus
|
||||
* `createSystemBusConnection(const std::string& name)` - opens a connection with the given name to the system bus
|
||||
* `createSessionBusConnection()` - opens a connection to the session bus
|
||||
* `createSessionBusConnection(const std::string& name)` - opens a connection with the given name to the session bus
|
||||
* `createSessionBusConnectionWithAddress(const std::string& address)` - opens a connection to the session bus at a custom address
|
||||
* `createRemoteSystemBusConnection(const std::string& host)` - opens a connection to the system bus on a remote host using ssh
|
||||
* `createDirectBusConnection(const std::string& address)` - opens direct D-Bus connection at a custom address (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
|
||||
* `createDirectBusConnection(int fd)` - opens direct D-Bus connection at the given file descriptor (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
|
||||
* `createServerBus(int fd)` - opens direct D-Bus connection at the given file descriptor as a server (see [Using direct (peer-to-peer) D-Bus connections](#using-direct-peer-to-peer-d-bus-connections))
|
||||
* `createBusConnection(sd_bus *bus)` - creates a connection directly from the underlying sd_bus connection instance (which has been created and set up upfront directly through sd-bus API).
|
||||
|
||||
For more information, peek into [`IConnection.h`](/include/sdbus-c++/IConnection.h) where these functions are declared and documented.
|
||||
|
||||
### Working with D-Bus connections in sdbus-c++
|
||||
|
||||
The design of D-Bus connections in sdbus-c++ allows for certain flexibility and enables users to choose simplicity over scalability or scalability (at a finer granularity of user's choice) at the cost of slightly decreased simplicity.
|
||||
@ -564,6 +592,7 @@ We recommend that sdbus-c++ users prefer the convenience API to the lower level,
|
||||
> assert(e->getMessage() == "Failed to deserialize a int32 value");
|
||||
> }
|
||||
> ```
|
||||
> Signature mismatch in signal handlers is probably the most common reason why signals are not received in the client, while we can see them on the bus with `dbus-monitor`. Use `const sdbus::Error*`-based callback variant and inspect the error to check if that's the cause of such problems.
|
||||
|
||||
> **_Tip_:** When registering a D-Bus object, we can additionally provide names of input and output parameters of its methods and names of parameters of its signals. When the object is introspected, these names are listed in the resulting introspection XML, which improves the description of object's interfaces:
|
||||
> ```c++
|
||||
@ -1256,9 +1285,67 @@ Annotate the element with `org.freedesktop.DBus.Method.Timeout` in order to spec
|
||||
Using D-Bus properties
|
||||
----------------------
|
||||
|
||||
sdbus-c++ provides functionality for convenient working with D-Bus properties, on both convenience and generated code API level.
|
||||
|
||||
### Convenience API
|
||||
|
||||
Let's say a remote D-Bus object provides property `status` of type `u` under interface `org.sdbuscpp.Concatenator`.
|
||||
|
||||
#### Reading a property
|
||||
|
||||
We read property value easily through `IProxy::getProperty()` method:
|
||||
|
||||
```c++
|
||||
uint32_t status = proxy->getProperty("status").onInterface("org.sdbuscpp.Concatenator");
|
||||
```
|
||||
|
||||
Getting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::getPropertyAsync()` method:
|
||||
|
||||
```c++
|
||||
// Callback-based method:
|
||||
auto callback = [](const sdbus::Error* err, sdbus::Variant value)
|
||||
{
|
||||
std::cout << "Got property value: " << value.get<uint32_t>() << std::endl;
|
||||
};
|
||||
uint32_t status = proxy->getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").uponReplyInvoke(std::move(callback));
|
||||
// Future-based method:
|
||||
std::future<sdbus::Variant> statusFuture = object.getPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
|
||||
...
|
||||
std::cout << "Got property value: " << statusFuture.get().get<uint32_t>() << std::endl;
|
||||
```
|
||||
|
||||
More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
|
||||
|
||||
#### Writing a property
|
||||
|
||||
Writing a property is equally simple, through `IProxy::setProperty()`:
|
||||
|
||||
```c++
|
||||
uint32_t status = ...;
|
||||
proxy->setProperty("status").onInterface("org.sdbuscpp.Concatenator").toValue(status);
|
||||
```
|
||||
|
||||
Setting a property in asynchronous manner is also possible, in both callback-based and future-based way, by calling `IProxy::setPropertyAsync()` method:
|
||||
|
||||
```c++
|
||||
// Callback-based method:
|
||||
auto callback = [](const sdbus::Error* err) { /*... Error handling in case err is non-null...*/ };
|
||||
uint32_t status = proxy->setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").toValue(status).uponReplyInvoke(std::move(callback));
|
||||
// Future-based method:
|
||||
std::future<void> statusFuture = object.setPropertyAsync("status").onInterface("org.sdbuscpp.Concatenator").getResultAsFuture();
|
||||
```
|
||||
|
||||
More information on `error` callback handler parameter, on behavior of `future` in erroneous situations, can be found in section [Asynchronous client-side methods](#asynchronous-client-side-methods).
|
||||
|
||||
#### Getting all properties
|
||||
|
||||
In a very analogous way, with both synchronous and asynchronous options, it's possible to read all properties of an object under given interface at once. `IProxy::getAllProperties()` is what you're looking for.
|
||||
|
||||
### Generated bindings API
|
||||
|
||||
Defining and working with D-Bus properties using XML description is quite easy.
|
||||
|
||||
### Defining a property in the IDL
|
||||
#### Defining a property in the IDL
|
||||
|
||||
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’.
|
||||
|
||||
@ -1276,7 +1363,9 @@ An example of a read-write property `status`:
|
||||
</node>
|
||||
```
|
||||
|
||||
### Generated C++ bindings
|
||||
The property may also have annotations. In addition to standard annotations defined in D-Bus specification, there are sdbus-c++-specific ones, discussed further below.
|
||||
|
||||
#### Generated C++ bindings
|
||||
|
||||
This is how generated adaptor and proxy classes would look like with the read-write `status` property. The adaptor:
|
||||
|
||||
@ -1329,7 +1418,60 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
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.
|
||||
When implementing the adaptor, we simply need to provide the body for the `status` getter and setter methods by overriding them. Then in the proxy, we just call them.
|
||||
|
||||
#### Client-side asynchronous properties
|
||||
|
||||
We can mark the property so that the generator generates either asynchronous variant of getter method, or asynchronous variant of setter method, or both. Annotations names are `org.freedesktop.DBus.Property.Get.Async`, or `org.freedesktop.DBus.Property.Set.Async`, respectively. Their values must be set to `client`.
|
||||
|
||||
In addition, we can choose through annotations `org.freedesktop.DBus.Property.Get.Async.ClientImpl`, or `org.freedesktop.DBus.Property.Set.Async.ClientImpl`, respectively, whether a callback-based or future-based variant will be generated. The concept is analogous to the one for asynchronous D-Bus methods described above in this document.
|
||||
|
||||
The callback-based method will generate a pure virtual function `On<PropertyName>Property[Get|Set]Reply()`, which must be overridden by the derived class.
|
||||
|
||||
For example, this description:
|
||||
|
||||
```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">
|
||||
<annotation name="org.freedesktop.DBus.Property.Get.Async" value="client"/>
|
||||
<annotation name="org.freedesktop.DBus.Property.Get.Async.ClientImpl" value="callback"/>
|
||||
</property>
|
||||
<!--...-->
|
||||
</interface>
|
||||
</node>
|
||||
```
|
||||
|
||||
will get generated into this C++ code on client side:
|
||||
|
||||
```cpp
|
||||
class PropertyProvider_proxy
|
||||
{
|
||||
/*...*/
|
||||
|
||||
virtual void onStatusPropertyGetReply(const uint32_t& value, const sdbus::Error* error) = 0;
|
||||
|
||||
public:
|
||||
// getting the property value
|
||||
sdbus::PendingAsyncCall status()
|
||||
{
|
||||
return object_->getPropertyAsync("status").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value){ this->onActionPropertyGetReply(value.get<uint32_t>(), error); });
|
||||
}
|
||||
|
||||
// setting the property value
|
||||
void status(const uint32_t& value)
|
||||
{
|
||||
object_->setProperty("status").onInterface(INTERFACE_NAME).toValue(value);
|
||||
}
|
||||
|
||||
/*...*/
|
||||
};
|
||||
```
|
||||
|
||||
In addition to custom generated code for getting/setting properties, `org.freedesktop.DBus.Properties` standard D-Bus interface, implemented through pre-defined `sdbus::Properties_proxy` in `sdbus-c++/StandardInterfaces.h`, can also be used for reading/writing properties. See next section.
|
||||
|
||||
Standard D-Bus interfaces
|
||||
-------------------------
|
||||
@ -1398,10 +1540,10 @@ The above mapping between D-Bus and C++ types is what sdbus-c++ provides by defa
|
||||
|
||||
We need two things to do that:
|
||||
|
||||
* implement `sdbus::Message` insertion and extraction operators, so sdbus-c++ knows how to serialize/deserialize our custom type,
|
||||
* implement `sdbus::Message` insertion (serialization) and extraction (deserialization) operators, so sdbus-c++ knows how to serialize/deserialize our custom type,
|
||||
* specialize `sdbus::signature_of` template for our custom type, so sdbus-c++ knows the mapping to D-Bus type and other necessary information about our type.
|
||||
|
||||
Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension:
|
||||
Say, we would like to represent D-Bus arrays as `std::list`s in our application. Since sdbus-c++ comes with pre-defined support for `std::vector`s, `std::array`s and `std::span`s as D-Bus array representations, we have to provide an extension. To implement message serialization and deserialization functions for `std::list`, we can simply copy the sdbus-c++ implementation of these functions for `std::vector`, and simply adjust for `std::list`. Then we provide `signature_of` specialization, again written on terms of one specialized for `std::vector`:
|
||||
|
||||
```c++
|
||||
#include <list>
|
||||
@ -1454,12 +1596,11 @@ template <typename _Element, typename _Allocator>
|
||||
struct sdbus::signature_of<std::list<_Element, _Allocator>>
|
||||
: sdbus::signature_of<std::vector<_Element, _Allocator>>
|
||||
{};
|
||||
};
|
||||
```
|
||||
|
||||
Then we can simply use `std::list`s, serialize/deserialize them in a D-Bus message, in D-Bus method calls or return values... and they will be simply transmitted as D-Bus arrays.
|
||||
|
||||
As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate:
|
||||
As another example, say we have our custom type `my::Struct` which we'd like to use as a D-Bus structure representation (sdbus-c++ provides `sdbus::Struct` type for that, but we don't want to use it because using our custom type directly is more convenient). Again, we have to provide type traits and message serialization/deserialization functions for our custom type. We build our functions and specializations on top of `sdbus::Struct`, so we don't have to copy and write a lot of boiler-plate. Serialization/deserialization functions can be placed in the same namespace as our custom type, and will be found thanks to the ADR lookup. The `signature_of` specialization must always be in either `sdbus` namespace or in a global namespace:
|
||||
|
||||
```c++
|
||||
namespace my {
|
||||
@ -1482,7 +1623,7 @@ namespace my {
|
||||
sdbus::Struct s{std::forward_as_tuple(items.i, items.s, items.l)};
|
||||
return msg >> s;
|
||||
}
|
||||
}
|
||||
} // namespace my
|
||||
|
||||
template <>
|
||||
struct sdbus::signature_of<my::Struct>
|
||||
@ -1497,7 +1638,66 @@ Live examples of extending sdbus-c++ types can be found in [Message unit tests](
|
||||
Support for match rules
|
||||
-----------------------
|
||||
|
||||
`IConnection` class provides `addMatch` method that you can use to install match rules. An associated callback handler will be called upon an incoming message matching given match rule. There is support for both client-owned and floating (library-owned) match rules. Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
|
||||
`IConnection` class provides `addMatch` and `addMatchAsync` family of methods that you can use to install match rules on that bus connection. An associated callback handler will be called when an incoming D-Bus message matches the given match rule. Clients can decide whether they own and control the match rule lifetime, or whether the match rule lifetime is bound the connection object lifetime (so-called floating match rule). Consult `IConnection` header or sdbus-c++ doxygen documentation for more information.
|
||||
|
||||
Using direct (peer-to-peer) D-Bus connections
|
||||
---------------------------------------------
|
||||
|
||||
sdbus-c++ provides an API to establish a direct connection between two peers -- between a client and a server, without going via the D-Bus daemon. The methods of interest, which will create a D-Bus server bus, and a client connection to it, respectively, are:
|
||||
|
||||
* `sdbus::createServerBus()` creates and returns a new, custom bus object in server mode, out of provided file descriptor parameter.
|
||||
* `sdbus::createDirectBusConnection()` opens and returns direct D-Bus connection at the provided custom address(es), or at the provided file descriptor.
|
||||
|
||||
Here is an example, extracted from the analogous test case in sdbus-c++ integration tests suite:
|
||||
|
||||
```c++
|
||||
#include "Concatenator.h"
|
||||
#include "ConcatenatorProxy.h"
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int fds[2];
|
||||
|
||||
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> serverConnection;
|
||||
std::unique_ptr<sdbus::IConnection> clientConnection;
|
||||
|
||||
std::thread t([&]()
|
||||
{
|
||||
serverConnection = sdbus::createServerBus(fds[0]);
|
||||
// This is necessary so that createDirectBusConnection() below does not block
|
||||
serverConnection->enterEventLoopAsync();
|
||||
});
|
||||
|
||||
clientConnection = sdbus::createDirectBusConnection(fds[1]);
|
||||
clientConnection->enterEventLoopAsync();
|
||||
|
||||
t.join();
|
||||
|
||||
// We can now use connection objects in a familiar way, e.g. create adaptor and proxy objects on them, and exchange messages.
|
||||
// Here, using Concatenator IDL-generated bindings example from chapters above:
|
||||
const char* objectPath = "/org/sdbuscpp/concatenator";
|
||||
Concatenator concatenator(*serverConnection, objectPath);
|
||||
const char* emptyDestinationName = ""; // Destination may be empty in case of direct connections
|
||||
ConcatenatorProxy concatenatorProxy(*clientConnection, emptyDestinationName, objectPath);
|
||||
|
||||
// Perform call of concatenate D-Bus method
|
||||
std::vector<int> numbers = {1, 2, 3};
|
||||
std::string separator = ":";
|
||||
auto concatenatedString = concatenatorProxy.concatenate(numbers, separator);
|
||||
assert(concatenatedString == "1:2:3");
|
||||
|
||||
// Explicitly stop working on socket fd's to avoid "Connection reset by peer" errors
|
||||
clientConnection->leaveEventLoop();
|
||||
serverConnection->leaveEventLoop();
|
||||
}
|
||||
```
|
||||
|
||||
> **_Note_:** The example above explicitly stops the event loops on both sides, before the connection objects are destroyed. This avoids potential `Connection reset by peer` errors caused when one side closes its socket while the other side is still working on the counterpart socket. This is a recommended workflow for closing direct D-Bus connections.
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
#include <sdbus-c++/Message.h>
|
||||
#include <sdbus-c++/TypeTraits.h>
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <sdbus-c++/Flags.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -41,7 +42,6 @@
|
||||
namespace sdbus {
|
||||
class IObject;
|
||||
class IProxy;
|
||||
class Variant;
|
||||
class Error;
|
||||
class PendingAsyncCall;
|
||||
}
|
||||
@ -225,7 +225,7 @@ namespace sdbus {
|
||||
{
|
||||
public:
|
||||
SignalUnsubscriber(IProxy& proxy, const std::string& signalName);
|
||||
void onInterface(std::string interfaceName);
|
||||
void onInterface(const std::string& interfaceName);
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
@ -236,25 +236,81 @@ namespace sdbus {
|
||||
{
|
||||
public:
|
||||
PropertyGetter(IProxy& proxy, const std::string& propertyName);
|
||||
sdbus::Variant onInterface(const std::string& interfaceName);
|
||||
Variant onInterface(const std::string& interfaceName);
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string& propertyName_;
|
||||
};
|
||||
|
||||
class AsyncPropertyGetter
|
||||
{
|
||||
public:
|
||||
AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName);
|
||||
AsyncPropertyGetter& onInterface(const std::string& interfaceName);
|
||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||
std::future<Variant> getResultAsFuture();
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string& propertyName_;
|
||||
const std::string* interfaceName_{};
|
||||
};
|
||||
|
||||
class PropertySetter
|
||||
{
|
||||
public:
|
||||
PropertySetter(IProxy& proxy, const std::string& propertyName);
|
||||
PropertySetter& onInterface(std::string interfaceName);
|
||||
PropertySetter& onInterface(const std::string& interfaceName);
|
||||
template <typename _Value> void toValue(const _Value& value);
|
||||
void toValue(const sdbus::Variant& 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:
|
||||
IProxy& proxy_;
|
||||
const std::string& propertyName_;
|
||||
std::string interfaceName_;
|
||||
const std::string* interfaceName_{};
|
||||
};
|
||||
|
||||
class AsyncPropertySetter
|
||||
{
|
||||
public:
|
||||
AsyncPropertySetter(IProxy& proxy, const std::string& propertyName);
|
||||
AsyncPropertySetter& onInterface(const std::string& interfaceName);
|
||||
template <typename _Value> AsyncPropertySetter& toValue(_Value&& value);
|
||||
AsyncPropertySetter& toValue(Variant value);
|
||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||
std::future<void> getResultAsFuture();
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string& propertyName_;
|
||||
const std::string* interfaceName_{};
|
||||
Variant value_;
|
||||
};
|
||||
|
||||
class AllPropertiesGetter
|
||||
{
|
||||
public:
|
||||
AllPropertiesGetter(IProxy& proxy);
|
||||
std::map<std::string, Variant> onInterface(const std::string& interfaceName);
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
};
|
||||
|
||||
class AsyncAllPropertiesGetter
|
||||
{
|
||||
public:
|
||||
AsyncAllPropertiesGetter(IProxy& proxy);
|
||||
AsyncAllPropertiesGetter& onInterface(const std::string& interfaceName);
|
||||
template <typename _Function> PendingAsyncCall uponReplyInvoke(_Function&& callback);
|
||||
std::future<std::map<std::string, Variant>> getResultAsFuture();
|
||||
|
||||
private:
|
||||
IProxy& proxy_;
|
||||
const std::string* interfaceName_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -293,7 +293,7 @@ namespace sdbus {
|
||||
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(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 (propertySignature_.empty())
|
||||
@ -311,7 +311,7 @@ namespace sdbus {
|
||||
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(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 (propertySignature_.empty())
|
||||
@ -595,9 +595,8 @@ namespace sdbus {
|
||||
}
|
||||
catch (const Error& e)
|
||||
{
|
||||
// Catch message unpack exceptions and pass them to the callback
|
||||
// in the expected manner to avoid propagating them up the call
|
||||
// stack to the event loop.
|
||||
// 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;
|
||||
}
|
||||
@ -676,8 +675,10 @@ namespace sdbus {
|
||||
}
|
||||
catch (const sdbus::Error& e)
|
||||
{
|
||||
// Invoke callback with error argument and input arguments from the tuple.
|
||||
// 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.
|
||||
@ -704,7 +705,7 @@ namespace sdbus {
|
||||
{
|
||||
}
|
||||
|
||||
inline void SignalUnsubscriber::onInterface(std::string interfaceName)
|
||||
inline void SignalUnsubscriber::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
proxy_.unregisterSignalHandler(interfaceName, signalName_);
|
||||
}
|
||||
@ -719,17 +720,56 @@ namespace sdbus {
|
||||
{
|
||||
}
|
||||
|
||||
inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
|
||||
inline Variant PropertyGetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
sdbus::Variant var;
|
||||
proxy_
|
||||
.callMethod("Get")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(interfaceName, propertyName_)
|
||||
.storeResultsTo(var);
|
||||
Variant var;
|
||||
proxy_.callMethod("Get")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(interfaceName, propertyName_)
|
||||
.storeResultsTo(var);
|
||||
return var;
|
||||
}
|
||||
|
||||
/*** ------------------- ***/
|
||||
/*** AsyncPropertyGetter ***/
|
||||
/*** ------------------- ***/
|
||||
|
||||
inline AsyncPropertyGetter::AsyncPropertyGetter(IProxy& proxy, const std::string& propertyName)
|
||||
: proxy_(proxy)
|
||||
, propertyName_(propertyName)
|
||||
{
|
||||
}
|
||||
|
||||
inline AsyncPropertyGetter& AsyncPropertyGetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
interfaceName_ = &interfaceName;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Function>
|
||||
PendingAsyncCall AsyncPropertyGetter::uponReplyInvoke(_Function&& callback)
|
||||
{
|
||||
static_assert(std::is_invocable_r_v<void, _Function, const Error*, Variant>, "Property get callback function must accept Error* and property value as Variant");
|
||||
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("Get")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_)
|
||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
inline std::future<Variant> AsyncPropertyGetter::getResultAsFuture()
|
||||
{
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("Get")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_)
|
||||
.getResultAsFuture<Variant>();
|
||||
}
|
||||
|
||||
/*** -------------- ***/
|
||||
/*** PropertySetter ***/
|
||||
/*** -------------- ***/
|
||||
@ -740,9 +780,9 @@ namespace sdbus {
|
||||
{
|
||||
}
|
||||
|
||||
inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
|
||||
inline PropertySetter& PropertySetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
interfaceName_ = std::move(interfaceName);
|
||||
interfaceName_ = &interfaceName;
|
||||
|
||||
return *this;
|
||||
}
|
||||
@ -750,17 +790,144 @@ namespace sdbus {
|
||||
template <typename _Value>
|
||||
inline void PropertySetter::toValue(const _Value& value)
|
||||
{
|
||||
PropertySetter::toValue(sdbus::Variant{value});
|
||||
PropertySetter::toValue(Variant{value});
|
||||
}
|
||||
|
||||
inline void PropertySetter::toValue(const sdbus::Variant& value)
|
||||
template <typename _Value>
|
||||
inline void PropertySetter::toValue(const _Value& value, dont_expect_reply_t)
|
||||
{
|
||||
assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
|
||||
PropertySetter::toValue(Variant{value}, dont_expect_reply);
|
||||
}
|
||||
|
||||
proxy_
|
||||
.callMethod("Set")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(interfaceName_, propertyName_, value);
|
||||
inline void PropertySetter::toValue(const Variant& value)
|
||||
{
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
proxy_.callMethod("Set")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_, value);
|
||||
}
|
||||
|
||||
inline void PropertySetter::toValue(const Variant& value, dont_expect_reply_t)
|
||||
{
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
proxy_.callMethod("Set")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_, value)
|
||||
.dontExpectReply();
|
||||
}
|
||||
|
||||
/*** ------------------- ***/
|
||||
/*** AsyncPropertySetter ***/
|
||||
/*** ------------------- ***/
|
||||
|
||||
inline AsyncPropertySetter::AsyncPropertySetter(IProxy& proxy, const std::string& propertyName)
|
||||
: proxy_(proxy)
|
||||
, propertyName_(propertyName)
|
||||
{
|
||||
}
|
||||
|
||||
inline AsyncPropertySetter& AsyncPropertySetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
interfaceName_ = &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, const Error*>, "Property set callback function must accept Error* only");
|
||||
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("Set")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_, std::move(value_))
|
||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
inline std::future<void> AsyncPropertySetter::getResultAsFuture()
|
||||
{
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("Set")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_, propertyName_, std::move(value_))
|
||||
.getResultAsFuture<>();
|
||||
}
|
||||
|
||||
/*** ------------------- ***/
|
||||
/*** AllPropertiesGetter ***/
|
||||
/*** ------------------- ***/
|
||||
|
||||
inline AllPropertiesGetter::AllPropertiesGetter(IProxy& proxy)
|
||||
: proxy_(proxy)
|
||||
{
|
||||
}
|
||||
|
||||
inline std::map<std::string, Variant> AllPropertiesGetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
std::map<std::string, Variant> props;
|
||||
proxy_.callMethod("GetAll")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(interfaceName)
|
||||
.storeResultsTo(props);
|
||||
return props;
|
||||
}
|
||||
|
||||
/*** ------------------------ ***/
|
||||
/*** AsyncAllPropertiesGetter ***/
|
||||
/*** ------------------------ ***/
|
||||
|
||||
inline AsyncAllPropertiesGetter::AsyncAllPropertiesGetter(IProxy& proxy)
|
||||
: proxy_(proxy)
|
||||
{
|
||||
}
|
||||
|
||||
inline AsyncAllPropertiesGetter& AsyncAllPropertiesGetter::onInterface(const std::string& interfaceName)
|
||||
{
|
||||
interfaceName_ = &interfaceName;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename _Function>
|
||||
PendingAsyncCall AsyncAllPropertiesGetter::uponReplyInvoke(_Function&& callback)
|
||||
{
|
||||
static_assert( std::is_invocable_r_v<void, _Function, const Error*, std::map<std::string, Variant>>
|
||||
, "All properties get callback function must accept Error* and a map of property names to their values" );
|
||||
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("GetAll")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_)
|
||||
.uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
inline std::future<std::map<std::string, Variant>> AsyncAllPropertiesGetter::getResultAsFuture()
|
||||
{
|
||||
assert(interfaceName_ != nullptr); // onInterface() must be placed/called prior to this function
|
||||
|
||||
return proxy_.callMethodAsync("GetAll")
|
||||
.onInterface("org.freedesktop.DBus.Properties")
|
||||
.withArguments(*interfaceName_)
|
||||
.getResultAsFuture<std::map<std::string, Variant>>();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ namespace sdbus {
|
||||
};
|
||||
|
||||
sdbus::Error createError(int errNo, const std::string& customMsg);
|
||||
|
||||
inline const char* SDBUSCPP_ERROR_NAME = "org.sdbuscpp.Error";
|
||||
}
|
||||
|
||||
#define SDBUS_THROW_ERROR(_MSG, _ERRNO) \
|
||||
|
@ -34,6 +34,8 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
struct sd_bus;
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
/********************************************//**
|
||||
@ -266,10 +268,10 @@ namespace sdbus {
|
||||
virtual void addObjectManager(const std::string& objectPath, floating_slot_t) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Adds a match rule for incoming message dispatching
|
||||
* @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 incoming D-Bus message matching the rule
|
||||
* @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.
|
||||
@ -289,10 +291,10 @@ namespace sdbus {
|
||||
[[nodiscard]] virtual Slot addMatch(const std::string& match, message_handler callback) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Adds a floating match rule for incoming message dispatching
|
||||
* @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 incoming D-Bus message matching the rule
|
||||
* @param[in] callback Callback handler to be called upon processing an inbound D-Bus message matching the rule
|
||||
*
|
||||
* 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
|
||||
@ -305,6 +307,45 @@ namespace sdbus {
|
||||
*/
|
||||
virtual void addMatch(const std::string& match, message_handler callback, floating_slot_t) = 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.
|
||||
*
|
||||
* Refer to the @c addMatch(const std::string& match, message_handler callback) documentation, and consult
|
||||
* `man sd_bus_add_match`, for more information.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
[[nodiscard]] virtual Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) = 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
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Refer to the @c addMatch(const std::string& match, message_handler callback, message_handler installCallback)
|
||||
* documentation for more information.
|
||||
*
|
||||
* @throws sdbus::Error in case of failure
|
||||
*/
|
||||
virtual void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) = 0;
|
||||
|
||||
/*!
|
||||
* @copydoc IConnection::enterEventLoop()
|
||||
*
|
||||
@ -458,6 +499,74 @@ namespace sdbus {
|
||||
* @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_ */
|
||||
|
@ -83,7 +83,7 @@ namespace sdbus {
|
||||
virtual MethodCall createMethodCall(const std::string& interfaceName, const std::string& methodName) = 0;
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object
|
||||
* @brief Calls method on the D-Bus object
|
||||
*
|
||||
* @param[in] message Message representing a method call
|
||||
* @param[in] timeout Timeout for dbus call in microseconds
|
||||
@ -109,7 +109,7 @@ namespace sdbus {
|
||||
MethodReply callMethod(const MethodCall& message, const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object asynchronously
|
||||
* @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
|
||||
@ -133,7 +133,7 @@ namespace sdbus {
|
||||
PendingAsyncCall callMethod(const MethodCall& message, async_reply_handler asyncReplyCallback, const std::chrono::duration<_Rep, _Period>& timeout);
|
||||
|
||||
/*!
|
||||
* @brief Registers a handler for the desired signal emitted by the proxied D-Bus object
|
||||
* @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
|
||||
@ -178,7 +178,7 @@ namespace sdbus {
|
||||
virtual void unregister() = 0;
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object
|
||||
* @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
|
||||
@ -199,7 +199,7 @@ namespace sdbus {
|
||||
[[nodiscard]] MethodInvoker callMethod(const std::string& methodName);
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object asynchronously
|
||||
* @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
|
||||
@ -223,7 +223,7 @@ namespace sdbus {
|
||||
[[nodiscard]] AsyncMethodInvoker callMethodAsync(const std::string& methodName);
|
||||
|
||||
/*!
|
||||
* @brief Registers signal handler for a given signal of the proxied D-Bus object
|
||||
* @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
|
||||
@ -243,7 +243,7 @@ namespace sdbus {
|
||||
[[nodiscard]] SignalSubscriber uponSignal(const std::string& signalName);
|
||||
|
||||
/*!
|
||||
* @brief Unregisters signal handler of a given signal of the proxied D-Bus object
|
||||
* @brief Unregisters signal handler of a given signal of the D-Bus object
|
||||
*
|
||||
* @param[in] signalName Name of the signal
|
||||
* @return A helper object for convenient unregistration of the signal handler
|
||||
@ -260,7 +260,7 @@ namespace sdbus {
|
||||
[[nodiscard]] SignalUnsubscriber muteSignal(const std::string& signalName);
|
||||
|
||||
/*!
|
||||
* @brief Gets value of a property of the proxied D-Bus object
|
||||
* @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
|
||||
@ -279,23 +279,101 @@ namespace sdbus {
|
||||
[[nodiscard]] PropertyGetter getProperty(const std::string& propertyName);
|
||||
|
||||
/*!
|
||||
* @brief Sets value of a property of the proxied D-Bus object
|
||||
* @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 = [](const 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 std::string& 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 std::string& 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 = ...;
|
||||
* object_.setProperty("state").onInterface("com.kistler.foo").toValue(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]] PropertySetter setProperty(const std::string& propertyName);
|
||||
[[nodiscard]] AsyncPropertySetter setPropertyAsync(const std::string& 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 = [](const sdbus::Error* err, const std::map<std::string, 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
|
||||
@ -326,7 +404,7 @@ namespace sdbus {
|
||||
virtual const Message* getCurrentlyProcessedMessage() const = 0;
|
||||
|
||||
/*!
|
||||
* @brief Calls method on the proxied D-Bus object asynchronously
|
||||
* @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
|
||||
@ -445,11 +523,31 @@ namespace sdbus {
|
||||
return PropertyGetter(*this, propertyName);
|
||||
}
|
||||
|
||||
inline AsyncPropertyGetter IProxy::getPropertyAsync(const std::string& propertyName)
|
||||
{
|
||||
return AsyncPropertyGetter(*this, propertyName);
|
||||
}
|
||||
|
||||
inline PropertySetter IProxy::setProperty(const std::string& propertyName)
|
||||
{
|
||||
return PropertySetter(*this, propertyName);
|
||||
}
|
||||
|
||||
inline AsyncPropertySetter IProxy::setPropertyAsync(const std::string& propertyName)
|
||||
{
|
||||
return AsyncPropertySetter(*this, 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
|
||||
*
|
||||
@ -464,6 +562,9 @@ namespace sdbus {
|
||||
* 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");
|
||||
@ -487,6 +588,9 @@ namespace sdbus {
|
||||
* 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");
|
||||
@ -505,10 +609,14 @@ namespace sdbus {
|
||||
* @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 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
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <sys/types.h>
|
||||
#include <algorithm>
|
||||
|
||||
// Forward declarations
|
||||
namespace sdbus {
|
||||
|
@ -138,16 +138,52 @@ namespace sdbus {
|
||||
return proxy_->getProperty(propertyName).onInterface(interfaceName);
|
||||
}
|
||||
|
||||
template <typename _Function>
|
||||
PendingAsyncCall GetAsync(const std::string& interfaceName, const std::string& propertyName, _Function&& callback)
|
||||
{
|
||||
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
std::future<sdbus::Variant> GetAsync(const std::string& interfaceName, const std::string& propertyName, with_future_t)
|
||||
{
|
||||
return proxy_->getPropertyAsync(propertyName).onInterface(interfaceName).getResultAsFuture();
|
||||
}
|
||||
|
||||
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value)
|
||||
{
|
||||
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value);
|
||||
}
|
||||
|
||||
void Set(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, dont_expect_reply_t)
|
||||
{
|
||||
proxy_->setProperty(propertyName).onInterface(interfaceName).toValue(value, dont_expect_reply);
|
||||
}
|
||||
|
||||
template <typename _Function>
|
||||
PendingAsyncCall SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, _Function&& callback)
|
||||
{
|
||||
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
std::future<void> SetAsync(const std::string& interfaceName, const std::string& propertyName, const sdbus::Variant& value, with_future_t)
|
||||
{
|
||||
return proxy_->setPropertyAsync(propertyName).onInterface(interfaceName).toValue(value).getResultAsFuture();
|
||||
}
|
||||
|
||||
std::map<std::string, sdbus::Variant> GetAll(const std::string& interfaceName)
|
||||
{
|
||||
std::map<std::string, sdbus::Variant> props;
|
||||
proxy_->callMethod("GetAll").onInterface(INTERFACE_NAME).withArguments(interfaceName).storeResultsTo(props);
|
||||
return props;
|
||||
return proxy_->getAllProperties().onInterface(interfaceName);
|
||||
}
|
||||
|
||||
template <typename _Function>
|
||||
PendingAsyncCall GetAllAsync(const std::string& interfaceName, _Function&& callback)
|
||||
{
|
||||
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).uponReplyInvoke(std::forward<_Function>(callback));
|
||||
}
|
||||
|
||||
std::future<std::map<std::string, sdbus::Variant>> GetAllAsync(const std::string& interfaceName, with_future_t)
|
||||
{
|
||||
return proxy_->getAllPropertiesAsync().onInterface(interfaceName).getResultAsFuture();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -93,6 +93,9 @@ namespace sdbus {
|
||||
// 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{};
|
||||
|
||||
// Template specializations for getting D-Bus signatures from C++ types
|
||||
template <typename _T>
|
||||
|
@ -34,7 +34,7 @@
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
@ -56,7 +56,7 @@ namespace sdbus {
|
||||
Variant();
|
||||
|
||||
template <typename _ValueType>
|
||||
/*explicit*/ Variant(const _ValueType& value) // TODO: Mark explicit in new major version so we don't break client code within v1
|
||||
Variant(const _ValueType& value)
|
||||
: Variant()
|
||||
{
|
||||
msg_.openVariant(signature_of<_ValueType>::str());
|
||||
@ -209,7 +209,7 @@ namespace sdbus {
|
||||
UnixFd() = default;
|
||||
|
||||
explicit UnixFd(int fd)
|
||||
: fd_(::dup(fd))
|
||||
: fd_(checkedDup(fd))
|
||||
{
|
||||
}
|
||||
|
||||
@ -225,8 +225,12 @@ namespace sdbus {
|
||||
|
||||
UnixFd& operator=(const UnixFd& other)
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
close();
|
||||
fd_ = ::dup(other.fd_);
|
||||
fd_ = checkedDup(other.fd_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -237,9 +241,12 @@ namespace sdbus {
|
||||
|
||||
UnixFd& operator=(UnixFd&& other)
|
||||
{
|
||||
if (this == &other)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
fd_ = std::exchange(other.fd_, -1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -265,9 +272,7 @@ namespace sdbus {
|
||||
|
||||
int release()
|
||||
{
|
||||
auto fd = fd_;
|
||||
fd_ = -1;
|
||||
return fd;
|
||||
return std::exchange(fd_, -1);
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
@ -276,11 +281,12 @@ namespace sdbus {
|
||||
}
|
||||
|
||||
private:
|
||||
void close()
|
||||
{
|
||||
if (fd_ >= 0)
|
||||
::close(fd_);
|
||||
}
|
||||
/// 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;
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
|
||||
Name: @PROJECT_NAME@
|
||||
Description: C++ library on top of sd-bus, a systemd D-Bus library
|
||||
Requires@PKGCONFIG_REQS@: @LIBSYSTEMD@
|
||||
Requires@PKGCONFIG_REQS@: @PKGCONFIG_DEPS@
|
||||
Version: @SDBUSCPP_VERSION@
|
||||
Libs: -L${libdir} -l@PROJECT_NAME@
|
||||
Cflags: -I${includedir}
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include <sdbus-c++/Message.h>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include "ScopeGuard.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
@ -70,6 +70,26 @@ Connection::Connection(std::unique_ptr<ISdBus>&& interface, remote_system_bus_t,
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, const std::string& address)
|
||||
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, address.c_str()); })
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, private_bus_t, int fd)
|
||||
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_direct(bus, fd); })
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, server_bus_t, int fd)
|
||||
: Connection(std::move(interface), [&](sd_bus** bus) { return iface_->sd_bus_open_server(bus, fd); })
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, sdbus_bus_t, sd_bus *bus)
|
||||
: Connection(std::move(interface), [&](sd_bus** b) { *b = bus; return 0; })
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(std::unique_ptr<ISdBus>&& interface, pseudo_bus_t)
|
||||
: iface_(std::move(interface))
|
||||
, bus_(openPseudoBus())
|
||||
@ -88,12 +108,20 @@ void Connection::requestName(const std::string& name)
|
||||
|
||||
auto r = iface_->sd_bus_request_name(bus_.get(), name.c_str(), 0);
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to request bus name", -r);
|
||||
|
||||
// In some cases we need to explicitly notify the event loop
|
||||
// to process messages that may have arrived while executing the call.
|
||||
notifyEventLoop(eventFd_.fd);
|
||||
}
|
||||
|
||||
void Connection::releaseName(const std::string& name)
|
||||
{
|
||||
auto r = iface_->sd_bus_release_name(bus_.get(), name.c_str());
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to release bus name", -r);
|
||||
|
||||
// In some cases we need to explicitly notify the event loop
|
||||
// to process messages that may have arrived while executing the call.
|
||||
notifyEventLoop(eventFd_.fd);
|
||||
}
|
||||
|
||||
std::string Connection::getUniqueName() const
|
||||
@ -197,17 +225,11 @@ uint64_t Connection::getMethodCallTimeout() const
|
||||
|
||||
Slot Connection::addMatch(const std::string& match, message_handler callback)
|
||||
{
|
||||
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), *this, {}});
|
||||
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
|
||||
|
||||
auto messageHandler = [](sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) -> int
|
||||
{
|
||||
auto* matchInfo = static_cast<MatchInfo*>(userData);
|
||||
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
|
||||
matchInfo->callback(message);
|
||||
return 0;
|
||||
};
|
||||
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), {}, *this, {}});
|
||||
|
||||
auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), std::move(messageHandler), matchInfo.get());
|
||||
auto r = iface_->sd_bus_add_match(bus_.get(), &matchInfo->slot, match.c_str(), &Connection::sdbus_match_callback, matchInfo.get());
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r);
|
||||
|
||||
return {matchInfo.release(), [this](void *ptr)
|
||||
@ -223,6 +245,34 @@ void Connection::addMatch(const std::string& match, message_handler callback, fl
|
||||
floatingMatchRules_.push_back(addMatch(match, std::move(callback)));
|
||||
}
|
||||
|
||||
Slot Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(!callback, "Invalid match callback handler provided", EINVAL);
|
||||
|
||||
sd_bus_message_handler_t sdbusInstallCallback = installCallback ? &Connection::sdbus_match_install_callback : nullptr;
|
||||
auto matchInfo = std::make_unique<MatchInfo>(MatchInfo{std::move(callback), std::move(installCallback), *this, {}});
|
||||
|
||||
auto r = iface_->sd_bus_add_match_async( bus_.get()
|
||||
, &matchInfo->slot
|
||||
, match.c_str()
|
||||
, &Connection::sdbus_match_callback
|
||||
, sdbusInstallCallback
|
||||
, matchInfo.get());
|
||||
SDBUS_THROW_ERROR_IF(r < 0, "Failed to add match", -r);
|
||||
|
||||
return {matchInfo.release(), [this](void *ptr)
|
||||
{
|
||||
auto* matchInfo = static_cast<MatchInfo*>(ptr);
|
||||
iface_->sd_bus_slot_unref(matchInfo->slot);
|
||||
std::default_delete<MatchInfo>{}(matchInfo);
|
||||
}};
|
||||
}
|
||||
|
||||
void Connection::addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t)
|
||||
{
|
||||
floatingMatchRules_.push_back(addMatchAsync(match, std::move(callback), std::move(installCallback)));
|
||||
}
|
||||
|
||||
Slot Connection::addObjectVTable( const std::string& objectPath
|
||||
, const std::string& interfaceName
|
||||
, const sd_bus_vtable* vtable
|
||||
@ -262,7 +312,7 @@ MethodCall Connection::createMethodCall( const std::string& destination
|
||||
|
||||
auto r = iface_->sd_bus_message_new_method_call( bus_.get()
|
||||
, &sdbusMsg
|
||||
, destination.c_str()
|
||||
, destination.empty() ? nullptr : destination.c_str()
|
||||
, objectPath.c_str()
|
||||
, interfaceName.c_str()
|
||||
, methodName.c_str() );
|
||||
@ -527,7 +577,8 @@ std::string Connection::composeSignalMatchFilter( const std::string &sender
|
||||
std::string filter;
|
||||
|
||||
filter += "type='signal',";
|
||||
filter += "sender='" + sender + "',";
|
||||
if (!sender.empty())
|
||||
filter += "sender='" + sender + "',";
|
||||
filter += "interface='" + interfaceName + "',";
|
||||
filter += "member='" + signalName + "',";
|
||||
filter += "path='" + objectPath + "'";
|
||||
@ -544,6 +595,22 @@ std::vector</*const */char*> Connection::to_strv(const std::vector<std::string>&
|
||||
return strv;
|
||||
}
|
||||
|
||||
int Connection::sdbus_match_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
auto* matchInfo = static_cast<MatchInfo*>(userData);
|
||||
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->callback(message); }, retError);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
int Connection::sdbus_match_install_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
auto* matchInfo = static_cast<MatchInfo*>(userData);
|
||||
auto message = Message::Factory::create<PlainMessage>(sdbusMessage, &matchInfo->connection.getSdBusInterface());
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ matchInfo->installCallback(message); }, retError);
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
Connection::EventFd::EventFd()
|
||||
{
|
||||
fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
||||
@ -661,7 +728,6 @@ std::unique_ptr<sdbus::IConnection> createSessionBusConnection(const std::string
|
||||
std::unique_ptr<sdbus::IConnection> createSessionBusConnectionWithAddress(const std::string &address)
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
assert(interface != nullptr);
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::custom_session_bus, address);
|
||||
}
|
||||
|
||||
@ -671,4 +737,30 @@ std::unique_ptr<sdbus::IConnection> createRemoteSystemBusConnection(const std::s
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::remote_system_bus, host);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(const std::string& address)
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, address);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createDirectBusConnection(int fd)
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::private_bus, fd);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createServerBus(int fd)
|
||||
{
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::server_bus, fd);
|
||||
}
|
||||
|
||||
std::unique_ptr<sdbus::IConnection> createBusConnection(sd_bus *bus)
|
||||
{
|
||||
SDBUS_THROW_ERROR_IF(bus == nullptr, "Invalid bus argument", EINVAL);
|
||||
|
||||
auto interface = std::make_unique<sdbus::internal::SdBus>();
|
||||
return std::make_unique<sdbus::internal::Connection>(std::move(interface), Connection::sdbus_bus, bus);
|
||||
}
|
||||
|
||||
} // namespace sdbus
|
||||
|
@ -32,7 +32,7 @@
|
||||
#include "IConnection.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include "ISdBus.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
@ -57,6 +57,12 @@ namespace sdbus::internal {
|
||||
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{};
|
||||
|
||||
@ -65,6 +71,10 @@ namespace sdbus::internal {
|
||||
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;
|
||||
|
||||
@ -86,6 +96,8 @@ namespace sdbus::internal {
|
||||
|
||||
[[nodiscard]] Slot addMatch(const std::string& match, message_handler callback) override;
|
||||
void addMatch(const std::string& match, message_handler callback, floating_slot_t) override;
|
||||
[[nodiscard]] Slot addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback) override;
|
||||
void addMatchAsync(const std::string& match, message_handler callback, message_handler installCallback, floating_slot_t) override;
|
||||
|
||||
const ISdBus& getSdBusInterface() const override;
|
||||
ISdBus& getSdBusInterface() override;
|
||||
@ -141,10 +153,13 @@ namespace sdbus::internal {
|
||||
void clearEventLoopNotification(int fd) const;
|
||||
void notifyEventLoopNewTimeout() const override;
|
||||
|
||||
private:
|
||||
void joinWithEventLoop();
|
||||
static std::vector</*const */char*> to_strv(const std::vector<std::string>& 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:
|
||||
struct EventFd
|
||||
{
|
||||
EventFd();
|
||||
@ -155,6 +170,7 @@ namespace sdbus::internal {
|
||||
struct MatchInfo
|
||||
{
|
||||
message_handler callback;
|
||||
message_handler installCallback;
|
||||
Connection& connection;
|
||||
sd_bus_slot *slot;
|
||||
};
|
||||
|
@ -25,7 +25,7 @@
|
||||
*/
|
||||
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include "ScopeGuard.h"
|
||||
|
||||
namespace sdbus
|
||||
|
@ -25,7 +25,7 @@
|
||||
*/
|
||||
|
||||
#include <sdbus-c++/Flags.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
|
||||
namespace sdbus
|
||||
{
|
||||
|
@ -28,7 +28,7 @@
|
||||
#define SDBUS_CXX_INTERNAL_ICONNECTION_H_
|
||||
|
||||
#include <sdbus-c++/IConnection.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
@ -28,7 +28,7 @@
|
||||
#ifndef SDBUS_CXX_ISDBUS_H
|
||||
#define SDBUS_CXX_ISDBUS_H
|
||||
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
|
||||
namespace sdbus::internal {
|
||||
|
||||
@ -71,12 +71,16 @@ namespace sdbus::internal {
|
||||
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 sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) = 0;
|
||||
|
||||
virtual int sd_bus_new(sd_bus **ret) = 0;
|
||||
|
@ -31,7 +31,7 @@
|
||||
#include "ISdBus.h"
|
||||
#include "IConnection.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <cassert>
|
||||
|
||||
namespace sdbus {
|
||||
|
@ -35,7 +35,7 @@
|
||||
#include "IConnection.h"
|
||||
#include "Utils.h"
|
||||
#include "VTableUtils.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
|
||||
@ -347,16 +347,9 @@ int Object::sdbus_method_callback(sd_bus_message *sdbusMessage, void *userData,
|
||||
auto& callback = interfaceData->methods[message.getMemberName()].callback;
|
||||
assert(callback);
|
||||
|
||||
try
|
||||
{
|
||||
callback(message);
|
||||
}
|
||||
catch (const Error& e)
|
||||
{
|
||||
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
|
||||
}
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ callback(message); }, retError);
|
||||
|
||||
return 1;
|
||||
return ok ? 1 : -1;
|
||||
}
|
||||
|
||||
int Object::sdbus_property_get_callback( sd_bus */*bus*/
|
||||
@ -381,16 +374,9 @@ int Object::sdbus_property_get_callback( sd_bus */*bus*/
|
||||
|
||||
auto reply = Message::Factory::create<PropertyGetReply>(sdbusReply, &object.connection_.getSdBusInterface());
|
||||
|
||||
try
|
||||
{
|
||||
callback(reply);
|
||||
}
|
||||
catch (const Error& e)
|
||||
{
|
||||
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
|
||||
}
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ callback(reply); }, retError);
|
||||
|
||||
return 1;
|
||||
return ok ? 1 : -1;
|
||||
}
|
||||
|
||||
int Object::sdbus_property_set_callback( sd_bus */*bus*/
|
||||
@ -416,16 +402,9 @@ int Object::sdbus_property_set_callback( sd_bus */*bus*/
|
||||
object.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
callback(value);
|
||||
}
|
||||
catch (const Error& e)
|
||||
{
|
||||
sd_bus_error_set(retError, e.getName().c_str(), e.getMessage().c_str());
|
||||
}
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ callback(value); }, retError);
|
||||
|
||||
return 1;
|
||||
return ok ? 1 : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
#include <sdbus-c++/IObject.h>
|
||||
#include "IConnection.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
@ -32,7 +32,7 @@
|
||||
#include "sdbus-c++/IConnection.h"
|
||||
#include "sdbus-c++/Error.h"
|
||||
#include "ScopeGuard.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
@ -120,7 +120,7 @@ PendingAsyncCall Proxy::callMethod(const MethodCall& message, async_reply_handle
|
||||
SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid async method call message provided", EINVAL);
|
||||
|
||||
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
|
||||
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}});
|
||||
auto callData = std::make_shared<AsyncCalls::CallData>(AsyncCalls::CallData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::RUNNING});
|
||||
auto weakData = std::weak_ptr<AsyncCalls::CallData>{callData};
|
||||
|
||||
callData->slot = message.send(callback, callData.get(), timeout);
|
||||
@ -143,7 +143,7 @@ std::future<MethodReply> Proxy::callMethod(const MethodCall& message, uint64_t t
|
||||
async_reply_handler asyncReplyCallback = [promise = std::move(promise)](MethodReply& reply, const Error* error) noexcept
|
||||
{
|
||||
if (error == nullptr)
|
||||
promise->set_value(reply); // TODO: std::move? Can't move now because currently processed message. TODO: Refactor
|
||||
promise->set_value(reply);
|
||||
else
|
||||
promise->set_exception(std::make_exception_ptr(*error));
|
||||
};
|
||||
@ -162,7 +162,7 @@ MethodReply Proxy::sendMethodCallMessageAndWaitForReply(const MethodCall& messag
|
||||
syncCallReplyData.sendMethodReplyToWaitingThread(reply, error);
|
||||
};
|
||||
auto callback = (void*)&Proxy::sdbus_async_reply_handler;
|
||||
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}};
|
||||
AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}, AsyncCalls::CallData::State::NOT_ASYNC};
|
||||
|
||||
message.send(callback, &callData, timeout, floating_slot);
|
||||
|
||||
@ -269,20 +269,22 @@ const Message* Proxy::getCurrentlyProcessedMessage() const
|
||||
return m_CurrentlyProcessedMessage.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
|
||||
int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
auto* asyncCallData = static_cast<AsyncCalls::CallData*>(userData);
|
||||
assert(asyncCallData != nullptr);
|
||||
assert(asyncCallData->callback);
|
||||
auto& proxy = asyncCallData->proxy;
|
||||
auto state = asyncCallData->state;
|
||||
|
||||
// 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
|
||||
{
|
||||
// Remove call meta-data if it's a real async call (a sync call done in terms of async has slot == nullptr)
|
||||
proxy.pendingAsyncCalls_.removeCall(asyncCallData);
|
||||
// Remove call meta-data if it's a real async call (a sync call done in terms of async has STATE_NOT_ASYNC)
|
||||
if (state != AsyncCalls::CallData::State::NOT_ASYNC)
|
||||
proxy.pendingAsyncCalls_.removeCall(asyncCallData);
|
||||
};
|
||||
|
||||
auto message = Message::Factory::create<MethodReply>(sdbusMessage, &proxy.connection_->getSdBusInterface());
|
||||
@ -293,7 +295,7 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
|
||||
proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
|
||||
};
|
||||
|
||||
try
|
||||
auto ok = invokeHandlerAndCatchErrors([&]
|
||||
{
|
||||
const auto* error = sd_bus_message_get_error(sdbusMessage);
|
||||
if (error == nullptr)
|
||||
@ -305,16 +307,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat
|
||||
Error exception(error->name, error->message);
|
||||
asyncCallData->callback(message, &exception);
|
||||
}
|
||||
}
|
||||
catch (const Error&)
|
||||
{
|
||||
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
|
||||
}
|
||||
}, retError);
|
||||
|
||||
return 0;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/)
|
||||
int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError)
|
||||
{
|
||||
auto* signalData = static_cast<InterfaceData::SignalData*>(userData);
|
||||
assert(signalData != nullptr);
|
||||
@ -328,16 +326,9 @@ int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd
|
||||
signalData->proxy.m_CurrentlyProcessedMessage.store(nullptr, std::memory_order_relaxed);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
signalData->callback(message);
|
||||
}
|
||||
catch (const Error&)
|
||||
{
|
||||
// Intentionally left blank -- sdbus-c++ exceptions shall not bubble up to the underlying C sd-bus library
|
||||
}
|
||||
auto ok = invokeHandlerAndCatchErrors([&](){ signalData->callback(message); }, retError);
|
||||
|
||||
return 0;
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
14
src/Proxy.h
14
src/Proxy.h
@ -29,7 +29,7 @@
|
||||
|
||||
#include <sdbus-c++/IProxy.h>
|
||||
#include "IConnection.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
@ -135,10 +135,16 @@ namespace sdbus::internal {
|
||||
public:
|
||||
struct CallData
|
||||
{
|
||||
enum class State
|
||||
{ NOT_ASYNC
|
||||
, RUNNING
|
||||
, FINISHED
|
||||
};
|
||||
|
||||
Proxy& proxy;
|
||||
async_reply_handler callback;
|
||||
Slot slot;
|
||||
bool finished { false };
|
||||
State state;
|
||||
};
|
||||
|
||||
~AsyncCalls()
|
||||
@ -149,14 +155,14 @@ namespace sdbus::internal {
|
||||
void addCall(std::shared_ptr<CallData> asyncCallData)
|
||||
{
|
||||
std::lock_guard lock(mutex_);
|
||||
if (!asyncCallData->finished) // The call may have finished in the mean time
|
||||
if (asyncCallData->state != CallData::State::FINISHED) // The call may have finished in the meantime
|
||||
calls_.emplace_back(std::move(asyncCallData));
|
||||
}
|
||||
|
||||
void removeCall(CallData* data)
|
||||
{
|
||||
std::unique_lock lock(mutex_);
|
||||
data->finished = true;
|
||||
data->state = CallData::State::FINISHED;
|
||||
if (auto it = std::find_if(calls_.begin(), calls_.end(), [data](auto const& entry){ return entry.get() == data; }); it != calls_.end())
|
||||
{
|
||||
auto callData = std::move(*it);
|
||||
|
118
src/ScopeGuard.h
118
src/ScopeGuard.h
@ -55,62 +55,104 @@
|
||||
// return; // exiting scope normally
|
||||
// }
|
||||
|
||||
#define SCOPE_EXIT \
|
||||
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
|
||||
= ::sdbus::internal::detail::ScopeGuardOnExit() + [&]() \
|
||||
#define SCOPE_EXIT \
|
||||
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
|
||||
= ::sdbus::internal::ScopeGuardOnExitTag{} + [&]() \
|
||||
/**/
|
||||
|
||||
#define SCOPE_EXIT_NAMED(NAME) \
|
||||
auto NAME \
|
||||
= ::sdbus::internal::detail::ScopeGuardOnExit() + [&]() \
|
||||
#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{} + [&]() \
|
||||
/**/
|
||||
|
||||
namespace sdbus::internal {
|
||||
|
||||
template <class _Fun>
|
||||
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, 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
|
||||
{
|
||||
};
|
||||
return ScopeGuard<_Fun, ScopeGuardOnExitTag>(std::forward<_Fun>(fnc));
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@ -119,13 +161,9 @@ namespace sdbus::internal {
|
||||
#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_ */
|
||||
|
@ -194,26 +194,98 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
|
||||
{
|
||||
sd_bus* bus = nullptr;
|
||||
|
||||
int r = sd_bus_new(&bus);
|
||||
int r = ::sd_bus_new(&bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_set_address(bus, address);
|
||||
r = ::sd_bus_set_address(bus, address);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_set_bus_client(bus, true);
|
||||
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);
|
||||
r = ::sd_bus_set_trusted(bus, true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_bus_start(bus);
|
||||
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;
|
||||
|
||||
@ -224,7 +296,12 @@ int SdBus::sd_bus_open_user_with_address(sd_bus **ret, const char* address)
|
||||
|
||||
int SdBus::sd_bus_open_system_remote(sd_bus **ret, const char *host)
|
||||
{
|
||||
#ifdef SDBUS_basu
|
||||
// https://git.sr.ht/~emersion/basu/commit/01d33b244eb6
|
||||
return -EOPNOTSUPP;
|
||||
#else
|
||||
return ::sd_bus_open_system_remote(ret, host);
|
||||
#endif
|
||||
}
|
||||
|
||||
int SdBus::sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags)
|
||||
@ -265,7 +342,14 @@ int SdBus::sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match,
|
||||
{
|
||||
std::lock_guard lock(sdbusMutex_);
|
||||
|
||||
return :: sd_bus_add_match(bus, slot, match, callback, userdata);
|
||||
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);
|
||||
}
|
||||
|
||||
sd_bus_slot* SdBus::sd_bus_slot_unref(sd_bus_slot *slot)
|
||||
|
@ -63,12 +63,16 @@ public:
|
||||
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 sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) override;
|
||||
|
||||
virtual int sd_bus_new(sd_bus **ret) override;
|
||||
|
@ -27,8 +27,11 @@
|
||||
#include <sdbus-c++/Types.h>
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include "MessageUtils.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <system_error>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace sdbus {
|
||||
|
||||
@ -64,4 +67,27 @@ bool Variant::isEmpty() const
|
||||
return msg_.isEmpty();
|
||||
}
|
||||
|
||||
void UnixFd::close()
|
||||
{
|
||||
if (fd_ >= 0)
|
||||
{
|
||||
::close(fd_);
|
||||
}
|
||||
}
|
||||
|
||||
int UnixFd::checkedDup(int fd)
|
||||
{
|
||||
if (fd < 0)
|
||||
{
|
||||
return fd;
|
||||
}
|
||||
|
||||
int ret = ::dup(fd);
|
||||
if (ret < 0)
|
||||
{
|
||||
throw std::system_error(errno, std::generic_category(), "dup failed");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
52
src/Utils.h
52
src/Utils.h
@ -28,20 +28,20 @@
|
||||
#define SDBUS_CXX_INTERNAL_UTILS_H_
|
||||
|
||||
#include <sdbus-c++/Error.h>
|
||||
#include <systemd/sd-bus.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.c_str()), "Invalid object path '" + _PATH + "' provided", EINVAL) \
|
||||
#define SDBUS_CHECK_OBJECT_PATH(_PATH) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_object_path_is_valid(_PATH.c_str()), "Invalid object path '" + _PATH + "' provided", EINVAL) \
|
||||
/**/
|
||||
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \
|
||||
#define SDBUS_CHECK_INTERFACE_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_interface_name_is_valid(_NAME.c_str()), "Invalid interface name '" + _NAME + "' provided", EINVAL) \
|
||||
/**/
|
||||
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \
|
||||
#define SDBUS_CHECK_SERVICE_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!_NAME.empty() && !sd_bus_service_name_is_valid(_NAME.c_str()), "Invalid service name '" + _NAME + "' provided", EINVAL) \
|
||||
/**/
|
||||
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), "Invalid member name '" + _NAME + "' provided", EINVAL) \
|
||||
#define SDBUS_CHECK_MEMBER_NAME(_NAME) \
|
||||
SDBUS_THROW_ERROR_IF(!sd_bus_member_name_is_valid(_NAME.c_str()), "Invalid member name '" + _NAME + "' provided", EINVAL) \
|
||||
/**/
|
||||
#else
|
||||
#define SDBUS_CHECK_OBJECT_PATH(_PATH)
|
||||
@ -50,4 +50,38 @@
|
||||
#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, e.what());
|
||||
return false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
sd_bus_error_set(retError, SDBUSCPP_ERROR_NAME, "Unknown error occurred");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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_ */
|
||||
|
@ -25,7 +25,7 @@
|
||||
*/
|
||||
|
||||
#include "VTableUtils.h"
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
|
||||
sd_bus_vtable createVTableStartItem(uint64_t flags)
|
||||
{
|
||||
|
@ -27,7 +27,7 @@
|
||||
#ifndef SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
|
||||
#define SDBUS_CXX_INTERNAL_VTABLEUTILS_H_
|
||||
|
||||
#include <systemd/sd-bus.h>
|
||||
#include SDBUS_HEADER
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -104,7 +104,9 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
#----------------------------------
|
||||
|
||||
add_executable(sdbus-c++-unit-tests ${UNITTESTS_SRCS})
|
||||
target_compile_definitions(sdbus-c++-unit-tests PRIVATE LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION})
|
||||
target_compile_definitions(sdbus-c++-unit-tests PRIVATE
|
||||
LIBSYSTEMD_VERSION=${LIBSYSTEMD_VERSION}
|
||||
SDBUS_HEADER=<${LIBSYSTEMD_IMPL}/sd-bus.h>)
|
||||
target_link_libraries(sdbus-c++-unit-tests sdbus-c++-objlib GTest::gmock)
|
||||
|
||||
add_executable(sdbus-c++-integration-tests ${INTEGRATIONTESTS_SRCS})
|
||||
@ -138,21 +140,26 @@ endif()
|
||||
# INSTALLATION
|
||||
#----------------------------------
|
||||
|
||||
set(TESTS_INSTALL_PATH "/opt/test/bin" CACHE STRING "Specifies where the test binaries will be installed")
|
||||
option(INSTALL_TESTS "Install tests (default OFF)" OFF)
|
||||
|
||||
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
|
||||
if(INSTALL_TESTS)
|
||||
include(GNUInstallDirs)
|
||||
set(TESTS_INSTALL_PATH "tests/${PROJECT_NAME}" CACHE STRING "Specifies where the test binaries will be installed")
|
||||
|
||||
if(ENABLE_PERF_TESTS)
|
||||
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
|
||||
endif()
|
||||
install(TARGETS sdbus-c++-unit-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(TARGETS sdbus-c++-integration-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${INTEGRATIONTESTS_SOURCE_DIR}/files/org.sdbuscpp.integrationtests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
|
||||
|
||||
if(ENABLE_STRESS_TESTS)
|
||||
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION /etc/dbus-1/system.d COMPONENT test)
|
||||
if(ENABLE_PERF_TESTS)
|
||||
install(TARGETS sdbus-c++-perf-tests-client DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(TARGETS sdbus-c++-perf-tests-server DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${PERFTESTS_SOURCE_DIR}/files/org.sdbuscpp.perftests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
|
||||
endif()
|
||||
|
||||
if(ENABLE_STRESS_TESTS)
|
||||
install(TARGETS sdbus-c++-stress-tests DESTINATION ${TESTS_INSTALL_PATH} COMPONENT test)
|
||||
install(FILES ${STRESSTESTS_SOURCE_DIR}/files/org.sdbuscpp.stresstests.conf DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR}/dbus-1/system.d COMPONENT test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#----------------------------------
|
||||
|
@ -46,6 +46,7 @@ using namespace std::chrono_literals;
|
||||
using namespace sdbus::test;
|
||||
|
||||
using AConnection = TestFixture;
|
||||
using ADirectConnection = TestFixtureWithDirectConnection;
|
||||
|
||||
/*-------------------------------------*/
|
||||
/* -- TEST CASES -- */
|
||||
@ -87,6 +88,29 @@ TEST_F(AConnection, WillCallCallbackHandlerForIncomingMessageMatchingMatchRule)
|
||||
ASSERT_TRUE(waitUntil(matchingMessageReceived));
|
||||
}
|
||||
|
||||
TEST_F(AConnection, CanInstallMatchRuleAsynchronously)
|
||||
{
|
||||
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
|
||||
std::atomic<bool> matchingMessageReceived{false};
|
||||
std::atomic<bool> matchRuleInstalled{false};
|
||||
auto slot = s_proxyConnection->addMatchAsync( matchRule
|
||||
, [&](sdbus::Message& msg)
|
||||
{
|
||||
if(msg.getPath() == OBJECT_PATH)
|
||||
matchingMessageReceived = true;
|
||||
}
|
||||
, [&](sdbus::Message& /*msg*/)
|
||||
{
|
||||
matchRuleInstalled = true;
|
||||
} );
|
||||
|
||||
EXPECT_TRUE(waitUntil(matchRuleInstalled));
|
||||
|
||||
m_adaptor->emitSimpleSignal();
|
||||
|
||||
ASSERT_TRUE(waitUntil(matchingMessageReceived));
|
||||
}
|
||||
|
||||
TEST_F(AConnection, WillUnsubscribeMatchRuleWhenClientDestroysTheAssociatedSlot)
|
||||
{
|
||||
auto matchRule = "sender='" + BUS_NAME + "',path='" + OBJECT_PATH + "'";
|
||||
@ -144,3 +168,14 @@ TEST_F(AConnection, WillNotPassToMatchCallbackMessagesThatDoNotMatchTheRule)
|
||||
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));
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut)
|
||||
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"));
|
||||
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));
|
||||
}
|
||||
@ -277,6 +277,10 @@ TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessTh
|
||||
|
||||
TEST_F(SdbusTestObject, CanCallMethodSynchronouslyWithoutAnEventLoopThread)
|
||||
{
|
||||
#if defined(__clang__) && defined(__FreeBSD__)
|
||||
GTEST_SKIP() << "https://github.com/Kistler-Group/sdbus-cpp/issues/359";
|
||||
#endif
|
||||
|
||||
auto proxy = std::make_unique<TestProxy>(BUS_NAME, OBJECT_PATH, sdbus::dont_run_event_loop_thread);
|
||||
|
||||
auto multiplyRes = proxy->multiply(INT64_VALUE, DOUBLE_VALUE);
|
||||
|
@ -61,12 +61,9 @@ TEST_F(SdbusTestObject, PingsViaPeerInterface)
|
||||
|
||||
TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface)
|
||||
{
|
||||
// If /etc/machine-id does not exist in your system (which is very likely because you have
|
||||
// a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve
|
||||
// this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id,
|
||||
// and then org.freedesktop.DBus.Peer.GetMachineId() will start to work.
|
||||
if (::access("/etc/machine-id", F_OK) == -1)
|
||||
GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work";
|
||||
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(m_proxy->GetMachineId());
|
||||
}
|
||||
@ -82,6 +79,29 @@ TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface)
|
||||
ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterface)
|
||||
{
|
||||
std::promise<std::string> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
m_proxy->GetAsync(INTERFACE_NAME, "state", [&](const sdbus::Error* err, sdbus::Variant value)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value(value.get<std::string>());
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
ASSERT_THAT(future.get(), Eq(DEFAULT_STATE_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, GetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
|
||||
{
|
||||
auto future = m_proxy->GetAsync(INTERFACE_NAME, "state", sdbus::with_future);
|
||||
|
||||
ASSERT_THAT(future.get().get<std::string>(), Eq(DEFAULT_STATE_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface)
|
||||
{
|
||||
uint32_t newActionValue = 2345;
|
||||
@ -91,6 +111,34 @@ TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface)
|
||||
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterface)
|
||||
{
|
||||
uint32_t newActionValue = 2346;
|
||||
std::promise<void> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, [&](const sdbus::Error* err)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value();
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
|
||||
ASSERT_NO_THROW(future.get());
|
||||
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, SetsPropertyAsynchronouslyViaPropertiesInterfaceWithFuture)
|
||||
{
|
||||
uint32_t newActionValue = 2347;
|
||||
|
||||
auto future = m_proxy->SetAsync(INTERFACE_NAME, "action", sdbus::Variant{newActionValue}, sdbus::with_future);
|
||||
|
||||
ASSERT_NO_THROW(future.get());
|
||||
ASSERT_THAT(m_proxy->action(), Eq(newActionValue));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
|
||||
{
|
||||
const auto properties = m_proxy->GetAll(INTERFACE_NAME);
|
||||
@ -101,6 +149,38 @@ TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface)
|
||||
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterface)
|
||||
{
|
||||
std::promise<std::map<std::string, sdbus::Variant>> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
m_proxy->GetAllAsync(INTERFACE_NAME, [&](const sdbus::Error* err, std::map<std::string, sdbus::Variant> value)
|
||||
{
|
||||
if (err == nullptr)
|
||||
promise.set_value(std::move(value));
|
||||
else
|
||||
promise.set_exception(std::make_exception_ptr(*err));
|
||||
});
|
||||
const auto properties = future.get();
|
||||
|
||||
ASSERT_THAT(properties, SizeIs(3));
|
||||
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
|
||||
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
|
||||
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, GetsAllPropertiesAsynchronouslyViaPropertiesInterfaceWithFuture)
|
||||
{
|
||||
auto future = m_proxy->GetAllAsync(INTERFACE_NAME, sdbus::with_future);
|
||||
|
||||
auto properties = future.get();
|
||||
|
||||
ASSERT_THAT(properties, SizeIs(3));
|
||||
EXPECT_THAT(properties.at("state").get<std::string>(), Eq(DEFAULT_STATE_VALUE));
|
||||
EXPECT_THAT(properties.at("action").get<uint32_t>(), Eq(DEFAULT_ACTION_VALUE));
|
||||
EXPECT_THAT(properties.at("blocking").get<bool>(), Eq(DEFAULT_BLOCKING_VALUE));
|
||||
}
|
||||
|
||||
TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties)
|
||||
{
|
||||
std::atomic<bool> signalReceived{false};
|
||||
|
@ -30,14 +30,17 @@
|
||||
#include "sdbus-c++/Types.h"
|
||||
#include <chrono>
|
||||
#include <ostream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace sdbus { namespace test {
|
||||
|
||||
const std::string INTERFACE_NAME{"org.sdbuscpp.integrationtests"};
|
||||
const std::string BUS_NAME = INTERFACE_NAME;
|
||||
const std::string EMPTY_DESTINATION;
|
||||
const std::string MANAGER_PATH {"/org/sdbuscpp/integrationtests"};
|
||||
const std::string OBJECT_PATH {"/org/sdbuscpp/integrationtests/ObjectA1"};
|
||||
const std::string OBJECT_PATH_2{"/org/sdbuscpp/integrationtests/ObjectB1"};
|
||||
const std::string DIRECT_CONNECTION_SOCKET_PATH{std::filesystem::temp_directory_path() / "sdbus-cpp-direct-connection-test"};
|
||||
|
||||
constexpr const uint8_t UINT8_VALUE{1};
|
||||
constexpr const int16_t INT16_VALUE{21};
|
||||
|
@ -39,6 +39,11 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
namespace sdbus { namespace test {
|
||||
|
||||
class TestFixture : public ::testing::Test
|
||||
@ -59,28 +64,6 @@ public:
|
||||
s_proxyConnection->leaveEventLoop();
|
||||
}
|
||||
|
||||
template <typename _Fnc>
|
||||
static bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::chrono::milliseconds elapsed{};
|
||||
std::chrono::milliseconds step{5ms};
|
||||
do {
|
||||
std::this_thread::sleep_for(step);
|
||||
elapsed += step;
|
||||
if (elapsed > timeout)
|
||||
return false;
|
||||
} while (!fnc());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
|
||||
{
|
||||
return waitUntil([&flag]() -> bool { return flag; }, timeout);
|
||||
}
|
||||
|
||||
private:
|
||||
void SetUp() override
|
||||
{
|
||||
@ -106,6 +89,101 @@ public:
|
||||
std::unique_ptr<TestProxy> m_proxy;
|
||||
};
|
||||
|
||||
class TestFixtureWithDirectConnection : public ::testing::Test
|
||||
{
|
||||
private:
|
||||
void SetUp() override
|
||||
{
|
||||
int sock = openUnixSocket();
|
||||
createClientAndServerConnections(sock);
|
||||
createAdaptorAndProxyObjects();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_proxy.reset();
|
||||
m_adaptor.reset();
|
||||
m_proxyConnection->leaveEventLoop();
|
||||
m_adaptorConnection->leaveEventLoop();
|
||||
}
|
||||
|
||||
static int openUnixSocket()
|
||||
{
|
||||
int sock = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
|
||||
assert(sock >= 0);
|
||||
|
||||
sockaddr_un sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sun_family = AF_UNIX;
|
||||
snprintf(sa.sun_path, sizeof(sa.sun_path), "%s", DIRECT_CONNECTION_SOCKET_PATH.c_str());
|
||||
|
||||
unlink(DIRECT_CONNECTION_SOCKET_PATH.c_str());
|
||||
|
||||
umask(0000);
|
||||
[[maybe_unused]] int r = bind(sock, (const sockaddr*) &sa, sizeof(sa.sun_path));
|
||||
assert(r >= 0);
|
||||
|
||||
r = listen(sock, 5);
|
||||
assert(r >= 0);
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
void createClientAndServerConnections(int sock)
|
||||
{
|
||||
std::thread t([&]()
|
||||
{
|
||||
auto fd = accept4(sock, NULL, NULL, /*SOCK_NONBLOCK|*/SOCK_CLOEXEC);
|
||||
m_adaptorConnection = sdbus::createServerBus(fd);
|
||||
// This is necessary so that createDirectBusConnection() below does not block
|
||||
m_adaptorConnection->enterEventLoopAsync();
|
||||
});
|
||||
|
||||
m_proxyConnection = sdbus::createDirectBusConnection("unix:path=" + DIRECT_CONNECTION_SOCKET_PATH);
|
||||
m_proxyConnection->enterEventLoopAsync();
|
||||
|
||||
t.join();
|
||||
}
|
||||
|
||||
void createAdaptorAndProxyObjects()
|
||||
{
|
||||
assert(m_adaptorConnection != nullptr);
|
||||
assert(m_proxyConnection != nullptr);
|
||||
|
||||
m_adaptor = std::make_unique<TestAdaptor>(*m_adaptorConnection, OBJECT_PATH);
|
||||
// Destination parameter can be empty in case of direct connections
|
||||
m_proxy = std::make_unique<TestProxy>(*m_proxyConnection, EMPTY_DESTINATION, OBJECT_PATH);
|
||||
}
|
||||
|
||||
public:
|
||||
std::unique_ptr<sdbus::IConnection> m_adaptorConnection;
|
||||
std::unique_ptr<sdbus::IConnection> m_proxyConnection;
|
||||
std::unique_ptr<TestAdaptor> m_adaptor;
|
||||
std::unique_ptr<TestProxy> m_proxy;
|
||||
};
|
||||
|
||||
template <typename _Fnc>
|
||||
inline bool waitUntil(_Fnc&& fnc, std::chrono::milliseconds timeout = std::chrono::seconds(5))
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::chrono::milliseconds elapsed{};
|
||||
std::chrono::milliseconds step{5ms};
|
||||
do {
|
||||
std::this_thread::sleep_for(step);
|
||||
elapsed += step;
|
||||
if (elapsed > timeout)
|
||||
return false;
|
||||
} while (!fnc());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool waitUntil(std::atomic<bool>& flag, std::chrono::milliseconds timeout = std::chrono::seconds(5))
|
||||
{
|
||||
return waitUntil([&flag]() -> bool { return flag; }, timeout);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
#endif /* SDBUS_CPP_INTEGRATIONTESTS_TESTFIXTURE_H_ */
|
||||
|
@ -62,12 +62,16 @@ public:
|
||||
MOCK_METHOD1(sd_bus_open_user, int(sd_bus **ret));
|
||||
MOCK_METHOD2(sd_bus_open_user_with_address, int(sd_bus **ret, const char* address));
|
||||
MOCK_METHOD2(sd_bus_open_system_remote, int(sd_bus **ret, const char *host));
|
||||
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, const char* address));
|
||||
MOCK_METHOD2(sd_bus_open_direct, int(sd_bus **ret, int fd));
|
||||
MOCK_METHOD2(sd_bus_open_server, int(sd_bus **ret, int fd));
|
||||
MOCK_METHOD3(sd_bus_request_name, int(sd_bus *bus, const char *name, uint64_t flags));
|
||||
MOCK_METHOD2(sd_bus_release_name, int(sd_bus *bus, const char *name));
|
||||
MOCK_METHOD2(sd_bus_get_unique_name, int(sd_bus *bus, const char **name));
|
||||
MOCK_METHOD6(sd_bus_add_object_vtable, int(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata));
|
||||
MOCK_METHOD3(sd_bus_add_object_manager, int(sd_bus *bus, sd_bus_slot **slot, const char *path));
|
||||
MOCK_METHOD5(sd_bus_add_match, int(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata));
|
||||
MOCK_METHOD6(sd_bus_add_match_async, int(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));
|
||||
MOCK_METHOD1(sd_bus_slot_unref, sd_bus_slot*(sd_bus_slot *slot));
|
||||
|
||||
MOCK_METHOD1(sd_bus_new, int(sd_bus **ret));
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(sdbus-c++-tools VERSION 1.3.0)
|
||||
project(sdbus-c++-tools VERSION 1.4.0)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
|
@ -108,12 +108,18 @@ std::string ProxyGenerator::processInterface(Node& interface) const
|
||||
if (!declaration.empty())
|
||||
body << declaration << endl;
|
||||
|
||||
std::string methodDefinitions, asyncDeclarations;
|
||||
std::tie(methodDefinitions, asyncDeclarations) = processMethods(methods);
|
||||
std::string methodDefinitions, asyncDeclarationsMethods;
|
||||
std::tie(methodDefinitions, asyncDeclarationsMethods) = processMethods(methods);
|
||||
std::string propertyDefinitions, asyncDeclarationsProperties;
|
||||
std::tie(propertyDefinitions, asyncDeclarationsProperties) = processProperties(properties);
|
||||
|
||||
if (!asyncDeclarations.empty())
|
||||
if (!asyncDeclarationsMethods.empty())
|
||||
{
|
||||
body << asyncDeclarations << endl;
|
||||
body << asyncDeclarationsMethods << endl;
|
||||
}
|
||||
if (!asyncDeclarationsProperties.empty())
|
||||
{
|
||||
body << asyncDeclarationsProperties << endl;
|
||||
}
|
||||
|
||||
if (!methodDefinitions.empty())
|
||||
@ -121,7 +127,6 @@ std::string ProxyGenerator::processInterface(Node& interface) const
|
||||
body << "public:" << endl << methodDefinitions;
|
||||
}
|
||||
|
||||
std::string propertyDefinitions = processProperties(properties);
|
||||
if (!propertyDefinitions.empty())
|
||||
{
|
||||
body << "public:" << endl << propertyDefinitions;
|
||||
@ -293,9 +298,9 @@ std::tuple<std::string, std::string> ProxyGenerator::processSignals(const Nodes&
|
||||
return std::make_tuple(registrationSS.str(), declarationSS.str());
|
||||
}
|
||||
|
||||
std::string ProxyGenerator::processProperties(const Nodes& properties) const
|
||||
std::tuple<std::string, std::string> ProxyGenerator::processProperties(const Nodes& properties) const
|
||||
{
|
||||
std::ostringstream propertySS;
|
||||
std::ostringstream propertySS, asyncDeclarationSS;
|
||||
for (const auto& property : properties)
|
||||
{
|
||||
auto propertyName = property->get("name");
|
||||
@ -307,25 +312,93 @@ std::string ProxyGenerator::processProperties(const Nodes& properties) const
|
||||
auto propertyArg = std::string("value");
|
||||
auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg;
|
||||
|
||||
bool asyncGet{false};
|
||||
bool futureGet{false}; // Async property getter implemented by means of either std::future or callbacks
|
||||
bool asyncSet{false};
|
||||
bool futureSet{false}; // Async property setter implemented by means of either std::future or callbacks
|
||||
|
||||
Nodes annotations = (*property)["annotation"];
|
||||
for (const auto& annotation : annotations)
|
||||
{
|
||||
const auto annotationName = annotation->get("name");
|
||||
const auto annotationValue = annotation->get("value");
|
||||
|
||||
if (annotationName == "org.freedesktop.DBus.Property.Get.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
|
||||
asyncGet = true;
|
||||
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && annotationValue == "callback")
|
||||
futureGet = false;
|
||||
else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
|
||||
futureGet = true;
|
||||
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async" && annotationValue == "client") // Server-side not supported (may be in the future)
|
||||
asyncSet = true;
|
||||
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && annotationValue == "callback")
|
||||
futureSet = false;
|
||||
else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future"))
|
||||
futureSet = true;
|
||||
}
|
||||
|
||||
if (propertyAccess == "read" || propertyAccess == "readwrite")
|
||||
{
|
||||
propertySS << tab << propertyType << " " << propertyNameSafe << "()" << endl
|
||||
const std::string realRetType = (asyncGet ? (futureGet ? "std::future<sdbus::Variant>" : "sdbus::PendingAsyncCall") : propertyType);
|
||||
|
||||
propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl
|
||||
<< tab << "{" << endl;
|
||||
propertySS << tab << tab << "return proxy_->getProperty(\"" << propertyName << "\")"
|
||||
propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")"
|
||||
".onInterface(INTERFACE_NAME)";
|
||||
if (asyncGet)
|
||||
{
|
||||
auto nameBigFirst = propertyName;
|
||||
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
||||
|
||||
if (futureGet) // Async methods implemented through future
|
||||
{
|
||||
propertySS << ".getResultAsFuture()";
|
||||
}
|
||||
else // Async methods implemented through callbacks
|
||||
{
|
||||
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value)"
|
||||
"{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), error); })";
|
||||
|
||||
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply("
|
||||
<< "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl;
|
||||
}
|
||||
}
|
||||
propertySS << ";" << endl << tab << "}" << endl << endl;
|
||||
}
|
||||
|
||||
if (propertyAccess == "readwrite" || propertyAccess == "write")
|
||||
{
|
||||
propertySS << tab << "void " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
|
||||
<< tab << "{" << endl;
|
||||
propertySS << tab << tab << "proxy_->setProperty(\"" << propertyName << "\")"
|
||||
const std::string realRetType = (asyncSet ? (futureSet ? "std::future<void>" : "sdbus::PendingAsyncCall") : "void");
|
||||
|
||||
propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl
|
||||
<< tab << "{" << endl;
|
||||
propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "")
|
||||
<< "(\"" << propertyName << "\")"
|
||||
".onInterface(INTERFACE_NAME)"
|
||||
".toValue(" << propertyArg << ")";
|
||||
|
||||
if (asyncSet)
|
||||
{
|
||||
auto nameBigFirst = propertyName;
|
||||
nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0];
|
||||
|
||||
if (futureSet) // Async methods implemented through future
|
||||
{
|
||||
propertySS << ".getResultAsFuture()";
|
||||
}
|
||||
else // Async methods implemented through callbacks
|
||||
{
|
||||
propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)"
|
||||
"{ this->on" << nameBigFirst << "PropertySetReply(error); })";
|
||||
|
||||
asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply("
|
||||
<< "const sdbus::Error* error) = 0;" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
propertySS << ";" << endl << tab << "}" << endl << endl;
|
||||
}
|
||||
}
|
||||
|
||||
return propertySS.str();
|
||||
return std::make_tuple(propertySS.str(), asyncDeclarationSS.str());
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ private:
|
||||
* @param properties
|
||||
* @return source code
|
||||
*/
|
||||
std::string processProperties(const sdbuscpp::xml::Nodes& properties) const;
|
||||
std::tuple<std::string, std::string> processProperties(const sdbuscpp::xml::Nodes& properties) const;
|
||||
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user