Move get_object, constants into Connection and BusConnection. Add docstrings
[dbus-python-phuang.git] / dbus / _dbus.py
blobbab195a6cc0b7566b414d0e113cb589a9e1995f8
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'
29 import os
30 import logging
31 import sys
32 import weakref
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, \
42 SignalMessage,\
43 HANDLER_RESULT_NOT_YET_HANDLED,\
44 HANDLER_RESULT_HANDLED
45 from dbus.bus import BusConnection
46 from dbus.proxies import ProxyObject
48 try:
49 import thread
50 except ImportError:
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,
59 BUS_DAEMON_PATH))
60 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
61 messages"""
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,
77 **kwargs):
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)
88 self._sender = sender
89 self._interface = dbus_interface
90 self._member = member
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)
96 else:
97 self.sender_unique = sender
98 self._utf8_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
108 if not kwargs:
109 self._int_args_match = None
110 else:
111 self._int_args_match = {}
112 for kwarg in kwargs:
113 if not kwarg.startswith('arg'):
114 raise TypeError('SignalMatch: unknown keyword argument %s'
115 % kwarg)
116 try:
117 index = int(kwarg[3:])
118 except ValueError:
119 raise TypeError('SignalMatch: unknown keyword argument %s'
120 % kwarg)
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)
142 def __str__(self):
143 return self._rule
145 def __repr__(self):
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):
152 return False
153 if sender != self._sender:
154 return False
155 if object_path != self._path:
156 return False
157 if dbus_interface != self._interface:
158 return False
159 if member != self._member:
160 return False
161 if kwargs != self._args_match:
162 return False
163 return True
165 def maybe_handle_message(self, message):
166 args = None
168 # these haven't been checked yet by the match tree
169 if self.sender_unique not in (None, message.get_sender()):
170 return False
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):
178 return False
180 # these have likely already been checked by the match tree
181 if self._member not in (None, message.get_member()):
182 return False
183 if self._interface not in (None, message.get_interface()):
184 return False
185 if self._path not in (None, message.get_path()):
186 return False
188 try:
189 # minor optimization: if we already extracted the args with the
190 # right calling convention to do the args match, don't bother
191 # doing so again
192 if args is None or not self._utf8_strings or not self._byte_arrays:
193 args = message.get_args_list(utf8_strings=self._utf8_strings,
194 byte_arrays=self._byte_arrays)
195 kwargs = {}
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)
209 except:
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')
213 print_exc()
215 return True
217 def remove(self):
218 #logger.debug('%r: removing', self)
219 conn = self._conn_weakref()
220 # do nothing if the connection has already vanished
221 if conn is not None:
222 #logger.debug('%r: removing from connection %r', self, conn)
223 conn.remove_signal_receiver(self, self._member,
224 self._interface, self._sender,
225 self._path,
226 **self._args_match)
229 class Bus(BusConnection):
230 """A connection to one of three possible standard buses, the SESSION,
231 SYSTEM, or STARTER bus. This class manages shared connections to those
232 buses.
234 If you're trying to subclass `Bus`, you may be better off subclassing
235 `BusConnection`, which doesn't have all this magic.
238 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
239 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
241 _shared_instances = {}
243 def __new__(cls, bus_type=BusConnection.TYPE_SESSION, private=False,
244 mainloop=None):
245 """Constructor, returning an existing instance where appropriate.
247 The returned instance is actually always an instance of `SessionBus`,
248 `SystemBus` or `StarterBus`.
250 :Parameters:
251 `bus_type` : cls.TYPE_SESSION, cls.TYPE_SYSTEM or cls.TYPE_STARTER
252 Connect to the appropriate bus
253 `private` : bool
254 If true, never return an existing shared instance, but instead
255 return a private connection
256 `mainloop` : dbus.mainloop.NativeMainLoop
257 The main loop to use. The default is to use the default
258 main loop if one has been set up, or raise an exception
259 if none has been.
260 :ToDo:
261 - There is currently no way to connect this class to a custom
262 address.
263 - Some of this functionality should be available on
264 peer-to-peer D-Bus connections too.
265 :Changed: in dbus-python 0.80:
266 converted from a wrapper around a Connection to a Connection
267 subclass.
269 if (not private and bus_type in cls._shared_instances):
270 return cls._shared_instances[bus_type]
272 # this is a bit odd, but we create instances of the subtypes
273 # so we can return the shared instances if someone tries to
274 # construct one of them (otherwise we'd eg try and return an
275 # instance of Bus from __new__ in SessionBus). why are there
276 # three ways to construct this class? we just don't know.
277 if bus_type == BUS_SESSION:
278 subclass = SessionBus
279 elif bus_type == BUS_SYSTEM:
280 subclass = SystemBus
281 elif bus_type == BUS_STARTER:
282 subclass = StarterBus
283 else:
284 raise ValueError('invalid bus_type %s' % bus_type)
286 bus = subclass._new_for_bus(bus_type, mainloop=mainloop)
288 bus._bus_type = bus_type
289 # _bus_names is used by dbus.service.BusName!
290 bus._bus_names = weakref.WeakValueDictionary()
292 bus._signal_recipients_by_object_path = {}
293 """Map from object path to dict mapping dbus_interface to dict
294 mapping member to list of SignalMatch objects."""
296 bus._signal_sender_matches = {}
297 """Map from sender well-known name to list of match rules for all
298 signal handlers that match on sender well-known name."""
300 bus._signals_lock = thread.allocate_lock()
301 """Lock used to protect signal data structures if doing two
302 removals at the same time (everything else is atomic, thanks to
303 the GIL)"""
305 bus.add_message_filter(bus.__class__._signal_func)
307 if not private:
308 cls._shared_instances[bus_type] = bus
310 return bus
312 def close(self):
313 t = self._bus_type
314 if self.__class__._shared_instances[t] is self:
315 del self.__class__._shared_instances[t]
316 super(BusConnection, self).close()
318 def get_connection(self):
319 """(Deprecated - in new code, just use self)
321 Return self, for backwards compatibility with earlier dbus-python
322 versions where Bus was not a subclass of Connection.
324 return self
325 _connection = property(get_connection, None, None,
326 """self._connection == self, for backwards
327 compatibility with earlier dbus-python versions
328 where Bus was not a subclass of Connection.""")
330 def get_session(private=False):
331 """Static method that returns a connection to the session bus.
333 :Parameters:
334 `private` : bool
335 If true, do not return a shared connection.
337 return SessionBus(private=private)
339 get_session = staticmethod(get_session)
341 def get_system(private=False):
342 """Static method that returns a connection to the system bus.
344 :Parameters:
345 `private` : bool
346 If true, do not return a shared connection.
348 return SystemBus(private=private)
350 get_system = staticmethod(get_system)
353 def get_starter(private=False):
354 """Static method that returns a connection to the starter bus.
356 :Parameters:
357 `private` : bool
358 If true, do not return a shared connection.
360 return StarterBus(private=private)
362 get_starter = staticmethod(get_starter)
364 def add_signal_receiver(self, handler_function,
365 signal_name=None,
366 dbus_interface=None,
367 named_service=None,
368 path=None,
369 **keywords):
370 """Arrange for the given function to be called when a signal matching
371 the parameters is received.
373 :Parameters:
374 `handler_function` : callable
375 The function to be called. Its positional arguments will
376 be the arguments of the signal. By default it will receive
377 no keyword arguments, but see the description of
378 the optional keyword arguments below.
379 `signal_name` : str
380 The signal name; None (the default) matches all names
381 `dbus_interface` : str
382 The D-Bus interface name with which to qualify the signal;
383 None (the default) matches all interface names
384 `named_service` : str
385 A bus name for the sender, which will be resolved to a
386 unique name if it is not already; None (the default) matches
387 any sender
388 `path` : str
389 The object path of the object which must have emitted the
390 signal; None (the default) matches any object path
391 :Keywords:
392 `utf8_strings` : bool
393 If True, the handler function will receive any string
394 arguments as dbus.UTF8String objects (a subclass of str
395 guaranteed to be UTF-8). If False (default) it will receive
396 any string arguments as dbus.String objects (a subclass of
397 unicode).
398 `byte_arrays` : bool
399 If True, the handler function will receive any byte-array
400 arguments as dbus.ByteArray objects (a subclass of str).
401 If False (default) it will receive any byte-array
402 arguments as a dbus.Array of dbus.Byte (subclasses of:
403 a list of ints).
404 `sender_keyword` : str
405 If not None (the default), the handler function will receive
406 the unique name of the sending endpoint as a keyword
407 argument with this name.
408 `destination_keyword` : str
409 If not None (the default), the handler function will receive
410 the bus name of the destination (or None if the signal is a
411 broadcast, as is usual) as a keyword argument with this name.
412 `interface_keyword` : str
413 If not None (the default), the handler function will receive
414 the signal interface as a keyword argument with this name.
415 `member_keyword` : str
416 If not None (the default), the handler function will receive
417 the signal name as a keyword argument with this name.
418 `path_keyword` : str
419 If not None (the default), the handler function will receive
420 the object-path of the sending object as a keyword argument
421 with this name.
422 `message_keyword` : str
423 If not None (the default), the handler function will receive
424 the `dbus.lowlevel.SignalMessage` as a keyword argument with
425 this name.
426 `arg...` : unicode or UTF-8 str
427 If there are additional keyword parameters of the form
428 ``arg``\ *n*, match only signals where the *n*\ th argument
429 is the value given for that keyword parameter. As of this
430 time only string arguments can be matched (in particular,
431 object paths and signatures can't).
433 self._require_main_loop()
435 match = SignalMatch(self, named_service, path, dbus_interface,
436 signal_name, handler_function, **keywords)
437 by_interface = self._signal_recipients_by_object_path.setdefault(path,
439 by_member = by_interface.setdefault(dbus_interface, {})
440 matches = by_member.setdefault(signal_name, [])
441 # The bus daemon is special - its unique-name is org.freedesktop.DBus
442 # rather than starting with :
443 if (named_service is not None and named_service[:1] != ':'
444 and named_service != BUS_DAEMON_NAME):
445 notification = self._signal_sender_matches.setdefault(named_service,
447 if not notification:
448 self.add_match_string(_NAME_OWNER_CHANGE_MATCH % named_service)
449 notification.append(match)
450 # make sure nobody is currently manipulating the list
451 self._signals_lock.acquire()
452 try:
453 matches.append(match)
454 finally:
455 self._signals_lock.release()
456 self.add_match_string(str(match))
457 return match
459 def _iter_easy_matches(self, path, dbus_interface, member):
460 if path is not None:
461 path_keys = (None, path)
462 else:
463 path_keys = (None,)
464 if dbus_interface is not None:
465 interface_keys = (None, dbus_interface)
466 else:
467 interface_keys = (None,)
468 if member is not None:
469 member_keys = (None, member)
470 else:
471 member_keys = (None,)
473 for path in path_keys:
474 by_interface = self._signal_recipients_by_object_path.get(path,
475 None)
476 if by_interface is None:
477 continue
478 for dbus_interface in interface_keys:
479 by_member = by_interface.get(dbus_interface, None)
480 if by_member is None:
481 continue
482 for member in member_keys:
483 matches = by_member.get(member, None)
484 if matches is None:
485 continue
486 for m in matches:
487 yield m
489 def _remove_name_owner_changed_for_match(self, named_service, match):
490 # The signals lock must be held.
491 notification = self._signal_sender_matches.get(named_service, False)
492 if notification:
493 try:
494 notification.remove(match)
495 except LookupError:
496 pass
497 if not notification:
498 self.remove_match_string(_NAME_OWNER_CHANGE_MATCH
499 % named_service)
501 def remove_signal_receiver(self, handler_or_match,
502 signal_name=None,
503 dbus_interface=None,
504 named_service=None,
505 path=None,
506 **keywords):
507 #logger.debug('%r: removing signal receiver %r: member=%s, '
508 #'iface=%s, sender=%s, path=%s, kwargs=%r',
509 #self, handler_or_match, signal_name,
510 #dbus_interface, named_service, path, keywords)
511 #logger.debug('%r', self._signal_recipients_by_object_path)
512 by_interface = self._signal_recipients_by_object_path.get(path, None)
513 if by_interface is None:
514 return
515 by_member = by_interface.get(dbus_interface, None)
516 if by_member is None:
517 return
518 matches = by_member.get(signal_name, None)
519 if matches is None:
520 return
521 self._signals_lock.acquire()
522 #logger.debug(matches)
523 try:
524 new = []
525 for match in matches:
526 if (handler_or_match is match
527 or match.matches_removal_spec(named_service,
528 path,
529 dbus_interface,
530 signal_name,
531 handler_or_match,
532 **keywords)):
533 #logger.debug('Removing match string: %s', match)
534 self.remove_match_string(str(match))
535 self._remove_name_owner_changed_for_match(named_service,
536 match)
537 else:
538 new.append(match)
539 by_member[signal_name] = new
540 finally:
541 self._signals_lock.release()
543 def _signal_func(self, message):
544 """D-Bus filter function. Handle signals by dispatching to Python
545 callbacks kept in the match-rule tree.
548 #logger.debug('Incoming message %r with args %r', message,
549 #message.get_args_list())
551 if not isinstance(message, SignalMessage):
552 return HANDLER_RESULT_NOT_YET_HANDLED
554 # If it's NameOwnerChanged, we'll need to update our
555 # sender well-known name -> sender unique name mappings
556 if (message.is_signal(BUS_DAEMON_IFACE, 'NameOwnerChanged')
557 and message.has_sender(BUS_DAEMON_NAME)
558 and message.has_path(BUS_DAEMON_PATH)):
559 name, unused, new = message.get_args_list()
560 for match in self._signal_sender_matches.get(name, (None,))[1:]:
561 match.sender_unique = new
563 # See if anyone else wants to know
564 dbus_interface = message.get_interface()
565 path = message.get_path()
566 signal_name = message.get_member()
568 ret = HANDLER_RESULT_NOT_YET_HANDLED
569 for match in self._iter_easy_matches(path, dbus_interface,
570 signal_name):
571 if match.maybe_handle_message(message):
572 ret = HANDLER_RESULT_HANDLED
573 return ret
575 def __repr__(self):
576 if self._bus_type == BUS_SESSION:
577 name = 'SESSION'
578 elif self._bus_type == BUS_SYSTEM:
579 name = 'SYSTEM'
580 elif self._bus_type == BUS_STARTER:
581 name = 'STARTER'
582 else:
583 raise AssertionError('Unable to represent unknown bus type.')
585 return '<dbus.Bus on %s at %#x>' % (name, id(self))
586 __str__ = __repr__
589 # FIXME: Drop the subclasses here? I can't think why we'd ever want
590 # polymorphism
591 class SystemBus(Bus):
592 """The system-wide message bus."""
593 def __new__(cls, private=False, mainloop=None):
594 """Return a connection to the system bus.
596 :Parameters:
597 `private` : bool
598 If true, never return an existing shared instance, but instead
599 return a private connection.
600 `mainloop` : dbus.mainloop.NativeMainLoop
601 The main loop to use. The default is to use the default
602 main loop if one has been set up, or raise an exception
603 if none has been.
605 return Bus.__new__(cls, Bus.TYPE_SYSTEM, mainloop=mainloop,
606 private=private)
608 class SessionBus(Bus):
609 """The session (current login) message bus."""
610 def __new__(cls, private=False, mainloop=None):
611 """Return a connection to the session bus.
613 :Parameters:
614 `private` : bool
615 If true, never return an existing shared instance, but instead
616 return a private connection.
617 `mainloop` : dbus.mainloop.NativeMainLoop
618 The main loop to use. The default is to use the default
619 main loop if one has been set up, or raise an exception
620 if none has been.
622 return Bus.__new__(cls, Bus.TYPE_SESSION, private=private,
623 mainloop=mainloop)
625 class StarterBus(Bus):
626 """The bus that activated this process (only valid if
627 this process was launched by DBus activation).
629 def __new__(cls, private=False, mainloop=None):
630 """Return a connection to the bus that activated this process.
632 :Parameters:
633 `private` : bool
634 If true, never return an existing shared instance, but instead
635 return a private connection.
636 `mainloop` : dbus.mainloop.NativeMainLoop
637 The main loop to use. The default is to use the default
638 main loop if one has been set up, or raise an exception
639 if none has been.
641 return Bus.__new__(cls, Bus.TYPE_STARTER, private=private,
642 mainloop=mainloop)
645 _dbus_bindings_warning = DeprecationWarning("""\
646 The dbus_bindings module is deprecated and will go away soon.
648 dbus-python 0.80 provides only a partial emulation of the old
649 dbus_bindings, which was never meant to be public API.
651 Most uses of dbus_bindings are applications catching the exception
652 dbus.dbus_bindings.DBusException. You should use dbus.DBusException
653 instead (this is compatible with all dbus-python versions since 0.40.2).
655 If you need additional public API, please contact the maintainers via
656 <dbus@lists.freedesktop.org>.
657 """)
659 if 'DBUS_PYTHON_NO_DEPRECATED' not in os.environ:
661 class _DBusBindingsEmulation:
662 """A partial emulation of the dbus_bindings module."""
663 _module = None
664 def __str__(self):
665 return '_DBusBindingsEmulation()'
666 def __repr__(self):
667 return '_DBusBindingsEmulation()'
668 def __getattr__(self, attr):
669 if self._module is None:
670 from warnings import warn as _warn
671 _warn(_dbus_bindings_warning, DeprecationWarning, stacklevel=2)
673 import dbus.dbus_bindings as m
674 self._module = m
675 return getattr(self._module, attr)
677 dbus_bindings = _DBusBindingsEmulation()
678 """Deprecated, don't use."""