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_unique', '_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
)
70 self
._conn
_weakref
= weakref
.ref(conn
)
72 self
._interface
= dbus_interface
74 self
._path
= object_path
75 self
._handler
= handler
76 self
.sender_unique
= sender
77 self
._utf
8_strings
= utf8_strings
78 self
._byte
_arrays
= byte_arrays
79 self
._sender
_keyword
= sender_keyword
80 self
._path
_keyword
= path_keyword
81 self
._member
_keyword
= member_keyword
82 self
._interface
_keyword
= interface_keyword
83 self
._message
_keyword
= message_keyword
84 self
._destination
_keyword
= destination_keyword
86 self
._args
_match
= kwargs
88 self
._int
_args
_match
= None
90 self
._int
_args
_match
= {}
92 if not kwarg
.startswith('arg'):
93 raise TypeError('SignalMatch: unknown keyword argument %s'
96 index
= int(kwarg
[3:])
98 raise TypeError('SignalMatch: unknown keyword argument %s'
100 if index
< 0 or index
> 63:
101 raise TypeError('SignalMatch: arg match index must be in '
102 'range(64), not %d' % index
)
103 self
._int
_args
_match
[index
] = kwargs
[kwarg
]
105 # we're probably going to have to calculate the match rule for
106 # the Bus's benefit, so this constructor might as well do the work
107 rule
= ["type='signal'"]
108 if self
._sender
is not None:
109 rule
.append("sender='%s'" % self
._sender
)
110 if self
._path
is not None:
111 rule
.append("path='%s'" % self
._path
)
112 if self
._interface
is not None:
113 rule
.append("interface='%s'" % self
._interface
)
114 if self
._member
is not None:
115 rule
.append("member='%s'" % self
._member
)
116 for kwarg
, value
in kwargs
.iteritems():
117 rule
.append("%s='%s'" % (kwarg
, value
))
119 self
._rule
= ','.join(rule
)
121 sender
= property(lambda self
: self
._sender
)
127 return ('<%s at %x "%s" on conn %r>'
128 % (self
.__class
__, id(self
), self
._rule
, self
._conn
_weakref
()))
130 def matches_removal_spec(self
, sender
, object_path
,
131 dbus_interface
, member
, handler
, **kwargs
):
132 if handler
not in (None, self
._handler
):
134 if sender
!= self
._sender
:
136 if object_path
!= self
._path
:
138 if dbus_interface
!= self
._interface
:
140 if member
!= self
._member
:
142 if kwargs
!= self
._args
_match
:
146 def maybe_handle_message(self
, message
):
149 # these haven't been checked yet by the match tree
150 if self
.sender_unique
not in (None, message
.get_sender()):
152 if self
._int
_args
_match
is not None:
153 # extracting args with utf8_strings and byte_arrays is less work
154 args
= message
.get_args_list(utf8_strings
=True, byte_arrays
=True)
155 for index
, value
in self
._int
_args
_match
.iteritems():
156 if (index
>= len(args
)
157 or not isinstance(args
[index
], UTF8String
)
158 or args
[index
] != value
):
161 # these have likely already been checked by the match tree
162 if self
._member
not in (None, message
.get_member()):
164 if self
._interface
not in (None, message
.get_interface()):
166 if self
._path
not in (None, message
.get_path()):
170 # minor optimization: if we already extracted the args with the
171 # right calling convention to do the args match, don't bother
173 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
174 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
175 byte_arrays
=self
._byte
_arrays
)
177 if self
._sender
_keyword
is not None:
178 kwargs
[self
._sender
_keyword
] = message
.get_sender()
179 if self
._destination
_keyword
is not None:
180 kwargs
[self
._destination
_keyword
] = message
.get_destination()
181 if self
._path
_keyword
is not None:
182 kwargs
[self
._path
_keyword
] = message
.get_path()
183 if self
._member
_keyword
is not None:
184 kwargs
[self
._member
_keyword
] = message
.get_member()
185 if self
._interface
_keyword
is not None:
186 kwargs
[self
._interface
_keyword
] = message
.get_interface()
187 if self
._message
_keyword
is not None:
188 kwargs
[self
._message
_keyword
] = message
189 self
._handler
(*args
, **kwargs
)
191 # basicConfig is a no-op if logging is already configured
192 logging
.basicConfig()
193 _logger
.error('Exception in handler for D-Bus signal:', exc_info
=1)
198 conn
= self
._conn
_weakref
()
199 # do nothing if the connection has already vanished
201 conn
.remove_signal_receiver(self
, self
._member
,
202 self
._interface
, self
._sender
,
207 class Connection(_Connection
):
208 """A connection to another application. In this base class there is
209 assumed to be no bus daemon.
212 ProxyObjectClass
= ProxyObject
214 def __init__(self
, *args
, **kwargs
):
215 super(Connection
, self
).__init
__(*args
, **kwargs
)
217 # this if-block is needed because shared bus connections can be
218 # __init__'ed more than once
219 if not hasattr(self
, '_dbus_Connection_initialized'):
220 self
._dbus
_Connection
_initialized
= 1
222 self
._signal
_recipients
_by
_object
_path
= {}
223 """Map from object path to dict mapping dbus_interface to dict
224 mapping member to list of SignalMatch objects."""
226 self
._signals
_lock
= thread
.allocate_lock()
227 """Lock used to protect signal data structures if doing two
228 removals at the same time (everything else is atomic, thanks to
231 self
.add_message_filter(self
.__class
__._signal
_func
)
233 def activate_name_owner(self
, bus_name
):
234 """Return the unique name for the given bus name, activating it
235 if necessary and possible.
237 If the name is already unique or this connection is not to a
238 bus daemon, just return it.
240 :Returns: a bus name. If the given `bus_name` exists, the returned
241 name identifies its current owner; otherwise the returned name
243 :Raises DBusException: if the implementation has failed
244 to activate the given bus name.
248 def get_object(self
, named_service
, object_path
, introspect
=True):
249 """Return a local proxy for the given remote object.
251 Method calls on the proxy are translated into method calls on the
255 `named_service` : str
256 A bus name (either the unique name or a well-known name)
257 of the application owning the object
259 The object path of the desired object
261 If true (default), attempt to introspect the remote
262 object to find out supported methods and their signatures
264 :Returns: a `dbus.proxies.ProxyObject`
266 return self
.ProxyObjectClass(self
, named_service
, object_path
,
267 introspect
=introspect
)
269 def add_signal_receiver(self
, handler_function
,
275 """Arrange for the given function to be called when a signal matching
276 the parameters is received.
279 `handler_function` : callable
280 The function to be called. Its positional arguments will
281 be the arguments of the signal. By default it will receive
282 no keyword arguments, but see the description of
283 the optional keyword arguments below.
285 The signal name; None (the default) matches all names
286 `dbus_interface` : str
287 The D-Bus interface name with which to qualify the signal;
288 None (the default) matches all interface names
289 `named_service` : str
290 A bus name for the sender, which will be resolved to a
291 unique name if it is not already; None (the default) matches
294 The object path of the object which must have emitted the
295 signal; None (the default) matches any object path
297 `utf8_strings` : bool
298 If True, the handler function will receive any string
299 arguments as dbus.UTF8String objects (a subclass of str
300 guaranteed to be UTF-8). If False (default) it will receive
301 any string arguments as dbus.String objects (a subclass of
304 If True, the handler function will receive any byte-array
305 arguments as dbus.ByteArray objects (a subclass of str).
306 If False (default) it will receive any byte-array
307 arguments as a dbus.Array of dbus.Byte (subclasses of:
309 `sender_keyword` : str
310 If not None (the default), the handler function will receive
311 the unique name of the sending endpoint as a keyword
312 argument with this name.
313 `destination_keyword` : str
314 If not None (the default), the handler function will receive
315 the bus name of the destination (or None if the signal is a
316 broadcast, as is usual) as a keyword argument with this name.
317 `interface_keyword` : str
318 If not None (the default), the handler function will receive
319 the signal interface as a keyword argument with this name.
320 `member_keyword` : str
321 If not None (the default), the handler function will receive
322 the signal name as a keyword argument with this name.
324 If not None (the default), the handler function will receive
325 the object-path of the sending object as a keyword argument
327 `message_keyword` : str
328 If not None (the default), the handler function will receive
329 the `dbus.lowlevel.SignalMessage` as a keyword argument with
331 `arg...` : unicode or UTF-8 str
332 If there are additional keyword parameters of the form
333 ``arg``\ *n*, match only signals where the *n*\ th argument
334 is the value given for that keyword parameter. As of this
335 time only string arguments can be matched (in particular,
336 object paths and signatures can't).
338 self
._require
_main
_loop
()
340 match
= SignalMatch(self
, named_service
, path
, dbus_interface
,
341 signal_name
, handler_function
, **keywords
)
342 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
344 by_member
= by_interface
.setdefault(dbus_interface
, {})
345 matches
= by_member
.setdefault(signal_name
, [])
346 self
._signals
_lock
.acquire()
348 matches
.append(match
)
350 self
._signals
_lock
.release()
353 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
355 path_keys
= (None, path
)
358 if dbus_interface
is not None:
359 interface_keys
= (None, dbus_interface
)
361 interface_keys
= (None,)
362 if member
is not None:
363 member_keys
= (None, member
)
365 member_keys
= (None,)
367 for path
in path_keys
:
368 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
370 if by_interface
is None:
372 for dbus_interface
in interface_keys
:
373 by_member
= by_interface
.get(dbus_interface
, None)
374 if by_member
is None:
376 for member
in member_keys
:
377 matches
= by_member
.get(member
, None)
383 def remove_signal_receiver(self
, handler_or_match
,
389 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
390 if by_interface
is None:
392 by_member
= by_interface
.get(dbus_interface
, None)
393 if by_member
is None:
395 matches
= by_member
.get(signal_name
, None)
398 self
._signals
_lock
.acquire()
401 for match
in matches
:
402 if (handler_or_match
is match
403 or match
.matches_removal_spec(named_service
,
409 self
._clean
_up
_signal
_match
(match
)
412 by_member
[signal_name
] = new
414 self
._signals
_lock
.release()
416 def _clean_up_signal_match(self
, match
):
417 # Called with the signals lock held
420 def _signal_func(self
, message
):
421 """D-Bus filter function. Handle signals by dispatching to Python
422 callbacks kept in the match-rule tree.
425 if not isinstance(message
, SignalMessage
):
426 return HANDLER_RESULT_NOT_YET_HANDLED
428 dbus_interface
= message
.get_interface()
429 path
= message
.get_path()
430 signal_name
= message
.get_member()
432 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
434 match
.maybe_handle_message(message
)
435 return HANDLER_RESULT_NOT_YET_HANDLED
437 def call_async(self
, bus_name
, object_path
, dbus_interface
, method
,
438 signature
, args
, reply_handler
, error_handler
,
439 timeout
=-1.0, utf8_strings
=False, byte_arrays
=False,
440 require_main_loop
=True):
441 """Call the given method, asynchronously.
443 If the reply_handler is None, successful replies will be ignored.
444 If the error_handler is None, failures will be ignored. If both
445 are None, the implementation may request that no reply is sent.
447 :Returns: The dbus.lowlevel.PendingCall.
449 if object_path
== LOCAL_PATH
:
450 raise DBusException('Methods may not be called on the reserved '
451 'path %s' % LOCAL_PATH
)
452 if dbus_interface
== LOCAL_IFACE
:
453 raise DBusException('Methods may not be called on the reserved '
454 'interface %s' % LOCAL_IFACE
)
455 # no need to validate other args - MethodCallMessage ctor will do
457 get_args_opts
= {'utf8_strings': utf8_strings
,
458 'byte_arrays': byte_arrays
}
460 message
= MethodCallMessage(destination
=bus_name
,
462 interface
=dbus_interface
,
464 # Add the arguments to the function
466 message
.append(signature
=signature
, *args
)
468 logging
.basicConfig()
469 _logger
.error('Unable to set arguments %r according to '
470 'signature %r: %s: %s',
471 args
, signature
, e
.__class
__, e
)
474 if reply_handler
is None and error_handler
is None:
475 # we don't care what happens, so just send it
476 self
.send_message(message
)
479 if reply_handler
is None:
480 reply_handler
= _noop
481 if error_handler
is None:
482 error_handler
= _noop
484 def msg_reply_handler(message
):
485 if isinstance(message
, MethodReturnMessage
):
486 reply_handler(*message
.get_args_list(**get_args_opts
))
487 elif isinstance(message
, ErrorMessage
):
488 args
= message
.get_args_list()
489 # FIXME: should we do something with the rest?
491 error_handler(DBusException(args
[0]))
493 error_handler(DBusException())
495 error_handler(TypeError('Unexpected type for reply '
496 'message: %r' % message
))
497 return self
.send_message_with_reply(message
, msg_reply_handler
,
499 require_main_loop
=require_main_loop
)
501 def call_blocking(self
, bus_name
, object_path
, dbus_interface
, method
,
502 signature
, args
, timeout
=-1.0, utf8_strings
=False,
504 """Call the given method, synchronously.
506 if object_path
== LOCAL_PATH
:
507 raise DBusException('Methods may not be called on the reserved '
508 'path %s' % LOCAL_PATH
)
509 if dbus_interface
== LOCAL_IFACE
:
510 raise DBusException('Methods may not be called on the reserved '
511 'interface %s' % LOCAL_IFACE
)
512 # no need to validate other args - MethodCallMessage ctor will do
514 get_args_opts
= {'utf8_strings': utf8_strings
,
515 'byte_arrays': byte_arrays
}
517 message
= MethodCallMessage(destination
=bus_name
,
519 interface
=dbus_interface
,
521 # Add the arguments to the function
523 message
.append(signature
=signature
, *args
)
525 logging
.basicConfig()
526 _logger
.error('Unable to set arguments %r according to '
527 'signature %r: %s: %s',
528 args
, signature
, e
.__class
__, e
)
531 # make a blocking call
532 reply_message
= self
.send_message_with_reply_and_block(
534 args_list
= reply_message
.get_args_list(**get_args_opts
)
535 if len(args_list
) == 0:
537 elif len(args_list
) == 1:
540 return tuple(args_list
)