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 _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
, allow_unique
=False)
56 def signal_cb(owned
, old_owner
, new_owner
):
60 if e
.get_dbus_name() == _NAME_HAS_NO_OWNER
:
64 _logger
.error('GetNameOwner(%s) failed:', bus_name
,
65 exc_info
=(e
.__class
__, e
, None))
67 self
._match
= bus_conn
.add_signal_receiver(signal_cb
,
72 self
._pending
_call
= bus_conn
.call_async(BUS_DAEMON_NAME
,
81 if self
._match
is not None:
83 if self
._pending
_call
is not None:
84 self
._pending
_call
.cancel()
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.
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."""
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 '
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
,
136 # The bus daemon is special - its unique-name is org.freedesktop.DBus
137 # rather than starting with :
138 if (bus_name
is not None
139 and bus_name
[:1] != ':'
140 and bus_name
!= BUS_DAEMON_NAME
):
141 watch
= self
.watch_name_owner(bus_name
,
142 match
.set_sender_name_owner
)
143 self
._signal
_sender
_matches
[match
] = watch
145 self
.add_match_string(str(match
))
149 def _clean_up_signal_match(self
, match
):
150 # The signals lock is no longer held here (it was in <= 0.81.0)
151 self
.remove_match_string(str(match
))
152 watch
= self
._signal
_sender
_matches
.pop(match
, None)
153 if watch
is not None:
156 def activate_name_owner(self
, bus_name
):
157 if (bus_name
is not None and bus_name
[:1] != ':'
158 and bus_name
!= BUS_DAEMON_NAME
):
160 return self
.get_name_owner(bus_name
)
161 except DBusException
, e
:
162 if e
.get_dbus_name() != _NAME_HAS_NO_OWNER
:
164 # else it doesn't 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
,),