From 64f24611aacfea2ee645294f4c23b13ca02e077c Mon Sep 17 00:00:00 2001 From: Pavel Strashkin Date: Thu, 3 May 2012 08:51:23 -0700 Subject: [PATCH] Add support for Python-based mainloop --- Makefile.am | 5 + _dbus_bindings/Makefile.am | 4 +- _dbus_bindings/dbus_bindings-internal.h | 11 + _dbus_bindings/mainloop-python.c | 491 +++++++++++++++++++++++++++++++ _dbus_bindings/module.c | 2 + _dbus_bindings/util.c | 130 ++++++++ dbus/mainloop/__init__.py | 5 +- dbus/mainloop/python/base.py | 28 ++ dbus/mainloop/python/poll.py | 43 +++ dbus/mainloop/python/select.py | 65 ++++ dbus/mainloop/python/twisted.py | 69 +++++ examples/example-mainloop-python.py | 16 + 12 files changed, 866 insertions(+), 3 deletions(-) create mode 100644 _dbus_bindings/mainloop-python.c create mode 100644 _dbus_bindings/util.c create mode 100644 dbus/mainloop/python/__init__.py create mode 100644 dbus/mainloop/python/base.py create mode 100644 dbus/mainloop/python/poll.py create mode 100644 dbus/mainloop/python/select.py create mode 100644 dbus/mainloop/python/twisted.py create mode 100644 examples/example-mainloop-python.py diff --git a/Makefile.am b/Makefile.am index ebc2e43..3b8475f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,11 @@ nobase_python_PYTHON = \ dbus/lowlevel.py \ dbus/mainloop/__init__.py \ dbus/mainloop/glib.py \ + dbus/mainloop/python/__init__.py \ + dbus/mainloop/python/base.py \ + dbus/mainloop/python/select.py \ + dbus/mainloop/python/poll.py \ + dbus/mainloop/python/twisted.py \ dbus/proxies.py \ dbus/server.py \ dbus/service.py \ diff --git a/_dbus_bindings/Makefile.am b/_dbus_bindings/Makefile.am index 2a5ec28..feeb39b 100644 --- a/_dbus_bindings/Makefile.am +++ b/_dbus_bindings/Makefile.am @@ -22,6 +22,7 @@ _dbus_bindings_la_SOURCES = \ unixfd.c \ libdbusconn.c \ mainloop.c \ + mainloop-python.c \ message-append.c \ message.c \ message-get-args.c \ @@ -32,7 +33,8 @@ _dbus_bindings_la_SOURCES = \ signature.c \ string.c \ types-internal.h \ - validation.c + validation.c \ + util.c check_c_sources = $(_dbus_bindings_la_SOURCES) include $(top_srcdir)/tools/check-coding-style.mk diff --git a/_dbus_bindings/dbus_bindings-internal.h b/_dbus_bindings/dbus_bindings-internal.h index 4b831e8..8cc4c89 100644 --- a/_dbus_bindings/dbus_bindings-internal.h +++ b/_dbus_bindings/dbus_bindings-internal.h @@ -202,6 +202,10 @@ extern dbus_bool_t dbus_py_check_mainloop_sanity(PyObject *); extern dbus_bool_t dbus_py_init_mainloop(void); extern dbus_bool_t dbus_py_insert_mainloop_types(PyObject *); +/* mainloop-python.c */ +extern dbus_bool_t dbus_py_init_mainloop_python(void); +extern dbus_bool_t dbus_py_insert_mainloop_python_types(PyObject *); + /* server.c */ extern PyTypeObject DBusPyServer_Type; DEFINE_CHECK(DBusPyServer) @@ -218,6 +222,13 @@ dbus_bool_t dbus_py_validate_interface_name(const char *name); dbus_bool_t dbus_py_validate_object_path(const char *path); #define dbus_py_validate_error_name dbus_py_validate_interface_name +/* util.c */ +extern int dbus_py_socketpair(int fds[2]); +extern ssize_t dbus_py_send(int sockfd, const void *buf, size_t len, int flags); +extern ssize_t dbus_py_recv(int sockfd, void *buf, size_t len, int flags); +extern int dbus_py_close(int fd); +extern int dbus_py_set_blocking(int sockfd, int blocking); + /* debugging support */ void _dbus_py_assertion_failed(const char *); #define DBUS_PY_RAISE_VIA_NULL_IF_FAIL(assertion) \ diff --git a/_dbus_bindings/mainloop-python.c b/_dbus_bindings/mainloop-python.c new file mode 100644 index 0000000..31497f5 --- /dev/null +++ b/_dbus_bindings/mainloop-python.c @@ -0,0 +1,491 @@ +#include "config.h" + +#include "dbus_bindings-internal.h" + +typedef struct { + PyObject_HEAD + PyObject * (*callback)(PyObject *, PyObject *, PyObject *); + void *data; +} Callback; + +static PyObject * +CallbackType_call(PyObject *self, PyObject *args, PyObject *kw) +{ + return ((Callback *)self)->callback(self, args, kw); +} + +static PyTypeObject CallbackType = { + PyObject_HEAD_INIT(NULL) + 0, /*ob_size*/ + "dbus.mainloop.python.Callback", /*tp_name*/ + sizeof(Callback), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + 0, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + (ternaryfunc)CallbackType_call, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT, /*tp_flags*/ + 0, /*tp_doc*/ +}; + +static PyObject * +DBusPyCallback_New2(PyObject * (*callback)(PyObject *, PyObject *, PyObject *), + void *data) +{ + Callback *self = PyObject_New(Callback, &CallbackType); + if (self) { + self->callback = callback; + self->data = data; + } + return (PyObject *)self; +} + +typedef struct +{ + DBusConnection *connection; + PyObject *mainloop; + int socketpair[2]; +} ConnectionSetup; + +static ConnectionSetup * +ConnectionSetup_init(DBusConnection *conn, PyObject *loop) +{ + ConnectionSetup *cs = NULL; + int socketpair[2] = {-1, -1}; + int socketpair_result = -1; + + cs = dbus_new0(ConnectionSetup, 1); + if (!cs) { + PyErr_SetString(PyExc_RuntimeError, "Failed to allocate memory for ConnectionSetup"); + goto fail; + } + + cs->connection = conn; + cs->mainloop = loop; + + if (!conn) { + /* we came here from the server setup + * so we don't need any socketpair + */ + return cs; + } + + Py_BEGIN_ALLOW_THREADS + socketpair_result = dbus_py_socketpair(socketpair); + Py_END_ALLOW_THREADS + + if (socketpair_result == -1) { + PyErr_SetFromErrno(PyExc_RuntimeError); + goto fail; + } + + cs->socketpair[0] = socketpair[0]; + cs->socketpair[1] = socketpair[1]; + + /* set readable part be non-blocking */ + if (dbus_py_set_blocking(cs->socketpair[0], 0) == -1) { + PyErr_SetFromErrno(PyExc_RuntimeError); + goto fail; + } + + return cs; + +fail: + if (socketpair[0] != -1) { + dbus_py_close(socketpair[0]); + } + + if (socketpair[1] != -1) { + dbus_py_close(socketpair[1]); + } + + if (cs) { + dbus_free(cs); + } + + return NULL; +} + +static void +ConnectionSetup_free(ConnectionSetup *cs) +{ + if (cs->connection) { + /* only DBusConnection has allocated + * socketpair so clean it up + */ + dbus_py_close(cs->socketpair[0]); + dbus_py_close(cs->socketpair[1]); + } + + dbus_free(cs); +} + +static PyObject * +python_mainloop_watch_callback(PyObject *self, PyObject *args, PyObject *kw) +{ + Callback *callback = (Callback *)self; + DBusWatch *watch = (DBusWatch *)callback->data; + unsigned int flags = 0; + + if (!PyArg_ParseTuple(args, "I", &flags)) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + dbus_watch_handle(watch, flags); + Py_END_ALLOW_THREADS + + Py_RETURN_NONE; +} + +static dbus_bool_t +dbus_py_mainloop_add_watch(DBusWatch *watch, void *data) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + ConnectionSetup *cs = data; + + dbus_bool_t result = TRUE; /* this function result */ + int unix_fd = dbus_watch_get_unix_fd(watch); + unsigned int flags = dbus_watch_get_flags(watch); + + PyObject *callback = NULL; + + if (!dbus_watch_get_enabled(watch)) { + goto out; + } + + callback = DBusPyCallback_New2(python_mainloop_watch_callback, watch); + if (!callback) { + result = FALSE; + goto out; + } + + if (!PyObject_CallMethod(cs->mainloop, "add_watch", "iIN", unix_fd, flags, callback)) { + result = FALSE; + goto out; + } + +out: + if (!result) { + Py_CLEAR(callback); + } + + PyGILState_Release(gil); + return result; +} + +static void +dbus_py_mainloop_remove_watch(DBusWatch *watch, void *data) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + ConnectionSetup *cs = data; + + int unix_fd = dbus_watch_get_unix_fd(watch); + unsigned int flags = dbus_watch_get_flags(watch); + PyObject_CallMethod(cs->mainloop, "remove_watch", "iI", unix_fd, flags); + + PyGILState_Release(gil); +} + +static void +dbus_py_mainloop_toggle_watch(DBusWatch *watch, void *data) +{ + if (dbus_watch_get_enabled(watch)) { + dbus_py_mainloop_add_watch(watch, data); + } + else { + dbus_py_mainloop_remove_watch(watch, data); + } +} + +static PyObject * +python_mainloop_timeout_callback(PyObject *self, PyObject *args, PyObject *kw) +{ + Callback *callback = (Callback *)self; + DBusTimeout *timeout = (DBusTimeout *)callback->data; + + Py_BEGIN_ALLOW_THREADS + dbus_timeout_handle(timeout); + Py_END_ALLOW_THREADS + + Py_RETURN_NONE; +} + +static dbus_bool_t +dbus_py_mainloop_add_timeout(DBusTimeout *timeout, void *data) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + ConnectionSetup *cs = data; + + dbus_bool_t result = TRUE; /* this function result */ + int interval = dbus_timeout_get_interval(timeout); + + PyObject *callback = NULL; + PyObject *id = NULL; + + if (!dbus_timeout_get_enabled(timeout)) { + goto out; + } + + callback = DBusPyCallback_New2(python_mainloop_timeout_callback, timeout); + if (!callback) { + result = FALSE; + goto out; + } + + id = PyObject_CallMethod(cs->mainloop, "add_timeout", "iN", interval, callback); + if (!id) { + result = FALSE; + goto out; + } + + Py_INCREF(id); + dbus_timeout_set_data(timeout, id, NULL); + +out: + if (!result) { + Py_CLEAR(callback); + } + + PyGILState_Release(gil); + return result; +} + +static void +dbus_py_mainloop_remove_timeout(DBusTimeout *timeout, void *data) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + + ConnectionSetup *cs = data; + PyObject *id = dbus_timeout_get_data(timeout); + + PyObject_CallMethod(cs->mainloop, "remove_timeout", "O", id); + Py_DECREF(id); + + PyGILState_Release(gil); +} + +static void +dbus_py_mainloop_toggle_timeout(DBusTimeout *timeout, void *data) +{ + if (dbus_timeout_get_enabled(timeout)) { + dbus_py_mainloop_add_timeout(timeout, data); + } + else { + dbus_py_mainloop_remove_timeout(timeout, data); + } +} + +static void +dbus_py_mainloop_dispatch_status(DBusConnection *connection, DBusDispatchStatus status, void *data) +{ + ConnectionSetup *cs = data; + + if (status == DBUS_DISPATCH_DATA_REMAINS) { + dbus_py_send(cs->socketpair[1], "\0", 1, 0); + } +} + +static PyObject * +python_mainloop_dispatch(PyObject *self, PyObject *args, PyObject *kw) +{ + Callback *callback = (Callback *)self; + ConnectionSetup *cs = callback->data; + char flag; + + Py_BEGIN_ALLOW_THREADS + while (dbus_py_recv(cs->socketpair[0], &flag, 1, 0) == 1); + Py_END_ALLOW_THREADS + + if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) { + return PyErr_SetFromErrno(PyExc_RuntimeError); + } + + Py_BEGIN_ALLOW_THREADS + while (dbus_connection_dispatch(cs->connection) == DBUS_DISPATCH_DATA_REMAINS); + Py_END_ALLOW_THREADS + + Py_RETURN_NONE; +} + +static int dbus_py_mainloop_connection_data_slot = -1; + +static dbus_bool_t +dbus_py_mainloop_set_up_connection(DBusConnection *connection, void *data) +{ + ConnectionSetup *cs = NULL; + PyObject *callback = NULL; + + cs = ConnectionSetup_init(connection, data); + if (!cs) { + return FALSE; + } + + dbus_connection_allocate_data_slot(&dbus_py_mainloop_connection_data_slot); + if (dbus_py_mainloop_connection_data_slot < 0) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to allocate connection data slot"); + goto fail; + } + + if (!dbus_connection_set_data(connection, + dbus_py_mainloop_connection_data_slot, + cs, + (DBusFreeFunction)ConnectionSetup_free)) + { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set connection data slot"); + goto fail; + } + + if (!dbus_connection_set_watch_functions(connection, + dbus_py_mainloop_add_watch, + dbus_py_mainloop_remove_watch, + dbus_py_mainloop_toggle_watch, + cs, + NULL)) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set up connection watch functions"); + goto fail; + } + + if (!dbus_connection_set_timeout_functions(connection, + dbus_py_mainloop_add_timeout, + dbus_py_mainloop_remove_timeout, + dbus_py_mainloop_toggle_timeout, + cs, + NULL)) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set up connection timeout functions"); + goto fail; + } + + dbus_connection_set_dispatch_status_function(connection, + dbus_py_mainloop_dispatch_status, + cs, + NULL); + + callback = DBusPyCallback_New2(python_mainloop_dispatch, cs); + if (!callback) { + goto fail; + } + + if (!PyObject_CallMethod(data, "add_watch", "iIN", cs->socketpair[0], DBUS_WATCH_READABLE, callback)) { + goto fail; + } + + /* force dbus connection to change its status to COMPLETE */ + Py_BEGIN_ALLOW_THREADS + while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS); + Py_END_ALLOW_THREADS + + return TRUE; + +fail: + Py_CLEAR(callback); + ConnectionSetup_free(cs); + return FALSE; +} + +static dbus_bool_t +dbus_py_mainloop_set_up_server(DBusServer *server, void *data) +{ + if (!dbus_server_set_watch_functions(server, + dbus_py_mainloop_add_watch, + dbus_py_mainloop_remove_watch, + dbus_py_mainloop_toggle_watch, + data, + NULL)) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set up server watch functions"); + return FALSE; + } + + if (!dbus_server_set_timeout_functions(server, + dbus_py_mainloop_add_timeout, + dbus_py_mainloop_remove_timeout, + dbus_py_mainloop_toggle_timeout, + data, + NULL)) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set up server timeout functions"); + return FALSE; + } + + return TRUE; +} + +static void +dbus_py_mainloop_free(void *data) +{ + Py_CLEAR(data); +} + +static PyObject * +dbus_py_mainloop_init(PyObject *self, PyObject *args, PyObject *kw) +{ + PyObject *python_mainloop = NULL; + PyObject *native_mainloop = NULL; + + if (!PyArg_ParseTuple(args, "O", &python_mainloop)) { + return NULL; + } + + native_mainloop = DBusPyNativeMainLoop_New4(dbus_py_mainloop_set_up_connection, + dbus_py_mainloop_set_up_server, + dbus_py_mainloop_free, + python_mainloop); + if (!native_mainloop) { + return NULL; + } + + Py_INCREF(python_mainloop); + return native_mainloop; +} + +dbus_bool_t +dbus_py_init_mainloop_python(void) +{ + CallbackType.tp_new = PyType_GenericNew; + if (PyType_Ready(&CallbackType) < 0) { + return 0; + } + + return 1; +} + +dbus_bool_t +dbus_py_insert_mainloop_python_types(PyObject *this_module) +{ + /* the factory function that wraps python main loop into a native one */ + PyObject *PythonMainLoop = NULL; + + PythonMainLoop = DBusPyCallback_New2(dbus_py_mainloop_init, + NULL); + if (!PythonMainLoop) { + return 0; + } + + if (PyModule_AddObject(this_module, + "PythonMainLoop", + PythonMainLoop) < 0) { + + Py_CLEAR(PythonMainLoop); + return 0; + } + + return 1; +} + +/* vim:set ft=c cino< sw=4 sts=4 et: */ diff --git a/_dbus_bindings/module.c b/_dbus_bindings/module.c index 2408ff8..c9c29c5 100644 --- a/_dbus_bindings/module.c +++ b/_dbus_bindings/module.c @@ -276,6 +276,7 @@ init_dbus_bindings(void) if (!dbus_py_init_message_types()) goto init_error; if (!dbus_py_init_pending_call()) goto init_error; if (!dbus_py_init_mainloop()) goto init_error; + if (!dbus_py_init_mainloop_python()) goto init_error; if (!dbus_py_init_libdbus_conn_types()) goto init_error; if (!dbus_py_init_conn_types()) goto init_error; if (!dbus_py_init_server_types()) goto init_error; @@ -299,6 +300,7 @@ init_dbus_bindings(void) if (!dbus_py_insert_message_types(this_module)) goto init_error; if (!dbus_py_insert_pending_call(this_module)) goto init_error; if (!dbus_py_insert_mainloop_types(this_module)) goto init_error; + if (!dbus_py_insert_mainloop_python_types(this_module)) goto init_error; if (!dbus_py_insert_libdbus_conn_types(this_module)) goto init_error; if (!dbus_py_insert_conn_types(this_module)) goto init_error; if (!dbus_py_insert_server_types(this_module)) goto init_error; diff --git a/_dbus_bindings/util.c b/_dbus_bindings/util.c new file mode 100644 index 0000000..6c8a46c --- /dev/null +++ b/_dbus_bindings/util.c @@ -0,0 +1,130 @@ +#include "config.h" + +#include "dbus_bindings-internal.h" + +#include +#include +#include +#include + +/* + * all functions here have to be ported + * to Windows and other platforms, but + * right now it's written for Linux only + */ + +int +dbus_py_close(int fd) +{ + return close(fd); +} + +/* + * the idea and some part of this code + * has been taken from Perl's util.c + */ +int +dbus_py_socketpair(int fds[2]) +{ + int listener = -1; + int connector = -1; + int acceptor = -1; + struct sockaddr_in listen_addr; + struct sockaddr_in connect_addr; + socklen_t size; + int saved_errno = -1; + + listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == -1) { + goto fail; + } + + memset(&listen_addr, 0, sizeof(listen_addr)); + listen_addr.sin_family = AF_INET; + listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + listen_addr.sin_port = 0; /* random port */ + + if (bind(listener, (struct sockaddr *) &listen_addr, sizeof(listen_addr)) == -1) { + goto fail; + } + + if (listen(listener, 1) == -1) { + goto fail; + } + + connector = socket(AF_INET, SOCK_STREAM, 0); + if (connector == -1) { + goto fail; + } + + size = sizeof(connect_addr); + if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1) { + goto fail; + } + + if (connect(connector, (struct sockaddr *) &connect_addr, sizeof(connect_addr)) == -1) { + goto fail; + } + + size = sizeof(listen_addr); + acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size); + if (acceptor == -1) { + goto fail; + } + + dbus_py_close(listener); + + fds[0] = connector; + fds[1] = acceptor; + + return 0; + +fail: + saved_errno = errno; + + if (listener != -1) { + dbus_py_close(listener); + } + + if (connector != -1) { + dbus_py_close(connector); + } + + if (acceptor != -1) { + dbus_py_close(acceptor); + } + + errno = saved_errno; + return -1; +} + +int +dbus_py_set_blocking(int sockfd, int blocking) +{ + int flags; + + flags = fcntl(sockfd, F_GETFL, 0); + if (flags == -1) { + return -1; + } + + if (blocking) { + flags = flags & (~O_NONBLOCK); + } else { + flags = flags | O_NONBLOCK; + } + + return fcntl(sockfd, F_SETFL, flags); +} + +ssize_t +dbus_py_send(int sockfd, const void *buf, size_t len, int flags) +{ + return send(sockfd, buf, len, flags); +} + +ssize_t +dbus_py_recv(int sockfd, void *buf, size_t len, int flags) +{ + return recv(sockfd, buf, len, flags); +} diff --git a/dbus/mainloop/__init__.py b/dbus/mainloop/__init__.py index dfaeefb..4eca425 100644 --- a/dbus/mainloop/__init__.py +++ b/dbus/mainloop/__init__.py @@ -27,6 +27,7 @@ import _dbus_bindings NativeMainLoop = _dbus_bindings.NativeMainLoop +PythonMainLoop = _dbus_bindings.PythonMainLoop NULL_MAIN_LOOP = _dbus_bindings.NULL_MAIN_LOOP """A null mainloop which doesn't actually do anything. @@ -54,8 +55,8 @@ Used to implement file descriptor watches.""" __all__ = ( # Imported into this module - 'NativeMainLoop', 'WATCH_READABLE', 'WATCH_WRITABLE', - 'WATCH_HANGUP', 'WATCH_ERROR', 'NULL_MAIN_LOOP', + 'NativeMainLoop', 'PythonMainLoop', 'NULL_MAIN_LOOP', + 'WATCH_READABLE', 'WATCH_WRITABLE', 'WATCH_HANGUP', 'WATCH_ERROR' # Submodules 'glib' diff --git a/dbus/mainloop/python/__init__.py b/dbus/mainloop/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dbus/mainloop/python/base.py b/dbus/mainloop/python/base.py new file mode 100644 index 0000000..7b830a0 --- /dev/null +++ b/dbus/mainloop/python/base.py @@ -0,0 +1,28 @@ +# dbus +import _dbus_bindings + +class MainLoop(object): + @classmethod + def install(klas, *args, **kwargs): + instance = klas(*args, **kwargs) + mainloop = _dbus_bindings.PythonMainLoop(instance) + _dbus_bindings.set_default_main_loop(mainloop) + return instance + + def add_watch(self, fd, flags, callback): + raise NotImplementedError + + def remove_watch(self, fd, flags): + raise NotImplementedError + + def add_timeout(self, interval, callback): + raise NotImplementedError + + def remove_timeout(self, id): + raise NotImplementedError + + def start(self): + raise NotImplementedError + + def stop(self): + raise NotImplementedError diff --git a/dbus/mainloop/python/poll.py b/dbus/mainloop/python/poll.py new file mode 100644 index 0000000..7fef9a2 --- /dev/null +++ b/dbus/mainloop/python/poll.py @@ -0,0 +1,43 @@ +# future (to load python's select first) +from __future__ import absolute_import + +# std +import select +import time + +# dbus +from dbus.mainloop import WATCH_READABLE, WATCH_WRITABLE +from dbus.mainloop.python.select import SelectMainLoop + +class PollMainLoop(SelectMainLoop): + def tick(self): + timeout = min([t['seconds'] for t in self.tlist.values()] or [None]) + started = time.time() + results = self._prepare_poll().poll(timeout) + elapsed = time.time() - started + + for fd, event in results: + if event & select.POLLIN: + self.rlist[fd](WATCH_READABLE) + + if event & select.POLLOUT: + self.wlist[fd](WATCH_WRITABLE) + + for (i, t) in self.tlist.items(): + t['seconds'] -= elapsed + if t['seconds'] <= 0: + self.tlist.pop(i)['callback']() + + def _prepare_poll(self): + poll = select.poll() + mask = {} + + for r in self.rlist: + mask[r] = select.POLLIN + for w in self.wlist: + mask[w] = mask.get(w, 0) | select.POLLOUT + + for fd, eventmask in mask.items(): + poll.register(fd, eventmask) + + return poll diff --git a/dbus/mainloop/python/select.py b/dbus/mainloop/python/select.py new file mode 100644 index 0000000..73951e7 --- /dev/null +++ b/dbus/mainloop/python/select.py @@ -0,0 +1,65 @@ +# future (to load python's select first) +from __future__ import absolute_import + +# std +import itertools +import select +import time + +# dbus +from dbus.mainloop import WATCH_READABLE, WATCH_WRITABLE +from dbus.mainloop.python.base import MainLoop + +class SelectMainLoop(MainLoop): + def __init__(self): + self.rlist = {} # readable list + self.wlist = {} # writable list + self.tlist = {} # timeouts list + self.idgen = itertools.count(1).next + + def add_watch(self, fd, flags, callback): + if flags & WATCH_READABLE: + self.rlist[fd] = callback + + if flags & WATCH_WRITABLE: + self.wlist[fd] = callback + + def remove_watch(self, fd, flags): + if flags & WATCH_READABLE: + self.rlist.pop(fd, None) + + if flags & WATCH_WRITABLE: + self.wlist.pop(fd, None) + + def add_timeout(self, interval, callback): + id = self.idgen() + self.tlist[id] = dict(seconds=interval, callback=callback) + return id + + def remove_timeout(self, id): + self.tlist.pop(id, None) + + def start(self): + self.running = True + while self.running: + self.tick() + + def stop(self): + self.running = False + + def tick(self): + timeout = min([t['seconds'] for t in self.tlist.values()] or [None]) + started = time.time() + rlist, wlist, xlist = select.select(self.rlist.keys(), self.wlist.keys(), [], timeout) + elapsed = time.time() - started + + for r in rlist: + self.rlist[r](WATCH_READABLE) + + for w in wlist: + self.wlist[w](WATCH_WRITABLE) + + for (i, t) in self.tlist.items(): + t['seconds'] -= elapsed + if t['seconds'] <= 0: + self.tlist.pop(i)['callback']() diff --git a/dbus/mainloop/python/twisted.py b/dbus/mainloop/python/twisted.py new file mode 100644 index 0000000..a921bd1 --- /dev/null +++ b/dbus/mainloop/python/twisted.py @@ -0,0 +1,69 @@ +# future (to load real twisted first) +from __future__ import absolute_import + +# std +import sys + +# dbus +from dbus.mainloop import WATCH_READABLE, WATCH_WRITABLE +from dbus.mainloop.python.base import MainLoop + +# zope +from zope.interface import implements + +# twisted +from twisted.internet import interfaces + +class Descriptor(object): + implements(interfaces.IReadWriteDescriptor) + + def __init__(self, fileno, doRecv=None, doSend=None): + self.fileno = lambda:fileno + self.doRead = doRecv + self.doWrite = doSend + + def logPrefix(self): + return '[%s fileno=%d]' % (self.__class__.__name__, self.fileno()) + + def connectionLost(self, reason): + pass + +class TwistedMainLoop(MainLoop): + def __init__(self, reactor=None): + self.reactor = reactor or sys.modules['twisted.internet.reactor'] + self.descriptors = {} + + def add_watch(self, fd, flags, callback): + if flags & WATCH_READABLE: + reader = self.descriptors.setdefault(fd, {})['r'] = Descriptor(fd, doRecv=lambda:callback(WATCH_READABLE)) + self.reactor.addReader(reader) + + if flags & WATCH_WRITABLE: + writer = self.descriptors.setdefault(fd, {})['w'] = Descriptor(fd, doSend=lambda:callback(WATCH_WRITABLE)) + self.reactor.addWriter(writer) + + def remove_watch(self, fd, flags): + if flags & WATCH_READABLE: + reader = self.descriptors[fd].pop('r') + self.reactor.removeReader(reader) + + if flags & WATCH_WRITABLE: + writer = self.descriptors[fd].pop('w') + self.reactor.removeWriter(writer) + + if not self.descriptors[fd]: + del self.descriptors[fd] + + def add_timeout(self, interval, callback): + delayed_call = self.reactor.callLater(interval, callback) + return delayed_call + + def remove_timeout(self, delayed_call): + if delayed_call.active(): + delayed_call.cancel() + + def start(self): + self.reactor.run() + + def stop(self): + self.reactor.stop() diff --git a/examples/example-mainloop-python.py b/examples/example-mainloop-python.py new file mode 100644 index 0000000..557e6e5 --- /dev/null +++ b/examples/example-mainloop-python.py @@ -0,0 +1,16 @@ +# dbus +import dbus + +# dbus mainloop (python + poll) +from dbus.mainloop.python.poll import PollMainLoop +mainloop = PollMainLoop.install() + +def name_owner_changed(owner): + if owner: + print 'name is owned: %s' % owner + mainloop.stop() + +bus = dbus.SystemBus() +bus.watch_name_owner('com.example.SampleService', name_owner_changed) + +mainloop.start() -- 1.7.4.1