1 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
2 # Copyright (C) 2003 David Zeuthen
3 # Copyright (C) 2004 Rob Taylor
4 # Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
6 # Licensed under the Academic Free License version 2.1
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 __all__
= ('BusName', 'Object', 'method', 'signal')
23 __docformat__
= 'restructuredtext'
32 import dummy_thread
as thread
35 from dbus
import SessionBus
36 from dbus
.exceptions
import DBusException
, \
37 NameExistsException
, \
38 UnknownMethodException
39 from dbus
.decorators
import method
40 from dbus
.decorators
import signal
41 from dbus
.proxies
import LOCAL_PATH
44 _logger
= logging
.getLogger('dbus.service')
47 class _VariantSignature(object):
48 """A fake method signature which, when iterated, yields an endless stream
49 of 'v' characters representing variants (handy with zip()).
51 It has no string representation.
58 """Return 'v' whenever called."""
61 class BusName(object):
62 """A base class for exporting your own Named Services across the Bus.
64 When instantiated, objects of this class attempt to claim the given
65 well-known name on the given bus for the current process. The name is
66 released when the BusName object becomes unreferenced.
68 If a well-known name is requested multiple times, multiple references
69 to the same BusName object will be returned.
73 - Assumes that named services are only ever requested using this class -
74 if you request names from the bus directly, confusion may occur.
75 - Does not handle queueing.
77 def __new__(cls
, name
, bus
=None, allow_replacement
=False , replace_existing
=False, do_not_queue
=False):
78 """Constructor, which may either return an existing cached object
83 The well-known name to be advertised
85 A Bus on which this service will be advertised; if None
86 (default) the session bus will be used
87 `allow_replacement` : bool
88 If True, other processes trying to claim the same well-known
89 name will take precedence over this one.
90 `replace_existing` : bool
91 If True, this process can take over the well-known name
92 from other processes already holding it.
94 If True, this service will not be placed in the queue of
95 services waiting for the requested name if another service
98 _dbus_bindings
.validate_bus_name(name
, allow_well_known
=True,
105 # see if this name is already defined, return it if so
106 # FIXME: accessing internals of Bus
107 if name
in bus
._bus
_names
:
108 return bus
._bus
_names
[name
]
110 # otherwise register the name
112 (allow_replacement
and _dbus_bindings
.NAME_FLAG_ALLOW_REPLACEMENT
or 0) |
113 (replace_existing
and _dbus_bindings
.NAME_FLAG_REPLACE_EXISTING
or 0) |
114 (do_not_queue
and _dbus_bindings
.NAME_FLAG_DO_NOT_QUEUE
or 0))
116 retval
= bus
.request_name(name
, name_flags
)
118 # TODO: more intelligent tracking of bus name states?
119 if retval
== _dbus_bindings
.REQUEST_NAME_REPLY_PRIMARY_OWNER
:
121 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_IN_QUEUE
:
122 # queueing can happen by default, maybe we should
123 # track this better or let the user know if they're
126 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_EXISTS
:
127 raise NameExistsException(name
)
128 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_ALREADY_OWNER
:
129 # if this is a shared bus which is being used by someone
130 # else in this process, this can happen legitimately
133 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name
, retval
))
135 # and create the object
136 bus_name
= object.__new
__(cls
)
138 bus_name
._name
= name
140 # cache instance (weak ref only)
141 # FIXME: accessing Bus internals again
142 bus
._bus
_names
[name
] = bus_name
146 # do nothing because this is called whether or not the bus name
147 # object was retrieved from the cache or created new
148 def __init__(self
, *args
, **keywords
):
151 # we can delete the low-level name here because these objects
152 # are guaranteed to exist only once for each bus name
154 self
._bus
.release_name(self
._name
)
158 """Get the Bus this Service is on"""
162 """Get the name of this service"""
166 return '<dbus.service.BusName %s on %r at %#x>' % (self
._name
, self
._bus
, id(self
))
170 def _method_lookup(self
, method_name
, dbus_interface
):
171 """Walks the Python MRO of the given class to find the method to invoke.
173 Returns two methods, the one to call, and the one it inherits from which
174 defines its D-Bus interface name, signature, and attributes.
177 candidate_class
= None
180 # split up the cases when we do and don't have an interface because the
181 # latter is much simpler
183 # search through the class hierarchy in python MRO order
184 for cls
in self
.__class
__.__mro
__:
185 # if we haven't got a candidate class yet, and we find a class with a
186 # suitably named member, save this as a candidate class
187 if (not candidate_class
and method_name
in cls
.__dict
__):
188 if ("_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
189 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__):
190 # however if it is annotated for a different interface
191 # than we are looking for, it cannot be a candidate
192 if cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
:
193 candidate_class
= cls
194 parent_method
= cls
.__dict
__[method_name
]
200 candidate_class
= cls
202 # if we have a candidate class, carry on checking this and all
203 # superclasses for a method annoated as a dbus method
204 # on the correct interface
205 if (candidate_class
and method_name
in cls
.__dict
__
206 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
207 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__
208 and cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
):
209 # the candidate class has a dbus method on the correct interface,
210 # or overrides a method that is, success!
211 parent_method
= cls
.__dict
__[method_name
]
216 # simpler version of above
217 for cls
in self
.__class
__.__mro
__:
218 if (not candidate_class
and method_name
in cls
.__dict
__):
219 candidate_class
= cls
221 if (candidate_class
and method_name
in cls
.__dict
__
222 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__):
223 parent_method
= cls
.__dict
__[method_name
]
228 return (candidate_class
.__dict
__[method_name
], parent_method
)
231 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name
, dbus_interface
))
233 raise UnknownMethodException('%s is not a valid method' % method_name
)
236 def _method_reply_return(connection
, message
, method_name
, signature
, *retval
):
237 reply
= _dbus_bindings
.MethodReturnMessage(message
)
239 reply
.append(signature
=signature
, *retval
)
241 logging
.basicConfig()
242 if signature
is None:
244 signature
= reply
.guess_signature(retval
) + ' (guessed)'
246 _logger
.error('Unable to guess signature for arguments %r: '
247 '%s: %s', retval
, e
.__class
__, e
)
249 _logger
.error('Unable to append %r to message with signature %s: '
250 '%s: %s', retval
, signature
, e
.__class
__, e
)
253 connection
.send_message(reply
)
256 def _method_reply_error(connection
, message
, exception
):
257 name
= getattr(exception
, '_dbus_error_name', None)
261 elif getattr(exception
, '__module__', '') in ('', '__main__'):
262 name
= 'org.freedesktop.DBus.Python.%s' % exception
.__class
__.__name
__
264 name
= 'org.freedesktop.DBus.Python.%s.%s' % (exception
.__module
__, exception
.__class
__.__name
__)
266 contents
= traceback
.format_exc()
267 reply
= _dbus_bindings
.ErrorMessage(message
, name
, contents
)
269 connection
.send_message(reply
)
272 class InterfaceType(type):
273 def __init__(cls
, name
, bases
, dct
):
274 # these attributes are shared between all instances of the Interface
275 # object, so this has to be a dictionary that maps class names to
276 # the per-class introspection/interface data
277 class_table
= getattr(cls
, '_dbus_class_table', {})
278 cls
._dbus
_class
_table
= class_table
279 interface_table
= class_table
[cls
.__module
__ + '.' + name
] = {}
281 # merge all the name -> method tables for all the interfaces
282 # implemented by our base classes into our own
284 base_name
= b
.__module
__ + '.' + b
.__name
__
285 if getattr(b
, '_dbus_class_table', False):
286 for (interface
, method_table
) in class_table
[base_name
].iteritems():
287 our_method_table
= interface_table
.setdefault(interface
, {})
288 our_method_table
.update(method_table
)
290 # add in all the name -> method entries for our own methods/signals
291 for func
in dct
.values():
292 if getattr(func
, '_dbus_interface', False):
293 method_table
= interface_table
.setdefault(func
._dbus
_interface
, {})
294 method_table
[func
.__name
__] = func
296 super(InterfaceType
, cls
).__init
__(name
, bases
, dct
)
298 # methods are different to signals, so we have two functions... :)
299 def _reflect_on_method(cls
, func
):
300 args
= func
._dbus
_args
302 if func
._dbus
_in
_signature
:
303 # convert signature into a tuple so length refers to number of
304 # types, not number of characters. the length is checked by
305 # the decorator to make sure it matches the length of args.
306 in_sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_in
_signature
))
308 # magic iterator which returns as many v's as we need
309 in_sig
= _VariantSignature()
311 if func
._dbus
_out
_signature
:
312 out_sig
= _dbus_bindings
.Signature(func
._dbus
_out
_signature
)
314 # its tempting to default to _dbus_bindings.Signature('v'), but
315 # for methods that return nothing, providing incorrect
316 # introspection data is worse than providing none at all
319 reflection_data
= ' <method name="%s">\n' % (func
.__name
__)
320 for pair
in zip(in_sig
, args
):
321 reflection_data
+= ' <arg direction="in" type="%s" name="%s" />\n' % pair
323 reflection_data
+= ' <arg direction="out" type="%s" />\n' % type
324 reflection_data
+= ' </method>\n'
326 return reflection_data
328 def _reflect_on_signal(cls
, func
):
329 args
= func
._dbus
_args
331 if func
._dbus
_signature
:
332 # convert signature into a tuple so length refers to number of
333 # types, not number of characters
334 sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_signature
))
336 # magic iterator which returns as many v's as we need
337 sig
= _VariantSignature()
339 reflection_data
= ' <signal name="%s">\n' % (func
.__name
__)
340 for pair
in zip(sig
, args
):
341 reflection_data
= reflection_data
+ ' <arg type="%s" name="%s" />\n' % pair
342 reflection_data
= reflection_data
+ ' </signal>\n'
344 return reflection_data
346 class Interface(object):
347 __metaclass__
= InterfaceType
349 #: A unique object used as the value of Object._object_path and
350 #: Object._connection if it's actually in more than one place
353 class Object(Interface
):
354 r
"""A base class for exporting your own Objects across the Bus.
356 Just inherit from Object and mark exported methods with the
357 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
361 class Example(dbus.service.object):
362 def __init__(self, object_path):
363 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
364 self._last_input = None
366 @dbus.service.method(interface='com.example.Sample',
367 in_signature='v', out_signature='s')
368 def StringifyVariant(self, var):
369 self.LastInputChanged(var) # emits the signal
372 @dbus.service.signal(interface='com.example.Sample',
374 def LastInputChanged(self, var):
375 # run just before the signal is actually emitted
376 # just put "pass" if nothing should happen
377 self._last_input = var
379 @dbus.service.method(interface='com.example.Sample',
380 in_signature='', out_signature='v')
381 def GetLastInput(self):
382 return self._last_input
385 #: If True, this object can be made available at more than one object path.
386 #: If True but `SUPPORTS_MULTIPLE_CONNECTIONS` is False, the object may
387 #: handle more than one object path, but they must all be on the same
389 SUPPORTS_MULTIPLE_OBJECT_PATHS
= False
391 #: If True, this object can be made available on more than one connection.
392 #: If True but `SUPPORTS_MULTIPLE_OBJECT_PATHS` is False, the object must
393 #: have the same object path on all its connections.
394 SUPPORTS_MULTIPLE_CONNECTIONS
= False
396 # the signature of __init__ is a bit mad, for backwards compatibility
397 def __init__(self
, conn
=None, object_path
=None, bus_name
=None):
398 """Constructor. Either conn or bus_name is required; object_path
402 `conn` : dbus.connection.Connection or None
403 The connection on which to export this object.
405 If None, use the Bus associated with the given ``bus_name``.
406 If there is no ``bus_name`` either, the object is not
407 initially available on any Connection.
409 For backwards compatibility, if an instance of
410 dbus.service.BusName is passed as the first parameter,
411 this is equivalent to passing its associated Bus as
412 ``conn``, and passing the BusName itself as ``bus_name``.
414 `object_path` : str or None
415 A D-Bus object path at which to make this Object available
416 immediately. If this is not None, a `conn` or `bus_name` must
419 `bus_name` : dbus.service.BusName or None
420 Represents a well-known name claimed by this process. A
421 reference to the BusName object will be held by this
422 Object, preventing the name from being released during this
423 Object's lifetime (unless it's released manually).
425 if object_path
is not None:
426 _dbus_bindings
.validate_object_path(object_path
)
428 if isinstance(conn
, BusName
):
429 # someone's using the old API; don't gratuitously break them
431 conn
= bus_name
.get_bus()
433 if bus_name
is not None:
434 # someone's using the old API but naming arguments, probably
435 conn
= bus_name
.get_bus()
437 #: Either an object path, None or _MANY
438 self
._object
_path
= None
439 #: Either a dbus.connection.Connection, None or _MANY
440 self
._connection
= None
441 #: A list of tuples (Connection, object path, False) where the False
442 #: is for future expansion (to support fallback paths)
444 #: Lock protecting `_locations`, `_connection` and `_object_path`
445 self
._locations
_lock
= thread
.allocate_lock()
447 self
._name
= bus_name
449 if conn
is None and object_path
is not None:
450 raise TypeError('If object_path is given, either conn or bus_name '
452 if conn
is not None and object_path
is not None:
453 self
.add_to_connection(conn
, object_path
)
456 def __dbus_object_path__(self
):
457 """The object-path at which this object is available.
458 Access raises AttributeError if there is no object path, or more than
461 if self
._object
_path
is _MANY
:
462 raise AttributeError('Object %r has more than one object path: '
463 'use Object.locations instead' % self
)
464 elif self
._object
_path
is None:
465 raise AttributeError('Object %r has no object path yet' % self
)
467 return self
._object
_path
470 def connection(self
):
471 """The Connection on which this object is available.
472 Access raises AttributeError if there is no Connection, or more than
475 if self
._connection
is _MANY
:
476 raise AttributeError('Object %r is on more than one Connection: '
477 'use Object.locations instead' % self
)
478 elif self
._connection
is None:
479 raise AttributeError('Object %r has no Connection yet' % self
)
481 return self
._connection
485 """An iterable over tuples representing locations at which this
488 Each tuple has at least two items, but may have more in future
489 versions of dbus-python, so do not rely on their exact length.
490 The first two items are the dbus.connection.Connection and the object
493 return iter(self
._locations
)
495 def add_to_connection(self
, connection
, path
):
496 """Make this object accessible via the given D-Bus connection and
500 `connection` : dbus.connection.Connection
501 Export the object on this connection. If the class attribute
502 SUPPORTS_MULTIPLE_CONNECTIONS is False (default), this object
503 can only be made available on one connection; if the class
504 attribute is set True by a subclass, the object can be made
505 available on more than one connection.
507 `path` : dbus.ObjectPath or other str
508 Place the object at this object path. If the class attribute
509 SUPPORTS_MULTIPLE_OBJECT_PATHS is False (default), this object
510 can only be made available at one object path; if the class
511 attribute is set True by a subclass, the object can be made
512 available with more than one object path.
513 :Raises ValueError: if the object's class attributes do not allow the
514 object to be exported in the desired way.
516 if path
== LOCAL_PATH
:
517 raise ValueError('Objects may not be exported on the reserved '
518 'path %s' % LOCAL_PATH
)
520 self
._locations
_lock
.acquire()
522 if (self
._connection
is not None and
523 self
._connection
is not connection
and
524 not self
.SUPPORTS_MULTIPLE_CONNECTIONS
):
525 raise ValueError('%r is already exported on '
526 'connection %r' % (self
, self
._connection
))
528 if (self
._object
_path
is not None and
529 not self
.SUPPORTS_MULTIPLE_OBJECT_PATHS
and
530 self
._object
_path
!= path
):
531 raise ValueError('%r is already exported at object '
532 'path %s' % (self
, self
._object
_path
))
534 connection
._register
_object
_path
(path
, self
._message
_cb
,
537 if self
._connection
is None:
538 self
._connection
= connection
539 elif self
._connection
is not connection
:
540 self
._connection
= _MANY
542 if self
._object
_path
is None:
543 self
._object
_path
= path
544 elif self
._object
_path
!= path
:
545 self
._object
_path
= _MANY
547 self
._locations
.append((connection
, path
, False))
549 self
._locations
_lock
.release()
551 def remove_from_connection(self
, connection
=None, path
=None):
552 """Make this object inaccessible via the given D-Bus connection
553 and object path. If no connection or path is specified,
554 the object ceases to be accessible via any connection or path.
556 It's not currently possible to export an object on more than one
557 connection or with more than one object-path, but this will be
561 `connection` : dbus.connection.Connection or None
562 Only remove the object from this Connection. If None,
563 remove from all Connections on which it's exported.
564 `path` : dbus.ObjectPath or other str, or None
565 Only remove the object from this object path. If None,
566 remove from all object paths.
568 if the object was not exported on the requested connection
569 or path, or (if both are None) was not exported at all.
571 self
._locations
_lock
.acquire()
573 if self
._object
_path
is None or self
._connection
is None:
574 raise LookupError('%r is not exported' % self
)
576 if connection
is not None or path
is not None:
578 for location
in self
._locations
:
579 if ((connection
is None or location
[0] is connection
) and
580 (path
is None or location
[1] == path
)):
581 dropped
.append(location
)
583 dropped
= self
._locations
587 raise LookupError('%r is not exported at a location matching '
588 '(%r,%r)' % (self
, connection
, path
))
590 for location
in dropped
:
592 location
[0]._unregister
_object
_path
(location
[1])
597 self
._locations
.remove(location
)
601 self
._locations
_lock
.release()
603 def _unregister_cb(self
, connection
):
604 # there's not really enough information to do anything useful here
605 _logger
.info('Unregistering exported object %r from some path '
606 'on %r', self
, connection
)
608 def _message_cb(self
, connection
, message
):
610 # lookup candidate method and parent method
611 method_name
= message
.get_member()
612 interface_name
= message
.get_interface()
613 (candidate_method
, parent_method
) = _method_lookup(self
, method_name
, interface_name
)
615 # set up method call parameters
616 args
= message
.get_args_list(**parent_method
._dbus
_get
_args
_options
)
619 if parent_method
._dbus
_out
_signature
is not None:
620 signature
= _dbus_bindings
.Signature(parent_method
._dbus
_out
_signature
)
624 # set up async callback functions
625 if parent_method
._dbus
_async
_callbacks
:
626 (return_callback
, error_callback
) = parent_method
._dbus
_async
_callbacks
627 keywords
[return_callback
] = lambda *retval
: _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
628 keywords
[error_callback
] = lambda exception
: _method_reply_error(connection
, message
, exception
)
630 # include the sender etc. if desired
631 if parent_method
._dbus
_sender
_keyword
:
632 keywords
[parent_method
._dbus
_sender
_keyword
] = message
.get_sender()
633 if parent_method
._dbus
_path
_keyword
:
634 keywords
[parent_method
._dbus
_path
_keyword
] = message
.get_path()
635 if parent_method
._dbus
_destination
_keyword
:
636 keywords
[parent_method
._dbus
_destination
_keyword
] = message
.get_destination()
637 if parent_method
._dbus
_message
_keyword
:
638 keywords
[parent_method
._dbus
_message
_keyword
] = message
639 if parent_method
._dbus
_connection
_keyword
:
640 keywords
[parent_method
._dbus
_connection
_keyword
] = connection
643 retval
= candidate_method(self
, *args
, **keywords
)
645 # we're done - the method has got callback functions to reply with
646 if parent_method
._dbus
_async
_callbacks
:
649 # otherwise we send the return values in a reply. if we have a
650 # signature, use it to turn the return value into a tuple as
652 if signature
is not None:
653 signature_tuple
= tuple(signature
)
654 # if we have zero or one return values we want make a tuple
655 # for the _method_reply_return function, otherwise we need
656 # to check we're passing it a sequence
657 if len(signature_tuple
) == 0:
661 raise TypeError('%s has an empty output signature but did not return None' %
663 elif len(signature_tuple
) == 1:
666 if operator
.isSequenceType(retval
):
667 # multi-value signature, multi-value return... proceed unchanged
670 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
671 (method_name
, signature
))
673 # no signature, so just turn the return into a tuple and send it as normal
677 elif (isinstance(retval
, tuple)
678 and not isinstance(retval
, _dbus_bindings
.Struct
)):
679 # If the return is a tuple that is not a Struct, we use it
680 # as-is on the assumption that there are multiple return
681 # values - this is the usual Python idiom. (fd.o #10174)
686 _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
687 except Exception, exception
:
689 _method_reply_error(connection
, message
, exception
)
691 @method('org.freedesktop.DBus.Introspectable', in_signature
='', out_signature
='s')
692 def Introspect(self
):
693 """Return a string of XML encoding this object's supported interfaces,
696 reflection_data
= '<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n'
697 reflection_data
+= '<node name="%s">\n' % (self
._object
_path
)
699 interfaces
= self
._dbus
_class
_table
[self
.__class
__.__module
__ + '.' + self
.__class
__.__name
__]
700 for (name
, funcs
) in interfaces
.iteritems():
701 reflection_data
+= ' <interface name="%s">\n' % (name
)
703 for func
in funcs
.values():
704 if getattr(func
, '_dbus_is_method', False):
705 reflection_data
+= self
.__class
__._reflect
_on
_method
(func
)
706 elif getattr(func
, '_dbus_is_signal', False):
707 reflection_data
+= self
.__class
__._reflect
_on
_signal
(func
)
709 reflection_data
+= ' </interface>\n'
711 for name
in self
._connection
.list_exported_child_objects(
713 reflection_data
+= ' <node name="%s"/>\n' % name
715 reflection_data
+= '</node>\n'
717 return reflection_data
720 return '<dbus.service.Object %s on %r at %#x>' % (self
._object
_path
, self
._name
, id(self
))