AUI Framework  master
Cross-platform module-based framework for developing C++20 desktop applications
ADBus.h
1/*
2 * AUI Framework - Declarative UI toolkit for modern C++20
3 * Copyright (C) 2020-2024 Alex2772 and Contributors
4 *
5 * SPDX-License-Identifier: MPL-2.0
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#pragma once
13
14#include <AUI/Common/AObject.h>
15#include <AUI/Traits/concepts.h>
16#include <AUI/Util/APimpl.h>
17#include <AUI/Util/ARaiiHelper.h>
18#include "AUI/Common/AException.h"
19#include <dbus/dbus.h>
20#include <list>
21
22struct DBusError;
23struct DBusConnection;
24
25namespace aui::dbus {
26 class ObjectPath: public std::string {
27 using std::string::string;
28 };
29
30 template<typename T>
31 struct converter;
32
33 template<typename T>
34 concept convertible = requires(T t) {
35 { converter<T>::iter_append(static_cast<DBusMessageIter*>(nullptr), t) };
36 { converter<T>::signature } -> aui::convertible_to<std::string>;
37 };
38
39 struct Unknown {
40 public:
41 explicit Unknown(const DBusMessageIter& mIter) : mIter(mIter) {}
42
43 template<convertible T>
44 T as();
45
46 template<convertible T>
47 bool as(T& v);
48
49 private:
50 DBusMessageIter mIter;
51 };
52
53 using VariantImpl = std::variant<
54 std::nullopt_t, // DBUS_TYPE_INVALID
55 std::uint8_t, // DBUS_TYPE_BYTE
56 bool, // DBUS_TYPE_BOOLEAN
57 std::int16_t, // DBUS_TYPE_INT16
58 std::uint16_t, // DBUS_TYPE_UINT16
59 std::int32_t, // DBUS_TYPE_INT32
60 std::uint32_t, // DBUS_TYPE_UINT32
61 std::int64_t, // DBUS_TYPE_INT64
62 std::uint64_t, // DBUS_TYPE_UINT64
63 double, // DBUS_TYPE_DOUBLE
64 std::string, // DBUS_TYPE_STRING
65 ObjectPath, // DBUS_TYPE_OBJECT_PATH
66 AVector<Unknown>, // DBUS_TYPE_ARRAY
67 AVector<std::uint8_t> // DBUS_TYPE_ARRAY
68 >;
69 struct Variant: VariantImpl {
70 using VariantImpl::variant;
71 Variant(): VariantImpl(std::nullopt) {}
72 };
73
74 template<typename T>
75 concept convertible_or_void = convertible<T> || std::is_void_v<T>;
76
77
78 template<convertible T>
79 void iter_append(DBusMessageIter* iter, const T& value) {
80 converter<T>::iter_append(iter, value);
81 }
82 template<convertible T>
83 T iter_get(DBusMessageIter* iter) {
84 return converter<T>::iter_get(iter);
85 }
86
87 template<convertible T>
88 T Unknown::as() {
89 return aui::dbus::iter_get<T>(&mIter);
90 }
91
92 template<convertible T>
93 bool Unknown::as(T& v) {
94 if (auto got = dbus_message_iter_get_arg_type(&mIter); got != converter<T>::signature[0]) {
95 return false;
96 }
97
98 v = aui::dbus::iter_get<T>(&mIter);
99 return true;
100 }
101
102
103 namespace impl {
104 template<typename T, char dbusType = DBUS_TYPE_INVALID>
106 static inline std::string signature = fmt::format("{}", dbusType);
107
108 static void iter_append(DBusMessageIter* iter, const T& t) {
109 dbus_message_iter_append_basic(iter, dbusType, &t);
110 }
111 static T iter_get(DBusMessageIter* iter) {
112 if (auto got = dbus_message_iter_get_arg_type(iter); got != dbusType) {
113 throw AException("type error: expected '{:c}', got '{:c}'"_format(dbusType, got));
114 }
115 T t;
116 dbus_message_iter_get_basic(iter, &t);
117 return t;
118 }
119 };
120 }
121
122 template<> struct converter<std::nullopt_t> { // -> DBUS_TYPE_INVALID
123 static inline std::string signature = DBUS_TYPE_INVALID_AS_STRING;
124
125 static void iter_append(DBusMessageIter* iter, std::nullopt_t) {
126 int v = 0;
127 dbus_message_iter_append_basic(iter, DBUS_TYPE_INVALID, &v);
128 }
129
130 static auto iter_get(DBusMessageIter* iter) {
131 if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_INVALID) {
132 throw AException("type error: expected '{:c}', got '{:c}'"_format(DBUS_TYPE_INVALID, got));
133 }
134 return std::nullopt;
135 }
136 };
137
138 template<> struct converter<std::uint8_t >: impl::basic_converter<std::uint8_t , DBUS_TYPE_BYTE> {};
139 template<> struct converter<std::int16_t >: impl::basic_converter<std::int16_t , DBUS_TYPE_INT16> {};
140 template<> struct converter<std::uint16_t >: impl::basic_converter<std::uint16_t , DBUS_TYPE_UINT16> {};
141 template<> struct converter<std::int32_t >: impl::basic_converter<std::int32_t , DBUS_TYPE_INT32> {};
142 template<> struct converter<std::uint32_t >: impl::basic_converter<std::uint32_t , DBUS_TYPE_UINT32> {};
143 template<> struct converter<std::int64_t >: impl::basic_converter<std::int64_t , DBUS_TYPE_INT64> {};
144 template<> struct converter<std::uint64_t >: impl::basic_converter<std::uint64_t , DBUS_TYPE_UINT64> {};
145 template<> struct converter<double >: impl::basic_converter<double , DBUS_TYPE_DOUBLE> {};
147 template<> struct converter<bool> {
148 static inline std::string signature = DBUS_TYPE_BOOLEAN_AS_STRING;
149
150 static void iter_append(DBusMessageIter* iter, const bool& t) {
152 }
153 static bool iter_get(DBusMessageIter* iter) {
155 }
156 };
157
158 template<> struct converter<std::string>: converter<const char*> {
159 static void iter_append(DBusMessageIter* iter, const std::string& t) {
161 }
162 static std::string iter_get(DBusMessageIter* iter) {
164 }
165 };
166
167 template<> struct converter<AString>: converter<std::string> {
168 static void iter_append(DBusMessageIter* iter, const AString& t) {
169 converter<std::string>::iter_append(iter, t.toStdString());
170 }
171 static AString iter_get(DBusMessageIter* iter) {
173 }
174 };
175 template<> struct converter<ObjectPath>: impl::basic_converter<const char*, DBUS_TYPE_OBJECT_PATH> {
177 static void iter_append(DBusMessageIter* iter, const ObjectPath& t) {
178 super::iter_append(iter, t.c_str());
179 }
180
181 static ObjectPath iter_get(DBusMessageIter* iter) {
182 return super::iter_get(iter);
183 }
184 };
185 template<std::size_t N> struct converter<char[N]>: converter<const char*> {};
186
187
188 template<convertible T>
189 struct converter<AVector<T>> {
190 static inline std::string signature = fmt::format("a{}", converter<T>::signature);
191
192 static void iter_append(DBusMessageIter* iter, const AVector<T>& t) {
193 DBusMessageIter sub;
194 const auto s = converter<T>::signature.c_str();
195 if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, s, &sub)) {
196 throw AException("dbus_message_iter_open_container failed");
197 }
198 ARaiiHelper h = [&] {
199 dbus_message_iter_close_container(iter, &sub);
200 };
201
202 for (const auto& v : t) {
203 aui::dbus::iter_append(&sub, v);
204 }
205 }
206 static AVector<T> iter_get(DBusMessageIter* iter) {
207 if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_ARRAY) {
208 throw AException("type error: array expected, got '{:c}'"_format(got));
209 }
210 DBusMessageIter sub;
211 dbus_message_iter_recurse(iter, &sub);
212
213 AVector<T> result;
214 for (;;) {
215 result << aui::dbus::iter_get<T>(&sub);
216 if (!dbus_message_iter_next(&sub)) break;
217 }
218
219 return result;
220 }
221 };
222
223 template<convertible K, convertible V>
224 struct converter<AMap<K, V>> {
225 static inline std::string signature = fmt::format("a{{{}{}}}", converter<K>::signature, converter<V>::signature);
226
227 static void iter_append(DBusMessageIter* iter, const AMap<K, V>& t) {
228 DBusMessageIter sub;
229 if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, fmt::format("{{{}{}}}", converter<K>::signature, converter<V>::signature).c_str(), &sub)) {
230 throw AException("dbus_message_iter_open_container failed");
231 }
232 ARaiiHelper h = [&] {
233 dbus_message_iter_close_container(iter, &sub);
234 };
235
236 for (const auto&[k, v] : t) {
237 DBusMessageIter item;
238 dbus_message_iter_open_container(&sub, DBUS_TYPE_DICT_ENTRY, nullptr, &item);
239 ARaiiHelper r = [&] {
240 dbus_message_iter_close_container(&sub, &item);
241 };
242
243 aui::dbus::iter_append(&item, k);
244 aui::dbus::iter_append(&item, v);
245 }
246 }
247 static AMap<K, V> iter_get(DBusMessageIter* iter) {
248 if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_ARRAY) {
249 throw AException("type error: array expected, got '{:c}'"_format(got));
250 }
251 DBusMessageIter sub;
252 dbus_message_iter_recurse(iter, &sub);
253
254 AMap<K, V> result;
255 for (;;) {
256 AUI_ASSERT(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
257 DBusMessageIter item;
258 dbus_message_iter_recurse(&sub, &item);
259 auto k = aui::dbus::iter_get<K>(&item);
260 if (!dbus_message_iter_next(&item)) {
261 throw AException("bad dict");
262 }
263 auto v = aui::dbus::iter_get<V>(&item);
264 result[k] = v;
265 if (!dbus_message_iter_next(&sub)) break;
266 }
267
268 return result;
269 }
270 };
271
272 template<convertible... Types>
273 struct converter<std::tuple<Types...>> {
274 static inline std::string signature = "(" + (... + converter<Types>::signature) + ")";
275
276 static void iter_append(DBusMessageIter* iter, const std::tuple<Types...>& t) {
277 if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_STRUCT) {
278 throw AException("type error: struct expected, got '{:c}'"_format(got));
279 }
280
281 DBusMessageIter sub;
282 dbus_message_iter_recurse(iter, &sub);
283
284 std::apply([&](const auto&... args){
285 (..., aui::dbus::iter_append(&sub, args));
286 }, t);
287 }
288
289 static std::tuple<Types...> iter_get(DBusMessageIter* iter) {
290 if (auto got = dbus_message_iter_get_arg_type(iter); got != DBUS_TYPE_STRUCT) {
291 throw AException("type error: struct expected, got '{:c}'"_format(got));
292 }
293
294 DBusMessageIter sub;
295 dbus_message_iter_recurse(iter, &sub);
296
297 bool hasNext = true;
298 return aui::tuple_visitor<std::tuple<Types...>>::for_each_make_tuple([&]<typename T>() {
299 if (!hasNext) {
300 throw AException("too few arguments");
301 }
302 auto v = aui::dbus::iter_get<T>(&sub);
303 hasNext = dbus_message_iter_next(&sub);
304 return v;
305 });
306 }
307 };
308
309 template<>
310 struct API_AUI_VIEWS converter<Variant> {
311 static inline std::string signature = "v";
312
313 static void iter_append(DBusMessageIter* iter, const Variant& t);
314
315 static Variant iter_get(DBusMessageIter* iter);
316 };
317
318 template<>
319 struct API_AUI_VIEWS converter<Unknown> {
320 static inline std::string signature = "";
321
322 static void iter_append(DBusMessageIter* iter, const Unknown& t) {
323 // TODO
324 AUI_ASSERT(0);
325 }
326
327 static Unknown iter_get(DBusMessageIter* iter);
328 };
329}
330
335class API_AUI_VIEWS ADBus: public aui::noncopyable {
336public:
337
341 class Exception: public AException {
342 using AException::AException;
343 };
344
345 static ADBus& inst();
346
347 template<aui::dbus::convertible_or_void Return = void, aui::dbus::convertible... Args>
348 Return callBlocking(const AString& bus,
349 const AString& path,
350 const AString& interface,
351 const AString& method,
352 const Args&... args) {
353 auto msg = aui::ptr::make_unique_with_deleter(dbus_message_new_method_call(bus.toStdString().c_str(),
354 path.toStdString().c_str(),
355 interface.toStdString().c_str(),
356 method.toStdString().c_str()),
357 dbus_message_unref);
358
359 auto formatError = [&](const AString& info) {
360 return "unable to invoke {};{};{};{}: {}"_format(bus, path, interface, method, info);
361 };
362
363 if (!msg) {
364 throw ADBus::Exception(formatError("message is null"));
365 }
366
367 DBusMessageIter dbusArgs;
368 dbus_message_iter_init_append(msg.get(), &dbusArgs);
369
370 aui::parameter_pack::for_each([&](const auto& v) {
371 aui::dbus::iter_append(&dbusArgs, v);
372 }, args...);
373
374 auto pending = aui::ptr::make_unique_with_deleter([&] {
375 DBusPendingCall* pending;
376 if (!dbus_connection_send_with_reply(mConnection, msg.get(), &pending, -1)) { // -1 is default timeout
377 throw ADBus::Exception(formatError("dbus_connection_send_with_reply failed"));
378 }
379 return pending;
380 }(), dbus_pending_call_unref);
381
382 // block until we receive a reply
383 dbus_pending_call_block(pending.get());
384
385 // get the reply message
386 msg.reset(dbus_pending_call_steal_reply(pending.get()));
387
388 if (dbus_message_get_type(msg.get()) == DBUS_MESSAGE_TYPE_ERROR) {
389 if (auto e = dbus_message_get_error_name(msg.get())) {
390 throw ADBus::Exception(formatError(e));
391 }
392 throw ADBus::Exception(formatError("dbus replied unknown error"));
393 }
394
395 if constexpr (!std::is_void_v<Return>) {
396 if (!dbus_message_iter_init(msg.get(), &dbusArgs)) {
397 throw ADBus::Exception(formatError("dbus replied no arguments"));
398 }
399 return aui::dbus::iter_get<Return>(&dbusArgs);
400 }
401 }
402
403 template<aui::not_overloaded_lambda Callback>
404 std::function<void()> addSignalListener(aui::dbus::ObjectPath object, const AString& interface, const AString& signal, Callback&& callback) {
405 return addListener([object = std::move(object),
406 interface = interface.toStdString(),
407 signal = signal.toStdString(),
408 callback = std::forward<Callback>(callback)](DBusMessage* msg) {
409 if (dbus_message_is_signal(msg, DBUS_INTERFACE_LOCAL, "Disconnected")) {
410 return DBUS_HANDLER_RESULT_HANDLED;
411 }
412
413 if (!dbus_message_is_signal(msg, interface.c_str(), signal.c_str())) {
414 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
415 }
416
417 if (object != std::string_view(dbus_message_get_path(msg))) {
418 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
419 }
420
421 using argz = typename aui::lambda_info<Callback>::args;
422 if constexpr (std::tuple_size_v<argz> > 0) {
423 DBusMessageIter dbusArgs;
424 if (!dbus_message_iter_init(msg, &dbusArgs)) {
425 throw ADBus::Exception("dbus replied no arguments");
426 }
427
428 bool hasNext = true;
429 auto args = aui::tuple_visitor<argz>::for_each_make_tuple([&]<typename T>() {
430 if (!hasNext) {
431 throw ADBus::Exception("too few arguments");
432 }
433 auto v = aui::dbus::iter_get<T>(&dbusArgs);
434 hasNext = dbus_message_iter_next(&dbusArgs);
435 return v;
436 });
437
438 std::apply(callback, std::move(args));
439 } else {
440 callback();
441 }
442
443
444 return DBUS_HANDLER_RESULT_HANDLED;
445 });
446 }
447
448 void processMessages();
449
450private:
451 struct RawMessageListener {
452 ADBus* parent;
453 using Callback = std::function<DBusHandlerResult(DBusMessage* message)>;
454 Callback function;
455 };
456 std::list<RawMessageListener> mListeners; // guarantees safe pointers to it's elements
457 aui::fast_pimpl<DBusError, sizeof(void*) * 3 + 20, alignof(void*)> mError;
458 DBusConnection* mConnection = nullptr;
459 std::atomic_bool mProcessingScheduled = false;
460
461 ADBus();
462 ~ADBus();
463
464 template <aui::invocable Callback>
465 void throwExceptionOnError(Callback&& callback);
466 std::function<void()> addListener(RawMessageListener::Callback listener);
467 static dbus_bool_t addWatch(DBusWatch* watch, void* data);
468
469 static DBusHandlerResult listener(DBusConnection *connection,
470 DBusMessage *message,
471 void *user_data) noexcept;
472 static void deleter(void* userData) noexcept;
473};
Exception thrown on dbus errors.
Definition: ADBus.h:341
IPC on freedesktop linux.
Definition: ADBus.h:335
Abstract AUI exception.
Definition: AException.h:29
A std::map with AUI extensions.
Definition: AMap.h:218
Definition: ARaiiHelper.h:17
Represents a Unicode character string.
Definition: AString.h:37
std::string toStdString() const noexcept
Definition: AString.cpp:338
A std::vector with AUI extensions.
Definition: AVector.h:38
Definition: ADBus.h:26
API_AUI_CORE const ACommandLineArgs & args() noexcept
Definition: OSAndroid.cpp:29
#define AUI_ASSERT(condition)
Asserts that the passed condition evaluates to true.
Definition: Assert.h:55
Definition: ADBus.h:39
Definition: ADBus.h:69
Definition: ADBus.h:31
Definition: ADBus.h:105
Utility wrapper implementing the stack-allocated (fast) pimpl idiom.
Definition: APimpl.h:31
Definition: members.h:19
Forbids copy of your class.
Definition: values.h:40
static _unique< T, Deleter > make_unique_with_deleter(T *ptr, Deleter deleter=Deleter{})
Creates unique_ptr from raw pointer and a deleter.
Definition: SharedPtrTypes.h:125
Definition: parameter_pack.h:76