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 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
):
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,
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
)
72 self
._conn
_weakref
= weakref
.ref(conn
)
74 self
._interface
= dbus_interface
76 self
._path
= object_path
77 self
._handler
= handler
79 # if the connection is actually a bus, it's responsible for changing
81 self
._sender
_name
_owner
= sender
83 self
._utf
8_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
94 self
._int
_args
_match
= None
96 self
._int
_args
_match
= {}
98 if not kwarg
.startswith('arg'):
99 raise TypeError('SignalMatch: unknown keyword argument %s'
102 index
= int(kwarg
[3:])
104 raise TypeError('SignalMatch: unknown keyword argument %s'
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
]
112 """SignalMatch objects are compared by identity."""
113 return hash(id(self
))
115 def __eq__(self
, other
):
116 """SignalMatch objects are compared by identity."""
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
)
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
)
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
):
155 if sender
!= self
._sender
:
157 if object_path
!= self
._path
:
159 if dbus_interface
!= self
._interface
:
161 if member
!= self
._member
:
163 if kwargs
!= self
._args
_match
:
167 def maybe_handle_message(self
, message
):
170 # these haven't been checked yet by the match tree
171 if self
._sender
_name
_owner
not in (None, message
.get_sender()):
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
):
182 # these have likely already been checked by the match tree
183 if self
._member
not in (None, message
.get_member()):
185 if self
._interface
not in (None, message
.get_interface()):
187 if self
._path
not in (None, message
.get_path()):
191 # minor optimization: if we already extracted the args with the
192 # right calling convention to do the args match, don't bother
194 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
195 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
196 byte_arrays
=self
._byte
_arrays
)
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
)
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)
219 conn
= self
._conn
_weakref
()
220 # do nothing if the connection has already vanished
222 conn
.remove_signal_receiver(self
, self
._member
,
223 self
._interface
, self
._sender
,
228 class Connection(_Connection
):
229 """A connection to another application. In this base class there is
230 assumed to be no bus daemon.
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
264 :Raises DBusException: if the implementation has failed
265 to activate the given bus name.
270 def get_object(self
, bus_name
=None, object_path
=None, introspect
=True,
272 """Return a local proxy for the given remote object.
274 Method calls on the proxy are translated into method calls on the
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.
283 The object path of the desired object
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 '
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
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
,
313 """Arrange for the given function to be called when a signal matching
314 the parameters is received.
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.
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
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
332 The object path of the object which must have emitted the
333 signal; None (the default) matches any object path
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
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:
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.
362 If not None (the default), the handler function will receive
363 the object-path of the sending object as a keyword argument
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
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 '
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()
396 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(
398 by_member
= by_interface
.setdefault(dbus_interface
, {})
399 matches
= by_member
.setdefault(signal_name
, [])
401 matches
.append(match
)
403 self
._signals
_lock
.release()
407 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
409 path_keys
= (None, path
)
412 if dbus_interface
is not None:
413 interface_keys
= (None, dbus_interface
)
415 interface_keys
= (None,)
416 if member
is not None:
417 member_keys
= (None, member
)
419 member_keys
= (None,)
421 for path
in path_keys
:
422 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
424 if by_interface
is None:
426 for dbus_interface
in interface_keys
:
427 by_member
= by_interface
.get(dbus_interface
, None)
428 if by_member
is None:
430 for member
in member_keys
:
431 matches
= by_member
.get(member
, None)
437 def remove_signal_receiver(self
, handler_or_match
,
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 '
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)
457 self
._signals
_lock
.acquire()
459 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
461 if by_interface
is None:
463 by_member
= by_interface
.get(dbus_interface
, None)
464 if by_member
is None:
466 matches
= by_member
.get(signal_name
, None)
470 for match
in matches
:
471 if (handler_or_match
is match
472 or match
.matches_removal_spec(bus_name
,
478 deletions
.append(match
)
481 by_member
[signal_name
] = new
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)
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
,
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.
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
,
535 interface
=dbus_interface
,
537 # Add the arguments to the function
539 message
.append(signature
=signature
, *args
)
541 logging
.basicConfig()
542 _logger
.error('Unable to set arguments %r according to '
543 'signature %r: %s: %s',
544 args
, signature
, e
.__class
__, e
)
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
)
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()))
564 error_handler(TypeError('Unexpected type for reply '
565 'message: %r' % message
))
566 return self
.send_message_with_reply(message
, msg_reply_handler
,
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,
573 """Call the given method, synchronously.
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
,
589 interface
=dbus_interface
,
591 # Add the arguments to the function
593 message
.append(signature
=signature
, *args
)
595 logging
.basicConfig()
596 _logger
.error('Unable to set arguments %r according to '
597 'signature %r: %s: %s',
598 args
, signature
, e
.__class
__, e
)
601 # make a blocking call
602 reply_message
= self
.send_message_with_reply_and_block(
604 args_list
= reply_message
.get_args_list(**get_args_opts
)
605 if len(args_list
) == 0:
607 elif len(args_list
) == 1:
610 return tuple(args_list
)