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
16QT_BEGIN_NAMESPACE
17
18class QDBusServiceWatcherPrivate: public QObjectPrivate
19{
20 Q_DECLARE_PUBLIC(QDBusServiceWatcher)
21public:
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
50void 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
60void 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
83void 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
91void 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*/
217QDBusServiceWatcher::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*/
230QDBusServiceWatcher::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*/
240QDBusServiceWatcher::~QDBusServiceWatcher()
241{
242}
243
244/*!
245 Returns the list of D-Bus services that are being watched.
246
247 \sa setWatchedServices()
248*/
249QStringList 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*/
264void 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
275QBindable<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*/
288void 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*/
314bool 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
330QDBusServiceWatcher::WatchMode QDBusServiceWatcher::watchMode() const
331{
332 return d_func()->watchMode;
333}
334
335QBindable<QDBusServiceWatcher::WatchMode> QDBusServiceWatcher::bindableWatchMode()
336{
337 return &d_func()->watchMode;
338}
339
340void 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*/
356QDBusConnection 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*/
373void 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
381QT_END_NAMESPACE
382
383#endif // QT_NO_DBUS
384
385#include "moc_qdbusservicewatcher.cpp"
386

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