Simplify dbus_bindings backwards compatibility glue
[dbus-python-phuang.git] / dbus / bus.py
blobf5eeaf84a59ead655ea8a0a675900bf695c1f142
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 DBusException, \
29 BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
30 DBUS_START_REPLY_SUCCESS, \
31 DBUS_START_REPLY_ALREADY_RUNNING, \
32 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
33 HANDLER_RESULT_NOT_YET_HANDLED
34 from dbus.connection import Connection
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, named_service=None,
119 path=None, **keywords):
120 match = super(BusConnection, self).add_signal_receiver(
121 handler_function, signal_name, dbus_interface, named_service,
122 path, **keywords)
124 # The bus daemon is special - its unique-name is org.freedesktop.DBus
125 # rather than starting with :
126 if (named_service is not None
127 and named_service[:1] != ':'
128 and named_service != BUS_DAEMON_NAME):
129 watch = self.watch_name_owner(named_service,
130 match.set_sender_name_owner)
131 self._signal_sender_matches[match] = watch
133 self.add_match_string(str(match))
135 return match
137 def _clean_up_signal_match(self, match):
138 # The signals lock must be held.
139 self.remove_match_string(str(match))
140 watch = self._signal_sender_matches.pop(match, None)
141 if watch is not None:
142 watch.cancel()
144 def activate_name_owner(self, bus_name):
145 if (bus_name is not None and bus_name[:1] != ':'
146 and bus_name != BUS_DAEMON_NAME):
147 try:
148 return self.get_name_owner(bus_name)
149 except DBusException, e:
150 # FIXME: detect whether it's NameHasNoOwner, but properly
151 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
152 # raise
153 # it might not exist: try to start it
154 self.start_service_by_name(bus_name)
155 return self.get_name_owner(bus_name)
156 else:
157 # already unique
158 return bus_name
160 def get_object(self, named_service, object_path, introspect=True,
161 follow_name_owner_changes=False):
162 """Return a local proxy for the given remote object.
164 Method calls on the proxy are translated into method calls on the
165 remote object.
167 :Parameters:
168 `named_service` : str
169 A bus name (either the unique name or a well-known name)
170 of the application owning the object
171 `object_path` : str
172 The object path of the desired object
173 `introspect` : bool
174 If true (default), attempt to introspect the remote
175 object to find out supported methods and their signatures
176 `follow_name_owner_changes` : bool
177 If the object path is a well-known name and this parameter
178 is false (default), resolve the well-known name to the unique
179 name of its current owner and bind to that instead; if the
180 ownership of the well-known name changes in future,
181 keep communicating with the original owner.
182 This is necessary if the D-Bus API used is stateful.
184 If the object path is a well-known name and this parameter
185 is true, whenever the well-known name changes ownership in
186 future, bind to the new owner, if any.
188 If the given object path is a unique name, this parameter
189 has no effect.
191 :Returns: a `dbus.proxies.ProxyObject`
192 :Raises `DBusException`: if resolving the well-known name to a
193 unique name fails
195 if follow_name_owner_changes:
196 self._require_main_loop() # we don't get the signals otherwise
197 return self.ProxyObjectClass(self, named_service, object_path,
198 introspect=introspect,
199 follow_name_owner_changes=follow_name_owner_changes)
201 def get_unix_user(self, bus_name):
202 """Get the numeric uid of the process owning the given bus name.
204 :Parameters:
205 `bus_name` : str
206 A bus name, either unique or well-known
207 :Returns: a `dbus.UInt32`
209 validate_bus_name(bus_name)
210 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
211 BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
212 's', (bus_name,))
214 def start_service_by_name(self, bus_name, flags=0):
215 """Start a service which will implement the given bus name on this Bus.
217 :Parameters:
218 `bus_name` : str
219 The well-known bus name to be activated.
220 `flags` : dbus.UInt32
221 Flags to pass to StartServiceByName (currently none are
222 defined)
224 :Returns: A tuple of 2 elements. The first is always True, the
225 second is either START_REPLY_SUCCESS or
226 START_REPLY_ALREADY_RUNNING.
228 :Raises DBusException: if the service could not be started.
230 validate_bus_name(bus_name)
231 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
232 BUS_DAEMON_IFACE,
233 'StartServiceByName',
234 'su', (bus_name, flags)))
236 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
237 # but this would not be backwards-compatible
238 def request_name(self, name, flags=0):
239 """Request a bus name.
241 :Parameters:
242 `name` : str
243 The well-known name to be requested
244 `flags` : dbus.UInt32
245 A bitwise-OR of 0 or more of the flags
246 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
247 `DBUS_NAME_FLAG_REPLACE_EXISTING`
248 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
249 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
250 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
251 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
252 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
253 :Raises DBusException: if the bus daemon cannot be contacted or
254 returns an error.
256 validate_bus_name(name, allow_unique=False)
257 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
258 BUS_DAEMON_IFACE, 'RequestName',
259 'su', (name, flags))
261 def release_name(self, name):
262 """Release a bus name.
264 :Parameters:
265 `name` : str
266 The well-known name to be released
267 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
268 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
269 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
270 :Raises DBusException: if the bus daemon cannot be contacted or
271 returns an error.
273 validate_bus_name(name, allow_unique=False)
274 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
275 BUS_DAEMON_IFACE, 'ReleaseName',
276 's', (name,))
278 def list_names(self):
279 """Return a list of all currently-owned names on the bus.
281 :Returns: a dbus.Array of dbus.UTF8String
283 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
284 BUS_DAEMON_IFACE, 'ListNames',
285 '', (), utf8_strings=True)
287 def list_activatable_names(self):
288 """Return a list of all names that can be activated on the bus.
290 :Returns: a dbus.Array of dbus.UTF8String
292 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
293 BUS_DAEMON_IFACE, 'ListNames',
294 '', (), utf8_strings=True)
296 def get_name_owner(self, bus_name):
297 """Return the unique connection name of the primary owner of the
298 given name.
300 :Raises DBusException: if the `bus_name` has no owner
302 validate_bus_name(bus_name, allow_unique=False)
303 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
304 BUS_DAEMON_IFACE, 'GetNameOwner',
305 's', (bus_name,), utf8_strings=True)
307 def watch_name_owner(self, bus_name, callback):
308 """Watch the unique connection name of the primary owner of the
309 given name.
311 `callback` will be called with one argument, which is either the
312 unique connection name, or the empty string (meaning the name is
313 not owned).
315 return NameOwnerWatch(self, bus_name, callback)
317 def name_has_owner(self, bus_name):
318 """Return True iff the given bus name has an owner on this bus.
320 :Parameters:
321 `name` : str
322 The bus name to look up
323 :Returns: a `bool`
325 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
326 BUS_DAEMON_IFACE, 'NameHasOwner',
327 's', (bus_name,)))
329 def add_match_string(self, rule):
330 """Arrange for this application to receive messages on the bus that
331 match the given rule. This version will block.
333 :Parameters:
334 `rule` : str
335 The match rule
336 :Raises: `DBusException` on error.
338 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
339 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
341 # FIXME: add an async success/error handler capability?
342 # (and the same for remove_...)
343 def add_match_string_non_blocking(self, rule):
344 """Arrange for this application to receive messages on the bus that
345 match the given rule. This version will not block, but any errors
346 will be ignored.
349 :Parameters:
350 `rule` : str
351 The match rule
352 :Raises: `DBusException` on error.
354 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
355 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
356 None, None)
358 def remove_match_string(self, rule):
359 """Arrange for this application to receive messages on the bus that
360 match the given rule. This version will block.
362 :Parameters:
363 `rule` : str
364 The match rule
365 :Raises: `DBusException` on error.
367 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
368 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
370 def remove_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, 'RemoveMatch', 's', (rule,),
383 None, None)