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'
25 from _dbus_bindings
import validate_interface_name
, validate_member_name
,\
26 validate_bus_name
, validate_object_path
,\
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
,
42 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
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
):
58 # FIXME: detect whether it's NameHasNoOwner properly
59 if str(e
).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
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
,
71 self
._pending
_call
= bus_conn
.call_async(BUS_DAEMON_NAME
,
80 if self
._match
is not None:
82 if self
._pending
_call
is not None:
83 self
._pending
_call
.cancel()
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.
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."""
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 '
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
,
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
))
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:
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
):
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:'):
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
)
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
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.
184 The object path of the desired object
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
203 :Returns: a `dbus.proxies.ProxyObject`
204 :Raises `DBusException`: if resolving the well-known name to a
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 '
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
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.
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',
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.
246 The well-known bus name to be activated.
247 `flags` : dbus.UInt32
248 Flags to pass to StartServiceByName (currently none are
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
,
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.
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
283 validate_bus_name(name
, allow_unique
=False)
284 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
285 BUS_DAEMON_IFACE
, 'RequestName',
288 def release_name(self
, name
):
289 """Release a bus name.
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
300 validate_bus_name(name
, allow_unique
=False)
301 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
302 BUS_DAEMON_IFACE
, 'ReleaseName',
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
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
338 `callback` will be called with one argument, which is either the
339 unique connection name, or the empty string (meaning the name is
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.
349 The bus name to look up
352 return bool(self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
353 BUS_DAEMON_IFACE
, 'NameHasOwner',
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.
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
379 :Raises: `DBusException` on error.
381 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
382 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,),
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.
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
406 :Raises: `DBusException` on error.
408 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
409 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,),