dbus.bus: when a unique name goes away, disconnect all signal handlers.
[dbus-python-phuang.git] / dbus / bus.py
blob86ca159ef9471cbdbc0fe5ce6ef3466a42cc7881
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)
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.debug('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 if (bus_name is not None and bus_name != BUS_DAEMON_NAME):
137 if bus_name[:1] == ':':
138 def callback(new_owner):
139 if new_owner == '':
140 match.remove()
141 else:
142 callback = match.set_sender_name_owner
143 watch = self.watch_name_owner(bus_name, callback)
144 self._signal_sender_matches[match] = watch
146 self.add_match_string(str(match))
148 return match
150 def _clean_up_signal_match(self, match):
151 # The signals lock is no longer held here (it was in <= 0.81.0)
152 self.remove_match_string(str(match))
153 watch = self._signal_sender_matches.pop(match, None)
154 if watch is not None:
155 watch.cancel()
157 def activate_name_owner(self, bus_name):
158 if (bus_name is not None and bus_name[:1] != ':'
159 and bus_name != BUS_DAEMON_NAME):
160 try:
161 return self.get_name_owner(bus_name)
162 except DBusException, e:
163 if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
164 raise
165 # else it doesn't exist: try to start it
166 self.start_service_by_name(bus_name)
167 return self.get_name_owner(bus_name)
168 else:
169 # already unique
170 return bus_name
172 def get_object(self, bus_name, object_path, introspect=True,
173 follow_name_owner_changes=False, **kwargs):
174 """Return a local proxy for the given remote object.
176 Method calls on the proxy are translated into method calls on the
177 remote object.
179 :Parameters:
180 `bus_name` : str
181 A bus name (either the unique name or a well-known name)
182 of the application owning the object. The keyword argument
183 named_service is a deprecated alias for this.
184 `object_path` : str
185 The object path of the desired object
186 `introspect` : bool
187 If true (default), attempt to introspect the remote
188 object to find out supported methods and their signatures
189 `follow_name_owner_changes` : bool
190 If the object path is a well-known name and this parameter
191 is false (default), resolve the well-known name to the unique
192 name of its current owner and bind to that instead; if the
193 ownership of the well-known name changes in future,
194 keep communicating with the original owner.
195 This is necessary if the D-Bus API used is stateful.
197 If the object path is a well-known name and this parameter
198 is true, whenever the well-known name changes ownership in
199 future, bind to the new owner, if any.
201 If the given object path is a unique name, this parameter
202 has no effect.
204 :Returns: a `dbus.proxies.ProxyObject`
205 :Raises `DBusException`: if resolving the well-known name to a
206 unique name fails
208 if follow_name_owner_changes:
209 self._require_main_loop() # we don't get the signals otherwise
211 named_service = kwargs.pop('named_service', None)
212 if named_service is not None:
213 if bus_name is not None:
214 raise TypeError('bus_name and named_service cannot both '
215 'be specified')
216 from warnings import warn
217 warn('Passing the named_service parameter to get_object by name '
218 'is deprecated: please use positional parameters',
219 DeprecationWarning, stacklevel=2)
220 bus_name = named_service
221 if kwargs:
222 raise TypeError('get_object does not take these keyword '
223 'arguments: %s' % ', '.join(kwargs.iterkeys()))
225 return self.ProxyObjectClass(self, bus_name, object_path,
226 introspect=introspect,
227 follow_name_owner_changes=follow_name_owner_changes)
229 def get_unix_user(self, bus_name):
230 """Get the numeric uid of the process owning the given bus name.
232 :Parameters:
233 `bus_name` : str
234 A bus name, either unique or well-known
235 :Returns: a `dbus.UInt32`
237 validate_bus_name(bus_name)
238 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
239 BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
240 's', (bus_name,))
242 def start_service_by_name(self, bus_name, flags=0):
243 """Start a service which will implement the given bus name on this Bus.
245 :Parameters:
246 `bus_name` : str
247 The well-known bus name to be activated.
248 `flags` : dbus.UInt32
249 Flags to pass to StartServiceByName (currently none are
250 defined)
252 :Returns: A tuple of 2 elements. The first is always True, the
253 second is either START_REPLY_SUCCESS or
254 START_REPLY_ALREADY_RUNNING.
256 :Raises DBusException: if the service could not be started.
258 validate_bus_name(bus_name)
259 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
260 BUS_DAEMON_IFACE,
261 'StartServiceByName',
262 'su', (bus_name, flags)))
264 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
265 # but this would not be backwards-compatible
266 def request_name(self, name, flags=0):
267 """Request a bus name.
269 :Parameters:
270 `name` : str
271 The well-known name to be requested
272 `flags` : dbus.UInt32
273 A bitwise-OR of 0 or more of the flags
274 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
275 `DBUS_NAME_FLAG_REPLACE_EXISTING`
276 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
277 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
278 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
279 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
280 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
281 :Raises DBusException: if the bus daemon cannot be contacted or
282 returns an error.
284 validate_bus_name(name, allow_unique=False)
285 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
286 BUS_DAEMON_IFACE, 'RequestName',
287 'su', (name, flags))
289 def release_name(self, name):
290 """Release a bus name.
292 :Parameters:
293 `name` : str
294 The well-known name to be released
295 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
296 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
297 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
298 :Raises DBusException: if the bus daemon cannot be contacted or
299 returns an error.
301 validate_bus_name(name, allow_unique=False)
302 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
303 BUS_DAEMON_IFACE, 'ReleaseName',
304 's', (name,))
306 def list_names(self):
307 """Return a list of all currently-owned names on the bus.
309 :Returns: a dbus.Array of dbus.UTF8String
311 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
312 BUS_DAEMON_IFACE, 'ListNames',
313 '', (), utf8_strings=True)
315 def list_activatable_names(self):
316 """Return a list of all names that can be activated on the bus.
318 :Returns: a dbus.Array of dbus.UTF8String
320 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
321 BUS_DAEMON_IFACE, 'ListNames',
322 '', (), utf8_strings=True)
324 def get_name_owner(self, bus_name):
325 """Return the unique connection name of the primary owner of the
326 given name.
328 :Raises DBusException: if the `bus_name` has no owner
330 validate_bus_name(bus_name, allow_unique=False)
331 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
332 BUS_DAEMON_IFACE, 'GetNameOwner',
333 's', (bus_name,), utf8_strings=True)
335 def watch_name_owner(self, bus_name, callback):
336 """Watch the unique connection name of the primary owner of the
337 given name.
339 `callback` will be called with one argument, which is either the
340 unique connection name, or the empty string (meaning the name is
341 not owned).
343 return NameOwnerWatch(self, bus_name, callback)
345 def name_has_owner(self, bus_name):
346 """Return True iff the given bus name has an owner on this bus.
348 :Parameters:
349 `name` : str
350 The bus name to look up
351 :Returns: a `bool`
353 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
354 BUS_DAEMON_IFACE, 'NameHasOwner',
355 's', (bus_name,)))
357 def add_match_string(self, rule):
358 """Arrange for this application to receive messages on the bus that
359 match the given rule. This version will block.
361 :Parameters:
362 `rule` : str
363 The match rule
364 :Raises: `DBusException` on error.
366 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
367 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
369 # FIXME: add an async success/error handler capability?
370 # (and the same for remove_...)
371 def add_match_string_non_blocking(self, rule):
372 """Arrange for this application to receive messages on the bus that
373 match the given rule. This version will not block, but any errors
374 will be ignored.
377 :Parameters:
378 `rule` : str
379 The match rule
380 :Raises: `DBusException` on error.
382 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
383 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
384 None, None)
386 def remove_match_string(self, rule):
387 """Arrange for this application to receive messages on the bus that
388 match the given rule. This version will block.
390 :Parameters:
391 `rule` : str
392 The match rule
393 :Raises: `DBusException` on error.
395 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
396 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
398 def remove_match_string_non_blocking(self, rule):
399 """Arrange for this application to receive messages on the bus that
400 match the given rule. This version will not block, but any errors
401 will be ignored.
404 :Parameters:
405 `rule` : str
406 The match rule
407 :Raises: `DBusException` on error.
409 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
410 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
411 None, None)