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