From e390c945a4da8283fa7bc674c637d5511c996289 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 | 4 + _dbus_bindings/Makefile.am | 1 + _dbus_bindings/dbus_bindings-internal.h | 6 + _dbus_bindings/mainloop-python.c | 532 +++++++++++++++++++++++++++++++ _dbus_bindings/mainloop.c | 132 +++++++-- _dbus_bindings/module.c | 2 + dbus/mainloop/__init__.py | 5 +- dbus/mainloop/base.py | 51 +++ dbus/mainloop/poll.py | 67 ++++ dbus/mainloop/select.py | 90 ++++++ dbus/mainloop/twisted.py | 99 ++++++ examples/example-mainloop-python.py | 16 + 12 files changed, 983 insertions(+), 22 deletions(-) create mode 100644 _dbus_bindings/mainloop-python.c create mode 100644 dbus/mainloop/base.py create mode 100644 dbus/mainloop/poll.py create mode 100644 dbus/mainloop/select.py create mode 100644 dbus/mainloop/twisted.py create mode 100644 examples/example-mainloop-python.py diff --git a/Makefile.am b/Makefile.am index ebc2e43..601c307 100644 --- a/Makefile.am +++ b/Makefile.am @@ -32,6 +32,10 @@ nobase_python_PYTHON = \ dbus/lowlevel.py \ dbus/mainloop/__init__.py \ dbus/mainloop/glib.py \ + dbus/mainloop/base.py \ + dbus/mainloop/select.py \ + dbus/mainloop/poll.py \ + dbus/mainloop/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..c86bdda 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 \ diff --git a/_dbus_bindings/dbus_bindings-internal.h b/_dbus_bindings/dbus_bindings-internal.h index 4b831e8..4688343 100644 --- a/_dbus_bindings/dbus_bindings-internal.h +++ b/_dbus_bindings/dbus_bindings-internal.h @@ -202,6 +202,12 @@ 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 *this_module); +extern dbus_bool_t dbus_py_mainloop_python_set_up_connection(DBusConnection *, PyObject *); +extern dbus_bool_t dbus_py_mainloop_python_set_up_server(DBusServer *, PyObject *); + /* server.c */ extern PyTypeObject DBusPyServer_Type; DEFINE_CHECK(DBusPyServer) diff --git a/_dbus_bindings/mainloop-python.c b/_dbus_bindings/mainloop-python.c new file mode 100644 index 0000000..17fab73 --- /dev/null +++ b/_dbus_bindings/mainloop-python.c @@ -0,0 +1,532 @@ +/* Implementation of python main-loop integration for dbus-python. + * + * Copyright (C) 2006 Collabora Ltd. + * Copyright (C) 2008 Huang Peng + * Copyright (C) 2012 Pavel Strashkin + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "config.h" + +#include "dbus_bindings-internal.h" + +#include +#include + +typedef struct { + PyObject_HEAD + PyObject * (*function)(PyObject *, PyObject *, PyObject *); + void *data; +} CClosure; + +static PyObject * +CClosureType_call(PyObject *self, PyObject *args, PyObject *kwargs) +{ + return ((CClosure *)self)->function(self, args, kwargs); +} + +static PyTypeObject CClosureType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "dbus.mainloop.python.CClosure", /* tp_name */ + sizeof(CClosure), /* 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)CClosureType_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 * +CClosure_New(PyObject * (*function)(PyObject *, PyObject *, PyObject *), void *data) +{ + CClosure *self = PyObject_New(CClosure, &CClosureType); + + if (self) { + self->function = function; + self->data = data; + } + + return (PyObject *)self; +} + +typedef struct +{ + DBusConnection *connection; + PyObject *mainloop; + int pipefd[2]; +} ConnectionSetup; + +static ConnectionSetup * +ConnectionSetup_new(PyObject *mainloop, DBusConnection *conn, PyObject * (*dispatch)(PyObject *, PyObject *, PyObject *)) +{ + ConnectionSetup *cs = NULL; + int pipe2_result = -1; + PyObject *cclosure = NULL; + PyObject *tmp = NULL; + + cs = dbus_new0(ConnectionSetup, 1); + if (!cs) { + PyErr_SetString(PyExc_MemoryError, "Failed to allocate memory for ConnectionSetup"); + return NULL; + } + + cs->connection = conn; + cs->mainloop = mainloop; + cs->pipefd[0] = -1; + cs->pipefd[1] = -1; + + if (!conn) { + /* we came here from the server setup + * so we don't need any pipes + */ + return cs; + } + + Py_BEGIN_ALLOW_THREADS + pipe2_result = pipe2(cs->pipefd, O_NONBLOCK | O_CLOEXEC); + Py_END_ALLOW_THREADS + + if (pipe2_result == -1) { + PyErr_SetFromErrno(PyExc_RuntimeError); + goto fail; + } + + cclosure = CClosure_New(dispatch, cs); + if (!cclosure) { + goto fail; + } + + tmp = PyObject_CallMethod(mainloop, "add_watch", "iIO", cs->pipefd[0], DBUS_WATCH_READABLE, cclosure); + if (!tmp) { + goto fail; + } + + return cs; + +fail: + Py_CLEAR(cclosure); + Py_CLEAR(tmp); + + if (cs->pipefd[0] != -1) { + close(cs->pipefd[0]); + } + + if (cs->pipefd[1] != -1) { + close(cs->pipefd[1]); + } + + dbus_free(cs); + + return NULL; +} + +static void +ConnectionSetup_free(ConnectionSetup *cs) +{ + PyGILState_STATE gil = PyGILState_Ensure(); + { + PyObject *tmp = NULL; + + if (cs->connection) { + tmp = PyObject_CallMethod(cs->mainloop, "remove_watch", "iI", cs->pipefd[0], DBUS_WATCH_READABLE); + + /* ignore any error */ + Py_CLEAR(tmp); + PyErr_Clear(); + } + + if (cs->pipefd[0] != -1) { + close(cs->pipefd[0]); + } + + if (cs->pipefd[1] != -1) { + close(cs->pipefd[1]); + } + + dbus_free(cs); + } + PyGILState_Release(gil); +} + +static PyObject * +python_mainloop_watch_callback(PyObject *self, PyObject *args, PyObject *kwargs) +{ + CClosure *cclosure = (CClosure *)self; + DBusWatch *watch = (DBusWatch *)cclosure->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 *cclosure = NULL; + PyObject *tmp = NULL; + + if (!dbus_watch_get_enabled(watch)) { + goto out; + } + + cclosure = CClosure_New(python_mainloop_watch_callback, watch); + if (!cclosure) { + result = FALSE; + goto out; + } + + tmp = PyObject_CallMethod(cs->mainloop, "add_watch", "iIO", unix_fd, flags, cclosure); + if (!tmp) { + result = FALSE; + goto out; + } + +out: + Py_CLEAR(cclosure); + Py_CLEAR(tmp); + + 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 *tmp = NULL; + + tmp = PyObject_CallMethod(cs->mainloop, "remove_watch", "iI", unix_fd, flags); + + /* ignore any error */ + Py_CLEAR(tmp); + PyErr_Clear(); + + 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 *kwargs) +{ + CClosure *cclosure = (CClosure *)self; + DBusTimeout *timeout = (DBusTimeout *)cclosure->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 *cclosure = NULL; + PyObject *id = NULL; + + if (!dbus_timeout_get_enabled(timeout)) { + goto out; + } + + cclosure = CClosure_New(python_mainloop_timeout_callback, timeout); + if (!cclosure) { + result = FALSE; + goto out; + } + + id = PyObject_CallMethod(cs->mainloop, "add_timeout", "iO", interval, cclosure); + if (!id) { + result = FALSE; + goto out; + } + + dbus_timeout_set_data(timeout, id, NULL); + +out: + Py_CLEAR(cclosure); + + 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 *tmp = NULL; + + tmp = PyObject_CallMethod(cs->mainloop, "remove_timeout", "O", id); + + /* ignore any error */ + Py_CLEAR(tmp); + PyErr_Clear(); + + Py_CLEAR(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; + int count = -1; + + if (status == DBUS_DISPATCH_DATA_REMAINS) { + count = write(cs->pipefd[1], "\0", 1); + } +} + +static PyObject * +python_mainloop_dispatch(PyObject *self, PyObject *args, PyObject *kwargs) +{ + CClosure *cclosure = (CClosure *)self; + ConnectionSetup *cs = cclosure->data; + char flag; + + Py_BEGIN_ALLOW_THREADS + while (read(cs->pipefd[0], &flag, 1) == 1) { + /* do nothing */ + } + 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) { + /* do nothing */ + } + Py_END_ALLOW_THREADS + + Py_RETURN_NONE; +} + +static int dbus_py_mainloop_connection_data_slot = -1; + +dbus_bool_t +dbus_py_mainloop_python_set_up_connection(DBusConnection *connection, PyObject *mainloop) +{ + ConnectionSetup *cs = NULL; + + cs = ConnectionSetup_new(mainloop, connection, python_mainloop_dispatch); + 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_MemoryError, + "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_MemoryError, + "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_MemoryError, + "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_MemoryError, + "Failed to set up connection timeout functions"); + goto fail; + } + + dbus_connection_set_dispatch_status_function(connection, + dbus_py_mainloop_dispatch_status, + cs, + NULL); + + /* force dbus connection to change its status to COMPLETE */ + Py_BEGIN_ALLOW_THREADS + while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) { + /* do nothing */ + } + Py_END_ALLOW_THREADS + + return TRUE; + +fail: + ConnectionSetup_free(cs); + + return FALSE; +} + +static int dbus_py_mainloop_server_data_slot = -1; + +dbus_bool_t +dbus_py_mainloop_python_set_up_server(DBusServer *server, PyObject *mainloop) +{ + ConnectionSetup *cs = NULL; + + cs = ConnectionSetup_new(mainloop, NULL, NULL); + if (!cs) { + return FALSE; + } + + dbus_server_allocate_data_slot(&dbus_py_mainloop_server_data_slot); + if (dbus_py_mainloop_server_data_slot < 0) { + PyErr_SetString(PyExc_MemoryError, + "Failed to allocate server data slot"); + goto fail; + } + + if (!dbus_server_set_data(server, + dbus_py_mainloop_server_data_slot, + cs, + (DBusFreeFunction)ConnectionSetup_free)) + { + PyErr_SetString(PyExc_MemoryError, + "Failed to set server data slot"); + goto fail; + } + + if (!dbus_server_set_watch_functions(server, + 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 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, + cs, + NULL)) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to set up server timeout functions"); + return FALSE; + } + + return TRUE; + +fail: + ConnectionSetup_free(cs); + + return FALSE; +} + +dbus_bool_t +dbus_py_init_mainloop_python(void) +{ + CClosureType.tp_new = PyType_GenericNew; + if (PyType_Ready(&CClosureType) < 0) { + return 0; + } + + return 1; +} + +dbus_bool_t +dbus_py_insert_mainloop_python_types(PyObject *this_module) +{ + return 1; +} + +/* vim:set ft=c cino< sw=4 sts=4 et: */ diff --git a/_dbus_bindings/mainloop.c b/_dbus_bindings/mainloop.c index 367ae0e..af6bc45 100644 --- a/_dbus_bindings/mainloop.c +++ b/_dbus_bindings/mainloop.c @@ -2,6 +2,7 @@ * * Copyright (C) 2006 Collabora Ltd. * Copyright (C) 2008 Huang Peng + * Copyright (C) 2012 Pavel Strashkin * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -28,6 +29,57 @@ #include "dbus_bindings-internal.h" +/* Python mainloop wrapper ========================================= */ + +static PyTypeObject PythonMainLoop_Type; +DEFINE_CHECK(PythonMainLoop) + +typedef struct { + PyObject_HEAD +} PythonMainLoop; + +static PyTypeObject PythonMainLoop_Type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "dbus.mainloop.PythonMainLoop", /* tp_name */ + sizeof(PythonMainLoop), /* 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 */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + /* Native mainloop wrapper ========================================= */ PyDoc_STRVAR(NativeMainLoop_tp_doc, @@ -36,7 +88,6 @@ PyDoc_STRVAR(NativeMainLoop_tp_doc, ); static PyTypeObject NativeMainLoop_Type; - DEFINE_CHECK(NativeMainLoop) typedef struct { @@ -108,44 +159,60 @@ dbus_py_check_mainloop_sanity(PyObject *mainloop) if (NativeMainLoop_Check(mainloop)) { return TRUE; } + + if (PythonMainLoop_Check(mainloop)) { + return TRUE; + } + PyErr_SetString(PyExc_TypeError, - "A dbus.mainloop.NativeMainLoop instance is required"); + "A dbus.mainloop.NativeMainLoop or dbus.mainloop.PythonMainLoop instance is required"); return FALSE; } dbus_bool_t dbus_py_set_up_connection(PyObject *conn, PyObject *mainloop) { + DBusConnection *dbc = DBusPyConnection_BorrowDBusConnection(conn); + if (!dbc) { + return FALSE; + } + if (NativeMainLoop_Check(mainloop)) { /* Native mainloops are allowed to do arbitrary strange things */ NativeMainLoop *nml = (NativeMainLoop *)mainloop; - DBusConnection *dbc = DBusPyConnection_BorrowDBusConnection(conn); - - if (!dbc) { - return FALSE; - } return (nml->set_up_connection_cb)(dbc, nml->data); } + + if (PythonMainLoop_Check(mainloop)) { + /* Native mainloops are allowed to do arbitrary strange things */ + return dbus_py_mainloop_python_set_up_connection(dbc, mainloop); + } + PyErr_SetString(PyExc_TypeError, - "A dbus.mainloop.NativeMainLoop instance is required"); + "A dbus.mainloop.NativeMainLoop or dbus.mainloop.PythonMainLoop instance is required"); return FALSE; } dbus_bool_t dbus_py_set_up_server(PyObject *server, PyObject *mainloop) { + DBusServer *dbs = DBusPyServer_BorrowDBusServer(server); + if (!dbs) { + return FALSE; + } + if (NativeMainLoop_Check(mainloop)) { /* Native mainloops are allowed to do arbitrary strange things */ NativeMainLoop *nml = (NativeMainLoop *)mainloop; - DBusServer *dbs = DBusPyServer_BorrowDBusServer(server); - - if (!dbs) { - return FALSE; - } return (nml->set_up_server_cb)(dbs, nml->data); } + + if (PythonMainLoop_Check(mainloop)) { + return dbus_py_mainloop_python_set_up_server(dbs, mainloop); + } + PyErr_SetString(PyExc_TypeError, - "A dbus.mainloop.NativeMainLoop instance is required"); + "A dbus.mainloop.NativeMainLoop or dbus.mainloop.PythonMainLoop instance is required"); return FALSE; } @@ -183,7 +250,14 @@ noop_main_loop_cb(void *conn_or_server UNUSED, void *data UNUSED) dbus_bool_t dbus_py_init_mainloop(void) { - if (PyType_Ready (&NativeMainLoop_Type) < 0) return 0; + if (PyType_Ready(&NativeMainLoop_Type) < 0) { + return 0; + } + + PythonMainLoop_Type.tp_new = PyType_GenericNew; + if (PyType_Ready(&PythonMainLoop_Type) < 0) { + return 0; + } return 1; } @@ -195,14 +269,32 @@ dbus_py_insert_mainloop_types(PyObject *this_module) noop_server_cb, NULL, NULL); - if (!null_main_loop) return 0; + if (!null_main_loop) { + return 0; + } /* PyModule_AddObject steals a ref */ Py_INCREF (&NativeMainLoop_Type); - if (PyModule_AddObject (this_module, "NativeMainLoop", - (PyObject *)&NativeMainLoop_Type) < 0) return 0; - if (PyModule_AddObject (this_module, "NULL_MAIN_LOOP", - null_main_loop) < 0) return 0; + if (PyModule_AddObject (this_module, + "NativeMainLoop", + (PyObject *)&NativeMainLoop_Type) < 0) { + return 0; + } + + if (PyModule_AddObject (this_module, + "NULL_MAIN_LOOP", + null_main_loop) < 0) { + return 0; + } + + /* PyModule_AddObject steals a ref */ + Py_INCREF(&PythonMainLoop_Type); + if (PyModule_AddObject (this_module, + "PythonMainLoop", + (PyObject *)&PythonMainLoop_Type) < 0) { + return 0; + } + return 1; } 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/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/base.py b/dbus/mainloop/base.py new file mode 100644 index 0000000..2b2147b --- /dev/null +++ b/dbus/mainloop/base.py @@ -0,0 +1,51 @@ +# Copyright (C) 2006 Collabora Ltd. +# Copyright (C) 2012 Pavel Strashkin +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# dbus +import dbus +import _dbus_bindings + +class MainLoop(_dbus_bindings.PythonMainLoop): + def __init__(self, set_as_default=False): + super(MainLoop, self).__init__() + + if set_as_default: + dbus.set_default_main_loop(self) + + 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 run(self): + raise NotImplementedError + + def quit(self): + raise NotImplementedError diff --git a/dbus/mainloop/poll.py b/dbus/mainloop/poll.py new file mode 100644 index 0000000..fd8abc1 --- /dev/null +++ b/dbus/mainloop/poll.py @@ -0,0 +1,67 @@ +# Copyright (C) 2006 Collabora Ltd. +# Copyright (C) 2012 Pavel Strashkin +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# 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.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/select.py b/dbus/mainloop/select.py new file mode 100644 index 0000000..842258b --- /dev/null +++ b/dbus/mainloop/select.py @@ -0,0 +1,90 @@ +# Copyright (C) 2006 Collabora Ltd. +# Copyright (C) 2012 Pavel Strashkin +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# 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.base import MainLoop + +class SelectMainLoop(MainLoop): + def __init__(self, *args, **kwargs): + super(SelectMainLoop, self).__init__(*args, **kwargs) + + 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 run(self): + self.running = True + while self.running: + self._tick() + + def quit(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/twisted.py b/dbus/mainloop/twisted.py new file mode 100644 index 0000000..5973e80 --- /dev/null +++ b/dbus/mainloop/twisted.py @@ -0,0 +1,99 @@ +# Copyright (C) 2006 Collabora Ltd. +# Copyright (C) 2012 Pavel Strashkin +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, copy, +# modify, merge, publish, distribute, sublicense, and/or sell copies +# of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +# 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.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, *args, **kwargs): + super(TwistedMainLoop, self).__init__(*args, **kwargs) + + if not reactor: + if 'twisted.internet.reactor' not in sys.modules: + from twisted.internet import reactor + reactor = sys.modules['twisted.internet.reactor'] + + self.reactor = 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 run(self): + self.reactor.run() + + def quit(self): + self.reactor.stop() diff --git a/examples/example-mainloop-python.py b/examples/example-mainloop-python.py new file mode 100644 index 0000000..bb52fbe --- /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(set_as_default=True) + +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.run() -- 1.7.4.1