[PATCH 14/17] Move signal matching machinery into superclasses
Simon McVittie
simon.mcvittie at collabora.co.uk
Mon Apr 30 05:15:28 PDT 2007
diff --git a/dbus/_dbus.py b/dbus/_dbus.py
index bab195a..12aff2c 100644
--- a/dbus/_dbus.py
+++ b/dbus/_dbus.py
@@ -27,7 +27,6 @@ __all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
__docformat__ = 'reStructuredText'
import os
-import logging
import sys
import weakref
from traceback import print_exc
@@ -43,188 +42,12 @@ from _dbus_bindings import BUS_DAEMON_NAME, BUS_DAEMON_PATH,\
HANDLER_RESULT_NOT_YET_HANDLED,\
HANDLER_RESULT_HANDLED
from dbus.bus import BusConnection
-from dbus.proxies import ProxyObject
try:
import thread
except ImportError:
import dummy_thread as thread
-logger = logging.getLogger('dbus._dbus')
-
-_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
- "interface='%s',member='NameOwnerChanged',"
- "path='%s',arg0='%%s'"
- % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
- BUS_DAEMON_PATH))
-"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
-messages"""
-
-
-class SignalMatch(object):
- __slots__ = ('sender_unique', '_member', '_interface', '_sender',
- '_path', '_handler', '_args_match', '_rule',
- '_utf8_strings', '_byte_arrays', '_conn_weakref',
- '_destination_keyword', '_interface_keyword',
- '_message_keyword', '_member_keyword',
- '_sender_keyword', '_path_keyword', '_int_args_match')
-
- def __init__(self, conn, sender, object_path, dbus_interface,
- member, handler, utf8_strings=False, byte_arrays=False,
- sender_keyword=None, path_keyword=None,
- interface_keyword=None, member_keyword=None,
- message_keyword=None, destination_keyword=None,
- **kwargs):
- if member is not None:
- validate_member_name(member)
- if dbus_interface is not None:
- validate_interface_name(dbus_interface)
- if sender is not None:
- validate_bus_name(sender)
- if object_path is not None:
- validate_object_path(object_path)
-
- self._conn_weakref = weakref.ref(conn)
- self._sender = sender
- self._interface = dbus_interface
- self._member = member
- self._path = object_path
- self._handler = handler
- if (sender is not None and sender[:1] != ':'
- and sender != BUS_DAEMON_NAME):
- self.sender_unique = conn.get_name_owner(sender)
- else:
- self.sender_unique = sender
- self._utf8_strings = utf8_strings
- self._byte_arrays = byte_arrays
- self._sender_keyword = sender_keyword
- self._path_keyword = path_keyword
- self._member_keyword = member_keyword
- self._interface_keyword = interface_keyword
- self._message_keyword = message_keyword
- self._destination_keyword = destination_keyword
-
- self._args_match = kwargs
- if not kwargs:
- self._int_args_match = None
- else:
- self._int_args_match = {}
- for kwarg in kwargs:
- if not kwarg.startswith('arg'):
- raise TypeError('SignalMatch: unknown keyword argument %s'
- % kwarg)
- try:
- index = int(kwarg[3:])
- except ValueError:
- raise TypeError('SignalMatch: unknown keyword argument %s'
- % kwarg)
- if index < 0 or index > 63:
- raise TypeError('SignalMatch: arg match index must be in '
- 'range(64), not %d' % index)
- self._int_args_match[index] = kwargs[kwarg]
-
- # we're always going to have to calculate the match rule for
- # the Bus's benefit, so this constructor might as well do the work
- rule = ["type='signal'"]
- if self._sender is not None:
- rule.append("sender='%s'" % self._sender)
- if self._path is not None:
- rule.append("path='%s'" % self._path)
- if self._interface is not None:
- rule.append("interface='%s'" % self._interface)
- if self._member is not None:
- rule.append("member='%s'" % self._member)
- for kwarg, value in kwargs.iteritems():
- rule.append("%s='%s'" % (kwarg, value))
-
- self._rule = ','.join(rule)
-
- def __str__(self):
- return self._rule
-
- def __repr__(self):
- return ('<%s at %x "%s" on conn %r>'
- % (self.__class__, id(self), self._rule, self._conn_weakref()))
-
- def matches_removal_spec(self, sender, object_path,
- dbus_interface, member, handler, **kwargs):
- if handler not in (None, self._handler):
- return False
- if sender != self._sender:
- return False
- if object_path != self._path:
- return False
- if dbus_interface != self._interface:
- return False
- if member != self._member:
- return False
- if kwargs != self._args_match:
- return False
- return True
-
- def maybe_handle_message(self, message):
- args = None
-
- # these haven't been checked yet by the match tree
- if self.sender_unique not in (None, message.get_sender()):
- return False
- if self._int_args_match is not None:
- # extracting args with utf8_strings and byte_arrays is less work
- args = message.get_args_list(utf8_strings=True, byte_arrays=True)
- for index, value in self._int_args_match.iteritems():
- if (index >= len(args)
- or not isinstance(args[index], UTF8String)
- or args[index] != value):
- return False
-
- # these have likely already been checked by the match tree
- if self._member not in (None, message.get_member()):
- return False
- if self._interface not in (None, message.get_interface()):
- return False
- if self._path not in (None, message.get_path()):
- return False
-
- try:
- # minor optimization: if we already extracted the args with the
- # right calling convention to do the args match, don't bother
- # doing so again
- if args is None or not self._utf8_strings or not self._byte_arrays:
- args = message.get_args_list(utf8_strings=self._utf8_strings,
- byte_arrays=self._byte_arrays)
- kwargs = {}
- if self._sender_keyword is not None:
- kwargs[self._sender_keyword] = message.get_sender()
- if self._destination_keyword is not None:
- kwargs[self._destination_keyword] = message.get_destination()
- if self._path_keyword is not None:
- kwargs[self._path_keyword] = message.get_path()
- if self._member_keyword is not None:
- kwargs[self._member_keyword] = message.get_member()
- if self._interface_keyword is not None:
- kwargs[self._interface_keyword] = message.get_interface()
- if self._message_keyword is not None:
- kwargs[self._message_keyword] = message
- self._handler(*args, **kwargs)
- except:
- # FIXME: need to decide whether dbus-python uses logging, or
- # stderr, or what, and make it consistent
- sys.stderr.write('Exception in handler for D-Bus signal:\n')
- print_exc()
-
- return True
-
- def remove(self):
- #logger.debug('%r: removing', self)
- conn = self._conn_weakref()
- # do nothing if the connection has already vanished
- if conn is not None:
- #logger.debug('%r: removing from connection %r', self, conn)
- conn.remove_signal_receiver(self, self._member,
- self._interface, self._sender,
- self._path,
- **self._args_match)
-
class Bus(BusConnection):
"""A connection to one of three possible standard buses, the SESSION,
@@ -235,9 +58,6 @@ class Bus(BusConnection):
`BusConnection`, which doesn't have all this magic.
"""
- START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
- START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
-
_shared_instances = {}
def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
@@ -283,26 +103,9 @@ class Bus(BusConnection):
else:
raise ValueError('invalid bus_type %s' % bus_type)
- bus = subclass._new_for_bus(bus_type, mainloop=mainloop)
+ bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
bus._bus_type = bus_type
- # _bus_names is used by dbus.service.BusName!
- bus._bus_names = weakref.WeakValueDictionary()
-
- bus._signal_recipients_by_object_path = {}
- """Map from object path to dict mapping dbus_interface to dict
- mapping member to list of SignalMatch objects."""
-
- bus._signal_sender_matches = {}
- """Map from sender well-known name to list of match rules for all
- signal handlers that match on sender well-known name."""
-
- bus._signals_lock = thread.allocate_lock()
- """Lock used to protect signal data structures if doing two
- removals at the same time (everything else is atomic, thanks to
- the GIL)"""
-
- bus.add_message_filter(bus.__class__._signal_func)
if not private:
cls._shared_instances[bus_type] = bus
@@ -313,7 +116,7 @@ class Bus(BusConnection):
t = self._bus_type
if self.__class__._shared_instances[t] is self:
del self.__class__._shared_instances[t]
- super(BusConnection, self).close()
+ super(Bus, self).close()
def get_connection(self):
"""(Deprecated - in new code, just use self)
@@ -361,217 +164,6 @@ class Bus(BusConnection):
get_starter = staticmethod(get_starter)
- def add_signal_receiver(self, handler_function,
- signal_name=None,
- dbus_interface=None,
- named_service=None,
- path=None,
- **keywords):
- """Arrange for the given function to be called when a signal matching
- the parameters is received.
-
- :Parameters:
- `handler_function` : callable
- The function to be called. Its positional arguments will
- be the arguments of the signal. By default it will receive
- no keyword arguments, but see the description of
- the optional keyword arguments below.
- `signal_name` : str
- The signal name; None (the default) matches all names
- `dbus_interface` : str
- The D-Bus interface name with which to qualify the signal;
- None (the default) matches all interface names
- `named_service` : str
- A bus name for the sender, which will be resolved to a
- unique name if it is not already; None (the default) matches
- any sender
- `path` : str
- The object path of the object which must have emitted the
- signal; None (the default) matches any object path
- :Keywords:
- `utf8_strings` : bool
- If True, the handler function will receive any string
- arguments as dbus.UTF8String objects (a subclass of str
- guaranteed to be UTF-8). If False (default) it will receive
- any string arguments as dbus.String objects (a subclass of
- unicode).
- `byte_arrays` : bool
- If True, the handler function will receive any byte-array
- arguments as dbus.ByteArray objects (a subclass of str).
- If False (default) it will receive any byte-array
- arguments as a dbus.Array of dbus.Byte (subclasses of:
- a list of ints).
- `sender_keyword` : str
- If not None (the default), the handler function will receive
- the unique name of the sending endpoint as a keyword
- argument with this name.
- `destination_keyword` : str
- If not None (the default), the handler function will receive
- the bus name of the destination (or None if the signal is a
- broadcast, as is usual) as a keyword argument with this name.
- `interface_keyword` : str
- If not None (the default), the handler function will receive
- the signal interface as a keyword argument with this name.
- `member_keyword` : str
- If not None (the default), the handler function will receive
- the signal name as a keyword argument with this name.
- `path_keyword` : str
- If not None (the default), the handler function will receive
- the object-path of the sending object as a keyword argument
- with this name.
- `message_keyword` : str
- If not None (the default), the handler function will receive
- the `dbus.lowlevel.SignalMessage` as a keyword argument with
- this name.
- `arg...` : unicode or UTF-8 str
- If there are additional keyword parameters of the form
- ``arg``\ *n*, match only signals where the *n*\ th argument
- is the value given for that keyword parameter. As of this
- time only string arguments can be matched (in particular,
- object paths and signatures can't).
- """
- self._require_main_loop()
-
- match = SignalMatch(self, named_service, path, dbus_interface,
- signal_name, handler_function, **keywords)
- by_interface = self._signal_recipients_by_object_path.setdefault(path,
- {})
- by_member = by_interface.setdefault(dbus_interface, {})
- matches = by_member.setdefault(signal_name, [])
- # The bus daemon is special - its unique-name is org.freedesktop.DBus
- # rather than starting with :
- if (named_service is not None and named_service[:1] != ':'
- and named_service != BUS_DAEMON_NAME):
- notification = self._signal_sender_matches.setdefault(named_service,
- [])
- if not notification:
- self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
- notification.append(match)
- # make sure nobody is currently manipulating the list
- self._signals_lock.acquire()
- try:
- matches.append(match)
- finally:
- self._signals_lock.release()
- self.add_match_string(str(match))
- return match
-
- def _iter_easy_matches(self, path, dbus_interface, member):
- if path is not None:
- path_keys = (None, path)
- else:
- path_keys = (None,)
- if dbus_interface is not None:
- interface_keys = (None, dbus_interface)
- else:
- interface_keys = (None,)
- if member is not None:
- member_keys = (None, member)
- else:
- member_keys = (None,)
-
- for path in path_keys:
- by_interface = self._signal_recipients_by_object_path.get(path,
- None)
- if by_interface is None:
- continue
- for dbus_interface in interface_keys:
- by_member = by_interface.get(dbus_interface, None)
- if by_member is None:
- continue
- for member in member_keys:
- matches = by_member.get(member, None)
- if matches is None:
- continue
- for m in matches:
- yield m
-
- def _remove_name_owner_changed_for_match(self, named_service, match):
- # The signals lock must be held.
- notification = self._signal_sender_matches.get(named_service, False)
- if notification:
- try:
- notification.remove(match)
- except LookupError:
- pass
- if not notification:
- self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
- % named_service)
-
- def remove_signal_receiver(self, handler_or_match,
- signal_name=None,
- dbus_interface=None,
- named_service=None,
- path=None,
- **keywords):
- #logger.debug('%r: removing signal receiver %r: member=%s, '
- #'iface=%s, sender=%s, path=%s, kwargs=%r',
- #self, handler_or_match, signal_name,
- #dbus_interface, named_service, path, keywords)
- #logger.debug('%r', self._signal_recipients_by_object_path)
- by_interface = self._signal_recipients_by_object_path.get(path, None)
- if by_interface is None:
- return
- by_member = by_interface.get(dbus_interface, None)
- if by_member is None:
- return
- matches = by_member.get(signal_name, None)
- if matches is None:
- return
- self._signals_lock.acquire()
- #logger.debug(matches)
- try:
- new = []
- for match in matches:
- if (handler_or_match is match
- or match.matches_removal_spec(named_service,
- path,
- dbus_interface,
- signal_name,
- handler_or_match,
- **keywords)):
- #logger.debug('Removing match string: %s', match)
- self.remove_match_string(str(match))
- self._remove_name_owner_changed_for_match(named_service,
- match)
- else:
- new.append(match)
- by_member[signal_name] = new
- finally:
- self._signals_lock.release()
-
- def _signal_func(self, message):
- """D-Bus filter function. Handle signals by dispatching to Python
- callbacks kept in the match-rule tree.
- """
-
- #logger.debug('Incoming message %r with args %r', message,
- #message.get_args_list())
-
- if not isinstance(message, SignalMessage):
- return HANDLER_RESULT_NOT_YET_HANDLED
-
- # If it's NameOwnerChanged, we'll need to update our
- # sender well-known name -> sender unique name mappings
- if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
- and message.has_sender(BUS_DAEMON_NAME)
- and message.has_path(BUS_DAEMON_PATH)):
- name, unused, new = message.get_args_list()
- for match in self._signal_sender_matches.get(name, (None,))[1:]:
- match.sender_unique = new
-
- # See if anyone else wants to know
- dbus_interface = message.get_interface()
- path = message.get_path()
- signal_name = message.get_member()
-
- ret = HANDLER_RESULT_NOT_YET_HANDLED
- for match in self._iter_easy_matches(path, dbus_interface,
- signal_name):
- if match.maybe_handle_message(message):
- ret = HANDLER_RESULT_HANDLED
- return ret
-
def __repr__(self):
if self._bus_type == BUS_SESSION:
name = 'SESSION'
diff --git a/dbus/bus.py b/dbus/bus.py
index 45c8505..d981726 100644
--- a/dbus/bus.py
+++ b/dbus/bus.py
@@ -19,14 +19,31 @@
__all__ = ('BusConnection',)
__docformat__ = 'reStructuredText'
+import logging
+import weakref
+
from _dbus_bindings import validate_interface_name, validate_member_name,\
validate_bus_name, validate_object_path,\
validate_error_name,\
DBusException, \
BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
- BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE
+ DBUS_START_REPLY_SUCCESS, \
+ DBUS_START_REPLY_ALREADY_RUNNING, \
+ BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
+ HANDLER_RESULT_NOT_YET_HANDLED
from dbus.connection import Connection
+_NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
+ "interface='%s',member='NameOwnerChanged',"
+ "path='%s',arg0='%%s'"
+ % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
+ BUS_DAEMON_PATH))
+"""(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
+messages"""
+
+
+_logger = logging.getLogger('dbus.connection')
+
class BusConnection(Connection):
"""A connection to a D-Bus daemon that implements the
@@ -43,6 +60,77 @@ class BusConnection(Connection):
"""Represents the bus that started this service by activation (same as
the global dbus.BUS_STARTER)"""
+ START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
+ START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
+
+ def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
+ bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
+
+ # _bus_names is used by dbus.service.BusName!
+ bus._bus_names = weakref.WeakValueDictionary()
+
+ bus._signal_sender_matches = {}
+ """Map from sender well-known name to list of match rules for all
+ signal handlers that match on sender well-known name."""
+
+ bus.add_message_filter(bus.__class__._noc_signal_func)
+
+ return bus
+
+ def _noc_signal_func(self, message):
+ # If it's NameOwnerChanged, we'll need to update our
+ # sender well-known name -> sender unique name mappings
+ if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
+ and message.has_sender(BUS_DAEMON_NAME)
+ and message.has_path(BUS_DAEMON_PATH)):
+ name, unused, new = message.get_args_list()
+ for match in self._signal_sender_matches.get(name, (None,)):
+ match.sender_unique = new
+
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ def add_signal_receiver(self, handler_function, signal_name=None,
+ dbus_interface=None, named_service=None,
+ path=None, **keywords):
+ match = super(BusConnection, self).add_signal_receiver(
+ handler_function, signal_name, dbus_interface, named_service,
+ path, **keywords)
+
+ # The bus daemon is special - its unique-name is org.freedesktop.DBus
+ # rather than starting with :
+ if (named_service is not None
+ and named_service[:1] != ':'
+ and named_service != BUS_DAEMON_NAME):
+ try:
+ match.sender_unique = self.get_name_owner(named_service)
+ except DBusException:
+ # if the desired sender isn't actually running, we'll get
+ # notified by NameOwnerChanged when it appears
+ pass
+ notification = self._signal_sender_matches.setdefault(
+ named_service, [])
+ if not notification:
+ self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
+ notification.append(match)
+
+ self.add_match_string(str(match))
+
+ return match
+
+ def _clean_up_signal_match(self, match):
+ # The signals lock must be held.
+ self.remove_match_string(str(match))
+ notification = self._signal_sender_matches.get(match.sender, False)
+ if notification:
+ try:
+ notification.remove(match)
+ except LookupError:
+ pass
+ if not notification:
+ # nobody cares any more, so remove the match rule from the bus
+ self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
+ % match.sender)
+
def activate_name_owner(self, bus_name):
if (bus_name is not None and bus_name[:1] != ':'
and bus_name != BUS_DAEMON_NAME):
diff --git a/dbus/connection.py b/dbus/connection.py
index 1de5ec4..0ba64ab 100644
--- a/dbus/connection.py
+++ b/dbus/connection.py
@@ -16,24 +16,194 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-__all__ = ('Connection',)
+__all__ = ('Connection', 'SignalMatch')
__docformat__ = 'reStructuredText'
import logging
+try:
+ import thread
+except ImportError:
+ import dummy_thread as thread
+import weakref
from _dbus_bindings import Connection as _Connection, ErrorMessage, \
MethodCallMessage, MethodReturnMessage, \
- DBusException, LOCAL_PATH, LOCAL_IFACE
+ DBusException, LOCAL_PATH, LOCAL_IFACE, \
+ validate_interface_name, validate_member_name,\
+ validate_bus_name, validate_object_path,\
+ validate_error_name, \
+ HANDLER_RESULT_NOT_YET_HANDLED, \
+ UTF8String, SignalMessage
from dbus.proxies import ProxyObject
-_logger = logging.getLogger('dbus.methods')
+_logger = logging.getLogger('dbus.connection')
def _noop(*args, **kwargs):
pass
+class SignalMatch(object):
+ __slots__ = ('sender_unique', '_member', '_interface', '_sender',
+ '_path', '_handler', '_args_match', '_rule',
+ '_utf8_strings', '_byte_arrays', '_conn_weakref',
+ '_destination_keyword', '_interface_keyword',
+ '_message_keyword', '_member_keyword',
+ '_sender_keyword', '_path_keyword', '_int_args_match')
+
+ def __init__(self, conn, sender, object_path, dbus_interface,
+ member, handler, utf8_strings=False, byte_arrays=False,
+ sender_keyword=None, path_keyword=None,
+ interface_keyword=None, member_keyword=None,
+ message_keyword=None, destination_keyword=None,
+ **kwargs):
+ if member is not None:
+ validate_member_name(member)
+ if dbus_interface is not None:
+ validate_interface_name(dbus_interface)
+ if sender is not None:
+ validate_bus_name(sender)
+ if object_path is not None:
+ validate_object_path(object_path)
+
+ self._conn_weakref = weakref.ref(conn)
+ self._sender = sender
+ self._interface = dbus_interface
+ self._member = member
+ self._path = object_path
+ self._handler = handler
+ self.sender_unique = sender
+ self._utf8_strings = utf8_strings
+ self._byte_arrays = byte_arrays
+ self._sender_keyword = sender_keyword
+ self._path_keyword = path_keyword
+ self._member_keyword = member_keyword
+ self._interface_keyword = interface_keyword
+ self._message_keyword = message_keyword
+ self._destination_keyword = destination_keyword
+
+ self._args_match = kwargs
+ if not kwargs:
+ self._int_args_match = None
+ else:
+ self._int_args_match = {}
+ for kwarg in kwargs:
+ if not kwarg.startswith('arg'):
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ try:
+ index = int(kwarg[3:])
+ except ValueError:
+ raise TypeError('SignalMatch: unknown keyword argument %s'
+ % kwarg)
+ if index < 0 or index > 63:
+ raise TypeError('SignalMatch: arg match index must be in '
+ 'range(64), not %d' % index)
+ self._int_args_match[index] = kwargs[kwarg]
+
+ # we're probably going to have to calculate the match rule for
+ # the Bus's benefit, so this constructor might as well do the work
+ rule = ["type='signal'"]
+ if self._sender is not None:
+ rule.append("sender='%s'" % self._sender)
+ if self._path is not None:
+ rule.append("path='%s'" % self._path)
+ if self._interface is not None:
+ rule.append("interface='%s'" % self._interface)
+ if self._member is not None:
+ rule.append("member='%s'" % self._member)
+ for kwarg, value in kwargs.iteritems():
+ rule.append("%s='%s'" % (kwarg, value))
+
+ self._rule = ','.join(rule)
+
+ sender = property(lambda self: self._sender)
+
+ def __str__(self):
+ return self._rule
+
+ def __repr__(self):
+ return ('<%s at %x "%s" on conn %r>'
+ % (self.__class__, id(self), self._rule, self._conn_weakref()))
+
+ def matches_removal_spec(self, sender, object_path,
+ dbus_interface, member, handler, **kwargs):
+ if handler not in (None, self._handler):
+ return False
+ if sender != self._sender:
+ return False
+ if object_path != self._path:
+ return False
+ if dbus_interface != self._interface:
+ return False
+ if member != self._member:
+ return False
+ if kwargs != self._args_match:
+ return False
+ return True
+
+ def maybe_handle_message(self, message):
+ args = None
+
+ # these haven't been checked yet by the match tree
+ if self.sender_unique not in (None, message.get_sender()):
+ return False
+ if self._int_args_match is not None:
+ # extracting args with utf8_strings and byte_arrays is less work
+ args = message.get_args_list(utf8_strings=True, byte_arrays=True)
+ for index, value in self._int_args_match.iteritems():
+ if (index >= len(args)
+ or not isinstance(args[index], UTF8String)
+ or args[index] != value):
+ return False
+
+ # these have likely already been checked by the match tree
+ if self._member not in (None, message.get_member()):
+ return False
+ if self._interface not in (None, message.get_interface()):
+ return False
+ if self._path not in (None, message.get_path()):
+ return False
+
+ try:
+ # minor optimization: if we already extracted the args with the
+ # right calling convention to do the args match, don't bother
+ # doing so again
+ if args is None or not self._utf8_strings or not self._byte_arrays:
+ args = message.get_args_list(utf8_strings=self._utf8_strings,
+ byte_arrays=self._byte_arrays)
+ kwargs = {}
+ if self._sender_keyword is not None:
+ kwargs[self._sender_keyword] = message.get_sender()
+ if self._destination_keyword is not None:
+ kwargs[self._destination_keyword] = message.get_destination()
+ if self._path_keyword is not None:
+ kwargs[self._path_keyword] = message.get_path()
+ if self._member_keyword is not None:
+ kwargs[self._member_keyword] = message.get_member()
+ if self._interface_keyword is not None:
+ kwargs[self._interface_keyword] = message.get_interface()
+ if self._message_keyword is not None:
+ kwargs[self._message_keyword] = message
+ self._handler(*args, **kwargs)
+ except:
+ # basicConfig is a no-op if logging is already configured
+ logging.basicConfig()
+ _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
+
+ return True
+
+ def remove(self):
+ conn = self._conn_weakref()
+ # do nothing if the connection has already vanished
+ if conn is not None:
+ conn.remove_signal_receiver(self, self._member,
+ self._interface, self._sender,
+ self._path,
+ **self._args_match)
+
+
class Connection(_Connection):
"""A connection to another application. In this base class there is
assumed to be no bus daemon.
@@ -41,6 +211,40 @@ class Connection(_Connection):
ProxyObjectClass = ProxyObject
+ def __init__(self, *args, **kwargs):
+ super(Connection, self).__init__(*args, **kwargs)
+
+ # this if-block is needed because shared bus connections can be
+ # __init__'ed more than once
+ if not hasattr(self, '_dbus_Connection_initialized'):
+ self._dbus_Connection_initialized = 1
+
+ self._signal_recipients_by_object_path = {}
+ """Map from object path to dict mapping dbus_interface to dict
+ mapping member to list of SignalMatch objects."""
+
+ self._signals_lock = thread.allocate_lock()
+ """Lock used to protect signal data structures if doing two
+ removals at the same time (everything else is atomic, thanks to
+ the GIL)"""
+
+ self.add_message_filter(self.__class__._signal_func)
+
+ def activate_name_owner(self, bus_name):
+ """Return the unique name for the given bus name, activating it
+ if necessary and possible.
+
+ If the name is already unique or this connection is not to a
+ bus daemon, just return it.
+
+ :Returns: a bus name. If the given `bus_name` exists, the returned
+ name identifies its current owner; otherwise the returned name
+ does not exist.
+ :Raises DBusException: if the implementation has failed
+ to activate the given bus name.
+ """
+ return bus_name
+
def get_object(self, named_service, object_path, introspect=True):
"""Return a local proxy for the given remote object.
@@ -62,20 +266,173 @@ class Connection(_Connection):
return self.ProxyObjectClass(self, named_service, object_path,
introspect=introspect)
- def activate_name_owner(self, bus_name):
- """Return the unique name for the given bus name, activating it
- if necessary and possible.
+ def add_signal_receiver(self, handler_function,
+ signal_name=None,
+ dbus_interface=None,
+ named_service=None,
+ path=None,
+ **keywords):
+ """Arrange for the given function to be called when a signal matching
+ the parameters is received.
- If the name is already unique or this connection is not to a
- bus daemon, just return it.
+ :Parameters:
+ `handler_function` : callable
+ The function to be called. Its positional arguments will
+ be the arguments of the signal. By default it will receive
+ no keyword arguments, but see the description of
+ the optional keyword arguments below.
+ `signal_name` : str
+ The signal name; None (the default) matches all names
+ `dbus_interface` : str
+ The D-Bus interface name with which to qualify the signal;
+ None (the default) matches all interface names
+ `named_service` : str
+ A bus name for the sender, which will be resolved to a
+ unique name if it is not already; None (the default) matches
+ any sender
+ `path` : str
+ The object path of the object which must have emitted the
+ signal; None (the default) matches any object path
+ :Keywords:
+ `utf8_strings` : bool
+ If True, the handler function will receive any string
+ arguments as dbus.UTF8String objects (a subclass of str
+ guaranteed to be UTF-8). If False (default) it will receive
+ any string arguments as dbus.String objects (a subclass of
+ unicode).
+ `byte_arrays` : bool
+ If True, the handler function will receive any byte-array
+ arguments as dbus.ByteArray objects (a subclass of str).
+ If False (default) it will receive any byte-array
+ arguments as a dbus.Array of dbus.Byte (subclasses of:
+ a list of ints).
+ `sender_keyword` : str
+ If not None (the default), the handler function will receive
+ the unique name of the sending endpoint as a keyword
+ argument with this name.
+ `destination_keyword` : str
+ If not None (the default), the handler function will receive
+ the bus name of the destination (or None if the signal is a
+ broadcast, as is usual) as a keyword argument with this name.
+ `interface_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal interface as a keyword argument with this name.
+ `member_keyword` : str
+ If not None (the default), the handler function will receive
+ the signal name as a keyword argument with this name.
+ `path_keyword` : str
+ If not None (the default), the handler function will receive
+ the object-path of the sending object as a keyword argument
+ with this name.
+ `message_keyword` : str
+ If not None (the default), the handler function will receive
+ the `dbus.lowlevel.SignalMessage` as a keyword argument with
+ this name.
+ `arg...` : unicode or UTF-8 str
+ If there are additional keyword parameters of the form
+ ``arg``\ *n*, match only signals where the *n*\ th argument
+ is the value given for that keyword parameter. As of this
+ time only string arguments can be matched (in particular,
+ object paths and signatures can't).
+ """
+ self._require_main_loop()
- :Returns: a bus name. If the given `bus_name` exists, the returned
- name identifies its current owner; otherwise the returned name
- does not exist.
- :Raises DBusException: if the implementation has failed
- to activate the given bus name.
+ match = SignalMatch(self, named_service, path, dbus_interface,
+ signal_name, handler_function, **keywords)
+ by_interface = self._signal_recipients_by_object_path.setdefault(path,
+ {})
+ by_member = by_interface.setdefault(dbus_interface, {})
+ matches = by_member.setdefault(signal_name, [])
+ self._signals_lock.acquire()
+ try:
+ matches.append(match)
+ finally:
+ self._signals_lock.release()
+ return match
+
+ def _iter_easy_matches(self, path, dbus_interface, member):
+ if path is not None:
+ path_keys = (None, path)
+ else:
+ path_keys = (None,)
+ if dbus_interface is not None:
+ interface_keys = (None, dbus_interface)
+ else:
+ interface_keys = (None,)
+ if member is not None:
+ member_keys = (None, member)
+ else:
+ member_keys = (None,)
+
+ for path in path_keys:
+ by_interface = self._signal_recipients_by_object_path.get(path,
+ None)
+ if by_interface is None:
+ continue
+ for dbus_interface in interface_keys:
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ continue
+ for member in member_keys:
+ matches = by_member.get(member, None)
+ if matches is None:
+ continue
+ for m in matches:
+ yield m
+
+ def remove_signal_receiver(self, handler_or_match,
+ signal_name=None,
+ dbus_interface=None,
+ named_service=None,
+ path=None,
+ **keywords):
+ by_interface = self._signal_recipients_by_object_path.get(path, None)
+ if by_interface is None:
+ return
+ by_member = by_interface.get(dbus_interface, None)
+ if by_member is None:
+ return
+ matches = by_member.get(signal_name, None)
+ if matches is None:
+ return
+ self._signals_lock.acquire()
+ try:
+ new = []
+ for match in matches:
+ if (handler_or_match is match
+ or match.matches_removal_spec(named_service,
+ path,
+ dbus_interface,
+ signal_name,
+ handler_or_match,
+ **keywords)):
+ self._clean_up_signal_match(match)
+ else:
+ new.append(match)
+ by_member[signal_name] = new
+ finally:
+ self._signals_lock.release()
+
+ def _clean_up_signal_match(self, match):
+ # Called with the signals lock held
+ pass
+
+ def _signal_func(self, message):
+ """D-Bus filter function. Handle signals by dispatching to Python
+ callbacks kept in the match-rule tree.
"""
- return bus_name
+
+ if not isinstance(message, SignalMessage):
+ return HANDLER_RESULT_NOT_YET_HANDLED
+
+ dbus_interface = message.get_interface()
+ path = message.get_path()
+ signal_name = message.get_member()
+
+ for match in self._iter_easy_matches(path, dbus_interface,
+ signal_name):
+ match.maybe_handle_message(message)
+ return HANDLER_RESULT_NOT_YET_HANDLED
def call_async(self, bus_name, object_path, dbus_interface, method,
signature, args, reply_handler, error_handler,
@@ -108,6 +465,7 @@ class Connection(_Connection):
try:
message.append(signature=signature, *args)
except Exception, e:
+ logging.basicConfig()
_logger.error('Unable to set arguments %r according to '
'signature %r: %s: %s',
args, signature, e.__class__, e)
@@ -164,6 +522,7 @@ class Connection(_Connection):
try:
message.append(signature=signature, *args)
except Exception, e:
+ logging.basicConfig()
_logger.error('Unable to set arguments %r according to '
'signature %r: %s: %s',
args, signature, e.__class__, e)
More information about the dbus
mailing list