/** * (C) 2016 - 2021 KISTLER INSTRUMENTE AG, Winterthur, Switzerland * (C) 2016 - 2022 Stanislav Angelovic * * @file ProxyGenerator.cpp * * Created on: Feb 1, 2017 * Project: sdbus-c++ * Description: High-level D-Bus IPC C++ library based on sd-bus * * This file is part of sdbus-c++. * * sdbus-c++ is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * sdbus-c++ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with sdbus-c++. If not, see . */ // Own #include "generator_utils.h" #include "ProxyGenerator.h" // STL #include #include #include #include #include using std::endl; using sdbuscpp::xml::Document; using sdbuscpp::xml::Node; using sdbuscpp::xml::Nodes; /** * Generate proxy code - client glue */ int ProxyGenerator::transformXmlToFileImpl(const Document &doc, const char *filename) const { Node &root = *(doc.root); Nodes interfaces = root["interface"]; std::ostringstream code; code << createHeader(filename, StubType::PROXY); for (const auto& interface : interfaces) { code << processInterface(*interface); } code << "#endif" << endl; return writeToFile(filename, code.str()); } std::string ProxyGenerator::processInterface(Node& interface) const { std::string ifaceName = interface.get("name"); std::cout << "Generating proxy code for interface " << ifaceName << endl; unsigned int namespacesCount = 0; std::string namespacesStr; std::tie(namespacesCount, namespacesStr) = generateNamespaces(ifaceName); std::ostringstream body; body << namespacesStr; std::string className = ifaceName.substr(ifaceName.find_last_of(".") + 1) + "_proxy"; body << "class " << className << endl << "{" << endl << "public:" << endl << tab << "static constexpr const char* INTERFACE_NAME = \"" << ifaceName << "\";" << endl << endl << "protected:" << endl << tab << className << "(sdbus::IProxy& proxy)" << endl << tab << tab << ": proxy_(&proxy)" << endl << tab << "{" << endl << tab << "}" << endl << endl; // Rule of Five body << tab << className << "(const " << className << "&) = delete;" << endl; body << tab << className << "& operator=(const " << className << "&) = delete;" << endl; body << tab << className << "(" << className << "&&) = default;" << endl; body << tab << className << "& operator=(" << className << "&&) = default;" << endl << endl; body << tab << "~" << className << "() = default;" << endl << endl; Nodes methods = interface["method"]; Nodes signals = interface["signal"]; Nodes properties = interface["property"]; std::string registration, declaration; std::tie(registration, declaration) = processSignals(signals); body << tab << "void registerProxy()" << endl << tab << "{" << endl << registration << tab << "}" << endl << endl; if (!declaration.empty()) body << declaration << endl; std::string methodDefinitions, asyncDeclarationsMethods; std::tie(methodDefinitions, asyncDeclarationsMethods) = processMethods(methods); std::string propertyDefinitions, asyncDeclarationsProperties; std::tie(propertyDefinitions, asyncDeclarationsProperties) = processProperties(properties); if (!asyncDeclarationsMethods.empty()) { body << asyncDeclarationsMethods << endl; } if (!asyncDeclarationsProperties.empty()) { body << asyncDeclarationsProperties << endl; } if (!methodDefinitions.empty()) { body << "public:" << endl << methodDefinitions; } if (!propertyDefinitions.empty()) { body << "public:" << endl << propertyDefinitions; } body << "private:" << endl << tab << "sdbus::IProxy* proxy_;" << endl << "};" << endl << endl << std::string(namespacesCount, '}') << " // namespaces" << endl << endl; return body.str(); } std::tuple ProxyGenerator::processMethods(const Nodes& methods) const { const std::regex patternTimeout{R"(^(\d+)(min|s|ms|us)?$)"}; std::ostringstream definitionSS, asyncDeclarationSS; for (const auto& method : methods) { auto name = method->get("name"); auto nameSafe = mangle_name(name); Nodes args = (*method)["arg"]; Nodes inArgs = args.select("direction" , "in"); Nodes outArgs = args.select("direction" , "out"); bool dontExpectReply{false}; bool async{false}; bool future{false}; // Async methods implemented by means of either std::future or callbacks std::string timeoutValue; std::smatch smTimeout; Nodes annotations = (*method)["annotation"]; for (const auto& annotation : annotations) { const auto annotationName = annotation->get("name"); const auto annotationValue = annotation->get("value"); if (annotationName == "org.freedesktop.DBus.Method.NoReply" && annotationValue == "true") dontExpectReply = true; else { if (annotationName == "org.freedesktop.DBus.Method.Async" && (annotationValue == "client" || annotationValue == "clientserver" || annotationValue == "client-server")) async = true; else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && annotationValue == "callback") future = false; else if (annotationName == "org.freedesktop.DBus.Method.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future")) future = true; } if (annotationName == "org.freedesktop.DBus.Method.Timeout") timeoutValue = annotationValue; } if (dontExpectReply && outArgs.size() > 0) { std::cerr << "Function: " << name << ": "; std::cerr << "Option 'org.freedesktop.DBus.Method.NoReply' not allowed for methods with 'out' variables! Option ignored..." << std::endl; dontExpectReply = false; } if (!timeoutValue.empty() && dontExpectReply) { std::cerr << "Function: " << name << ": "; std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' not allowed for 'NoReply' methods! Option ignored..." << std::endl; timeoutValue.clear(); } if (!timeoutValue.empty() && !std::regex_match(timeoutValue, smTimeout, patternTimeout)) { std::cerr << "Function: " << name << ": "; std::cerr << "Option 'org.freedesktop.DBus.Method.Timeout' has unsupported timeout value! Option ignored..." << std::endl; timeoutValue.clear(); } auto retType = outArgsToType(outArgs); auto retTypeBare = outArgsToType(outArgs, true); std::string inArgStr, inArgTypeStr; std::tie(inArgStr, inArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(inArgs); std::string outArgStr, outArgTypeStr; std::tie(outArgStr, outArgTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(outArgs); const std::string realRetType = (async && !dontExpectReply ? (future ? "std::future<" + retType + ">" : "sdbus::PendingAsyncCall") : async ? "void" : retType); definitionSS << tab << realRetType << " " << nameSafe << "(" << inArgTypeStr << ")" << endl << tab << "{" << endl; if (!timeoutValue.empty()) { definitionSS << tab << tab << "using namespace std::chrono_literals;" << endl; } if (outArgs.size() > 0 && !async) { definitionSS << tab << tab << retType << " result;" << endl; } definitionSS << tab << tab << (async && !dontExpectReply ? "return " : "") << "proxy_->callMethod" << (async ? "Async" : "") << "(\"" << name << "\").onInterface(INTERFACE_NAME)"; if (!timeoutValue.empty()) { const auto val = smTimeout.str(1); const auto unit = smTimeout.str(2); definitionSS << ".withTimeout(" << val << (unit.empty() ? "us" : unit) << ")"; } if (inArgs.size() > 0) { definitionSS << ".withArguments(" << inArgStr << ")"; } if (async && !dontExpectReply) { auto nameBigFirst = name; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; if (future) // Async methods implemented through future { definitionSS << ".getResultAsFuture<" << retTypeBare << ">()"; } else // Async methods implemented through callbacks { definitionSS << ".uponReplyInvoke([this](const sdbus::Error* error" << (outArgTypeStr.empty() ? "" : ", ") << outArgTypeStr << ")" "{ this->on" << nameBigFirst << "Reply(" << outArgStr << (outArgStr.empty() ? "" : ", ") << "error); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "Reply(" << outArgTypeStr << (outArgTypeStr.empty() ? "" : ", ") << "const sdbus::Error* error) = 0;" << endl; } } else if (outArgs.size() > 0) { definitionSS << ".storeResultsTo(result);" << endl << tab << tab << "return result"; } else if (dontExpectReply) { definitionSS << ".dontExpectReply()"; } definitionSS << ";" << endl << tab << "}" << endl << endl; } return std::make_tuple(definitionSS.str(), asyncDeclarationSS.str()); } std::tuple ProxyGenerator::processSignals(const Nodes& signals) const { std::ostringstream registrationSS, declarationSS; for (const auto& signal : signals) { auto name = signal->get("name"); Nodes args = (*signal)["arg"]; auto nameBigFirst = name; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; std::string argStr, argTypeStr; std::tie(argStr, argTypeStr, std::ignore, std::ignore) = argsToNamesAndTypes(args); registrationSS << tab << tab << "proxy_" "->uponSignal(\"" << name << "\")" ".onInterface(INTERFACE_NAME)" ".call([this](" << argTypeStr << ")" "{ this->on" << nameBigFirst << "(" << argStr << "); });" << endl; declarationSS << tab << "virtual void on" << nameBigFirst << "(" << argTypeStr << ") = 0;" << endl; } return std::make_tuple(registrationSS.str(), declarationSS.str()); } std::tuple ProxyGenerator::processProperties(const Nodes& properties) const { std::ostringstream propertySS, asyncDeclarationSS; for (const auto& property : properties) { auto propertyName = property->get("name"); auto propertyNameSafe = mangle_name(propertyName); auto propertyAccess = property->get("access"); auto propertySignature = property->get("type"); auto propertyType = signature_to_type(propertySignature); auto propertyArg = std::string("value"); auto propertyTypeArg = std::string("const ") + propertyType + "& " + propertyArg; bool asyncGet{false}; bool futureGet{false}; // Async property getter implemented by means of either std::future or callbacks bool asyncSet{false}; bool futureSet{false}; // Async property setter implemented by means of either std::future or callbacks Nodes annotations = (*property)["annotation"]; for (const auto& annotation : annotations) { const auto annotationName = annotation->get("name"); const auto annotationValue = annotation->get("value"); if (annotationName == "org.freedesktop.DBus.Property.Get.Async" && annotationValue == "client") // Server-side not supported (may be in the future) asyncGet = true; else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && annotationValue == "callback") futureGet = false; else if (annotationName == "org.freedesktop.DBus.Property.Get.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future")) futureGet = true; else if (annotationName == "org.freedesktop.DBus.Property.Set.Async" && annotationValue == "client") // Server-side not supported (may be in the future) asyncSet = true; else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && annotationValue == "callback") futureSet = false; else if (annotationName == "org.freedesktop.DBus.Property.Set.Async.ClientImpl" && (annotationValue == "future" || annotationValue == "std::future")) futureSet = true; } if (propertyAccess == "read" || propertyAccess == "readwrite") { const std::string realRetType = (asyncGet ? (futureGet ? "std::future" : "sdbus::PendingAsyncCall") : propertyType); propertySS << tab << realRetType << " " << propertyNameSafe << "()" << endl << tab << "{" << endl; propertySS << tab << tab << "return proxy_->getProperty" << (asyncGet ? "Async" : "") << "(\"" << propertyName << "\")" ".onInterface(INTERFACE_NAME)"; if (asyncGet) { auto nameBigFirst = propertyName; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; if (futureGet) // Async methods implemented through future { propertySS << ".getResultAsFuture()"; } else // Async methods implemented through callbacks { propertySS << ".uponReplyInvoke([this](const sdbus::Error* error, const sdbus::Variant& value)" "{ this->on" << nameBigFirst << "PropertyGetReply(value.get<" << propertyType << ">(), error); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertyGetReply(" << "const " << propertyType << "& value, const sdbus::Error* error) = 0;" << endl; } } propertySS << ";" << endl << tab << "}" << endl << endl; } if (propertyAccess == "readwrite" || propertyAccess == "write") { const std::string realRetType = (asyncSet ? (futureSet ? "std::future" : "sdbus::PendingAsyncCall") : "void"); propertySS << tab << realRetType << " " << propertyNameSafe << "(" << propertyTypeArg << ")" << endl << tab << "{" << endl; propertySS << tab << tab << (asyncSet ? "return " : "") << "proxy_->setProperty" << (asyncSet ? "Async" : "") << "(\"" << propertyName << "\")" ".onInterface(INTERFACE_NAME)" ".toValue(" << propertyArg << ")"; if (asyncSet) { auto nameBigFirst = propertyName; nameBigFirst[0] = islower(nameBigFirst[0]) ? nameBigFirst[0] + 'A' - 'a' : nameBigFirst[0]; if (futureSet) // Async methods implemented through future { propertySS << ".getResultAsFuture()"; } else // Async methods implemented through callbacks { propertySS << ".uponReplyInvoke([this](const sdbus::Error* error)" "{ this->on" << nameBigFirst << "PropertySetReply(error); })"; asyncDeclarationSS << tab << "virtual void on" << nameBigFirst << "PropertySetReply(" << "const sdbus::Error* error) = 0;" << endl; } } propertySS << ";" << endl << tab << "}" << endl << endl; } } return std::make_tuple(propertySS.str(), asyncDeclarationSS.str()); }