dbus.proxies: Log more informatively when introspection fails, and use logging rather...
[dbus-python-phuang.git] / dbus / bus.py
blob645f6a8117bf85021fdbadd20354fe5ffb310384
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Licensed under the Academic Free License version 2.1
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published
7 # by the Free Software Foundation; either version 2.1 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 __all__ = ('BusConnection',)
20 __docformat__ = 'reStructuredText'
22 import logging
23 import weakref
25 from _dbus_bindings import validate_interface_name, validate_member_name,\
26 validate_bus_name, validate_object_path,\
27 validate_error_name,\
28 BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
29 DBUS_START_REPLY_SUCCESS, \
30 DBUS_START_REPLY_ALREADY_RUNNING, \
31 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
32 HANDLER_RESULT_NOT_YET_HANDLED
33 from dbus.connection import Connection
34 from dbus.exceptions import DBusException
37 _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
38 "interface='%s',member='NameOwnerChanged',"
39 "path='%s',arg0='%%s'"
40 % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
41 BUS_DAEMON_PATH))
42 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
43 messages"""
45 _logger = logging.getLogger('dbus.bus')
48 class NameOwnerWatch(object):
49 __slots__ = ('_match', '_pending_call')
51 def __init__(self, bus_conn, bus_name, callback):
52 validate_bus_name(bus_name, allow_unique=False)
54 def signal_cb(owned, old_owner, new_owner):
55 callback(new_owner)
57 def error_cb(e):
58 # FIXME: detect whether it's NameHasNoOwner properly
59 if str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
60 callback('')
61 else:
62 logging.basicConfig()
63 _logger.error('GetNameOwner(%s) failed:', bus_name,
64 exc_info=(e.__class__, e, None))
66 self._match = bus_conn.add_signal_receiver(signal_cb,
67 'NameOwnerChanged',
68 BUS_DAEMON_IFACE,
69 BUS_DAEMON_NAME,
70 BUS_DAEMON_PATH)
71 self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
72 BUS_DAEMON_PATH,
73 BUS_DAEMON_IFACE,
74 'GetNameOwner',
75 's', (bus_name,),
76 callback, error_cb,
77 utf8_strings=True)
79 def cancel(self):
80 if self._match is not None:
81 self._match.remove()
82 if self._pending_call is not None:
83 self._pending_call.cancel()
84 self._match = None
85 self._pending_call = None
88 class BusConnection(Connection):
89 """A connection to a D-Bus daemon that implements the
90 ``org.freedesktop.DBus`` pseudo-service.
91 """
93 TYPE_SESSION = BUS_SESSION
94 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
96 TYPE_SYSTEM = BUS_SYSTEM
97 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
99 TYPE_STARTER = BUS_STARTER
100 """Represents the bus that started this service by activation (same as
101 the global dbus.BUS_STARTER)"""
103 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
104 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
106 def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
107 bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
109 # _bus_names is used by dbus.service.BusName!
110 bus._bus_names = weakref.WeakValueDictionary()
112 bus._signal_sender_matches = {}
113 """Map from SignalMatch to NameOwnerWatch."""
115 return bus
117 def add_signal_receiver(self, handler_function, signal_name=None,
118 dbus_interface=None, bus_name=None,
119 path=None, **keywords):
120 named_service = keywords.pop('named_service', None)
121 if named_service is not None:
122 if bus_name is not None:
123 raise TypeError('bus_name and named_service cannot both be '
124 'specified')
125 bus_name = named_service
126 from warnings import warn
127 warn('Passing the named_service parameter to add_signal_receiver '
128 'by name is deprecated: please use positional parameters',
129 DeprecationWarning, stacklevel=2)
131 match = super(BusConnection, self).add_signal_receiver(
132 handler_function, signal_name, dbus_interface, bus_name,
133 path, **keywords)
135 # The bus daemon is special - its unique-name is org.freedesktop.DBus
136 # rather than starting with :
137 if (bus_name is not None
138 and bus_name[:1] != ':'
139 and bus_name != BUS_DAEMON_NAME):
140 watch = self.watch_name_owner(bus_name,
141 match.set_sender_name_owner)
142 self._signal_sender_matches[match] = watch
144 self.add_match_string(str(match))
146 return match
148 def _clean_up_signal_match(self, match):
149 # The signals lock must be held.
150 self.remove_match_string(str(match))
151 watch = self._signal_sender_matches.pop(match, None)
152 if watch is not None:
153 watch.cancel()
155 def activate_name_owner(self, bus_name):
156 if (bus_name is not None and bus_name[:1] != ':'
157 and bus_name != BUS_DAEMON_NAME):
158 try:
159 return self.get_name_owner(bus_name)
160 except DBusException, e:
161 # FIXME: detect whether it's NameHasNoOwner, but properly
162 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
163 # raise
164 # it might not exist: try to start it
165 self.start_service_by_name(bus_name)
166 return self.get_name_owner(bus_name)
167 else:
168 # already unique
169 return bus_name
171 def get_object(self, bus_name, object_path, introspect=True,
172 follow_name_owner_changes=False, **kwargs):
173 """Return a local proxy for the given remote object.
175 Method calls on the proxy are translated into method calls on the
176 remote object.
178 :Parameters:
179 `bus_name` : str
180 A bus name (either the unique name or a well-known name)
181 of the application owning the object. The keyword argument
182 named_service is a deprecated alias for this.
183 `object_path` : str
184 The object path of the desired object
185 `introspect` : bool
186 If true (default), attempt to introspect the remote
187 object to find out supported methods and their signatures
188 `follow_name_owner_changes` : bool
189 If the object path is a well-known name and this parameter
190 is false (default), resolve the well-known name to the unique
191 name of its current owner and bind to that instead; if the
192 ownership of the well-known name changes in future,
193 keep communicating with the original owner.
194 This is necessary if the D-Bus API used is stateful.
196 If the object path is a well-known name and this parameter
197 is true, whenever the well-known name changes ownership in
198 future, bind to the new owner, if any.
200 If the given object path is a unique name, this parameter
201 has no effect.
203 :Returns: a `dbus.proxies.ProxyObject`
204 :Raises `DBusException`: if resolving the well-known name to a
205 unique name fails
207 if follow_name_owner_changes:
208 self._require_main_loop() # we don't get the signals otherwise
210 named_service = kwargs.pop('named_service', None)
211 if named_service is not None:
212 if bus_name is not None:
213 raise TypeError('bus_name and named_service cannot both '
214 'be specified')
215 from warnings import warn
216 warn('Passing the named_service parameter to get_object by name '
217 'is deprecated: please use positional parameters',
218 DeprecationWarning, stacklevel=2)
219 bus_name = named_service
220 if kwargs:
221 raise TypeError('get_object does not take these keyword '
222 'arguments: %s' % ', '.join(kwargs.iterkeys()))
224 return self.ProxyObjectClass(self, bus_name, object_path,
225 introspect=introspect,
226 follow_name_owner_changes=follow_name_owner_changes)
228 def get_unix_user(self, bus_name):
229 """Get the numeric uid of the process owning the given bus name.
231 :Parameters:
232 `bus_name` : str
233 A bus name, either unique or well-known
234 :Returns: a `dbus.UInt32`
236 validate_bus_name(bus_name)
237 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
238 BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
239 's', (bus_name,))
241 def start_service_by_name(self, bus_name, flags=0):
242 """Start a service which will implement the given bus name on this Bus.
244 :Parameters:
245 `bus_name` : str
246 The well-known bus name to be activated.
247 `flags` : dbus.UInt32
248 Flags to pass to StartServiceByName (currently none are
249 defined)
251 :Returns: A tuple of 2 elements. The first is always True, the
252 second is either START_REPLY_SUCCESS or
253 START_REPLY_ALREADY_RUNNING.
255 :Raises DBusException: if the service could not be started.
257 validate_bus_name(bus_name)
258 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
259 BUS_DAEMON_IFACE,
260 'StartServiceByName',
261 'su', (bus_name, flags)))
263 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
264 # but this would not be backwards-compatible
265 def request_name(self, name, flags=0):
266 """Request a bus name.
268 :Parameters:
269 `name` : str
270 The well-known name to be requested
271 `flags` : dbus.UInt32
272 A bitwise-OR of 0 or more of the flags
273 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
274 `DBUS_NAME_FLAG_REPLACE_EXISTING`
275 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
276 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
277 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
278 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
279 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
280 :Raises DBusException: if the bus daemon cannot be contacted or
281 returns an error.
283 validate_bus_name(name, allow_unique=False)
284 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
285 BUS_DAEMON_IFACE, 'RequestName',
286 'su', (name, flags))
288 def release_name(self, name):
289 """Release a bus name.
291 :Parameters:
292 `name` : str
293 The well-known name to be released
294 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
295 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
296 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
297 :Raises DBusException: if the bus daemon cannot be contacted or
298 returns an error.
300 validate_bus_name(name, allow_unique=False)
301 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
302 BUS_DAEMON_IFACE, 'ReleaseName',
303 's', (name,))
305 def list_names(self):
306 """Return a list of all currently-owned names on the bus.
308 :Returns: a dbus.Array of dbus.UTF8String
310 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
311 BUS_DAEMON_IFACE, 'ListNames',
312 '', (), utf8_strings=True)
314 def list_activatable_names(self):
315 """Return a list of all names that can be activated on the bus.
317 :Returns: a dbus.Array of dbus.UTF8String
319 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
320 BUS_DAEMON_IFACE, 'ListNames',
321 '', (), utf8_strings=True)
323 def get_name_owner(self, bus_name):
324 """Return the unique connection name of the primary owner of the
325 given name.
327 :Raises DBusException: if the `bus_name` has no owner
329 validate_bus_name(bus_name, allow_unique=False)
330 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
331 BUS_DAEMON_IFACE, 'GetNameOwner',
332 's', (bus_name,), utf8_strings=True)
334 def watch_name_owner(self, bus_name, callback):
335 """Watch the unique connection name of the primary owner of the
336 given name.
338 `callback` will be called with one argument, which is either the
339 unique connection name, or the empty string (meaning the name is
340 not owned).
342 return NameOwnerWatch(self, bus_name, callback)
344 def name_has_owner(self, bus_name):
345 """Return True iff the given bus name has an owner on this bus.
347 :Parameters:
348 `name` : str
349 The bus name to look up
350 :Returns: a `bool`
352 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
353 BUS_DAEMON_IFACE, 'NameHasOwner',
354 's', (bus_name,)))
356 def add_match_string(self, rule):
357 """Arrange for this application to receive messages on the bus that
358 match the given rule. This version will block.
360 :Parameters:
361 `rule` : str
362 The match rule
363 :Raises: `DBusException` on error.
365 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
366 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
368 # FIXME: add an async success/error handler capability?
369 # (and the same for remove_...)
370 def add_match_string_non_blocking(self, rule):
371 """Arrange for this application to receive messages on the bus that
372 match the given rule. This version will not block, but any errors
373 will be ignored.
376 :Parameters:
377 `rule` : str
378 The match rule
379 :Raises: `DBusException` on error.
381 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
382 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
383 None, None)
385 def remove_match_string(self, rule):
386 """Arrange for this application to receive messages on the bus that
387 match the given rule. This version will block.
389 :Parameters:
390 `rule` : str
391 The match rule
392 :Raises: `DBusException` on error.
394 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
395 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
397 def remove_match_string_non_blocking(self, rule):
398 """Arrange for this application to receive messages on the bus that
399 match the given rule. This version will not block, but any errors
400 will be ignored.
403 :Parameters:
404 `rule` : str
405 The match rule
406 :Raises: `DBusException` on error.
408 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
409 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
410 None, None)