From: Radoslaw Cybulski Date: Fri, 8 Sep 2017 08:48:31 +0000 (+0200) Subject: Add server side dbus wrappers and tests X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7cfcc8ea742a90f8e48084e56dd75f3fa3e00858;p=platform%2Fcore%2Faccessibility%2Funiversal-switch.git Add server side dbus wrappers and tests Add dbus wrappers for server-side functionality (publishing interfaces, emiting signals and so on). Add tests for all functionality. Change-Id: I3f5ddcdc72f372b708ae99f15d0c9b63c9264f55 --- diff --git a/src/Atspi.cpp b/src/Atspi.cpp index 85d0f23..84b8b49 100644 --- a/src/Atspi.cpp +++ b/src/Atspi.cpp @@ -4,6 +4,8 @@ #include #include +using namespace DBus; + #define PRINT_ERROR_AND_FREE(error) \ do { \ if(error) { \ @@ -64,20 +66,20 @@ template struct InterfaceNameFromType { #define ADD_INTROSPECTION_FUNCTIONS(TYPE, NAME) \ template <> const std::string InterfaceNameFromType::interfaceName = \ ATSPI_DBUS_INTERFACE_ ## NAME; \ - static std::string getBusName(const std::shared_ptr &o) \ + std::string Atspi::getBusName(const std::shared_ptr &o) \ { \ return getBusNameImpl(o.get()); \ } \ - static std::string getPath(const std::shared_ptr &o) \ + std::string Atspi::getPath(const std::shared_ptr &o) \ { \ return getPathImpl(o.get()); \ } \ static void convert(std::shared_ptr &r, \ - const AtspiAccessiblePtr &obj) \ + const AtspiAccessiblePtr &obj) \ { \ r = { ATSPI_ ## NAME(g_object_ref(obj.get())), g_object_unref }; \ } \ - void Atspi::get ## TYPE ## Interface(const AtspiAccessiblePtr &obj, \ + void Atspi::get ## TYPE ## Interface(const AtspiAccessiblePtr &obj, \ AsyncCallback> callback) const \ { \ getInterface(obj, std::move(callback)); \ @@ -121,13 +123,13 @@ Atspi::Atspi() { ConnectAtClient(); - DBus proxy{"org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus"}; + DBusClient proxy{"org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus"}; auto addr = proxy.method("GetAddress").call(); if (!addr) { ERROR("failed"); } else { - eldbusConnection = { eldbus_address_connection_get((*addr).c_str()), eldbus_connection_unref }; + eldbusConnection = DBus::EldbusConnectionCallbackHandle{ eldbus_address_connection_get(std::get<0>(addr).c_str()), eldbus_connection_unref }; } } @@ -292,19 +294,30 @@ AtspiAccessiblePtr Atspi::getObjectInRelation(const AtspiAccessiblePtr &accessib return {relationObj, g_object_unref}; } -template DBus Atspi::getProxy(const std::shared_ptr &obj, const std::string &interface) const +template DBusClient Atspi::getProxy(const std::shared_ptr &obj, const std::string &interface) const { auto bus = getBusName(obj); auto path = getPath(obj); - return DBus { bus, path, interface, eldbusConnection }; + return DBusClient { bus, path, interface, eldbusConnection }; } +template struct function_return_type_trait { + using type = ValueOrError; + using stripped_type = T; +}; +template struct function_return_type_trait> { + using type = ValueOrError; +}; +template struct FunctionCallbackType; +template struct FunctionCallbackType { + using type = std::function::type)>; +}; template struct PropertyCallback; template struct PropertyCallback { - using VariantReturnType = EldbusVariant; + using VariantReturnType = ValueOrError>; using RealReturnType = RetType; - using CallType = VariantReturnType(std::string, std::string); + using CallType = EldbusVariant(std::string, std::string); }; template struct PropertySetterCallback; template struct PropertySetterCallback { @@ -312,66 +325,66 @@ template struct PropertySetterCallback { using RealReturnType = void; using CallType = void(std::string, std::string, VariantSetterType); }; -template struct returnType; -template struct returnType { - using type = RetType; -}; template void callFunction( const EldbusConnectionCallbackHandle &eldbusConnection, const std::string &interface, const std::shared_ptr &obj, - const std::string &func_name, - typename detail::AsyncCallbackHelper::type>::type callback, + const std::string &funcName, + typename FunctionCallbackType::type callback, ARGS &&... args ) { - auto bus = getBusName(obj); - auto path = getPath(obj); + auto bus = Atspi::getBusName(obj); + auto path = Atspi::getPath(obj); - auto dbus = DBus { bus, path, interface, eldbusConnection }; - dbus.method(func_name).asyncCall(std::forward(args)..., std::move(callback)); + auto dbus = DBusClient { bus, path, interface, eldbusConnection }; + dbus.method(funcName).asyncCall(std::move(callback), std::forward(args)...); } template void callFunction( const EldbusConnectionCallbackHandle &eldbusConnection, const std::shared_ptr &obj, - const std::string &func_name, - typename detail::AsyncCallbackHelper::type>::type callback, + const std::string &funcName, + typename FunctionCallbackType::type callback, ARGS &&... args ) { callFunction(eldbusConnection, InterfaceNameFromType::interfaceName, - obj, func_name, std::move(callback), std::forward(args)...); + obj, funcName, std::move(callback), std::forward(args)...); } template void getProperty( const EldbusConnectionCallbackHandle &eldbusConnection, const std::string &interface, const std::shared_ptr &obj, - const std::string &func_name, - typename detail::AsyncCallbackHelper::type>::type callback, + const std::string &funcName, + typename FunctionCallbackType::type callback, ARGS &&... args ) { callFunction::CallType>( eldbusConnection, DBUS_INTERFACE_PROPERTIES, obj, "Get", - [callback](Optional::VariantReturnType> val) { - if (!val) callback({}); - else callback(std::move((*val).value)); + [callback](typename PropertyCallback::VariantReturnType val) { + if (!val) { + callback(Error { val.getError() }); + } else { + auto z = std::get<0>(val); + callback(z.value); + } }, - interface, func_name); + interface, funcName); } template void getProperty( const EldbusConnectionCallbackHandle &eldbusConnection, const std::shared_ptr &obj, - const std::string &func_name, - typename detail::AsyncCallbackHelper::type>::type callback, + const std::string &funcName, + typename FunctionCallbackType::type callback, ARGS &&... args ) { getProperty(eldbusConnection, InterfaceNameFromType::interfaceName, - obj, func_name, std::move(callback), std::forward(args)...); + obj, funcName, std::move(callback), std::forward(args)...); } template void setProperty( @@ -470,11 +483,10 @@ void Atspi::getRole(const AtspiAccessiblePtr &accessibleObj, AsyncCallback val) { - if (!val) callback({}); - else callback((AtspiRole)*val); - } - ); + [callback](ValueOrError val) { + if (!val) callback(Error{ val.getError() }); + else callback((AtspiRole)std::get<0>(val)); + }); } void Atspi::getChildrenCount(const AtspiAccessiblePtr &accessibleObj, AsyncCallback callback) const @@ -483,11 +495,9 @@ void Atspi::getChildrenCount(const AtspiAccessiblePtr &accessibleObj, AsyncCallb eldbusConnection, accessibleObj, "ChildCount", - [callback](Optional val) { - if (!val || *val < 0) - callback({}); - else - callback((size_t)*val); + [callback](ValueOrError val) { + if (!val) callback(Error{ val.getError() }); + else callback((size_t)std::get<0>(val)); } ); } @@ -509,29 +519,28 @@ void Atspi::getStateSet(const AtspiAccessiblePtr &accessibleObj, AsyncCallback> data) { - if (data) { + [callback](ValueOrError> data) { + if (!data) { + callback(Error{ data.getError() }); + } else { StateSet s; for (size_t i = 0; i < (size_t)ATSPI_STATE_LAST_DEFINED; ++i) { - if ((*data)[i >> 5] & (1 << (i & 31))) { + if (std::get<0>(data)[i >> 5] & (1 << (i & 31))) { s[i] = true; - assert(s[i]); + ASSERT(s[i]); } } callback(s); - } else { - callback({}); } - } - ); + }); } void Atspi::getChildren(const AtspiAccessiblePtr &accessibleObj, AsyncCallback> callback) const { getChildrenCount(accessibleObj, - [accessibleObj, callback](Optional childrenCount) { + [accessibleObj, callback](ValueOrError childrenCount) { if (!childrenCount) { - callback({}); + callback(Error{ childrenCount.getError() }); } else { struct State { AsyncCallback> callback; @@ -541,19 +550,19 @@ void Atspi::getChildren(const AtspiAccessiblePtr &accessibleObj, AsyncCallback(); state->callback = std::move(callback); - state->children.resize(*childrenCount); - state->todoCount = *childrenCount; - for (size_t index = 0; index < *childrenCount; ++index) { - auto cback = [state, index](Optional child) { + state->children.resize(std::get<0>(childrenCount)); + state->todoCount = std::get<0>(childrenCount); + for (size_t index = 0; index < std::get<0>(childrenCount); ++index) { + auto cback = [state, index](ValueOrError child) { if (state->failed) return; if (!child) { state->failed = true; - state->callback({}); + state->callback(Error { child.getError() }); return; } - state->children[index] = std::move(*child); - assert(state->todoCount > 0); + state->children[index] = std::move(std::get<0>(child)); + ASSERT(state->todoCount > 0); --state->todoCount; if (state->todoCount == 0) state->callback(std::move(state->children)); @@ -573,20 +582,19 @@ void Atspi::getRelationSet(const std::shared_ptr &accessibleObj eldbusConnection, accessibleObj, "GetRelationSet", - [callback](Optional relationSet) { + [callback](ValueOrError relationSet) { if (!relationSet) { ERROR("dbus getRelationSet failed"); - callback({}); + callback(relationSet.getError()); return; } - std::vector relations(relationSet->size()); - for (auto &r : *relationSet) + std::vector relations(std::get<0>(relationSet).size()); + for (auto &r : std::get<0>(relationSet)) relations.push_back({std::get<0>(r), std::move(std::get<1>(r))}); callback(std::move(relations)); - } - ); + }); } void Atspi::getObjectInRelation(const std::shared_ptr &accessibleObj, AtspiRelationType searchType, @@ -594,15 +602,15 @@ void Atspi::getObjectInRelation(const std::shared_ptr &accessib { auto obj = accessibleObj; getRelationSet(accessibleObj, - [callback, obj, searchType](Optional> relations) { + [callback, obj, searchType](ValueOrError> relations) { if (!relations) { DEBUG("Relation set do not exist"); callback({}); return; } - DEBUG("Relations found: %d", relations->size()); - for (auto r : *relations) { + DEBUG("Relations found: %d", std::get<0>(relations).size()); + for (auto r : std::get<0>(relations)) { DEBUG("Relation type: %d", r. type); if (r.type == searchType) { DEBUG("Searched relation found"); @@ -618,20 +626,13 @@ void Atspi::getObjectInRelation(const std::shared_ptr &accessib namespace { void getValueTemplateFunction(const EldbusConnectionCallbackHandle &eldbusConnection, const std::shared_ptr &valueInterface, const std::string paramName, - detail::AsyncCallbackHelper::type callback) + AsyncCallback callback) { getProperty( eldbusConnection, valueInterface, paramName, - [callback](Optional value) { - if (!value) { - ERROR("can not get value"); - callback({}); - return; - } - callback(std::move(value)); - }); + std::move(callback)); } } @@ -667,46 +668,40 @@ template void Atspi::getInterface(const AtspiAccessiblePtr &accessi eldbusConnection, accessibleObj, "GetInterfaces", - [this, callback, accessibleObj](Optional> val) { + [this, callback, accessibleObj](ValueOrError> val) { const auto interfaceName = InterfaceNameFromType::interfaceName; - if (val) { - for (auto &z : *val) { + if (!val) { + callback(Error{ val.getError() }); + } else { + std::shared_ptr r; + for (auto &z : std::get<0>(val)) { if (z == interfaceName) { - std::shared_ptr r; convert(r, accessibleObj); - callback(std::move(r)); - return; + break; } } + callback(std::move(r)); } - std::string interfaces; - for (auto &z : *val) { - if (!interfaces.empty()) interfaces += " "; - interfaces += z; - } - callback({}); - } - ); + }); } void Atspi::getMatchedElements(const AtspiCollectionPtr &rootObj, AtspiCollectionSortOrder sortOrder, size_t maximumFoundElements, Matcher m, bool reverse, AsyncCallback> callback) { - assert(maximumFoundElements <= (size_t)std::numeric_limits::max()); + ASSERT(maximumFoundElements <= (size_t)std::numeric_limits::max()); auto bus = getBusName(rootObj); auto path = getPath(rootObj); - auto pr = DBus { bus, path, ATSPI_DBUS_INTERFACE_COLLECTION, eldbusConnection }; + auto pr = DBusClient { bus, path, ATSPI_DBUS_INTERFACE_COLLECTION, eldbusConnection }; std::get<8>(m.value) = reverse; auto mm = pr.method(decltype(Matcher::value), uint32_t, int32_t, bool)>("GetMatches"); - auto v = mm.call( + mm.asyncCall(std::move(callback), std::move(m.value), (uint32_t)sortOrder, (int32_t)maximumFoundElements, true); - callback(v); } void Atspi::getAtPoint(Point pt, CoordType type, const AtspiAccessiblePtr &root, AsyncCallback callback) const @@ -720,21 +715,21 @@ void Atspi::getAtPoint(Point pt, CoordType type, const AtspiAccessiblePtr &root, ctype = ATSPI_COORD_TYPE_WINDOW; break; } - using returnType = std::tuple; + using ReturnType = ValueOrError; struct handler { const Atspi *atspi; Point pt; uint32_t ctype; AsyncCallback callback; - static void process(std::shared_ptr self, Optional value) + static void process(std::shared_ptr self, ReturnType value) { if (!value) { - self->callback({}); + self->callback(Error { value.getError() }); } else { - auto &ptr = std::get<0>(*value); - auto recurse = std::get<1>(*value); - auto &deputy = std::get<2>(*value); + auto ptr = std::get<0>(value); + auto recurse = std::get<1>(value); + auto deputy = std::get<2>(value); if (!ptr && deputy) ptr = std::move(deputy); if (!recurse) { @@ -745,11 +740,11 @@ void Atspi::getAtPoint(Point pt, CoordType type, const AtspiAccessiblePtr &root, } return; } - callFunction( + callFunction( self->atspi->eldbusConnection, ptr, "GetNavigableAtPoint", - [self](Optional value) { + [self](ReturnType value) { process(std::move(self), std::move(value)); }, self->pt.x, self->pt.y, self->ctype); @@ -757,11 +752,11 @@ void Atspi::getAtPoint(Point pt, CoordType type, const AtspiAccessiblePtr &root, } }; auto h = std::make_shared(handler{this, pt, (uint32_t)ctype, std::move(callback)}); - callFunction(int32_t, int32_t, uint32_t)>( + callFunction( eldbusConnection, root, "GetNavigableAtPoint", - [h](Optional value) { + [h](ReturnType value) { handler::process(std::move(h), std::move(value)); }, pt.x, pt.y, (uint32_t)ctype); @@ -769,21 +764,24 @@ void Atspi::getAtPoint(Point pt, CoordType type, const AtspiAccessiblePtr &root, void Atspi::getAllAcceptedObjects(const AtspiAccessiblePtr &root, AsyncCallback, Rectangle>> callback) const { - using returnType = std::tuple>>; - callFunction( + callFunction( eldbusConnection, root, "GetAllAcceptedObjects", - [ = ](Optional value) { + [ = ](ReturnType value) { if (!value) { - callback({}); + callback(Error { value.getError() }); } else { std::tuple, Rectangle> res; auto &ois = std::get<0>(res); - std::get<1>(res) = { { std::get<0>(*value), std::get<1>(*value) }, { std::get<2>(*value), std::get<3>(*value) }}; - auto &v = std::get<4>(*value); + std::get<1>(res) = { + { std::get<0>(value), std::get<1>(value) }, + { std::get<2>(value), std::get<3>(value) } + }; + auto v = std::get<4>(value); ois.reserve(v.size()); for (auto &a : v) { ois.push_back({}); @@ -799,7 +797,7 @@ void Atspi::getAllAcceptedObjects(const AtspiAccessiblePtr &root, AsyncCallback< std::vector> Atspi::getAttributes(const AtspiAccessiblePtr &accessibleObj) const { - assert(accessibleObj == root_object); + ASSERT(accessibleObj == root_object); return root_object_attributes; #if 0 // do not delete - root_* hack is temporary diff --git a/src/Atspi.hpp b/src/Atspi.hpp index 141274e..156d4f3 100644 --- a/src/Atspi.hpp +++ b/src/Atspi.hpp @@ -12,31 +12,16 @@ #include #include -namespace detail -{ - template - struct AsyncCallbackHelper { - using type = std::function)>; - }; - - template <> - struct AsyncCallbackHelper { - using type = std::function; - }; -} - using AtspiAccessiblePtr = std::shared_ptr; using AtspiActionPtr = std::shared_ptr; using AtspiSelectionPtr = std::shared_ptr; using AtspiCollectionPtr = std::shared_ptr; using AtspiValuePtr = std::shared_ptr; using AtspiEditableTextPtr = std::shared_ptr; -template using AsyncCallback = typename detail::AsyncCallbackHelper::type; +template using AsyncCallback = typename std::function)>; class Atspi { - template using AsyncCallback = typename detail::AsyncCallbackHelper::type; - public: using StateSet = std::bitset; @@ -176,11 +161,13 @@ public: void getMaximumValue(const std::shared_ptr &valueInterface, AsyncCallback callback) const; void getMinimumValue(const std::shared_ptr &valueInterface, AsyncCallback callback) const; -#define ADD_INTROSPECTION_FUNCTIONS(TYPE, NAME) \ - void get ## TYPE ## Interface(const AtspiAccessiblePtr &obj, \ - AsyncCallback> callback) const; \ - std::string getUniqueId(const std::shared_ptr &obj) const; \ - AtspiAccessiblePtr getBase(const std::shared_ptr &) const; +#define ADD_INTROSPECTION_FUNCTIONS(TYPE, NAME) \ + void get ## TYPE ## Interface(const AtspiAccessiblePtr &obj, \ + AsyncCallback> callback) const; \ + std::string getUniqueId(const std::shared_ptr &obj) const; \ + AtspiAccessiblePtr getBase(const std::shared_ptr &) const; \ + static std::string getBusName(const std::shared_ptr &o); \ + static std::string getPath(const std::shared_ptr &o); ADD_INTROSPECTION_FUNCTIONS(Accessible, ACCESSIBLE); ADD_INTROSPECTION_FUNCTIONS(Action, ACTION); @@ -198,8 +185,8 @@ private: efl::eldbus::eldbus_init eldbus; efl::eldbus::connection connection; - EldbusConnectionCallbackHandle eldbusConnection; - template DBus getProxy(const std::shared_ptr &obj, const std::string &interface) const; + DBus::EldbusConnectionCallbackHandle eldbusConnection; + template DBus::DBusClient getProxy(const std::shared_ptr &obj, const std::string &interface) const; template void getInterface(const AtspiAccessiblePtr &accessibleObj, AsyncCallback> callback) const; void getInterface(const AtspiAccessiblePtr &accessibleObj, AsyncCallback callback) const { diff --git a/src/CallActivity.cpp b/src/CallActivity.cpp index cd4ba99..c62f175 100644 --- a/src/CallActivity.cpp +++ b/src/CallActivity.cpp @@ -20,7 +20,7 @@ public: bool process() override { - DBus dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; + DBus::DBusClient dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; dbus.method("AnswerCall").call(CM_TEL_CALL_ANSWER_TYPE_NORMAL); return true; } @@ -37,7 +37,7 @@ public: bool process() override { - DBus dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; + DBus::DBusClient dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; dbus.method("RejectCall").call(); return true; } @@ -54,7 +54,7 @@ public: bool process() override { - DBus dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; + DBus::DBusClient dbus{dbusLocators::callmgr::BUS, dbusLocators::callmgr::OBJ_PATH, dbusLocators::callmgr::INTERFACE, DBus::ConnectionType::SYSTEM}; dbus.method("EndCall").call(0, CM_TEL_CALL_RELEASE_TYPE_ALL_CALLS); return true; } diff --git a/src/DBus.cpp b/src/DBus.cpp index f3d106d..39d4e29 100644 --- a/src/DBus.cpp +++ b/src/DBus.cpp @@ -2,60 +2,415 @@ #include #include "Atspi.hpp" #include "Singleton.hpp" +#include -unsigned int detail::CallId::LastId = 0; +#define DBUS_DEBUG(...) do { DBus::debugPrint(__FILE__, __LINE__, __VA_ARGS__); } while (0) -DBus::DBus() +std::atomic DBus::detail::CallId::LastId { 0 }; +static std::function debugPrinter; +static std::mutex debugLock; + +void DBus::setDebugPrinter(std::function printer) +{ + std::lock_guard lock(debugLock); + debugPrinter = std::move(printer); +} + +void DBus::debugPrint(const char *file, size_t line, const char *format, ...) +{ + std::function debugPrintFunc; + { + std::lock_guard lock(debugLock); + if (!debugPrinter) return; + debugPrintFunc = debugPrinter; + } + std::vector buf(4096); + int offset; + while (true) { + offset = snprintf(buf.data(), buf.size(), "%s:%u: ", file, (unsigned int)line); + if (offset < 0) return; + if ((size_t)offset < buf.size()) break; + buf.resize(offset + 1); + } + + while (true) { + va_list args; + va_start(args, format); + int z = vsnprintf(buf.data() + offset, buf.size(), format, args); + va_end(args); + if (z < 0) return; + if ((size_t)z + (size_t)offset < buf.size()) break; + buf.resize((size_t)z + (size_t)offset + 1); + } + debugPrintFunc(buf.data(), buf.size()); +} + +DBus::EldbusConnectionCallbackHandle DBus::getDBusConnectionByName(const std::string &name) { + eldbus_init(); + auto z = getDBusConnectionByType(ConnectionType::SYSTEM); + auto connection = eldbus_address_connection_get(name.c_str()); + return { connection, [](Eldbus_Connection * c) + { + eldbus_connection_unref(c); + eldbus_shutdown(); + } }; } -static EldbusConnectionCallbackHandle getConnectionByType(DBus::ConnectionType connectionType) +DBus::EldbusConnectionCallbackHandle DBus::getDBusConnectionByType(ConnectionType connectionType) { + Eldbus_Connection_Type eldbusType = ELDBUS_CONNECTION_TYPE_SYSTEM; + switch (connectionType) { - case DBus::ConnectionType::SYSTEM: - return { eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM), eldbus_connection_unref }; - case DBus::ConnectionType::SESSION: - return { eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SESSION), eldbus_connection_unref }; + case ConnectionType::SYSTEM: + eldbusType = ELDBUS_CONNECTION_TYPE_SYSTEM; + break; + case ConnectionType::SESSION: + eldbusType = ELDBUS_CONNECTION_TYPE_SESSION; + break; default: ASSERT(0); } - return {}; + + eldbus_init(); + return { eldbus_connection_get(eldbusType), [](Eldbus_Connection * c) + { + eldbus_connection_unref(c); + eldbus_shutdown(); + } }; } -DBus::DBus(std::string bus_name, std::string path_name, std::string interface_name, ConnectionType tp) : - DBus(std::move(bus_name), std::move(path_name), std::move(interface_name), getConnectionByType(tp)) +DBus::DBusClient::DBusClient(std::string bus_name, std::string path_name, std::string interface_name, ConnectionType tp) : + DBusClient(std::move(bus_name), std::move(path_name), std::move(interface_name), getDBusConnectionByType(tp)) { } -DBus::DBus(std::string bus_name, std::string path_name, std::string interface_name, const EldbusConnectionCallbackHandle &conn) +struct caller_eldbus_object_unref { + void operator()(Eldbus_Object *p) const + { + eldbus_object_unref(p); + } +}; + +DBus::DBusClient::DBusClient(std::string bus_name, std::string path_name, std::string interface_name, const EldbusConnectionCallbackHandle &conn) { if (!conn) - connectionState.connection = getConnectionByType(DBus::ConnectionType::SESSION); + connectionState.connection = getDBusConnectionByType(ConnectionType::SESSION); else connectionState.connection = conn; std::ostringstream o; o << "bus = " << bus_name << " path = " << path_name << " connection = " << eldbus_connection_unique_name_get(connectionState.connection.get()); - if (interface_name != DBUS_INTERFACE_PROPERTIES) - o << " iname = " << interface_name; info = o.str(); - connectionState.object = { eldbus_object_get(connectionState.connection.get(), bus_name.c_str(), path_name.c_str()), eldbus_object_unref }; + + auto c = connectionState.connection; + connectionState.object = { + eldbus_object_get(connectionState.connection.get(), bus_name.c_str(), path_name.c_str()), + [c](Eldbus_Object * p) + { + eldbus_object_unref(p); + } + }; if (connectionState.object) { - connectionState.proxy = { eldbus_proxy_get(connectionState.object.get(), interface_name.c_str()), eldbus_proxy_unref }; + connectionState.proxy = eldbus_proxy_get(connectionState.object.get(), interface_name.c_str()); + if (interface_name != DBUS_INTERFACE_PROPERTIES) { + connectionState.propertiesProxy = eldbus_proxy_get(connectionState.object.get(), DBUS_INTERFACE_PROPERTIES); + } else { + connectionState.propertiesProxy = connectionState.proxy; + } } + interfaceName = std::move(interface_name); } -DBus::~DBus() +DBus::DBusClient::~DBusClient() { - for (auto &p : callbacks) - delete p; + for (auto &p : destructors) { + p(); + } } -bool detail::signature>::get(Eldbus_Message_Iter *iter, AtspiAccessiblePtr &v) +bool DBus::detail::signature>::get(Eldbus_Message_Iter *iter, AtspiAccessiblePtr &v) { subtype s; if (!signature::get(iter, s)) return false; v = Singleton::instance().make(s.first.c_str(), s.second.c_str()); return true; } + +DBus::DBusServer::DBusServer() +{ +} + +DBus::DBusServer::DBusServer(ConnectionType tp) : DBus::DBusServer(DBus::getDBusConnectionByType(tp)) +{ +} + +DBus::DBusServer::DBusServer(const EldbusConnectionCallbackHandle &conn) +{ + if (!conn) + connection = getDBusConnectionByType(ConnectionType::SESSION); + else + connection = conn; +} + +DBus::DBusServer::~DBusServer() +{ + for (auto &p : destructors) + p(); +} + +DBus::DBusInterfaceDescription::DBusInterfaceDescription(std::string interfaceName) : interfaceName(std::move(interfaceName)) +{ +} + +struct Implementation { + Eldbus_Service_Interface_Desc dsc; + std::vector methods; + std::vector signals; + std::vector properties; + DBus::detail::StringStorage strings; + + std::unordered_map methodsMap; + std::unordered_map propertiesMap; + std::unordered_map signalsMap; + + Eldbus_Connection *connection = nullptr; +}; + +static std::unordered_map> globalEntries; +static std::mutex globalEntriesMutex; +static thread_local const char *currentObjectPath = ""; +static thread_local Eldbus_Connection *currentConnection = nullptr; + +class CurrentObjectSetter +{ +public: + CurrentObjectSetter(Eldbus_Connection *con, const Eldbus_Message *m) + { + currentObjectPath = eldbus_message_path_get(m); + currentConnection = con; + } + ~CurrentObjectSetter() + { + currentObjectPath = ""; + currentConnection = nullptr; + } + CurrentObjectSetter(const CurrentObjectSetter &) = delete; + CurrentObjectSetter(CurrentObjectSetter &&) = delete; + void operator = (const CurrentObjectSetter &) = delete; + void operator = (CurrentObjectSetter &&) = delete; +}; + +std::string DBus::DBusServer::getCurrentObjectPath() +{ + return currentObjectPath; +} + +DBus::EldbusConnectionCallbackHandle DBus::DBusServer::getCurrentConnection() +{ + auto c = currentConnection; + return { eldbus_connection_ref(c), detail::caller_eldbus_connection_unref() }; +} + +static Eina_Bool property_get_callback(const Eldbus_Service_Interface *iface, const char *propertyName, Eldbus_Message_Iter *iter, + const Eldbus_Message *message, Eldbus_Message **error) +{ + Implementation *impl = nullptr; + { + std::lock_guard lock(globalEntriesMutex); + auto it = globalEntries.find(iface); + if (it != globalEntries.end()) impl = it->second.get(); + } + if (!impl) return EINA_FALSE; + + auto it = impl->propertiesMap.find(propertyName); + if (it == impl->propertiesMap.end() || !it->second.getCallback) return EINA_FALSE; + + CurrentObjectSetter currentObjectSetter(impl->connection, message); + auto reply = it->second.getCallback(message, iter); + if (!reply) { + if (error) + *error = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", reply.getError().message.c_str()); + return EINA_FALSE; + } + + return EINA_TRUE; +} + +static Eldbus_Message *property_set_callback(const Eldbus_Service_Interface *iface, const char *propertyName, Eldbus_Message_Iter *iter, + const Eldbus_Message *message) +{ + Implementation *impl = nullptr; + { + std::lock_guard lock(globalEntriesMutex); + auto it = globalEntries.find(iface); + if (it != globalEntries.end()) impl = it->second.get(); + } + if (!impl) { + auto ret = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", "Unknown interface"); + return ret; + } + auto it = impl->propertiesMap.find(propertyName); + if (it == impl->propertiesMap.end() || !it->second.setCallback) { + auto ret = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", "Unknown setter"); + return ret; + } + CurrentObjectSetter currentObjectSetter(impl->connection, message); + auto reply = it->second.setCallback(message, iter); + + Eldbus_Message *ret = nullptr; + if (!reply) { + ret = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", reply.getError().message.c_str()); + } else { + ret = eldbus_message_method_return_new(message); + } + return ret; +} + +static Eldbus_Message *method_callback(const Eldbus_Service_Interface *iface, const Eldbus_Message *message) +{ + Implementation *impl = nullptr; + { + std::lock_guard lock(globalEntriesMutex); + auto it = globalEntries.find(iface); + if (it != globalEntries.end()) impl = it->second.get(); + } + if (!impl) { + auto ret = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", "Unknown interface"); + return ret; + } + std::string memberName = eldbus_message_member_get(message); + auto it = impl->methodsMap.find(memberName); + if (it == impl->methodsMap.end()) { + auto ret = eldbus_message_error_new(message, "org.freedesktop.DBus.Error.Failed", "Unknown method"); + return ret; + } + CurrentObjectSetter currentObjectSetter(impl->connection, message); + auto reply = it->second.callback(message); + return reply; +} + +static void addInterfaceImpl(bool fallback, const std::string &pathName, + const DBus::EldbusConnectionCallbackHandle &connection, + const std::string &interfaceName, + std::unordered_map> &signalData, + DBus::detail::StringStorage &strings, + std::vector &dscrMethods, + std::vector &dscrProperties, + std::vector &dscrSignals, + std::vector> &destructors) +{ + std::vector methods; + std::vector signals; + std::vector properties; + std::unordered_map methodsMap; + std::unordered_map propertiesMap; + std::unordered_map signalsMap; + + DBUS_DEBUG("interface %s path %s on bus %s", interfaceName.c_str(), pathName.c_str(), DBus::getConnectionName(connection).c_str()); + for (auto &ee : dscrMethods) { + auto key = ee.memberName; + DBUS_DEBUG("adding method %s", ee.memberName.c_str()); + for (auto &r : ee.in) { + if (!r.name) break; + DBUS_DEBUG("in %s '%s'", r.name, r.signature); + } + for (auto &r : ee.out) { + if (!r.name) break; + DBUS_DEBUG("out %s '%s'", r.name, r.signature); + } + auto &e = (methodsMap[key] = std::move(ee)); + methods.push_back({}); + auto &m = methods.back(); + m.member = e.memberName.c_str(); + m.in = e.in.data(); + m.out = e.out.data(); + m.cb = method_callback; + m.flags = 0; + } + for (auto &ee : dscrProperties) { + auto key = ee.memberName; + DBUS_DEBUG("adding property %s", ee.memberName.c_str()); + auto &e = (propertiesMap[key] = std::move(ee)); + properties.push_back({}); + auto &m = properties.back(); + m.name = e.memberName.c_str(); + m.type = e.typeSignature.c_str(); + m.get_func = e.getCallback ? property_get_callback : nullptr; + m.set_func = e.setCallback ? property_set_callback : nullptr; + m.flags = 0; + } + unsigned int signalIndex = 0; + std::vector signalIds; + for (auto &ee : dscrSignals) { + DBUS_DEBUG("adding signal %s", ee.memberName.c_str()); + auto &e = (signalsMap[ee.id.id] = std::move(ee)); + signals.push_back({}); + auto &m = signals.back(); + m.name = e.memberName.c_str(); + m.args = e.args.data(); + m.flags = 0; + signalData[e.id.id].second = signalIndex++; + signalIds.push_back(e.id.id); + } + dscrMethods.clear(); + dscrProperties.clear(); + dscrSignals.clear(); + + methods.push_back({ nullptr, nullptr, nullptr, nullptr, 0 }); + signals.push_back({ nullptr, nullptr, 0 }); + properties.push_back({ nullptr, nullptr, nullptr, nullptr, 0 }); + + auto impl = std::make_unique(Implementation{ + { + interfaceName.c_str(), + methods.data(), + signals.data(), + properties.data(), + nullptr, + nullptr + }, + std::move(methods), + std::move(signals), + std::move(properties), + std::move(strings), + std::move(methodsMap), + std::move(propertiesMap), + std::move(signalsMap), + connection.get() + }); + + { + std::lock_guard lock(globalEntriesMutex); + auto v = fallback ? + eldbus_service_interface_fallback_register(connection.get(), pathName.c_str(), &impl->dsc) : + eldbus_service_interface_register(connection.get(), pathName.c_str(), &impl->dsc); + ASSERT(v); + globalEntries[v] = std::move(impl); + DBUS_DEBUG("registering interface %p (%d)", v, fallback ? 1 : 0); + destructors.push_back([ = ]() { + eldbus_service_interface_unregister(v); + std::lock_guard lock(globalEntriesMutex); + globalEntries.erase(v); + }); + for (auto id : signalIds) { + signalData[id].first = v; + } + } +} + +void DBus::DBusServer::addInterface(const std::string &pathName, DBusInterfaceDescription &dscr, bool fallback) +{ + addInterfaceImpl(fallback, pathName, connection, dscr.interfaceName, signalData, dscr.strings, dscr.methods, dscr.properties, dscr.signals, destructors); +} + +std::string DBus::DBusServer::getBusName() const +{ + return getConnectionName(connection); +} + +std::string DBus::getConnectionName(const EldbusConnectionCallbackHandle &c) +{ + return eldbus_connection_unique_name_get(c.get()); +} diff --git a/src/DBus.hpp b/src/DBus.hpp index 867ce18..954f62c 100644 --- a/src/DBus.hpp +++ b/src/DBus.hpp @@ -12,849 +12,1386 @@ #include #include #include +#include +#include +#include +#include -namespace detail +#define DBUS_DEBUG(...) do { DBus::debugPrint(__FILE__, __LINE__, __VA_ARGS__); } while(0) + +namespace DBus { - struct caller_eldbus_connection_unref { - void operator()(Eldbus_Connection *p) const - { - eldbus_connection_unref(p); - } - }; + class DBusServer; + class DBusClient; + class DBusInterfaceDescription; - struct caller_eldbus_message_unref { - void operator()(Eldbus_Message *p) const - { - eldbus_message_unref(p); - } - }; + void debugPrint(const char *file, size_t line, const char *format, ...); + void setDebugPrinter(std::function); - struct caller_eldbus_object_unref { - void operator()(Eldbus_Object *p) const - { - eldbus_object_unref(p); - } - }; + struct Error { + std::string message; - struct caller_eldbus_proxy_unref { - void operator()(Eldbus_Proxy *p) const + Error() = default; + Error(std::string msg) : message(std::move(msg)) { - eldbus_proxy_unref(p); + ASSERT(!message.empty()); } }; -} - -template struct EldbusVariant { - A value; -}; - -using EldbusConnectionCallbackHandle = std::shared_ptr; -using EldbusMessageCallbackHandle = std::unique_ptr; -using EldbusObjectCallbackHandle = std::shared_ptr; -using EldbusProxyCallbackHandle = std::shared_ptr; - -namespace detail -{ - template struct signature; - template struct signature>; - template struct signature>; - template struct signature>; - template struct signature>>; - template struct signature>; - template struct signature, N>>; - template struct signature>; - template struct signature>; + struct Success { }; - template struct signature::value, void>::type> { - static std::string sig() + template class ValueOrError + { + public: + ValueOrError() = default; + ValueOrError(ARGS ... t) : value(std::move(t)...) { } + ValueOrError(std::tuple t) : value(std::move(t)) { } + ValueOrError(Error e) : error(std::move(e)) { - return "i"; + ASSERT(!error.message.empty()); } - static void set(Eldbus_Message_Iter *iter, T v) - { - // TODO: add check for failure in marshalling arguments - eldbus_message_iter_arguments_append(iter, sig().c_str(), (int)v); - } - static bool get(Eldbus_Message_Iter *iter, T &v) + explicit operator bool () const { - int q; - auto z = eldbus_message_iter_get_and_next(iter, sig()[0], &q); - v = (T)q; - return z; + return error.message.empty(); } - }; - -#define SIGNATURE(T, S) \ - template <> struct signature { \ - static std::string sig() { return #S; } \ - static void set(Eldbus_Message_Iter *iter, T v) { \ - eldbus_message_iter_arguments_append(iter, sig().c_str(), v); \ - } \ - static bool get(Eldbus_Message_Iter *iter, T &v) { \ - return eldbus_message_iter_get_and_next(iter, sig()[0], &v); \ - } \ - }; - SIGNATURE(uint8_t, y); - SIGNATURE(uint16_t, q); - SIGNATURE(uint32_t, u); - SIGNATURE(uint64_t, t); - SIGNATURE(int16_t, n); - SIGNATURE(int32_t, i); - SIGNATURE(int64_t, x); - SIGNATURE(double, d); -#undef SIGNATURE - - template <> struct signature { - static std::string sig() + const Error &getError() const { - return "b"; + return error; } - static void set(Eldbus_Message_Iter *iter, bool v) + std::tuple &getValues() { - eldbus_message_iter_arguments_append(iter, sig().c_str(), v ? 1 : 0); + ASSERT(*this); + return value; } - static bool get(Eldbus_Message_Iter *iter, bool &v) + const std::tuple &getValues() const { - unsigned char q; - auto z = eldbus_message_iter_get_and_next(iter, sig()[0], &q); - v = q != 0; - return z; + ASSERT(*this); + return value; } + protected: + std::tuple value; + Error error; }; - template <> struct signature { - static std::string sig() - { - return "s"; - } - static void set(Eldbus_Message_Iter *iter, const std::string &v) - { - eldbus_message_iter_arguments_append(iter, sig().c_str(), v.c_str()); - } - static void set(Eldbus_Message_Iter *iter, const char *v) - { - eldbus_message_iter_arguments_append(iter, sig().c_str(), v); - } - static bool get(Eldbus_Message_Iter *iter, std::string &v) - { - const char *q; - if (!eldbus_message_iter_get_and_next(iter, 's', &q)) { - if (!eldbus_message_iter_get_and_next(iter, 'o', &q)) - return false; - } - v = q; - return true; - } - }; - template struct signature_tuple_element_type_helper { - using type = typename signature_tuple_element_type_helper < INDEX - 1, ARGS... >::type; - }; - template struct signature_tuple_element_type_helper<0, A, ARGS...> { - using type = A; - }; - - template struct signature_tuple_helper { - using current_type = typename signature_tuple_element_type_helper::type; - static std::string sig() - { - return signature::sig() + signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::sig(); - } - static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + template <> class ValueOrError<> + { + public: + ValueOrError() = default; + ValueOrError(std::tuple<> t) { } + ValueOrError(Error e) : error(std::move(e)) { - signature::set(iter, std::get(args)); - signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::set(iter, args); + ASSERT(!error.message.empty()); } - static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + + explicit operator bool () const { - return signature::get(iter, std::get(args)) && - signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::get(iter, args); + return error.message.empty(); } - }; - template struct signature_tuple_helper { - static std::string sig() + const Error &getError() const { - return ""; + return error; } - static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + std::tuple<> &getValues() { + ASSERT(*this); + static std::tuple<> t; + return t; } - static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + std::tuple<> getValues() const { - return true; + ASSERT(*this); + return {}; } + protected: + Error error; }; - template struct signature> { - static std::string sig() + + template <> class ValueOrError + { + public: + ValueOrError() = default; + ValueOrError(Success) { } + ValueOrError(Error e) : error(std::move(e)) { - return "(" + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::sig() + ")"; + ASSERT(!error.message.empty()); } - static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + + explicit operator bool () const { - auto entry = eldbus_message_iter_container_new(iter, 'r', NULL); - signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::set(entry, args); - eldbus_message_iter_container_close(iter, entry); + return error.message.empty(); } - static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + const Error &getError() const { - Eldbus_Message_Iter *entry; - if (!eldbus_message_iter_get_and_next(iter, 'r', &entry)) return false; - auto z = signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::get(entry, args); - eldbus_message_iter_container_close(iter, entry); - return z; + return error; } - }; - template struct signature> { - static std::string sig() + std::tuple<> &getValues() { - return "(" + signature_tuple_helper<0, 2, A, B>::sig() + ")"; + ASSERT(*this); + static std::tuple<> t; + return t; } - static void set(Eldbus_Message_Iter *iter, const std::pair &ab, char sg = 'r') + std::tuple<> getValues() const { - auto entry = eldbus_message_iter_container_new(iter, sg, NULL); - signature_tuple_helper<0, 2, A, B>::set(entry, ab); - eldbus_message_iter_container_close(iter, entry); + ASSERT(*this); + return {}; } - static bool get(Eldbus_Message_Iter *iter, std::pair &ab, char sg = 'r') - { - Eldbus_Message_Iter *entry; - if (!eldbus_message_iter_get_and_next(iter, sg, &entry)) return false; - std::tuple ab_tmp; - auto z = signature_tuple_helper<0, 2, A, B>::get(entry, ab_tmp); - if (z) { - ab.first = std::move(std::get<0>(ab_tmp)); - ab.second = std::move(std::get<1>(ab_tmp)); + protected: + Error error; + }; + + namespace detail + { + struct caller_eldbus_connection_unref { + void operator()(Eldbus_Connection *p) const + { + eldbus_connection_unref(p); } - eldbus_message_iter_container_close(iter, entry); - return z; - } + }; + + struct caller_eldbus_message_unref { + void operator()(Eldbus_Message *p) const + { + eldbus_message_unref(p); + } + }; + + struct caller_eldbus_proxy_unref { + void operator()(Eldbus_Proxy *p) const + { + eldbus_proxy_unref(p); + } + }; + } + + template struct EldbusVariant { + A value; }; - template struct signature> { - static std::string sig() - { - return "a" + signature::sig(); - } - static void set(Eldbus_Message_Iter *iter, const std::vector &v) - { - auto lst = eldbus_message_iter_container_new(iter, 'a', signature::sig().c_str()); - ASSERT(lst); - for (auto &a : v) { - signature::set(lst, a); + + using EldbusConnectionCallbackHandle = std::shared_ptr; + using EldbusMessageCallbackHandle = std::unique_ptr; + using EldbusObjectCallbackHandle = std::shared_ptr; + + namespace detail + { + template struct signature; + template struct signature>; + template struct signature>; + template struct signature>; + template struct signature>; + template struct signature>; + template struct signature>; + + template struct signature::value, void>::type> { + static std::string name() + { + return "enum"; } - eldbus_message_iter_container_close(iter, lst); - } - static bool get(Eldbus_Message_Iter *iter, std::vector &v) - { - Eldbus_Message_Iter *s; - v.clear(); - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; - A a; - while (signature::get(s, a)) - v.push_back(std::move(a)); - return true; - } + static std::string sig() + { + // TODO: add check for failure in marshalling arguments + return "i"; + } + static void set(Eldbus_Message_Iter *iter, T v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), (int)v); + } + static bool get(Eldbus_Message_Iter *iter, T &v) + { + int q; + auto z = eldbus_message_iter_get_and_next(iter, sig()[0], &q); + v = (T)q; + return z; + } + }; + +#define SIGNATURE(T, S) \ + template <> struct signature { \ + static std::string name() { return #T; } \ + static std::string sig() { return #S; } \ + static void set(Eldbus_Message_Iter *iter, T v) { \ + eldbus_message_iter_arguments_append(iter, sig().c_str(), v); \ + } \ + static bool get(Eldbus_Message_Iter *iter, T &v) { \ + return eldbus_message_iter_get_and_next(iter, sig()[0], &v); \ + } \ }; - template struct signature> { - static std::string sig() - { - return "a" + signature::sig(); - } - static void set(Eldbus_Message_Iter *iter, const std::array &v) - { - auto lst = eldbus_message_iter_container_new(iter, 'a', signature::sig().c_str()); - assert(lst); - for (auto &a : v) { - signature::set(lst, a); + SIGNATURE(uint8_t, y); + SIGNATURE(uint16_t, q); + SIGNATURE(uint32_t, u); + SIGNATURE(uint64_t, t); + SIGNATURE(int16_t, n); + SIGNATURE(int32_t, i); + SIGNATURE(int64_t, x); + SIGNATURE(double, d); +#undef SIGNATURE + + template <> struct signature { + static std::string name() + { + return "bool"; } - eldbus_message_iter_container_close(iter, lst); - } - static bool get(Eldbus_Message_Iter *iter, std::array &v) + static std::string sig() + { + return "b"; + } + static void set(Eldbus_Message_Iter *iter, bool v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v ? 1 : 0); + } + static bool get(Eldbus_Message_Iter *iter, bool &v) + { + unsigned char q; + auto z = eldbus_message_iter_get_and_next(iter, sig()[0], &q); + v = q != 0; + return z; + } + }; + template <> struct signature { + static std::string name() + { + return "string"; + } + static std::string sig() + { + return "s"; + } + static void set(Eldbus_Message_Iter *iter, const std::string &v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v.c_str()); + } + static void set(Eldbus_Message_Iter *iter, const char *v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v); + } + static bool get(Eldbus_Message_Iter *iter, std::string &v) + { + const char *q; + if (!eldbus_message_iter_get_and_next(iter, 's', &q)) { + if (!eldbus_message_iter_get_and_next(iter, 'o', &q)) + return false; + } + v = q; + return true; + } + }; + template <> struct signature { + static std::string name() + { + return "string"; + } + static std::string sig() + { + return "s"; + } + static void set(Eldbus_Message_Iter *iter, const std::string &v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v.c_str()); + } + static void set(Eldbus_Message_Iter *iter, const char *v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v); + } + }; + template struct signature { + static std::string name() + { + return "string"; + } + static std::string sig() + { + return "s"; + } + static void set(Eldbus_Message_Iter *iter, const std::string &v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v.c_str()); + } + static void set(Eldbus_Message_Iter *iter, const char *v) + { + eldbus_message_iter_arguments_append(iter, sig().c_str(), v); + } + }; + template struct signature_tuple_element_type_helper { + using type = typename signature_tuple_element_type_helper < INDEX - 1, ARGS... >::type; + }; + template struct signature_tuple_element_type_helper<0, A, ARGS...> { + using type = A; + }; + + template struct signature_tuple_helper { + using current_type = typename signature_tuple_element_type_helper::type; + + static std::string name() + { + if (INDEX + 1 >= SIZE) + return signature::name(); + return signature::name() + ", " + signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::name(); + } + static std::string sig() + { + return signature::sig() + signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::sig(); + } + static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + { + signature::set(iter, std::get(args)); + signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::set(iter, args); + } + static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + { + return signature::get(iter, std::get(args)) && + signature_tuple_helper < INDEX + 1, SIZE, ARGS... >::get(iter, args); + } + }; + template struct signature_tuple_helper { + static std::string name() + { + return ""; + } + static std::string sig() + { + return ""; + } + static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + { + } + static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + { + return true; + } + }; + template struct signature> { + static std::string name() + { + return "tuple<" + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::name() + ">"; + } + static std::string sig() + { + return "(" + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::sig() + ")"; + } + static void set(Eldbus_Message_Iter *iter, const std::tuple &args) + { + auto entry = eldbus_message_iter_container_new(iter, 'r', NULL); + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::set(entry, args); + eldbus_message_iter_container_close(iter, entry); + } + static bool get(Eldbus_Message_Iter *iter, std::tuple &args) + { + Eldbus_Message_Iter *entry; + if (!eldbus_message_iter_get_and_next(iter, 'r', &entry)) return false; + auto z = signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::get(entry, args); + return z; + } + }; + template struct signature> { + static std::string name() + { + return "ValueOrError<" + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::name() + ">"; + } + static std::string sig() + { + return signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::sig(); + } + static void set(Eldbus_Message_Iter *iter, const ValueOrError &args) + { + signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::set(iter, args.getValues()); + } + static bool get(Eldbus_Message_Iter *iter, ValueOrError &args) + { + return signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::get(iter, args.getValues()); + } + }; + template <> struct signature> { + static std::string name() + { + return ""; + } + static std::string sig() + { + return ""; + } + static void set(Eldbus_Message_Iter *iter, const ValueOrError &args) + { + } + static bool get(Eldbus_Message_Iter *iter, ValueOrError &args) + { + return true; + } + }; + template struct signature> { + static std::string name() + { + return "pair<" + signature_tuple_helper<0, 2, A, B>::name() + ">"; + } + static std::string sig() + { + return "(" + signature_tuple_helper<0, 2, A, B>::sig() + ")"; + } + static void set(Eldbus_Message_Iter *iter, const std::pair &ab, bool dictionary = false) + { + auto entry = eldbus_message_iter_container_new(iter, dictionary ? 'e' : 'r', NULL); + signature_tuple_helper<0, 2, A, B>::set(entry, ab); + eldbus_message_iter_container_close(iter, entry); + } + static bool get(Eldbus_Message_Iter *iter, std::pair &ab) + { + char sg = 'r'; + char *t = eldbus_message_iter_signature_get(iter); + if (t && t[0] == '{') + sg = '{'; + free(t); + + Eldbus_Message_Iter *entry; + if (!eldbus_message_iter_get_and_next(iter, sg, &entry)) return false; + std::tuple ab_tmp; + auto z = signature_tuple_helper<0, 2, A, B>::get(entry, ab_tmp); + if (z) { + ab.first = std::move(std::get<0>(ab_tmp)); + ab.second = std::move(std::get<1>(ab_tmp)); + } + return z; + } + }; + template struct signature> { + static std::string name() + { + return "vector<" + signature::name() + ">"; + } + static std::string sig() + { + return "a" + signature::sig(); + } + static void set(Eldbus_Message_Iter *iter, const std::vector &v) + { + auto lst = eldbus_message_iter_container_new(iter, 'a', signature::sig().c_str()); + ASSERT(lst); + for (auto &a : v) { + signature::set(lst, a); + } + eldbus_message_iter_container_close(iter, lst); + } + static bool get(Eldbus_Message_Iter *iter, std::vector &v) + { + Eldbus_Message_Iter *s; + v.clear(); + if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; + A a; + while (signature::get(s, a)) + v.push_back(std::move(a)); + + return true; + } + }; + template struct signature> { + static std::string name() + { + return "array<" + signature::name() + ", " + std::to_string(N) + ">"; + } + static std::string sig() + { + return "a" + signature::sig(); + } + static void set(Eldbus_Message_Iter *iter, const std::array &v) + { + auto lst = eldbus_message_iter_container_new(iter, 'a', signature::sig().c_str()); + ASSERT(lst); + for (auto &a : v) { + signature::set(lst, a); + } + eldbus_message_iter_container_close(iter, lst); + } + static bool get(Eldbus_Message_Iter *iter, std::array &v) + { + Eldbus_Message_Iter *s; + if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) + return false; + for (auto &a : v) { + if (!signature::get(s, a)) + return false; + } + return true; + } + }; + template struct signature> { + static std::string name() + { + return "variant<" + signature::name() + ">"; + } + static std::string sig() + { + return "v"; + } + static void set(Eldbus_Message_Iter *iter, const EldbusVariant &v) + { + set(iter, v.value); + } + static void set(Eldbus_Message_Iter *iter, const A &v) + { + auto var = eldbus_message_iter_container_new(iter, 'v', signature::sig().c_str()); + signature::set(var, v); + eldbus_message_iter_container_close(iter, var); + } + static bool get(Eldbus_Message_Iter *iter, EldbusVariant &v) + { + Eldbus_Message_Iter *s; + if (!eldbus_message_iter_get_and_next(iter, 'v', &s)) return false; + return signature::get(s, v.value); + } + }; + template struct signature> { + static std::string name() + { + return "unordered_map<" + signature::name() + ", " + signature::name() + ">"; + } + static std::string sig() + { + return "a{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; + } + static void set(Eldbus_Message_Iter *iter, const std::unordered_map &v) + { + auto sig = "{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; + auto lst = eldbus_message_iter_container_new(iter, 'a', sig.c_str()); + ASSERT(lst); + for (auto &a : v) { + signature>::set(lst, a, true); + } + eldbus_message_iter_container_close(iter, lst); + } + static bool get(Eldbus_Message_Iter *iter, std::unordered_map &v) + { + Eldbus_Message_Iter *s; + v.clear(); + if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; + std::pair a; + while (signature>::get(s, a)) + v.insert(std::move(a)); + return true; + } + }; + template struct signature> { + static std::string name() + { + return "map<" + signature::name() + ", " + signature::name() + ">"; + } + static std::string sig() + { + return "a{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; + } + static void set(Eldbus_Message_Iter *iter, const std::map &v) + { + auto sig = "{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; + auto lst = eldbus_message_iter_container_new(iter, 'a', sig.c_str()); + ASSERT(lst); + for (auto &a : v) { + signature>::set(lst, a, true); + } + eldbus_message_iter_container_close(iter, lst); + } + static bool get(Eldbus_Message_Iter *iter, std::map &v) + { + Eldbus_Message_Iter *s; + if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; + std::pair a; + while (signature>::get(s, a)) + v.insert(std::move(a)); + return true; + } + }; + template struct signature { + static std::string name() + { + return "const " + signature::name() + "&"; + } + static std::string sig() + { + return signature::sig(); + } + static void set(Eldbus_Message_Iter *iter, const A &v) + { + signature::set(iter, v); + } + static void get(Eldbus_Message_Iter *iter, A &v) + { + signature::get(iter, v); + } + }; + template struct signature { + static std::string name() + { + return signature::name() + "&"; + } + static std::string sig() + { + return signature::sig(); + } + static void set(Eldbus_Message_Iter *iter, const A &v) + { + signature::set(iter, v); + } + static void get(Eldbus_Message_Iter *iter, A &v) + { + signature::get(iter, v); + } + }; + template struct signature { + static std::string name() + { + return "const " + signature::name(); + } + static std::string sig() + { + return signature::sig(); + } + static void set(Eldbus_Message_Iter *iter, const A &v) + { + signature::set(iter, v); + } + static void get(Eldbus_Message_Iter *iter, A &v) + { + signature::get(iter, v); + } + }; + template <> struct signature> { + using subtype = std::pair; + + static std::string name() + { + return "AtspiAccessiblePtr"; + } + static std::string sig() + { + return "(so)"; + } + static void set(Eldbus_Message_Iter *iter, const std::shared_ptr &v) + { + const auto prefixPath = "/org/a11y/atspi/accessible/"; + const auto nullPath = "/org/a11y/atspi/null"; + + if (v) { + auto bus = atspi_accessible_get_bus_name(v.get(), NULL); + auto path = atspi_accessible_get_path(v.get(), NULL); + signature::set(iter, { bus, std::string{prefixPath} + path }); + g_free(path); + g_free(bus); + } else { + signature::set(iter, { {}, std::string{nullPath} }); + } + } + static bool get(Eldbus_Message_Iter *iter, std::shared_ptr &v); + }; + struct CallId { + friend class ::DBus::DBusServer; + friend class ::DBus::DBusClient; + friend class ::DBus::DBusInterfaceDescription; + static std::atomic LastId; + unsigned int id = ++LastId; + }; + template ValueType unpackValues(CallId callId, const Eldbus_Message *msg) { - Eldbus_Message_Iter *s; - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) - return false; - for (auto &a : v) { - if (!signature::get(s, a)) - return false; + auto iter = eldbus_message_iter_get(msg); + ValueType r; + + if (iter) { + if (!signature::get(iter, r)) { + DBUS_DEBUG("ValueType is %s", signature::name().c_str()); + r = Error { "call " + std::to_string(callId.id) + ": failed to unpack values, got signature '" + + eldbus_message_signature_get(msg) + "', expected '" + signature::sig() + "'" }; + } + } else { + r = Error { "call " + std::to_string(callId.id) + ": failed to get iterator" }; } - return true; + return r; } - }; - template struct signature, N>> { - static std::string sig() + inline void packValues_helper(Eldbus_Message_Iter *iter) {} + template void packValues_helper(Eldbus_Message_Iter *iter, A &&a, ARGS &&... r) { - return "a" + signature>::sig(); + signature::set(iter, std::forward(a)); + packValues_helper(iter, std::forward(r)...); } - static void set(Eldbus_Message_Iter *iter, const std::array, N> &v) + template void packValues(CallId callId, Eldbus_Message *msg, ARGS &&... r) { - auto lst = eldbus_message_iter_container_new(iter, 'a', signature>::sig().c_str()); - assert(lst); - for (auto &a : v) { - signature>::set(lst, a); - } - eldbus_message_iter_container_close(iter, lst); + auto iter = eldbus_message_iter_get(msg); + packValues_helper(iter, std::forward(r)...); } - static bool get(Eldbus_Message_Iter *iter, std::array, N> &v) - { - Eldbus_Message_Iter *s; - char sig = 'r'; - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; - char *t = eldbus_message_iter_signature_get(s); - if (t && t[0] == '{') - sig = '{'; - free(t); - for (auto &a : v) { - if (!signature>::get(s, a, sig)) - return false; + + template struct seq {}; + template struct gens : gens < N - 1, N - 1, S... > {}; + template struct gens<0, S...> { + typedef seq type; + }; + template struct ReturnType; + template struct ReturnType { + using type = R; + }; + template struct ReturnType> { + using type = R; + }; + template struct apply_helper { + const std::function &c; + const std::tuple &args; + + template auto apply_2(seq) + { + return c(std::get(args)...); } - return true; - } - }; - template struct signature>> { - static std::string sig() - { - return "a" + signature>::sig(); - } - static void set(Eldbus_Message_Iter *iter, const std::vector> &v) - { - auto lst = eldbus_message_iter_container_new(iter, 'a', signature>::sig().c_str()); - ASSERT(lst); - for (auto &a : v) { - signature>::set(lst, a); + auto apply_1() + { + return apply_2(typename gens::type()); } - eldbus_message_iter_container_close(iter, lst); - } - static bool get(Eldbus_Message_Iter *iter, std::vector> &v) - { - Eldbus_Message_Iter *s; - v.clear(); - char sig = 'r'; - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; - char *t = eldbus_message_iter_signature_get(s); + }; + template struct apply_helper_2 { + const std::function &c; + const D &d; + const std::tuple &args; - // TODO: move handling of std::pair special case involing '{' to signature> - if (t && t[0] == '{') - sig = '{'; - free(t); - std::pair a; - while (signature>::get(s, a, sig)) - v.push_back(std::move(a)); - return true; - } - }; - template struct signature> { - static std::string sig() - { - return "v"; - } - static void set(Eldbus_Message_Iter *iter, const EldbusVariant &v) - { - set(iter, v.value); - } - static void set(Eldbus_Message_Iter *iter, const A &v) - { - auto var = eldbus_message_iter_container_new(iter, 'v', signature::sig().c_str()); - signature::set(var, v); - eldbus_message_iter_container_close(iter, var); - } - static bool get(Eldbus_Message_Iter *iter, EldbusVariant &v) - { - Eldbus_Message_Iter *s; - if (!eldbus_message_iter_get_and_next(iter, 'v', &s)) return false; - return signature::get(s, v.value); - } - }; - template struct signature> { - static std::string sig() - { - return "a{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; - } - static void set(Eldbus_Message_Iter *iter, const std::unordered_map &v) - { - auto sig = "{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; - auto lst = eldbus_message_iter_container_new(iter, 'a', sig.c_str()); - assert(lst); - for (auto &a : v) { - signature>::set(lst, a, 'e'); + template auto apply_2(seq) + { + return c(d, std::get(args)...); } - eldbus_message_iter_container_close(iter, lst); - } - static bool get(Eldbus_Message_Iter *iter, std::unordered_map &v) - { - Eldbus_Message_Iter *s; - v.clear(); - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; - std::pair a; - while (signature>::get(s, a, '{')) - v.insert(std::move(a)); - return true; - } - }; - template struct signature> { - static std::string sig() - { - return "a{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; - } - static void set(Eldbus_Message_Iter *iter, const std::map &v) - { - auto sig = "{" + signature_tuple_helper<0, 2, A, B>::sig() + "}"; - auto lst = eldbus_message_iter_container_new(iter, 'a', sig.c_str()); - assert(lst); - for (auto &a : v) { - signature>::set(lst, a, 'e'); + auto apply_1() + { + return apply_2(typename gens::type()); } - eldbus_message_iter_container_close(iter, lst); - } - static bool get(Eldbus_Message_Iter *iter, std::map &v) - { - Eldbus_Message_Iter *s; - v.clear(); - if (!eldbus_message_iter_get_and_next(iter, 'a', &s)) return false; - std::pair a; - while (signature>::get(s, a, '{')) - v.insert(std::move(a)); - return true; - } - }; - template struct signature { - static std::string sig() + }; + template auto apply(const std::function &c, const std::tuple &args) -> typename ReturnType::type { - return signature::sig(); + apply_helper ah { c, args }; + return ah.apply_1(); } - static void set(Eldbus_Message_Iter *iter, const A &v) + template auto apply(const std::function &c, const D &d, const std::tuple &args) -> typename ReturnType::type { - signature::set(iter, v); + apply_helper_2 ah { c, d, args }; + return ah.apply_1(); } - static void get(Eldbus_Message_Iter *iter, A &v) + + struct EldbusProxyBase { + EldbusProxyBase() + { + eldbus_init(); + } + ~EldbusProxyBase() + { + eldbus_shutdown(); + } + }; + + constexpr static int ELDBUS_CALL_TIMEOUT = 1000; + + struct ConnectionState { + EldbusConnectionCallbackHandle connection; + EldbusObjectCallbackHandle object; + Eldbus_Proxy *proxy = nullptr; // proxy lives only as long as object + Eldbus_Proxy *propertiesProxy = nullptr; + }; + static void callAsyncCb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) { - signature::get(iter, v); + auto d = (std::pair> *)data; + DBUS_DEBUG("call %d: got reply", d->first.id); + d->second(msg); } - }; - template struct signature { - static std::string sig() + static void pendingFreeCb(void *data, const void *) { - return signature::sig(); + auto d = (std::pair> *)data; + DBUS_DEBUG("call %d: deleting", d->first.id); + delete d; } - static void set(Eldbus_Message_Iter *iter, const A &v) + template + RETTYPE call(CallId callId, ConnectionState &connectionState, bool property, const std::string &funcName, const ARGS &... args) { - signature::set(iter, v); + auto proxy = property ? connectionState.propertiesProxy : connectionState.proxy; + if (!proxy) { + DBUS_DEBUG("call %d: not initialized", callId.id); + return Error { "not initialized" }; + } + + DBUS_DEBUG("call %d: calling '%s'", callId.id, funcName.c_str()); + EldbusMessageCallbackHandle msg{eldbus_proxy_method_call_new(proxy, funcName.c_str())}; + if (!msg) { + DBUS_DEBUG("call %d: failed", callId.id); + return Error { "failed to create message" }; + } + + detail::packValues(callId, msg.get(), args...); + auto replyRawPtr = eldbus_proxy_send_and_block(proxy, msg.release(), ELDBUS_CALL_TIMEOUT); + EldbusMessageCallbackHandle reply{ replyRawPtr }; + DBUS_DEBUG("call %d: calling '%s' done", callId.id, funcName.c_str()); + if (!reply) { + DBUS_DEBUG("call %d: failed", callId.id); + return Error { "eldbus returned null as reply" }; + } + const char *errname, *errmsg; + if (eldbus_message_error_get(reply.get(), &errname, &errmsg)) { + DBUS_DEBUG("call %d: %s: %s", callId.id, errname, errmsg); + return Error { std::string(errname) + ": " + errmsg }; + } + DBUS_DEBUG("call %d: got reply with signature '%s'", callId.id, eldbus_message_signature_get(reply.get())); + return detail::unpackValues(callId, reply.get()); } - static void get(Eldbus_Message_Iter *iter, A &v) - { - signature::get(iter, v); + + template + void asyncCall(CallId callId, ConnectionState connectionState, bool property, const std::string &funcName, + std::function callback, const ARGS &... args) + { + auto proxy = property ? connectionState.propertiesProxy : connectionState.proxy; + if (!proxy) { + DBUS_DEBUG("call %d: not initialized", callId.id); + callback(Error { "not initialized" }); + return; + } + + EldbusMessageCallbackHandle msg{eldbus_proxy_method_call_new(proxy, funcName.c_str())}; + if (!msg) { + DBUS_DEBUG("call %d: failed", callId.id); + callback(Error { "failed to create message" }); + return; + } + + auto cbData = new std::pair> {callId, + [callback, connectionState, callId](const Eldbus_Message * reply) + { + DBUS_DEBUG("call %d: calling done", callId.id); + if (!reply) { + DBUS_DEBUG("call %d: failed", callId.id); + callback(Error { "eldbus returned null as reply" }); + } else { + const char *errname, *errmsg; + if (eldbus_message_error_get(reply, &errname, &errmsg)) { + DBUS_DEBUG("call %d: %s: %s", callId.id, errname, errmsg); + callback(Error { std::string(errname) + ": " + errmsg }); + } else { + DBUS_DEBUG("call %d: got reply with signature '%s'", callId.id, eldbus_message_signature_get(reply)); + callback(detail::unpackValues(callId, reply)); + } + } + } + }; + detail::packValues(callId, msg.get(), args...); + auto pending = eldbus_proxy_send(proxy, msg.release(), callAsyncCb, cbData, ELDBUS_CALL_TIMEOUT); + if (pending) { + eldbus_pending_free_cb_add(pending, pendingFreeCb, cbData); + DBUS_DEBUG("call %d: call sent", callId.id); + } else { + DBUS_DEBUG("call %d: failed to send call", callId.id); + callback(Error { "failed to send call" }); + } } - }; - template struct signature { - static std::string sig() + inline void displayDebugCallInfo(CallId callId, const std::string &funcName, const std::string &info, const std::string &interfaceName) { - return signature::sig(); + DBUS_DEBUG("call %d: %s iname = %s fname = %s", callId.id, info.c_str(), interfaceName.c_str(), funcName.c_str()); } - static void set(Eldbus_Message_Iter *iter, const A &v) + inline void displayDebugCallInfoSignal(CallId callId, const std::string &funcName, const std::string &info, const std::string &interfaceName) { - signature::set(iter, v); + DBUS_DEBUG("call %d: %s signal iname = %s fname = %s", callId.id, info.c_str(), interfaceName.c_str(), funcName.c_str()); } - static void get(Eldbus_Message_Iter *iter, A &v) + inline void displayDebugCallInfoProperty(CallId callId, const std::string &funcName, std::string info, const std::string &interfaceName, + const std::string &propertyName) { - signature::get(iter, v); + DBUS_DEBUG("call %d: %s iname = %s pname = %s", callId.id, info.c_str(), interfaceName.c_str(), propertyName.c_str()); } - }; - template <> struct signature> { - using subtype = std::pair; - static std::string sig() - { - return "(so)"; - } - static void set(Eldbus_Message_Iter *iter, const std::shared_ptr &v) + class StringStorage { - const auto prefixPath = "/org/a11y/atspi/accessible/"; - const auto nullPath = "/org/a11y/atspi/null"; - - if (v) { - auto bus = atspi_accessible_get_bus_name(v.get(), NULL); - auto path = atspi_accessible_get_path(v.get(), NULL); - signature::set(iter, { bus, std::string{prefixPath} + path }); - g_free(path); - g_free(bus); - } else { - signature::set(iter, { {}, std::string{nullPath} }); + struct char_ptr_deleter { + void operator()(char *p) + { + free(p); + } + }; + std::vector> storage; + public: + const char *add(const char *txt) + { + auto ptr = strdup(txt); + storage.push_back(std::unique_ptr(ptr)); + return storage.back().get(); } - } - static bool get(Eldbus_Message_Iter *iter, std::shared_ptr &v); - }; - struct CallId { - static unsigned int LastId; - unsigned int id = ++LastId; - }; - template struct unpackValues_Helper { - bool operator()(CallId callId, R &r, const Eldbus_Message *msg) const - { - auto iter = eldbus_message_iter_get(msg); + const char *add(const std::string &txt) + { + return add(txt.c_str()); + } + }; + template struct EldbusArgGenerator_Helper { + static void add(std::vector &r, StringStorage &strings) + { + auto s = r.size(); + auto sig = signature::sig(); + ASSERT(!sig.empty()); + auto name = "p" + std::to_string(s + 1); + r.push_back(Eldbus_Arg_Info{ strings.add(sig), strings.add(name) }); + EldbusArgGenerator_Helper::add(r, strings); + } + }; + template <> struct EldbusArgGenerator_Helper { + static void add(std::vector &, StringStorage &) + { + } + }; + template struct EldbusArgGenerator_Helper> { + static void add(std::vector &r, StringStorage &strings) + { + EldbusArgGenerator_Helper::add(r, strings); + } + }; + template struct dbus_interface_return_type_traits { + using type = ValueOrError; + }; + template struct dbus_interface_return_type_traits> { + using type = ValueOrError; + }; + template struct dbus_interface_traits; + template struct dbus_interface_traits { + using Ret = typename dbus_interface_return_type_traits::type; + using SyncCB = std::function; + using AsyncCB = std::function, ARGS...)>; + using VEArgs = ValueOrError; + }; + template struct EldbusArgGenerator_Args; + template struct EldbusArgGenerator_Args { + static auto name() + { + return signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::name(); + } + static auto get(StringStorage &strings) + { + std::vector tmp; + EldbusArgGenerator_Helper::add(tmp, strings); + tmp.push_back({ nullptr, nullptr }); + return tmp; + } + }; + template struct EldbusArgGenerator_ReturnType; + template struct EldbusArgGenerator_ReturnType { + static std::string name() + { + return signature::name(); + } + static auto get(StringStorage &strings) + { + std::vector tmp; + EldbusArgGenerator_Helper::add(tmp, strings); + tmp.push_back({ nullptr, nullptr }); + return tmp; + } + }; + template struct EldbusArgGenerator_ReturnType; + template struct EldbusArgGenerator_ReturnType { + static std::string name() + { + return ""; + } + static auto get(StringStorage &strings) + { + std::vector tmp; + tmp.push_back({ nullptr, nullptr }); + return tmp; + } + }; + } - if (iter) { - if (signature::get(iter, r)) - return true; - ERROR("call %d: signature mismatch, got %s, expected %s", - callId.id, - eldbus_message_signature_get(msg), - signature::sig().c_str()); - } else - ERROR("call %d: failed to get iter", callId.id); - return false; - } - }; - template struct unpackValues_Helper> { - bool operator()(CallId callId, std::tuple &r, const Eldbus_Message *msg) const - { - auto iter = eldbus_message_iter_get(msg); - if (iter) { - if (signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::get(iter, r)) - return true; - ERROR("call %d: signature mismatch, got %s, expected %s", - callId.id, - eldbus_message_signature_get(msg), - signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::sig().c_str()); - } else - ERROR("call %d: failed to get iter", callId.id); - return false; - } + enum class ConnectionType { + SYSTEM, SESSION }; - template bool unpackValues(CallId callId, R &r, const Eldbus_Message *msg) - { - return unpackValues_Helper()(callId, r, msg); - } - template void packValues(CallId callId, Eldbus_Message *msg, const R &r) - { - DEBUG("call %d: packing values with signature %s", callId.id, signature::sig().c_str()); - auto iter = eldbus_message_iter_get(msg); - signature::set(iter, r); - DEBUG("call %d: signature is %s", callId.id, eldbus_message_signature_get(msg)); - } - inline void packValues_helper(Eldbus_Message_Iter *iter) {} - template void packValues_helper(Eldbus_Message_Iter *iter, A &&a, ARGS &&... r) - { - signature::set(iter, std::forward(a)); - packValues_helper(iter, std::forward(r)...); - } - template void packValues(CallId callId, Eldbus_Message *msg, ARGS &&... r) + + class DBusClient : private detail::EldbusProxyBase { - DEBUG("call %d: packing values with signature %s", callId.id, signature_tuple_helper<0, sizeof...(ARGS), ARGS...>::sig().c_str()); - auto iter = eldbus_message_iter_get(msg); - packValues_helper(iter, std::forward(r)...); - DEBUG("call %d: signature is %s", callId.id, eldbus_message_signature_get(msg)); - } + public: + DBusClient() = default; + DBusClient(std::string bus_name, std::string path_name, std::string interface_name, + ConnectionType tp); + DBusClient(std::string bus_name, std::string path_name, std::string interface_name, + const EldbusConnectionCallbackHandle &conn = {}); + ~DBusClient(); - template struct seq {}; - template struct gens : gens < N - 1, N - 1, S... > {}; - template struct gens<0, S...> { - typedef seq type; - }; + DBusClient(const DBusClient &) = delete; + DBusClient(DBusClient &&) = default; - template struct apply_helper { - const C &c; - const std::tuple &args; + DBusClient &operator = (DBusClient &&) = default; + DBusClient &operator = (const DBusClient &) = delete; - template R apply_2(seq) + explicit operator bool () const { - return c(std::get(args)...); + return bool(connectionState.proxy); } - R apply_1() - { - return apply_2(typename gens::type()); - } - }; - template R apply(const C &c, const std::tuple &args) - { - apply_helper ah { c, args }; - return ah.apply_1(); - } - struct EldbusProxyBase { - EldbusProxyBase() + template struct Method { + using RetType = typename detail::dbus_interface_traits::Ret; + detail::ConnectionState connectionState; + std::string funcName, interfaceName; + std::string info; + + template RetType call(const ARGS &... args) + { + detail::CallId callId; + detail::displayDebugCallInfo(callId, funcName, info, interfaceName); + return detail::call(callId, connectionState, false, funcName, args...); + } + template void asyncCall(std::function callback, const ARGS &... args) + { + detail::CallId callId; + detail::displayDebugCallInfo(callId, funcName, info, interfaceName); + auto connectionState = this->connectionState; + detail::asyncCall(callId, connectionState, false, funcName, std::move(callback), args...); + } + }; + template struct Property { + using RetType = typename detail::dbus_interface_return_type_traits::type; + using VariantRetType = typename detail::dbus_interface_return_type_traits>::type; + detail::ConnectionState connectionState; + std::string propName, interfaceName; + std::string info; + + RetType get() + { + detail::CallId callId; + detail::displayDebugCallInfoProperty(callId, "Get", info, interfaceName, propName); + auto z = detail::call(callId, connectionState, true, "Get", interfaceName, propName); + if (!z) return z.getError(); + return { std::get<0>(z.getValues()).value }; + } + void asyncGet(std::function callback) + { + detail::CallId callId; + detail::displayDebugCallInfoProperty(callId, "Get", info, interfaceName, propName); + auto connectionState = this->connectionState; + auto cc = [callback](VariantRetType reply) { + if (reply) + callback(std::move(std::get<0>(reply.getValues()).value)); + else + callback(reply.getError()); + }; + detail::asyncCall(callId, connectionState, true, "Get", std::move(cc), interfaceName, propName); + } + ValueOrError set(const T &r) + { + detail::CallId callId; + detail::displayDebugCallInfoProperty(callId, "Set", info, interfaceName, propName); + EldbusVariant variantValue { std::move(r) }; + return detail::call>(callId, connectionState, true, "Set", interfaceName, propName, variantValue); + } + void asyncSet(std::function)> callback, const T &r) + { + detail::CallId callId; + detail::displayDebugCallInfoProperty(callId, "Set", info, interfaceName, propName); + EldbusVariant variantValue { std::move(r) }; + detail::asyncCall>(callId, connectionState, true, "Set", std::move(callback), interfaceName, propName, variantValue); + } + }; + + template + auto property(std::string propName) { - eldbus_init(); + return Property { connectionState, std::move(propName), interfaceName, info }; } - ~EldbusProxyBase() + template + auto method(std::string funcName) { - eldbus_shutdown(); + return Method { connectionState, std::move(funcName), interfaceName, info }; } - }; -} -namespace detail -{ - template struct Method; - template struct Method; - template struct Method; - template struct Listen; - template struct Listen; - - constexpr static int ELDBUS_CALL_TIMEOUT = 1000; + template void addSignal(std::string signalName, std::function callback) + { + detail::CallId callId; + detail::displayDebugCallInfoSignal(callId, signalName, info, interfaceName); + auto tmp = new std::function { + [ callId, connectionState = this->connectionState, callback ](const Eldbus_Message * msg) -> void { + const char *errname, *aux; + if (eldbus_message_error_get(msg, &errname, &aux)) + { + DBUS_DEBUG("call %d: Eldbus error: %s %s", callId.id, errname, aux); + return; + } - struct ConnectionState { - EldbusConnectionCallbackHandle connection; - EldbusObjectCallbackHandle object; - EldbusProxyCallbackHandle proxy; - }; - static void callAsyncCb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending) - { - auto d = (std::pair> *)data; - DEBUG("call %d: got reply", d->first.id); - d->second(msg); - } - static void pendingFreeCb(void *data, const void *) - { - auto d = (std::pair> *)data; - DEBUG("call %d: deleting", d->first.id); - delete d; - } - template - EldbusMessageCallbackHandle call(CallId callId, ConnectionState &connectionState, const std::string &func_name, const ARGS &... args) - { - if (!connectionState.proxy) { - DEBUG("call %d: not initialized", callId.id); - return {}; + DBUS_DEBUG("call %d: received signal with signature '%s'", callId.id, eldbus_message_signature_get(msg)); + using ParamsType = typename detail::dbus_interface_traits::VEArgs; + auto params = detail::unpackValues(callId, msg); + if (!params) + { + DBUS_DEBUG("call %d: failed: %s", callId.id, params.getError().message.c_str()); + return; + } + detail::apply(callback, params.getValues()); + } + }; + eldbus_proxy_signal_handler_add(connectionState.proxy, signalName.c_str(), listenerCallback, tmp); + destructors.push_back([ = ]() { + delete tmp; + }); } - DEBUG("call %d: calling '%s'", callId.id, func_name.c_str()); - EldbusMessageCallbackHandle msg{eldbus_proxy_method_call_new(connectionState.proxy.get(), func_name.c_str())}; - if (!msg) { - DEBUG("call %d: failed", callId.id); - return {}; - } + private: + detail::ConnectionState connectionState; + std::vector> destructors; + std::string info, interfaceName; - detail::packValues(callId, msg.get(), args...); - auto replyRawPtr = eldbus_proxy_send_and_block(connectionState.proxy.get(), msg.release(), ELDBUS_CALL_TIMEOUT); - EldbusMessageCallbackHandle reply{ replyRawPtr }; - DEBUG("call %d: calling '%s' done", callId.id, func_name.c_str()); - if (!reply) { - DEBUG("call %d: failed", callId.id); - return {}; - } - const char *errname, *errmsg; - if (eldbus_message_error_get(reply.get(), &errname, &errmsg)) { - DEBUG("call %d: %s: %s", callId.id, errname, errmsg); - return {}; + static void listenerCallback(void *data, const Eldbus_Message *msg) + { + (*((std::function *)data))(msg); } - DEBUG("call %d: got reply with signature '%s'", callId.id, eldbus_message_signature_get(reply.get())); - return reply; - } + }; - template - void asyncCall(CallId callId, ConnectionState connectionState, const std::string &func_name, - std::function callback, const ARGS &... args) + class DBusInterfaceDescription { - if (!connectionState.proxy) { - DEBUG("call %d: not initialized", callId.id); - callback({}); - return; - } + friend class DBusServer; + public: + enum class type { + method, property + }; + struct MethodInfo { + detail::CallId id; + std::string memberName; + std::vector in, out; + std::function callback; + }; + struct SignalInfo { + detail::CallId id; + std::string memberName; + std::vector args; + unsigned int uniqueId; + }; + struct PropertyInfo { + detail::CallId setterId, getterId; + std::string memberName, typeSignature; + std::function(const Eldbus_Message *src, Eldbus_Message_Iter *dst)> getCallback, setCallback; + }; + class SignalId + { + friend class ::DBus::DBusServer; + friend class ::DBus::DBusClient; + friend class ::DBus::DBusInterfaceDescription; + detail::CallId id; - EldbusMessageCallbackHandle msg{eldbus_proxy_method_call_new(connectionState.proxy.get(), func_name.c_str())}; - if (!msg) { - DEBUG("call %d: failed", callId.id); - callback({}); - return; - } + SignalId() = default; + SignalId(detail::CallId id) : id(id) { } + }; + DBusInterfaceDescription(std::string interfaceName); - auto cbData = new std::pair> { - callId, [callback, connectionState, callId](const Eldbus_Message * reply) - { - DEBUG("call %d: calling done", callId.id); - if (!reply) - DEBUG("call %d: failed", callId.id); - else { - const char *errname, *errmsg; - if (eldbus_message_error_get(reply, &errname, &errmsg)) { - DEBUG("call %d: %s: %s", callId.id, errname, errmsg); - reply = nullptr; - } else - DEBUG("call %d: got reply with signature '%s'", callId.id, eldbus_message_signature_get(reply)); - } - callback(reply); + template void addMethod(const std::string &memberName, typename detail::dbus_interface_traits::SyncCB callback) + { + detail::CallId callId; + MethodInfo mi; + methods.push_back(std::move(mi)); + auto &z = methods.back(); + z.in = detail::EldbusArgGenerator_Args::get(strings); + z.out = detail::EldbusArgGenerator_ReturnType::get(strings); + z.memberName = memberName; + DBUS_DEBUG("call %d: method %s, in %s, out %s", callId.id, memberName.c_str(), + detail::EldbusArgGenerator_Args::name().c_str(), + detail::EldbusArgGenerator_ReturnType::name().c_str() + ); + z.callback = construct(callId, callback); + z.id = callId; + } + template void addAsyncMethod(const std::string &memberName, typename detail::dbus_interface_traits::AsyncCB callback); + template void addProperty(const std::string &memberName, std::function()> getter, std::function(T)> setter) + { + properties.push_back({}); + auto &z = properties.back(); + z.memberName = memberName; + z.typeSignature = detail::signature::sig(); + if (getter) { + detail::CallId getterId; + z.getterId = getterId; + DBUS_DEBUG("call %d: property %s (get) type %s", getterId.id, memberName.c_str(), detail::signature::name().c_str()); + z.getCallback = [ = ](const Eldbus_Message * src, Eldbus_Message_Iter * dst) -> ValueOrError { + auto v = detail::apply(getter, std::tuple<>{}); + if (v) + { + detail::signature::set(dst, std::get<0>(v.getValues())); + DBUS_DEBUG("call %d: success", getterId.id); + return Success{}; + } + DBUS_DEBUG("call %d: failed: %s", getterId.id, v.getError().message.c_str()); + return v.getError(); + }; + } + if (setter) { + detail::CallId setterId; + z.setterId = setterId; + DBUS_DEBUG("call %d: property %s (set) type %s", setterId.id, memberName.c_str(), detail::signature::name().c_str()); + z.setCallback = [ = ](const Eldbus_Message * src, Eldbus_Message_Iter * src_iter) -> ValueOrError { + std::tuple value; + auto src_signature = eldbus_message_iter_signature_get(src_iter); + if (detail::signature::get(src_iter, std::get<0>(value))) + { + auto v = detail::apply(setter, std::move(value)); + if (v) { + DBUS_DEBUG("call %d: success", setterId.id); + return Success{}; + } + DBUS_DEBUG("call %d: failed: %s", setterId.id, v.getError().message.c_str()); + free(src_signature); + return v.getError(); + } + DBUS_DEBUG("call %d: failed to unpack values, got signature '%s', expected '%s'", setterId.id, + src_signature, detail::signature::sig().c_str()); + return Error { "call " + std::to_string(setterId.id) + ": failed to unpack values, got signature '" + + src_signature + "', expected '" + detail::signature::sig() + "'" }; + }; } - }; - detail::packValues(callId, msg.get(), args...); - auto pending = eldbus_proxy_send(connectionState.proxy.get(), msg.release(), callAsyncCb, cbData, ELDBUS_CALL_TIMEOUT); - if (pending) - eldbus_pending_free_cb_add(pending, pendingFreeCb, cbData); - else { - DEBUG("call %d: sending failed", callId.id); - delete cbData; - callback({}); - return; } - DEBUG("call %d: call sent", callId.id); - } - static void listenerCallback(void *data, const Eldbus_Message *msg) - { - (*((std::function *)data))(msg); - } - template void addListener(CallId callId, ConnectionState &connectionState, - const std::string &func_name, - std::vector*> &callbacks, - std::function callback) - { - auto tmp = new std::function { - [ callId, connectionState, callback ](const Eldbus_Message * msg) -> void { - const char *errname, *aux; - if (eldbus_message_error_get(msg, &errname, &aux)) + template SignalId addSignal(const std::string &memberName) + { + detail::CallId callId; + signals.push_back({}); + auto &z = signals.back(); + z.memberName = memberName; + z.args = detail::EldbusArgGenerator_Args::get(strings); + z.id = callId; + DBUS_DEBUG("call %d: signal %s", callId.id, memberName.c_str()); + return SignalId { callId }; + } + + private: + std::vector methods; + std::vector properties; + std::vector signals; + std::string interfaceName; + detail::StringStorage strings; + + template std::function construct(detail::CallId callId, + typename detail::dbus_interface_traits::SyncCB callback) + { + using VEArgs = typename detail::dbus_interface_traits::VEArgs; + return [ = ](const Eldbus_Message * msg) -> Eldbus_Message* { + Eldbus_Message *ret = nullptr; + auto args = detail::unpackValues(callId, msg); + if (args) + { + auto v = detail::apply(callback, std::move(args.getValues())); + if (v) { + DBUS_DEBUG("call %d: success", callId.id); + ret = eldbus_message_method_return_new(msg); + packValues(callId, ret, v); + } else { + DBUS_DEBUG("call %d: failed: %s", callId.id, v.getError().message.c_str()); + ret = eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.Failed", v.getError().message.c_str()); + } + } else { - ERROR("call %d: Eldbus error: %s %s", callId.id, errname, aux); - return; + std::ostringstream err; + err << "expected signature '" << detail::signature::sig() << + "', got '" << eldbus_message_signature_get(msg) << "'"; + auto str = err.str(); + DBUS_DEBUG("call %d: failed: %s", callId.id, str.c_str()); + ret = eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.InvalidArgs", str.c_str()); } + return ret; + }; + } + }; - DEBUG("call %d: received signal with signature '%s'", callId.id, eldbus_message_signature_get(msg)); - std::tuple params; - if (detail::unpackValues(callId, params, msg)) - detail::apply, void, ARGS...>(callback, params); - } - }; - eldbus_proxy_signal_handler_add(connectionState.proxy.get(), func_name.c_str(), listenerCallback, std::move(tmp)); - callbacks.push_back(tmp); - } - template - void displayDebugCallInfo(CallId callId, const std::string &func_name, const std::string &info, const ARGS &...) - { - DEBUG("call %d: %s fname = %s", callId, info.c_str(), func_name.c_str()); - } - template - void displayDebugCallInfo(CallId callId, const std::string &func_name, std::string info, std::string a, const std::string &b, const ARGS &...) + class DBusServer : private detail::EldbusProxyBase { - if (func_name == "Get" && info.find(" iname = ") == std::string::npos) - DEBUG("call %d: %s iname = %s pname = %s", callId, info.c_str(), a.c_str(), b.c_str()); - else - displayDebugCallInfo(callId, func_name, info); - } -} - -class DBus : private detail::EldbusProxyBase -{ -public: - enum class ConnectionType { - SYSTEM, SESSION - }; - DBus(); - DBus(std::string bus_name, std::string path_name, std::string interface_name, - ConnectionType tp); - DBus(std::string bus_name, std::string path_name, std::string interface_name, - const EldbusConnectionCallbackHandle &conn = {}); - ~DBus(); + public: + DBusServer(); + DBusServer(ConnectionType tp); + DBusServer(const EldbusConnectionCallbackHandle &conn); + ~DBusServer(); - DBus(const DBus &) = default; - DBus(DBus &&) = default; + DBusServer(const DBusServer &) = delete; + DBusServer(DBusServer &&) = default; - DBus &operator = (DBus &&) = default; - DBus &operator = (const DBus &) = default; + DBusServer &operator = (DBusServer &&) = default; + DBusServer &operator = (const DBusServer &) = delete; - explicit operator bool () const - { - return bool(connectionState.proxy); - } + void addInterface(const std::string &pathName, DBusInterfaceDescription &dscr, bool fallback = false); + std::string getBusName() const; - template using Method = detail::Method; - template using Listen = detail::Listen; + template void emit(DBusInterfaceDescription::SignalId signal, const ARGS &... args) + { + auto it = signalData.find(signal.id.id); + if (it != signalData.end()) { + auto msg = eldbus_service_signal_new(it->second.first, it->second.second); + detail::packValues(signal.id, msg, args...); + eldbus_service_signal_send(it->second.first, msg); + } else { + DBUS_DEBUG("signal %d not found", signal.id.id); + } + } + static std::string getCurrentObjectPath(); + static EldbusConnectionCallbackHandle getCurrentConnection(); + private: + EldbusConnectionCallbackHandle connection; + std::vector> destructors; + std::unordered_map> signalData; + }; - template - Method method(std::string func_name) + template void DBusInterfaceDescription::addAsyncMethod(const std::string &memberName, typename detail::dbus_interface_traits::AsyncCB callback) { - return Method { connectionState, std::move(func_name), info }; - } + detail::CallId callId; + MethodInfo mi; + methods.push_back(std::move(mi)); + auto &z = methods.back(); + z.in = detail::EldbusArgGenerator_Args::get(strings); + z.out = detail::EldbusArgGenerator_ReturnType::get(strings); + z.memberName = memberName; + DBUS_DEBUG("call %d: method %s, in %s, out %s", callId.id, memberName.c_str(), + detail::EldbusArgGenerator_Args::name().c_str(), + detail::EldbusArgGenerator_ReturnType::name().c_str() + ); + using VEArgs = typename detail::dbus_interface_traits::VEArgs; + z.callback = [ = ](const Eldbus_Message * msg) -> Eldbus_Message* { + struct CallState { + bool replyRunning = true; + Eldbus_Message *reply = nullptr; + EldbusMessageCallbackHandle message; + }; + auto callState = std::make_shared(); + callState->message.reset(eldbus_message_ref(const_cast(msg))); + auto connection = DBusServer::getCurrentConnection(); + auto retCallback = [ = ](typename detail::dbus_interface_traits::Ret v) + { + DBUS_DEBUG("."); + if (v) { + DBUS_DEBUG("."); + callState->reply = eldbus_message_method_return_new(callState->message.get()); + packValues(callId, callState->reply, v); + } else { + DBUS_DEBUG("call %d: failed: %s", callId.id, v.getError().message.c_str()); + callState->reply = eldbus_message_error_new(callState->message.get(), "org.freedesktop.DBus.Error.Failed", v.getError().message.c_str()); + } + if (!callState->replyRunning) { + DBUS_DEBUG("."); + eldbus_connection_send(connection.get(), callState->reply, NULL, NULL, -1); + } + }; + Eldbus_Message *ret = nullptr; + auto args = detail::unpackValues(callId, msg); + if (args) + { + detail::apply(callback, std::move(retCallback), std::move(args.getValues())); + callState->replyRunning = false; + ret = callState->reply; + } else + { + std::ostringstream err; + err << "expected signature '" << detail::signature::sig() << + "', got '" << eldbus_message_signature_get(msg) << "'"; + auto str = err.str(); + ret = eldbus_message_error_new(msg, "org.freedesktop.DBus.Error.InvalidArgs", str.c_str()); + } + return ret; + }; - template - Listen listen(std::string func_name) - { - return Listen { connectionState, callbacks, std::move(func_name), info }; + z.id = callId; } -private: - detail::ConnectionState connectionState; - std::vector*> callbacks; - std::string info; -}; - -template struct detail::Method { - ConnectionState connectionState; - std::string func_name; - std::string info; + EldbusConnectionCallbackHandle getDBusConnectionByType(ConnectionType tp); + EldbusConnectionCallbackHandle getDBusConnectionByName(const std::string &name); + std::string getConnectionName(const EldbusConnectionCallbackHandle &); +} - Optional call(const ARGS &... args) +namespace std +{ + template inline auto &get(DBus::ValueOrError &v) { - CallId callId; - detail::displayDebugCallInfo(callId, func_name, info, args...); - auto reply = detail::call(callId, connectionState, func_name, args...); - if (reply) { - RetType r; - if (detail::unpackValues(callId, r, reply.get())) - return r; - } - return {}; + return std::get(v.getValues()); } - void asyncCall(const ARGS &... args, std::function)> callback) + template inline auto get(const DBus::ValueOrError &v) { - CallId callId; - detail::displayDebugCallInfo(callId, func_name, info, args...); - auto connectionState = this->connectionState; - auto fn = func_name; - auto cc = [callId, connectionState, callback](const Eldbus_Message * reply) { - DEBUG("call %d: processing asynchronous reply (%d)", callId.id, reply ? 1 : 0); - if (!reply) - callback({}); - else { - RetType r; - if (!detail::unpackValues(callId, r, reply)) - callback({}); - else - callback(r); - } - DEBUG("call %d: processing asynchronous reply done", callId.id); - }; - detail::asyncCall(callId, connectionState, func_name, std::move(cc), args...); + return std::get(v.getValues()); } -}; -template struct detail::Method { - ConnectionState connectionState; - std::string func_name; - std::string info; +} - bool call(const ARGS &... args) - { - CallId callId; - detail::displayDebugCallInfo(callId, func_name, info, args...); - auto reply = detail::call(callId, connectionState, func_name, args...); - return bool(reply); - } - void asyncCall(const ARGS &... args, std::function callback) - { - CallId callId; - detail::displayDebugCallInfo(callId, func_name, info, args...); - auto connectionState = this->connectionState; - auto fn = func_name; - auto cc = [callId, connectionState, callback](const Eldbus_Message * reply) { - DEBUG("call %d: processing asynchronous reply", callId.id); - if (!reply) - callback(false); - else { - callback(true); - } - DEBUG("call %d: processing asynchronous reply done", callId.id); - }; - detail::asyncCall(callId, connectionState, func_name, std::move(cc), args...); - } -}; -template struct detail::Listen { - detail::ConnectionState connectionState; - std::vector*> &callbacks; - std::string func_name; - std::string info; - - void add(std::function cc) - { - CallId callId; - detail::displayDebugCallInfo(callId, func_name, info); - detail::addListener(callId, connectionState, func_name, callbacks, std::move(cc)); - } -}; +#undef DBUS_DEBUG #endif diff --git a/src/GestureActivity.cpp b/src/GestureActivity.cpp index 9663958..a89df17 100644 --- a/src/GestureActivity.cpp +++ b/src/GestureActivity.cpp @@ -35,7 +35,7 @@ public: bool process() override { auto point = uiElement->getScanningCoordinates(); - DBus dbus {dbusLocators::accessibilityEMod::BUS, dbusLocators::accessibilityEMod::OBJ_PATH, dbusLocators::accessibilityEMod::INTERFACE, DBus::ConnectionType::SYSTEM}; + DBus::DBusClient dbus {dbusLocators::accessibilityEMod::BUS, dbusLocators::accessibilityEMod::OBJ_PATH, dbusLocators::accessibilityEMod::INTERFACE, DBus::ConnectionType::SYSTEM}; dbus.method("DispatchGestureEvent").call(swipeType, point.x, point.y); return true; } diff --git a/src/NavigationInterface.cpp b/src/NavigationInterface.cpp index 0435b08..8300f52 100644 --- a/src/NavigationInterface.cpp +++ b/src/NavigationInterface.cpp @@ -42,19 +42,20 @@ class NavigationImpl : public NavigationInterface public: NavigationImpl() { - DBus proxy{"org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus"}; + DBus::DBusClient proxy{"org.a11y.Bus", "/org/a11y/bus", "org.a11y.Bus"}; auto addr = proxy.method("GetAddress").call(); if (addr) { - DEBUG("got dbus %s", (*addr).c_str()); - EldbusConnectionCallbackHandle connection { eldbus_address_connection_get((*addr).c_str()), eldbus_connection_unref }; + DEBUG("got dbus %s", std::get<0>(addr).c_str()); + DBus::EldbusConnectionCallbackHandle connection { eldbus_address_connection_get(std::get<0>(addr).c_str()), eldbus_connection_unref }; navProxy = { "org.tizen.ScreenNavigator", "/org/tizen/ScreenNavigator", "org.tizen.ScreenNavigator", std::move(connection) }; + navProxyServer = { connection }; } if (!navProxy) return; #define LISTEN(n) \ - do { navProxy.listen(#n).add([=]() { emitCallback(); }); } while (0) + do { navProxy.addSignal(#n, [=]() { emitCallback(); }); } while (0) LISTEN(FirstRow); LISTEN(LastRow); LISTEN(FirstElementInRow); @@ -62,20 +63,20 @@ public: #undef LISTEN #define LISTEN(n, SignalName) \ - do { navProxy.listen(SignalName).add([=]() { emitCallback(); }); } while (0) + do { navProxy.addSignal(SignalName, [=]() { emitCallback(); }); } while (0) LISTEN(DashedRow, "SpecialRow"); LISTEN(DashedElementInRow, "SpecialElementInRow"); #undef LISTEN - navProxy.listen("ContextChanged").add( + navProxy.addSignal("ContextChanged", [ = ](AtspiAccessiblePtr obj, int x, int y, int w, int h) { emitCallback(std::make_shared(obj), x, y, w, h); DEBUG("got element %s", Singleton::instance().getUniqueId(obj).c_str()); }); - navProxy.listen("BoxMoved").add( + navProxy.addSignal("BoxMoved", [ = ](int x, int y, int w, int h, BoxPositionMode mode) { emitCallback(x, y, w, h, mode); }); - navProxy.listen>)>("HackAttributesForRootAfterContextChanged").add( + navProxy.addSignal>)>("HackAttributesForRootAfterContextChanged", [ = ](AtspiAccessiblePtr root, std::vector> attrs) { hack_setRootObjectAttributes(root, std::move(attrs)); }); @@ -112,13 +113,13 @@ public: std::shared_ptr getCurrentElement() override { auto current = navProxy.method("GetCurrentElement").call(); - return std::make_shared(std::move(*current)); + return std::make_shared(std::move(std::get<0>(current))); } std::shared_ptr getElementAtPoint(int x, int y) override { auto elem = navProxy.method("GetElementAtPoint").call(x, y); - return std::make_shared(std::move(*elem), Point{x, y}); + return std::make_shared(std::move(std::get<0>(elem)), Point{x, y}); } private: @@ -134,7 +135,8 @@ private: } } - DBus navProxy; + DBus::DBusClient navProxy; + DBus::DBusServer navProxyServer; }; template<> diff --git a/src/ScreenSwitchProvider.cpp b/src/ScreenSwitchProvider.cpp index cc54a28..8675469 100644 --- a/src/ScreenSwitchProvider.cpp +++ b/src/ScreenSwitchProvider.cpp @@ -8,11 +8,11 @@ ScreenSwitchProvider::ScreenSwitchProvider() { switches.emplace_back(std::make_shared("touch", switchProviderId)); - dbus.listen("MouseDown").add([this](auto x) { + dbus.addSignal("MouseDown", [this](auto x) { this->onTouch(x); }); - dbus.listen("MouseUp").add([this](auto x) { + dbus.addSignal("MouseUp", [this](auto x) { this->onRelease(x); }); } diff --git a/src/ScreenSwitchProvider.hpp b/src/ScreenSwitchProvider.hpp index 918560f..9f83cd2 100644 --- a/src/ScreenSwitchProvider.hpp +++ b/src/ScreenSwitchProvider.hpp @@ -22,7 +22,7 @@ private: static bool isSingleTouch(int deviceId); - DBus dbus; + DBus::DBusClient dbus; static constexpr int SINGLE_TOUCH = 0; //When event comes multitouch is recognized as touch with indexes higher than 0 }; diff --git a/src/TextToSpeech.cpp b/src/TextToSpeech.cpp index f9eeae6..74e4882 100644 --- a/src/TextToSpeech.cpp +++ b/src/TextToSpeech.cpp @@ -125,9 +125,9 @@ void TextToSpeech::speak(const std::shared_ptr &element) const } Singleton::instance().getAtspi()->getName(element->getObject(), - [ptr = shared_from_this()](Optional name) { + [ptr = shared_from_this()](DBus::ValueOrError name) { if (name) - ptr->speak(*name); + ptr->speak(std::get<0>(name)); }); } diff --git a/src/UIElement.cpp b/src/UIElement.cpp index 5bba9fa..ff85778 100644 --- a/src/UIElement.cpp +++ b/src/UIElement.cpp @@ -22,13 +22,13 @@ std::shared_ptr UIElement::getObject() const void UIElement::getAttributesAsync(std::function callback) { atspi->getAttributes(obj, - [ptr = shared_from_this(), callback](Optional> data) { + [ptr = shared_from_this(), callback](DBus::ValueOrError> data) { if (!data) { ASSERT(0, "getAttributesAsync failed"); return; } - ptr->attributes = std::move(data); + ptr->attributes = std::move(std::get<0>(data)); for (const auto &a : *ptr->attributes) DEBUG("%s = %s", std::get<0>(a).c_str(), std::get<1>(a).c_str()); @@ -76,28 +76,28 @@ namespace void doSelect(std::shared_ptr atspi, const std::shared_ptr &obj) { atspi->getParent(obj, - [ = ](Optional> parent) { + [ = ](DBus::ValueOrError> parent) { if (!parent) { DEBUG("no parent"); return; } atspi->getIndexInParent(obj, - [ = ](Optional index) { + [ = ](DBus::ValueOrError index) { if (!index) { DEBUG("cant get index in parent"); return; } - atspi->getSelectionInterface(*parent, - [ = ](Optional> sel) { + atspi->getSelectionInterface(std::get<0>(parent), + [ = ](DBus::ValueOrError> sel) { if (!sel) { DEBUG("no selection interface"); return; } - atspi->selectChild(*sel, *index, - [](bool isSuccessful) { + atspi->selectChild(std::get<0>(sel), std::get<0>(index), + [](DBus::ValueOrError isSuccessful) { if (!isSuccessful) { DEBUG("failed to select"); return; @@ -112,15 +112,15 @@ namespace void doActivate(std::shared_ptr atspi, const std::shared_ptr obj) { atspi->getActionInterface(obj, - [atspi, obj](Optional> action) { + [atspi, obj](DBus::ValueOrError> action) { if (!action) { doSelect(atspi, obj); return; } - atspi->doActionName(*action, "activate", - [atspi, obj](Optional status) { - if (!status || !(*status)) { + atspi->doActionName(std::get<0>(action), "activate", + [atspi, obj](DBus::ValueOrError status) { + if (!status || !std::get<0>(status)) { doSelect(atspi, obj); return; } @@ -149,49 +149,53 @@ namespace void changeValue(std::shared_ptr obj, ChangeDirection change) { auto setCurrentValue = [change](auto valueInterface, auto currentValue, auto maximumValue, auto minimumValue) { - auto stepSize = (*maximumValue - *minimumValue) / NUMBER_OF_STEPS; + auto stepSize = (maximumValue - minimumValue) / NUMBER_OF_STEPS; auto newValue = 0.0; switch (change) { case ChangeDirection::INCREMENT: - newValue = *currentValue + stepSize; + newValue = currentValue + stepSize; break; case ChangeDirection::DECREMENT: - newValue = *currentValue - stepSize; + newValue = currentValue - stepSize; break; } - Singleton::instance().setCurrentValue(*valueInterface, newValue, - [](bool isSuccessful) { + Singleton::instance().setCurrentValue(valueInterface, newValue, + [](DBus::ValueOrError isSuccessful) { if (!isSuccessful) DEBUG("Value change failed"); }); }; Singleton::instance().getObjectInRelation(obj, ATSPI_RELATION_CONTROLLER_FOR, - [obj, setCurrentValue](Optional> objInRelation) { + [obj, setCurrentValue](DBus::ValueOrError> objInRelation) { if (!objInRelation) objInRelation = obj; - Singleton::instance().getValueInterface(*objInRelation, - [setCurrentValue](Optional> valueInterface) { + Singleton::instance().getValueInterface(std::get<0>(objInRelation), + [setCurrentValue](DBus::ValueOrError> valueInterface) { if (!valueInterface) return; - Singleton::instance().getCurrentValue(*valueInterface, - [setCurrentValue, valueInterface](Optional currentValue) { + Singleton::instance().getCurrentValue(std::get<0>(valueInterface), + [setCurrentValue, valueInterface](DBus::ValueOrError currentValue) { if (!currentValue) return; - Singleton::instance().getMaximumValue(*valueInterface, - [setCurrentValue, valueInterface, currentValue](Optional maximumValue) { + Singleton::instance().getMaximumValue(std::get<0>(valueInterface), + [setCurrentValue, valueInterface, currentValue](DBus::ValueOrError maximumValue) { if (!maximumValue) return; - Singleton::instance().getMinimumValue(*valueInterface, - [setCurrentValue, valueInterface, currentValue, maximumValue](Optional minimumValue) { + Singleton::instance().getMinimumValue(std::get<0>(valueInterface), + [setCurrentValue, valueInterface, currentValue, maximumValue](DBus::ValueOrError minimumValue) { if (!minimumValue) return; - setCurrentValue(valueInterface, currentValue, maximumValue, minimumValue); + setCurrentValue( + std::get<0>(valueInterface), + std::get<0>(currentValue), + std::get<0>(maximumValue), + std::get<0>(minimumValue)); }); }); }); diff --git a/tests/no-ui-scenarios/DBusTests.cpp b/tests/no-ui-scenarios/DBusTests.cpp new file mode 100644 index 0000000..7cc02e9 --- /dev/null +++ b/tests/no-ui-scenarios/DBusTests.cpp @@ -0,0 +1,694 @@ +#include "DBus.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct TimerData { + std::function callback; + Ecore_Timer *timer = nullptr; +}; +static std::vector> callbacks; + +class DBusTest : public ::testing::Test +{ +protected: + bool success = false, done = false; + + static Eina_Bool callback_fnc(void *data) + { + auto index = reinterpret_cast(data); + callbacks[index]->callback(); + callbacks[index]->timer = nullptr; + return ECORE_CALLBACK_CANCEL; + } + void SetUp(void) override + { + static bool init = true; + if (init) { + init = false; + auto z = ecore_init(); + ASSERT_EQ(z, 1); + ecore_event_init(); + eldbus_init(); + // DBus::setDebugPrinter([](const char *txt, size_t s) { + // std::cerr << txt << std::endl; + // }); + } + } + void cancelAll() + { + for (auto &a : callbacks) { + if (a->timer) + ecore_timer_del(a->timer); + } + callbacks.resize(0); + } + void TearDown() override + { + cancelAll(); + } + void finish(bool succ) + { + cancelAll(); + success = succ; + ecore_main_loop_quit(); + } + void addTimer(float timeout, std::function callback) + { + auto t = std::make_unique(); + t->callback = std::move(callback); + auto index = (unsigned int)callbacks.size(); + callbacks.push_back(std::move(t)); + callbacks.back()->timer = ecore_timer_add(timeout, callback_fnc, reinterpret_cast(index)); + } + void runTestInMainLoop(float timeout) + { + success = false; + done = false; + addTimer(timeout, [this]() { + done = true; + ecore_main_loop_quit(); + return ECORE_CALLBACK_CANCEL; + }); + ecore_main_loop_begin(); + } + + static void addValues(std::map &dst, std::map &s1, std::map &s2) + { + for (auto &a : s1) + dst[a.first] = a.second + s2[a.first]; + } + static void addValues(std::unordered_map &dst, std::unordered_map &s1, std::unordered_map &s2) + { + for (auto &a : s1) + dst[a.first] = a.second + s2[a.first]; + } + static void addValues(std::vector &dst, std::vector &s1, std::vector &s2) + { + dst.resize(s1.size()); + for (size_t i = 0; i < s1.size(); ++i) { + dst[i] = s1[i] + s2[i]; + } + } + template static void addValues(std::array &dst, std::array &s1, std::array &s2) + { + for (size_t i = 0; i < I; ++i) { + dst[i] = s1[i] + s2[i]; + } + } + template static void compareValues(T1 src, T2 expected) + { + EXPECT_EQ(src.size(), expected.size()); + if (src.size() == expected.size()) { + using T = std::reference_wrapper; + std::vector s { src.begin(), src.end() }, e { expected.begin(), expected.end() }; + std::sort(s.begin(), s.end(), [](auto a, auto b) { + return a.get() < b.get(); + }); + std::sort(e.begin(), e.end(), [](auto a, auto b) { + return a.get() < b.get(); + }); + for (size_t i = 0; i < s.size(); ++i) { + EXPECT_EQ(s[i].get(), e[i].get()); + } + } + } + + template void sendAndReceiveT(S source, T expected) + { + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + dsc.addMethod("ftest", [ = ](T a) -> DBus::ValueOrError { + EXPECT_EQ(a.size(), expected.size()); + if (a.size() == expected.size()) + { + compareValues(a, expected); + } + return {}; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + finish(true); + }, source); + runTestInMainLoop(5.0f); + EXPECT_TRUE(success); + } + template void sendAndReceiveMultipleArgs() + { + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + dsc.addMethod("ftest", [](int a, int b) -> DBus::ValueOrError { + return { { a, b} }; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + auto v = std::get<0>(val); + EXPECT_EQ(std::get<0>(v), 7); + EXPECT_EQ(std::get<1>(v), 15); + } + finish(bool(val)); + }, 7, 15); + runTestInMainLoop(5.0f); + EXPECT_TRUE(success); + } +}; + +TEST_F(DBusTest, timeouted) +{ + runTestInMainLoop(0.1f); + EXPECT_FALSE(success); +} + +// calls asynchronously a method. checks, error is passed correctly. +TEST_F(DBusTest, sendAndReceiveError) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + dsc.addMethod("ftest", [](int a, int b) -> DBus::ValueOrError { + return DBus::Error{ "error"}; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_FALSE(val); + finish(true); + }, 7, 15); + runTestInMainLoop(5.0f); + EXPECT_TRUE(success); +} + +// calls asynchronously a method. checks, int marshalls property. +TEST_F(DBusTest, sendAndReceiveInt) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + dsc.addMethod("ftest", [](int a, int b) -> DBus::ValueOrError { + return a + b; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) + EXPECT_EQ(std::get<0>(val), 7 + 15); + finish(true); + }, 7, 15); + runTestInMainLoop(5.0f); + EXPECT_TRUE(success); +} + +// calls method on extern process asynchronously, which in turn calls +// method asynchronously on the original process. Checks, if asynchronous return value production +// works - responding callback receives reply callback, which should be called, when return data is ready +TEST_F(DBusTest, sendAndReceiveAsyncInt) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + dsc.addAsyncMethod("ftest", [&](std::function)> reply, int a, int b) { + if (b == 0) { + reply(a + 10); + } else if (b == 1) { + reply(DBus::Error{"error1"}); + } else if (b == 2) { + addTimer(0.0f, [ = ]() { + reply(a + 15); + }); + } else if (b == 3) { + addTimer(0.0f, [ = ]() { + reply(DBus::Error { "error2"}); + }); + } else { + EXPECT_TRUE(0); + } + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 7 + 10); + finish(true); + } else { + finish(false); + } + }, 7, 0); + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_FALSE(val); + if (!val) { + EXPECT_EQ(val.getError().message, "org.freedesktop.DBus.Error.Failed: error1"); + finish(true); + } else { + finish(false); + } + }, 17, 1); + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 27 + 15); + finish(true); + } else { + finish(false); + } + }, 27, 2); + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + + db.method("ftest").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_FALSE(val); + if (!val) { + EXPECT_EQ(val.getError().message, "org.freedesktop.DBus.Error.Failed: error2"); + finish(true); + } else { + finish(false); + } + }, 37, 3); + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); +} + +// calls asynchronously a method. checks, std::vector marshalls property. +TEST_F(DBusTest, sendAndReceiveVector) +{ + sendAndReceiveT>({ 1, 2 }, { 1, 2 }); +} + +// calls asynchronously a method. checks, std::vector can be received as std::array +TEST_F(DBusTest, sendAndReceiveVector2) +{ + std::array arr { 1, 2 }; + sendAndReceiveT, std::array>({ 1, 2 }, arr); +} + +// calls asynchronously a method. checks, std::array marshalls property. +TEST_F(DBusTest, sendAndReceiveArray) +{ + std::array arr { 1, 2 }; + sendAndReceiveT>(arr, arr); +} + +// calls asynchronously a method. checks, std::array can be received as std::vector +TEST_F(DBusTest, sendAndReceiveArray2) +{ + std::array arr { 1, 2 }; + sendAndReceiveT, std::vector>(arr, { 1, 2 }); +} + +// calls asynchronously a method. checks, std::map marshalls property. +TEST_F(DBusTest, sendAndReceiveMap) +{ + sendAndReceiveT>({ { 1, 1}, {2, 2} }, { { 1, 1}, {2, 2} }); +} + +// calls asynchronously a method. checks, std::unordered_map marshalls property. +TEST_F(DBusTest, sendAndReceiveUnorderedMap) +{ + sendAndReceiveT>({ { 1, 1}, {2, 2} }, { { 1, 1}, {2, 2} }); +} + +// calls asynchronously a method. checks, if std::tuple of values can be +// received as std::pair +TEST_F(DBusTest, sendAndReceiveMultipleArgs) +{ + sendAndReceiveMultipleArgs, std::pair>(); +} + +// calls asynchronously a method. checks, std::tuple marshalls property. +TEST_F(DBusTest, sendAndReceiveMultipleArgs2) +{ + sendAndReceiveMultipleArgs, std::tuple>(); +} + +// calls asynchronously a method. checks, std::pair marshalls property. +TEST_F(DBusTest, sendAndReceiveMultipleArgs3) +{ + sendAndReceiveMultipleArgs, std::pair>(); +} + +// calls asynchronously a method. checks, if std::pair of values can be +// received as std::tuple +TEST_F(DBusTest, sendAndReceiveMultipleArgs4) +{ + sendAndReceiveMultipleArgs, std::tuple>(); +} + +// gets property asynchronously, then sets property asynchronously, +// then gets it again to check, if it has correct value +TEST_F(DBusTest, sendAndReceiveProperty) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + int realVal = 10; + dsc.addProperty("ftest", + [&]() -> DBus::ValueOrError { + return realVal; + }, + [&](int q) -> DBus::ValueOrError { + realVal = q; + return DBus::Success{}; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + db.property("ftest").asyncGet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 10); + EXPECT_EQ(realVal, 10); + + db.property("ftest").asyncSet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + EXPECT_EQ(realVal, 15); + if (val) { + db.property("ftest").asyncGet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 15); + EXPECT_EQ(realVal, 15); + finish(true); + } else { + finish(false); + } + }); + } else { + finish(false); + } + }, 15); + } else { + finish(false); + } + }); + runTestInMainLoop(1.0f); + EXPECT_TRUE(success); +} + +// gets property asynchronously, then sets property asynchronously, +// then gets it again to check, if it has correct value +// uses fallback interface (request path must begins with property interface path) +TEST_F(DBusTest, sendAndReceiveFallbackProperty) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + std::unordered_map values; + + dsc.addProperty("ftest", + [&]() -> DBus::ValueOrError { + auto it = values.find(DBus::DBusServer::getCurrentObjectPath()); + EXPECT_TRUE(it != values.end()); + if (it != values.end()) return it->second; + return -1; + }, + [&](int q) -> DBus::ValueOrError { + values[DBus::DBusServer::getCurrentObjectPath()] = q; + return DBus::Success{}; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc, true); + unsigned int doneCount = 0; + std::vector paths { "/ptest", "/ptest/p1", "/ptest/p2" }; + for (auto p : paths) + values[p] = 10 + (int)p.size(); + + for (auto path : paths) { + DBus::DBusClient db{server.getBusName(), path, "itest.itest", connection}; + db.property("ftest").asyncGet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 10 + (int)path.size()); + EXPECT_EQ(values[path], 10 + (int)path.size()); + + db.property("ftest").asyncSet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + EXPECT_EQ(values[path], 30 + (int)path.size()); + if (val) { + db.property("ftest").asyncGet([ & ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + ++doneCount; + if (val) { + EXPECT_EQ(std::get<0>(val), 30 + (int)path.size()); + EXPECT_EQ(values[path], 30 + (int)path.size()); + finish(true); + } else { + finish(false); + } + }); + } else { + finish(false); + } + }, 30 + path.size()); + } else { + finish(false); + } + }); + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + } +} + +// emits and catches signal +TEST_F(DBusTest, sendAndReceiveSignal) +{ + auto connection = getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusInterfaceDescription dsc("itest.itest"); + std::unordered_map values; + + auto signalId = dsc.addSignal("ftest"); + DBus::DBusServer server{connection}; + server.addInterface("/ptest", dsc, false); + + DBus::DBusClient db{server.getBusName(), "/ptest", "itest.itest", connection}; + int signalValue = 1; + db.addSignal("ftest", [&](int a1, int a2) { + signalValue = a1 + a2; + finish(true); + }); + addTimer(0.0f, [&]() { + server.emit(signalId, 11, 356); + }); + runTestInMainLoop(1.0f); + EXPECT_TRUE(success); + EXPECT_EQ(signalValue, 11 + 356); +} + +static const std::string syncInterfaceName = "sitest.sitest"; +static const std::string syncPathName = "/stest"; +static std::string testBusName; + +// Calls method on external process asynchronously +TEST_F(DBusTest, sendAndReceiveMethodAsync) +{ + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusClient db{testBusName, syncPathName, syncInterfaceName, connection}; + db.method("add").asyncCall([ = ](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) { + EXPECT_EQ(std::get<0>(val), 10 + 55); + finish(true); + } else { + finish(false); + } + }, 10, 55); + runTestInMainLoop(1.0f); + EXPECT_TRUE(success); +} + +// Calls method on external process synchronously +TEST_F(DBusTest, synchronousCall) +{ + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusClient db{testBusName, syncPathName, syncInterfaceName, connection}; + auto r = db.method("add").call(10, 55); + ASSERT_TRUE(r); + ASSERT_EQ(std::get<0>(r), 10 + 55); + r = db.method("add").call(-10, 55); + ASSERT_FALSE(r); + ASSERT_EQ(r.getError().message, "org.freedesktop.DBus.Error.Failed: negative"); +} + +// Calls method 'call' on external process asynchronously, +// which calls back synchronously method 'setReply' on original process +TEST_F(DBusTest, recursiveCall) +{ + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + + DBus::DBusInterfaceDescription dsc("itest.itest2"); + int calledValue = 1; + dsc.addMethod("setReply", [&](int val) -> DBus::ValueOrError { + calledValue = val; + return val + 10; + }); + + DBus::DBusServer server{connection}; + server.addInterface("/ptesttest", dsc, false); + + DBus::DBusClient db{testBusName, syncPathName, syncInterfaceName, connection}; + db.method("call").asyncCall([&](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) + EXPECT_EQ(std::get<0>(val), 111 + 10); + finish(bool(val)); + }, server.getBusName(), "/ptesttest", "itest.itest2", "setReply", 111); + + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + ASSERT_EQ(calledValue, 111); +} + +// Calls method 'emitS' on external process asynchronously, +// which emits signal, which is received on original process +TEST_F(DBusTest, recursiveSignal) +{ + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + + DBus::DBusClient db{testBusName, syncPathName, syncInterfaceName, connection}; + int realValue = 1; + db.addSignal("signal", [&](int val) { + EXPECT_EQ(val, 20); + realValue = val; + finish(true); + }); + + db.method("emitS").asyncCall([&](DBus::ValueOrError val) { + EXPECT_TRUE(val); + if (val) + EXPECT_EQ(std::get<0>(val), 10 + 30); + finish(bool(val)); + }, 10); + + runTestInMainLoop(1.0f); + ASSERT_TRUE(success); + ASSERT_EQ(realValue, 20); +} + +std::pair>, std::vector>> + initializeServices(char *busNameDest, size_t busNameSize) +{ + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + std::string busName = eldbus_connection_unique_name_get(connection.get()); + assert(busName.size() + 2 < busNameSize); + strncpy(busNameDest + 1, busName.c_str(), busName.size()); + busNameDest[0] = 1; + + std::vector> clients; + std::vector> servers; + + auto server = [&]() -> DBus::DBusServer * { + auto s = std::make_unique(connection); + servers.push_back(std::move(s)); + return servers.back().get(); + }; + + auto s = server(); + DBus::DBusInterfaceDescription descr(syncInterfaceName); + + auto signal = descr.addSignal("signal"); + descr.addMethod("add", [ = ](int a, int b) -> DBus::ValueOrError { + if (a >= 0 && b >= 0) + { + return a + b; + } + return DBus::Error { "negative" }; + }); + descr.addMethod("emitS", [ = ](int a) -> DBus::ValueOrError { + s->emit(signal, a + 10); + return a + 20; + }); + descr.addMethod("call", [ = ](std::string busName, std::string pName, std::string iName, std::string methodName, int a) -> DBus::ValueOrError { + auto connection = DBus::getDBusConnectionByType(DBus::ConnectionType::SYSTEM); + DBus::DBusClient client { busName, std::move(pName), std::move(iName), connection }; + return client.method(methodName).call(a); + }); + s->addInterface(syncPathName, descr, false); + return { std::move(clients), std::move(servers) }; +} + +void runServer(char *busNameDest, size_t busNameSize) +{ + // DBus::setDebugPrinter([](const char *txt, size_t s) { + // std::cerr << txt << std::endl; + // }); + ecore_init(); + ecore_event_init(); + eldbus_init(); + auto s = initializeServices(busNameDest, busNameSize); + ecore_main_loop_begin(); +} + +int main(int argc, char *argv[]) +{ + constexpr unsigned int size = 4096; + char *sharedMem = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0); + if (!sharedMem) return 1; + memset(sharedMem, 0, size); + + auto c = fork(); + if (c == 0) { + // child + runServer(sharedMem, size); + } else if (c > 0) { + // parent + DEBUG("main function called"); + auto now = std::chrono::high_resolution_clock::now() + std::chrono::seconds(1); + + while (now > std::chrono::high_resolution_clock::now()) { + if (((volatile char *)sharedMem)[0]) break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + if (!((volatile char *)sharedMem)[0]) return 1; + testBusName = sharedMem + 1; + + int r = 1; + try { + ::testing::InitGoogleTest(&argc, argv); + r = RUN_ALL_TESTS(); + } catch (...) { + DEBUG("exception"); + r = 1; + } + kill(c, 9); + return r; + } else { + ASSERT(0, "failed to fork"); + return 1; + } + return 0; +}