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 DBUS_START_REPLY_SUCCESS
, \
41 DBUS_START_REPLY_ALREADY_RUNNING
, \
43 HANDLER_RESULT_NOT_YET_HANDLED
,\
44 HANDLER_RESULT_HANDLED
45 from dbus
.bus
import BusConnection
46 from dbus
.proxies
import ProxyObject
51 import dummy_thread
as thread
53 logger
= logging
.getLogger('dbus._dbus')
55 _NAME_OWNER_CHANGE_MATCH
= ("type='signal',sender='%s',"
56 "interface='%s',member='NameOwnerChanged',"
57 "path='%s',arg0='%%s'"
58 % (BUS_DAEMON_NAME
, BUS_DAEMON_IFACE
,
60 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
64 class SignalMatch(object):
65 __slots__
= ('sender_unique', '_member', '_interface', '_sender',
66 '_path', '_handler', '_args_match', '_rule',
67 '_utf8_strings', '_byte_arrays', '_conn_weakref',
68 '_destination_keyword', '_interface_keyword',
69 '_message_keyword', '_member_keyword',
70 '_sender_keyword', '_path_keyword', '_int_args_match')
72 def __init__(self
, conn
, sender
, object_path
, dbus_interface
,
73 member
, handler
, utf8_strings
=False, byte_arrays
=False,
74 sender_keyword
=None, path_keyword
=None,
75 interface_keyword
=None, member_keyword
=None,
76 message_keyword
=None, destination_keyword
=None,
78 if member
is not None:
79 validate_member_name(member
)
80 if dbus_interface
is not None:
81 validate_interface_name(dbus_interface
)
82 if sender
is not None:
83 validate_bus_name(sender
)
84 if object_path
is not None:
85 validate_object_path(object_path
)
87 self
._conn
_weakref
= weakref
.ref(conn
)
89 self
._interface
= dbus_interface
91 self
._path
= object_path
92 self
._handler
= handler
93 if (sender
is not None and sender
[:1] != ':'
94 and sender
!= BUS_DAEMON_NAME
):
95 self
.sender_unique
= conn
.get_name_owner(sender
)
97 self
.sender_unique
= sender
98 self
._utf
8_strings
= utf8_strings
99 self
._byte
_arrays
= byte_arrays
100 self
._sender
_keyword
= sender_keyword
101 self
._path
_keyword
= path_keyword
102 self
._member
_keyword
= member_keyword
103 self
._interface
_keyword
= interface_keyword
104 self
._message
_keyword
= message_keyword
105 self
._destination
_keyword
= destination_keyword
107 self
._args
_match
= kwargs
109 self
._int
_args
_match
= None
111 self
._int
_args
_match
= {}
113 if not kwarg
.startswith('arg'):
114 raise TypeError('SignalMatch: unknown keyword argument %s'
117 index
= int(kwarg
[3:])
119 raise TypeError('SignalMatch: unknown keyword argument %s'
121 if index
< 0 or index
> 63:
122 raise TypeError('SignalMatch: arg match index must be in '
123 'range(64), not %d' % index
)
124 self
._int
_args
_match
[index
] = kwargs
[kwarg
]
126 # we're always going to have to calculate the match rule for
127 # the Bus's benefit, so this constructor might as well do the work
128 rule
= ["type='signal'"]
129 if self
._sender
is not None:
130 rule
.append("sender='%s'" % self
._sender
)
131 if self
._path
is not None:
132 rule
.append("path='%s'" % self
._path
)
133 if self
._interface
is not None:
134 rule
.append("interface='%s'" % self
._interface
)
135 if self
._member
is not None:
136 rule
.append("member='%s'" % self
._member
)
137 for kwarg
, value
in kwargs
.iteritems():
138 rule
.append("%s='%s'" % (kwarg
, value
))
140 self
._rule
= ','.join(rule
)
146 return ('<%s at %x "%s" on conn %r>'
147 % (self
.__class
__, id(self
), self
._rule
, self
._conn
_weakref
()))
149 def matches_removal_spec(self
, sender
, object_path
,
150 dbus_interface
, member
, handler
, **kwargs
):
151 if handler
not in (None, self
._handler
):
153 if sender
!= self
._sender
:
155 if object_path
!= self
._path
:
157 if dbus_interface
!= self
._interface
:
159 if member
!= self
._member
:
161 if kwargs
!= self
._args
_match
:
165 def maybe_handle_message(self
, message
):
168 # these haven't been checked yet by the match tree
169 if self
.sender_unique
not in (None, message
.get_sender()):
171 if self
._int
_args
_match
is not None:
172 # extracting args with utf8_strings and byte_arrays is less work
173 args
= message
.get_args_list(utf8_strings
=True, byte_arrays
=True)
174 for index
, value
in self
._int
_args
_match
.iteritems():
175 if (index
>= len(args
)
176 or not isinstance(args
[index
], UTF8String
)
177 or args
[index
] != value
):
180 # these have likely already been checked by the match tree
181 if self
._member
not in (None, message
.get_member()):
183 if self
._interface
not in (None, message
.get_interface()):
185 if self
._path
not in (None, message
.get_path()):
189 # minor optimization: if we already extracted the args with the
190 # right calling convention to do the args match, don't bother
192 if args
is None or not self
._utf
8_strings
or not self
._byte
_arrays
:
193 args
= message
.get_args_list(utf8_strings
=self
._utf
8_strings
,
194 byte_arrays
=self
._byte
_arrays
)
196 if self
._sender
_keyword
is not None:
197 kwargs
[self
._sender
_keyword
] = message
.get_sender()
198 if self
._destination
_keyword
is not None:
199 kwargs
[self
._destination
_keyword
] = message
.get_destination()
200 if self
._path
_keyword
is not None:
201 kwargs
[self
._path
_keyword
] = message
.get_path()
202 if self
._member
_keyword
is not None:
203 kwargs
[self
._member
_keyword
] = message
.get_member()
204 if self
._interface
_keyword
is not None:
205 kwargs
[self
._interface
_keyword
] = message
.get_interface()
206 if self
._message
_keyword
is not None:
207 kwargs
[self
._message
_keyword
] = message
208 self
._handler
(*args
, **kwargs
)
210 # FIXME: need to decide whether dbus-python uses logging, or
211 # stderr, or what, and make it consistent
212 sys
.stderr
.write('Exception in handler for D-Bus signal:\n')
218 #logger.debug('%r: removing', self)
219 conn
= self
._conn
_weakref
()
220 # do nothing if the connection has already vanished
222 #logger.debug('%r: removing from connection %r', self, conn)
223 conn
.remove_signal_receiver(self
, self
._member
,
224 self
._interface
, self
._sender
,
229 class Bus(BusConnection
):
230 """A connection to a DBus daemon.
232 One of three possible standard buses, the SESSION, SYSTEM,
236 TYPE_SESSION
= BUS_SESSION
237 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
239 TYPE_SYSTEM
= BUS_SYSTEM
240 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
242 TYPE_STARTER
= BUS_STARTER
243 """Represents the bus that started this service by activation (same as
244 the global dbus.BUS_STARTER)"""
246 ProxyObjectClass
= ProxyObject
248 START_REPLY_SUCCESS
= DBUS_START_REPLY_SUCCESS
249 START_REPLY_ALREADY_RUNNING
= DBUS_START_REPLY_ALREADY_RUNNING
251 _shared_instances
= {}
253 def __new__(cls
, bus_type
=TYPE_SESSION
, private
=False, mainloop
=None):
254 """Constructor, returning an existing instance where appropriate.
256 The returned instance is actually always an instance of `SessionBus`,
257 `SystemBus` or `StarterBus`.
260 `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
261 Connect to the appropriate bus
263 If true, never return an existing shared instance, but instead
264 return a private connection
265 `mainloop` : dbus.mainloop.NativeMainLoop
266 The main loop to use. The default is to use the default
267 main loop if one has been set up, or raise an exception
270 - There is currently no way to connect this class to a custom
272 - Some of this functionality should be available on
273 peer-to-peer D-Bus connections too.
274 :Changed: in dbus-python 0.80:
275 converted from a wrapper around a Connection to a Connection
278 if (not private
and bus_type
in cls
._shared
_instances
):
279 return cls
._shared
_instances
[bus_type
]
281 # this is a bit odd, but we create instances of the subtypes
282 # so we can return the shared instances if someone tries to
283 # construct one of them (otherwise we'd eg try and return an
284 # instance of Bus from __new__ in SessionBus). why are there
285 # three ways to construct this class? we just don't know.
286 if bus_type
== BUS_SESSION
:
287 subclass
= SessionBus
288 elif bus_type
== BUS_SYSTEM
:
290 elif bus_type
== BUS_STARTER
:
291 subclass
= StarterBus
293 raise ValueError('invalid bus_type %s' % bus_type
)
295 bus
= subclass
._new
_for
_bus
(bus_type
, mainloop
=mainloop
)
297 bus
._bus
_type
= bus_type
298 # _bus_names is used by dbus.service.BusName!
299 bus
._bus
_names
= weakref
.WeakValueDictionary()
301 bus
._signal
_recipients
_by
_object
_path
= {}
302 """Map from object path to dict mapping dbus_interface to dict
303 mapping member to list of SignalMatch objects."""
305 bus
._signal
_sender
_matches
= {}
306 """Map from sender well-known name to list of match rules for all
307 signal handlers that match on sender well-known name."""
309 bus
._signals
_lock
= thread
.allocate_lock()
310 """Lock used to protect signal data structures if doing two
311 removals at the same time (everything else is atomic, thanks to
314 bus
.add_message_filter(bus
.__class
__._signal
_func
)
317 cls
._shared
_instances
[bus_type
] = bus
323 if self
.__class
__._shared
_instances
[t
] is self
:
324 del self
.__class
__._shared
_instances
[t
]
325 super(BusConnection
, self
).close()
327 def get_connection(self
):
328 """(Deprecated - in new code, just use self)
330 Return self, for backwards compatibility with earlier dbus-python
331 versions where Bus was not a subclass of Connection.
334 _connection
= property(get_connection
, None, None,
335 """self._connection == self, for backwards
336 compatibility with earlier dbus-python versions
337 where Bus was not a subclass of Connection.""")
339 def get_session(private
=False):
340 """Static method that returns a connection to the session bus.
344 If true, do not return a shared connection.
346 return SessionBus(private
=private
)
348 get_session
= staticmethod(get_session
)
350 def get_system(private
=False):
351 """Static method that returns a connection to the system bus.
355 If true, do not return a shared connection.
357 return SystemBus(private
=private
)
359 get_system
= staticmethod(get_system
)
362 def get_starter(private
=False):
363 """Static method that returns a connection to the starter bus.
367 If true, do not return a shared connection.
369 return StarterBus(private
=private
)
371 get_starter
= staticmethod(get_starter
)
373 def get_object(self
, named_service
, object_path
, introspect
=True,
374 follow_name_owner_changes
=False):
375 """Return a local proxy for the given remote object.
377 Method calls on the proxy are translated into method calls on the
381 `named_service` : str
382 A bus name (either the unique name or a well-known name)
383 of the application owning the object
385 The object path of the desired object
387 If true (default), attempt to introspect the remote
388 object to find out supported methods and their signatures
389 `follow_name_owner_changes` : bool
390 If the object path is a well-known name and this parameter
391 is false (default), resolve the well-known name to the unique
392 name of its current owner and bind to that instead; if the
393 ownership of the well-known name changes in future,
394 keep communicating with the original owner.
395 This is necessary if the D-Bus API used is stateful.
397 If the object path is a well-known name and this parameter
398 is true, whenever the well-known name changes ownership in
399 future, bind to the new owner, if any.
401 If the given object path is a unique name, this parameter
404 :Returns: a `dbus.proxies.ProxyObject`
405 :Raises `DBusException`: if resolving the well-known name to a
408 if follow_name_owner_changes
:
409 self
._require
_main
_loop
() # we don't get the signals otherwise
410 return self
.ProxyObjectClass(self
, named_service
, object_path
,
411 introspect
=introspect
,
412 follow_name_owner_changes
=follow_name_owner_changes
)
414 def add_signal_receiver(self
, handler_function
,
420 """Arrange for the given function to be called when a signal matching
421 the parameters is received.
424 `handler_function` : callable
425 The function to be called. Its positional arguments will
426 be the arguments of the signal. By default it will receive
427 no keyword arguments, but see the description of
428 the optional keyword arguments below.
430 The signal name; None (the default) matches all names
431 `dbus_interface` : str
432 The D-Bus interface name with which to qualify the signal;
433 None (the default) matches all interface names
434 `named_service` : str
435 A bus name for the sender, which will be resolved to a
436 unique name if it is not already; None (the default) matches
439 The object path of the object which must have emitted the
440 signal; None (the default) matches any object path
442 `utf8_strings` : bool
443 If True, the handler function will receive any string
444 arguments as dbus.UTF8String objects (a subclass of str
445 guaranteed to be UTF-8). If False (default) it will receive
446 any string arguments as dbus.String objects (a subclass of
449 If True, the handler function will receive any byte-array
450 arguments as dbus.ByteArray objects (a subclass of str).
451 If False (default) it will receive any byte-array
452 arguments as a dbus.Array of dbus.Byte (subclasses of:
454 `sender_keyword` : str
455 If not None (the default), the handler function will receive
456 the unique name of the sending endpoint as a keyword
457 argument with this name.
458 `destination_keyword` : str
459 If not None (the default), the handler function will receive
460 the bus name of the destination (or None if the signal is a
461 broadcast, as is usual) as a keyword argument with this name.
462 `interface_keyword` : str
463 If not None (the default), the handler function will receive
464 the signal interface as a keyword argument with this name.
465 `member_keyword` : str
466 If not None (the default), the handler function will receive
467 the signal name as a keyword argument with this name.
469 If not None (the default), the handler function will receive
470 the object-path of the sending object as a keyword argument
472 `message_keyword` : str
473 If not None (the default), the handler function will receive
474 the `dbus.lowlevel.SignalMessage` as a keyword argument with
476 `arg...` : unicode or UTF-8 str
477 If there are additional keyword parameters of the form
478 ``arg``\ *n*, match only signals where the *n*\ th argument
479 is the value given for that keyword parameter. As of this
480 time only string arguments can be matched (in particular,
481 object paths and signatures can't).
483 self
._require
_main
_loop
()
485 match
= SignalMatch(self
, named_service
, path
, dbus_interface
,
486 signal_name
, handler_function
, **keywords
)
487 by_interface
= self
._signal
_recipients
_by
_object
_path
.setdefault(path
,
489 by_member
= by_interface
.setdefault(dbus_interface
, {})
490 matches
= by_member
.setdefault(signal_name
, [])
491 # The bus daemon is special - its unique-name is org.freedesktop.DBus
492 # rather than starting with :
493 if (named_service
is not None and named_service
[:1] != ':'
494 and named_service
!= BUS_DAEMON_NAME
):
495 notification
= self
._signal
_sender
_matches
.setdefault(named_service
,
498 self
.add_match_string(_NAME_OWNER_CHANGE_MATCH
% named_service
)
499 notification
.append(match
)
500 # make sure nobody is currently manipulating the list
501 self
._signals
_lock
.acquire()
503 matches
.append(match
)
505 self
._signals
_lock
.release()
506 self
.add_match_string(str(match
))
509 def _iter_easy_matches(self
, path
, dbus_interface
, member
):
511 path_keys
= (None, path
)
514 if dbus_interface
is not None:
515 interface_keys
= (None, dbus_interface
)
517 interface_keys
= (None,)
518 if member
is not None:
519 member_keys
= (None, member
)
521 member_keys
= (None,)
523 for path
in path_keys
:
524 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
,
526 if by_interface
is None:
528 for dbus_interface
in interface_keys
:
529 by_member
= by_interface
.get(dbus_interface
, None)
530 if by_member
is None:
532 for member
in member_keys
:
533 matches
= by_member
.get(member
, None)
539 def _remove_name_owner_changed_for_match(self
, named_service
, match
):
540 # The signals lock must be held.
541 notification
= self
._signal
_sender
_matches
.get(named_service
, False)
544 notification
.remove(match
)
548 self
.remove_match_string(_NAME_OWNER_CHANGE_MATCH
551 def remove_signal_receiver(self
, handler_or_match
,
557 #logger.debug('%r: removing signal receiver %r: member=%s, '
558 #'iface=%s, sender=%s, path=%s, kwargs=%r',
559 #self, handler_or_match, signal_name,
560 #dbus_interface, named_service, path, keywords)
561 #logger.debug('%r', self._signal_recipients_by_object_path)
562 by_interface
= self
._signal
_recipients
_by
_object
_path
.get(path
, None)
563 if by_interface
is None:
565 by_member
= by_interface
.get(dbus_interface
, None)
566 if by_member
is None:
568 matches
= by_member
.get(signal_name
, None)
571 self
._signals
_lock
.acquire()
572 #logger.debug(matches)
575 for match
in matches
:
576 if (handler_or_match
is match
577 or match
.matches_removal_spec(named_service
,
583 #logger.debug('Removing match string: %s', match)
584 self
.remove_match_string(str(match
))
585 self
._remove
_name
_owner
_changed
_for
_match
(named_service
,
589 by_member
[signal_name
] = new
591 self
._signals
_lock
.release()
593 def _signal_func(self
, message
):
594 """D-Bus filter function. Handle signals by dispatching to Python
595 callbacks kept in the match-rule tree.
598 #logger.debug('Incoming message %r with args %r', message,
599 #message.get_args_list())
601 if not isinstance(message
, SignalMessage
):
602 return HANDLER_RESULT_NOT_YET_HANDLED
604 # If it's NameOwnerChanged, we'll need to update our
605 # sender well-known name -> sender unique name mappings
606 if (message
.is_signal(BUS_DAEMON_IFACE
, 'NameOwnerChanged')
607 and message
.has_sender(BUS_DAEMON_NAME
)
608 and message
.has_path(BUS_DAEMON_PATH
)):
609 name
, unused
, new
= message
.get_args_list()
610 for match
in self
._signal
_sender
_matches
.get(name
, (None,))[1:]:
611 match
.sender_unique
= new
613 # See if anyone else wants to know
614 dbus_interface
= message
.get_interface()
615 path
= message
.get_path()
616 signal_name
= message
.get_member()
618 ret
= HANDLER_RESULT_NOT_YET_HANDLED
619 for match
in self
._iter
_easy
_matches
(path
, dbus_interface
,
621 if match
.maybe_handle_message(message
):
622 ret
= HANDLER_RESULT_HANDLED
626 if self
._bus
_type
== BUS_SESSION
:
628 elif self
._bus
_type
== BUS_SYSTEM
:
630 elif self
._bus
_type
== BUS_STARTER
:
633 raise AssertionError('Unable to represent unknown bus type.')
635 return '<dbus.Bus on %s at %#x>' % (name
, id(self
))
639 # FIXME: Drop the subclasses here? I can't think why we'd ever want
641 class SystemBus(Bus
):
642 """The system-wide message bus."""
643 def __new__(cls
, private
=False, mainloop
=None):
644 """Return a connection to the system bus.
648 If true, never return an existing shared instance, but instead
649 return a private connection.
650 `mainloop` : dbus.mainloop.NativeMainLoop
651 The main loop to use. The default is to use the default
652 main loop if one has been set up, or raise an exception
655 return Bus
.__new
__(cls
, Bus
.TYPE_SYSTEM
, mainloop
=mainloop
,
658 class SessionBus(Bus
):
659 """The session (current login) message bus."""
660 def __new__(cls
, private
=False, mainloop
=None):
661 """Return a connection to the session bus.
665 If true, never return an existing shared instance, but instead
666 return a private connection.
667 `mainloop` : dbus.mainloop.NativeMainLoop
668 The main loop to use. The default is to use the default
669 main loop if one has been set up, or raise an exception
672 return Bus
.__new
__(cls
, Bus
.TYPE_SESSION
, private
=private
,
675 class StarterBus(Bus
):
676 """The bus that activated this process (only valid if
677 this process was launched by DBus activation).
679 def __new__(cls
, private
=False, mainloop
=None):
680 """Return a connection to the bus that activated this process.
684 If true, never return an existing shared instance, but instead
685 return a private connection.
686 `mainloop` : dbus.mainloop.NativeMainLoop
687 The main loop to use. The default is to use the default
688 main loop if one has been set up, or raise an exception
691 return Bus
.__new
__(cls
, Bus
.TYPE_STARTER
, private
=private
,
695 _dbus_bindings_warning
= DeprecationWarning("""\
696 The dbus_bindings module is deprecated and will go away soon.
698 dbus-python 0.80 provides only a partial emulation of the old
699 dbus_bindings, which was never meant to be public API.
701 Most uses of dbus_bindings are applications catching the exception
702 dbus.dbus_bindings.DBusException. You should use dbus.DBusException
703 instead (this is compatible with all dbus-python versions since 0.40.2).
705 If you need additional public API, please contact the maintainers via
706 <dbus@lists.freedesktop.org>.
709 if 'DBUS_PYTHON_NO_DEPRECATED' not in os
.environ
:
711 class _DBusBindingsEmulation
:
712 """A partial emulation of the dbus_bindings module."""
715 return '_DBusBindingsEmulation()'
717 return '_DBusBindingsEmulation()'
718 def __getattr__(self
, attr
):
719 if self
._module
is None:
720 from warnings
import warn
as _warn
721 _warn(_dbus_bindings_warning
, DeprecationWarning, stacklevel
=2)
723 import dbus
.dbus_bindings
as m
725 return getattr(self
._module
, attr
)
727 dbus_bindings
= _DBusBindingsEmulation()
728 """Deprecated, don't use."""