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'
31 import dbus
._dbus
as _dbus
32 from dbus
.exceptions
import NameExistsException
33 from dbus
.exceptions
import UnknownMethodException
34 from dbus
.decorators
import method
35 from dbus
.decorators
import signal
36 from dbus
.proxies
import LOCAL_PATH
39 _logger
= logging
.getLogger('dbus.service')
42 class _VariantSignature(object):
43 """A fake method signature which, when iterated, yields an endless stream
44 of 'v' characters representing variants (handy with zip()).
46 It has no string representation.
53 """Return 'v' whenever called."""
56 class BusName(object):
57 """A base class for exporting your own Named Services across the Bus.
59 When instantiated, objects of this class attempt to claim the given
60 well-known name on the given bus for the current process. The name is
61 released when the BusName object becomes unreferenced.
63 If a well-known name is requested multiple times, multiple references
64 to the same BusName object will be returned.
68 - Assumes that named services are only ever requested using this class -
69 if you request names from the bus directly, confusion may occur.
70 - Does not handle queueing.
72 def __new__(cls
, name
, bus
=None, allow_replacement
=False , replace_existing
=False, do_not_queue
=False):
73 """Constructor, which may either return an existing cached object
78 The well-known name to be advertised
80 A Bus on which this service will be advertised; if None
81 (default) the session bus will be used
82 `allow_replacement` : bool
83 If True, other processes trying to claim the same well-known
84 name will take precedence over this one.
85 `replace_existing` : bool
86 If True, this process can take over the well-known name
87 from other processes already holding it.
89 If True, this service will not be placed in the queue of
90 services waiting for the requested name if another service
93 _dbus_bindings
.validate_bus_name(name
, allow_well_known
=True,
100 # see if this name is already defined, return it if so
101 # FIXME: accessing internals of Bus
102 if name
in bus
._bus
_names
:
103 return bus
._bus
_names
[name
]
105 # otherwise register the name
107 (allow_replacement
and _dbus_bindings
.NAME_FLAG_ALLOW_REPLACEMENT
or 0) |
108 (replace_existing
and _dbus_bindings
.NAME_FLAG_REPLACE_EXISTING
or 0) |
109 (do_not_queue
and _dbus_bindings
.NAME_FLAG_DO_NOT_QUEUE
or 0))
111 retval
= bus
.request_name(name
, name_flags
)
113 # TODO: more intelligent tracking of bus name states?
114 if retval
== _dbus_bindings
.REQUEST_NAME_REPLY_PRIMARY_OWNER
:
116 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_IN_QUEUE
:
117 # queueing can happen by default, maybe we should
118 # track this better or let the user know if they're
121 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_EXISTS
:
122 raise NameExistsException(name
)
123 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_ALREADY_OWNER
:
124 # if this is a shared bus which is being used by someone
125 # else in this process, this can happen legitimately
128 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name
, retval
))
130 # and create the object
131 bus_name
= object.__new
__(cls
)
133 bus_name
._name
= name
135 # cache instance (weak ref only)
136 # FIXME: accessing Bus internals again
137 bus
._bus
_names
[name
] = bus_name
141 # do nothing because this is called whether or not the bus name
142 # object was retrieved from the cache or created new
143 def __init__(self
, *args
, **keywords
):
146 # we can delete the low-level name here because these objects
147 # are guaranteed to exist only once for each bus name
149 self
._bus
.release_name(self
._name
)
153 """Get the Bus this Service is on"""
157 """Get the name of this service"""
161 return '<dbus.service.BusName %s on %r at %#x>' % (self
._name
, self
._bus
, id(self
))
165 def _method_lookup(self
, method_name
, dbus_interface
):
166 """Walks the Python MRO of the given class to find the method to invoke.
168 Returns two methods, the one to call, and the one it inherits from which
169 defines its D-Bus interface name, signature, and attributes.
172 candidate_class
= None
175 # split up the cases when we do and don't have an interface because the
176 # latter is much simpler
178 # search through the class hierarchy in python MRO order
179 for cls
in self
.__class
__.__mro
__:
180 # if we haven't got a candidate class yet, and we find a class with a
181 # suitably named member, save this as a candidate class
182 if (not candidate_class
and method_name
in cls
.__dict
__):
183 if ("_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
184 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__):
185 # however if it is annotated for a different interface
186 # than we are looking for, it cannot be a candidate
187 if cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
:
188 candidate_class
= cls
189 parent_method
= cls
.__dict
__[method_name
]
195 candidate_class
= cls
197 # if we have a candidate class, carry on checking this and all
198 # superclasses for a method annoated as a dbus method
199 # on the correct interface
200 if (candidate_class
and method_name
in cls
.__dict
__
201 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
202 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__
203 and cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
):
204 # the candidate class has a dbus method on the correct interface,
205 # or overrides a method that is, success!
206 parent_method
= cls
.__dict
__[method_name
]
211 # simpler version of above
212 for cls
in self
.__class
__.__mro
__:
213 if (not candidate_class
and method_name
in cls
.__dict
__):
214 candidate_class
= cls
216 if (candidate_class
and method_name
in cls
.__dict
__
217 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__):
218 parent_method
= cls
.__dict
__[method_name
]
223 return (candidate_class
.__dict
__[method_name
], parent_method
)
226 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name
, dbus_interface
))
228 raise UnknownMethodException('%s is not a valid method' % method_name
)
231 def _method_reply_return(connection
, message
, method_name
, signature
, *retval
):
232 reply
= _dbus_bindings
.MethodReturnMessage(message
)
234 reply
.append(signature
=signature
, *retval
)
236 if signature
is None:
238 signature
= reply
.guess_signature(retval
) + ' (guessed)'
240 _logger
.error('Unable to guess signature for arguments %r: '
241 '%s: %s', retval
, e
.__class
__, e
)
243 _logger
.error('Unable to append %r to message with signature %s: '
244 '%s: %s', retval
, signature
, e
.__class
__, e
)
247 connection
.send_message(reply
)
250 def _method_reply_error(connection
, message
, exception
):
251 if hasattr(exception
, '_dbus_error_name'):
252 name
= exception
._dbus
_error
_name
253 elif getattr(exception
, '__module__', '') in ('', '__main__'):
254 name
= 'org.freedesktop.DBus.Python.%s' % exception
.__class
__.__name
__
256 name
= 'org.freedesktop.DBus.Python.%s.%s' % (exception
.__module
__, exception
.__class
__.__name
__)
258 contents
= traceback
.format_exc()
259 reply
= _dbus_bindings
.ErrorMessage(message
, name
, contents
)
261 connection
.send_message(reply
)
264 class InterfaceType(type):
265 def __init__(cls
, name
, bases
, dct
):
266 # these attributes are shared between all instances of the Interface
267 # object, so this has to be a dictionary that maps class names to
268 # the per-class introspection/interface data
269 class_table
= getattr(cls
, '_dbus_class_table', {})
270 cls
._dbus
_class
_table
= class_table
271 interface_table
= class_table
[cls
.__module
__ + '.' + name
] = {}
273 # merge all the name -> method tables for all the interfaces
274 # implemented by our base classes into our own
276 base_name
= b
.__module
__ + '.' + b
.__name
__
277 if getattr(b
, '_dbus_class_table', False):
278 for (interface
, method_table
) in class_table
[base_name
].iteritems():
279 our_method_table
= interface_table
.setdefault(interface
, {})
280 our_method_table
.update(method_table
)
282 # add in all the name -> method entries for our own methods/signals
283 for func
in dct
.values():
284 if getattr(func
, '_dbus_interface', False):
285 method_table
= interface_table
.setdefault(func
._dbus
_interface
, {})
286 method_table
[func
.__name
__] = func
288 super(InterfaceType
, cls
).__init
__(name
, bases
, dct
)
290 # methods are different to signals, so we have two functions... :)
291 def _reflect_on_method(cls
, func
):
292 args
= func
._dbus
_args
294 if func
._dbus
_in
_signature
:
295 # convert signature into a tuple so length refers to number of
296 # types, not number of characters. the length is checked by
297 # the decorator to make sure it matches the length of args.
298 in_sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_in
_signature
))
300 # magic iterator which returns as many v's as we need
301 in_sig
= _VariantSignature()
303 if func
._dbus
_out
_signature
:
304 out_sig
= _dbus_bindings
.Signature(func
._dbus
_out
_signature
)
306 # its tempting to default to _dbus_bindings.Signature('v'), but
307 # for methods that return nothing, providing incorrect
308 # introspection data is worse than providing none at all
311 reflection_data
= ' <method name="%s">\n' % (func
.__name
__)
312 for pair
in zip(in_sig
, args
):
313 reflection_data
+= ' <arg direction="in" type="%s" name="%s" />\n' % pair
315 reflection_data
+= ' <arg direction="out" type="%s" />\n' % type
316 reflection_data
+= ' </method>\n'
318 return reflection_data
320 def _reflect_on_signal(cls
, func
):
321 args
= func
._dbus
_args
323 if func
._dbus
_signature
:
324 # convert signature into a tuple so length refers to number of
325 # types, not number of characters
326 sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_signature
))
328 # magic iterator which returns as many v's as we need
329 sig
= _VariantSignature()
331 reflection_data
= ' <signal name="%s">\n' % (func
.__name
__)
332 for pair
in zip(sig
, args
):
333 reflection_data
= reflection_data
+ ' <arg type="%s" name="%s" />\n' % pair
334 reflection_data
= reflection_data
+ ' </signal>\n'
336 return reflection_data
338 class Interface(object):
339 __metaclass__
= InterfaceType
341 class Object(Interface
):
342 r
"""A base class for exporting your own Objects across the Bus.
344 Just inherit from Object and mark exported methods with the
345 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
349 class Example(dbus.service.object):
350 def __init__(self, object_path):
351 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
352 self._last_input = None
354 @dbus.service.method(interface='com.example.Sample',
355 in_signature='v', out_signature='s')
356 def StringifyVariant(self, var):
357 self.LastInputChanged(var) # emits the signal
360 @dbus.service.signal(interface='com.example.Sample',
362 def LastInputChanged(self, var):
363 # run just before the signal is actually emitted
364 # just put "pass" if nothing should happen
365 self._last_input = var
367 @dbus.service.method(interface='com.example.Sample',
368 in_signature='', out_signature='v')
369 def GetLastInput(self):
370 return self._last_input
373 # the signature of __init__ is a bit mad, for backwards compatibility
374 def __init__(self
, conn
=None, object_path
=None, bus_name
=None):
375 """Constructor. Either conn or bus_name is required; object_path
379 `conn` : dbus.Connection
380 The connection on which to export this object.
382 If None, use the Bus associated with the given ``bus_name``,
383 or raise TypeError if there is no ``bus_name`` either.
385 For backwards compatibility, if an instance of
386 dbus.service.BusName is passed as the first parameter,
387 this is equivalent to passing its associated Bus as
388 ``conn``, and passing the BusName itself as ``bus_name``.
391 The D-Bus object path at which to export this Object.
393 `bus_name` : dbus.service.BusName
394 Represents a well-known name claimed by this process. A
395 reference to the BusName object will be held by this
396 Object, preventing the name from being released during this
397 Object's lifetime (unless it's released manually).
399 if object_path
is None:
400 raise TypeError('The object_path argument is required')
401 _dbus_bindings
.validate_object_path(object_path
)
402 if object_path
== LOCAL_PATH
:
403 raise DBusException('Objects may not be exported on the reserved '
404 'path %s' % LOCAL_PATH
)
406 if isinstance(conn
, BusName
):
407 # someone's using the old API; don't gratuitously break them
409 conn
= bus_name
.get_bus()
411 # someone's using the old API but naming arguments, probably
413 raise TypeError('Either conn or bus_name is required')
414 conn
= bus_name
.get_bus()
416 self
._object
_path
= object_path
417 self
._name
= bus_name
420 self
._connection
= self
._bus
.get_connection()
422 self
._connection
._register
_object
_path
(object_path
, self
._message
_cb
, self
._unregister
_cb
)
424 __dbus_object_path__
= property(lambda self
: self
._object
_path
, None, None,
425 "The D-Bus object path of this object")
427 def _unregister_cb(self
, connection
):
428 _logger
.info('Unregistering exported object %r', self
)
430 def _message_cb(self
, connection
, message
):
432 # lookup candidate method and parent method
433 method_name
= message
.get_member()
434 interface_name
= message
.get_interface()
435 (candidate_method
, parent_method
) = _method_lookup(self
, method_name
, interface_name
)
437 # set up method call parameters
438 args
= message
.get_args_list(**parent_method
._dbus
_get
_args
_options
)
441 if parent_method
._dbus
_out
_signature
is not None:
442 signature
= _dbus_bindings
.Signature(parent_method
._dbus
_out
_signature
)
446 # set up async callback functions
447 if parent_method
._dbus
_async
_callbacks
:
448 (return_callback
, error_callback
) = parent_method
._dbus
_async
_callbacks
449 keywords
[return_callback
] = lambda *retval
: _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
450 keywords
[error_callback
] = lambda exception
: _method_reply_error(connection
, message
, exception
)
452 # include the sender etc. if desired
453 if parent_method
._dbus
_sender
_keyword
:
454 keywords
[parent_method
._dbus
_sender
_keyword
] = message
.get_sender()
455 if parent_method
._dbus
_path
_keyword
:
456 keywords
[parent_method
._dbus
_path
_keyword
] = message
.get_path()
457 if parent_method
._dbus
_destination
_keyword
:
458 keywords
[parent_method
._dbus
_destination
_keyword
] = message
.get_destination()
459 if parent_method
._dbus
_message
_keyword
:
460 keywords
[parent_method
._dbus
_message
_keyword
] = message
463 retval
= candidate_method(self
, *args
, **keywords
)
465 # we're done - the method has got callback functions to reply with
466 if parent_method
._dbus
_async
_callbacks
:
469 # otherwise we send the return values in a reply. if we have a
470 # signature, use it to turn the return value into a tuple as
472 if signature
is not None:
473 signature_tuple
= tuple(signature
)
474 # if we have zero or one return values we want make a tuple
475 # for the _method_reply_return function, otherwise we need
476 # to check we're passing it a sequence
477 if len(signature_tuple
) == 0:
481 raise TypeError('%s has an empty output signature but did not return None' %
483 elif len(signature_tuple
) == 1:
486 if operator
.isSequenceType(retval
):
487 # multi-value signature, multi-value return... proceed unchanged
490 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
491 (method_name
, signature
))
493 # no signature, so just turn the return into a tuple and send it as normal
497 elif (isinstance(retval
, tuple)
498 and not isinstance(retval
, _dbus_bindings
.Struct
)):
499 # If the return is a tuple that is not a Struct, we use it
500 # as-is on the assumption that there are multiple return
501 # values - this is the usual Python idiom. (fd.o #10174)
506 _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
507 except Exception, exception
:
509 _method_reply_error(connection
, message
, exception
)
511 @method('org.freedesktop.DBus.Introspectable', in_signature
='', out_signature
='s')
512 def Introspect(self
):
513 """Return a string of XML encoding this object's supported interfaces,
516 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'
517 reflection_data
+= '<node name="%s">\n' % (self
._object
_path
)
519 interfaces
= self
._dbus
_class
_table
[self
.__class
__.__module
__ + '.' + self
.__class
__.__name
__]
520 for (name
, funcs
) in interfaces
.iteritems():
521 reflection_data
+= ' <interface name="%s">\n' % (name
)
523 for func
in funcs
.values():
524 if getattr(func
, '_dbus_is_method', False):
525 reflection_data
+= self
.__class
__._reflect
_on
_method
(func
)
526 elif getattr(func
, '_dbus_is_signal', False):
527 reflection_data
+= self
.__class
__._reflect
_on
_signal
(func
)
529 reflection_data
+= ' </interface>\n'
531 reflection_data
+= '</node>\n'
533 return reflection_data
536 return '<dbus.service.Object %s on %r at %#x>' % (self
._object
_path
, self
._name
, id(self
))