doc/tutorial.txt: Don't claim we have a tutorial for p2p connections yet
[dbus-python-phuang.git] / dbus / connection.py
blobfd0fe7aa8316ab2dc3e78bd602c6c3eb06abd229
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('Connection', 'SignalMatch')
20 __docformat__ = 'reStructuredText'
22 import logging
23 try:
24 import thread
25 except ImportError:
26 import dummy_thread as thread
27 import weakref
29 from _dbus_bindings import Connection as _Connection, ErrorMessage, \
30 MethodCallMessage, MethodReturnMessage, \
31 DBusException, LOCAL_PATH, LOCAL_IFACE, \
32 validate_interface_name, validate_member_name,\
33 validate_bus_name, validate_object_path,\
34 validate_error_name, \
35 HANDLER_RESULT_NOT_YET_HANDLED, \
36 UTF8String, SignalMessage
37 from dbus.proxies import ProxyObject
40 _logger = logging.getLogger('dbus.connection')
43 def _noop(*args, **kwargs):
44 pass
47 class SignalMatch(object):
48 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
49 '_path', '_handler', '_args_match', '_rule',
50 '_utf8_strings', '_byte_arrays', '_conn_weakref',
51 '_destination_keyword', '_interface_keyword',
52 '_message_keyword', '_member_keyword',
53 '_sender_keyword', '_path_keyword', '_int_args_match')
55 def __init__(self, conn, sender, object_path, dbus_interface,
56 member, handler, utf8_strings=False, byte_arrays=False,
57 sender_keyword=None, path_keyword=None,
58 interface_keyword=None, member_keyword=None,
59 message_keyword=None, destination_keyword=None,
60 **kwargs):
61 if member is not None:
62 validate_member_name(member)
63 if dbus_interface is not None:
64 validate_interface_name(dbus_interface)
65 if sender is not None:
66 validate_bus_name(sender)
67 if object_path is not None:
68 validate_object_path(object_path)
70 self._rule = None
71 self._conn_weakref = weakref.ref(conn)
72 self._sender = sender
73 self._interface = dbus_interface
74 self._member = member
75 self._path = object_path
76 self._handler = handler
78 # if the connection is actually a bus, it's responsible for changing
79 # this later
80 self._sender_name_owner = sender
82 self._utf8_strings = utf8_strings
83 self._byte_arrays = byte_arrays
84 self._sender_keyword = sender_keyword
85 self._path_keyword = path_keyword
86 self._member_keyword = member_keyword
87 self._interface_keyword = interface_keyword
88 self._message_keyword = message_keyword
89 self._destination_keyword = destination_keyword
91 self._args_match = kwargs
92 if not kwargs:
93 self._int_args_match = None
94 else:
95 self._int_args_match = {}
96 for kwarg in kwargs:
97 if not kwarg.startswith('arg'):
98 raise TypeError('SignalMatch: unknown keyword argument %s'
99 % kwarg)
100 try:
101 index = int(kwarg[3:])
102 except ValueError:
103 raise TypeError('SignalMatch: unknown keyword argument %s'
104 % kwarg)
105 if index < 0 or index > 63:
106 raise TypeError('SignalMatch: arg match index must be in '
107 'range(64), not %d' % index)
108 self._int_args_match[index] = kwargs[kwarg]
110 def __hash__(self):
111 """SignalMatch objects are compared by identity."""
112 return hash(id(self))
114 def __eq__(self, other):
115 """SignalMatch objects are compared by identity."""
116 return self is other
118 def __ne__(self, other):
119 """SignalMatch objects are compared by identity."""
120 return self is not other
122 sender = property(lambda self: self._sender)
124 def __str__(self):
125 if self._rule is None:
126 rule = ["type='signal'"]
127 if self._sender is not None:
128 rule.append("sender='%s'" % self._sender)
129 if self._path is not None:
130 rule.append("path='%s'" % self._path)
131 if self._interface is not None:
132 rule.append("interface='%s'" % self._interface)
133 if self._member is not None:
134 rule.append("member='%s'" % self._member)
135 if self._int_args_match is not None:
136 for index, value in self._int_args_match.iteritems():
137 rule.append("arg%d='%s'" % (index, value))
139 self._rule = ','.join(rule)
141 return self._rule
143 def __repr__(self):
144 return ('<%s at %x "%s" on conn %r>'
145 % (self.__class__, id(self), self._rule, self._conn_weakref()))
147 def set_sender_name_owner(self, new_name):
148 self._sender_name_owner = new_name
150 def matches_removal_spec(self, sender, object_path,
151 dbus_interface, member, handler, **kwargs):
152 if handler not in (None, self._handler):
153 return False
154 if sender != self._sender:
155 return False
156 if object_path != self._path:
157 return False
158 if dbus_interface != self._interface:
159 return False
160 if member != self._member:
161 return False
162 if kwargs != self._args_match:
163 return False
164 return True
166 def maybe_handle_message(self, message):
167 args = None
169 # these haven't been checked yet by the match tree
170 if self._sender_name_owner not in (None, message.get_sender()):
171 return False
172 if self._int_args_match is not None:
173 # extracting args with utf8_strings and byte_arrays is less work
174 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
175 for index, value in self._int_args_match.iteritems():
176 if (index >= len(args)
177 or not isinstance(args[index], UTF8String)
178 or args[index] != value):
179 return False
181 # these have likely already been checked by the match tree
182 if self._member not in (None, message.get_member()):
183 return False
184 if self._interface not in (None, message.get_interface()):
185 return False
186 if self._path not in (None, message.get_path()):
187 return False
189 try:
190 # minor optimization: if we already extracted the args with the
191 # right calling convention to do the args match, don't bother
192 # doing so again
193 if args is None or not self._utf8_strings or not self._byte_arrays:
194 args = message.get_args_list(utf8_strings=self._utf8_strings,
195 byte_arrays=self._byte_arrays)
196 kwargs = {}
197 if self._sender_keyword is not None:
198 kwargs[self._sender_keyword] = message.get_sender()
199 if self._destination_keyword is not None:
200 kwargs[self._destination_keyword] = message.get_destination()
201 if self._path_keyword is not None:
202 kwargs[self._path_keyword] = message.get_path()
203 if self._member_keyword is not None:
204 kwargs[self._member_keyword] = message.get_member()
205 if self._interface_keyword is not None:
206 kwargs[self._interface_keyword] = message.get_interface()
207 if self._message_keyword is not None:
208 kwargs[self._message_keyword] = message
209 self._handler(*args, **kwargs)
210 except:
211 # basicConfig is a no-op if logging is already configured
212 logging.basicConfig()
213 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
215 return True
217 def remove(self):
218 conn = self._conn_weakref()
219 # do nothing if the connection has already vanished
220 if conn is not None:
221 conn.remove_signal_receiver(self, self._member,
222 self._interface, self._sender,
223 self._path,
224 **self._args_match)
227 class Connection(_Connection):
228 """A connection to another application. In this base class there is
229 assumed to be no bus daemon.
232 ProxyObjectClass = ProxyObject
234 def __init__(self, *args, **kwargs):
235 super(Connection, self).__init__(*args, **kwargs)
237 # this if-block is needed because shared bus connections can be
238 # __init__'ed more than once
239 if not hasattr(self, '_dbus_Connection_initialized'):
240 self._dbus_Connection_initialized = 1
242 self._signal_recipients_by_object_path = {}
243 """Map from object path to dict mapping dbus_interface to dict
244 mapping member to list of SignalMatch objects."""
246 self._signals_lock = thread.allocate_lock()
247 """Lock used to protect signal data structures if doing two
248 removals at the same time (everything else is atomic, thanks to
249 the GIL)"""
251 self.add_message_filter(self.__class__._signal_func)
253 def activate_name_owner(self, bus_name):
254 """Return the unique name for the given bus name, activating it
255 if necessary and possible.
257 If the name is already unique or this connection is not to a
258 bus daemon, just return it.
260 :Returns: a bus name. If the given `bus_name` exists, the returned
261 name identifies its current owner; otherwise the returned name
262 does not exist.
263 :Raises DBusException: if the implementation has failed
264 to activate the given bus name.
266 return bus_name
268 def get_object(self, bus_name=None, object_path=None, introspect=True,
269 **kwargs):
270 """Return a local proxy for the given remote object.
272 Method calls on the proxy are translated into method calls on the
273 remote object.
275 :Parameters:
276 `bus_name` : str
277 A bus name (either the unique name or a well-known name)
278 of the application owning the object. The keyword argument
279 named_service is a deprecated alias for this.
280 `object_path` : str
281 The object path of the desired object
282 `introspect` : bool
283 If true (default), attempt to introspect the remote
284 object to find out supported methods and their signatures
286 :Returns: a `dbus.proxies.ProxyObject`
288 named_service = kwargs.pop('named_service', None)
289 if named_service is not None:
290 if bus_name is not None:
291 raise TypeError('bus_name and named_service cannot both '
292 'be specified')
293 from warnings import warn
294 warn('Passing the named_service parameter to get_object by name '
295 'is deprecated: please use positional parameters',
296 DeprecationWarning, stacklevel=2)
297 bus_name = named_service
298 if kwargs:
299 raise TypeError('get_object does not take these keyword '
300 'arguments: %s' % ', '.join(kwargs.iterkeys()))
302 return self.ProxyObjectClass(self, bus_name, object_path,
303 introspect=introspect)
305 def add_signal_receiver(self, handler_function,
306 signal_name=None,
307 dbus_interface=None,
308 bus_name=None,
309 path=None,
310 **keywords):
311 """Arrange for the given function to be called when a signal matching
312 the parameters is received.
314 :Parameters:
315 `handler_function` : callable
316 The function to be called. Its positional arguments will
317 be the arguments of the signal. By default it will receive
318 no keyword arguments, but see the description of
319 the optional keyword arguments below.
320 `signal_name` : str
321 The signal name; None (the default) matches all names
322 `dbus_interface` : str
323 The D-Bus interface name with which to qualify the signal;
324 None (the default) matches all interface names
325 `bus_name` : str
326 A bus name for the sender, which will be resolved to a
327 unique name if it is not already; None (the default) matches
328 any sender.
329 `path` : str
330 The object path of the object which must have emitted the
331 signal; None (the default) matches any object path
332 :Keywords:
333 `utf8_strings` : bool
334 If True, the handler function will receive any string
335 arguments as dbus.UTF8String objects (a subclass of str
336 guaranteed to be UTF-8). If False (default) it will receive
337 any string arguments as dbus.String objects (a subclass of
338 unicode).
339 `byte_arrays` : bool
340 If True, the handler function will receive any byte-array
341 arguments as dbus.ByteArray objects (a subclass of str).
342 If False (default) it will receive any byte-array
343 arguments as a dbus.Array of dbus.Byte (subclasses of:
344 a list of ints).
345 `sender_keyword` : str
346 If not None (the default), the handler function will receive
347 the unique name of the sending endpoint as a keyword
348 argument with this name.
349 `destination_keyword` : str
350 If not None (the default), the handler function will receive
351 the bus name of the destination (or None if the signal is a
352 broadcast, as is usual) as a keyword argument with this name.
353 `interface_keyword` : str
354 If not None (the default), the handler function will receive
355 the signal interface as a keyword argument with this name.
356 `member_keyword` : str
357 If not None (the default), the handler function will receive
358 the signal name as a keyword argument with this name.
359 `path_keyword` : str
360 If not None (the default), the handler function will receive
361 the object-path of the sending object as a keyword argument
362 with this name.
363 `message_keyword` : str
364 If not None (the default), the handler function will receive
365 the `dbus.lowlevel.SignalMessage` as a keyword argument with
366 this name.
367 `arg...` : unicode or UTF-8 str
368 If there are additional keyword parameters of the form
369 ``arg``\ *n*, match only signals where the *n*\ th argument
370 is the value given for that keyword parameter. As of this
371 time only string arguments can be matched (in particular,
372 object paths and signatures can't).
373 `named_service` : str
374 A deprecated alias for `bus_name`.
376 self._require_main_loop()
378 named_service = keywords.pop('named_service', None)
379 if named_service is not None:
380 if bus_name is not None:
381 raise TypeError('bus_name and named_service cannot both be '
382 'specified')
383 bus_name = named_service
384 from warnings import warn
385 warn('Passing the named_service parameter to add_signal_receiver '
386 'by name is deprecated: please use positional parameters',
387 DeprecationWarning, stacklevel=2)
389 match = SignalMatch(self, bus_name, path, dbus_interface,
390 signal_name, handler_function, **keywords)
391 by_interface = self._signal_recipients_by_object_path.setdefault(path,
393 by_member = by_interface.setdefault(dbus_interface, {})
394 matches = by_member.setdefault(signal_name, [])
396 # make sure nobody is currently manipulating the list
397 self._signals_lock.acquire()
398 try:
399 matches.append(match)
400 finally:
401 self._signals_lock.release()
402 return match
404 def _iter_easy_matches(self, path, dbus_interface, member):
405 if path is not None:
406 path_keys = (None, path)
407 else:
408 path_keys = (None,)
409 if dbus_interface is not None:
410 interface_keys = (None, dbus_interface)
411 else:
412 interface_keys = (None,)
413 if member is not None:
414 member_keys = (None, member)
415 else:
416 member_keys = (None,)
418 for path in path_keys:
419 by_interface = self._signal_recipients_by_object_path.get(path,
420 None)
421 if by_interface is None:
422 continue
423 for dbus_interface in interface_keys:
424 by_member = by_interface.get(dbus_interface, None)
425 if by_member is None:
426 continue
427 for member in member_keys:
428 matches = by_member.get(member, None)
429 if matches is None:
430 continue
431 for m in matches:
432 yield m
434 def remove_signal_receiver(self, handler_or_match,
435 signal_name=None,
436 dbus_interface=None,
437 bus_name=None,
438 path=None,
439 **keywords):
440 named_service = keywords.pop('named_service', None)
441 if named_service is not None:
442 if bus_name is not None:
443 raise TypeError('bus_name and named_service cannot both be '
444 'specified')
445 bus_name = named_service
446 from warnings import warn
447 warn('Passing the named_service parameter to '
448 'remove_signal_receiver by name is deprecated: please use '
449 'positional parameters',
450 DeprecationWarning, stacklevel=2)
452 by_interface = self._signal_recipients_by_object_path.get(path, None)
453 if by_interface is None:
454 return
455 by_member = by_interface.get(dbus_interface, None)
456 if by_member is None:
457 return
458 matches = by_member.get(signal_name, None)
459 if matches is None:
460 return
461 self._signals_lock.acquire()
462 try:
463 new = []
464 for match in matches:
465 if (handler_or_match is match
466 or match.matches_removal_spec(bus_name,
467 path,
468 dbus_interface,
469 signal_name,
470 handler_or_match,
471 **keywords)):
472 self._clean_up_signal_match(match)
473 else:
474 new.append(match)
475 by_member[signal_name] = new
476 finally:
477 self._signals_lock.release()
479 def _clean_up_signal_match(self, match):
480 # Called with the signals lock held
481 pass
483 def _signal_func(self, message):
484 """D-Bus filter function. Handle signals by dispatching to Python
485 callbacks kept in the match-rule tree.
488 if not isinstance(message, SignalMessage):
489 return HANDLER_RESULT_NOT_YET_HANDLED
491 dbus_interface = message.get_interface()
492 path = message.get_path()
493 signal_name = message.get_member()
495 for match in self._iter_easy_matches(path, dbus_interface,
496 signal_name):
497 match.maybe_handle_message(message)
498 return HANDLER_RESULT_NOT_YET_HANDLED
500 def call_async(self, bus_name, object_path, dbus_interface, method,
501 signature, args, reply_handler, error_handler,
502 timeout=-1.0, utf8_strings=False, byte_arrays=False,
503 require_main_loop=True):
504 """Call the given method, asynchronously.
506 If the reply_handler is None, successful replies will be ignored.
507 If the error_handler is None, failures will be ignored. If both
508 are None, the implementation may request that no reply is sent.
510 :Returns: The dbus.lowlevel.PendingCall.
512 if object_path == LOCAL_PATH:
513 raise DBusException('Methods may not be called on the reserved '
514 'path %s' % LOCAL_PATH)
515 if dbus_interface == LOCAL_IFACE:
516 raise DBusException('Methods may not be called on the reserved '
517 'interface %s' % LOCAL_IFACE)
518 # no need to validate other args - MethodCallMessage ctor will do
520 get_args_opts = {'utf8_strings': utf8_strings,
521 'byte_arrays': byte_arrays}
523 message = MethodCallMessage(destination=bus_name,
524 path=object_path,
525 interface=dbus_interface,
526 method=method)
527 # Add the arguments to the function
528 try:
529 message.append(signature=signature, *args)
530 except Exception, e:
531 logging.basicConfig()
532 _logger.error('Unable to set arguments %r according to '
533 'signature %r: %s: %s',
534 args, signature, e.__class__, e)
535 raise
537 if reply_handler is None and error_handler is None:
538 # we don't care what happens, so just send it
539 self.send_message(message)
540 return
542 if reply_handler is None:
543 reply_handler = _noop
544 if error_handler is None:
545 error_handler = _noop
547 def msg_reply_handler(message):
548 if isinstance(message, MethodReturnMessage):
549 reply_handler(*message.get_args_list(**get_args_opts))
550 elif isinstance(message, ErrorMessage):
551 args = message.get_args_list()
552 # FIXME: should we do something with the rest?
553 if len(args) > 0:
554 error_handler(DBusException(args[0]))
555 else:
556 error_handler(DBusException())
557 else:
558 error_handler(TypeError('Unexpected type for reply '
559 'message: %r' % message))
560 return self.send_message_with_reply(message, msg_reply_handler,
561 timeout/1000.0,
562 require_main_loop=require_main_loop)
564 def call_blocking(self, bus_name, object_path, dbus_interface, method,
565 signature, args, timeout=-1.0, utf8_strings=False,
566 byte_arrays=False):
567 """Call the given method, synchronously.
569 if object_path == LOCAL_PATH:
570 raise DBusException('Methods may not be called on the reserved '
571 'path %s' % LOCAL_PATH)
572 if dbus_interface == LOCAL_IFACE:
573 raise DBusException('Methods may not be called on the reserved '
574 'interface %s' % LOCAL_IFACE)
575 # no need to validate other args - MethodCallMessage ctor will do
577 get_args_opts = {'utf8_strings': utf8_strings,
578 'byte_arrays': byte_arrays}
580 message = MethodCallMessage(destination=bus_name,
581 path=object_path,
582 interface=dbus_interface,
583 method=method)
584 # Add the arguments to the function
585 try:
586 message.append(signature=signature, *args)
587 except Exception, e:
588 logging.basicConfig()
589 _logger.error('Unable to set arguments %r according to '
590 'signature %r: %s: %s',
591 args, signature, e.__class__, e)
592 raise
594 # make a blocking call
595 reply_message = self.send_message_with_reply_and_block(
596 message, timeout)
597 args_list = reply_message.get_args_list(**get_args_opts)
598 if len(args_list) == 0:
599 return None
600 elif len(args_list) == 1:
601 return args_list[0]
602 else:
603 return tuple(args_list)