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.
233 ProxyObjectClass
= ProxyObject
235 def __init__(self
, *args
, **kwargs
):
236 super(Connection
, self
).__init
__(*args
, **kwargs
)
238 # this if-block is needed because shared bus connections can be
239 # __init__'ed more than once
240 if not hasattr(self
, '_dbus_Connection_initialized'):
241 self
._dbus
_Connection
_initialized
= 1
243 self
._signal
_recipients
_by
_object
_path
= {}
244 """Map from object path to dict mapping dbus_interface to dict
245 mapping member to list of SignalMatch objects."""
247 self
._signals
_lock
= thread
.allocate_lock()
248 """Lock used to protect signal data structures"""
250 self
.add_message_filter(self
.__class
__._signal
_func
)
252 def activate_name_owner(self
, bus_name
):
253 """Return the unique name for the given bus name, activating it
254 if necessary and possible.
256 If the name is already unique or this connection is not to a
257 bus daemon, just return it.
259 :Returns: a bus name. If the given `bus_name` exists, the returned
260 name identifies its current owner; otherwise the returned name
262 :Raises DBusException: if the implementation has failed
263 to activate the given bus name.
267 def get_object(self
, bus_name
=None, object_path
=None, introspect
=True,
269 """Return a local proxy for the given remote object.
271 Method calls on the proxy are translated into method calls on the
276 A bus name (either the unique name or a well-known name)
277 of the application owning the object. The keyword argument
278 named_service is a deprecated alias for this.
280 The object path of the desired object
282 If true (default), attempt to introspect the remote
283 object to find out supported methods and their signatures
285 :Returns: a `dbus.proxies.ProxyObject`
287 named_service
= kwargs
.pop('named_service', None)
288 if named_service
is not None:
289 if bus_name
is not None:
290 raise TypeError('bus_name and named_service cannot both '
292 from warnings
import warn
293 warn('Passing the named_service parameter to get_object by name '
294 'is deprecated: please use positional parameters',
295 DeprecationWarning, stacklevel
=2)
296 bus_name
= named_service
298 raise TypeError('get_object does not take these keyword '
299 'arguments: %s' % ', '.join(kwargs
.iterkeys()))
301 return self
.ProxyObjectClass(self
, bus_name
, object_path
,
302 introspect
=introspect
)
304 def add_signal_receiver(self
, handler_function
,
310 """Arrange for the given function to be called when a signal matching
311 the parameters is received.
314 `handler_function` : callable
315 The function to be called. Its positional arguments will
316 be the arguments of the signal. By default it will receive
317 no keyword arguments, but see the description of
318 the optional keyword arguments below.
320 The signal name; None (the default) matches all names
321 `dbus_interface` : str
322 The D-Bus interface name with which to qualify the signal;
323 None (the default) matches all interface names
325 A bus name for the sender, which will be resolved to a
326 unique name if it is not already; None (the default) matches
329 The object path of the object which must have emitted the
330 signal; None (the default) matches any object path
332 `utf8_strings` : bool
333 If True, the handler function will receive any string
334 arguments as dbus.UTF8String objects (a subclass of str
335 guaranteed to be UTF-8). If False (default) it will receive
336 any string arguments as dbus.String objects (a subclass of
339 If True, the handler function will receive any byte-array
340 arguments as dbus.ByteArray objects (a subclass of str).
341 If False (default) it will receive any byte-array
342 arguments as a dbus.Array of dbus.Byte (subclasses of:
344 `sender_keyword` : str
345 If not None (the default), the handler function will receive
346 the unique name of the sending endpoint as a keyword
347 argument with this name.
348 `destination_keyword` : str
349 If not None (the default), the handler function will receive
350 the bus name of the destination (or None if the signal is a
351 broadcast, as is usual) as a keyword argument with this name.
352 `interface_keyword` : str
353 If not None (the default), the handler function will receive
354 the signal interface as a keyword argument with this name.
355 `member_keyword` : str
356 If not None (the default), the handler function will receive
357 the signal name as a keyword argument with this name.
359 If not None (the default), the handler function will receive
360 the object-path of the sending object as a keyword argument
362 `message_keyword` : str
363 If not None (the default), the handler function will receive
364 the `dbus.lowlevel.SignalMessage` as a keyword argument with
366 `arg...` : unicode or UTF-8 str
367 If there are additional keyword parameters of the form
368 ``arg``\ *n*, match only signals where the *n*\ th argument
369 is the value given for that keyword parameter. As of this
370 time only string arguments can be matched (in particular,
371 object paths and signatures can't).
372 `named_service` : str
373 A deprecated alias for `bus_name`.
375 self
._require
_main
_loop
()
377 named_service
= keywords
.pop('named_service', None)
378 if named_service
is not None:
379 if bus_name
is not None:
380 raise TypeError('bus_name and named_service cannot both be '
382 bus_name
= named_service
383 from warnings
import warn
384 warn('Passing the named_service parameter to add_signal_receiver '
385 'by name is deprecated: please use positional parameters',
386 DeprecationWarning, stacklevel
=2)
388 match
= SignalMatch(self
, bus_name
, path
, dbus_interface
,
389 signal_name
, handler_function
, **keywords
)
391 self
._signals
_lock
.acquire()
393 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(
395 by_member
= by_interface
.setdefault(dbus_interface
, {})
396 matches
= by_member
.setdefault(signal_name
, [])
398 matches
.append(match
)
400 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)
454 self
._signals
_lock
.acquire()
456 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
458 if by_interface
is None:
460 by_member
= by_interface
.get(dbus_interface
, None)
461 if by_member
is None:
463 matches
= by_member
.get(signal_name
, None)
467 for match
in matches
:
468 if (handler_or_match
is match
469 or match
.matches_removal_spec(bus_name
,
475 deletions
.append(match
)
478 by_member
[signal_name
] = new
480 self
._signals
_lock
.release()
482 for match
in deletions
:
483 self
._clean
_up
_signal
_match
(match
)
485 def _clean_up_signal_match(self
, match
):
486 # Now called without the signals lock held (it was held in <= 0.81.0)
489 def _signal_func(self
, message
):
490 """D-Bus filter function. Handle signals by dispatching to Python
491 callbacks kept in the match-rule tree.
494 if not isinstance(message
, SignalMessage
):
495 return HANDLER_RESULT_NOT_YET_HANDLED
497 dbus_interface
= message
.get_interface()
498 path
= message
.get_path()
499 signal_name
= message
.get_member()
501 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
503 match
.maybe_handle_message(message
)
504 return HANDLER_RESULT_NOT_YET_HANDLED
506 def call_async(self
, bus_name
, object_path
, dbus_interface
, method
,
507 signature
, args
, reply_handler
, error_handler
,
508 timeout
=-1.0, utf8_strings
=False, byte_arrays
=False,
509 require_main_loop
=True):
510 """Call the given method, asynchronously.
512 If the reply_handler is None, successful replies will be ignored.
513 If the error_handler is None, failures will be ignored. If both
514 are None, the implementation may request that no reply is sent.
516 :Returns: The dbus.lowlevel.PendingCall.
518 if object_path
== LOCAL_PATH
:
519 raise DBusException('Methods may not be called on the reserved '
520 'path %s' % LOCAL_PATH
)
521 if dbus_interface
== LOCAL_IFACE
:
522 raise DBusException('Methods may not be called on the reserved '
523 'interface %s' % LOCAL_IFACE
)
524 # no need to validate other args - MethodCallMessage ctor will do
526 get_args_opts
= {'utf8_strings': utf8_strings
,
527 'byte_arrays': byte_arrays
}
529 message
= MethodCallMessage(destination
=bus_name
,
531 interface
=dbus_interface
,
533 # Add the arguments to the function
535 message
.append(signature
=signature
, *args
)
537 logging
.basicConfig()
538 _logger
.error('Unable to set arguments %r according to '
539 'signature %r: %s: %s',
540 args
, signature
, e
.__class
__, e
)
543 if reply_handler
is None and error_handler
is None:
544 # we don't care what happens, so just send it
545 self
.send_message(message
)
548 if reply_handler
is None:
549 reply_handler
= _noop
550 if error_handler
is None:
551 error_handler
= _noop
553 def msg_reply_handler(message
):
554 if isinstance(message
, MethodReturnMessage
):
555 reply_handler(*message
.get_args_list(**get_args_opts
))
556 elif isinstance(message
, ErrorMessage
):
557 error_handler(DBusException(name
=message
.get_error_name(),
558 *message
.get_args_list()))
560 error_handler(TypeError('Unexpected type for reply '
561 'message: %r' % message
))
562 return self
.send_message_with_reply(message
, msg_reply_handler
,
564 require_main_loop
=require_main_loop
)
566 def call_blocking(self
, bus_name
, object_path
, dbus_interface
, method
,
567 signature
, args
, timeout
=-1.0, utf8_strings
=False,
569 """Call the given method, synchronously.
571 if object_path
== LOCAL_PATH
:
572 raise DBusException('Methods may not be called on the reserved '
573 'path %s' % LOCAL_PATH
)
574 if dbus_interface
== LOCAL_IFACE
:
575 raise DBusException('Methods may not be called on the reserved '
576 'interface %s' % LOCAL_IFACE
)
577 # no need to validate other args - MethodCallMessage ctor will do
579 get_args_opts
= {'utf8_strings': utf8_strings
,
580 'byte_arrays': byte_arrays
}
582 message
= MethodCallMessage(destination
=bus_name
,
584 interface
=dbus_interface
,
586 # Add the arguments to the function
588 message
.append(signature
=signature
, *args
)
590 logging
.basicConfig()
591 _logger
.error('Unable to set arguments %r according to '
592 'signature %r: %s: %s',
593 args
, signature
, e
.__class
__, e
)
596 # make a blocking call
597 reply_message
= self
.send_message_with_reply_and_block(
599 args_list
= reply_message
.get_args_list(**get_args_opts
)
600 if len(args_list
) == 0:
602 elif len(args_list
) == 1:
605 return tuple(args_list
)