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'
30 import dummy_thread
as thread
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
, \
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
):
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,
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
)
76 self
._conn
_weakref
= weakref
.ref(conn
)
78 self
._interface
= dbus_interface
80 self
._path
= object_path
81 self
._handler
= handler
83 # if the connection is actually a bus, it's responsible for changing
85 self
._sender
_name
_owner
= sender
87 self
._utf
8_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
98 self
._int
_args
_match
= None
100 self
._int
_args
_match
= {}
102 if not kwarg
.startswith('arg'):
103 raise TypeError('SignalMatch: unknown keyword argument %s'
106 index
= int(kwarg
[3:])
108 raise TypeError('SignalMatch: unknown keyword argument %s'
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
]
116 """SignalMatch objects are compared by identity."""
117 return hash(id(self
))
119 def __eq__(self
, other
):
120 """SignalMatch objects are compared by identity."""
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
)
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
)
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
):
159 if sender
!= self
._sender
:
161 if object_path
!= self
._path
:
163 if dbus_interface
!= self
._interface
:
165 if member
!= self
._member
:
167 if kwargs
!= self
._args
_match
:
171 def maybe_handle_message(self
, message
):
174 # these haven't been checked yet by the match tree
175 if self
._sender
_name
_owner
not in (None, message
.get_sender()):
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
):
186 # these have likely already been checked by the match tree
187 if self
._member
not in (None, message
.get_member()):
189 if self
._interface
not in (None, message
.get_interface()):
191 if self
._path
not in (None, message
.get_path()):
195 # minor optimization: if we already extracted the args with the
196 # right calling convention to do the args match, don't bother
198 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
199 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
200 byte_arrays
=self
._byte
_arrays
)
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
)
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)
223 conn
= self
._conn
_weakref
()
224 # do nothing if the connection has already vanished
226 conn
.remove_signal_receiver(self
, self
._member
,
227 self
._interface
, self
._sender
,
232 class Connection(_Connection
):
233 """A connection to another application. In this base class there is
234 assumed to be no bus daemon.
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
268 :Raises DBusException: if the implementation has failed
269 to activate the given bus name.
274 def get_object(self
, bus_name
=None, object_path
=None, introspect
=True,
276 """Return a local proxy for the given remote object.
278 Method calls on the proxy are translated into method calls on the
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.
287 The object path of the desired object
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 '
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
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
,
317 """Arrange for the given function to be called when a signal matching
318 the parameters is received.
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.
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
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
336 The object path of the object which must have emitted the
337 signal; None (the default) matches any object path
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
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:
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.
366 If not None (the default), the handler function will receive
367 the object-path of the sending object as a keyword argument
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
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 '
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()
400 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(
402 by_member
= by_interface
.setdefault(dbus_interface
, {})
403 matches
= by_member
.setdefault(signal_name
, [])
405 matches
.append(match
)
407 self
._signals
_lock
.release()
411 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
413 path_keys
= (None, path
)
416 if dbus_interface
is not None:
417 interface_keys
= (None, dbus_interface
)
419 interface_keys
= (None,)
420 if member
is not None:
421 member_keys
= (None, member
)
423 member_keys
= (None,)
425 for path
in path_keys
:
426 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
428 if by_interface
is None:
430 for dbus_interface
in interface_keys
:
431 by_member
= by_interface
.get(dbus_interface
, None)
432 if by_member
is None:
434 for member
in member_keys
:
435 matches
= by_member
.get(member
, None)
441 def remove_signal_receiver(self
, handler_or_match
,
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 '
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)
461 self
._signals
_lock
.acquire()
463 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
465 if by_interface
is None:
467 by_member
= by_interface
.get(dbus_interface
, None)
468 if by_member
is None:
470 matches
= by_member
.get(signal_name
, None)
474 for match
in matches
:
475 if (handler_or_match
is match
476 or match
.matches_removal_spec(bus_name
,
482 deletions
.append(match
)
485 by_member
[signal_name
] = new
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)
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
,
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.
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
,
539 interface
=dbus_interface
,
541 # Add the arguments to the function
543 message
.append(signature
=signature
, *args
)
545 logging
.basicConfig()
546 _logger
.error('Unable to set arguments %r according to '
547 'signature %r: %s: %s',
548 args
, signature
, e
.__class
__, e
)
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
)
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()))
568 error_handler(TypeError('Unexpected type for reply '
569 'message: %r' % message
))
570 return self
.send_message_with_reply(message
, msg_reply_handler
,
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,
577 """Call the given method, synchronously.
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
,
593 interface
=dbus_interface
,
595 # Add the arguments to the function
597 message
.append(signature
=signature
, *args
)
599 logging
.basicConfig()
600 _logger
.error('Unable to set arguments %r according to '
601 'signature %r: %s: %s',
602 args
, signature
, e
.__class
__, e
)
605 # make a blocking call
606 reply_message
= self
.send_message_with_reply_and_block(
608 args_list
= reply_message
.get_args_list(**get_args_opts
)
609 if len(args_list
) == 0:
611 elif len(args_list
) == 1:
614 return tuple(args_list
)