Add wrapper for DBusServer.
[dbus-python-phuang.git] / dbus / bus.py
blobedd5ef745d85de8fe8ba1ca61fd8ee2cbd06f078
1 # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use, copy,
7 # modify, merge, publish, distribute, sublicense, and/or sell copies
8 # of the Software, and to permit persons to whom the Software is
9 # furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice shall be
12 # included in all copies or substantial portions of the Software.
14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 # DEALINGS IN THE SOFTWARE.
23 __all__ = ('BusConnection',)
24 __docformat__ = 'reStructuredText'
26 import logging
27 import weakref
29 from _dbus_bindings import validate_interface_name, validate_member_name,\
30 validate_bus_name, validate_object_path,\
31 validate_error_name,\
32 BUS_SESSION, BUS_STARTER, BUS_SYSTEM, \
33 DBUS_START_REPLY_SUCCESS, \
34 DBUS_START_REPLY_ALREADY_RUNNING, \
35 BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_DAEMON_IFACE,\
36 NAME_FLAG_ALLOW_REPLACEMENT, \
37 NAME_FLAG_DO_NOT_QUEUE, \
38 NAME_FLAG_REPLACE_EXISTING, \
39 RELEASE_NAME_REPLY_NON_EXISTENT, \
40 RELEASE_NAME_REPLY_NOT_OWNER, \
41 RELEASE_NAME_REPLY_RELEASED, \
42 REQUEST_NAME_REPLY_ALREADY_OWNER, \
43 REQUEST_NAME_REPLY_EXISTS, \
44 REQUEST_NAME_REPLY_IN_QUEUE, \
45 REQUEST_NAME_REPLY_PRIMARY_OWNER
46 from dbus.connection import Connection
47 from dbus.exceptions import DBusException
48 from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
51 _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
52 "interface='%s',member='NameOwnerChanged',"
53 "path='%s',arg0='%%s'"
54 % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
55 BUS_DAEMON_PATH))
56 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
57 messages"""
59 _NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
61 _logger = logging.getLogger('dbus.bus')
64 class NameOwnerWatch(object):
65 __slots__ = ('_match', '_pending_call')
67 def __init__(self, bus_conn, bus_name, callback):
68 validate_bus_name(bus_name)
70 def signal_cb(owned, old_owner, new_owner):
71 callback(new_owner)
73 def error_cb(e):
74 if e.get_dbus_name() == _NAME_HAS_NO_OWNER:
75 callback('')
76 else:
77 logging.basicConfig()
78 _logger.debug('GetNameOwner(%s) failed:', bus_name,
79 exc_info=(e.__class__, e, None))
81 self._match = bus_conn.add_signal_receiver(signal_cb,
82 'NameOwnerChanged',
83 BUS_DAEMON_IFACE,
84 BUS_DAEMON_NAME,
85 BUS_DAEMON_PATH,
86 arg0=bus_name)
87 self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
88 BUS_DAEMON_PATH,
89 BUS_DAEMON_IFACE,
90 'GetNameOwner',
91 's', (bus_name,),
92 callback, error_cb,
93 utf8_strings=True)
95 def cancel(self):
96 if self._match is not None:
97 self._match.remove()
98 if self._pending_call is not None:
99 self._pending_call.cancel()
100 self._match = None
101 self._pending_call = None
104 class BusConnection(Connection):
105 """A connection to a D-Bus daemon that implements the
106 ``org.freedesktop.DBus`` pseudo-service.
108 :Since: 0.81.0
111 TYPE_SESSION = BUS_SESSION
112 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
114 TYPE_SYSTEM = BUS_SYSTEM
115 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
117 TYPE_STARTER = BUS_STARTER
118 """Represents the bus that started this service by activation (same as
119 the global dbus.BUS_STARTER)"""
121 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
122 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
124 def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
125 bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
127 # _bus_names is used by dbus.service.BusName!
128 bus._bus_names = weakref.WeakValueDictionary()
130 bus._signal_sender_matches = {}
131 """Map from SignalMatch to NameOwnerWatch."""
133 return bus
135 def add_signal_receiver(self, handler_function, signal_name=None,
136 dbus_interface=None, bus_name=None,
137 path=None, **keywords):
138 named_service = keywords.pop('named_service', None)
139 if named_service is not None:
140 if bus_name is not None:
141 raise TypeError('bus_name and named_service cannot both be '
142 'specified')
143 bus_name = named_service
144 from warnings import warn
145 warn('Passing the named_service parameter to add_signal_receiver '
146 'by name is deprecated: please use positional parameters',
147 DeprecationWarning, stacklevel=2)
149 match = super(BusConnection, self).add_signal_receiver(
150 handler_function, signal_name, dbus_interface, bus_name,
151 path, **keywords)
153 if (bus_name is not None and bus_name != BUS_DAEMON_NAME):
154 if bus_name[:1] == ':':
155 def callback(new_owner):
156 if new_owner == '':
157 match.remove()
158 else:
159 callback = match.set_sender_name_owner
160 watch = self.watch_name_owner(bus_name, callback)
161 self._signal_sender_matches[match] = watch
163 self.add_match_string(str(match))
165 return match
167 def _clean_up_signal_match(self, match):
168 # The signals lock is no longer held here (it was in <= 0.81.0)
169 self.remove_match_string_non_blocking(str(match))
170 watch = self._signal_sender_matches.pop(match, None)
171 if watch is not None:
172 watch.cancel()
174 def activate_name_owner(self, bus_name):
175 if (bus_name is not None and bus_name[:1] != ':'
176 and bus_name != BUS_DAEMON_NAME):
177 try:
178 return self.get_name_owner(bus_name)
179 except DBusException, e:
180 if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
181 raise
182 # else it doesn't exist: try to start it
183 self.start_service_by_name(bus_name)
184 return self.get_name_owner(bus_name)
185 else:
186 # already unique
187 return bus_name
189 def get_object(self, bus_name, object_path, introspect=True,
190 follow_name_owner_changes=False, **kwargs):
191 """Return a local proxy for the given remote object.
193 Method calls on the proxy are translated into method calls on the
194 remote object.
196 :Parameters:
197 `bus_name` : str
198 A bus name (either the unique name or a well-known name)
199 of the application owning the object. The keyword argument
200 named_service is a deprecated alias for this.
201 `object_path` : str
202 The object path of the desired object
203 `introspect` : bool
204 If true (default), attempt to introspect the remote
205 object to find out supported methods and their signatures
206 `follow_name_owner_changes` : bool
207 If the object path is a well-known name and this parameter
208 is false (default), resolve the well-known name to the unique
209 name of its current owner and bind to that instead; if the
210 ownership of the well-known name changes in future,
211 keep communicating with the original owner.
212 This is necessary if the D-Bus API used is stateful.
214 If the object path is a well-known name and this parameter
215 is true, whenever the well-known name changes ownership in
216 future, bind to the new owner, if any.
218 If the given object path is a unique name, this parameter
219 has no effect.
221 :Returns: a `dbus.proxies.ProxyObject`
222 :Raises `DBusException`: if resolving the well-known name to a
223 unique name fails
225 if follow_name_owner_changes:
226 self._require_main_loop() # we don't get the signals otherwise
228 named_service = kwargs.pop('named_service', None)
229 if named_service is not None:
230 if bus_name is not None:
231 raise TypeError('bus_name and named_service cannot both '
232 'be specified')
233 from warnings import warn
234 warn('Passing the named_service parameter to get_object by name '
235 'is deprecated: please use positional parameters',
236 DeprecationWarning, stacklevel=2)
237 bus_name = named_service
238 if kwargs:
239 raise TypeError('get_object does not take these keyword '
240 'arguments: %s' % ', '.join(kwargs.iterkeys()))
242 return self.ProxyObjectClass(self, bus_name, object_path,
243 introspect=introspect,
244 follow_name_owner_changes=follow_name_owner_changes)
246 def get_unix_user(self, bus_name):
247 """Get the numeric uid of the process owning the given bus name.
249 :Parameters:
250 `bus_name` : str
251 A bus name, either unique or well-known
252 :Returns: a `dbus.UInt32`
253 :Since: 0.80.0
255 validate_bus_name(bus_name)
256 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
257 BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
258 's', (bus_name,))
260 def start_service_by_name(self, bus_name, flags=0):
261 """Start a service which will implement the given bus name on this Bus.
263 :Parameters:
264 `bus_name` : str
265 The well-known bus name to be activated.
266 `flags` : dbus.UInt32
267 Flags to pass to StartServiceByName (currently none are
268 defined)
270 :Returns: A tuple of 2 elements. The first is always True, the
271 second is either START_REPLY_SUCCESS or
272 START_REPLY_ALREADY_RUNNING.
274 :Raises `DBusException`: if the service could not be started.
275 :Since: 0.80.0
277 validate_bus_name(bus_name)
278 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
279 BUS_DAEMON_IFACE,
280 'StartServiceByName',
281 'su', (bus_name, flags)))
283 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
284 # but this would not be backwards-compatible
285 def request_name(self, name, flags=0):
286 """Request a bus name.
288 :Parameters:
289 `name` : str
290 The well-known name to be requested
291 `flags` : dbus.UInt32
292 A bitwise-OR of 0 or more of the flags
293 `NAME_FLAG_ALLOW_REPLACEMENT`,
294 `NAME_FLAG_REPLACE_EXISTING`
295 and `NAME_FLAG_DO_NOT_QUEUE`
296 :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`,
297 `REQUEST_NAME_REPLY_IN_QUEUE`,
298 `REQUEST_NAME_REPLY_EXISTS` or
299 `REQUEST_NAME_REPLY_ALREADY_OWNER`
300 :Raises `DBusException`: if the bus daemon cannot be contacted or
301 returns an error.
303 validate_bus_name(name, allow_unique=False)
304 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
305 BUS_DAEMON_IFACE, 'RequestName',
306 'su', (name, flags))
308 def release_name(self, name):
309 """Release a bus name.
311 :Parameters:
312 `name` : str
313 The well-known name to be released
314 :Returns: `RELEASE_NAME_REPLY_RELEASED`,
315 `RELEASE_NAME_REPLY_NON_EXISTENT`
316 or `RELEASE_NAME_REPLY_NOT_OWNER`
317 :Raises `DBusException`: if the bus daemon cannot be contacted or
318 returns an error.
320 validate_bus_name(name, allow_unique=False)
321 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
322 BUS_DAEMON_IFACE, 'ReleaseName',
323 's', (name,))
325 def list_names(self):
326 """Return a list of all currently-owned names on the bus.
328 :Returns: a dbus.Array of dbus.UTF8String
329 :Since: 0.81.0
331 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
332 BUS_DAEMON_IFACE, 'ListNames',
333 '', (), utf8_strings=True)
335 def list_activatable_names(self):
336 """Return a list of all names that can be activated on the bus.
338 :Returns: a dbus.Array of dbus.UTF8String
339 :Since: 0.81.0
341 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
342 BUS_DAEMON_IFACE, 'ListNames',
343 '', (), utf8_strings=True)
345 def get_name_owner(self, bus_name):
346 """Return the unique connection name of the primary owner of the
347 given name.
349 :Raises `DBusException`: if the `bus_name` has no owner
350 :Since: 0.81.0
352 validate_bus_name(bus_name, allow_unique=False)
353 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
354 BUS_DAEMON_IFACE, 'GetNameOwner',
355 's', (bus_name,), utf8_strings=True)
357 def watch_name_owner(self, bus_name, callback):
358 """Watch the unique connection name of the primary owner of the
359 given name.
361 `callback` will be called with one argument, which is either the
362 unique connection name, or the empty string (meaning the name is
363 not owned).
365 :Since: 0.81.0
367 return NameOwnerWatch(self, bus_name, callback)
369 def name_has_owner(self, bus_name):
370 """Return True iff the given bus name has an owner on this bus.
372 :Parameters:
373 `bus_name` : str
374 The bus name to look up
375 :Returns: a `bool`
377 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
378 BUS_DAEMON_IFACE, 'NameHasOwner',
379 's', (bus_name,)))
381 def add_match_string(self, rule):
382 """Arrange for this application to receive messages on the bus that
383 match the given rule. This version will block.
385 :Parameters:
386 `rule` : str
387 The match rule
388 :Raises `DBusException`: on error.
389 :Since: 0.80.0
391 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
392 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
394 # FIXME: add an async success/error handler capability?
395 # (and the same for remove_...)
396 def add_match_string_non_blocking(self, rule):
397 """Arrange for this application to receive messages on the bus that
398 match the given rule. This version will not block, but any errors
399 will be ignored.
402 :Parameters:
403 `rule` : str
404 The match rule
405 :Raises `DBusException`: on error.
406 :Since: 0.80.0
408 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
409 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
410 None, None)
412 def remove_match_string(self, rule):
413 """Arrange for this application to receive messages on the bus that
414 match the given rule. This version will block.
416 :Parameters:
417 `rule` : str
418 The match rule
419 :Raises `DBusException`: on error.
420 :Since: 0.80.0
422 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
423 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
425 def remove_match_string_non_blocking(self, rule):
426 """Arrange for this application to receive messages on the bus that
427 match the given rule. This version will not block, but any errors
428 will be ignored.
431 :Parameters:
432 `rule` : str
433 The match rule
434 :Raises `DBusException`: on error.
435 :Since: 0.80.0
437 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
438 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
439 None, None)