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 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, |
22 | const QMetaObject *base, int flags); |
23 | |
24 | static 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 += "<"_L1 ; |
34 | else if (plain.at(i) == u'>') |
35 | rich += ">"_L1 ; |
36 | else if (plain.at(i) == u'&') |
37 | rich += "&"_L1 ; |
38 | else |
39 | rich += plain.at(i); |
40 | } |
41 | return rich; |
42 | } |
43 | |
44 | static 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 | |
55 | static 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 | |
212 | QString 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 |
232 | QString 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 | |
282 | QT_END_NAMESPACE |
283 | |
284 | #endif // QT_NO_DBUS |
285 | |