dbus.bus: detect NameHasNoOwner correctly, using new get_dbus_name() method
[dbus-python-phuang.git] / dbus / bus.py
blob9313289ba65f3c9bc0a9cc47110ce236edcb0c96
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 _NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
47 _logger = logging.getLogger('dbus.bus')
50 class NameOwnerWatch(object):
51 __slots__ = ('_match', '_pending_call')
53 def __init__(self, bus_conn, bus_name, callback):
54 validate_bus_name(bus_name, allow_unique=False)
56 def signal_cb(owned, old_owner, new_owner):
57 callback(new_owner)
59 def error_cb(e):
60 if e.get_dbus_name() == _NAME_HAS_NO_OWNER:
61 callback('')
62 else:
63 logging.basicConfig()
64 _logger.error('GetNameOwner(%s) failed:', bus_name,
65 exc_info=(e.__class__, e, None))
67 self._match = bus_conn.add_signal_receiver(signal_cb,
68 'NameOwnerChanged',
69 BUS_DAEMON_IFACE,
70 BUS_DAEMON_NAME,
71 BUS_DAEMON_PATH)
72 self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
73 BUS_DAEMON_PATH,
74 BUS_DAEMON_IFACE,
75 'GetNameOwner',
76 's', (bus_name,),
77 callback, error_cb,
78 utf8_strings=True)
80 def cancel(self):
81 if self._match is not None:
82 self._match.remove()
83 if self._pending_call is not None:
84 self._pending_call.cancel()
85 self._match = None
86 self._pending_call = None
89 class BusConnection(Connection):
90 """A connection to a D-Bus daemon that implements the
91 ``org.freedesktop.DBus`` pseudo-service.
92 """
94 TYPE_SESSION = BUS_SESSION
95 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
97 TYPE_SYSTEM = BUS_SYSTEM
98 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
100 TYPE_STARTER = BUS_STARTER
101 """Represents the bus that started this service by activation (same as
102 the global dbus.BUS_STARTER)"""
104 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
105 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
107 def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
108 bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
110 # _bus_names is used by dbus.service.BusName!
111 bus._bus_names = weakref.WeakValueDictionary()
113 bus._signal_sender_matches = {}
114 """Map from SignalMatch to NameOwnerWatch."""
116 return bus
118 def add_signal_receiver(self, handler_function, signal_name=None,
119 dbus_interface=None, bus_name=None,
120 path=None, **keywords):
121 named_service = keywords.pop('named_service', None)
122 if named_service is not None:
123 if bus_name is not None:
124 raise TypeError('bus_name and named_service cannot both be '
125 'specified')
126 bus_name = named_service
127 from warnings import warn
128 warn('Passing the named_service parameter to add_signal_receiver '
129 'by name is deprecated: please use positional parameters',
130 DeprecationWarning, stacklevel=2)
132 match = super(BusConnection, self).add_signal_receiver(
133 handler_function, signal_name, dbus_interface, bus_name,
134 path, **keywords)
136 # The bus daemon is special - its unique-name is org.freedesktop.DBus
137 # rather than starting with :
138 if (bus_name is not None
139 and bus_name[:1] != ':'
140 and bus_name != BUS_DAEMON_NAME):
141 watch = self.watch_name_owner(bus_name,
142 match.set_sender_name_owner)
143 self._signal_sender_matches[match] = watch
145 self.add_match_string(str(match))
147 return match
149 def _clean_up_signal_match(self, match):
150 # The signals lock must be held.
151 self.remove_match_string(str(match))
152 watch = self._signal_sender_matches.pop(match, None)
153 if watch is not None:
154 watch.cancel()
156 def activate_name_owner(self, bus_name):
157 if (bus_name is not None and bus_name[:1] != ':'
158 and bus_name != BUS_DAEMON_NAME):
159 try:
160 return self.get_name_owner(bus_name)
161 except DBusException, e:
162 if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
163 raise
164 # else it doesn't 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)