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
488 if new:
489 by_member[signal_name] = new
490 else:
491 del by_member[signal_name]
492 if not by_member:
493 del by_interface[dbus_interface]
494 if not by_interface:
495 del self._signal_recipients_by_object_path[path]
496 finally:
497 self._signals_lock.release()
498
499 for match in deletions:
500 self._clean_up_signal_match(match)
501
505
535
536 - def call_async(self, bus_name, object_path, dbus_interface, method,
537 signature, args, reply_handler, error_handler,
538 timeout=-1.0, utf8_strings=False, byte_arrays=False,
539 require_main_loop=True):
540 """Call the given method, asynchronously.
541
542 If the reply_handler is None, successful replies will be ignored.
543 If the error_handler is None, failures will be ignored. If both
544 are None, the implementation may request that no reply is sent.
545
546 :Returns: The dbus.lowlevel.PendingCall.
547 :Since: 0.81.0
548 """
549 if object_path == LOCAL_PATH:
550 raise DBusException('Methods may not be called on the reserved '
551 'path %s' % LOCAL_PATH)
552 if dbus_interface == LOCAL_IFACE:
553 raise DBusException('Methods may not be called on the reserved '
554 'interface %s' % LOCAL_IFACE)
555
556
557 get_args_opts = {'utf8_strings': utf8_strings,
558 'byte_arrays': byte_arrays}
559
560 message = MethodCallMessage(destination=bus_name,
561 path=object_path,
562 interface=dbus_interface,
563 method=method)
564
565 try:
566 message.append(signature=signature, *args)
567 except Exception, e:
568 logging.basicConfig()
569 _logger.error('Unable to set arguments %r according to '
570 'signature %r: %s: %s',
571 args, signature, e.__class__, e)
572 raise
573
574 if reply_handler is None and error_handler is None:
575
576 self.send_message(message)
577 return
578
579 if reply_handler is None:
580 reply_handler = _noop
581 if error_handler is None:
582 error_handler = _noop
583
584 def msg_reply_handler(message):
585 if isinstance(message, MethodReturnMessage):
586 reply_handler(*message.get_args_list(**get_args_opts))
587 elif isinstance(message, ErrorMessage):
588 error_handler(DBusException(name=message.get_error_name(),
589 *message.get_args_list()))
590 else:
591 error_handler(TypeError('Unexpected type for reply '
592 'message: %r' % message))
593 return self.send_message_with_reply(message, msg_reply_handler,
594 timeout,
595 require_main_loop=require_main_loop)
596
597 - def call_blocking(self, bus_name, object_path, dbus_interface, method,
598 signature, args, timeout=-1.0, utf8_strings=False,
599 byte_arrays=False):
600 """Call the given method, synchronously.
601 :Since: 0.81.0
602 """
603 if object_path == LOCAL_PATH:
604 raise DBusException('Methods may not be called on the reserved '
605 'path %s' % LOCAL_PATH)
606 if dbus_interface == LOCAL_IFACE:
607 raise DBusException('Methods may not be called on the reserved '
608 'interface %s' % LOCAL_IFACE)
609
610
611 get_args_opts = {'utf8_strings': utf8_strings,
612 'byte_arrays': byte_arrays}
613
614 message = MethodCallMessage(destination=bus_name,
615 path=object_path,
616 interface=dbus_interface,
617 method=method)
618
619 try:
620 message.append(signature=signature, *args)
621 except Exception, e:
622 logging.basicConfig()
623 _logger.error('Unable to set arguments %r according to '
624 'signature %r: %s: %s',
625 args, signature, e.__class__, e)
626 raise
627
628
629 reply_message = self.send_message_with_reply_and_block(
630 message, timeout)
631 args_list = reply_message.get_args_list(**get_args_opts)
632 if len(args_list) == 0:
633 return None
634 elif len(args_list) == 1:
635 return args_list[0]
636 else:
637 return tuple(args_list)
638
640 """Arrange for `callable` to be called with one argument (this
641 Connection object) when the Connection becomes
642 disconnected.
643
644 :Since: 0.83.0
645 """
646 self.__call_on_disconnection.append(callable)
647