Add wrapper for DBusServer.
[dbus-python-phuang.git] / dbus / connection.py
blob3a551ea5ea1212460276dbabaa7505e471623cc9
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use, copy,
7 # modify, merge, publish, distribute, sublicense, and/or sell copies
8 # of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 # DEALINGS IN THE SOFTWARE.
23 __all__ = ('Connection', 'SignalMatch')
24 __docformat__ = 'reStructuredText'
26 import logging
27 try:
28 import thread
29 except ImportError:
30 import dummy_thread as thread
31 import weakref
33 from _dbus_bindings import Connection as _Connection, \
34 LOCAL_PATH, LOCAL_IFACE, \
35 validate_interface_name, validate_member_name,\
36 validate_bus_name, validate_object_path,\
37 validate_error_name, \
38 UTF8String
39 from dbus.exceptions import DBusException
40 from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
41 MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
42 from dbus.proxies import ProxyObject
45 _logger = logging.getLogger('dbus.connection')
48 def _noop(*args, **kwargs):
49 pass
52 class SignalMatch(object):
53 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
54 '_path', '_handler', '_args_match', '_rule',
55 '_utf8_strings', '_byte_arrays', '_conn_weakref',
56 '_destination_keyword', '_interface_keyword',
57 '_message_keyword', '_member_keyword',
58 '_sender_keyword', '_path_keyword', '_int_args_match')
60 def __init__(self, conn, sender, object_path, dbus_interface,
61 member, handler, utf8_strings=False, byte_arrays=False,
62 sender_keyword=None, path_keyword=None,
63 interface_keyword=None, member_keyword=None,
64 message_keyword=None, destination_keyword=None,
65 **kwargs):
66 if member is not None:
67 validate_member_name(member)
68 if dbus_interface is not None:
69 validate_interface_name(dbus_interface)
70 if sender is not None:
71 validate_bus_name(sender)
72 if object_path is not None:
73 validate_object_path(object_path)
75 self._rule = None
76 self._conn_weakref = weakref.ref(conn)
77 self._sender = sender
78 self._interface = dbus_interface
79 self._member = member
80 self._path = object_path
81 self._handler = handler
83 # if the connection is actually a bus, it's responsible for changing
84 # this later
85 self._sender_name_owner = sender
87 self._utf8_strings = utf8_strings
88 self._byte_arrays = byte_arrays
89 self._sender_keyword = sender_keyword
90 self._path_keyword = path_keyword
91 self._member_keyword = member_keyword
92 self._interface_keyword = interface_keyword
93 self._message_keyword = message_keyword
94 self._destination_keyword = destination_keyword
96 self._args_match = kwargs
97 if not kwargs:
98 self._int_args_match = None
99 else:
100 self._int_args_match = {}
101 for kwarg in kwargs:
102 if not kwarg.startswith('arg'):
103 raise TypeError('SignalMatch: unknown keyword argument %s'
104 % kwarg)
105 try:
106 index = int(kwarg[3:])
107 except ValueError:
108 raise TypeError('SignalMatch: unknown keyword argument %s'
109 % kwarg)
110 if index < 0 or index > 63:
111 raise TypeError('SignalMatch: arg match index must be in '
112 'range(64), not %d' % index)
113 self._int_args_match[index] = kwargs[kwarg]
115 def __hash__(self):
116 """SignalMatch objects are compared by identity."""
117 return hash(id(self))
119 def __eq__(self, other):
120 """SignalMatch objects are compared by identity."""
121 return self is other
123 def __ne__(self, other):
124 """SignalMatch objects are compared by identity."""
125 return self is not other
127 sender = property(lambda self: self._sender)
129 def __str__(self):
130 if self._rule is None:
131 rule = ["type='signal'"]
132 if self._sender is not None:
133 rule.append("sender='%s'" % self._sender)
134 if self._path is not None:
135 rule.append("path='%s'" % self._path)
136 if self._interface is not None:
137 rule.append("interface='%s'" % self._interface)
138 if self._member is not None:
139 rule.append("member='%s'" % self._member)
140 if self._int_args_match is not None:
141 for index, value in self._int_args_match.iteritems():
142 rule.append("arg%d='%s'" % (index, value))
144 self._rule = ','.join(rule)
146 return self._rule
148 def __repr__(self):
149 return ('<%s at %x "%s" on conn %r>'
150 % (self.__class__, id(self), self._rule, self._conn_weakref()))
152 def set_sender_name_owner(self, new_name):
153 self._sender_name_owner = new_name
155 def matches_removal_spec(self, sender, object_path,
156 dbus_interface, member, handler, **kwargs):
157 if handler not in (None, self._handler):
158 return False
159 if sender != self._sender:
160 return False
161 if object_path != self._path:
162 return False
163 if dbus_interface != self._interface:
164 return False
165 if member != self._member:
166 return False
167 if kwargs != self._args_match:
168 return False
169 return True
171 def maybe_handle_message(self, message):
172 args = None
174 # these haven't been checked yet by the match tree
175 if self._sender_name_owner not in (None, message.get_sender()):
176 return False
177 if self._int_args_match is not None:
178 # extracting args with utf8_strings and byte_arrays is less work
179 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
180 for index, value in self._int_args_match.iteritems():
181 if (index >= len(args)
182 or not isinstance(args[index], UTF8String)
183 or args[index] != value):
184 return False
186 # these have likely already been checked by the match tree
187 if self._member not in (None, message.get_member()):
188 return False
189 if self._interface not in (None, message.get_interface()):
190 return False
191 if self._path not in (None, message.get_path()):
192 return False
194 try:
195 # minor optimization: if we already extracted the args with the
196 # right calling convention to do the args match, don't bother
197 # doing so again
198 if args is None or not self._utf8_strings or not self._byte_arrays:
199 args = message.get_args_list(utf8_strings=self._utf8_strings,
200 byte_arrays=self._byte_arrays)
201 kwargs = {}
202 if self._sender_keyword is not None:
203 kwargs[self._sender_keyword] = message.get_sender()
204 if self._destination_keyword is not None:
205 kwargs[self._destination_keyword] = message.get_destination()
206 if self._path_keyword is not None:
207 kwargs[self._path_keyword] = message.get_path()
208 if self._member_keyword is not None:
209 kwargs[self._member_keyword] = message.get_member()
210 if self._interface_keyword is not None:
211 kwargs[self._interface_keyword] = message.get_interface()
212 if self._message_keyword is not None:
213 kwargs[self._message_keyword] = message
214 self._handler(*args, **kwargs)
215 except:
216 # basicConfig is a no-op if logging is already configured
217 logging.basicConfig()
218 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
220 return True
222 def remove(self):
223 conn = self._conn_weakref()
224 # do nothing if the connection has already vanished
225 if conn is not None:
226 conn.remove_signal_receiver(self, self._member,
227 self._interface, self._sender,
228 self._path,
229 **self._args_match)
232 class Connection(_Connection):
233 """A connection to another application. In this base class there is
234 assumed to be no bus daemon.
236 :Since: 0.81.0
239 ProxyObjectClass = ProxyObject
241 def __init__(self, *args, **kwargs):
242 super(Connection, self).__init__(*args, **kwargs)
244 # this if-block is needed because shared bus connections can be
245 # __init__'ed more than once
246 if not hasattr(self, '_dbus_Connection_initialized'):
247 self._dbus_Connection_initialized = 1
249 self._signal_recipients_by_object_path = {}
250 """Map from object path to dict mapping dbus_interface to dict
251 mapping member to list of SignalMatch objects."""
253 self._signals_lock = thread.allocate_lock()
254 """Lock used to protect signal data structures"""
256 self.add_message_filter(self.__class__._signal_func)
258 def activate_name_owner(self, bus_name):
259 """Return the unique name for the given bus name, activating it
260 if necessary and possible.
262 If the name is already unique or this connection is not to a
263 bus daemon, just return it.
265 :Returns: a bus name. If the given `bus_name` exists, the returned
266 name identifies its current owner; otherwise the returned name
267 does not exist.
268 :Raises DBusException: if the implementation has failed
269 to activate the given bus name.
270 :Since: 0.81.0
272 return bus_name
274 def get_object(self, bus_name=None, object_path=None, introspect=True,
275 **kwargs):
276 """Return a local proxy for the given remote object.
278 Method calls on the proxy are translated into method calls on the
279 remote object.
281 :Parameters:
282 `bus_name` : str
283 A bus name (either the unique name or a well-known name)
284 of the application owning the object. The keyword argument
285 named_service is a deprecated alias for this.
286 `object_path` : str
287 The object path of the desired object
288 `introspect` : bool
289 If true (default), attempt to introspect the remote
290 object to find out supported methods and their signatures
292 :Returns: a `dbus.proxies.ProxyObject`
294 named_service = kwargs.pop('named_service', None)
295 if named_service is not None:
296 if bus_name is not None:
297 raise TypeError('bus_name and named_service cannot both '
298 'be specified')
299 from warnings import warn
300 warn('Passing the named_service parameter to get_object by name '
301 'is deprecated: please use positional parameters',
302 DeprecationWarning, stacklevel=2)
303 bus_name = named_service
304 if kwargs:
305 raise TypeError('get_object does not take these keyword '
306 'arguments: %s' % ', '.join(kwargs.iterkeys()))
308 return self.ProxyObjectClass(self, bus_name, object_path,
309 introspect=introspect)
311 def add_signal_receiver(self, handler_function,
312 signal_name=None,
313 dbus_interface=None,
314 bus_name=None,
315 path=None,
316 **keywords):
317 """Arrange for the given function to be called when a signal matching
318 the parameters is received.
320 :Parameters:
321 `handler_function` : callable
322 The function to be called. Its positional arguments will
323 be the arguments of the signal. By default it will receive
324 no keyword arguments, but see the description of
325 the optional keyword arguments below.
326 `signal_name` : str
327 The signal name; None (the default) matches all names
328 `dbus_interface` : str
329 The D-Bus interface name with which to qualify the signal;
330 None (the default) matches all interface names
331 `bus_name` : str
332 A bus name for the sender, which will be resolved to a
333 unique name if it is not already; None (the default) matches
334 any sender.
335 `path` : str
336 The object path of the object which must have emitted the
337 signal; None (the default) matches any object path
338 :Keywords:
339 `utf8_strings` : bool
340 If True, the handler function will receive any string
341 arguments as dbus.UTF8String objects (a subclass of str
342 guaranteed to be UTF-8). If False (default) it will receive
343 any string arguments as dbus.String objects (a subclass of
344 unicode).
345 `byte_arrays` : bool
346 If True, the handler function will receive any byte-array
347 arguments as dbus.ByteArray objects (a subclass of str).
348 If False (default) it will receive any byte-array
349 arguments as a dbus.Array of dbus.Byte (subclasses of:
350 a list of ints).
351 `sender_keyword` : str
352 If not None (the default), the handler function will receive
353 the unique name of the sending endpoint as a keyword
354 argument with this name.
355 `destination_keyword` : str
356 If not None (the default), the handler function will receive
357 the bus name of the destination (or None if the signal is a
358 broadcast, as is usual) as a keyword argument with this name.
359 `interface_keyword` : str
360 If not None (the default), the handler function will receive
361 the signal interface as a keyword argument with this name.
362 `member_keyword` : str
363 If not None (the default), the handler function will receive
364 the signal name as a keyword argument with this name.
365 `path_keyword` : str
366 If not None (the default), the handler function will receive
367 the object-path of the sending object as a keyword argument
368 with this name.
369 `message_keyword` : str
370 If not None (the default), the handler function will receive
371 the `dbus.lowlevel.SignalMessage` as a keyword argument with
372 this name.
373 `arg...` : unicode or UTF-8 str
374 If there are additional keyword parameters of the form
375 ``arg``\ *n*, match only signals where the *n*\ th argument
376 is the value given for that keyword parameter. As of this
377 time only string arguments can be matched (in particular,
378 object paths and signatures can't).
379 `named_service` : str
380 A deprecated alias for `bus_name`.
382 self._require_main_loop()
384 named_service = keywords.pop('named_service', None)
385 if named_service is not None:
386 if bus_name is not None:
387 raise TypeError('bus_name and named_service cannot both be '
388 'specified')
389 bus_name = named_service
390 from warnings import warn
391 warn('Passing the named_service parameter to add_signal_receiver '
392 'by name is deprecated: please use positional parameters',
393 DeprecationWarning, stacklevel=2)
395 match = SignalMatch(self, bus_name, path, dbus_interface,
396 signal_name, handler_function, **keywords)
398 self._signals_lock.acquire()
399 try:
400 by_interface = self._signal_recipients_by_object_path.setdefault(
401 path, {})
402 by_member = by_interface.setdefault(dbus_interface, {})
403 matches = by_member.setdefault(signal_name, [])
405 matches.append(match)
406 finally:
407 self._signals_lock.release()
409 return match
411 def _iter_easy_matches(self, path, dbus_interface, member):
412 if path is not None:
413 path_keys = (None, path)
414 else:
415 path_keys = (None,)
416 if dbus_interface is not None:
417 interface_keys = (None, dbus_interface)
418 else:
419 interface_keys = (None,)
420 if member is not None:
421 member_keys = (None, member)
422 else:
423 member_keys = (None,)
425 for path in path_keys:
426 by_interface = self._signal_recipients_by_object_path.get(path,
427 None)
428 if by_interface is None:
429 continue
430 for dbus_interface in interface_keys:
431 by_member = by_interface.get(dbus_interface, None)
432 if by_member is None:
433 continue
434 for member in member_keys:
435 matches = by_member.get(member, None)
436 if matches is None:
437 continue
438 for m in matches:
439 yield m
441 def remove_signal_receiver(self, handler_or_match,
442 signal_name=None,
443 dbus_interface=None,
444 bus_name=None,
445 path=None,
446 **keywords):
447 named_service = keywords.pop('named_service', None)
448 if named_service is not None:
449 if bus_name is not None:
450 raise TypeError('bus_name and named_service cannot both be '
451 'specified')
452 bus_name = named_service
453 from warnings import warn
454 warn('Passing the named_service parameter to '
455 'remove_signal_receiver by name is deprecated: please use '
456 'positional parameters',
457 DeprecationWarning, stacklevel=2)
459 new = []
460 deletions = []
461 self._signals_lock.acquire()
462 try:
463 by_interface = self._signal_recipients_by_object_path.get(path,
464 None)
465 if by_interface is None:
466 return
467 by_member = by_interface.get(dbus_interface, None)
468 if by_member is None:
469 return
470 matches = by_member.get(signal_name, None)
471 if matches is None:
472 return
474 for match in matches:
475 if (handler_or_match is match
476 or match.matches_removal_spec(bus_name,
477 path,
478 dbus_interface,
479 signal_name,
480 handler_or_match,
481 **keywords)):
482 deletions.append(match)
483 else:
484 new.append(match)
485 by_member[signal_name] = new
486 finally:
487 self._signals_lock.release()
489 for match in deletions:
490 self._clean_up_signal_match(match)
492 def _clean_up_signal_match(self, match):
493 # Now called without the signals lock held (it was held in <= 0.81.0)
494 pass
496 def _signal_func(self, message):
497 """D-Bus filter function. Handle signals by dispatching to Python
498 callbacks kept in the match-rule tree.
501 if not isinstance(message, SignalMessage):
502 return HANDLER_RESULT_NOT_YET_HANDLED
504 dbus_interface = message.get_interface()
505 path = message.get_path()
506 signal_name = message.get_member()
508 for match in self._iter_easy_matches(path, dbus_interface,
509 signal_name):
510 match.maybe_handle_message(message)
511 return HANDLER_RESULT_NOT_YET_HANDLED
513 def call_async(self, bus_name, object_path, dbus_interface, method,
514 signature, args, reply_handler, error_handler,
515 timeout=-1.0, utf8_strings=False, byte_arrays=False,
516 require_main_loop=True):
517 """Call the given method, asynchronously.
519 If the reply_handler is None, successful replies will be ignored.
520 If the error_handler is None, failures will be ignored. If both
521 are None, the implementation may request that no reply is sent.
523 :Returns: The dbus.lowlevel.PendingCall.
524 :Since: 0.81.0
526 if object_path == LOCAL_PATH:
527 raise DBusException('Methods may not be called on the reserved '
528 'path %s' % LOCAL_PATH)
529 if dbus_interface == LOCAL_IFACE:
530 raise DBusException('Methods may not be called on the reserved '
531 'interface %s' % LOCAL_IFACE)
532 # no need to validate other args - MethodCallMessage ctor will do
534 get_args_opts = {'utf8_strings': utf8_strings,
535 'byte_arrays': byte_arrays}
537 message = MethodCallMessage(destination=bus_name,
538 path=object_path,
539 interface=dbus_interface,
540 method=method)
541 # Add the arguments to the function
542 try:
543 message.append(signature=signature, *args)
544 except Exception, e:
545 logging.basicConfig()
546 _logger.error('Unable to set arguments %r according to '
547 'signature %r: %s: %s',
548 args, signature, e.__class__, e)
549 raise
551 if reply_handler is None and error_handler is None:
552 # we don't care what happens, so just send it
553 self.send_message(message)
554 return
556 if reply_handler is None:
557 reply_handler = _noop
558 if error_handler is None:
559 error_handler = _noop
561 def msg_reply_handler(message):
562 if isinstance(message, MethodReturnMessage):
563 reply_handler(*message.get_args_list(**get_args_opts))
564 elif isinstance(message, ErrorMessage):
565 error_handler(DBusException(name=message.get_error_name(),
566 *message.get_args_list()))
567 else:
568 error_handler(TypeError('Unexpected type for reply '
569 'message: %r' % message))
570 return self.send_message_with_reply(message, msg_reply_handler,
571 timeout,
572 require_main_loop=require_main_loop)
574 def call_blocking(self, bus_name, object_path, dbus_interface, method,
575 signature, args, timeout=-1.0, utf8_strings=False,
576 byte_arrays=False):
577 """Call the given method, synchronously.
578 :Since: 0.81.0
580 if object_path == LOCAL_PATH:
581 raise DBusException('Methods may not be called on the reserved '
582 'path %s' % LOCAL_PATH)
583 if dbus_interface == LOCAL_IFACE:
584 raise DBusException('Methods may not be called on the reserved '
585 'interface %s' % LOCAL_IFACE)
586 # no need to validate other args - MethodCallMessage ctor will do
588 get_args_opts = {'utf8_strings': utf8_strings,
589 'byte_arrays': byte_arrays}
591 message = MethodCallMessage(destination=bus_name,
592 path=object_path,
593 interface=dbus_interface,
594 method=method)
595 # Add the arguments to the function
596 try:
597 message.append(signature=signature, *args)
598 except Exception, e:
599 logging.basicConfig()
600 _logger.error('Unable to set arguments %r according to '
601 'signature %r: %s: %s',
602 args, signature, e.__class__, e)
603 raise
605 # make a blocking call
606 reply_message = self.send_message_with_reply_and_block(
607 message, timeout)
608 args_list = reply_message.get_args_list(**get_args_opts)
609 if len(args_list) == 0:
610 return None
611 elif len(args_list) == 1:
612 return args_list[0]
613 else:
614 return tuple(args_list)