Stop using interactive-Python syntax in tutorial to reduce user confusion.
[dbus-python-phuang.git] / dbus / connection.py
blob6931d9bd2591ee336c222f084e1de364365f23aa
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('Connection', 'SignalMatch')
20 __docformat__ = 'reStructuredText'
22 import logging
23 try:
24 import thread
25 except ImportError:
26 import dummy_thread as thread
27 import weakref
29 from _dbus_bindings import Connection as _Connection, ErrorMessage, \
30 MethodCallMessage, MethodReturnMessage, \
31 LOCAL_PATH, LOCAL_IFACE, \
32 validate_interface_name, validate_member_name,\
33 validate_bus_name, validate_object_path,\
34 validate_error_name, \
35 HANDLER_RESULT_NOT_YET_HANDLED, \
36 UTF8String, SignalMessage
37 from dbus.exceptions import DBusException
38 from dbus.proxies import ProxyObject
41 _logger = logging.getLogger('dbus.connection')
44 def _noop(*args, **kwargs):
45 pass
48 class SignalMatch(object):
49 __slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
50 '_path', '_handler', '_args_match', '_rule',
51 '_utf8_strings', '_byte_arrays', '_conn_weakref',
52 '_destination_keyword', '_interface_keyword',
53 '_message_keyword', '_member_keyword',
54 '_sender_keyword', '_path_keyword', '_int_args_match')
56 def __init__(self, conn, sender, object_path, dbus_interface,
57 member, handler, utf8_strings=False, byte_arrays=False,
58 sender_keyword=None, path_keyword=None,
59 interface_keyword=None, member_keyword=None,
60 message_keyword=None, destination_keyword=None,
61 **kwargs):
62 if member is not None:
63 validate_member_name(member)
64 if dbus_interface is not None:
65 validate_interface_name(dbus_interface)
66 if sender is not None:
67 validate_bus_name(sender)
68 if object_path is not None:
69 validate_object_path(object_path)
71 self._rule = None
72 self._conn_weakref = weakref.ref(conn)
73 self._sender = sender
74 self._interface = dbus_interface
75 self._member = member
76 self._path = object_path
77 self._handler = handler
79 # if the connection is actually a bus, it's responsible for changing
80 # this later
81 self._sender_name_owner = sender
83 self._utf8_strings = utf8_strings
84 self._byte_arrays = byte_arrays
85 self._sender_keyword = sender_keyword
86 self._path_keyword = path_keyword
87 self._member_keyword = member_keyword
88 self._interface_keyword = interface_keyword
89 self._message_keyword = message_keyword
90 self._destination_keyword = destination_keyword
92 self._args_match = kwargs
93 if not kwargs:
94 self._int_args_match = None
95 else:
96 self._int_args_match = {}
97 for kwarg in kwargs:
98 if not kwarg.startswith('arg'):
99 raise TypeError('SignalMatch: unknown keyword argument %s'
100 % kwarg)
101 try:
102 index = int(kwarg[3:])
103 except ValueError:
104 raise TypeError('SignalMatch: unknown keyword argument %s'
105 % kwarg)
106 if index < 0 or index > 63:
107 raise TypeError('SignalMatch: arg match index must be in '
108 'range(64), not %d' % index)
109 self._int_args_match[index] = kwargs[kwarg]
111 def __hash__(self):
112 """SignalMatch objects are compared by identity."""
113 return hash(id(self))
115 def __eq__(self, other):
116 """SignalMatch objects are compared by identity."""
117 return self is other
119 def __ne__(self, other):
120 """SignalMatch objects are compared by identity."""
121 return self is not other
123 sender = property(lambda self: self._sender)
125 def __str__(self):
126 if self._rule is None:
127 rule = ["type='signal'"]
128 if self._sender is not None:
129 rule.append("sender='%s'" % self._sender)
130 if self._path is not None:
131 rule.append("path='%s'" % self._path)
132 if self._interface is not None:
133 rule.append("interface='%s'" % self._interface)
134 if self._member is not None:
135 rule.append("member='%s'" % self._member)
136 if self._int_args_match is not None:
137 for index, value in self._int_args_match.iteritems():
138 rule.append("arg%d='%s'" % (index, value))
140 self._rule = ','.join(rule)
142 return self._rule
144 def __repr__(self):
145 return ('<%s at %x "%s" on conn %r>'
146 % (self.__class__, id(self), self._rule, self._conn_weakref()))
148 def set_sender_name_owner(self, new_name):
149 self._sender_name_owner = new_name
151 def matches_removal_spec(self, sender, object_path,
152 dbus_interface, member, handler, **kwargs):
153 if handler not in (None, self._handler):
154 return False
155 if sender != self._sender:
156 return False
157 if object_path != self._path:
158 return False
159 if dbus_interface != self._interface:
160 return False
161 if member != self._member:
162 return False
163 if kwargs != self._args_match:
164 return False
165 return True
167 def maybe_handle_message(self, message):
168 args = None
170 # these haven't been checked yet by the match tree
171 if self._sender_name_owner not in (None, message.get_sender()):
172 return False
173 if self._int_args_match is not None:
174 # extracting args with utf8_strings and byte_arrays is less work
175 args = message.get_args_list(utf8_strings=True, byte_arrays=True)
176 for index, value in self._int_args_match.iteritems():
177 if (index >= len(args)
178 or not isinstance(args[index], UTF8String)
179 or args[index] != value):
180 return False
182 # these have likely already been checked by the match tree
183 if self._member not in (None, message.get_member()):
184 return False
185 if self._interface not in (None, message.get_interface()):
186 return False
187 if self._path not in (None, message.get_path()):
188 return False
190 try:
191 # minor optimization: if we already extracted the args with the
192 # right calling convention to do the args match, don't bother
193 # doing so again
194 if args is None or not self._utf8_strings or not self._byte_arrays:
195 args = message.get_args_list(utf8_strings=self._utf8_strings,
196 byte_arrays=self._byte_arrays)
197 kwargs = {}
198 if self._sender_keyword is not None:
199 kwargs[self._sender_keyword] = message.get_sender()
200 if self._destination_keyword is not None:
201 kwargs[self._destination_keyword] = message.get_destination()
202 if self._path_keyword is not None:
203 kwargs[self._path_keyword] = message.get_path()
204 if self._member_keyword is not None:
205 kwargs[self._member_keyword] = message.get_member()
206 if self._interface_keyword is not None:
207 kwargs[self._interface_keyword] = message.get_interface()
208 if self._message_keyword is not None:
209 kwargs[self._message_keyword] = message
210 self._handler(*args, **kwargs)
211 except:
212 # basicConfig is a no-op if logging is already configured
213 logging.basicConfig()
214 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
216 return True
218 def remove(self):
219 conn = self._conn_weakref()
220 # do nothing if the connection has already vanished
221 if conn is not None:
222 conn.remove_signal_receiver(self, self._member,
223 self._interface, self._sender,
224 self._path,
225 **self._args_match)
228 class Connection(_Connection):
229 """A connection to another application. In this base class there is
230 assumed to be no bus daemon.
232 :Since: 0.81.0
235 ProxyObjectClass = ProxyObject
237 def __init__(self, *args, **kwargs):
238 super(Connection, self).__init__(*args, **kwargs)
240 # this if-block is needed because shared bus connections can be
241 # __init__'ed more than once
242 if not hasattr(self, '_dbus_Connection_initialized'):
243 self._dbus_Connection_initialized = 1
245 self._signal_recipients_by_object_path = {}
246 """Map from object path to dict mapping dbus_interface to dict
247 mapping member to list of SignalMatch objects."""
249 self._signals_lock = thread.allocate_lock()
250 """Lock used to protect signal data structures"""
252 self.add_message_filter(self.__class__._signal_func)
254 def activate_name_owner(self, bus_name):
255 """Return the unique name for the given bus name, activating it
256 if necessary and possible.
258 If the name is already unique or this connection is not to a
259 bus daemon, just return it.
261 :Returns: a bus name. If the given `bus_name` exists, the returned
262 name identifies its current owner; otherwise the returned name
263 does not exist.
264 :Raises DBusException: if the implementation has failed
265 to activate the given bus name.
266 :Since: 0.81.0
268 return bus_name
270 def get_object(self, bus_name=None, object_path=None, introspect=True,
271 **kwargs):
272 """Return a local proxy for the given remote object.
274 Method calls on the proxy are translated into method calls on the
275 remote object.
277 :Parameters:
278 `bus_name` : str
279 A bus name (either the unique name or a well-known name)
280 of the application owning the object. The keyword argument
281 named_service is a deprecated alias for this.
282 `object_path` : str
283 The object path of the desired object
284 `introspect` : bool
285 If true (default), attempt to introspect the remote
286 object to find out supported methods and their signatures
288 :Returns: a `dbus.proxies.ProxyObject`
290 named_service = kwargs.pop('named_service', None)
291 if named_service is not None:
292 if bus_name is not None:
293 raise TypeError('bus_name and named_service cannot both '
294 'be specified')
295 from warnings import warn
296 warn('Passing the named_service parameter to get_object by name '
297 'is deprecated: please use positional parameters',
298 DeprecationWarning, stacklevel=2)
299 bus_name = named_service
300 if kwargs:
301 raise TypeError('get_object does not take these keyword '
302 'arguments: %s' % ', '.join(kwargs.iterkeys()))
304 return self.ProxyObjectClass(self, bus_name, object_path,
305 introspect=introspect)
307 def add_signal_receiver(self, handler_function,
308 signal_name=None,
309 dbus_interface=None,
310 bus_name=None,
311 path=None,
312 **keywords):
313 """Arrange for the given function to be called when a signal matching
314 the parameters is received.
316 :Parameters:
317 `handler_function` : callable
318 The function to be called. Its positional arguments will
319 be the arguments of the signal. By default it will receive
320 no keyword arguments, but see the description of
321 the optional keyword arguments below.
322 `signal_name` : str
323 The signal name; None (the default) matches all names
324 `dbus_interface` : str
325 The D-Bus interface name with which to qualify the signal;
326 None (the default) matches all interface names
327 `bus_name` : str
328 A bus name for the sender, which will be resolved to a
329 unique name if it is not already; None (the default) matches
330 any sender.
331 `path` : str
332 The object path of the object which must have emitted the
333 signal; None (the default) matches any object path
334 :Keywords:
335 `utf8_strings` : bool
336 If True, the handler function will receive any string
337 arguments as dbus.UTF8String objects (a subclass of str
338 guaranteed to be UTF-8). If False (default) it will receive
339 any string arguments as dbus.String objects (a subclass of
340 unicode).
341 `byte_arrays` : bool
342 If True, the handler function will receive any byte-array
343 arguments as dbus.ByteArray objects (a subclass of str).
344 If False (default) it will receive any byte-array
345 arguments as a dbus.Array of dbus.Byte (subclasses of:
346 a list of ints).
347 `sender_keyword` : str
348 If not None (the default), the handler function will receive
349 the unique name of the sending endpoint as a keyword
350 argument with this name.
351 `destination_keyword` : str
352 If not None (the default), the handler function will receive
353 the bus name of the destination (or None if the signal is a
354 broadcast, as is usual) as a keyword argument with this name.
355 `interface_keyword` : str
356 If not None (the default), the handler function will receive
357 the signal interface as a keyword argument with this name.
358 `member_keyword` : str
359 If not None (the default), the handler function will receive
360 the signal name as a keyword argument with this name.
361 `path_keyword` : str
362 If not None (the default), the handler function will receive
363 the object-path of the sending object as a keyword argument
364 with this name.
365 `message_keyword` : str
366 If not None (the default), the handler function will receive
367 the `dbus.lowlevel.SignalMessage` as a keyword argument with
368 this name.
369 `arg...` : unicode or UTF-8 str
370 If there are additional keyword parameters of the form
371 ``arg``\ *n*, match only signals where the *n*\ th argument
372 is the value given for that keyword parameter. As of this
373 time only string arguments can be matched (in particular,
374 object paths and signatures can't).
375 `named_service` : str
376 A deprecated alias for `bus_name`.
378 self._require_main_loop()
380 named_service = keywords.pop('named_service', None)
381 if named_service is not None:
382 if bus_name is not None:
383 raise TypeError('bus_name and named_service cannot both be '
384 'specified')
385 bus_name = named_service
386 from warnings import warn
387 warn('Passing the named_service parameter to add_signal_receiver '
388 'by name is deprecated: please use positional parameters',
389 DeprecationWarning, stacklevel=2)
391 match = SignalMatch(self, bus_name, path, dbus_interface,
392 signal_name, handler_function, **keywords)
394 self._signals_lock.acquire()
395 try:
396 by_interface = self._signal_recipients_by_object_path.setdefault(
397 path, {})
398 by_member = by_interface.setdefault(dbus_interface, {})
399 matches = by_member.setdefault(signal_name, [])
401 matches.append(match)
402 finally:
403 self._signals_lock.release()
405 return match
407 def _iter_easy_matches(self, path, dbus_interface, member):
408 if path is not None:
409 path_keys = (None, path)
410 else:
411 path_keys = (None,)
412 if dbus_interface is not None:
413 interface_keys = (None, dbus_interface)
414 else:
415 interface_keys = (None,)
416 if member is not None:
417 member_keys = (None, member)
418 else:
419 member_keys = (None,)
421 for path in path_keys:
422 by_interface = self._signal_recipients_by_object_path.get(path,
423 None)
424 if by_interface is None:
425 continue
426 for dbus_interface in interface_keys:
427 by_member = by_interface.get(dbus_interface, None)
428 if by_member is None:
429 continue
430 for member in member_keys:
431 matches = by_member.get(member, None)
432 if matches is None:
433 continue
434 for m in matches:
435 yield m
437 def remove_signal_receiver(self, handler_or_match,
438 signal_name=None,
439 dbus_interface=None,
440 bus_name=None,
441 path=None,
442 **keywords):
443 named_service = keywords.pop('named_service', None)
444 if named_service is not None:
445 if bus_name is not None:
446 raise TypeError('bus_name and named_service cannot both be '
447 'specified')
448 bus_name = named_service
449 from warnings import warn
450 warn('Passing the named_service parameter to '
451 'remove_signal_receiver by name is deprecated: please use '
452 'positional parameters',
453 DeprecationWarning, stacklevel=2)
455 new = []
456 deletions = []
457 self._signals_lock.acquire()
458 try:
459 by_interface = self._signal_recipients_by_object_path.get(path,
460 None)
461 if by_interface is None:
462 return
463 by_member = by_interface.get(dbus_interface, None)
464 if by_member is None:
465 return
466 matches = by_member.get(signal_name, None)
467 if matches is None:
468 return
470 for match in matches:
471 if (handler_or_match is match
472 or match.matches_removal_spec(bus_name,
473 path,
474 dbus_interface,
475 signal_name,
476 handler_or_match,
477 **keywords)):
478 deletions.append(match)
479 else:
480 new.append(match)
481 by_member[signal_name] = new
482 finally:
483 self._signals_lock.release()
485 for match in deletions:
486 self._clean_up_signal_match(match)
488 def _clean_up_signal_match(self, match):
489 # Now called without the signals lock held (it was held in <= 0.81.0)
490 pass
492 def _signal_func(self, message):
493 """D-Bus filter function. Handle signals by dispatching to Python
494 callbacks kept in the match-rule tree.
497 if not isinstance(message, SignalMessage):
498 return HANDLER_RESULT_NOT_YET_HANDLED
500 dbus_interface = message.get_interface()
501 path = message.get_path()
502 signal_name = message.get_member()
504 for match in self._iter_easy_matches(path, dbus_interface,
505 signal_name):
506 match.maybe_handle_message(message)
507 return HANDLER_RESULT_NOT_YET_HANDLED
509 def call_async(self, bus_name, object_path, dbus_interface, method,
510 signature, args, reply_handler, error_handler,
511 timeout=-1.0, utf8_strings=False, byte_arrays=False,
512 require_main_loop=True):
513 """Call the given method, asynchronously.
515 If the reply_handler is None, successful replies will be ignored.
516 If the error_handler is None, failures will be ignored. If both
517 are None, the implementation may request that no reply is sent.
519 :Returns: The dbus.lowlevel.PendingCall.
520 :Since: 0.81.0
522 if object_path == LOCAL_PATH:
523 raise DBusException('Methods may not be called on the reserved '
524 'path %s' % LOCAL_PATH)
525 if dbus_interface == LOCAL_IFACE:
526 raise DBusException('Methods may not be called on the reserved '
527 'interface %s' % LOCAL_IFACE)
528 # no need to validate other args - MethodCallMessage ctor will do
530 get_args_opts = {'utf8_strings': utf8_strings,
531 'byte_arrays': byte_arrays}
533 message = MethodCallMessage(destination=bus_name,
534 path=object_path,
535 interface=dbus_interface,
536 method=method)
537 # Add the arguments to the function
538 try:
539 message.append(signature=signature, *args)
540 except Exception, e:
541 logging.basicConfig()
542 _logger.error('Unable to set arguments %r according to '
543 'signature %r: %s: %s',
544 args, signature, e.__class__, e)
545 raise
547 if reply_handler is None and error_handler is None:
548 # we don't care what happens, so just send it
549 self.send_message(message)
550 return
552 if reply_handler is None:
553 reply_handler = _noop
554 if error_handler is None:
555 error_handler = _noop
557 def msg_reply_handler(message):
558 if isinstance(message, MethodReturnMessage):
559 reply_handler(*message.get_args_list(**get_args_opts))
560 elif isinstance(message, ErrorMessage):
561 error_handler(DBusException(name=message.get_error_name(),
562 *message.get_args_list()))
563 else:
564 error_handler(TypeError('Unexpected type for reply '
565 'message: %r' % message))
566 return self.send_message_with_reply(message, msg_reply_handler,
567 timeout/1000.0,
568 require_main_loop=require_main_loop)
570 def call_blocking(self, bus_name, object_path, dbus_interface, method,
571 signature, args, timeout=-1.0, utf8_strings=False,
572 byte_arrays=False):
573 """Call the given method, synchronously.
574 :Since: 0.81.0
576 if object_path == LOCAL_PATH:
577 raise DBusException('Methods may not be called on the reserved '
578 'path %s' % LOCAL_PATH)
579 if dbus_interface == LOCAL_IFACE:
580 raise DBusException('Methods may not be called on the reserved '
581 'interface %s' % LOCAL_IFACE)
582 # no need to validate other args - MethodCallMessage ctor will do
584 get_args_opts = {'utf8_strings': utf8_strings,
585 'byte_arrays': byte_arrays}
587 message = MethodCallMessage(destination=bus_name,
588 path=object_path,
589 interface=dbus_interface,
590 method=method)
591 # Add the arguments to the function
592 try:
593 message.append(signature=signature, *args)
594 except Exception, e:
595 logging.basicConfig()
596 _logger.error('Unable to set arguments %r according to '
597 'signature %r: %s: %s',
598 args, signature, e.__class__, e)
599 raise
601 # make a blocking call
602 reply_message = self.send_message_with_reply_and_block(
603 message, timeout)
604 args_list = reply_message.get_args_list(**get_args_opts)
605 if len(args_list) == 0:
606 return None
607 elif len(args_list) == 1:
608 return args_list[0]
609 else:
610 return tuple(args_list)