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'
26 import dummy_thread
as thread
29 from _dbus_bindings
import Connection
as _Connection
, ErrorMessage
, \
30 MethodCallMessage
, MethodReturnMessage
, \
31 DBusException
, 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
.proxies
import ProxyObject
40 _logger
= logging
.getLogger('dbus.connection')
43 def _noop(*args
, **kwargs
):
47 class SignalMatch(object):
48 __slots__
= ('_sender_name_owner', '_member', '_interface', '_sender',
49 '_path', '_handler', '_args_match', '_rule',
50 '_utf8_strings', '_byte_arrays', '_conn_weakref',
51 '_destination_keyword', '_interface_keyword',
52 '_message_keyword', '_member_keyword',
53 '_sender_keyword', '_path_keyword', '_int_args_match')
55 def __init__(self
, conn
, sender
, object_path
, dbus_interface
,
56 member
, handler
, utf8_strings
=False, byte_arrays
=False,
57 sender_keyword
=None, path_keyword
=None,
58 interface_keyword
=None, member_keyword
=None,
59 message_keyword
=None, destination_keyword
=None,
61 if member
is not None:
62 validate_member_name(member
)
63 if dbus_interface
is not None:
64 validate_interface_name(dbus_interface
)
65 if sender
is not None:
66 validate_bus_name(sender
)
67 if object_path
is not None:
68 validate_object_path(object_path
)
71 self
._conn
_weakref
= weakref
.ref(conn
)
73 self
._interface
= dbus_interface
75 self
._path
= object_path
76 self
._handler
= handler
78 # if the connection is actually a bus, it's responsible for changing
80 self
._sender
_name
_owner
= sender
82 self
._utf
8_strings
= utf8_strings
83 self
._byte
_arrays
= byte_arrays
84 self
._sender
_keyword
= sender_keyword
85 self
._path
_keyword
= path_keyword
86 self
._member
_keyword
= member_keyword
87 self
._interface
_keyword
= interface_keyword
88 self
._message
_keyword
= message_keyword
89 self
._destination
_keyword
= destination_keyword
91 self
._args
_match
= kwargs
93 self
._int
_args
_match
= None
95 self
._int
_args
_match
= {}
97 if not kwarg
.startswith('arg'):
98 raise TypeError('SignalMatch: unknown keyword argument %s'
101 index
= int(kwarg
[3:])
103 raise TypeError('SignalMatch: unknown keyword argument %s'
105 if index
< 0 or index
> 63:
106 raise TypeError('SignalMatch: arg match index must be in '
107 'range(64), not %d' % index
)
108 self
._int
_args
_match
[index
] = kwargs
[kwarg
]
111 """SignalMatch objects are compared by identity."""
112 return hash(id(self
))
114 def __eq__(self
, other
):
115 """SignalMatch objects are compared by identity."""
118 def __ne__(self
, other
):
119 """SignalMatch objects are compared by identity."""
120 return self
is not other
122 sender
= property(lambda self
: self
._sender
)
125 if self
._rule
is None:
126 rule
= ["type='signal'"]
127 if self
._sender
is not None:
128 rule
.append("sender='%s'" % self
._sender
)
129 if self
._path
is not None:
130 rule
.append("path='%s'" % self
._path
)
131 if self
._interface
is not None:
132 rule
.append("interface='%s'" % self
._interface
)
133 if self
._member
is not None:
134 rule
.append("member='%s'" % self
._member
)
135 if self
._int
_args
_match
is not None:
136 for index
, value
in self
._int
_args
_match
.iteritems():
137 rule
.append("arg%d='%s'" % (index
, value
))
139 self
._rule
= ','.join(rule
)
144 return ('<%s at %x "%s" on conn %r>'
145 % (self
.__class
__, id(self
), self
._rule
, self
._conn
_weakref
()))
147 def set_sender_name_owner(self
, new_name
):
148 self
._sender
_name
_owner
= new_name
150 def matches_removal_spec(self
, sender
, object_path
,
151 dbus_interface
, member
, handler
, **kwargs
):
152 if handler
not in (None, self
._handler
):
154 if sender
!= self
._sender
:
156 if object_path
!= self
._path
:
158 if dbus_interface
!= self
._interface
:
160 if member
!= self
._member
:
162 if kwargs
!= self
._args
_match
:
166 def maybe_handle_message(self
, message
):
169 # these haven't been checked yet by the match tree
170 if self
._sender
_name
_owner
not in (None, message
.get_sender()):
172 if self
._int
_args
_match
is not None:
173 # extracting args with utf8_strings and byte_arrays is less work
174 args
= message
.get_args_list(utf8_strings
=True, byte_arrays
=True)
175 for index
, value
in self
._int
_args
_match
.iteritems():
176 if (index
>= len(args
)
177 or not isinstance(args
[index
], UTF8String
)
178 or args
[index
] != value
):
181 # these have likely already been checked by the match tree
182 if self
._member
not in (None, message
.get_member()):
184 if self
._interface
not in (None, message
.get_interface()):
186 if self
._path
not in (None, message
.get_path()):
190 # minor optimization: if we already extracted the args with the
191 # right calling convention to do the args match, don't bother
193 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
194 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
195 byte_arrays
=self
._byte
_arrays
)
197 if self
._sender
_keyword
is not None:
198 kwargs
[self
._sender
_keyword
] = message
.get_sender()
199 if self
._destination
_keyword
is not None:
200 kwargs
[self
._destination
_keyword
] = message
.get_destination()
201 if self
._path
_keyword
is not None:
202 kwargs
[self
._path
_keyword
] = message
.get_path()
203 if self
._member
_keyword
is not None:
204 kwargs
[self
._member
_keyword
] = message
.get_member()
205 if self
._interface
_keyword
is not None:
206 kwargs
[self
._interface
_keyword
] = message
.get_interface()
207 if self
._message
_keyword
is not None:
208 kwargs
[self
._message
_keyword
] = message
209 self
._handler
(*args
, **kwargs
)
211 # basicConfig is a no-op if logging is already configured
212 logging
.basicConfig()
213 _logger
.error('Exception in handler for D-Bus signal:', exc_info
=1)
218 conn
= self
._conn
_weakref
()
219 # do nothing if the connection has already vanished
221 conn
.remove_signal_receiver(self
, self
._member
,
222 self
._interface
, self
._sender
,
227 class Connection(_Connection
):
228 """A connection to another application. In this base class there is
229 assumed to be no bus daemon.
232 ProxyObjectClass
= ProxyObject
234 def __init__(self
, *args
, **kwargs
):
235 super(Connection
, self
).__init
__(*args
, **kwargs
)
237 # this if-block is needed because shared bus connections can be
238 # __init__'ed more than once
239 if not hasattr(self
, '_dbus_Connection_initialized'):
240 self
._dbus
_Connection
_initialized
= 1
242 self
._signal
_recipients
_by
_object
_path
= {}
243 """Map from object path to dict mapping dbus_interface to dict
244 mapping member to list of SignalMatch objects."""
246 self
._signals
_lock
= thread
.allocate_lock()
247 """Lock used to protect signal data structures if doing two
248 removals at the same time (everything else is atomic, thanks to
251 self
.add_message_filter(self
.__class
__._signal
_func
)
253 def activate_name_owner(self
, bus_name
):
254 """Return the unique name for the given bus name, activating it
255 if necessary and possible.
257 If the name is already unique or this connection is not to a
258 bus daemon, just return it.
260 :Returns: a bus name. If the given `bus_name` exists, the returned
261 name identifies its current owner; otherwise the returned name
263 :Raises DBusException: if the implementation has failed
264 to activate the given bus name.
268 def get_object(self
, bus_name
=None, object_path
=None, introspect
=True,
270 """Return a local proxy for the given remote object.
272 Method calls on the proxy are translated into method calls on the
277 A bus name (either the unique name or a well-known name)
278 of the application owning the object. The keyword argument
279 named_service is a deprecated alias for this.
281 The object path of the desired object
283 If true (default), attempt to introspect the remote
284 object to find out supported methods and their signatures
286 :Returns: a `dbus.proxies.ProxyObject`
288 named_service
= kwargs
.pop('named_service', None)
289 if named_service
is not None:
290 if bus_name
is not None:
291 raise TypeError('bus_name and named_service cannot both '
293 from warnings
import warn
294 warn('Passing the named_service parameter to get_object by name '
295 'is deprecated: please use positional parameters',
296 DeprecationWarning, stacklevel
=2)
297 bus_name
= named_service
299 raise TypeError('get_object does not take these keyword '
300 'arguments: %s' % ', '.join(kwargs
.iterkeys()))
302 return self
.ProxyObjectClass(self
, bus_name
, object_path
,
303 introspect
=introspect
)
305 def add_signal_receiver(self
, handler_function
,
311 """Arrange for the given function to be called when a signal matching
312 the parameters is received.
315 `handler_function` : callable
316 The function to be called. Its positional arguments will
317 be the arguments of the signal. By default it will receive
318 no keyword arguments, but see the description of
319 the optional keyword arguments below.
321 The signal name; None (the default) matches all names
322 `dbus_interface` : str
323 The D-Bus interface name with which to qualify the signal;
324 None (the default) matches all interface names
326 A bus name for the sender, which will be resolved to a
327 unique name if it is not already; None (the default) matches
330 The object path of the object which must have emitted the
331 signal; None (the default) matches any object path
333 `utf8_strings` : bool
334 If True, the handler function will receive any string
335 arguments as dbus.UTF8String objects (a subclass of str
336 guaranteed to be UTF-8). If False (default) it will receive
337 any string arguments as dbus.String objects (a subclass of
340 If True, the handler function will receive any byte-array
341 arguments as dbus.ByteArray objects (a subclass of str).
342 If False (default) it will receive any byte-array
343 arguments as a dbus.Array of dbus.Byte (subclasses of:
345 `sender_keyword` : str
346 If not None (the default), the handler function will receive
347 the unique name of the sending endpoint as a keyword
348 argument with this name.
349 `destination_keyword` : str
350 If not None (the default), the handler function will receive
351 the bus name of the destination (or None if the signal is a
352 broadcast, as is usual) as a keyword argument with this name.
353 `interface_keyword` : str
354 If not None (the default), the handler function will receive
355 the signal interface as a keyword argument with this name.
356 `member_keyword` : str
357 If not None (the default), the handler function will receive
358 the signal name as a keyword argument with this name.
360 If not None (the default), the handler function will receive
361 the object-path of the sending object as a keyword argument
363 `message_keyword` : str
364 If not None (the default), the handler function will receive
365 the `dbus.lowlevel.SignalMessage` as a keyword argument with
367 `arg...` : unicode or UTF-8 str
368 If there are additional keyword parameters of the form
369 ``arg``\ *n*, match only signals where the *n*\ th argument
370 is the value given for that keyword parameter. As of this
371 time only string arguments can be matched (in particular,
372 object paths and signatures can't).
373 `named_service` : str
374 A deprecated alias for `bus_name`.
376 self
._require
_main
_loop
()
378 named_service
= keywords
.pop('named_service', None)
379 if named_service
is not None:
380 if bus_name
is not None:
381 raise TypeError('bus_name and named_service cannot both be '
383 bus_name
= named_service
384 from warnings
import warn
385 warn('Passing the named_service parameter to add_signal_receiver '
386 'by name is deprecated: please use positional parameters',
387 DeprecationWarning, stacklevel
=2)
389 match
= SignalMatch(self
, bus_name
, path
, dbus_interface
,
390 signal_name
, handler_function
, **keywords
)
391 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
393 by_member
= by_interface
.setdefault(dbus_interface
, {})
394 matches
= by_member
.setdefault(signal_name
, [])
396 # make sure nobody is currently manipulating the list
397 self
._signals
_lock
.acquire()
399 matches
.append(match
)
401 self
._signals
_lock
.release()
404 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
406 path_keys
= (None, path
)
409 if dbus_interface
is not None:
410 interface_keys
= (None, dbus_interface
)
412 interface_keys
= (None,)
413 if member
is not None:
414 member_keys
= (None, member
)
416 member_keys
= (None,)
418 for path
in path_keys
:
419 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
421 if by_interface
is None:
423 for dbus_interface
in interface_keys
:
424 by_member
= by_interface
.get(dbus_interface
, None)
425 if by_member
is None:
427 for member
in member_keys
:
428 matches
= by_member
.get(member
, None)
434 def remove_signal_receiver(self
, handler_or_match
,
440 named_service
= keywords
.pop('named_service', None)
441 if named_service
is not None:
442 if bus_name
is not None:
443 raise TypeError('bus_name and named_service cannot both be '
445 bus_name
= named_service
446 from warnings
import warn
447 warn('Passing the named_service parameter to '
448 'remove_signal_receiver by name is deprecated: please use '
449 'positional parameters',
450 DeprecationWarning, stacklevel
=2)
452 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
453 if by_interface
is None:
455 by_member
= by_interface
.get(dbus_interface
, None)
456 if by_member
is None:
458 matches
= by_member
.get(signal_name
, None)
461 self
._signals
_lock
.acquire()
464 for match
in matches
:
465 if (handler_or_match
is match
466 or match
.matches_removal_spec(bus_name
,
472 self
._clean
_up
_signal
_match
(match
)
475 by_member
[signal_name
] = new
477 self
._signals
_lock
.release()
479 def _clean_up_signal_match(self
, match
):
480 # Called with the signals lock held
483 def _signal_func(self
, message
):
484 """D-Bus filter function. Handle signals by dispatching to Python
485 callbacks kept in the match-rule tree.
488 if not isinstance(message
, SignalMessage
):
489 return HANDLER_RESULT_NOT_YET_HANDLED
491 dbus_interface
= message
.get_interface()
492 path
= message
.get_path()
493 signal_name
= message
.get_member()
495 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
497 match
.maybe_handle_message(message
)
498 return HANDLER_RESULT_NOT_YET_HANDLED
500 def call_async(self
, bus_name
, object_path
, dbus_interface
, method
,
501 signature
, args
, reply_handler
, error_handler
,
502 timeout
=-1.0, utf8_strings
=False, byte_arrays
=False,
503 require_main_loop
=True):
504 """Call the given method, asynchronously.
506 If the reply_handler is None, successful replies will be ignored.
507 If the error_handler is None, failures will be ignored. If both
508 are None, the implementation may request that no reply is sent.
510 :Returns: The dbus.lowlevel.PendingCall.
512 if object_path
== LOCAL_PATH
:
513 raise DBusException('Methods may not be called on the reserved '
514 'path %s' % LOCAL_PATH
)
515 if dbus_interface
== LOCAL_IFACE
:
516 raise DBusException('Methods may not be called on the reserved '
517 'interface %s' % LOCAL_IFACE
)
518 # no need to validate other args - MethodCallMessage ctor will do
520 get_args_opts
= {'utf8_strings': utf8_strings
,
521 'byte_arrays': byte_arrays
}
523 message
= MethodCallMessage(destination
=bus_name
,
525 interface
=dbus_interface
,
527 # Add the arguments to the function
529 message
.append(signature
=signature
, *args
)
531 logging
.basicConfig()
532 _logger
.error('Unable to set arguments %r according to '
533 'signature %r: %s: %s',
534 args
, signature
, e
.__class
__, e
)
537 if reply_handler
is None and error_handler
is None:
538 # we don't care what happens, so just send it
539 self
.send_message(message
)
542 if reply_handler
is None:
543 reply_handler
= _noop
544 if error_handler
is None:
545 error_handler
= _noop
547 def msg_reply_handler(message
):
548 if isinstance(message
, MethodReturnMessage
):
549 reply_handler(*message
.get_args_list(**get_args_opts
))
550 elif isinstance(message
, ErrorMessage
):
551 args
= message
.get_args_list()
552 # FIXME: should we do something with the rest?
554 error_handler(DBusException(args
[0]))
556 error_handler(DBusException())
558 error_handler(TypeError('Unexpected type for reply '
559 'message: %r' % message
))
560 return self
.send_message_with_reply(message
, msg_reply_handler
,
562 require_main_loop
=require_main_loop
)
564 def call_blocking(self
, bus_name
, object_path
, dbus_interface
, method
,
565 signature
, args
, timeout
=-1.0, utf8_strings
=False,
567 """Call the given method, synchronously.
569 if object_path
== LOCAL_PATH
:
570 raise DBusException('Methods may not be called on the reserved '
571 'path %s' % LOCAL_PATH
)
572 if dbus_interface
== LOCAL_IFACE
:
573 raise DBusException('Methods may not be called on the reserved '
574 'interface %s' % LOCAL_IFACE
)
575 # no need to validate other args - MethodCallMessage ctor will do
577 get_args_opts
= {'utf8_strings': utf8_strings
,
578 'byte_arrays': byte_arrays
}
580 message
= MethodCallMessage(destination
=bus_name
,
582 interface
=dbus_interface
,
584 # Add the arguments to the function
586 message
.append(signature
=signature
, *args
)
588 logging
.basicConfig()
589 _logger
.error('Unable to set arguments %r according to '
590 'signature %r: %s: %s',
591 args
, signature
, e
.__class
__, e
)
594 # make a blocking call
595 reply_message
= self
.send_message_with_reply_and_block(
597 args_list
= reply_message
.get_args_list(**get_args_opts
)
598 if len(args_list
) == 0:
600 elif len(args_list
) == 1:
603 return tuple(args_list
)