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 "qdbusservicewatcher.h" |
5 | #include "qdbusconnection.h" |
6 | #include "qdbusutil_p.h" |
7 | |
8 | #include <QStringList> |
9 | |
10 | #include <private/qproperty_p.h> |
11 | #include <private/qobject_p.h> |
12 | #include <private/qdbusconnection_p.h> |
13 | |
14 | #ifndef QT_NO_DBUS |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | class QDBusServiceWatcherPrivate: public QObjectPrivate |
19 | { |
20 | Q_DECLARE_PUBLIC(QDBusServiceWatcher) |
21 | public: |
22 | QDBusServiceWatcherPrivate(const QDBusConnection &c, QDBusServiceWatcher::WatchMode wm) |
23 | : connection(c), watchMode(wm) |
24 | { |
25 | } |
26 | |
27 | void setWatchedServicesForwardToQ(const QStringList &list) |
28 | { |
29 | q_func()->setWatchedServices(list); |
30 | } |
31 | Q_OBJECT_COMPAT_PROPERTY(QDBusServiceWatcherPrivate, QStringList, watchedServicesData, |
32 | &QDBusServiceWatcherPrivate::setWatchedServicesForwardToQ) |
33 | |
34 | QDBusConnection connection; |
35 | void setWatchModeForwardToQ(QDBusServiceWatcher::WatchMode mode) |
36 | { |
37 | q_func()->setWatchMode(mode); |
38 | } |
39 | Q_OBJECT_COMPAT_PROPERTY(QDBusServiceWatcherPrivate, QDBusServiceWatcher::WatchMode, watchMode, |
40 | &QDBusServiceWatcherPrivate::setWatchModeForwardToQ) |
41 | |
42 | void _q_serviceOwnerChanged(const QString &, const QString &, const QString &); |
43 | void setConnection(const QStringList &newServices, const QDBusConnection &newConnection, |
44 | QDBusServiceWatcher::WatchMode newMode); |
45 | |
46 | void addService(const QString &service, QDBusServiceWatcher::WatchMode mode); |
47 | void removeService(const QString &service, QDBusServiceWatcher::WatchMode mode); |
48 | }; |
49 | |
50 | void QDBusServiceWatcherPrivate::_q_serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner) |
51 | { |
52 | Q_Q(QDBusServiceWatcher); |
53 | emit q->serviceOwnerChanged(service, oldOwner, newOwner); |
54 | if (oldOwner.isEmpty()) |
55 | emit q->serviceRegistered(service); |
56 | else if (newOwner.isEmpty()) |
57 | emit q->serviceUnregistered(service); |
58 | } |
59 | |
60 | void QDBusServiceWatcherPrivate::setConnection(const QStringList &newServices, |
61 | const QDBusConnection &newConnection, |
62 | QDBusServiceWatcher::WatchMode newMode) |
63 | { |
64 | const QStringList oldServices = watchedServicesData.valueBypassingBindings(); |
65 | const QDBusServiceWatcher::WatchMode oldMode = watchMode.valueBypassingBindings(); |
66 | if (connection.isConnected()) { |
67 | // remove older rules |
68 | for (const QString &s : oldServices) |
69 | removeService(service: s, mode: oldMode); |
70 | } |
71 | |
72 | connection = newConnection; |
73 | watchMode.setValueBypassingBindings(newMode); // caller has to call notify() |
74 | watchedServicesData.setValueBypassingBindings(newServices); // caller has to call notify() |
75 | |
76 | if (connection.isConnected()) { |
77 | // add new rules |
78 | for (const QString &s : newServices) |
79 | addService(service: s, mode: newMode); |
80 | } |
81 | } |
82 | |
83 | void QDBusServiceWatcherPrivate::addService(const QString &service, |
84 | QDBusServiceWatcher::WatchMode mode) |
85 | { |
86 | QDBusConnectionPrivate *d = QDBusConnectionPrivate::d(q: connection); |
87 | if (d && d->shouldWatchService(service)) |
88 | d->watchService(service, mode, obj: q_func(), SLOT(_q_serviceOwnerChanged(QString,QString,QString))); |
89 | } |
90 | |
91 | void QDBusServiceWatcherPrivate::removeService(const QString &service, |
92 | QDBusServiceWatcher::WatchMode mode) |
93 | { |
94 | QDBusConnectionPrivate *d = QDBusConnectionPrivate::d(q: connection); |
95 | if (d && d->shouldWatchService(service)) |
96 | d->unwatchService(service, mode, obj: q_func(), SLOT(_q_serviceOwnerChanged(QString,QString,QString))); |
97 | } |
98 | |
99 | /*! |
100 | \class QDBusServiceWatcher |
101 | \since 4.6 |
102 | \inmodule QtDBus |
103 | |
104 | \brief The QDBusServiceWatcher class allows the user to watch for a bus service change. |
105 | |
106 | A QDBusServiceWatcher object can be used to notify the application about |
107 | an ownership change of a service name on the bus. It has three watch |
108 | modes: |
109 | |
110 | \list |
111 | \li Watching for service registration only. |
112 | \li Watching for service unregistration only. |
113 | \li Watching for any kind of service ownership change (the default mode). |
114 | \endlist |
115 | |
116 | Besides being created or deleted, services may change owners without a |
117 | unregister/register operation happening. So the serviceRegistered() |
118 | and serviceUnregistered() signals may not be emitted if that |
119 | happens. |
120 | |
121 | This class is more efficient than using the |
122 | QDBusConnectionInterface::serviceOwnerChanged() signal because it allows |
123 | one to receive only the signals for which the class is interested in. |
124 | |
125 | Ending a service name with the character '*' will match all service names |
126 | within the specified namespace. |
127 | |
128 | For example "com.example.backend1*" will match |
129 | \list |
130 | \li com.example.backend1 |
131 | \li com.example.backend1.foo |
132 | \li com.example.backend1.foo.bar |
133 | \endlist |
134 | Substrings in the same domain will not be matched, i.e "com.example.backend12". |
135 | |
136 | \sa QDBusConnection |
137 | */ |
138 | |
139 | /*! |
140 | \enum QDBusServiceWatcher::WatchModeFlag |
141 | |
142 | QDBusServiceWatcher supports three different watch modes, which are configured by this flag: |
143 | |
144 | \value WatchForRegistration watch for service registration only, ignoring |
145 | any signals related to other service ownership change. |
146 | |
147 | \value WatchForUnregistration watch for service unregistration only, |
148 | ignoring any signals related to other service ownership change. |
149 | |
150 | \value WatchForOwnerChange watch for any kind of service ownership |
151 | change. |
152 | */ |
153 | |
154 | /*! |
155 | \property QDBusServiceWatcher::watchMode |
156 | \brief the current watch mode for this QDBusServiceWatcher object. |
157 | |
158 | The default value for this property is |
159 | QDBusServiceWatcher::WatchForOwnershipChange. |
160 | */ |
161 | |
162 | /*! |
163 | \property QDBusServiceWatcher::watchedServices |
164 | \brief the list of services watched. |
165 | |
166 | \note Modifying this list with setServicesWatched() is an expensive |
167 | operation. If you can, prefer to change it by way of addWatchedService() |
168 | and removeWatchedService(). |
169 | */ |
170 | |
171 | /*! |
172 | \fn void QDBusServiceWatcher::serviceRegistered(const QString &serviceName) |
173 | |
174 | This signal is emitted whenever this object detects that the service \a |
175 | serviceName became available on the bus. |
176 | |
177 | \sa serviceUnregistered(), serviceOwnerChanged() |
178 | */ |
179 | |
180 | /*! |
181 | \fn void QDBusServiceWatcher::serviceUnregistered(const QString &serviceName) |
182 | |
183 | This signal is emitted whenever this object detects that the service \a |
184 | serviceName was unregistered from the bus and is no longer available. |
185 | |
186 | \sa serviceRegistered(), serviceOwnerChanged() |
187 | */ |
188 | |
189 | /*! |
190 | \fn void QDBusServiceWatcher::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) |
191 | |
192 | This signal is emitted whenever this object detects that there was a |
193 | service ownership change relating to the \a serviceName service. The \a |
194 | oldOwner parameter contains the old owner name and \a newOwner is the new |
195 | owner. Both \a oldOwner and \a newOwner are unique connection names. |
196 | |
197 | Note that this signal is also emitted whenever the \a serviceName service |
198 | was registered or unregistered. If it was registered, \a oldOwner will |
199 | contain an empty string, whereas if it was unregistered, \a newOwner will |
200 | contain an empty string. |
201 | |
202 | If you need only to find out if the service is registered or unregistered |
203 | only, without being notified that the ownership changed, consider using |
204 | the specific modes for those operations. This class is more efficient if |
205 | you use the more specific modes. |
206 | |
207 | \sa serviceRegistered(), serviceUnregistered() |
208 | */ |
209 | |
210 | /*! |
211 | Creates a QDBusServiceWatcher object. Note that until you set a |
212 | connection with setConnection(), this object will not emit any signals. |
213 | |
214 | The \a parent parameter is passed to QObject to set the parent of this |
215 | object. |
216 | */ |
217 | QDBusServiceWatcher::QDBusServiceWatcher(QObject *parent) |
218 | : QObject(*new QDBusServiceWatcherPrivate(QDBusConnection(QString()), WatchForOwnerChange), parent) |
219 | { |
220 | } |
221 | |
222 | /*! |
223 | Creates a QDBusServiceWatcher object and attaches it to the \a connection |
224 | connection. Also, this function immediately starts watching for \a |
225 | watchMode changes to service \a service. |
226 | |
227 | The \a parent parameter is passed to QObject to set the parent of this |
228 | object. |
229 | */ |
230 | QDBusServiceWatcher::QDBusServiceWatcher(const QString &service, const QDBusConnection &connection, WatchMode watchMode, QObject *parent) |
231 | : QObject(*new QDBusServiceWatcherPrivate(connection, watchMode), parent) |
232 | { |
233 | d_func()->setConnection(newServices: QStringList() << service, newConnection: connection, newMode: watchMode); |
234 | } |
235 | |
236 | /*! |
237 | Destroys the QDBusServiceWatcher object and releases any resources |
238 | associated with it. |
239 | */ |
240 | QDBusServiceWatcher::~QDBusServiceWatcher() |
241 | { |
242 | } |
243 | |
244 | /*! |
245 | Returns the list of D-Bus services that are being watched. |
246 | |
247 | \sa setWatchedServices() |
248 | */ |
249 | QStringList QDBusServiceWatcher::watchedServices() const |
250 | { |
251 | return d_func()->watchedServicesData; |
252 | } |
253 | |
254 | /*! |
255 | Sets the list of D-Bus services being watched to be \a services. |
256 | |
257 | Note that setting the entire list means removing all previous rules for |
258 | watching services and adding new ones. This is an expensive operation and |
259 | should be avoided, if possible. Instead, use addWatchedService() and |
260 | removeWatchedService() if you can to manipulate entries in the list. |
261 | |
262 | Removes any existing binding of watchedServices. |
263 | */ |
264 | void QDBusServiceWatcher::setWatchedServices(const QStringList &services) |
265 | { |
266 | Q_D(QDBusServiceWatcher); |
267 | d->watchedServicesData.removeBindingUnlessInWrapper(); |
268 | if (services == d->watchedServicesData.valueBypassingBindings()) |
269 | return; |
270 | // trigger watchMode re-evaluation, but only once for the setter |
271 | d->setConnection(newServices: services, newConnection: d->connection, newMode: d->watchMode); |
272 | d->watchedServicesData.notify(); |
273 | } |
274 | |
275 | QBindable<QStringList> QDBusServiceWatcher::bindableWatchedServices() |
276 | { |
277 | Q_D(QDBusServiceWatcher); |
278 | return &d->watchedServicesData; |
279 | } |
280 | |
281 | /*! |
282 | Adds \a newService to the list of services to be watched by this object. |
283 | This function is more efficient than setWatchedServices() and should be |
284 | used whenever possible to add services. |
285 | |
286 | Removes any existing binding of watchedServices. |
287 | */ |
288 | void QDBusServiceWatcher::addWatchedService(const QString &newService) |
289 | { |
290 | Q_D(QDBusServiceWatcher); |
291 | d->watchedServicesData.removeBindingUnlessInWrapper(); |
292 | auto services = d->watchedServicesData.valueBypassingBindings(); |
293 | if (services.contains(str: newService)) |
294 | return; |
295 | // re-evaluate watch mode |
296 | d->addService(service: newService, mode: d->watchMode); |
297 | |
298 | services << newService; |
299 | d->watchedServicesData.setValueBypassingBindings(services); |
300 | |
301 | d->watchedServicesData.notify(); |
302 | } |
303 | |
304 | /*! |
305 | Removes the \a service from the list of services being watched by this |
306 | object. Note that D-Bus notifications are asynchronous, so there may |
307 | still be signals pending delivery about \a service. Those signals will |
308 | still be emitted whenever the D-Bus messages are processed. |
309 | |
310 | Removes any existing binding of watchedServices. |
311 | |
312 | This function returns \c true if any services were removed. |
313 | */ |
314 | bool QDBusServiceWatcher::removeWatchedService(const QString &service) |
315 | { |
316 | Q_D(QDBusServiceWatcher); |
317 | d->watchedServicesData.removeBindingUnlessInWrapper(); |
318 | auto tempList = d->watchedServicesData.valueBypassingBindings(); |
319 | const bool result = tempList.removeOne(t: service); |
320 | if (!result) |
321 | return false; // nothing changed |
322 | |
323 | // re-evaluate watch mode |
324 | d->removeService(service, mode: d->watchMode); |
325 | d->watchedServicesData.setValueBypassingBindings(tempList); |
326 | d->watchedServicesData.notify(); |
327 | return true; |
328 | } |
329 | |
330 | QDBusServiceWatcher::WatchMode QDBusServiceWatcher::watchMode() const |
331 | { |
332 | return d_func()->watchMode; |
333 | } |
334 | |
335 | QBindable<QDBusServiceWatcher::WatchMode> QDBusServiceWatcher::bindableWatchMode() |
336 | { |
337 | return &d_func()->watchMode; |
338 | } |
339 | |
340 | void QDBusServiceWatcher::setWatchMode(WatchMode mode) |
341 | { |
342 | Q_D(QDBusServiceWatcher); |
343 | d->watchMode.removeBindingUnlessInWrapper(); |
344 | if (mode == d->watchMode.valueBypassingBindings()) |
345 | return; |
346 | // trigger watchedServicesData re-evaluation, but only once for the setter |
347 | d->setConnection(newServices: d->watchedServicesData, newConnection: d->connection, newMode: mode); |
348 | d->watchMode.notify(); |
349 | } |
350 | |
351 | /*! |
352 | Returns the QDBusConnection that this object is attached to. |
353 | |
354 | \sa setConnection() |
355 | */ |
356 | QDBusConnection QDBusServiceWatcher::connection() const |
357 | { |
358 | return d_func()->connection; |
359 | } |
360 | |
361 | /*! |
362 | Sets the D-Bus connection that this object is attached to be \a |
363 | connection. All services watched will be transferred to this connection. |
364 | |
365 | Note that QDBusConnection objects are reference counted: |
366 | QDBusServiceWatcher will keep a reference for this connection while it |
367 | exists. The connection is not closed until the reference count drops to |
368 | zero, so this will ensure that any notifications are received while this |
369 | QDBusServiceWatcher object exists. |
370 | |
371 | \sa connection() |
372 | */ |
373 | void QDBusServiceWatcher::setConnection(const QDBusConnection &connection) |
374 | { |
375 | Q_D(QDBusServiceWatcher); |
376 | if (connection.name() == d->connection.name()) |
377 | return; |
378 | d->setConnection(newServices: d->watchedServicesData, newConnection: connection, newMode: d->watchMode); |
379 | } |
380 | |
381 | QT_END_NAMESPACE |
382 | |
383 | #endif // QT_NO_DBUS |
384 | |
385 | #include "moc_qdbusservicewatcher.cpp" |
386 | |