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 if doing two
249 removals at the same time (everything else is atomic, thanks to
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.
269 def get_object(self
, bus_name
=None, object_path
=None, introspect
=True,
271 """Return a local proxy for the given remote object.
273 Method calls on the proxy are translated into method calls on the
278 A bus name (either the unique name or a well-known name)
279 of the application owning the object. The keyword argument
280 named_service is a deprecated alias for this.
282 The object path of the desired object
284 If true (default), attempt to introspect the remote
285 object to find out supported methods and their signatures
287 :Returns: a `dbus.proxies.ProxyObject`
289 named_service
= kwargs
.pop('named_service', None)
290 if named_service
is not None:
291 if bus_name
is not None:
292 raise TypeError('bus_name and named_service cannot both '
294 from warnings
import warn
295 warn('Passing the named_service parameter to get_object by name '
296 'is deprecated: please use positional parameters',
297 DeprecationWarning, stacklevel
=2)
298 bus_name
= named_service
300 raise TypeError('get_object does not take these keyword '
301 'arguments: %s' % ', '.join(kwargs
.iterkeys()))
303 return self
.ProxyObjectClass(self
, bus_name
, object_path
,
304 introspect
=introspect
)
306 def add_signal_receiver(self
, handler_function
,
312 """Arrange for the given function to be called when a signal matching
313 the parameters is received.
316 `handler_function` : callable
317 The function to be called. Its positional arguments will
318 be the arguments of the signal. By default it will receive
319 no keyword arguments, but see the description of
320 the optional keyword arguments below.
322 The signal name; None (the default) matches all names
323 `dbus_interface` : str
324 The D-Bus interface name with which to qualify the signal;
325 None (the default) matches all interface names
327 A bus name for the sender, which will be resolved to a
328 unique name if it is not already; None (the default) matches
331 The object path of the object which must have emitted the
332 signal; None (the default) matches any object path
334 `utf8_strings` : bool
335 If True, the handler function will receive any string
336 arguments as dbus.UTF8String objects (a subclass of str
337 guaranteed to be UTF-8). If False (default) it will receive
338 any string arguments as dbus.String objects (a subclass of
341 If True, the handler function will receive any byte-array
342 arguments as dbus.ByteArray objects (a subclass of str).
343 If False (default) it will receive any byte-array
344 arguments as a dbus.Array of dbus.Byte (subclasses of:
346 `sender_keyword` : str
347 If not None (the default), the handler function will receive
348 the unique name of the sending endpoint as a keyword
349 argument with this name.
350 `destination_keyword` : str
351 If not None (the default), the handler function will receive
352 the bus name of the destination (or None if the signal is a
353 broadcast, as is usual) as a keyword argument with this name.
354 `interface_keyword` : str
355 If not None (the default), the handler function will receive
356 the signal interface as a keyword argument with this name.
357 `member_keyword` : str
358 If not None (the default), the handler function will receive
359 the signal name as a keyword argument with this name.
361 If not None (the default), the handler function will receive
362 the object-path of the sending object as a keyword argument
364 `message_keyword` : str
365 If not None (the default), the handler function will receive
366 the `dbus.lowlevel.SignalMessage` as a keyword argument with
368 `arg...` : unicode or UTF-8 str
369 If there are additional keyword parameters of the form
370 ``arg``\ *n*, match only signals where the *n*\ th argument
371 is the value given for that keyword parameter. As of this
372 time only string arguments can be matched (in particular,
373 object paths and signatures can't).
374 `named_service` : str
375 A deprecated alias for `bus_name`.
377 self
._require
_main
_loop
()
379 named_service
= keywords
.pop('named_service', None)
380 if named_service
is not None:
381 if bus_name
is not None:
382 raise TypeError('bus_name and named_service cannot both be '
384 bus_name
= named_service
385 from warnings
import warn
386 warn('Passing the named_service parameter to add_signal_receiver '
387 'by name is deprecated: please use positional parameters',
388 DeprecationWarning, stacklevel
=2)
390 match
= SignalMatch(self
, bus_name
, path
, dbus_interface
,
391 signal_name
, handler_function
, **keywords
)
392 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
394 by_member
= by_interface
.setdefault(dbus_interface
, {})
395 matches
= by_member
.setdefault(signal_name
, [])
397 # make sure nobody is currently manipulating the list
398 self
._signals
_lock
.acquire()
400 matches
.append(match
)
402 self
._signals
_lock
.release()
405 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
407 path_keys
= (None, path
)
410 if dbus_interface
is not None:
411 interface_keys
= (None, dbus_interface
)
413 interface_keys
= (None,)
414 if member
is not None:
415 member_keys
= (None, member
)
417 member_keys
= (None,)
419 for path
in path_keys
:
420 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
422 if by_interface
is None:
424 for dbus_interface
in interface_keys
:
425 by_member
= by_interface
.get(dbus_interface
, None)
426 if by_member
is None:
428 for member
in member_keys
:
429 matches
= by_member
.get(member
, None)
435 def remove_signal_receiver(self
, handler_or_match
,
441 named_service
= keywords
.pop('named_service', None)
442 if named_service
is not None:
443 if bus_name
is not None:
444 raise TypeError('bus_name and named_service cannot both be '
446 bus_name
= named_service
447 from warnings
import warn
448 warn('Passing the named_service parameter to '
449 'remove_signal_receiver by name is deprecated: please use '
450 'positional parameters',
451 DeprecationWarning, stacklevel
=2)
453 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
454 if by_interface
is None:
456 by_member
= by_interface
.get(dbus_interface
, None)
457 if by_member
is None:
459 matches
= by_member
.get(signal_name
, None)
462 self
._signals
_lock
.acquire()
465 for match
in matches
:
466 if (handler_or_match
is match
467 or match
.matches_removal_spec(bus_name
,
473 self
._clean
_up
_signal
_match
(match
)
476 by_member
[signal_name
] = new
478 self
._signals
_lock
.release()
480 def _clean_up_signal_match(self
, match
):
481 # Called with the signals lock held
484 def _signal_func(self
, message
):
485 """D-Bus filter function. Handle signals by dispatching to Python
486 callbacks kept in the match-rule tree.
489 if not isinstance(message
, SignalMessage
):
490 return HANDLER_RESULT_NOT_YET_HANDLED
492 dbus_interface
= message
.get_interface()
493 path
= message
.get_path()
494 signal_name
= message
.get_member()
496 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
498 match
.maybe_handle_message(message
)
499 return HANDLER_RESULT_NOT_YET_HANDLED
501 def call_async(self
, bus_name
, object_path
, dbus_interface
, method
,
502 signature
, args
, reply_handler
, error_handler
,
503 timeout
=-1.0, utf8_strings
=False, byte_arrays
=False,
504 require_main_loop
=True):
505 """Call the given method, asynchronously.
507 If the reply_handler is None, successful replies will be ignored.
508 If the error_handler is None, failures will be ignored. If both
509 are None, the implementation may request that no reply is sent.
511 :Returns: The dbus.lowlevel.PendingCall.
513 if object_path
== LOCAL_PATH
:
514 raise DBusException('Methods may not be called on the reserved '
515 'path %s' % LOCAL_PATH
)
516 if dbus_interface
== LOCAL_IFACE
:
517 raise DBusException('Methods may not be called on the reserved '
518 'interface %s' % LOCAL_IFACE
)
519 # no need to validate other args - MethodCallMessage ctor will do
521 get_args_opts
= {'utf8_strings': utf8_strings
,
522 'byte_arrays': byte_arrays
}
524 message
= MethodCallMessage(destination
=bus_name
,
526 interface
=dbus_interface
,
528 # Add the arguments to the function
530 message
.append(signature
=signature
, *args
)
532 logging
.basicConfig()
533 _logger
.error('Unable to set arguments %r according to '
534 'signature %r: %s: %s',
535 args
, signature
, e
.__class
__, e
)
538 if reply_handler
is None and error_handler
is None:
539 # we don't care what happens, so just send it
540 self
.send_message(message
)
543 if reply_handler
is None:
544 reply_handler
= _noop
545 if error_handler
is None:
546 error_handler
= _noop
548 def msg_reply_handler(message
):
549 if isinstance(message
, MethodReturnMessage
):
550 reply_handler(*message
.get_args_list(**get_args_opts
))
551 elif isinstance(message
, ErrorMessage
):
552 error_handler(DBusException(name
=message
.get_error_name(),
553 *message
.get_args_list()))
555 error_handler(TypeError('Unexpected type for reply '
556 'message: %r' % message
))
557 return self
.send_message_with_reply(message
, msg_reply_handler
,
559 require_main_loop
=require_main_loop
)
561 def call_blocking(self
, bus_name
, object_path
, dbus_interface
, method
,
562 signature
, args
, timeout
=-1.0, utf8_strings
=False,
564 """Call the given method, synchronously.
566 if object_path
== LOCAL_PATH
:
567 raise DBusException('Methods may not be called on the reserved '
568 'path %s' % LOCAL_PATH
)
569 if dbus_interface
== LOCAL_IFACE
:
570 raise DBusException('Methods may not be called on the reserved '
571 'interface %s' % LOCAL_IFACE
)
572 # no need to validate other args - MethodCallMessage ctor will do
574 get_args_opts
= {'utf8_strings': utf8_strings
,
575 'byte_arrays': byte_arrays
}
577 message
= MethodCallMessage(destination
=bus_name
,
579 interface
=dbus_interface
,
581 # Add the arguments to the function
583 message
.append(signature
=signature
, *args
)
585 logging
.basicConfig()
586 _logger
.error('Unable to set arguments %r according to '
587 'signature %r: %s: %s',
588 args
, signature
, e
.__class
__, e
)
591 # make a blocking call
592 reply_message
= self
.send_message_with_reply_and_block(
594 args_list
= reply_message
.get_args_list(**get_args_opts
)
595 if len(args_list
) == 0:
597 elif len(args_list
) == 1:
600 return tuple(args_list
)