1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 __all__ = ('Connection', 'SignalMatch')
24 __docformat__ = 'reStructuredText'
25
26 import logging
27 try:
28 import thread
29 except ImportError:
30 import dummy_thread as thread
31 import weakref
32
33 from _dbus_bindings import Connection as _Connection, \
34 LOCAL_PATH, LOCAL_IFACE, \
35 validate_interface_name, validate_member_name,\
36 validate_bus_name, validate_object_path,\
37 validate_error_name, \
38 UTF8String
39 from dbus.exceptions import DBusException
40 from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
41 MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
42 from dbus.proxies import ProxyObject
43
44
45 _logger = logging.getLogger('dbus.connection')
46
47
48 -def _noop(*args, **kwargs):
50
51
53 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
54 '_path', '_handler', '_args_match', '_rule',
55 '_utf8_strings', '_byte_arrays', '_conn_weakref',
56 '_destination_keyword', '_interface_keyword',
57 '_message_keyword', '_member_keyword',
58 '_sender_keyword', '_path_keyword', '_int_args_match')
59
60 - def __init__(self, conn, sender, object_path, dbus_interface,
61 member, handler, utf8_strings=False, byte_arrays=False,
62 sender_keyword=None, path_keyword=None,
63 interface_keyword=None, member_keyword=None,
64 message_keyword=None, destination_keyword=None,
65 **kwargs):
114
116 """SignalMatch objects are compared by identity."""
117 return hash(id(self))
118
120 """SignalMatch objects are compared by identity."""
121 return self is other
122
124 """SignalMatch objects are compared by identity."""
125 return self is not other
126
127 sender = property(lambda self: self._sender)
128
147
149 return ('<%s at %x "%s" on conn %r>'
150 % (self.__class__, id(self), self._rule, self._conn_weakref()))
151
154
155 - def matches_removal_spec(self, sender, object_path,
156 dbus_interface, member, handler, **kwargs):
170
172 args = None
173
174
175 if self._sender_name_owner not in (None, message.get_sender()):
176 return False
177 if self._int_args_match is not None:
178
179 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
180 for index, value in self._int_args_match.iteritems():
181 if (index >= len(args)
182 or not isinstance(args[index], UTF8String)
183 or args[index] != value):
184 return False
185
186
187 if self._member not in (None, message.get_member()):
188 return False
189 if self._interface not in (None, message.get_interface()):
190 return False
191 if self._path not in (None, message.get_path()):
192 return False
193
194 try:
195
196
197
198 if args is None or not self._utf8_strings or not self._byte_arrays:
199 args = message.get_args_list(utf8_strings=self._utf8_strings,
200 byte_arrays=self._byte_arrays)
201 kwargs = {}
202 if self._sender_keyword is not None:
203 kwargs[self._sender_keyword] = message.get_sender()
204 if self._destination_keyword is not None:
205 kwargs[self._destination_keyword] = message.get_destination()
206 if self._path_keyword is not None:
207 kwargs[self._path_keyword] = message.get_path()
208 if self._member_keyword is not None:
209 kwargs[self._member_keyword] = message.get_member()
210 if self._interface_keyword is not None:
211 kwargs[self._interface_keyword] = message.get_interface()
212 if self._message_keyword is not None:
213 kwargs[self._message_keyword] = message
214 self._handler(*args, **kwargs)
215 except:
216
217 logging.basicConfig()
218 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
219
220 return True
221
230
231
233 """A connection to another application. In this base class there is
234 assumed to be no bus daemon.
235
236 :Since: 0.81.0
237 """
238
239 ProxyObjectClass = ProxyObject
240
242 super(Connection, self).__init__(*args, **kwargs)
243
244
245
246 if not hasattr(self, '_dbus_Connection_initialized'):
247 self._dbus_Connection_initialized = 1
248
249 self.__call_on_disconnection = []
250
251 self._signal_recipients_by_object_path = {}
252 """Map from object path to dict mapping dbus_interface to dict
253 mapping member to list of SignalMatch objects."""
254
255 self._signals_lock = thread.allocate_lock()
256 """Lock used to protect signal data structures"""
257
258 self.add_message_filter(self.__class__._signal_func)
259
261 """Return the unique name for the given bus name, activating it
262 if necessary and possible.
263
264 If the name is already unique or this connection is not to a
265 bus daemon, just return it.
266
267 :Returns: a bus name. If the given `bus_name` exists, the returned
268 name identifies its current owner; otherwise the returned name
269 does not exist.
270 :Raises DBusException: if the implementation has failed
271 to activate the given bus name.
272 :Since: 0.81.0
273 """
274 return bus_name
275
276 - def get_object(self, bus_name=None, object_path=None, introspect=True,
277 **kwargs):
278 """Return a local proxy for the given remote object.
279
280 Method calls on the proxy are translated into method calls on the
281 remote object.
282
283 :Parameters:
284 `bus_name` : str
285 A bus name (either the unique name or a well-known name)
286 of the application owning the object. The keyword argument
287 named_service is a deprecated alias for this.
288 `object_path` : str
289 The object path of the desired object
290 `introspect` : bool
291 If true (default), attempt to introspect the remote
292 object to find out supported methods and their signatures
293
294 :Returns: a `dbus.proxies.ProxyObject`
295 """
296 named_service = kwargs.pop('named_service', None)
297 if named_service is not None:
298 if bus_name is not None:
299 raise TypeError('bus_name and named_service cannot both '
300 'be specified')
301 from warnings import warn
302 warn('Passing the named_service parameter to get_object by name '
303 'is deprecated: please use positional parameters',
304 DeprecationWarning, stacklevel=2)
305 bus_name = named_service
306 if kwargs:
307 raise TypeError('get_object does not take these keyword '
308 'arguments: %s' % ', '.join(kwargs.iterkeys()))
309
310 return self.ProxyObjectClass(self, bus_name, object_path,
311 introspect=introspect)
312
313 - def add_signal_receiver(self, handler_function,
314 signal_name=None,
315 dbus_interface=None,
316 bus_name=None,
317 path=None,
318 **keywords):
319 """Arrange for the given function to be called when a signal matching
320 the parameters is received.
321
322 :Parameters:
323 `handler_function` : callable
324 The function to be called. Its positional arguments will
325 be the arguments of the signal. By default it will receive
326 no keyword arguments, but see the description of
327 the optional keyword arguments below.
328 `signal_name` : str
329 The signal name; None (the default) matches all names
330 `dbus_interface` : str
331 The D-Bus interface name with which to qualify the signal;
332 None (the default) matches all interface names
333 `bus_name` : str
334 A bus name for the sender, which will be resolved to a
335 unique name if it is not already; None (the default) matches
336 any sender.
337 `path` : str
338 The object path of the object which must have emitted the
339 signal; None (the default) matches any object path
340 :Keywords:
341 `utf8_strings` : bool
342 If True, the handler function will receive any string
343 arguments as dbus.UTF8String objects (a subclass of str
344 guaranteed to be UTF-8). If False (default) it will receive
345 any string arguments as dbus.String objects (a subclass of
346 unicode).
347 `byte_arrays` : bool
348 If True, the handler function will receive any byte-array
349 arguments as dbus.ByteArray objects (a subclass of str).
350 If False (default) it will receive any byte-array
351 arguments as a dbus.Array of dbus.Byte (subclasses of:
352 a list of ints).
353 `sender_keyword` : str
354 If not None (the default), the handler function will receive
355 the unique name of the sending endpoint as a keyword
356 argument with this name.
357 `destination_keyword` : str
358 If not None (the default), the handler function will receive
359 the bus name of the destination (or None if the signal is a
360 broadcast, as is usual) as a keyword argument with this name.
361 `interface_keyword` : str
362 If not None (the default), the handler function will receive
363 the signal interface as a keyword argument with this name.
364 `member_keyword` : str
365 If not None (the default), the handler function will receive
366 the signal name as a keyword argument with this name.
367 `path_keyword` : str
368 If not None (the default), the handler function will receive
369 the object-path of the sending object as a keyword argument
370 with this name.
371 `message_keyword` : str
372 If not None (the default), the handler function will receive
373 the `dbus.lowlevel.SignalMessage` as a keyword argument with
374 this name.
375 `arg...` : unicode or UTF-8 str
376 If there are additional keyword parameters of the form
377 ``arg``\ *n*, match only signals where the *n*\ th argument
378 is the value given for that keyword parameter. As of this
379 time only string arguments can be matched (in particular,
380 object paths and signatures can't).
381 `named_service` : str
382 A deprecated alias for `bus_name`.
383 """
384 self._require_main_loop()
385
386 named_service = keywords.pop('named_service', None)
387 if named_service is not None:
388 if bus_name is not None:
389 raise TypeError('bus_name and named_service cannot both be '
390 'specified')
391 bus_name = named_service
392 from warnings import warn
393 warn('Passing the named_service parameter to add_signal_receiver '
394 'by name is deprecated: please use positional parameters',
395 DeprecationWarning, stacklevel=2)
396
397 match = SignalMatch(self, bus_name, path, dbus_interface,
398 signal_name, handler_function, **keywords)
399
400 self._signals_lock.acquire()
401 try:
402 by_interface = self._signal_recipients_by_object_path.setdefault(
403 path, {})
404 by_member = by_interface.setdefault(dbus_interface, {})
405 matches = by_member.setdefault(signal_name, [])
406
407 matches.append(match)
408 finally:
409 self._signals_lock.release()
410
411 return match
412
414 if path is not None:
415 path_keys = (None, path)
416 else:
417 path_keys = (None,)
418 if dbus_interface is not None:
419 interface_keys = (None, dbus_interface)
420 else:
421 interface_keys = (None,)
422 if member is not None:
423 member_keys = (None, member)
424 else:
425 member_keys = (None,)
426
427 for path in path_keys:
428 by_interface = self._signal_recipients_by_object_path.get(path,
429 None)
430 if by_interface is None:
431 continue
432 for dbus_interface in interface_keys:
433 by_member = by_interface.get(dbus_interface, None)
434 if by_member is None:
435 continue
436 for member in member_keys:
437 matches = by_member.get(member, None)
438 if matches is None:
439 continue
440 for m in matches:
441 yield m
442
443 - def remove_signal_receiver(self, handler_or_match,
444 signal_name=None,
445 dbus_interface=None,
446 bus_name=None,
447 path=None,
448 **keywords):
449 named_service = keywords.pop('named_service', None)
450 if named_service is not None:
451 if bus_name is not None:
452 raise TypeError('bus_name and named_service cannot both be '
453 'specified')
454 bus_name = named_service
455 from warnings import warn
456 warn('Passing the named_service parameter to '
457 'remove_signal_receiver by name is deprecated: please use '
458 'positional parameters',
459 DeprecationWarning, stacklevel=2)
460
461 new = []
462 deletions = []
463 self._signals_lock.acquire()
464 try:
465 by_interface = self._signal_recipients_by_object_path.get(path,
466 None)
467 if by_interface is None:
468 return
469 by_member = by_interface.get(dbus_interface, None)
470 if by_member is None:
471 return
472 matches = by_member.get(signal_name, None)
473 if matches is None:
474 return
475
476 for match in matches:
477 if (handler_or_match is match
478 or match.matches_removal_spec(bus_name,
479 path,
480 dbus_interface,
481 signal_name,
482 handler_or_match,
483 **keywords)):
484 deletions.append(match)
485 else:
486 new.append(match)
487 by_member[signal_name] = new
488 finally:
489 self._signals_lock.release()
490
491 for match in deletions:
492 self._clean_up_signal_match(match)
493
497
527
528 - def call_async(self, bus_name, object_path, dbus_interface, method,
529 signature, args, reply_handler, error_handler,
530 timeout=-1.0, utf8_strings=False, byte_arrays=False,
531 require_main_loop=True):
532 """Call the given method, asynchronously.
533
534 If the reply_handler is None, successful replies will be ignored.
535 If the error_handler is None, failures will be ignored. If both
536 are None, the implementation may request that no reply is sent.
537
538 :Returns: The dbus.lowlevel.PendingCall.
539 :Since: 0.81.0
540 """
541 if object_path == LOCAL_PATH:
542 raise DBusException('Methods may not be called on the reserved '
543 'path %s' % LOCAL_PATH)
544 if dbus_interface == LOCAL_IFACE:
545 raise DBusException('Methods may not be called on the reserved '
546 'interface %s' % LOCAL_IFACE)
547
548
549 get_args_opts = {'utf8_strings': utf8_strings,
550 'byte_arrays': byte_arrays}
551
552 message = MethodCallMessage(destination=bus_name,
553 path=object_path,
554 interface=dbus_interface,
555 method=method)
556
557 try:
558 message.append(signature=signature, *args)
559 except Exception, e:
560 logging.basicConfig()
561 _logger.error('Unable to set arguments %r according to '
562 'signature %r: %s: %s',
563 args, signature, e.__class__, e)
564 raise
565
566 if reply_handler is None and error_handler is None:
567
568 self.send_message(message)
569 return
570
571 if reply_handler is None:
572 reply_handler = _noop
573 if error_handler is None:
574 error_handler = _noop
575
576 def msg_reply_handler(message):
577 if isinstance(message, MethodReturnMessage):
578 reply_handler(*message.get_args_list(**get_args_opts))
579 elif isinstance(message, ErrorMessage):
580 error_handler(DBusException(name=message.get_error_name(),
581 *message.get_args_list()))
582 else:
583 error_handler(TypeError('Unexpected type for reply '
584 'message: %r' % message))
585 return self.send_message_with_reply(message, msg_reply_handler,
586 timeout,
587 require_main_loop=require_main_loop)
588
589 - def call_blocking(self, bus_name, object_path, dbus_interface, method,
590 signature, args, timeout=-1.0, utf8_strings=False,
591 byte_arrays=False):
592 """Call the given method, synchronously.
593 :Since: 0.81.0
594 """
595 if object_path == LOCAL_PATH:
596 raise DBusException('Methods may not be called on the reserved '
597 'path %s' % LOCAL_PATH)
598 if dbus_interface == LOCAL_IFACE:
599 raise DBusException('Methods may not be called on the reserved '
600 'interface %s' % LOCAL_IFACE)
601
602
603 get_args_opts = {'utf8_strings': utf8_strings,
604 'byte_arrays': byte_arrays}
605
606 message = MethodCallMessage(destination=bus_name,
607 path=object_path,
608 interface=dbus_interface,
609 method=method)
610
611 try:
612 message.append(signature=signature, *args)
613 except Exception, e:
614 logging.basicConfig()
615 _logger.error('Unable to set arguments %r according to '
616 'signature %r: %s: %s',
617 args, signature, e.__class__, e)
618 raise
619
620
621 reply_message = self.send_message_with_reply_and_block(
622 message, timeout)
623 args_list = reply_message.get_args_list(**get_args_opts)
624 if len(args_list) == 0:
625 return None
626 elif len(args_list) == 1:
627 return args_list[0]
628 else:
629 return tuple(args_list)
630
632 """Arrange for `callable` to be called with one argument (this
633 Connection object) when the Connection becomes
634 disconnected.
635
636 :Since: 0.83.0
637 """
638 self.__call_on_disconnection.append(callable)
639