1
# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3
# Permission is hereby granted, free of charge, to any person
4
# obtaining a copy of this software and associated documentation
5
# files (the "Software"), to deal in the Software without
6
# restriction, including without limitation the rights to use, copy,
7
# modify, merge, publish, distribute, sublicense, and/or sell copies
8
# of the Software, and to permit persons to whom the Software is
9
# furnished to do so, subject to the following conditions:
11
# The above copyright notice and this permission notice shall be
12
# included in all copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
# DEALINGS IN THE SOFTWARE.
23
__all__ = ('Connection', 'SignalMatch')
24
__docformat__ = 'reStructuredText'
30
import dummy_thread as thread
33
from _dbus_bindings import Connection as _Connection, \
34
LOCAL_PATH, LOCAL_IFACE, \
35
validate_interface_name, validate_member_name,\
36
validate_bus_name, validate_object_path,\
37
validate_error_name, \
39
from dbus.exceptions import DBusException
40
from dbus.lowlevel import ErrorMessage, MethodCallMessage, SignalMessage, \
41
MethodReturnMessage, HANDLER_RESULT_NOT_YET_HANDLED
42
from dbus.proxies import ProxyObject
45
_logger = logging.getLogger('dbus.connection')
48
def _noop(*args, **kwargs):
52
class SignalMatch(object):
53
__slots__ = ('_sender_name_owner', '_member', '_interface', '_sender',
54
'_path', '_handler', '_args_match', '_rule',
55
'_utf8_strings', '_byte_arrays', '_conn_weakref',
56
'_destination_keyword', '_interface_keyword',
57
'_message_keyword', '_member_keyword',
58
'_sender_keyword', '_path_keyword', '_int_args_match')
60
def __init__(self, conn, sender, object_path, dbus_interface,
61
member, handler, utf8_strings=False, byte_arrays=False,
62
sender_keyword=None, path_keyword=None,
63
interface_keyword=None, member_keyword=None,
64
message_keyword=None, destination_keyword=None,
66
if member is not None:
67
validate_member_name(member)
68
if dbus_interface is not None:
69
validate_interface_name(dbus_interface)
70
if sender is not None:
71
validate_bus_name(sender)
72
if object_path is not None:
73
validate_object_path(object_path)
76
self._conn_weakref = weakref.ref(conn)
78
self._interface = dbus_interface
80
self._path = object_path
81
self._handler = handler
83
# if the connection is actually a bus, it's responsible for changing
85
self._sender_name_owner = sender
87
self._utf8_strings = utf8_strings
88
self._byte_arrays = byte_arrays
89
self._sender_keyword = sender_keyword
90
self._path_keyword = path_keyword
91
self._member_keyword = member_keyword
92
self._interface_keyword = interface_keyword
93
self._message_keyword = message_keyword
94
self._destination_keyword = destination_keyword
96
self._args_match = kwargs
98
self._int_args_match = None
100
self._int_args_match = {}
102
if not kwarg.startswith('arg'):
103
raise TypeError('SignalMatch: unknown keyword argument %s'
106
index = int(kwarg[3:])
108
raise TypeError('SignalMatch: unknown keyword argument %s'
110
if index < 0 or index > 63:
111
raise TypeError('SignalMatch: arg match index must be in '
112
'range(64), not %d' % index)
113
self._int_args_match[index] = kwargs[kwarg]
116
"""SignalMatch objects are compared by identity."""
117
return hash(id(self))
119
def __eq__(self, other):
120
"""SignalMatch objects are compared by identity."""
123
def __ne__(self, other):
124
"""SignalMatch objects are compared by identity."""
125
return self is not other
127
sender = property(lambda self: self._sender)
130
if self._rule is None:
131
rule = ["type='signal'"]
132
if self._sender is not None:
133
rule.append("sender='%s'" % self._sender)
134
if self._path is not None:
135
rule.append("path='%s'" % self._path)
136
if self._interface is not None:
137
rule.append("interface='%s'" % self._interface)
138
if self._member is not None:
139
rule.append("member='%s'" % self._member)
140
if self._int_args_match is not None:
141
for index, value in self._int_args_match.iteritems():
142
rule.append("arg%d='%s'" % (index, value))
144
self._rule = ','.join(rule)
149
return ('<%s at %x "%s" on conn %r>'
150
% (self.__class__, id(self), self._rule, self._conn_weakref()))
152
def set_sender_name_owner(self, new_name):
153
self._sender_name_owner = new_name
155
def matches_removal_spec(self, sender, object_path,
156
dbus_interface, member, handler, **kwargs):
157
if handler not in (None, self._handler):
159
if sender != self._sender:
161
if object_path != self._path:
163
if dbus_interface != self._interface:
165
if member != self._member:
167
if kwargs != self._args_match:
171
def maybe_handle_message(self, message):
174
# these haven't been checked yet by the match tree
175
if self._sender_name_owner not in (None, message.get_sender()):
177
if self._int_args_match is not None:
178
# extracting args with utf8_strings and byte_arrays is less work
179
args = message.get_args_list(utf8_strings=True, byte_arrays=True)
180
for index, value in self._int_args_match.iteritems():
181
if (index >= len(args)
182
or not isinstance(args[index], UTF8String)
183
or args[index] != value):
186
# these have likely already been checked by the match tree
187
if self._member not in (None, message.get_member()):
189
if self._interface not in (None, message.get_interface()):
191
if self._path not in (None, message.get_path()):
195
# minor optimization: if we already extracted the args with the
196
# right calling convention to do the args match, don't bother
198
if args is None or not self._utf8_strings or not self._byte_arrays:
199
args = message.get_args_list(utf8_strings=self._utf8_strings,
200
byte_arrays=self._byte_arrays)
202
if self._sender_keyword is not None:
203
kwargs[self._sender_keyword] = message.get_sender()
204
if self._destination_keyword is not None:
205
kwargs[self._destination_keyword] = message.get_destination()
206
if self._path_keyword is not None:
207
kwargs[self._path_keyword] = message.get_path()
208
if self._member_keyword is not None:
209
kwargs[self._member_keyword] = message.get_member()
210
if self._interface_keyword is not None:
211
kwargs[self._interface_keyword] = message.get_interface()
212
if self._message_keyword is not None:
213
kwargs[self._message_keyword] = message
214
self._handler(*args, **kwargs)
216
# basicConfig is a no-op if logging is already configured
217
logging.basicConfig()
218
_logger.error('Exception in handler for D-Bus signal:', exc_info=1)
223
conn = self._conn_weakref()
224
# do nothing if the connection has already vanished
226
conn.remove_signal_receiver(self, self._member,
227
self._interface, self._sender,
232
class Connection(_Connection):
233
"""A connection to another application. In this base class there is
234
assumed to be no bus daemon.
239
ProxyObjectClass = ProxyObject
241
def __init__(self, *args, **kwargs):
242
super(Connection, self).__init__(*args, **kwargs)
244
# this if-block is needed because shared bus connections can be
245
# __init__'ed more than once
246
if not hasattr(self, '_dbus_Connection_initialized'):
247
self._dbus_Connection_initialized = 1
249
self.__call_on_disconnection = []
251
self._signal_recipients_by_object_path = {}
252
"""Map from object path to dict mapping dbus_interface to dict
253
mapping member to list of SignalMatch objects."""
255
self._signals_lock = thread.allocate_lock()
256
"""Lock used to protect signal data structures"""
258
self.add_message_filter(self.__class__._signal_func)
260
def activate_name_owner(self, bus_name):
261
"""Return the unique name for the given bus name, activating it
262
if necessary and possible.
264
If the name is already unique or this connection is not to a
265
bus daemon, just return it.
267
:Returns: a bus name. If the given `bus_name` exists, the returned
268
name identifies its current owner; otherwise the returned name
270
:Raises DBusException: if the implementation has failed
271
to activate the given bus name.
276
def get_object(self, bus_name=None, object_path=None, introspect=True,
278
"""Return a local proxy for the given remote object.
280
Method calls on the proxy are translated into method calls on the
285
A bus name (either the unique name or a well-known name)
286
of the application owning the object. The keyword argument
287
named_service is a deprecated alias for this.
289
The object path of the desired object
291
If true (default), attempt to introspect the remote
292
object to find out supported methods and their signatures
294
:Returns: a `dbus.proxies.ProxyObject`
296
named_service = kwargs.pop('named_service', None)
297
if named_service is not None:
298
if bus_name is not None:
299
raise TypeError('bus_name and named_service cannot both '
301
from warnings import warn
302
warn('Passing the named_service parameter to get_object by name '
303
'is deprecated: please use positional parameters',
304
DeprecationWarning, stacklevel=2)
305
bus_name = named_service
307
raise TypeError('get_object does not take these keyword '
308
'arguments: %s' % ', '.join(kwargs.iterkeys()))
310
return self.ProxyObjectClass(self, bus_name, object_path,
311
introspect=introspect)
313
def add_signal_receiver(self, handler_function,
319
"""Arrange for the given function to be called when a signal matching
320
the parameters is received.
323
`handler_function` : callable
324
The function to be called. Its positional arguments will
325
be the arguments of the signal. By default it will receive
326
no keyword arguments, but see the description of
327
the optional keyword arguments below.
329
The signal name; None (the default) matches all names
330
`dbus_interface` : str
331
The D-Bus interface name with which to qualify the signal;
332
None (the default) matches all interface names
334
A bus name for the sender, which will be resolved to a
335
unique name if it is not already; None (the default) matches
338
The object path of the object which must have emitted the
339
signal; None (the default) matches any object path
341
`utf8_strings` : bool
342
If True, the handler function will receive any string
343
arguments as dbus.UTF8String objects (a subclass of str
344
guaranteed to be UTF-8). If False (default) it will receive
345
any string arguments as dbus.String objects (a subclass of
348
If True, the handler function will receive any byte-array
349
arguments as dbus.ByteArray objects (a subclass of str).
350
If False (default) it will receive any byte-array
351
arguments as a dbus.Array of dbus.Byte (subclasses of:
353
`sender_keyword` : str
354
If not None (the default), the handler function will receive
355
the unique name of the sending endpoint as a keyword
356
argument with this name.
357
`destination_keyword` : str
358
If not None (the default), the handler function will receive
359
the bus name of the destination (or None if the signal is a
360
broadcast, as is usual) as a keyword argument with this name.
361
`interface_keyword` : str
362
If not None (the default), the handler function will receive
363
the signal interface as a keyword argument with this name.
364
`member_keyword` : str
365
If not None (the default), the handler function will receive
366
the signal name as a keyword argument with this name.
368
If not None (the default), the handler function will receive
369
the object-path of the sending object as a keyword argument
371
`message_keyword` : str
372
If not None (the default), the handler function will receive
373
the `dbus.lowlevel.SignalMessage` as a keyword argument with
375
`arg...` : unicode or UTF-8 str
376
If there are additional keyword parameters of the form
377
``arg``\ *n*, match only signals where the *n*\ th argument
378
is the value given for that keyword parameter. As of this
379
time only string arguments can be matched (in particular,
380
object paths and signatures can't).
381
`named_service` : str
382
A deprecated alias for `bus_name`.
384
self._require_main_loop()
386
named_service = keywords.pop('named_service', None)
387
if named_service is not None:
388
if bus_name is not None:
389
raise TypeError('bus_name and named_service cannot both be '
391
bus_name = named_service
392
from warnings import warn
393
warn('Passing the named_service parameter to add_signal_receiver '
394
'by name is deprecated: please use positional parameters',
395
DeprecationWarning, stacklevel=2)
397
match = SignalMatch(self, bus_name, path, dbus_interface,
398
signal_name, handler_function, **keywords)
400
self._signals_lock.acquire()
402
by_interface = self._signal_recipients_by_object_path.setdefault(
404
by_member = by_interface.setdefault(dbus_interface, {})
405
matches = by_member.setdefault(signal_name, [])
407
matches.append(match)
409
self._signals_lock.release()
413
def _iter_easy_matches(self, path, dbus_interface, member):
415
path_keys = (None, path)
418
if dbus_interface is not None:
419
interface_keys = (None, dbus_interface)
421
interface_keys = (None,)
422
if member is not None:
423
member_keys = (None, member)
425
member_keys = (None,)
427
for path in path_keys:
428
by_interface = self._signal_recipients_by_object_path.get(path,
430
if by_interface is None:
432
for dbus_interface in interface_keys:
433
by_member = by_interface.get(dbus_interface, None)
434
if by_member is None:
436
for member in member_keys:
437
matches = by_member.get(member, None)
443
def remove_signal_receiver(self, handler_or_match,
449
named_service = keywords.pop('named_service', None)
450
if named_service is not None:
451
if bus_name is not None:
452
raise TypeError('bus_name and named_service cannot both be '
454
bus_name = named_service
455
from warnings import warn
456
warn('Passing the named_service parameter to '
457
'remove_signal_receiver by name is deprecated: please use '
458
'positional parameters',
459
DeprecationWarning, stacklevel=2)
463
self._signals_lock.acquire()
465
by_interface = self._signal_recipients_by_object_path.get(path,
467
if by_interface is None:
469
by_member = by_interface.get(dbus_interface, None)
470
if by_member is None:
472
matches = by_member.get(signal_name, None)
476
for match in matches:
477
if (handler_or_match is match
478
or match.matches_removal_spec(bus_name,
484
deletions.append(match)
489
by_member[signal_name] = new
491
del by_member[signal_name]
493
del by_interface[dbus_interface]
495
del self._signal_recipients_by_object_path[path]
497
self._signals_lock.release()
499
for match in deletions:
500
self._clean_up_signal_match(match)
502
def _clean_up_signal_match(self, match):
503
# Now called without the signals lock held (it was held in <= 0.81.0)
506
def _signal_func(self, message):
507
"""D-Bus filter function. Handle signals by dispatching to Python
508
callbacks kept in the match-rule tree.
511
if not isinstance(message, SignalMessage):
512
return HANDLER_RESULT_NOT_YET_HANDLED
514
dbus_interface = message.get_interface()
515
path = message.get_path()
516
signal_name = message.get_member()
518
for match in self._iter_easy_matches(path, dbus_interface,
520
match.maybe_handle_message(message)
522
if (dbus_interface == LOCAL_IFACE and
523
path == LOCAL_PATH and
524
signal_name == 'Disconnected'):
525
for cb in self.__call_on_disconnection:
528
except Exception as e:
529
# basicConfig is a no-op if logging is already configured
530
logging.basicConfig()
531
_logger.error('Exception in handler for Disconnected '
532
'signal:', exc_info=1)
534
return HANDLER_RESULT_NOT_YET_HANDLED
536
def call_async(self, bus_name, object_path, dbus_interface, method,
537
signature, args, reply_handler, error_handler,
538
timeout=-1.0, utf8_strings=False, byte_arrays=False,
539
require_main_loop=True):
540
"""Call the given method, asynchronously.
542
If the reply_handler is None, successful replies will be ignored.
543
If the error_handler is None, failures will be ignored. If both
544
are None, the implementation may request that no reply is sent.
546
:Returns: The dbus.lowlevel.PendingCall.
549
if object_path == LOCAL_PATH:
550
raise DBusException('Methods may not be called on the reserved '
551
'path %s' % LOCAL_PATH)
552
if dbus_interface == LOCAL_IFACE:
553
raise DBusException('Methods may not be called on the reserved '
554
'interface %s' % LOCAL_IFACE)
555
# no need to validate other args - MethodCallMessage ctor will do
557
get_args_opts = {'utf8_strings': utf8_strings,
558
'byte_arrays': byte_arrays}
560
message = MethodCallMessage(destination=bus_name,
562
interface=dbus_interface,
564
# Add the arguments to the function
566
message.append(signature=signature, *args)
567
except Exception as e:
568
logging.basicConfig()
569
_logger.error('Unable to set arguments %r according to '
570
'signature %r: %s: %s',
571
args, signature, e.__class__, e)
574
if reply_handler is None and error_handler is None:
575
# we don't care what happens, so just send it
576
self.send_message(message)
579
if reply_handler is None:
580
reply_handler = _noop
581
if error_handler is None:
582
error_handler = _noop
584
def msg_reply_handler(message):
585
if isinstance(message, MethodReturnMessage):
586
reply_handler(*message.get_args_list(**get_args_opts))
587
elif isinstance(message, ErrorMessage):
588
error_handler(DBusException(name=message.get_error_name(),
589
*message.get_args_list()))
591
error_handler(TypeError('Unexpected type for reply '
592
'message: %r' % message))
593
return self.send_message_with_reply(message, msg_reply_handler,
595
require_main_loop=require_main_loop)
597
def call_blocking(self, bus_name, object_path, dbus_interface, method,
598
signature, args, timeout=-1.0, utf8_strings=False,
600
"""Call the given method, synchronously.
603
if object_path == LOCAL_PATH:
604
raise DBusException('Methods may not be called on the reserved '
605
'path %s' % LOCAL_PATH)
606
if dbus_interface == LOCAL_IFACE:
607
raise DBusException('Methods may not be called on the reserved '
608
'interface %s' % LOCAL_IFACE)
609
# no need to validate other args - MethodCallMessage ctor will do
611
get_args_opts = {'utf8_strings': utf8_strings,
612
'byte_arrays': byte_arrays}
614
message = MethodCallMessage(destination=bus_name,
616
interface=dbus_interface,
618
# Add the arguments to the function
620
message.append(signature=signature, *args)
621
except Exception as e:
622
logging.basicConfig()
623
_logger.error('Unable to set arguments %r according to '
624
'signature %r: %s: %s',
625
args, signature, e.__class__, e)
628
# make a blocking call
629
reply_message = self.send_message_with_reply_and_block(
631
args_list = reply_message.get_args_list(**get_args_opts)
632
if len(args_list) == 0:
634
elif len(args_list) == 1:
637
return tuple(args_list)
639
def call_on_disconnection(self, callable):
640
"""Arrange for `callable` to be called with one argument (this
641
Connection object) when the Connection becomes
646
self.__call_on_disconnection.append(callable)