1 // This is the SIP interface definition for the Qt support for the standard
2 // Python DBus bindings.
3 //
4 // Copyright (c) 2014 Riverbank Computing Limited
5 //
6 // Permission is hereby granted, free of charge, to any person
7 // obtaining a copy of this software and associated documentation
8 // files (the "Software"), to deal in the Software without
9 // restriction, including without limitation the rights to use, copy,
10 // modify, merge, publish, distribute, sublicense, and/or sell copies
11 // of the Software, and to permit persons to whom the Software is
12 // furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
25
26
27 #include <dbus/dbus-python.h>
28
29 #include <QCoreApplication>
30 #include <QSocketNotifier>
31 #include <QTimer>
32 #include <QTimerEvent>
33
34 #include "helper.h"
35
36
37 // The callback to add a watch.
38 extern "C" {static dbus_bool_t add_watch(DBusWatch *watch, void *data);}
add_watch(DBusWatch * watch,void * data)39 static dbus_bool_t add_watch(DBusWatch *watch, void *data)
40 {
41 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
42
43 int fd = dbus_watch_get_fd(watch);
44 unsigned int flags = dbus_watch_get_flags(watch);
45 dbus_bool_t enabled = dbus_watch_get_enabled(watch);
46
47 pyqt5DBusHelper::Watcher watcher;
48 watcher.watch = watch;
49
50 if (flags & DBUS_WATCH_READABLE)
51 {
52 watcher.read = new QSocketNotifier(fd, QSocketNotifier::Read, hlp);
53 watcher.read->setEnabled(enabled);
54 hlp->connect(watcher.read, SIGNAL(activated(int)), SLOT(readSocket(int)));
55 }
56
57 if (flags & DBUS_WATCH_WRITABLE)
58 {
59 watcher.write = new QSocketNotifier(fd, QSocketNotifier::Write, hlp);
60 watcher.write->setEnabled(enabled);
61 hlp->connect(watcher.write, SIGNAL(activated(int)), SLOT(writeSocket(int)));
62 }
63
64 hlp->watchers.insertMulti(fd, watcher);
65
66 return true;
67 }
68
69
70 // The callback to remove a watch.
71 extern "C" {static void remove_watch(DBusWatch *watch, void *data);}
remove_watch(DBusWatch * watch,void * data)72 static void remove_watch(DBusWatch *watch, void *data)
73 {
74 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
75
76 int fd = dbus_watch_get_fd(watch);
77
78 pyqt5DBusHelper::Watchers::iterator it = hlp->watchers.find(fd);
79
80 while (it != hlp->watchers.end() && it.key() == fd)
81 {
82 pyqt5DBusHelper::Watcher &watcher = it.value();
83
84 if (watcher.watch == watch)
85 {
86 if (watcher.read)
87 delete watcher.read;
88
89 if (watcher.write)
90 delete watcher.write;
91
92 hlp->watchers.erase(it);
93
94 return;
95 }
96
97 ++it;
98 }
99 }
100
101
102 // The callback to toggle a watch.
103 extern "C" {static void toggle_watch(DBusWatch *watch, void *data);}
toggle_watch(DBusWatch * watch,void * data)104 static void toggle_watch(DBusWatch *watch, void *data)
105 {
106 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
107
108 int fd = dbus_watch_get_fd(watch);
109 unsigned int flags = dbus_watch_get_flags(watch);
110 dbus_bool_t enabled = dbus_watch_get_enabled(watch);
111
112 pyqt5DBusHelper::Watchers::const_iterator it = hlp->watchers.find(fd);
113
114 while (it != hlp->watchers.end() && it.key() == fd)
115 {
116 const pyqt5DBusHelper::Watcher &watcher = it.value();
117
118 if (watcher.watch == watch)
119 {
120 if (flags & DBUS_WATCH_READABLE && watcher.read)
121 watcher.read->setEnabled(enabled);
122
123 if (flags & DBUS_WATCH_WRITABLE && watcher.write)
124 watcher.write->setEnabled(enabled);
125
126 return;
127 }
128
129 ++it;
130 }
131 }
132
133
134 // The callback to add a timeout.
135 extern "C" {static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data);}
add_timeout(DBusTimeout * timeout,void * data)136 static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data)
137 {
138 // Nothing to do if the timeout is disabled.
139 if (!dbus_timeout_get_enabled(timeout))
140 return true;
141
142 // Pretend it is successful if there is no application instance. This can
143 // happen if the global application gets garbage collected before the
144 // dbus-python main loop does.
145 if (!QCoreApplication::instance())
146 return true;
147
148 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
149
150 int id = hlp->startTimer(dbus_timeout_get_interval(timeout));
151
152 if (!id)
153 return false;
154
155 hlp->timeouts[id] = timeout;
156
157 return true;
158 }
159
160
161 // The callback to remove a timeout.
162 extern "C" {static void remove_timeout(DBusTimeout *timeout, void *data);}
remove_timeout(DBusTimeout * timeout,void * data)163 static void remove_timeout(DBusTimeout *timeout, void *data)
164 {
165 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
166
167 pyqt5DBusHelper::Timeouts::iterator it = hlp->timeouts.begin();
168
169 while (it != hlp->timeouts.end())
170 if (it.value() == timeout)
171 {
172 hlp->killTimer(it.key());
173 it = hlp->timeouts.erase(it);
174 }
175 else
176 ++it;
177 }
178
179
180 // The callback to toggle a timeout.
181 extern "C" {static void toggle_timeout(DBusTimeout *timeout, void *data);}
toggle_timeout(DBusTimeout * timeout,void * data)182 static void toggle_timeout(DBusTimeout *timeout, void *data)
183 {
184 remove_timeout(timeout, data);
185 add_timeout(timeout, data);
186 }
187
188
189 // The callback to delete a helper instance.
190 extern "C" {static void dbus_qt_delete_helper(void *data);}
dbus_qt_delete_helper(void * data)191 static void dbus_qt_delete_helper(void *data)
192 {
193 delete reinterpret_cast<pyqt5DBusHelper *>(data);
194 }
195
196
197 // The callback to wakeup the event loop.
198 extern "C" {static void wakeup_main(void *data);}
wakeup_main(void * data)199 static void wakeup_main(void *data)
200 {
201 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
202
203 QTimer::singleShot(0, hlp, SLOT(dispatch()));
204 }
205
206
207 // The callback to set up a DBus connection.
208 extern "C" {static dbus_bool_t dbus_qt_conn(DBusConnection *conn, void *data);}
dbus_qt_conn(DBusConnection * conn,void * data)209 static dbus_bool_t dbus_qt_conn(DBusConnection *conn, void *data)
210 {
211 bool rc;
212
213 Py_BEGIN_ALLOW_THREADS
214
215 pyqt5DBusHelper *hlp = reinterpret_cast<pyqt5DBusHelper *>(data);
216
217 hlp->connections.append(conn);
218
219 if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch,
220 toggle_watch, data, 0))
221 rc = false;
222 else if (!dbus_connection_set_timeout_functions(conn, add_timeout,
223 remove_timeout, toggle_timeout, data, 0))
224 rc = false;
225 else
226 rc = true;
227
228 dbus_connection_set_wakeup_main_function(conn, wakeup_main, hlp, 0);
229
230 Py_END_ALLOW_THREADS
231
232 return rc;
233 }
234
235
236 // The callback to set up a DBus server.
237 extern "C" {static dbus_bool_t dbus_qt_srv(DBusServer *srv, void *data);}
dbus_qt_srv(DBusServer * srv,void * data)238 static dbus_bool_t dbus_qt_srv(DBusServer *srv, void *data)
239 {
240 bool rc;
241
242 Py_BEGIN_ALLOW_THREADS
243
244 if (!dbus_server_set_watch_functions(srv, add_watch, remove_watch,
245 toggle_watch, data, 0))
246 rc = false;
247 else if (!dbus_server_set_timeout_functions(srv, add_timeout,
248 remove_timeout, toggle_timeout, data, 0))
249 rc = false;
250 else
251 rc = true;
252
253 Py_END_ALLOW_THREADS
254
255 return rc;
256 }
257
258
259 // Create a new helper instance.
pyqt5DBusHelper()260 pyqt5DBusHelper::pyqt5DBusHelper() : QObject()
261 {
262 }
263
264
265 // Handle a socket being ready to read.
readSocket(int fd)266 void pyqt5DBusHelper::readSocket(int fd)
267 {
268 Watchers::const_iterator it = watchers.find(fd);
269
270 while (it != watchers.end() && it.key() == fd)
271 {
272 const Watcher &watcher = it.value();
273
274 if (watcher.read && watcher.read->isEnabled())
275 {
276 watcher.read->setEnabled(false);
277 dbus_watch_handle(watcher.watch, DBUS_WATCH_READABLE);
278
279 // The notifier could have just been destroyed.
280 if (watcher.read)
281 watcher.read->setEnabled(true);
282
283 break;
284 }
285
286 ++it;
287 }
288
289 dispatch();
290 }
291
292
293 // Handle a socket being ready to write.
writeSocket(int fd)294 void pyqt5DBusHelper::writeSocket(int fd)
295 {
296 Watchers::const_iterator it = watchers.find(fd);
297
298 while (it != watchers.end() && it.key() == fd)
299 {
300 const Watcher &watcher = it.value();
301
302 if (watcher.write && watcher.write->isEnabled())
303 {
304 watcher.write->setEnabled(false);
305 dbus_watch_handle(watcher.watch, DBUS_WATCH_WRITABLE);
306
307 // The notifier could have just been destroyed.
308 if (watcher.write)
309 watcher.write->setEnabled(true);
310
311 break;
312 }
313
314 ++it;
315 }
316 }
317
318
319 // Handoff to the connection dispatcher while there is data.
dispatch()320 void pyqt5DBusHelper::dispatch()
321 {
322 for (Connections::const_iterator it = connections.constBegin(); it != connections.constEnd(); ++it)
323 while (dbus_connection_dispatch(*it) == DBUS_DISPATCH_DATA_REMAINS)
324 ;
325 }
326
327
328 // Reimplemented to handle timer events.
timerEvent(QTimerEvent * e)329 void pyqt5DBusHelper::timerEvent(QTimerEvent *e)
330 {
331 DBusTimeout *timeout = timeouts.value(e->timerId());
332
333 if (timeout)
334 dbus_timeout_handle(timeout);
335 }
336
337
338 PyDoc_STRVAR(DBusQtMainLoop__doc__,
339 "DBusQtMainLoop([set_as_default=False]) -> NativeMainLoop\n"
340 "\n"
341 "Return a NativeMainLoop object.\n"
342 "\n"
343 "If the keyword argument set_as_default is given and is True, set the new\n"
344 "main loop as the default for all new Connection or Bus instances.\n");
345
346 extern "C" {static PyObject *DBusQtMainLoop(PyObject *, PyObject *args, PyObject *kwargs);}
DBusQtMainLoop(PyObject *,PyObject * args,PyObject * kwargs)347 static PyObject *DBusQtMainLoop(PyObject *, PyObject *args, PyObject *kwargs)
348 {
349 if (PyTuple_Size(args) != 0)
350 {
351 PyErr_SetString(PyExc_TypeError, "DBusQtMainLoop() takes no positional arguments");
352 return 0;
353 }
354
355 int set_as_default = 0;
356 static char *argnames[] = {"set_as_default", 0};
357
358 if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", argnames, &set_as_default))
359 return 0;
360
361 pyqt5DBusHelper *hlp = new pyqt5DBusHelper;
362
363 PyObject *mainloop = DBusPyNativeMainLoop_New4(dbus_qt_conn, dbus_qt_srv,
364 dbus_qt_delete_helper, hlp);
365
366 if (!mainloop)
367 {
368 delete hlp;
369 return 0;
370 }
371
372 if (set_as_default)
373 {
374 PyObject *func = PyObject_GetAttrString(_dbus_bindings_module, "set_default_main_loop");
375
376 if (!func)
377 {
378 Py_DECREF(mainloop);
379 return 0;
380 }
381
382 PyObject *res = PyObject_CallFunctionObjArgs(func, mainloop, 0);
383 Py_DECREF(func);
384
385 if (!res)
386 {
387 Py_DECREF(mainloop);
388 return 0;
389 }
390
391 Py_DECREF(res);
392 }
393
394 return mainloop;
395 }
396
397
398 // The table of module functions.
399 static PyMethodDef module_functions[] = {
400 {"DBusQtMainLoop", (PyCFunction)DBusQtMainLoop, METH_VARARGS|METH_KEYWORDS,
401 DBusQtMainLoop__doc__},
402 {0, 0, 0, 0}
403 };
404
405
406 // The module entry point.
407 #if PY_MAJOR_VERSION >= 3
PyInit_pyqt5()408 PyMODINIT_FUNC PyInit_pyqt5()
409 {
410 static PyModuleDef module_def = {
411 PyModuleDef_HEAD_INIT,
412 "pyqt5",
413 NULL,
414 -1,
415 module_functions,
416 };
417
418 // Import the generic part of the Python DBus bindings.
419 if (import_dbus_bindings("dbus.mainloop.pyqt5") < 0)
420 return 0;
421
422 return PyModule_Create(&module_def);
423 }
424 #else
initpyqt5()425 PyMODINIT_FUNC initpyqt5()
426 {
427 // Import the generic part of the Python DBus bindings.
428 if (import_dbus_bindings("dbus.mainloop.pyqt5") < 0)
429 return;
430
431 Py_InitModule("pyqt5", module_functions);
432 }
433 #endif
434