| Home | Trees | Indices | Help |
|---|
|
|
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
2 #
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:
10 #
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
13 #
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.
22
23 __all__ = ('Connection', 'SignalMatch')
24 __docformat__ = 'reStructuredText'
25
26 import logging
27 import threading
28 import weakref
29
30 from _dbus_bindings import (
31 Connection as _Connection, LOCAL_IFACE, LOCAL_PATH, validate_bus_name,
32 validate_interface_name, validate_member_name, validate_object_path)
33 from dbus.exceptions import DBusException
34 from dbus.lowlevel import (
35 ErrorMessage, HANDLER_RESULT_NOT_YET_HANDLED, MethodCallMessage,
36 MethodReturnMessage, SignalMessage)
37 from dbus.proxies import ProxyObject
38 from dbus._compat import is_py2, is_py3
39
40 if is_py3:
41 from _dbus_bindings import String
42 else:
43 from _dbus_bindings import UTF8String
44
45
46 _logger = logging.getLogger('dbus.connection')
47
48
51
52
54 _slots = ['_sender_name_owner', '_member', '_interface', '_sender',
55 '_path', '_handler', '_args_match', '_rule',
56 '_byte_arrays', '_conn_weakref',
57 '_destination_keyword', '_interface_keyword',
58 '_message_keyword', '_member_keyword',
59 '_sender_keyword', '_path_keyword', '_int_args_match']
60 if is_py2:
61 _slots.append('_utf8_strings')
62
63 __slots__ = tuple(_slots)
64
65 - def __init__(self, conn, sender, object_path, dbus_interface,
66 member, handler, byte_arrays=False,
67 sender_keyword=None, path_keyword=None,
68 interface_keyword=None, member_keyword=None,
69 message_keyword=None, destination_keyword=None,
70 **kwargs):
71 if member is not None:
72 validate_member_name(member)
73 if dbus_interface is not None:
74 validate_interface_name(dbus_interface)
75 if sender is not None:
76 validate_bus_name(sender)
77 if object_path is not None:
78 validate_object_path(object_path)
79
80 self._rule = None
81 self._conn_weakref = weakref.ref(conn)
82 self._sender = sender
83 self._interface = dbus_interface
84 self._member = member
85 self._path = object_path
86 self._handler = handler
87
88 # if the connection is actually a bus, it's responsible for changing
89 # this later
90 self._sender_name_owner = sender
91
92 if is_py2:
93 self._utf8_strings = kwargs.pop('utf8_strings', False)
94 elif 'utf8_strings' in kwargs:
95 raise TypeError("unexpected keyword argument 'utf8_strings'")
96
97 self._byte_arrays = byte_arrays
98 self._sender_keyword = sender_keyword
99 self._path_keyword = path_keyword
100 self._member_keyword = member_keyword
101 self._interface_keyword = interface_keyword
102 self._message_keyword = message_keyword
103 self._destination_keyword = destination_keyword
104
105 self._args_match = kwargs
106 if not kwargs:
107 self._int_args_match = None
108 else:
109 self._int_args_match = {}
110 for kwarg in kwargs:
111 if not kwarg.startswith('arg'):
112 raise TypeError('SignalMatch: unknown keyword argument %s'
113 % kwarg)
114 try:
115 index = int(kwarg[3:])
116 except ValueError:
117 raise TypeError('SignalMatch: unknown keyword argument %s'
118 % kwarg)
119 if index < 0 or index > 63:
120 raise TypeError('SignalMatch: arg match index must be in '
121 'range(64), not %d' % index)
122 self._int_args_match[index] = kwargs[kwarg]
123
127
131
135
136 sender = property(lambda self: self._sender)
137
139 if self._rule is None:
140 rule = ["type='signal'"]
141 if self._sender is not None:
142 rule.append("sender='%s'" % self._sender)
143 if self._path is not None:
144 rule.append("path='%s'" % self._path)
145 if self._interface is not None:
146 rule.append("interface='%s'" % self._interface)
147 if self._member is not None:
148 rule.append("member='%s'" % self._member)
149 if self._int_args_match is not None:
150 for index, value in self._int_args_match.items():
151 rule.append("arg%d='%s'" % (index, value))
152
153 self._rule = ','.join(rule)
154
155 return self._rule
156
158 return ('<%s at %x "%s" on conn %r>'
159 % (self.__class__, id(self), self._rule, self._conn_weakref()))
160
162 self._sender_name_owner = new_name
163
164 - def matches_removal_spec(self, sender, object_path,
165 dbus_interface, member, handler, **kwargs):
166 if handler not in (None, self._handler):
167 return False
168 if sender != self._sender:
169 return False
170 if object_path != self._path:
171 return False
172 if dbus_interface != self._interface:
173 return False
174 if member != self._member:
175 return False
176 if kwargs != self._args_match:
177 return False
178 return True
179
181 args = None
182
183 # these haven't been checked yet by the match tree
184 if self._sender_name_owner not in (None, message.get_sender()):
185 return False
186 if self._int_args_match is not None:
187 # extracting args with utf8_strings and byte_arrays is less work
188 kwargs = dict(byte_arrays=True)
189 arg_type = (String if is_py3 else UTF8String)
190 if is_py2:
191 kwargs['utf8_strings'] = True
192 args = message.get_args_list(**kwargs)
193 for index, value in self._int_args_match.items():
194 if (index >= len(args)
195 or not isinstance(args[index], arg_type)
196 or args[index] != value):
197 return False
198
199 # these have likely already been checked by the match tree
200 if self._member not in (None, message.get_member()):
201 return False
202 if self._interface not in (None, message.get_interface()):
203 return False
204 if self._path not in (None, message.get_path()):
205 return False
206
207 try:
208 # minor optimization: if we already extracted the args with the
209 # right calling convention to do the args match, don't bother
210 # doing so again
211 utf8_strings = (is_py2 and self._utf8_strings)
212 if args is None or not utf8_strings or not self._byte_arrays:
213 kwargs = dict(byte_arrays=self._byte_arrays)
214 if is_py2:
215 kwargs['utf8_strings'] = self._utf8_strings
216 args = message.get_args_list(**kwargs)
217 kwargs = {}
218 if self._sender_keyword is not None:
219 kwargs[self._sender_keyword] = message.get_sender()
220 if self._destination_keyword is not None:
221 kwargs[self._destination_keyword] = message.get_destination()
222 if self._path_keyword is not None:
223 kwargs[self._path_keyword] = message.get_path()
224 if self._member_keyword is not None:
225 kwargs[self._member_keyword] = message.get_member()
226 if self._interface_keyword is not None:
227 kwargs[self._interface_keyword] = message.get_interface()
228 if self._message_keyword is not None:
229 kwargs[self._message_keyword] = message
230 self._handler(*args, **kwargs)
231 except:
232 # basicConfig is a no-op if logging is already configured
233 logging.basicConfig()
234 _logger.error('Exception in handler for D-Bus signal:', exc_info=1)
235
236 return True
237
239 conn = self._conn_weakref()
240 # do nothing if the connection has already vanished
241 if conn is not None:
242 conn.remove_signal_receiver(self, self._member,
243 self._interface, self._sender,
244 self._path,
245 **self._args_match)
246
247
249 """A connection to another application. In this base class there is
250 assumed to be no bus daemon.
251
252 :Since: 0.81.0
253 """
254
255 ProxyObjectClass = ProxyObject
256
258 super(Connection, self).__init__(*args, **kwargs)
259
260 # this if-block is needed because shared bus connections can be
261 # __init__'ed more than once
262 if not hasattr(self, '_dbus_Connection_initialized'):
263 self._dbus_Connection_initialized = 1
264
265 self.__call_on_disconnection = []
266
267 self._signal_recipients_by_object_path = {}
268 """Map from object path to dict mapping dbus_interface to dict
269 mapping member to list of SignalMatch objects."""
270
271 self._signals_lock = threading.Lock()
272 """Lock used to protect signal data structures"""
273
274 self.add_message_filter(self.__class__._signal_func)
275
277 """Return the unique name for the given bus name, activating it
278 if necessary and possible.
279
280 If the name is already unique or this connection is not to a
281 bus daemon, just return it.
282
283 :Returns: a bus name. If the given `bus_name` exists, the returned
284 name identifies its current owner; otherwise the returned name
285 does not exist.
286 :Raises DBusException: if the implementation has failed
287 to activate the given bus name.
288 :Since: 0.81.0
289 """
290 return bus_name
291
294 """Return a local proxy for the given remote object.
295
296 Method calls on the proxy are translated into method calls on the
297 remote object.
298
299 :Parameters:
300 `bus_name` : str
301 A bus name (either the unique name or a well-known name)
302 of the application owning the object. The keyword argument
303 named_service is a deprecated alias for this.
304 `object_path` : str
305 The object path of the desired object
306 `introspect` : bool
307 If true (default), attempt to introspect the remote
308 object to find out supported methods and their signatures
309
310 :Returns: a `dbus.proxies.ProxyObject`
311 """
312 named_service = kwargs.pop('named_service', None)
313 if named_service is not None:
314 if bus_name is not None:
315 raise TypeError('bus_name and named_service cannot both '
316 'be specified')
317 from warnings import warn
318 warn('Passing the named_service parameter to get_object by name '
319 'is deprecated: please use positional parameters',
320 DeprecationWarning, stacklevel=2)
321 bus_name = named_service
322 if kwargs:
323 raise TypeError('get_object does not take these keyword '
324 'arguments: %s' % ', '.join(kwargs.keys()))
325
326 return self.ProxyObjectClass(self, bus_name, object_path,
327 introspect=introspect)
328
329 - def add_signal_receiver(self, handler_function,
330 signal_name=None,
331 dbus_interface=None,
332 bus_name=None,
333 path=None,
334 **keywords):
335 """Arrange for the given function to be called when a signal matching
336 the parameters is received.
337
338 :Parameters:
339 `handler_function` : callable
340 The function to be called. Its positional arguments will
341 be the arguments of the signal. By default it will receive
342 no keyword arguments, but see the description of
343 the optional keyword arguments below.
344 `signal_name` : str
345 The signal name; None (the default) matches all names
346 `dbus_interface` : str
347 The D-Bus interface name with which to qualify the signal;
348 None (the default) matches all interface names
349 `bus_name` : str
350 A bus name for the sender, which will be resolved to a
351 unique name if it is not already; None (the default) matches
352 any sender.
353 `path` : str
354 The object path of the object which must have emitted the
355 signal; None (the default) matches any object path
356 :Keywords:
357 `utf8_strings` : bool
358 If True, the handler function will receive any string
359 arguments as dbus.UTF8String objects (a subclass of str
360 guaranteed to be UTF-8). If False (default) it will receive
361 any string arguments as dbus.String objects (a subclass of
362 unicode).
363 `byte_arrays` : bool
364 If True, the handler function will receive any byte-array
365 arguments as dbus.ByteArray objects (a subclass of str).
366 If False (default) it will receive any byte-array
367 arguments as a dbus.Array of dbus.Byte (subclasses of:
368 a list of ints).
369 `sender_keyword` : str
370 If not None (the default), the handler function will receive
371 the unique name of the sending endpoint as a keyword
372 argument with this name.
373 `destination_keyword` : str
374 If not None (the default), the handler function will receive
375 the bus name of the destination (or None if the signal is a
376 broadcast, as is usual) as a keyword argument with this name.
377 `interface_keyword` : str
378 If not None (the default), the handler function will receive
379 the signal interface as a keyword argument with this name.
380 `member_keyword` : str
381 If not None (the default), the handler function will receive
382 the signal name as a keyword argument with this name.
383 `path_keyword` : str
384 If not None (the default), the handler function will receive
385 the object-path of the sending object as a keyword argument
386 with this name.
387 `message_keyword` : str
388 If not None (the default), the handler function will receive
389 the `dbus.lowlevel.SignalMessage` as a keyword argument with
390 this name.
391 `arg...` : unicode or UTF-8 str
392 If there are additional keyword parameters of the form
393 ``arg``\ *n*, match only signals where the *n*\ th argument
394 is the value given for that keyword parameter. As of this
395 time only string arguments can be matched (in particular,
396 object paths and signatures can't).
397 `named_service` : str
398 A deprecated alias for `bus_name`.
399 """
400 self._require_main_loop()
401
402 named_service = keywords.pop('named_service', None)
403 if named_service is not None:
404 if bus_name is not None:
405 raise TypeError('bus_name and named_service cannot both be '
406 'specified')
407 bus_name = named_service
408 from warnings import warn
409 warn('Passing the named_service parameter to add_signal_receiver '
410 'by name is deprecated: please use positional parameters',
411 DeprecationWarning, stacklevel=2)
412
413 match = SignalMatch(self, bus_name, path, dbus_interface,
414 signal_name, handler_function, **keywords)
415
416 self._signals_lock.acquire()
417 try:
418 by_interface = self._signal_recipients_by_object_path.setdefault(
419 path, {})
420 by_member = by_interface.setdefault(dbus_interface, {})
421 matches = by_member.setdefault(signal_name, [])
422
423 matches.append(match)
424 finally:
425 self._signals_lock.release()
426
427 return match
428
430 if path is not None:
431 path_keys = (None, path)
432 else:
433 path_keys = (None,)
434 if dbus_interface is not None:
435 interface_keys = (None, dbus_interface)
436 else:
437 interface_keys = (None,)
438 if member is not None:
439 member_keys = (None, member)
440 else:
441 member_keys = (None,)
442
443 for path in path_keys:
444 by_interface = self._signal_recipients_by_object_path.get(path)
445 if by_interface is None:
446 continue
447 for dbus_interface in interface_keys:
448 by_member = by_interface.get(dbus_interface, None)
449 if by_member is None:
450 continue
451 for member in member_keys:
452 matches = by_member.get(member, None)
453 if matches is None:
454 continue
455 for m in matches:
456 yield m
457
458 - def remove_signal_receiver(self, handler_or_match,
459 signal_name=None,
460 dbus_interface=None,
461 bus_name=None,
462 path=None,
463 **keywords):
464 named_service = keywords.pop('named_service', None)
465 if named_service is not None:
466 if bus_name is not None:
467 raise TypeError('bus_name and named_service cannot both be '
468 'specified')
469 bus_name = named_service
470 from warnings import warn
471 warn('Passing the named_service parameter to '
472 'remove_signal_receiver by name is deprecated: please use '
473 'positional parameters',
474 DeprecationWarning, stacklevel=2)
475
476 new = []
477 deletions = []
478 self._signals_lock.acquire()
479 try:
480 by_interface = self._signal_recipients_by_object_path.get(path,
481 None)
482 if by_interface is None:
483 return
484 by_member = by_interface.get(dbus_interface, None)
485 if by_member is None:
486 return
487 matches = by_member.get(signal_name, None)
488 if matches is None:
489 return
490
491 for match in matches:
492 if (handler_or_match is match
493 or match.matches_removal_spec(bus_name,
494 path,
495 dbus_interface,
496 signal_name,
497 handler_or_match,
498 **keywords)):
499 deletions.append(match)
500 else:
501 new.append(match)
502
503 if new:
504 by_member[signal_name] = new
505 else:
506 del by_member[signal_name]
507 if not by_member:
508 del by_interface[dbus_interface]
509 if not by_interface:
510 del self._signal_recipients_by_object_path[path]
511 finally:
512 self._signals_lock.release()
513
514 for match in deletions:
515 self._clean_up_signal_match(match)
516
520
522 """D-Bus filter function. Handle signals by dispatching to Python
523 callbacks kept in the match-rule tree.
524 """
525
526 if not isinstance(message, SignalMessage):
527 return HANDLER_RESULT_NOT_YET_HANDLED
528
529 dbus_interface = message.get_interface()
530 path = message.get_path()
531 signal_name = message.get_member()
532
533 for match in self._iter_easy_matches(path, dbus_interface,
534 signal_name):
535 match.maybe_handle_message(message)
536
537 if (dbus_interface == LOCAL_IFACE and
538 path == LOCAL_PATH and
539 signal_name == 'Disconnected'):
540 for cb in self.__call_on_disconnection:
541 try:
542 cb(self)
543 except Exception:
544 # basicConfig is a no-op if logging is already configured
545 logging.basicConfig()
546 _logger.error('Exception in handler for Disconnected '
547 'signal:', exc_info=1)
548
549 return HANDLER_RESULT_NOT_YET_HANDLED
550
551 - def call_async(self, bus_name, object_path, dbus_interface, method,
552 signature, args, reply_handler, error_handler,
553 timeout=-1.0, byte_arrays=False,
554 require_main_loop=True, **kwargs):
555 """Call the given method, asynchronously.
556
557 If the reply_handler is None, successful replies will be ignored.
558 If the error_handler is None, failures will be ignored. If both
559 are None, the implementation may request that no reply is sent.
560
561 :Returns: The dbus.lowlevel.PendingCall.
562 :Since: 0.81.0
563 """
564 if object_path == LOCAL_PATH:
565 raise DBusException('Methods may not be called on the reserved '
566 'path %s' % LOCAL_PATH)
567 if dbus_interface == LOCAL_IFACE:
568 raise DBusException('Methods may not be called on the reserved '
569 'interface %s' % LOCAL_IFACE)
570 # no need to validate other args - MethodCallMessage ctor will do
571
572 get_args_opts = dict(byte_arrays=byte_arrays)
573 if is_py2:
574 get_args_opts['utf8_strings'] = kwargs.get('utf8_strings', False)
575 elif 'utf8_strings' in kwargs:
576 raise TypeError("unexpected keyword argument 'utf8_strings'")
577
578 message = MethodCallMessage(destination=bus_name,
579 path=object_path,
580 interface=dbus_interface,
581 method=method)
582 # Add the arguments to the function
583 try:
584 message.append(signature=signature, *args)
585 except Exception as e:
586 logging.basicConfig()
587 _logger.error('Unable to set arguments %r according to '
588 'signature %r: %s: %s',
589 args, signature, e.__class__, e)
590 raise
591
592 if reply_handler is None and error_handler is None:
593 # we don't care what happens, so just send it
594 self.send_message(message)
595 return
596
597 if reply_handler is None:
598 reply_handler = _noop
599 if error_handler is None:
600 error_handler = _noop
601
602 def msg_reply_handler(message):
603 if isinstance(message, MethodReturnMessage):
604 reply_handler(*message.get_args_list(**get_args_opts))
605 elif isinstance(message, ErrorMessage):
606 error_handler(DBusException(name=message.get_error_name(),
607 *message.get_args_list()))
608 else:
609 error_handler(TypeError('Unexpected type for reply '
610 'message: %r' % message))
611 return self.send_message_with_reply(message, msg_reply_handler,
612 timeout,
613 require_main_loop=require_main_loop)
614
615 - def call_blocking(self, bus_name, object_path, dbus_interface, method,
616 signature, args, timeout=-1.0,
617 byte_arrays=False, **kwargs):
618 """Call the given method, synchronously.
619 :Since: 0.81.0
620 """
621 if object_path == LOCAL_PATH:
622 raise DBusException('Methods may not be called on the reserved '
623 'path %s' % LOCAL_PATH)
624 if dbus_interface == LOCAL_IFACE:
625 raise DBusException('Methods may not be called on the reserved '
626 'interface %s' % LOCAL_IFACE)
627 # no need to validate other args - MethodCallMessage ctor will do
628
629 get_args_opts = dict(byte_arrays=byte_arrays)
630 if is_py2:
631 get_args_opts['utf8_strings'] = kwargs.get('utf8_strings', False)
632 elif 'utf8_strings' in kwargs:
633 raise TypeError("unexpected keyword argument 'utf8_strings'")
634
635 message = MethodCallMessage(destination=bus_name,
636 path=object_path,
637 interface=dbus_interface,
638 method=method)
639 # Add the arguments to the function
640 try:
641 message.append(signature=signature, *args)
642 except Exception as e:
643 logging.basicConfig()
644 _logger.error('Unable to set arguments %r according to '
645 'signature %r: %s: %s',
646 args, signature, e.__class__, e)
647 raise
648
649 # make a blocking call
650 reply_message = self.send_message_with_reply_and_block(
651 message, timeout)
652 args_list = reply_message.get_args_list(**get_args_opts)
653 if len(args_list) == 0:
654 return None
655 elif len(args_list) == 1:
656 return args_list[0]
657 else:
658 return tuple(args_list)
659
661 """Arrange for `callable` to be called with one argument (this
662 Connection object) when the Connection becomes
663 disconnected.
664
665 :Since: 0.83.0
666 """
667 self.__call_on_disconnection.append(callable)
668
| Home | Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Sun Mar 6 17:56:49 2016 | http://epydoc.sourceforge.net |