sdbus-c++ 0.8.3
High-level C++ D-Bus library based on systemd D-Bus implementation
Loading...
Searching...
No Matches
ConvenienceApiClasses.inl
Go to the documentation of this file.
1
27#ifndef SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
28#define SDBUS_CPP_CONVENIENCEAPICLASSES_INL_
29
30#include <sdbus-c++/IObject.h>
31#include <sdbus-c++/IProxy.h>
32#include <sdbus-c++/Message.h>
34#include <sdbus-c++/Types.h>
36#include <sdbus-c++/Error.h>
37#include <string>
38#include <tuple>
39#include <exception>
40#include <cassert>
41
42namespace sdbus {
43
44 /*** ----------------- ***/
45 /*** MethodRegistrator ***/
46 /*** ----------------- ***/
47
48 inline MethodRegistrator::MethodRegistrator(IObject& object, const std::string& methodName)
49 : object_(object)
50 , methodName_(methodName)
51 , exceptions_(std::uncaught_exceptions())
52 {
53 }
54
55 inline MethodRegistrator::~MethodRegistrator() noexcept(false) // since C++11, destructors must
56 { // explicitly be allowed to throw
57 // Don't register the method if MethodRegistrator threw an exception in one of its methods
58 if (std::uncaught_exceptions() != exceptions_)
59 return;
60
61 assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
62 assert(methodCallback_); // implementedAs() must be placed/called prior to this function
63
64 // registerMethod() can throw. But as the MethodRegistrator shall always be used as an unnamed,
65 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
66 // shall never happen. I.e. it should not happen that this destructor is directly called
67 // in the stack-unwinding process of another flying exception (which would lead to immediate
68 // termination). It can be called indirectly in the destructor of another object, but that's
69 // fine and safe provided that the caller catches exceptions thrown from here.
70 // Therefore, we can allow registerMethod() to throw even if we are in the destructor.
71 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
72 // to the exception thrown from here if the caller is a destructor itself.
73 object_.registerMethod( interfaceName_
74 , std::move(methodName_)
75 , std::move(inputSignature_)
76 , std::move(inputParamNames_)
77 , std::move(outputSignature_)
78 , std::move(outputParamNames_)
79 , std::move(methodCallback_)
80 , std::move(flags_));
81 }
82
83 inline MethodRegistrator& MethodRegistrator::onInterface(std::string interfaceName)
84 {
85 interfaceName_ = std::move(interfaceName);
86
87 return *this;
88 }
89
90 template <typename _Function>
91 MethodRegistrator& MethodRegistrator::implementedAs(_Function&& callback)
92 {
93 inputSignature_ = signature_of_function_input_arguments<_Function>::str();
94 outputSignature_ = signature_of_function_output_arguments<_Function>::str();
95 methodCallback_ = [callback = std::forward<_Function>(callback)](MethodCall call)
96 {
97 // Create a tuple of callback input arguments' types, which will be used
98 // as a storage for the argument values deserialized from the message.
99 tuple_of_function_input_arg_types_t<_Function> inputArgs;
100
101 // Deserialize input arguments from the message into the tuple.
102 call >> inputArgs;
103
104 if constexpr (!is_async_method_v<_Function>)
105 {
106 // Invoke callback with input arguments from the tuple.
107 auto ret = sdbus::apply(callback, inputArgs);
108
109 // Store output arguments to the reply message and send it back.
110 auto reply = call.createReply();
111 reply << ret;
112 reply.send();
113 }
114 else
115 {
116 // Invoke callback with input arguments from the tuple and with result object to be set later
117 using AsyncResult = typename function_traits<_Function>::async_result_t;
118 sdbus::apply(callback, AsyncResult{std::move(call)}, std::move(inputArgs));
119 }
120 };
121
122 return *this;
123 }
124
125 inline MethodRegistrator& MethodRegistrator::withInputParamNames(std::vector<std::string> paramNames)
126 {
127 inputParamNames_ = std::move(paramNames);
128
129 return *this;
130 }
131
132 template <typename... _String>
133 inline MethodRegistrator& MethodRegistrator::withInputParamNames(_String... paramNames)
134 {
135 static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
136
137 return withInputParamNames({paramNames...});
138 }
139
140 inline MethodRegistrator& MethodRegistrator::withOutputParamNames(std::vector<std::string> paramNames)
141 {
142 outputParamNames_ = std::move(paramNames);
143
144 return *this;
145 }
146
147 template <typename... _String>
148 inline MethodRegistrator& MethodRegistrator::withOutputParamNames(_String... paramNames)
149 {
150 static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
151
152 return withOutputParamNames({paramNames...});
153 }
154
155 inline MethodRegistrator& MethodRegistrator::markAsDeprecated()
156 {
157 flags_.set(Flags::DEPRECATED);
158
159 return *this;
160 }
161
162 inline MethodRegistrator& MethodRegistrator::markAsPrivileged()
163 {
164 flags_.set(Flags::PRIVILEGED);
165
166 return *this;
167 }
168
169 inline MethodRegistrator& MethodRegistrator::withNoReply()
170 {
171 flags_.set(Flags::METHOD_NO_REPLY);
172
173 return *this;
174 }
175
176 /*** ----------------- ***/
177 /*** SignalRegistrator ***/
178 /*** ----------------- ***/
179
180 inline SignalRegistrator::SignalRegistrator(IObject& object, const std::string& signalName)
181 : object_(object)
182 , signalName_(signalName)
183 , exceptions_(std::uncaught_exceptions())
184 {
185 }
186
187 inline SignalRegistrator::~SignalRegistrator() noexcept(false) // since C++11, destructors must
188 { // explicitly be allowed to throw
189 // Don't register the signal if SignalRegistrator threw an exception in one of its methods
190 if (std::uncaught_exceptions() != exceptions_)
191 return;
192
193 assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
194
195 // registerSignal() can throw. But as the SignalRegistrator shall always be used as an unnamed,
196 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
197 // shall never happen. I.e. it should not happen that this destructor is directly called
198 // in the stack-unwinding process of another flying exception (which would lead to immediate
199 // termination). It can be called indirectly in the destructor of another object, but that's
200 // fine and safe provided that the caller catches exceptions thrown from here.
201 // Therefore, we can allow registerSignal() to throw even if we are in the destructor.
202 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
203 // to the exception thrown from here if the caller is a destructor itself.
204 object_.registerSignal( interfaceName_
205 , std::move(signalName_)
206 , std::move(signalSignature_)
207 , std::move(paramNames_)
208 , std::move(flags_) );
209 }
210
211 inline SignalRegistrator& SignalRegistrator::onInterface(std::string interfaceName)
212 {
213 interfaceName_ = std::move(interfaceName);
214
215 return *this;
216 }
217
218 template <typename... _Args>
219 inline SignalRegistrator& SignalRegistrator::withParameters()
220 {
221 signalSignature_ = signature_of_function_input_arguments<void(_Args...)>::str();
222
223 return *this;
224 }
225
226 template <typename... _Args>
227 inline SignalRegistrator& SignalRegistrator::withParameters(std::vector<std::string> paramNames)
228 {
229 paramNames_ = std::move(paramNames);
230
231 return withParameters<_Args...>();
232 }
233
234 template <typename... _Args, typename... _String>
235 inline SignalRegistrator& SignalRegistrator::withParameters(_String... paramNames)
236 {
237 static_assert(std::conjunction_v<std::is_convertible<_String, std::string>...>, "Parameter names must be (convertible to) strings");
238 static_assert(sizeof...(_Args) == sizeof...(_String), "Numbers of signal parameters and their names don't match");
239
240 return withParameters<_Args...>({paramNames...});
241 }
242
243 inline SignalRegistrator& SignalRegistrator::markAsDeprecated()
244 {
245 flags_.set(Flags::DEPRECATED);
246
247 return *this;
248 }
249
250 /*** ------------------- ***/
251 /*** PropertyRegistrator ***/
252 /*** ------------------- ***/
253
254 inline PropertyRegistrator::PropertyRegistrator(IObject& object, const std::string& propertyName)
255 : object_(object)
256 , propertyName_(propertyName)
257 , exceptions_(std::uncaught_exceptions())
258 {
259 }
260
261 inline PropertyRegistrator::~PropertyRegistrator() noexcept(false) // since C++11, destructors must
262 { // explicitly be allowed to throw
263 // Don't register the property if PropertyRegistrator threw an exception in one of its methods
264 if (std::uncaught_exceptions() != exceptions_)
265 return;
266
267 assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
268
269 // registerProperty() can throw. But as the PropertyRegistrator shall always be used as an unnamed,
270 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
271 // shall never happen. I.e. it should not happen that this destructor is directly called
272 // in the stack-unwinding process of another flying exception (which would lead to immediate
273 // termination). It can be called indirectly in the destructor of another object, but that's
274 // fine and safe provided that the caller catches exceptions thrown from here.
275 // Therefore, we can allow registerProperty() to throw even if we are in the destructor.
276 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
277 // to the exception thrown from here if the caller is a destructor itself.
278 object_.registerProperty( interfaceName_
279 , propertyName_
280 , propertySignature_
281 , std::move(getter_)
282 , std::move(setter_)
283 , flags_ );
284 }
285
286 inline PropertyRegistrator& PropertyRegistrator::onInterface(std::string interfaceName)
287 {
288 interfaceName_ = std::move(interfaceName);
289
290 return *this;
291 }
292
293 template <typename _Function>
294 inline PropertyRegistrator& PropertyRegistrator::withGetter(_Function&& callback)
295 {
296 static_assert(function_traits<_Function>::arity == 0, "Property getter function must not take any arguments");
297 static_assert(!std::is_void<function_result_t<_Function>>::value, "Property getter function must return property value");
298
299 if (propertySignature_.empty())
300 propertySignature_ = signature_of_function_output_arguments<_Function>::str();
301
302 getter_ = [callback = std::forward<_Function>(callback)](PropertyGetReply& reply)
303 {
304 // Get the propety value and serialize it into the pre-constructed reply message
305 reply << callback();
306 };
307
308 return *this;
309 }
310
311 template <typename _Function>
312 inline PropertyRegistrator& PropertyRegistrator::withSetter(_Function&& callback)
313 {
314 static_assert(function_traits<_Function>::arity == 1, "Property setter function must take one parameter - the property value");
315 static_assert(std::is_void<function_result_t<_Function>>::value, "Property setter function must not return any value");
316
317 if (propertySignature_.empty())
318 propertySignature_ = signature_of_function_input_arguments<_Function>::str();
319
320 setter_ = [callback = std::forward<_Function>(callback)](PropertySetCall& call)
321 {
322 // Default-construct property value
323 using property_type = function_argument_t<_Function, 0>;
324 std::decay_t<property_type> property;
325
326 // Deserialize property value from the incoming call message
327 call >> property;
328
329 // Invoke setter with the value
330 callback(property);
331 };
332
333 return *this;
334 }
335
336 inline PropertyRegistrator& PropertyRegistrator::markAsDeprecated()
337 {
338 flags_.set(Flags::DEPRECATED);
339
340 return *this;
341 }
342
343 inline PropertyRegistrator& PropertyRegistrator::markAsPrivileged()
344 {
345 flags_.set(Flags::PRIVILEGED);
346
347 return *this;
348 }
349
350 inline PropertyRegistrator& PropertyRegistrator::withUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
351 {
352 flags_.set(behavior);
353
354 return *this;
355 }
356
357 /*** -------------------- ***/
358 /*** InterfaceFlagsSetter ***/
359 /*** -------------------- ***/
360
361 inline InterfaceFlagsSetter::InterfaceFlagsSetter(IObject& object, const std::string& interfaceName)
362 : object_(object)
363 , interfaceName_(interfaceName)
364 , exceptions_(std::uncaught_exceptions())
365 {
366 }
367
368 inline InterfaceFlagsSetter::~InterfaceFlagsSetter() noexcept(false) // since C++11, destructors must
369 { // explicitly be allowed to throw
370 // Don't set any flags if InterfaceFlagsSetter threw an exception in one of its methods
371 if (std::uncaught_exceptions() != exceptions_)
372 return;
373
374 // setInterfaceFlags() can throw. But as the InterfaceFlagsSetter shall always be used as an unnamed,
375 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
376 // shall never happen. I.e. it should not happen that this destructor is directly called
377 // in the stack-unwinding process of another flying exception (which would lead to immediate
378 // termination). It can be called indirectly in the destructor of another object, but that's
379 // fine and safe provided that the caller catches exceptions thrown from here.
380 // Therefore, we can allow setInterfaceFlags() to throw even if we are in the destructor.
381 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
382 // to the exception thrown from here if the caller is a destructor itself.
383 object_.setInterfaceFlags(interfaceName_, std::move(flags_));
384 }
385
386 inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsDeprecated()
387 {
388 flags_.set(Flags::DEPRECATED);
389
390 return *this;
391 }
392
393 inline InterfaceFlagsSetter& InterfaceFlagsSetter::markAsPrivileged()
394 {
395 flags_.set(Flags::PRIVILEGED);
396
397 return *this;
398 }
399
400 inline InterfaceFlagsSetter& InterfaceFlagsSetter::withNoReplyMethods()
401 {
402 flags_.set(Flags::METHOD_NO_REPLY);
403
404 return *this;
405 }
406
407 inline InterfaceFlagsSetter& InterfaceFlagsSetter::withPropertyUpdateBehavior(Flags::PropertyUpdateBehaviorFlags behavior)
408 {
409 flags_.set(behavior);
410
411 return *this;
412 }
413
414 /*** ------------- ***/
415 /*** SignalEmitter ***/
416 /*** ------------- ***/
417
418 inline SignalEmitter::SignalEmitter(IObject& object, const std::string& signalName)
419 : object_(object)
420 , signalName_(signalName)
421 , exceptions_(std::uncaught_exceptions())
422 {
423 }
424
425 inline SignalEmitter::~SignalEmitter() noexcept(false) // since C++11, destructors must
426 { // explicitly be allowed to throw
427 // Don't emit the signal if SignalEmitter threw an exception in one of its methods
428 if (std::uncaught_exceptions() != exceptions_)
429 return;
430
431 // emitSignal() can throw. But as the SignalEmitter shall always be used as an unnamed,
432 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
433 // shall never happen. I.e. it should not happen that this destructor is directly called
434 // in the stack-unwinding process of another flying exception (which would lead to immediate
435 // termination). It can be called indirectly in the destructor of another object, but that's
436 // fine and safe provided that the caller catches exceptions thrown from here.
437 // Therefore, we can allow emitSignal() to throw even if we are in the destructor.
438 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
439 // to the exception thrown from here if the caller is a destructor itself.
440 object_.emitSignal(signal_);
441 }
442
443 inline SignalEmitter& SignalEmitter::onInterface(const std::string& interfaceName)
444 {
445 signal_ = object_.createSignal(interfaceName, signalName_);
446
447 return *this;
448 }
449
450 template <typename... _Args>
451 inline void SignalEmitter::withArguments(_Args&&... args)
452 {
453 assert(signal_.isValid()); // onInterface() must be placed/called prior to withArguments()
454
455 detail::serialize_pack(signal_, std::forward<_Args>(args)...);
456 }
457
458 /*** ------------- ***/
459 /*** MethodInvoker ***/
460 /*** ------------- ***/
461
462 inline MethodInvoker::MethodInvoker(IProxy& proxy, const std::string& methodName)
463 : proxy_(proxy)
464 , methodName_(methodName)
465 , exceptions_(std::uncaught_exceptions())
466 {
467 }
468
469 inline MethodInvoker::~MethodInvoker() noexcept(false) // since C++11, destructors must
470 { // explicitly be allowed to throw
471 // Don't call the method if it has been called already or if MethodInvoker
472 // threw an exception in one of its methods
473 if (methodCalled_ || std::uncaught_exceptions() != exceptions_)
474 return;
475
476 // callMethod() can throw. But as the MethodInvoker shall always be used as an unnamed,
477 // temporary object, i.e. not as a stack-allocated object, the double-exception situation
478 // shall never happen. I.e. it should not happen that this destructor is directly called
479 // in the stack-unwinding process of another flying exception (which would lead to immediate
480 // termination). It can be called indirectly in the destructor of another object, but that's
481 // fine and safe provided that the caller catches exceptions thrown from here.
482 // Therefore, we can allow callMethod() to throw even if we are in the destructor.
483 // Bottomline is, to be on the safe side, the caller must take care of catching and reacting
484 // to the exception thrown from here if the caller is a destructor itself.
485 proxy_.callMethod(method_, timeout_);
486 }
487
488 inline MethodInvoker& MethodInvoker::onInterface(const std::string& interfaceName)
489 {
490 method_ = proxy_.createMethodCall(interfaceName, methodName_);
491
492 return *this;
493 }
494
495 inline MethodInvoker& MethodInvoker::withTimeout(uint64_t usec)
496 {
497 timeout_ = usec;
498
499 return *this;
500 }
501
502 template <typename _Rep, typename _Period>
503 inline MethodInvoker& MethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
504 {
505 auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
506 return withTimeout(microsecs.count());
507 }
508
509 template <typename... _Args>
510 inline MethodInvoker& MethodInvoker::withArguments(_Args&&... args)
511 {
512 assert(method_.isValid()); // onInterface() must be placed/called prior to this function
513
514 detail::serialize_pack(method_, std::forward<_Args>(args)...);
515
516 return *this;
517 }
518
519 template <typename... _Args>
520 inline void MethodInvoker::storeResultsTo(_Args&... args)
521 {
522 assert(method_.isValid()); // onInterface() must be placed/called prior to this function
523
524 auto reply = proxy_.callMethod(method_, timeout_);
525 methodCalled_ = true;
526
527 detail::deserialize_pack(reply, args...);
528 }
529
530 inline void MethodInvoker::dontExpectReply()
531 {
532 assert(method_.isValid()); // onInterface() must be placed/called prior to this function
533
534 method_.dontExpectReply();
535 }
536
537 /*** ------------------ ***/
538 /*** AsyncMethodInvoker ***/
539 /*** ------------------ ***/
540
541 inline AsyncMethodInvoker::AsyncMethodInvoker(IProxy& proxy, const std::string& methodName)
542 : proxy_(proxy)
543 , methodName_(methodName)
544 {
545 }
546
547 inline AsyncMethodInvoker& AsyncMethodInvoker::onInterface(const std::string& interfaceName)
548 {
549 method_ = proxy_.createMethodCall(interfaceName, methodName_);
550
551 return *this;
552 }
553
554 inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(uint64_t usec)
555 {
556 timeout_ = usec;
557
558 return *this;
559 }
560
561 template <typename _Rep, typename _Period>
562 inline AsyncMethodInvoker& AsyncMethodInvoker::withTimeout(const std::chrono::duration<_Rep, _Period>& timeout)
563 {
564 auto microsecs = std::chrono::duration_cast<std::chrono::microseconds>(timeout);
565 return withTimeout(microsecs.count());
566 }
567
568 template <typename... _Args>
569 inline AsyncMethodInvoker& AsyncMethodInvoker::withArguments(_Args&&... args)
570 {
571 assert(method_.isValid()); // onInterface() must be placed/called prior to this function
572
573 detail::serialize_pack(method_, std::forward<_Args>(args)...);
574
575 return *this;
576 }
577
578 template <typename _Function>
579 PendingAsyncCall AsyncMethodInvoker::uponReplyInvoke(_Function&& callback)
580 {
581 assert(method_.isValid()); // onInterface() must be placed/called prior to this function
582
583 auto asyncReplyHandler = [callback = std::forward<_Function>(callback)](MethodReply& reply, const Error* error)
584 {
585 // Create a tuple of callback input arguments' types, which will be used
586 // as a storage for the argument values deserialized from the message.
587 tuple_of_function_input_arg_types_t<_Function> args;
588
589 // Deserialize input arguments from the message into the tuple (if no error occurred).
590 if (error == nullptr)
591 reply >> args;
592
593 // Invoke callback with input arguments from the tuple.
594 sdbus::apply(callback, error, args);
595 };
596
597 return proxy_.callMethod(method_, std::move(asyncReplyHandler), timeout_);
598 }
599
600 /*** ---------------- ***/
601 /*** SignalSubscriber ***/
602 /*** ---------------- ***/
603
604 inline SignalSubscriber::SignalSubscriber(IProxy& proxy, const std::string& signalName)
605 : proxy_(proxy)
606 , signalName_(signalName)
607 {
608 }
609
610 inline SignalSubscriber& SignalSubscriber::onInterface(std::string interfaceName)
611 {
612 interfaceName_ = std::move(interfaceName);
613
614 return *this;
615 }
616
617 template <typename _Function>
618 inline void SignalSubscriber::call(_Function&& callback)
619 {
620 assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
621
622 proxy_.registerSignalHandler( interfaceName_
623 , signalName_
624 , [callback = std::forward<_Function>(callback)](Signal& signal)
625 {
626 // Create a tuple of callback input arguments' types, which will be used
627 // as a storage for the argument values deserialized from the signal message.
628 tuple_of_function_input_arg_types_t<_Function> signalArgs;
629
630 // Deserialize input arguments from the signal message into the tuple
631 signal >> signalArgs;
632
633 // Invoke callback with input arguments from the tuple.
634 sdbus::apply(callback, signalArgs);
635 });
636 }
637
638 /*** -------------- ***/
639 /*** PropertyGetter ***/
640 /*** -------------- ***/
641
642 inline PropertyGetter::PropertyGetter(IProxy& proxy, const std::string& propertyName)
643 : proxy_(proxy)
644 , propertyName_(propertyName)
645 {
646 }
647
648 inline sdbus::Variant PropertyGetter::onInterface(const std::string& interfaceName)
649 {
650 sdbus::Variant var;
651 proxy_
652 .callMethod("Get")
653 .onInterface("org.freedesktop.DBus.Properties")
654 .withArguments(interfaceName, propertyName_)
655 .storeResultsTo(var);
656 return var;
657 }
658
659 /*** -------------- ***/
660 /*** PropertySetter ***/
661 /*** -------------- ***/
662
663 inline PropertySetter::PropertySetter(IProxy& proxy, const std::string& propertyName)
664 : proxy_(proxy)
665 , propertyName_(propertyName)
666 {
667 }
668
669 inline PropertySetter& PropertySetter::onInterface(std::string interfaceName)
670 {
671 interfaceName_ = std::move(interfaceName);
672
673 return *this;
674 }
675
676 template <typename _Value>
677 inline void PropertySetter::toValue(const _Value& value)
678 {
679 PropertySetter::toValue(sdbus::Variant{value});
680 }
681
682 inline void PropertySetter::toValue(const sdbus::Variant& value)
683 {
684 assert(!interfaceName_.empty()); // onInterface() must be placed/called prior to this function
685
686 proxy_
687 .callMethod("Set")
688 .onInterface("org.freedesktop.DBus.Properties")
689 .withArguments(interfaceName_, propertyName_, value);
690 }
691
692}
693
694#endif /* SDBUS_CPP_CONVENIENCEAPICLASSES_INL_ */
Definition Types.h:54