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 NAME_FLAG_ALLOW_REPLACEMENT
, \
34 NAME_FLAG_DO_NOT_QUEUE
, \
35 NAME_FLAG_REPLACE_EXISTING
, \
36 RELEASE_NAME_REPLY_NON_EXISTENT
, \
37 RELEASE_NAME_REPLY_NOT_OWNER
, \
38 RELEASE_NAME_REPLY_RELEASED
, \
39 REQUEST_NAME_REPLY_ALREADY_OWNER
, \
40 REQUEST_NAME_REPLY_EXISTS
, \
41 REQUEST_NAME_REPLY_IN_QUEUE
, \
42 REQUEST_NAME_REPLY_PRIMARY_OWNER
43 from dbus
.connection
import Connection
44 from dbus
.exceptions
import DBusException
47 _NAME_OWNER_CHANGE_MATCH
= ("type='signal',sender='%s',"
48 "interface='%s',member='NameOwnerChanged',"
49 "path='%s',arg0='%%s'"
50 % (BUS_DAEMON_NAME
, BUS_DAEMON_IFACE
,
52 """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
55 _NAME_HAS_NO_OWNER
= 'org.freedesktop.DBus.Error.NameHasNoOwner'
57 _logger
= logging
.getLogger('dbus.bus')
60 class NameOwnerWatch(object):
61 __slots__
= ('_match', '_pending_call')
63 def __init__(self
, bus_conn
, bus_name
, callback
):
64 validate_bus_name(bus_name
)
66 def signal_cb(owned
, old_owner
, new_owner
):
70 if e
.get_dbus_name() == _NAME_HAS_NO_OWNER
:
74 _logger
.debug('GetNameOwner(%s) failed:', bus_name
,
75 exc_info
=(e
.__class
__, e
, None))
77 self
._match
= bus_conn
.add_signal_receiver(signal_cb
,
83 self
._pending
_call
= bus_conn
.call_async(BUS_DAEMON_NAME
,
92 if self
._match
is not None:
94 if self
._pending
_call
is not None:
95 self
._pending
_call
.cancel()
97 self
._pending
_call
= None
100 class BusConnection(Connection
):
101 """A connection to a D-Bus daemon that implements the
102 ``org.freedesktop.DBus`` pseudo-service.
107 TYPE_SESSION
= BUS_SESSION
108 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
110 TYPE_SYSTEM
= BUS_SYSTEM
111 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
113 TYPE_STARTER
= BUS_STARTER
114 """Represents the bus that started this service by activation (same as
115 the global dbus.BUS_STARTER)"""
117 START_REPLY_SUCCESS
= DBUS_START_REPLY_SUCCESS
118 START_REPLY_ALREADY_RUNNING
= DBUS_START_REPLY_ALREADY_RUNNING
120 def __new__(cls
, address_or_type
=TYPE_SESSION
, mainloop
=None):
121 bus
= cls
._new
_for
_bus
(address_or_type
, mainloop
=mainloop
)
123 # _bus_names is used by dbus.service.BusName!
124 bus
._bus
_names
= weakref
.WeakValueDictionary()
126 bus
._signal
_sender
_matches
= {}
127 """Map from SignalMatch to NameOwnerWatch."""
131 def add_signal_receiver(self
, handler_function
, signal_name
=None,
132 dbus_interface
=None, bus_name
=None,
133 path
=None, **keywords
):
134 named_service
= keywords
.pop('named_service', None)
135 if named_service
is not None:
136 if bus_name
is not None:
137 raise TypeError('bus_name and named_service cannot both be '
139 bus_name
= named_service
140 from warnings
import warn
141 warn('Passing the named_service parameter to add_signal_receiver '
142 'by name is deprecated: please use positional parameters',
143 DeprecationWarning, stacklevel
=2)
145 match
= super(BusConnection
, self
).add_signal_receiver(
146 handler_function
, signal_name
, dbus_interface
, bus_name
,
149 if (bus_name
is not None and bus_name
!= BUS_DAEMON_NAME
):
150 if bus_name
[:1] == ':':
151 def callback(new_owner
):
155 callback
= match
.set_sender_name_owner
156 watch
= self
.watch_name_owner(bus_name
, callback
)
157 self
._signal
_sender
_matches
[match
] = watch
159 self
.add_match_string(str(match
))
163 def _clean_up_signal_match(self
, match
):
164 # The signals lock is no longer held here (it was in <= 0.81.0)
165 self
.remove_match_string_non_blocking(str(match
))
166 watch
= self
._signal
_sender
_matches
.pop(match
, None)
167 if watch
is not None:
170 def activate_name_owner(self
, bus_name
):
171 if (bus_name
is not None and bus_name
[:1] != ':'
172 and bus_name
!= BUS_DAEMON_NAME
):
174 return self
.get_name_owner(bus_name
)
175 except DBusException
, e
:
176 if e
.get_dbus_name() != _NAME_HAS_NO_OWNER
:
178 # else it doesn't exist: try to start it
179 self
.start_service_by_name(bus_name
)
180 return self
.get_name_owner(bus_name
)
185 def get_object(self
, bus_name
, object_path
, introspect
=True,
186 follow_name_owner_changes
=False, **kwargs
):
187 """Return a local proxy for the given remote object.
189 Method calls on the proxy are translated into method calls on the
194 A bus name (either the unique name or a well-known name)
195 of the application owning the object. The keyword argument
196 named_service is a deprecated alias for this.
198 The object path of the desired object
200 If true (default), attempt to introspect the remote
201 object to find out supported methods and their signatures
202 `follow_name_owner_changes` : bool
203 If the object path is a well-known name and this parameter
204 is false (default), resolve the well-known name to the unique
205 name of its current owner and bind to that instead; if the
206 ownership of the well-known name changes in future,
207 keep communicating with the original owner.
208 This is necessary if the D-Bus API used is stateful.
210 If the object path is a well-known name and this parameter
211 is true, whenever the well-known name changes ownership in
212 future, bind to the new owner, if any.
214 If the given object path is a unique name, this parameter
217 :Returns: a `dbus.proxies.ProxyObject`
218 :Raises `DBusException`: if resolving the well-known name to a
221 if follow_name_owner_changes
:
222 self
._require
_main
_loop
() # we don't get the signals otherwise
224 named_service
= kwargs
.pop('named_service', None)
225 if named_service
is not None:
226 if bus_name
is not None:
227 raise TypeError('bus_name and named_service cannot both '
229 from warnings
import warn
230 warn('Passing the named_service parameter to get_object by name '
231 'is deprecated: please use positional parameters',
232 DeprecationWarning, stacklevel
=2)
233 bus_name
= named_service
235 raise TypeError('get_object does not take these keyword '
236 'arguments: %s' % ', '.join(kwargs
.iterkeys()))
238 return self
.ProxyObjectClass(self
, bus_name
, object_path
,
239 introspect
=introspect
,
240 follow_name_owner_changes
=follow_name_owner_changes
)
242 def get_unix_user(self
, bus_name
):
243 """Get the numeric uid of the process owning the given bus name.
247 A bus name, either unique or well-known
248 :Returns: a `dbus.UInt32`
251 validate_bus_name(bus_name
)
252 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
253 BUS_DAEMON_IFACE
, 'GetConnectionUnixUser',
256 def start_service_by_name(self
, bus_name
, flags
=0):
257 """Start a service which will implement the given bus name on this Bus.
261 The well-known bus name to be activated.
262 `flags` : dbus.UInt32
263 Flags to pass to StartServiceByName (currently none are
266 :Returns: A tuple of 2 elements. The first is always True, the
267 second is either START_REPLY_SUCCESS or
268 START_REPLY_ALREADY_RUNNING.
270 :Raises `DBusException`: if the service could not be started.
273 validate_bus_name(bus_name
)
274 return (True, self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
276 'StartServiceByName',
277 'su', (bus_name
, flags
)))
279 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
280 # but this would not be backwards-compatible
281 def request_name(self
, name
, flags
=0):
282 """Request a bus name.
286 The well-known name to be requested
287 `flags` : dbus.UInt32
288 A bitwise-OR of 0 or more of the flags
289 `NAME_FLAG_ALLOW_REPLACEMENT`,
290 `NAME_FLAG_REPLACE_EXISTING`
291 and `NAME_FLAG_DO_NOT_QUEUE`
292 :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`,
293 `REQUEST_NAME_REPLY_IN_QUEUE`,
294 `REQUEST_NAME_REPLY_EXISTS` or
295 `REQUEST_NAME_REPLY_ALREADY_OWNER`
296 :Raises `DBusException`: if the bus daemon cannot be contacted or
299 validate_bus_name(name
, allow_unique
=False)
300 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
301 BUS_DAEMON_IFACE
, 'RequestName',
304 def release_name(self
, name
):
305 """Release a bus name.
309 The well-known name to be released
310 :Returns: `RELEASE_NAME_REPLY_RELEASED`,
311 `RELEASE_NAME_REPLY_NON_EXISTENT`
312 or `RELEASE_NAME_REPLY_NOT_OWNER`
313 :Raises `DBusException`: if the bus daemon cannot be contacted or
316 validate_bus_name(name
, allow_unique
=False)
317 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
318 BUS_DAEMON_IFACE
, 'ReleaseName',
321 def list_names(self
):
322 """Return a list of all currently-owned names on the bus.
324 :Returns: a dbus.Array of dbus.UTF8String
327 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
328 BUS_DAEMON_IFACE
, 'ListNames',
329 '', (), utf8_strings
=True)
331 def list_activatable_names(self
):
332 """Return a list of all names that can be activated on the bus.
334 :Returns: a dbus.Array of dbus.UTF8String
337 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
338 BUS_DAEMON_IFACE
, 'ListNames',
339 '', (), utf8_strings
=True)
341 def get_name_owner(self
, bus_name
):
342 """Return the unique connection name of the primary owner of the
345 :Raises `DBusException`: if the `bus_name` has no owner
348 validate_bus_name(bus_name
, allow_unique
=False)
349 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
350 BUS_DAEMON_IFACE
, 'GetNameOwner',
351 's', (bus_name
,), utf8_strings
=True)
353 def watch_name_owner(self
, bus_name
, callback
):
354 """Watch the unique connection name of the primary owner of the
357 `callback` will be called with one argument, which is either the
358 unique connection name, or the empty string (meaning the name is
363 return NameOwnerWatch(self
, bus_name
, callback
)
365 def name_has_owner(self
, bus_name
):
366 """Return True iff the given bus name has an owner on this bus.
370 The bus name to look up
373 return bool(self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
374 BUS_DAEMON_IFACE
, 'NameHasOwner',
377 def add_match_string(self
, rule
):
378 """Arrange for this application to receive messages on the bus that
379 match the given rule. This version will block.
384 :Raises `DBusException`: on error.
387 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
388 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,))
390 # FIXME: add an async success/error handler capability?
391 # (and the same for remove_...)
392 def add_match_string_non_blocking(self
, rule
):
393 """Arrange for this application to receive messages on the bus that
394 match the given rule. This version will not block, but any errors
401 :Raises `DBusException`: on error.
404 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
405 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,),
408 def remove_match_string(self
, rule
):
409 """Arrange for this application to receive messages on the bus that
410 match the given rule. This version will block.
415 :Raises `DBusException`: on error.
418 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
419 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,))
421 def remove_match_string_non_blocking(self
, rule
):
422 """Arrange for this application to receive messages on the bus that
423 match the given rule. This version will not block, but any errors
430 :Raises `DBusException`: on error.
433 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
434 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,),