1 """Implementation for dbus.Bus. Not to be imported directly."""
3 # Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
4 # Copyright (C) 2003 David Zeuthen
5 # Copyright (C) 2004 Rob Taylor
6 # Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
8 # Licensed under the Academic Free License version 2.1
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with this program; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 from __future__
import generators
26 __all__
= ('Bus', 'SystemBus', 'SessionBus', 'StarterBus')
27 __docformat__
= 'reStructuredText'
33 from traceback
import print_exc
35 from _dbus_bindings
import BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,\
36 BUS_DAEMON_IFACE
, DBusException
, UTF8String
,\
37 validate_member_name
, validate_interface_name
,\
38 validate_bus_name
, validate_object_path
,\
39 BUS_SESSION
, BUS_SYSTEM
, BUS_STARTER
,\
40 Connection
as _Connection
,\
41 DBUS_START_REPLY_SUCCESS
, \
42 DBUS_START_REPLY_ALREADY_RUNNING
, \
44 HANDLER_RESULT_NOT_YET_HANDLED
,\
45 HANDLER_RESULT_HANDLED
46 from dbus
.bus
import _BusDaemonMixin
47 from dbus
.connection
import _MethodCallMixin
48 from dbus
.proxies
import ProxyObject
53 import dummy_thread
as thread
55 logger
= logging
.getLogger('dbus._dbus')
57 _NAME_OWNER_CHANGE_MATCH
= ("type='signal',sender='%s',"
58 "interface='%s',member='NameOwnerChanged',"
59 "path='%s',arg0='%%s'"
60 % (BUS_DAEMON_NAME
, BUS_DAEMON_IFACE
,
62 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
66 class SignalMatch(object):
67 __slots__
= ('sender_unique', '_member', '_interface', '_sender',
68 '_path', '_handler', '_args_match', '_rule',
69 '_utf8_strings', '_byte_arrays', '_conn_weakref',
70 '_destination_keyword', '_interface_keyword',
71 '_message_keyword', '_member_keyword',
72 '_sender_keyword', '_path_keyword', '_int_args_match')
74 def __init__(self
, conn
, sender
, object_path
, dbus_interface
,
75 member
, handler
, utf8_strings
=False, byte_arrays
=False,
76 sender_keyword
=None, path_keyword
=None,
77 interface_keyword
=None, member_keyword
=None,
78 message_keyword
=None, destination_keyword
=None,
80 if member
is not None:
81 validate_member_name(member
)
82 if dbus_interface
is not None:
83 validate_interface_name(dbus_interface
)
84 if sender
is not None:
85 validate_bus_name(sender
)
86 if object_path
is not None:
87 validate_object_path(object_path
)
89 self
._conn
_weakref
= weakref
.ref(conn
)
91 self
._interface
= dbus_interface
93 self
._path
= object_path
94 self
._handler
= handler
95 if (sender
is not None and sender
[:1] != ':'
96 and sender
!= BUS_DAEMON_NAME
):
97 self
.sender_unique
= conn
.get_object(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
).GetNameOwner(sender
, dbus_interface
=BUS_DAEMON_IFACE
)
99 self
.sender_unique
= sender
100 self
._utf
8_strings
= utf8_strings
101 self
._byte
_arrays
= byte_arrays
102 self
._sender
_keyword
= sender_keyword
103 self
._path
_keyword
= path_keyword
104 self
._member
_keyword
= member_keyword
105 self
._interface
_keyword
= interface_keyword
106 self
._message
_keyword
= message_keyword
107 self
._destination
_keyword
= destination_keyword
109 self
._args
_match
= kwargs
111 self
._int
_args
_match
= None
113 self
._int
_args
_match
= {}
115 if not kwarg
.startswith('arg'):
116 raise TypeError('SignalMatch: unknown keyword argument %s'
119 index
= int(kwarg
[3:])
121 raise TypeError('SignalMatch: unknown keyword argument %s'
123 if index
< 0 or index
> 63:
124 raise TypeError('SignalMatch: arg match index must be in '
125 'range(64), not %d' % index
)
126 self
._int
_args
_match
[index
] = kwargs
[kwarg
]
128 # we're always going to have to calculate the match rule for
129 # the Bus's benefit, so this constructor might as well do the work
130 rule
= ["type='signal'"]
131 if self
._sender
is not None:
132 rule
.append("sender='%s'" % self
._sender
)
133 if self
._path
is not None:
134 rule
.append("path='%s'" % self
._path
)
135 if self
._interface
is not None:
136 rule
.append("interface='%s'" % self
._interface
)
137 if self
._member
is not None:
138 rule
.append("member='%s'" % self
._member
)
139 for kwarg
, value
in kwargs
.iteritems():
140 rule
.append("%s='%s'" % (kwarg
, value
))
142 self
._rule
= ','.join(rule
)
148 return ('<%s at %x "%s" on conn %r>'
149 % (self
.__class
__, id(self
), self
._rule
, self
._conn
_weakref
()))
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_unique
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 # FIXME: need to decide whether dbus-python uses logging, or
213 # stderr, or what, and make it consistent
214 sys
.stderr
.write('Exception in handler for D-Bus signal:\n')
220 #logger.debug('%r: removing', self)
221 conn
= self
._conn
_weakref
()
222 # do nothing if the connection has already vanished
224 #logger.debug('%r: removing from connection %r', self, conn)
225 conn
.remove_signal_receiver(self
, self
._member
,
226 self
._interface
, self
._sender
,
231 class Bus(_Connection
, _MethodCallMixin
, _BusDaemonMixin
):
232 """A connection to a DBus daemon.
234 One of three possible standard buses, the SESSION, SYSTEM,
238 TYPE_SESSION
= BUS_SESSION
239 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
241 TYPE_SYSTEM
= BUS_SYSTEM
242 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
244 TYPE_STARTER
= BUS_STARTER
245 """Represents the bus that started this service by activation (same as
246 the global dbus.BUS_STARTER)"""
248 ProxyObjectClass
= ProxyObject
250 START_REPLY_SUCCESS
= DBUS_START_REPLY_SUCCESS
251 START_REPLY_ALREADY_RUNNING
= DBUS_START_REPLY_ALREADY_RUNNING
253 _shared_instances
= {}
255 def __new__(cls
, bus_type
=TYPE_SESSION
, private
=False, mainloop
=None):
256 """Constructor, returning an existing instance where appropriate.
258 The returned instance is actually always an instance of `SessionBus`,
259 `SystemBus` or `StarterBus`.
262 `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
263 Connect to the appropriate bus
265 If true, never return an existing shared instance, but instead
266 return a private connection
267 `mainloop` : dbus.mainloop.NativeMainLoop
268 The main loop to use. The default is to use the default
269 main loop if one has been set up, or raise an exception
272 - There is currently no way to connect this class to a custom
274 - Some of this functionality should be available on
275 peer-to-peer D-Bus connections too.
276 :Changed: in dbus-python 0.80:
277 converted from a wrapper around a Connection to a Connection
280 if (not private
and bus_type
in cls
._shared
_instances
):
281 return cls
._shared
_instances
[bus_type
]
283 # this is a bit odd, but we create instances of the subtypes
284 # so we can return the shared instances if someone tries to
285 # construct one of them (otherwise we'd eg try and return an
286 # instance of Bus from __new__ in SessionBus). why are there
287 # three ways to construct this class? we just don't know.
288 if bus_type
== BUS_SESSION
:
289 subclass
= SessionBus
290 elif bus_type
== BUS_SYSTEM
:
292 elif bus_type
== BUS_STARTER
:
293 subclass
= StarterBus
295 raise ValueError('invalid bus_type %s' % bus_type
)
297 bus
= subclass
._new
_for
_bus
(bus_type
, mainloop
=mainloop
)
299 bus
._bus
_type
= bus_type
300 # _bus_names is used by dbus.service.BusName!
301 bus
._bus
_names
= weakref
.WeakValueDictionary()
303 bus
._signal
_recipients
_by
_object
_path
= {}
304 """Map from object path to dict mapping dbus_interface to dict
305 mapping member to list of SignalMatch objects."""
307 bus
._signal
_sender
_matches
= {}
308 """Map from sender well-known name to list of match rules for all
309 signal handlers that match on sender well-known name."""
311 bus
._signals
_lock
= thread
.allocate_lock()
312 """Lock used to protect signal data structures if doing two
313 removals at the same time (everything else is atomic, thanks to
316 bus
.add_message_filter(bus
.__class
__._signal
_func
)
319 cls
._shared
_instances
[bus_type
] = bus
325 if self
.__class
__._shared
_instances
[t
] is self
:
326 del self
.__class
__._shared
_instances
[t
]
327 _Connection
.close(self
)
329 def get_connection(self
):
330 """(Deprecated - in new code, just use self)
332 Return self, for backwards compatibility with earlier dbus-python
333 versions where Bus was not a subclass of Connection.
336 _connection
= property(get_connection
, None, None,
337 """self._connection == self, for backwards
338 compatibility with earlier dbus-python versions
339 where Bus was not a subclass of Connection.""")
341 def get_session(private
=False):
342 """Static method that returns a connection to the session bus.
346 If true, do not return a shared connection.
348 return SessionBus(private
=private
)
350 get_session
= staticmethod(get_session
)
352 def get_system(private
=False):
353 """Static method that returns a connection to the system bus.
357 If true, do not return a shared connection.
359 return SystemBus(private
=private
)
361 get_system
= staticmethod(get_system
)
364 def get_starter(private
=False):
365 """Static method that returns a connection to the starter bus.
369 If true, do not return a shared connection.
371 return StarterBus(private
=private
)
373 get_starter
= staticmethod(get_starter
)
375 def get_object(self
, named_service
, object_path
, introspect
=True,
376 follow_name_owner_changes
=False):
377 """Return a local proxy for the given remote object.
379 Method calls on the proxy are translated into method calls on the
383 `named_service` : str
384 A bus name (either the unique name or a well-known name)
385 of the application owning the object
387 The object path of the desired object
389 If true (default), attempt to introspect the remote
390 object to find out supported methods and their signatures
391 `follow_name_owner_changes` : bool
392 If the object path is a well-known name and this parameter
393 is false (default), resolve the well-known name to the unique
394 name of its current owner and bind to that instead; if the
395 ownership of the well-known name changes in future,
396 keep communicating with the original owner.
397 This is necessary if the D-Bus API used is stateful.
399 If the object path is a well-known name and this parameter
400 is true, whenever the well-known name changes ownership in
401 future, bind to the new owner, if any.
403 If the given object path is a unique name, this parameter
406 :Returns: a `dbus.proxies.ProxyObject`
407 :Raises `DBusException`: if resolving the well-known name to a
410 if follow_name_owner_changes
:
411 self
._require
_main
_loop
() # we don't get the signals otherwise
412 return self
.ProxyObjectClass(self
, named_service
, object_path
,
413 introspect
=introspect
,
414 follow_name_owner_changes
=follow_name_owner_changes
)
416 def add_signal_receiver(self
, handler_function
,
422 """Arrange for the given function to be called when a signal matching
423 the parameters is received.
426 `handler_function` : callable
427 The function to be called. Its positional arguments will
428 be the arguments of the signal. By default it will receive
429 no keyword arguments, but see the description of
430 the optional keyword arguments below.
432 The signal name; None (the default) matches all names
433 `dbus_interface` : str
434 The D-Bus interface name with which to qualify the signal;
435 None (the default) matches all interface names
436 `named_service` : str
437 A bus name for the sender, which will be resolved to a
438 unique name if it is not already; None (the default) matches
441 The object path of the object which must have emitted the
442 signal; None (the default) matches any object path
444 `utf8_strings` : bool
445 If True, the handler function will receive any string
446 arguments as dbus.UTF8String objects (a subclass of str
447 guaranteed to be UTF-8). If False (default) it will receive
448 any string arguments as dbus.String objects (a subclass of
451 If True, the handler function will receive any byte-array
452 arguments as dbus.ByteArray objects (a subclass of str).
453 If False (default) it will receive any byte-array
454 arguments as a dbus.Array of dbus.Byte (subclasses of:
456 `sender_keyword` : str
457 If not None (the default), the handler function will receive
458 the unique name of the sending endpoint as a keyword
459 argument with this name.
460 `destination_keyword` : str
461 If not None (the default), the handler function will receive
462 the bus name of the destination (or None if the signal is a
463 broadcast, as is usual) as a keyword argument with this name.
464 `interface_keyword` : str
465 If not None (the default), the handler function will receive
466 the signal interface as a keyword argument with this name.
467 `member_keyword` : str
468 If not None (the default), the handler function will receive
469 the signal name as a keyword argument with this name.
471 If not None (the default), the handler function will receive
472 the object-path of the sending object as a keyword argument
474 `message_keyword` : str
475 If not None (the default), the handler function will receive
476 the `dbus.lowlevel.SignalMessage` as a keyword argument with
478 `arg...` : unicode or UTF-8 str
479 If there are additional keyword parameters of the form
480 ``arg``\ *n*, match only signals where the *n*\ th argument
481 is the value given for that keyword parameter. As of this
482 time only string arguments can be matched (in particular,
483 object paths and signatures can't).
485 self
._require
_main
_loop
()
487 match
= SignalMatch(self
, named_service
, path
, dbus_interface
,
488 signal_name
, handler_function
, **keywords
)
489 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
491 by_member
= by_interface
.setdefault(dbus_interface
, {})
492 matches
= by_member
.setdefault(signal_name
, [])
493 # The bus daemon is special - its unique-name is org.freedesktop.DBus
494 # rather than starting with :
495 if (named_service
is not None and named_service
[:1] != ':'
496 and named_service
!= BUS_DAEMON_NAME
):
497 notification
= self
._signal
_sender
_matches
.setdefault(named_service
,
500 self
.add_match_string(_NAME_OWNER_CHANGE_MATCH
% named_service
)
501 notification
.append(match
)
502 # make sure nobody is currently manipulating the list
503 self
._signals
_lock
.acquire()
505 matches
.append(match
)
507 self
._signals
_lock
.release()
508 self
.add_match_string(str(match
))
511 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
513 path_keys
= (None, path
)
516 if dbus_interface
is not None:
517 interface_keys
= (None, dbus_interface
)
519 interface_keys
= (None,)
520 if member
is not None:
521 member_keys
= (None, member
)
523 member_keys
= (None,)
525 for path
in path_keys
:
526 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
528 if by_interface
is None:
530 for dbus_interface
in interface_keys
:
531 by_member
= by_interface
.get(dbus_interface
, None)
532 if by_member
is None:
534 for member
in member_keys
:
535 matches
= by_member
.get(member
, None)
541 def _remove_name_owner_changed_for_match(self
, named_service
, match
):
542 # The signals lock must be held.
543 notification
= self
._signal
_sender
_matches
.get(named_service
, False)
546 notification
.remove(match
)
550 self
.remove_match_string(_NAME_OWNER_CHANGE_MATCH
553 def remove_signal_receiver(self
, handler_or_match
,
559 #logger.debug('%r: removing signal receiver %r: member=%s, '
560 #'iface=%s, sender=%s, path=%s, kwargs=%r',
561 #self, handler_or_match, signal_name,
562 #dbus_interface, named_service, path, keywords)
563 #logger.debug('%r', self._signal_recipients_by_object_path)
564 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
565 if by_interface
is None:
567 by_member
= by_interface
.get(dbus_interface
, None)
568 if by_member
is None:
570 matches
= by_member
.get(signal_name
, None)
573 self
._signals
_lock
.acquire()
574 #logger.debug(matches)
577 for match
in matches
:
578 if (handler_or_match
is match
579 or match
.matches_removal_spec(named_service
,
585 #logger.debug('Removing match string: %s', match)
586 self
.remove_match_string(str(match
))
587 self
._remove
_name
_owner
_changed
_for
_match
(named_service
,
591 by_member
[signal_name
] = new
593 self
._signals
_lock
.release()
595 def _signal_func(self
, message
):
596 """D-Bus filter function. Handle signals by dispatching to Python
597 callbacks kept in the match-rule tree.
600 #logger.debug('Incoming message %r with args %r', message,
601 #message.get_args_list())
603 if not isinstance(message
, SignalMessage
):
604 return HANDLER_RESULT_NOT_YET_HANDLED
606 # If it's NameOwnerChanged, we'll need to update our
607 # sender well-known name -> sender unique name mappings
608 if (message
.is_signal(BUS_DAEMON_IFACE
, 'NameOwnerChanged')
609 and message
.has_sender(BUS_DAEMON_NAME
)
610 and message
.has_path(BUS_DAEMON_PATH
)):
611 name
, unused
, new
= message
.get_args_list()
612 for match
in self
._signal
_sender
_matches
.get(name
, (None,))[1:]:
613 match
.sender_unique
= new
615 # See if anyone else wants to know
616 dbus_interface
= message
.get_interface()
617 path
= message
.get_path()
618 signal_name
= message
.get_member()
620 ret
= HANDLER_RESULT_NOT_YET_HANDLED
621 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
623 if match
.maybe_handle_message(message
):
624 ret
= HANDLER_RESULT_HANDLED
628 if self
._bus
_type
== BUS_SESSION
:
630 elif self
._bus
_type
== BUS_SYSTEM
:
632 elif self
._bus
_type
== BUS_STARTER
:
635 raise AssertionError('Unable to represent unknown bus type.')
637 return '<dbus.Bus on %s at %#x>' % (name
, id(self
))
641 # FIXME: Drop the subclasses here? I can't think why we'd ever want
643 class SystemBus(Bus
):
644 """The system-wide message bus."""
645 def __new__(cls
, private
=False, mainloop
=None):
646 """Return a connection to the system bus.
650 If true, never return an existing shared instance, but instead
651 return a private connection.
652 `mainloop` : dbus.mainloop.NativeMainLoop
653 The main loop to use. The default is to use the default
654 main loop if one has been set up, or raise an exception
657 return Bus
.__new
__(cls
, Bus
.TYPE_SYSTEM
, mainloop
=mainloop
,
660 class SessionBus(Bus
):
661 """The session (current login) message bus."""
662 def __new__(cls
, private
=False, mainloop
=None):
663 """Return a connection to the session bus.
667 If true, never return an existing shared instance, but instead
668 return a private connection.
669 `mainloop` : dbus.mainloop.NativeMainLoop
670 The main loop to use. The default is to use the default
671 main loop if one has been set up, or raise an exception
674 return Bus
.__new
__(cls
, Bus
.TYPE_SESSION
, private
=private
,
677 class StarterBus(Bus
):
678 """The bus that activated this process (only valid if
679 this process was launched by DBus activation).
681 def __new__(cls
, private
=False, mainloop
=None):
682 """Return a connection to the bus that activated this process.
686 If true, never return an existing shared instance, but instead
687 return a private connection.
688 `mainloop` : dbus.mainloop.NativeMainLoop
689 The main loop to use. The default is to use the default
690 main loop if one has been set up, or raise an exception
693 return Bus
.__new
__(cls
, Bus
.TYPE_STARTER
, private
=private
,
697 _dbus_bindings_warning
= DeprecationWarning("""\
698 The dbus_bindings module is deprecated and will go away soon.
700 dbus-python 0.80 provides only a partial emulation of the old
701 dbus_bindings, which was never meant to be public API.
703 Most uses of dbus_bindings are applications catching the exception
704 dbus.dbus_bindings.DBusException. You should use dbus.DBusException
705 instead (this is compatible with all dbus-python versions since 0.40.2).
707 If you need additional public API, please contact the maintainers via
708 <dbus@lists.freedesktop.org>.
711 if 'DBUS_PYTHON_NO_DEPRECATED' not in os
.environ
:
713 class _DBusBindingsEmulation
:
714 """A partial emulation of the dbus_bindings module."""
717 return '_DBusBindingsEmulation()'
719 return '_DBusBindingsEmulation()'
720 def __getattr__(self
, attr
):
721 if self
._module
is None:
722 from warnings
import warn
as _warn
723 _warn(_dbus_bindings_warning
, DeprecationWarning, stacklevel
=2)
725 import dbus
.dbus_bindings
as m
727 return getattr(self
._module
, attr
)
729 dbus_bindings
= _DBusBindingsEmulation()
730 """Deprecated, don't use."""