1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <QtCore/qmetaobject.h>
5#include <QtCore/qstringlist.h>
6#include <QtCore/qdebug.h>
7
8#include "qdbusinterface_p.h" // for ANNOTATION_NO_WAIT
9#include "qdbusabstractadaptor_p.h" // for QCLASSINFO_DBUS_*
10#include "qdbusconnection_p.h" // for the flags
11#include "qdbusmetatype_p.h"
12#include "qdbusmetatype.h"
13#include "qdbusutil_p.h"
14
15#ifndef QT_NO_DBUS
16
17QT_BEGIN_NAMESPACE
18
19using namespace Qt::StringLiterals;
20
21extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
22 const QMetaObject *base, int flags);
23
24static inline QString typeNameToXml(const char *typeName)
25{
26 // ### copied from qtextdocument.cpp
27 // ### move this into Qt Core at some point
28 const QLatin1StringView plain(typeName);
29 QString rich;
30 rich.reserve(asize: int(plain.size() * 1.1));
31 for (int i = 0; i < plain.size(); ++i) {
32 if (plain.at(i) == u'<')
33 rich += "&lt;"_L1;
34 else if (plain.at(i) == u'>')
35 rich += "&gt;"_L1;
36 else if (plain.at(i) == u'&')
37 rich += "&amp;"_L1;
38 else
39 rich += plain.at(i);
40 }
41 return rich;
42}
43
44static inline QLatin1StringView accessAsString(bool read, bool write)
45{
46 if (read)
47 return write ? "readwrite"_L1 : "read"_L1 ;
48 else
49 return write ? "write"_L1 : ""_L1 ;
50}
51
52// implement the D-Bus org.freedesktop.DBus.Introspectable interface
53// we do that by analysing the metaObject of all the adaptor interfaces
54
55static QString generateInterfaceXml(const QMetaObject *mo, int flags, int methodOffset, int propOffset)
56{
57 QString retval;
58
59 // start with properties:
60 if (flags & (QDBusConnection::ExportScriptableProperties |
61 QDBusConnection::ExportNonScriptableProperties)) {
62 for (int i = propOffset; i < mo->propertyCount(); ++i) {
63
64 QMetaProperty mp = mo->property(index: i);
65
66 if (!((mp.isScriptable() && (flags & QDBusConnection::ExportScriptableProperties)) ||
67 (!mp.isScriptable() && (flags & QDBusConnection::ExportNonScriptableProperties))))
68 continue;
69
70 QMetaType type = mp.metaType();
71 if (!type.isValid())
72 continue;
73 const char *signature = QDBusMetaType::typeToSignature(type);
74 if (!signature)
75 continue;
76
77 retval += " <property name=\"%1\" type=\"%2\" access=\"%3\""_L1
78 .arg(args: QLatin1StringView(mp.name()),
79 args: QLatin1StringView(signature),
80 args: accessAsString(read: mp.isReadable(), write: mp.isWritable()));
81
82 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
83 const char *typeName = type.name();
84 retval += ">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n"_L1
85 .arg(args: typeNameToXml(typeName));
86 } else {
87 retval += "/>\n"_L1;
88 }
89 }
90 }
91
92 // now add methods:
93 for (int i = methodOffset; i < mo->methodCount(); ++i) {
94 QMetaMethod mm = mo->method(index: i);
95
96 bool isSignal = false;
97 bool isSlot = false;
98 if (mm.methodType() == QMetaMethod::Signal)
99 // adding a signal
100 isSignal = true;
101 else if (mm.access() == QMetaMethod::Public && mm.methodType() == QMetaMethod::Slot)
102 isSlot = true;
103 else if (mm.access() == QMetaMethod::Public && mm.methodType() == QMetaMethod::Method)
104 ; // invokable, neither signal nor slot
105 else
106 continue; // neither signal nor public method/slot
107
108 if (isSignal && !(flags & (QDBusConnection::ExportScriptableSignals |
109 QDBusConnection::ExportNonScriptableSignals)))
110 continue; // we're not exporting any signals
111 if (!isSignal && (!(flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) &&
112 !(flags & (QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportNonScriptableInvokables))))
113 continue; // we're not exporting any slots or invokables
114
115 // we want to skip non-scriptable stuff as early as possible to avoid bogus warning
116 // for methods that are not being exported at all
117 bool isScriptable = mm.attributes() & QMetaMethod::Scriptable;
118 if (!isScriptable && !(flags & (isSignal ? QDBusConnection::ExportNonScriptableSignals : QDBusConnection::ExportNonScriptableInvokables | QDBusConnection::ExportNonScriptableSlots)))
119 continue;
120
121 QString xml = QString::asprintf(format: " <%s name=\"%s\">\n",
122 isSignal ? "signal" : "method", mm.name().constData());
123
124 // check the return type first
125 QMetaType typeId = mm.returnMetaType();
126 if (typeId.isValid() && typeId.id() != QMetaType::Void) {
127 const char *typeName = QDBusMetaType::typeToSignature(type: typeId);
128 if (typeName) {
129 xml += " <arg type=\"%1\" direction=\"out\"/>\n"_L1
130 .arg(args: typeNameToXml(typeName));
131
132 // do we need to describe this argument?
133 if (!QDBusMetaType::signatureToMetaType(signature: typeName).isValid())
134 xml += " <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n"_L1
135 .arg(args: typeNameToXml(typeName: QMetaType(typeId).name()));
136 } else {
137 qWarning() << "Unsupported return type" << typeId.id() << typeId.name() << "in method" << mm.name();
138 continue;
139 }
140 }
141 else if (!typeId.isValid()) {
142 qWarning() << "Invalid return type in method" << mm.name();
143 continue; // wasn't a valid type
144 }
145
146 QList<QByteArray> names = mm.parameterNames();
147 QList<QMetaType> types;
148 QString errorMsg;
149 int inputCount = qDBusParametersForMethod(mm, metaTypes&: types, errorMsg);
150 if (inputCount == -1) {
151 qWarning() << "Skipped method" << mm.name() << ":" << qPrintable(errorMsg);
152 continue; // invalid form
153 }
154 if (isSignal && inputCount + 1 != types.size())
155 continue; // signal with output arguments?
156 if (isSignal && types.at(i: inputCount) == QDBusMetaTypeId::message())
157 continue; // signal with QDBusMessage argument?
158 if (isSignal && mm.attributes() & QMetaMethod::Cloned)
159 continue; // cloned signal?
160
161 int j;
162 for (j = 1; j < types.size(); ++j) {
163 // input parameter for a slot or output for a signal
164 if (types.at(i: j) == QDBusMetaTypeId::message()) {
165 isScriptable = true;
166 continue;
167 }
168
169 QString name;
170 if (!names.at(i: j - 1).isEmpty())
171 name = "name=\"%1\" "_L1.arg(args: QLatin1StringView(names.at(i: j - 1)));
172
173 bool isOutput = isSignal || j > inputCount;
174
175 const char *signature = QDBusMetaType::typeToSignature(type: types.at(i: j));
176 xml += QString::asprintf(format: " <arg %lstype=\"%s\" direction=\"%s\"/>\n",
177 qUtf16Printable(name), signature, isOutput ? "out" : "in");
178
179 // do we need to describe this argument?
180 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
181 const char *typeName = QMetaType(types.at(i: j)).name();
182 xml += QString::fromLatin1(ba: " <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
183 .arg(a: isOutput ? "Out"_L1 : "In"_L1)
184 .arg(a: isOutput && !isSignal ? j - inputCount : j - 1)
185 .arg(a: typeNameToXml(typeName));
186 }
187 }
188
189 int wantedMask;
190 if (isScriptable)
191 wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
192 : isSlot ? QDBusConnection::ExportScriptableSlots
193 : QDBusConnection::ExportScriptableInvokables;
194 else
195 wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
196 : isSlot ? QDBusConnection::ExportNonScriptableSlots
197 : QDBusConnection::ExportNonScriptableInvokables;
198 if ((flags & wantedMask) != wantedMask)
199 continue;
200
201 if (qDBusCheckAsyncTag(tag: mm.tag()))
202 // add the no-reply annotation
203 xml += " <annotation name=\"" ANNOTATION_NO_WAIT "\" value=\"true\"/>\n"_L1;
204
205 retval += xml;
206 retval += " </%1>\n"_L1.arg(args: isSignal ? "signal"_L1 : "method"_L1);
207 }
208
209 return retval;
210}
211
212QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
213 const QMetaObject *base, int flags)
214{
215 if (interface.isEmpty())
216 // generate the interface name from the meta object
217 interface = qDBusInterfaceFromMetaObject(mo);
218
219 QString xml;
220 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
221 if (idx >= mo->classInfoOffset())
222 return QString::fromUtf8(utf8: mo->classInfo(index: idx).value());
223 else
224 xml = generateInterfaceXml(mo, flags, methodOffset: base->methodCount(), propOffset: base->propertyCount());
225
226 if (xml.isEmpty())
227 return QString(); // don't add an empty interface
228 return " <interface name=\"%1\">\n%2 </interface>\n"_L1
229 .arg(args&: interface, args&: xml);
230}
231#if 0
232QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, const QMetaObject *base,
233 int flags)
234{
235 if (interface.isEmpty()) {
236 // generate the interface name from the meta object
237 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTERFACE);
238 if (idx >= mo->classInfoOffset()) {
239 interface = QLatin1StringView(mo->classInfo(idx).value());
240 } else {
241 interface = QLatin1StringView(mo->className());
242 interface.replace("::"_L1, "."_L1);
243
244 if (interface.startsWith("QDBus"_L1)) {
245 interface.prepend("org.qtproject.QtDBus."_L1);
246 } else if (interface.startsWith(u'Q') &&
247 interface.length() >= 2 && interface.at(1).isUpper()) {
248 // assume it's Qt
249 interface.prepend("org.qtproject.Qt."_L1);
250 } else if (!QCoreApplication::instance()||
251 QCoreApplication::instance()->applicationName().isEmpty()) {
252 interface.prepend("local."_L1);
253 } else {
254 interface.prepend(u'.').prepend(QCoreApplication::instance()->applicationName());
255 QStringList domainName =
256 QCoreApplication::instance()->organizationDomain().split(u'.',
257 Qt::SkipEmptyParts);
258 if (domainName.isEmpty())
259 interface.prepend("local."_L1);
260 else
261 for (int i = 0; i < domainName.count(); ++i)
262 interface.prepend(u'.').prepend(domainName.at(i));
263 }
264 }
265 }
266
267 QString xml;
268 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
269 if (idx >= mo->classInfoOffset())
270 return QString::fromUtf8(mo->classInfo(idx).value());
271 else
272 xml = generateInterfaceXml(mo, flags, base->methodCount(), base->propertyCount());
273
274 if (xml.isEmpty())
275 return QString(); // don't add an empty interface
276 return QString::fromLatin1(" <interface name=\"%1\">\n%2 </interface>\n")
277 .arg(interface, xml);
278}
279
280#endif
281
282QT_END_NAMESPACE
283
284#endif // QT_NO_DBUS
285

source code of qtbase/src/dbus/qdbusxmlgenerator.cpp