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
77 # if the connection is actually a bus, it's responsible for changing
79 self
.sender_unique
= sender
81 self
._utf
8_strings
= utf8_strings
82 self
._byte
_arrays
= byte_arrays
83 self
._sender
_keyword
= sender_keyword
84 self
._path
_keyword
= path_keyword
85 self
._member
_keyword
= member_keyword
86 self
._interface
_keyword
= interface_keyword
87 self
._message
_keyword
= message_keyword
88 self
._destination
_keyword
= destination_keyword
90 self
._args
_match
= kwargs
92 self
._int
_args
_match
= None
94 self
._int
_args
_match
= {}
96 if not kwarg
.startswith('arg'):
97 raise TypeError('SignalMatch: unknown keyword argument %s'
100 index
= int(kwarg
[3:])
102 raise TypeError('SignalMatch: unknown keyword argument %s'
104 if index
< 0 or index
> 63:
105 raise TypeError('SignalMatch: arg match index must be in '
106 'range(64), not %d' % index
)
107 self
._int
_args
_match
[index
] = kwargs
[kwarg
]
109 # we're probably going to have to calculate the match rule for
110 # the Bus's benefit, so this constructor might as well do the work
111 rule
= ["type='signal'"]
112 if self
._sender
is not None:
113 rule
.append("sender='%s'" % self
._sender
)
114 if self
._path
is not None:
115 rule
.append("path='%s'" % self
._path
)
116 if self
._interface
is not None:
117 rule
.append("interface='%s'" % self
._interface
)
118 if self
._member
is not None:
119 rule
.append("member='%s'" % self
._member
)
120 for kwarg
, value
in kwargs
.iteritems():
121 rule
.append("%s='%s'" % (kwarg
, value
))
123 self
._rule
= ','.join(rule
)
125 sender
= property(lambda self
: self
._sender
)
131 return ('<%s at %x "%s" on conn %r>'
132 % (self
.__class
__, id(self
), self
._rule
, self
._conn
_weakref
()))
134 def matches_removal_spec(self
, sender
, object_path
,
135 dbus_interface
, member
, handler
, **kwargs
):
136 if handler
not in (None, self
._handler
):
138 if sender
!= self
._sender
:
140 if object_path
!= self
._path
:
142 if dbus_interface
!= self
._interface
:
144 if member
!= self
._member
:
146 if kwargs
!= self
._args
_match
:
150 def maybe_handle_message(self
, message
):
153 # these haven't been checked yet by the match tree
154 if self
.sender_unique
not in (None, message
.get_sender()):
156 if self
._int
_args
_match
is not None:
157 # extracting args with utf8_strings and byte_arrays is less work
158 args
= message
.get_args_list(utf8_strings
=True, byte_arrays
=True)
159 for index
, value
in self
._int
_args
_match
.iteritems():
160 if (index
>= len(args
)
161 or not isinstance(args
[index
], UTF8String
)
162 or args
[index
] != value
):
165 # these have likely already been checked by the match tree
166 if self
._member
not in (None, message
.get_member()):
168 if self
._interface
not in (None, message
.get_interface()):
170 if self
._path
not in (None, message
.get_path()):
174 # minor optimization: if we already extracted the args with the
175 # right calling convention to do the args match, don't bother
177 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
178 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
179 byte_arrays
=self
._byte
_arrays
)
181 if self
._sender
_keyword
is not None:
182 kwargs
[self
._sender
_keyword
] = message
.get_sender()
183 if self
._destination
_keyword
is not None:
184 kwargs
[self
._destination
_keyword
] = message
.get_destination()
185 if self
._path
_keyword
is not None:
186 kwargs
[self
._path
_keyword
] = message
.get_path()
187 if self
._member
_keyword
is not None:
188 kwargs
[self
._member
_keyword
] = message
.get_member()
189 if self
._interface
_keyword
is not None:
190 kwargs
[self
._interface
_keyword
] = message
.get_interface()
191 if self
._message
_keyword
is not None:
192 kwargs
[self
._message
_keyword
] = message
193 self
._handler
(*args
, **kwargs
)
195 # basicConfig is a no-op if logging is already configured
196 logging
.basicConfig()
197 _logger
.error('Exception in handler for D-Bus signal:', exc_info
=1)
202 conn
= self
._conn
_weakref
()
203 # do nothing if the connection has already vanished
205 conn
.remove_signal_receiver(self
, self
._member
,
206 self
._interface
, self
._sender
,
211 class Connection(_Connection
):
212 """A connection to another application. In this base class there is
213 assumed to be no bus daemon.
216 ProxyObjectClass
= ProxyObject
218 def __init__(self
, *args
, **kwargs
):
219 super(Connection
, self
).__init
__(*args
, **kwargs
)
221 # this if-block is needed because shared bus connections can be
222 # __init__'ed more than once
223 if not hasattr(self
, '_dbus_Connection_initialized'):
224 self
._dbus
_Connection
_initialized
= 1
226 self
._signal
_recipients
_by
_object
_path
= {}
227 """Map from object path to dict mapping dbus_interface to dict
228 mapping member to list of SignalMatch objects."""
230 self
._signals
_lock
= thread
.allocate_lock()
231 """Lock used to protect signal data structures if doing two
232 removals at the same time (everything else is atomic, thanks to
235 self
.add_message_filter(self
.__class
__._signal
_func
)
237 def activate_name_owner(self
, bus_name
):
238 """Return the unique name for the given bus name, activating it
239 if necessary and possible.
241 If the name is already unique or this connection is not to a
242 bus daemon, just return it.
244 :Returns: a bus name. If the given `bus_name` exists, the returned
245 name identifies its current owner; otherwise the returned name
247 :Raises DBusException: if the implementation has failed
248 to activate the given bus name.
252 def get_object(self
, named_service
, object_path
, introspect
=True):
253 """Return a local proxy for the given remote object.
255 Method calls on the proxy are translated into method calls on the
259 `named_service` : str
260 A bus name (either the unique name or a well-known name)
261 of the application owning the object
263 The object path of the desired object
265 If true (default), attempt to introspect the remote
266 object to find out supported methods and their signatures
268 :Returns: a `dbus.proxies.ProxyObject`
270 return self
.ProxyObjectClass(self
, named_service
, object_path
,
271 introspect
=introspect
)
273 def add_signal_receiver(self
, handler_function
,
279 """Arrange for the given function to be called when a signal matching
280 the parameters is received.
283 `handler_function` : callable
284 The function to be called. Its positional arguments will
285 be the arguments of the signal. By default it will receive
286 no keyword arguments, but see the description of
287 the optional keyword arguments below.
289 The signal name; None (the default) matches all names
290 `dbus_interface` : str
291 The D-Bus interface name with which to qualify the signal;
292 None (the default) matches all interface names
293 `named_service` : str
294 A bus name for the sender, which will be resolved to a
295 unique name if it is not already; None (the default) matches
298 The object path of the object which must have emitted the
299 signal; None (the default) matches any object path
301 `utf8_strings` : bool
302 If True, the handler function will receive any string
303 arguments as dbus.UTF8String objects (a subclass of str
304 guaranteed to be UTF-8). If False (default) it will receive
305 any string arguments as dbus.String objects (a subclass of
308 If True, the handler function will receive any byte-array
309 arguments as dbus.ByteArray objects (a subclass of str).
310 If False (default) it will receive any byte-array
311 arguments as a dbus.Array of dbus.Byte (subclasses of:
313 `sender_keyword` : str
314 If not None (the default), the handler function will receive
315 the unique name of the sending endpoint as a keyword
316 argument with this name.
317 `destination_keyword` : str
318 If not None (the default), the handler function will receive
319 the bus name of the destination (or None if the signal is a
320 broadcast, as is usual) as a keyword argument with this name.
321 `interface_keyword` : str
322 If not None (the default), the handler function will receive
323 the signal interface as a keyword argument with this name.
324 `member_keyword` : str
325 If not None (the default), the handler function will receive
326 the signal name as a keyword argument with this name.
328 If not None (the default), the handler function will receive
329 the object-path of the sending object as a keyword argument
331 `message_keyword` : str
332 If not None (the default), the handler function will receive
333 the `dbus.lowlevel.SignalMessage` as a keyword argument with
335 `arg...` : unicode or UTF-8 str
336 If there are additional keyword parameters of the form
337 ``arg``\ *n*, match only signals where the *n*\ th argument
338 is the value given for that keyword parameter. As of this
339 time only string arguments can be matched (in particular,
340 object paths and signatures can't).
342 self
._require
_main
_loop
()
344 match
= SignalMatch(self
, named_service
, path
, dbus_interface
,
345 signal_name
, handler_function
, **keywords
)
346 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
348 by_member
= by_interface
.setdefault(dbus_interface
, {})
349 matches
= by_member
.setdefault(signal_name
, [])
351 # make sure nobody is currently manipulating the list
352 self
._signals
_lock
.acquire()
354 matches
.append(match
)
356 self
._signals
_lock
.release()
359 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
361 path_keys
= (None, path
)
364 if dbus_interface
is not None:
365 interface_keys
= (None, dbus_interface
)
367 interface_keys
= (None,)
368 if member
is not None:
369 member_keys
= (None, member
)
371 member_keys
= (None,)
373 for path
in path_keys
:
374 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
376 if by_interface
is None:
378 for dbus_interface
in interface_keys
:
379 by_member
= by_interface
.get(dbus_interface
, None)
380 if by_member
is None:
382 for member
in member_keys
:
383 matches
= by_member
.get(member
, None)
389 def remove_signal_receiver(self
, handler_or_match
,
395 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
396 if by_interface
is None:
398 by_member
= by_interface
.get(dbus_interface
, None)
399 if by_member
is None:
401 matches
= by_member
.get(signal_name
, None)
404 self
._signals
_lock
.acquire()
407 for match
in matches
:
408 if (handler_or_match
is match
409 or match
.matches_removal_spec(named_service
,
415 self
._clean
_up
_signal
_match
(match
)
418 by_member
[signal_name
] = new
420 self
._signals
_lock
.release()
422 def _clean_up_signal_match(self
, match
):
423 # Called with the signals lock held
426 def _signal_func(self
, message
):
427 """D-Bus filter function. Handle signals by dispatching to Python
428 callbacks kept in the match-rule tree.
431 if not isinstance(message
, SignalMessage
):
432 return HANDLER_RESULT_NOT_YET_HANDLED
434 dbus_interface
= message
.get_interface()
435 path
= message
.get_path()
436 signal_name
= message
.get_member()
438 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
440 match
.maybe_handle_message(message
)
441 return HANDLER_RESULT_NOT_YET_HANDLED
443 def call_async(self
, bus_name
, object_path
, dbus_interface
, method
,
444 signature
, args
, reply_handler
, error_handler
,
445 timeout
=-1.0, utf8_strings
=False, byte_arrays
=False,
446 require_main_loop
=True):
447 """Call the given method, asynchronously.
449 If the reply_handler is None, successful replies will be ignored.
450 If the error_handler is None, failures will be ignored. If both
451 are None, the implementation may request that no reply is sent.
453 :Returns: The dbus.lowlevel.PendingCall.
455 if object_path
== LOCAL_PATH
:
456 raise DBusException('Methods may not be called on the reserved '
457 'path %s' % LOCAL_PATH
)
458 if dbus_interface
== LOCAL_IFACE
:
459 raise DBusException('Methods may not be called on the reserved '
460 'interface %s' % LOCAL_IFACE
)
461 # no need to validate other args - MethodCallMessage ctor will do
463 get_args_opts
= {'utf8_strings': utf8_strings
,
464 'byte_arrays': byte_arrays
}
466 message
= MethodCallMessage(destination
=bus_name
,
468 interface
=dbus_interface
,
470 # Add the arguments to the function
472 message
.append(signature
=signature
, *args
)
474 logging
.basicConfig()
475 _logger
.error('Unable to set arguments %r according to '
476 'signature %r: %s: %s',
477 args
, signature
, e
.__class
__, e
)
480 if reply_handler
is None and error_handler
is None:
481 # we don't care what happens, so just send it
482 self
.send_message(message
)
485 if reply_handler
is None:
486 reply_handler
= _noop
487 if error_handler
is None:
488 error_handler
= _noop
490 def msg_reply_handler(message
):
491 if isinstance(message
, MethodReturnMessage
):
492 reply_handler(*message
.get_args_list(**get_args_opts
))
493 elif isinstance(message
, ErrorMessage
):
494 args
= message
.get_args_list()
495 # FIXME: should we do something with the rest?
497 error_handler(DBusException(args
[0]))
499 error_handler(DBusException())
501 error_handler(TypeError('Unexpected type for reply '
502 'message: %r' % message
))
503 return self
.send_message_with_reply(message
, msg_reply_handler
,
505 require_main_loop
=require_main_loop
)
507 def call_blocking(self
, bus_name
, object_path
, dbus_interface
, method
,
508 signature
, args
, timeout
=-1.0, utf8_strings
=False,
510 """Call the given method, synchronously.
512 if object_path
== LOCAL_PATH
:
513 raise DBusException('Methods may not be called on the reserved '
514 'path %s' % LOCAL_PATH
)
515 if dbus_interface
== LOCAL_IFACE
:
516 raise DBusException('Methods may not be called on the reserved '
517 'interface %s' % LOCAL_IFACE
)
518 # no need to validate other args - MethodCallMessage ctor will do
520 get_args_opts
= {'utf8_strings': utf8_strings
,
521 'byte_arrays': byte_arrays
}
523 message
= MethodCallMessage(destination
=bus_name
,
525 interface
=dbus_interface
,
527 # Add the arguments to the function
529 message
.append(signature
=signature
, *args
)
531 logging
.basicConfig()
532 _logger
.error('Unable to set arguments %r according to '
533 'signature %r: %s: %s',
534 args
, signature
, e
.__class
__, e
)
537 # make a blocking call
538 reply_message
= self
.send_message_with_reply_and_block(
540 args_list
= reply_message
.get_args_list(**get_args_opts
)
541 if len(args_list
) == 0:
543 elif len(args_list
) == 1:
546 return tuple(args_list
)