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