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
38 _logger
= logging
.getLogger('dbus.service')
41 class _VariantSignature(object):
42 """A fake method signature which, when iterated, yields an endless stream
43 of 'v' characters representing variants (handy with zip()).
45 It has no string representation.
52 """Return 'v' whenever called."""
55 class BusName(object):
56 """A base class for exporting your own Named Services across the Bus.
58 When instantiated, objects of this class attempt to claim the given
59 well-known name on the given bus for the current process. The name is
60 released when the BusName object becomes unreferenced.
62 If a well-known name is requested multiple times, multiple references
63 to the same BusName object will be returned.
67 - Assumes that named services are only ever requested using this class -
68 if you request names from the bus directly, confusion may occur.
69 - Does not handle queueing.
71 def __new__(cls
, name
, bus
=None, allow_replacement
=False , replace_existing
=False, do_not_queue
=False):
72 """Constructor, which may either return an existing cached object
77 The well-known name to be advertised
79 A Bus on which this service will be advertised; if None
80 (default) the session bus will be used
81 `allow_replacement` : bool
82 If True, other processes trying to claim the same well-known
83 name will take precedence over this one.
84 `replace_existing` : bool
85 If True, this process can take over the well-known name
86 from other processes already holding it.
88 If True, this service will not be placed in the queue of
89 services waiting for the requested name if another service
96 # see if this name is already defined, return it if so
97 # FIXME: accessing internals of Bus
98 if name
in bus
._bus
_names
:
99 return bus
._bus
_names
[name
]
101 # otherwise register the name
103 (allow_replacement
and _dbus_bindings
.NAME_FLAG_ALLOW_REPLACEMENT
or 0) |
104 (replace_existing
and _dbus_bindings
.NAME_FLAG_REPLACE_EXISTING
or 0) |
105 (do_not_queue
and _dbus_bindings
.NAME_FLAG_DO_NOT_QUEUE
or 0))
107 retval
= bus
.request_name(name
, name_flags
)
109 # TODO: more intelligent tracking of bus name states?
110 if retval
== _dbus_bindings
.REQUEST_NAME_REPLY_PRIMARY_OWNER
:
112 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_IN_QUEUE
:
113 # queueing can happen by default, maybe we should
114 # track this better or let the user know if they're
117 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_EXISTS
:
118 raise NameExistsException(name
)
119 elif retval
== _dbus_bindings
.REQUEST_NAME_REPLY_ALREADY_OWNER
:
120 # if this is a shared bus which is being used by someone
121 # else in this process, this can happen legitimately
124 raise RuntimeError('requesting bus name %s returned unexpected value %s' % (name
, retval
))
126 # and create the object
127 bus_name
= object.__new
__(cls
)
129 bus_name
._name
= name
131 # cache instance (weak ref only)
132 # FIXME: accessing Bus internals again
133 bus
._bus
_names
[name
] = bus_name
137 # do nothing because this is called whether or not the bus name
138 # object was retrieved from the cache or created new
139 def __init__(self
, *args
, **keywords
):
142 # we can delete the low-level name here because these objects
143 # are guaranteed to exist only once for each bus name
145 self
._bus
.release_name(self
._name
)
149 """Get the Bus this Service is on"""
153 """Get the name of this service"""
157 return '<dbus.service.BusName %s on %r at %#x>' % (self
._name
, self
._bus
, id(self
))
161 def _method_lookup(self
, method_name
, dbus_interface
):
162 """Walks the Python MRO of the given class to find the method to invoke.
164 Returns two methods, the one to call, and the one it inherits from which
165 defines its D-Bus interface name, signature, and attributes.
168 candidate_class
= None
171 # split up the cases when we do and don't have an interface because the
172 # latter is much simpler
174 # search through the class hierarchy in python MRO order
175 for cls
in self
.__class
__.__mro
__:
176 # if we haven't got a candidate class yet, and we find a class with a
177 # suitably named member, save this as a candidate class
178 if (not candidate_class
and method_name
in cls
.__dict
__):
179 if ("_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
180 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__):
181 # however if it is annotated for a different interface
182 # than we are looking for, it cannot be a candidate
183 if cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
:
184 candidate_class
= cls
185 parent_method
= cls
.__dict
__[method_name
]
191 candidate_class
= cls
193 # if we have a candidate class, carry on checking this and all
194 # superclasses for a method annoated as a dbus method
195 # on the correct interface
196 if (candidate_class
and method_name
in cls
.__dict
__
197 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__
198 and "_dbus_interface" in cls
.__dict
__[method_name
].__dict
__
199 and cls
.__dict
__[method_name
]._dbus
_interface
== dbus_interface
):
200 # the candidate class has a dbus method on the correct interface,
201 # or overrides a method that is, success!
202 parent_method
= cls
.__dict
__[method_name
]
207 # simpler version of above
208 for cls
in self
.__class
__.__mro
__:
209 if (not candidate_class
and method_name
in cls
.__dict
__):
210 candidate_class
= cls
212 if (candidate_class
and method_name
in cls
.__dict
__
213 and "_dbus_is_method" in cls
.__dict
__[method_name
].__dict
__):
214 parent_method
= cls
.__dict
__[method_name
]
219 return (candidate_class
.__dict
__[method_name
], parent_method
)
222 raise UnknownMethodException('%s is not a valid method of interface %s' % (method_name
, dbus_interface
))
224 raise UnknownMethodException('%s is not a valid method' % method_name
)
227 def _method_reply_return(connection
, message
, method_name
, signature
, *retval
):
228 reply
= _dbus_bindings
.MethodReturnMessage(message
)
230 reply
.append(signature
=signature
, *retval
)
232 if signature
is None:
234 signature
= reply
.guess_signature(retval
) + ' (guessed)'
236 _logger
.error('Unable to guess signature for arguments %r: '
237 '%s: %s', retval
, e
.__class
__, e
)
239 _logger
.error('Unable to append %r to message with signature %s: '
240 '%s: %s', retval
, signature
, e
.__class
__, e
)
243 connection
.send_message(reply
)
246 def _method_reply_error(connection
, message
, exception
):
247 if hasattr(exception
, '_dbus_error_name'):
248 name
= exception
._dbus
_error
_name
249 elif getattr(exception
, '__module__', '') in ('', '__main__'):
250 name
= 'org.freedesktop.DBus.Python.%s' % exception
.__class
__.__name
__
252 name
= 'org.freedesktop.DBus.Python.%s.%s' % (exception
.__module
__, exception
.__class
__.__name
__)
254 contents
= traceback
.format_exc()
255 reply
= _dbus_bindings
.ErrorMessage(message
, name
, contents
)
257 connection
.send_message(reply
)
260 class InterfaceType(type):
261 def __init__(cls
, name
, bases
, dct
):
262 # these attributes are shared between all instances of the Interface
263 # object, so this has to be a dictionary that maps class names to
264 # the per-class introspection/interface data
265 class_table
= getattr(cls
, '_dbus_class_table', {})
266 cls
._dbus
_class
_table
= class_table
267 interface_table
= class_table
[cls
.__module
__ + '.' + name
] = {}
269 # merge all the name -> method tables for all the interfaces
270 # implemented by our base classes into our own
272 base_name
= b
.__module
__ + '.' + b
.__name
__
273 if getattr(b
, '_dbus_class_table', False):
274 for (interface
, method_table
) in class_table
[base_name
].iteritems():
275 our_method_table
= interface_table
.setdefault(interface
, {})
276 our_method_table
.update(method_table
)
278 # add in all the name -> method entries for our own methods/signals
279 for func
in dct
.values():
280 if getattr(func
, '_dbus_interface', False):
281 method_table
= interface_table
.setdefault(func
._dbus
_interface
, {})
282 method_table
[func
.__name
__] = func
284 super(InterfaceType
, cls
).__init
__(name
, bases
, dct
)
286 # methods are different to signals, so we have two functions... :)
287 def _reflect_on_method(cls
, func
):
288 args
= func
._dbus
_args
290 if func
._dbus
_in
_signature
:
291 # convert signature into a tuple so length refers to number of
292 # types, not number of characters. the length is checked by
293 # the decorator to make sure it matches the length of args.
294 in_sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_in
_signature
))
296 # magic iterator which returns as many v's as we need
297 in_sig
= _VariantSignature()
299 if func
._dbus
_out
_signature
:
300 out_sig
= _dbus_bindings
.Signature(func
._dbus
_out
_signature
)
302 # its tempting to default to _dbus_bindings.Signature('v'), but
303 # for methods that return nothing, providing incorrect
304 # introspection data is worse than providing none at all
307 reflection_data
= ' <method name="%s">\n' % (func
.__name
__)
308 for pair
in zip(in_sig
, args
):
309 reflection_data
+= ' <arg direction="in" type="%s" name="%s" />\n' % pair
311 reflection_data
+= ' <arg direction="out" type="%s" />\n' % type
312 reflection_data
+= ' </method>\n'
314 return reflection_data
316 def _reflect_on_signal(cls
, func
):
317 args
= func
._dbus
_args
319 if func
._dbus
_signature
:
320 # convert signature into a tuple so length refers to number of
321 # types, not number of characters
322 sig
= tuple(_dbus_bindings
.Signature(func
._dbus
_signature
))
324 # magic iterator which returns as many v's as we need
325 sig
= _VariantSignature()
327 reflection_data
= ' <signal name="%s">\n' % (func
.__name
__)
328 for pair
in zip(sig
, args
):
329 reflection_data
= reflection_data
+ ' <arg type="%s" name="%s" />\n' % pair
330 reflection_data
= reflection_data
+ ' </signal>\n'
332 return reflection_data
334 class Interface(object):
335 __metaclass__
= InterfaceType
337 class Object(Interface
):
338 r
"""A base class for exporting your own Objects across the Bus.
340 Just inherit from Object and mark exported methods with the
341 @\ `dbus.service.method` or @\ `dbus.service.signal` decorator.
345 class Example(dbus.service.object):
346 def __init__(self, object_path):
347 dbus.service.Object.__init__(self, dbus.SessionBus(), path)
348 self._last_input = None
350 @dbus.service.method(interface='com.example.Sample',
351 in_signature='v', out_signature='s')
352 def StringifyVariant(self, var):
353 self.LastInputChanged(var) # emits the signal
356 @dbus.service.signal(interface='com.example.Sample',
358 def LastInputChanged(self, var):
359 # run just before the signal is actually emitted
360 # just put "pass" if nothing should happen
361 self._last_input = var
363 @dbus.service.method(interface='com.example.Sample',
364 in_signature='', out_signature='v')
365 def GetLastInput(self):
366 return self._last_input
369 # the signature of __init__ is a bit mad, for backwards compatibility
370 def __init__(self
, conn
=None, object_path
=None, bus_name
=None):
371 """Constructor. Either conn or bus_name is required; object_path
375 `conn` : dbus.Connection
376 The connection on which to export this object.
378 If None, use the Bus associated with the given ``bus_name``,
379 or raise TypeError if there is no ``bus_name`` either.
381 For backwards compatibility, if an instance of
382 dbus.service.BusName is passed as the first parameter,
383 this is equivalent to passing its associated Bus as
384 ``conn``, and passing the BusName itself as ``bus_name``.
387 The D-Bus object path at which to export this Object.
389 `bus_name` : dbus.service.BusName
390 Represents a well-known name claimed by this process. A
391 reference to the BusName object will be held by this
392 Object, preventing the name from being released during this
393 Object's lifetime (unless it's released manually).
395 if object_path
is None:
396 raise TypeError('The object_path argument is required')
398 if isinstance(conn
, BusName
):
399 # someone's using the old API; don't gratuitously break them
401 conn
= bus_name
.get_bus()
403 # someone's using the old API but naming arguments, probably
405 raise TypeError('Either conn or bus_name is required')
406 conn
= bus_name
.get_bus()
408 self
._object
_path
= object_path
409 self
._name
= bus_name
412 self
._connection
= self
._bus
.get_connection()
414 self
._connection
._register
_object
_path
(object_path
, self
._message
_cb
, self
._unregister
_cb
)
416 __dbus_object_path__
= property(lambda self
: self
._object
_path
, None, None,
417 "The D-Bus object path of this object")
419 def _unregister_cb(self
, connection
):
420 _logger
.info('Unregistering exported object %r', self
)
422 def _message_cb(self
, connection
, message
):
424 # lookup candidate method and parent method
425 method_name
= message
.get_member()
426 interface_name
= message
.get_interface()
427 (candidate_method
, parent_method
) = _method_lookup(self
, method_name
, interface_name
)
429 # set up method call parameters
430 args
= message
.get_args_list(**parent_method
._dbus
_get
_args
_options
)
433 if parent_method
._dbus
_out
_signature
is not None:
434 signature
= _dbus_bindings
.Signature(parent_method
._dbus
_out
_signature
)
438 # set up async callback functions
439 if parent_method
._dbus
_async
_callbacks
:
440 (return_callback
, error_callback
) = parent_method
._dbus
_async
_callbacks
441 keywords
[return_callback
] = lambda *retval
: _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
442 keywords
[error_callback
] = lambda exception
: _method_reply_error(connection
, message
, exception
)
444 # include the sender if desired
445 if parent_method
._dbus
_sender
_keyword
:
446 keywords
[parent_method
._dbus
_sender
_keyword
] = message
.get_sender()
449 retval
= candidate_method(self
, *args
, **keywords
)
451 # we're done - the method has got callback functions to reply with
452 if parent_method
._dbus
_async
_callbacks
:
455 # otherwise we send the return values in a reply. if we have a
456 # signature, use it to turn the return value into a tuple as
458 if signature
is not None:
459 signature_tuple
= tuple(signature
)
460 # if we have zero or one return values we want make a tuple
461 # for the _method_reply_return function, otherwise we need
462 # to check we're passing it a sequence
463 if len(signature_tuple
) == 0:
467 raise TypeError('%s has an empty output signature but did not return None' %
469 elif len(signature_tuple
) == 1:
472 if operator
.isSequenceType(retval
):
473 # multi-value signature, multi-value return... proceed unchanged
476 raise TypeError('%s has multiple output values in signature %s but did not return a sequence' %
477 (method_name
, signature
))
479 # no signature, so just turn the return into a tuple and send it as normal
486 _method_reply_return(connection
, message
, method_name
, signature
, *retval
)
487 except Exception, exception
:
489 _method_reply_error(connection
, message
, exception
)
491 @method('org.freedesktop.DBus.Introspectable', in_signature
='', out_signature
='s')
492 def Introspect(self
):
493 """Return a string of XML encoding this object's supported interfaces,
496 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'
497 reflection_data
+= '<node name="%s">\n' % (self
._object
_path
)
499 interfaces
= self
._dbus
_class
_table
[self
.__class
__.__module
__ + '.' + self
.__class
__.__name
__]
500 for (name
, funcs
) in interfaces
.iteritems():
501 reflection_data
+= ' <interface name="%s">\n' % (name
)
503 for func
in funcs
.values():
504 if getattr(func
, '_dbus_is_method', False):
505 reflection_data
+= self
.__class
__._reflect
_on
_method
(func
)
506 elif getattr(func
, '_dbus_is_signal', False):
507 reflection_data
+= self
.__class
__._reflect
_on
_signal
(func
)
509 reflection_data
+= ' </interface>\n'
511 reflection_data
+= '</node>\n'
513 return reflection_data
516 return '<dbus.service.Object %s on %r at %#x>' % (self
._object
_path
, self
._name
, id(self
))