1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 __all__ = ('BusName', 'Object', 'method', 'signal')
27 __docformat__ = 'restructuredtext'
28
29 import sys
30 import logging
31 import operator
32 import traceback
33 try:
34 import thread
35 except ImportError:
36 import dummy_thread as thread
37
38 import _dbus_bindings
39 from dbus import SessionBus, Signature, Struct, validate_bus_name, \
40 validate_object_path, INTROSPECTABLE_IFACE, ObjectPath
41 from dbus.decorators import method, signal
42 from dbus.exceptions import DBusException, \
43 NameExistsException, \
44 UnknownMethodException
45 from dbus.lowlevel import ErrorMessage, MethodReturnMessage, MethodCallMessage
46 from dbus.proxies import LOCAL_PATH
47
48
49 _logger = logging.getLogger('dbus.service')
53 """A fake method signature which, when iterated, yields an endless stream
54 of 'v' characters representing variants (handy with zip()).
55
56 It has no string representation.
57 """
59 """Return self."""
60 return self
61
63 """Return 'v' whenever called."""
64 return 'v'
65
67 """A base class for exporting your own Named Services across the Bus.
68
69 When instantiated, objects of this class attempt to claim the given
70 well-known name on the given bus for the current process. The name is
71 released when the BusName object becomes unreferenced.
72
73 If a well-known name is requested multiple times, multiple references
74 to the same BusName object will be returned.
75
76 Caveats
77 -------
78 - Assumes that named services are only ever requested using this class -
79 if you request names from the bus directly, confusion may occur.
80 - Does not handle queueing.
81 """
82 - def __new__(cls, name, bus=None, allow_replacement=False , replace_existing=False, do_not_queue=False):
83 """Constructor, which may either return an existing cached object
84 or a new object.
85
86 :Parameters:
87 `name` : str
88 The well-known name to be advertised
89 `bus` : dbus.Bus
90 A Bus on which this service will be advertised.
91
92 Omitting this parameter or setting it to None has been
93 deprecated since version 0.82.1. For backwards compatibility,
94 if this is done, the global shared connection to the session
95 bus will be used.
96
97 `allow_replacement` : bool
98 If True, other processes trying to claim the same well-known
99 name will take precedence over this one.
100 `replace_existing` : bool
101 If True, this process can take over the well-known name
102 from other processes already holding it.
103 `do_not_queue` : bool
104 If True, this service will not be placed in the queue of
105 services waiting for the requested name if another service
106 already holds it.
107 """
108 validate_bus_name(name, allow_well_known=True, allow_unique=False)
109
110
111 if bus is None:
112 import warnings
113 warnings.warn('Omitting the "bus" parameter to '
114 'dbus.service.BusName.__init__ is deprecated',
115 DeprecationWarning, stacklevel=2)
116 bus = SessionBus()
117
118
119
120 if name in bus._bus_names:
121 return bus._bus_names[name]
122
123
124 name_flags = (
125 (allow_replacement and _dbus_bindings.NAME_FLAG_ALLOW_REPLACEMENT or 0) |
126 (replace_existing and _dbus_bindings.NAME_FLAG_REPLACE_EXISTING or 0) |
127 (do_not_queue and _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE or 0))
128
129 retval = bus.request_name(name, name_flags)
130
131
132 if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER:
133 pass
134 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_IN_QUEUE:
135
136
137
138 pass
139 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_EXISTS:
140 raise NameExistsException(name)
141 elif retval == _dbus_bindings.REQUEST_NAME_REPLY_ALREADY_OWNER:
142
143
144 pass
145 else:
146 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name, retval))
147
148
149 bus_name = object.__new__(cls)
150 bus_name._bus = bus
151 bus_name._name = name
152
153
154
155 bus._bus_names[name] = bus_name
156
157 return bus_name
158
159
160
163
164
165
169
171 """Get the Bus this Service is on"""
172 return self._bus
173
175 """Get the name of this service"""
176 return self._name
177
179 return '<dbus.service.BusName %s on %r at %#x>' % (self._name, self._bus, id(self))
180 __str__ = __repr__
181
184 """Walks the Python MRO of the given class to find the method to invoke.
185
186 Returns two methods, the one to call, and the one it inherits from which
187 defines its D-Bus interface name, signature, and attributes.
188 """
189 parent_method = None
190 candidate_class = None
191 successful = False
192
193
194
195 if dbus_interface:
196
197 for cls in self.__class__.__mro__:
198
199
200 if (not candidate_class and method_name in cls.__dict__):
201 if ("_dbus_is_method" in cls.__dict__[method_name].__dict__
202 and "_dbus_interface" in cls.__dict__[method_name].__dict__):
203
204
205 if cls.__dict__[method_name]._dbus_interface == dbus_interface:
206 candidate_class = cls
207 parent_method = cls.__dict__[method_name]
208 successful = True
209 break
210 else:
211 pass
212 else:
213 candidate_class = cls
214
215
216
217
218 if (candidate_class and method_name in cls.__dict__
219 and "_dbus_is_method" in cls.__dict__[method_name].__dict__
220 and "_dbus_interface" in cls.__dict__[method_name].__dict__
221 and cls.__dict__[method_name]._dbus_interface == dbus_interface):
222
223
224 parent_method = cls.__dict__[method_name]
225 successful = True
226 break
227
228 else:
229
230 for cls in self.__class__.__mro__:
231 if (not candidate_class and method_name in cls.__dict__):
232 candidate_class = cls
233
234 if (candidate_class and method_name in cls.__dict__
235 and "_dbus_is_method" in cls.__dict__[method_name].__dict__):
236 parent_method = cls.__dict__[method_name]
237 successful = True
238 break
239
240 if successful:
241 return (candidate_class.__dict__[method_name], parent_method)
242 else:
243 if dbus_interface:
244 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name, dbus_interface))
245 else:
246 raise UnknownMethodException('%s is not a valid method' % method_name)
247
250 reply = MethodReturnMessage(message)
251 try:
252 reply.append(signature=signature, *retval)
253 except Exception, e:
254 logging.basicConfig()
255 if signature is None:
256 try:
257 signature = reply.guess_signature(retval) + ' (guessed)'
258 except Exception, e:
259 _logger.error('Unable to guess signature for arguments %r: '
260 '%s: %s', retval, e.__class__, e)
261 raise
262 _logger.error('Unable to append %r to message with signature %s: '
263 '%s: %s', retval, signature, e.__class__, e)
264 raise
265
266 connection.send_message(reply)
267
270 name = getattr(exception, '_dbus_error_name', None)
271
272 if name is not None:
273 pass
274 elif getattr(exception, '__module__', '') in ('', '__main__'):
275 name = 'org.freedesktop.DBus.Python.%s' % exception.__class__.__name__
276 else:
277 name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__)
278
279 et, ev, etb = sys.exc_info()
280 if isinstance(exception, DBusException) and not exception.include_traceback:
281
282 contents = exception.get_dbus_message()
283 elif ev is exception:
284
285 contents = ''.join(traceback.format_exception(et, ev, etb))
286 else:
287
288
289
290 contents = ''.join(traceback.format_exception_only(exception.__class__,
291 exception))
292 reply = ErrorMessage(message, name, contents)
293
294 connection.send_message(reply)
295
299
300
301
302 class_table = getattr(cls, '_dbus_class_table', {})
303 cls._dbus_class_table = class_table
304 interface_table = class_table[cls.__module__ + '.' + name] = {}
305
306
307
308 for b in bases:
309 base_name = b.__module__ + '.' + b.__name__
310 if getattr(b, '_dbus_class_table', False):
311 for (interface, method_table) in class_table[base_name].iteritems():
312 our_method_table = interface_table.setdefault(interface, {})
313 our_method_table.update(method_table)
314
315
316 for func in dct.values():
317 if getattr(func, '_dbus_interface', False):
318 method_table = interface_table.setdefault(func._dbus_interface, {})
319 method_table[func.__name__] = func
320
321 super(InterfaceType, cls).__init__(name, bases, dct)
322
323
325 args = func._dbus_args
326
327 if func._dbus_in_signature:
328
329
330
331 in_sig = tuple(Signature(func._dbus_in_signature))
332 else:
333
334 in_sig = _VariantSignature()
335
336 if func._dbus_out_signature:
337 out_sig = Signature(func._dbus_out_signature)
338 else:
339
340
341
342 out_sig = []
343
344 reflection_data = ' <method name="%s">\n' % (func.__name__)
345 for pair in zip(in_sig, args):
346 reflection_data += ' <arg direction="in" type="%s" name="%s" />\n' % pair
347 for type in out_sig:
348 reflection_data += ' <arg direction="out" type="%s" />\n' % type
349 reflection_data += ' </method>\n'
350
351 return reflection_data
352
354 args = func._dbus_args
355
356 if func._dbus_signature:
357
358
359 sig = tuple(Signature(func._dbus_signature))
360 else:
361
362 sig = _VariantSignature()
363
364 reflection_data = ' <signal name="%s">\n' % (func.__name__)
365 for pair in zip(sig, args):
366 reflection_data = reflection_data + ' <arg type="%s" name="%s" />\n' % pair
367 reflection_data = reflection_data + ' </signal>\n'
368
369 return reflection_data
370
373
374
375
376 _MANY = object()
377
378 -class Object(Interface):
379 r"""A base class for exporting your own Objects across the Bus.
380
381 Just inherit from Object and mark exported methods with the
382 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
383
384 Example::
385
386 class Example(dbus.service.object):
387 def __init__(self, object_path):
388 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
389 self._last_input = None
390
391 @dbus.service.method(interface='com.example.Sample',
392 in_signature='v', out_signature='s')
393 def StringifyVariant(self, var):
394 self.LastInputChanged(var) # emits the signal
395 return str(var)
396
397 @dbus.service.signal(interface='com.example.Sample',
398 signature='v')
399 def LastInputChanged(self, var):
400 # run just before the signal is actually emitted
401 # just put "pass" if nothing should happen
402 self._last_input = var
403
404 @dbus.service.method(interface='com.example.Sample',
405 in_signature='', out_signature='v')
406 def GetLastInput(self):
407 return self._last_input
408 """
409
410
411
412
413
414 SUPPORTS_MULTIPLE_OBJECT_PATHS = False
415
416
417
418
419 SUPPORTS_MULTIPLE_CONNECTIONS = False
420
421 - def __init__(self, conn=None, object_path=None, bus_name=None):
422 """Constructor. Either conn or bus_name is required; object_path
423 is also required.
424
425 :Parameters:
426 `conn` : dbus.connection.Connection or None
427 The connection on which to export this object.
428
429 If None, use the Bus associated with the given ``bus_name``.
430 If there is no ``bus_name`` either, the object is not
431 initially available on any Connection.
432
433 For backwards compatibility, if an instance of
434 dbus.service.BusName is passed as the first parameter,
435 this is equivalent to passing its associated Bus as
436 ``conn``, and passing the BusName itself as ``bus_name``.
437
438 `object_path` : str or None
439 A D-Bus object path at which to make this Object available
440 immediately. If this is not None, a `conn` or `bus_name` must
441 also be provided.
442
443 `bus_name` : dbus.service.BusName or None
444 Represents a well-known name claimed by this process. A
445 reference to the BusName object will be held by this
446 Object, preventing the name from being released during this
447 Object's lifetime (unless it's released manually).
448 """
449 if object_path is not None:
450 validate_object_path(object_path)
451
452 if isinstance(conn, BusName):
453
454 bus_name = conn
455 conn = bus_name.get_bus()
456 elif conn is None:
457 if bus_name is not None:
458
459 conn = bus_name.get_bus()
460
461
462 self._object_path = None
463
464 self._connection = None
465
466
467 self._locations = []
468
469 self._locations_lock = thread.allocate_lock()
470
471
472 self._fallback = False
473
474 self._name = bus_name
475
476 if conn is None and object_path is not None:
477 raise TypeError('If object_path is given, either conn or bus_name '
478 'is required')
479 if conn is not None and object_path is not None:
480 self.add_to_connection(conn, object_path)
481
482 @property
484 """The object-path at which this object is available.
485 Access raises AttributeError if there is no object path, or more than
486 one object path.
487
488 Changed in 0.82.0: AttributeError can be raised.
489 """
490 if self._object_path is _MANY:
491 raise AttributeError('Object %r has more than one object path: '
492 'use Object.locations instead' % self)
493 elif self._object_path is None:
494 raise AttributeError('Object %r has no object path yet' % self)
495 else:
496 return self._object_path
497
498 @property
500 """The Connection on which this object is available.
501 Access raises AttributeError if there is no Connection, or more than
502 one Connection.
503
504 Changed in 0.82.0: AttributeError can be raised.
505 """
506 if self._connection is _MANY:
507 raise AttributeError('Object %r is on more than one Connection: '
508 'use Object.locations instead' % self)
509 elif self._connection is None:
510 raise AttributeError('Object %r has no Connection yet' % self)
511 else:
512 return self._connection
513
514 @property
516 """An iterable over tuples representing locations at which this
517 object is available.
518
519 Each tuple has at least two items, but may have more in future
520 versions of dbus-python, so do not rely on their exact length.
521 The first two items are the dbus.connection.Connection and the object
522 path.
523
524 :Since: 0.82.0
525 """
526 return iter(self._locations)
527
529 """Make this object accessible via the given D-Bus connection and
530 object path.
531
532 :Parameters:
533 `connection` : dbus.connection.Connection
534 Export the object on this connection. If the class attribute
535 SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
536 can only be made available on one connection; if the class
537 attribute is set True by a subclass, the object can be made
538 available on more than one connection.
539
540 `path` : dbus.ObjectPath or other str
541 Place the object at this object path. If the class attribute
542 SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
543 can only be made available at one object path; if the class
544 attribute is set True by a subclass, the object can be made
545 available with more than one object path.
546
547 :Raises ValueError: if the object's class attributes do not allow the
548 object to be exported in the desired way.
549 :Since: 0.82.0
550 """
551 if path == LOCAL_PATH:
552 raise ValueError('Objects may not be exported on the reserved '
553 'path %s' % LOCAL_PATH)
554
555 self._locations_lock.acquire()
556 try:
557 if (self._connection is not None and
558 self._connection is not connection and
559 not self.SUPPORTS_MULTIPLE_CONNECTIONS):
560 raise ValueError('%r is already exported on '
561 'connection %r' % (self, self._connection))
562
563 if (self._object_path is not None and
564 not self.SUPPORTS_MULTIPLE_OBJECT_PATHS and
565 self._object_path != path):
566 raise ValueError('%r is already exported at object '
567 'path %s' % (self, self._object_path))
568
569 connection._register_object_path(path, self._message_cb,
570 self._unregister_cb,
571 self._fallback)
572
573 if self._connection is None:
574 self._connection = connection
575 elif self._connection is not connection:
576 self._connection = _MANY
577
578 if self._object_path is None:
579 self._object_path = path
580 elif self._object_path != path:
581 self._object_path = _MANY
582
583 self._locations.append((connection, path, self._fallback))
584 finally:
585 self._locations_lock.release()
586
588 """Make this object inaccessible via the given D-Bus connection
589 and object path. If no connection or path is specified,
590 the object ceases to be accessible via any connection or path.
591
592 :Parameters:
593 `connection` : dbus.connection.Connection or None
594 Only remove the object from this Connection. If None,
595 remove from all Connections on which it's exported.
596 `path` : dbus.ObjectPath or other str, or None
597 Only remove the object from this object path. If None,
598 remove from all object paths.
599 :Raises LookupError:
600 if the object was not exported on the requested connection
601 or path, or (if both are None) was not exported at all.
602 :Since: 0.81.1
603 """
604 self._locations_lock.acquire()
605 try:
606 if self._object_path is None or self._connection is None:
607 raise LookupError('%r is not exported' % self)
608
609 if connection is not None or path is not None:
610 dropped = []
611 for location in self._locations:
612 if ((connection is None or location[0] is connection) and
613 (path is None or location[1] == path)):
614 dropped.append(location)
615 else:
616 dropped = self._locations
617 self._locations = []
618
619 if not dropped:
620 raise LookupError('%r is not exported at a location matching '
621 '(%r,%r)' % (self, connection, path))
622
623 for location in dropped:
624 try:
625 location[0]._unregister_object_path(location[1])
626 except LookupError:
627 pass
628 if self._locations:
629 try:
630 self._locations.remove(location)
631 except ValueError:
632 pass
633 finally:
634 self._locations_lock.release()
635
637
638 _logger.info('Unregistering exported object %r from some path '
639 'on %r', self, connection)
640
642 if not isinstance(message, MethodCallMessage):
643 return
644
645 try:
646
647 method_name = message.get_member()
648 interface_name = message.get_interface()
649 (candidate_method, parent_method) = _method_lookup(self, method_name, interface_name)
650
651
652 args = message.get_args_list(**parent_method._dbus_get_args_options)
653 keywords = {}
654
655 if parent_method._dbus_out_signature is not None:
656 signature = Signature(parent_method._dbus_out_signature)
657 else:
658 signature = None
659
660
661 if parent_method._dbus_async_callbacks:
662 (return_callback, error_callback) = parent_method._dbus_async_callbacks
663 keywords[return_callback] = lambda *retval: _method_reply_return(connection, message, method_name, signature, *retval)
664 keywords[error_callback] = lambda exception: _method_reply_error(connection, message, exception)
665
666
667 if parent_method._dbus_sender_keyword:
668 keywords[parent_method._dbus_sender_keyword] = message.get_sender()
669 if parent_method._dbus_path_keyword:
670 keywords[parent_method._dbus_path_keyword] = message.get_path()
671 if parent_method._dbus_rel_path_keyword:
672 path = message.get_path()
673 rel_path = path
674 for exp in self._locations:
675
676
677
678
679 if exp[0] is connection:
680 if path == exp[1]:
681 rel_path = '/'
682 break
683 if exp[1] == '/':
684
685 continue
686 if path.startswith(exp[1] + '/'):
687
688 suffix = path[len(exp[1]):]
689 if len(suffix) < len(rel_path):
690 rel_path = suffix
691 rel_path = ObjectPath(rel_path)
692 keywords[parent_method._dbus_rel_path_keyword] = rel_path
693
694 if parent_method._dbus_destination_keyword:
695 keywords[parent_method._dbus_destination_keyword] = message.get_destination()
696 if parent_method._dbus_message_keyword:
697 keywords[parent_method._dbus_message_keyword] = message
698 if parent_method._dbus_connection_keyword:
699 keywords[parent_method._dbus_connection_keyword] = connection
700
701
702 retval = candidate_method(self, *args, **keywords)
703
704
705 if parent_method._dbus_async_callbacks:
706 return
707
708
709
710
711 if signature is not None:
712 signature_tuple = tuple(signature)
713
714
715
716 if len(signature_tuple) == 0:
717 if retval == None:
718 retval = ()
719 else:
720 raise TypeError('%s has an empty output signature but did not return None' %
721 method_name)
722 elif len(signature_tuple) == 1:
723 retval = (retval,)
724 else:
725 if operator.isSequenceType(retval):
726
727 pass
728 else:
729 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
730 (method_name, signature))
731
732
733 else:
734 if retval is None:
735 retval = ()
736 elif (isinstance(retval, tuple)
737 and not isinstance(retval, Struct)):
738
739
740
741 pass
742 else:
743 retval = (retval,)
744
745 _method_reply_return(connection, message, method_name, signature, *retval)
746 except Exception, exception:
747
748 _method_reply_error(connection, message, exception)
749
750 @method(INTROSPECTABLE_IFACE, in_signature='', out_signature='s',
751 path_keyword='object_path', connection_keyword='connection')
753 """Return a string of XML encoding this object's supported interfaces,
754 methods and signals.
755 """
756 reflection_data = _dbus_bindings.DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
757 reflection_data += '<node name="%s">\n' % object_path
758
759 interfaces = self._dbus_class_table[self.__class__.__module__ + '.' + self.__class__.__name__]
760 for (name, funcs) in interfaces.iteritems():
761 reflection_data += ' <interface name="%s">\n' % (name)
762
763 for func in funcs.values():
764 if getattr(func, '_dbus_is_method', False):
765 reflection_data += self.__class__._reflect_on_method(func)
766 elif getattr(func, '_dbus_is_signal', False):
767 reflection_data += self.__class__._reflect_on_signal(func)
768
769 reflection_data += ' </interface>\n'
770
771 for name in connection.list_exported_child_objects(object_path):
772 reflection_data += ' <node name="%s"/>\n' % name
773
774 reflection_data += '</node>\n'
775
776 return reflection_data
777
779 where = ''
780 if (self._object_path is not _MANY
781 and self._object_path is not None):
782 where = ' at %s' % self._object_path
783 return '<%s.%s%s at %#x>' % (self.__class__.__module__,
784 self.__class__.__name__, where,
785 id(self))
786 __str__ = __repr__
787
789 """An object that implements an entire subtree of the object-path
790 tree.
791
792 :Since: 0.82.0
793 """
794
795 SUPPORTS_MULTIPLE_OBJECT_PATHS = True
796
797 - def __init__(self, conn=None, object_path=None):
798 """Constructor.
799
800 Note that the superclass' ``bus_name`` __init__ argument is not
801 supported here.
802
803 :Parameters:
804 `conn` : dbus.connection.Connection or None
805 The connection on which to export this object. If this is not
806 None, an `object_path` must also be provided.
807
808 If None, the object is not initially available on any
809 Connection.
810
811 `object_path` : str or None
812 A D-Bus object path at which to make this Object available
813 immediately. If this is not None, a `conn` must also be
814 provided.
815
816 This object will implements all object-paths in the subtree
817 starting at this object-path, except where a more specific
818 object has been added.
819 """
820 super(FallbackObject, self).__init__()
821 self._fallback = True
822
823 if conn is None:
824 if object_path is not None:
825 raise TypeError('If object_path is given, conn is required')
826 elif object_path is None:
827 raise TypeError('If conn is given, object_path is required')
828 else:
829 self.add_to_connection(conn, object_path)
830