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
)
56 def signal_cb(owned
, old_owner
, new_owner
):
60 if e
.get_dbus_name() == _NAME_HAS_NO_OWNER
:
64 _logger
.debug('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 if (bus_name
is not None and bus_name
!= BUS_DAEMON_NAME
):
137 if bus_name
[:1] == ':':
138 def callback(new_owner
):
142 callback
= match
.set_sender_name_owner
143 watch
= self
.watch_name_owner(bus_name
, callback
)
144 self
._signal
_sender
_matches
[match
] = watch
146 self
.add_match_string(str(match
))
150 def _clean_up_signal_match(self
, match
):
151 # The signals lock is no longer held here (it was in <= 0.81.0)
152 self
.remove_match_string_non_blocking(str(match
))
153 watch
= self
._signal
_sender
_matches
.pop(match
, None)
154 if watch
is not None:
157 def activate_name_owner(self
, bus_name
):
158 if (bus_name
is not None and bus_name
[:1] != ':'
159 and bus_name
!= BUS_DAEMON_NAME
):
161 return self
.get_name_owner(bus_name
)
162 except DBusException
, e
:
163 if e
.get_dbus_name() != _NAME_HAS_NO_OWNER
:
165 # else it doesn't exist: try to start it
166 self
.start_service_by_name(bus_name
)
167 return self
.get_name_owner(bus_name
)
172 def get_object(self
, bus_name
, object_path
, introspect
=True,
173 follow_name_owner_changes
=False, **kwargs
):
174 """Return a local proxy for the given remote object.
176 Method calls on the proxy are translated into method calls on the
181 A bus name (either the unique name or a well-known name)
182 of the application owning the object. The keyword argument
183 named_service is a deprecated alias for this.
185 The object path of the desired object
187 If true (default), attempt to introspect the remote
188 object to find out supported methods and their signatures
189 `follow_name_owner_changes` : bool
190 If the object path is a well-known name and this parameter
191 is false (default), resolve the well-known name to the unique
192 name of its current owner and bind to that instead; if the
193 ownership of the well-known name changes in future,
194 keep communicating with the original owner.
195 This is necessary if the D-Bus API used is stateful.
197 If the object path is a well-known name and this parameter
198 is true, whenever the well-known name changes ownership in
199 future, bind to the new owner, if any.
201 If the given object path is a unique name, this parameter
204 :Returns: a `dbus.proxies.ProxyObject`
205 :Raises `DBusException`: if resolving the well-known name to a
208 if follow_name_owner_changes
:
209 self
._require
_main
_loop
() # we don't get the signals otherwise
211 named_service
= kwargs
.pop('named_service', None)
212 if named_service
is not None:
213 if bus_name
is not None:
214 raise TypeError('bus_name and named_service cannot both '
216 from warnings
import warn
217 warn('Passing the named_service parameter to get_object by name '
218 'is deprecated: please use positional parameters',
219 DeprecationWarning, stacklevel
=2)
220 bus_name
= named_service
222 raise TypeError('get_object does not take these keyword '
223 'arguments: %s' % ', '.join(kwargs
.iterkeys()))
225 return self
.ProxyObjectClass(self
, bus_name
, object_path
,
226 introspect
=introspect
,
227 follow_name_owner_changes
=follow_name_owner_changes
)
229 def get_unix_user(self
, bus_name
):
230 """Get the numeric uid of the process owning the given bus name.
234 A bus name, either unique or well-known
235 :Returns: a `dbus.UInt32`
237 validate_bus_name(bus_name
)
238 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
239 BUS_DAEMON_IFACE
, 'GetConnectionUnixUser',
242 def start_service_by_name(self
, bus_name
, flags
=0):
243 """Start a service which will implement the given bus name on this Bus.
247 The well-known bus name to be activated.
248 `flags` : dbus.UInt32
249 Flags to pass to StartServiceByName (currently none are
252 :Returns: A tuple of 2 elements. The first is always True, the
253 second is either START_REPLY_SUCCESS or
254 START_REPLY_ALREADY_RUNNING.
256 :Raises DBusException: if the service could not be started.
258 validate_bus_name(bus_name
)
259 return (True, self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
261 'StartServiceByName',
262 'su', (bus_name
, flags
)))
264 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
265 # but this would not be backwards-compatible
266 def request_name(self
, name
, flags
=0):
267 """Request a bus name.
271 The well-known name to be requested
272 `flags` : dbus.UInt32
273 A bitwise-OR of 0 or more of the flags
274 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
275 `DBUS_NAME_FLAG_REPLACE_EXISTING`
276 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
277 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
278 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
279 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
280 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
281 :Raises DBusException: if the bus daemon cannot be contacted or
284 validate_bus_name(name
, allow_unique
=False)
285 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
286 BUS_DAEMON_IFACE
, 'RequestName',
289 def release_name(self
, name
):
290 """Release a bus name.
294 The well-known name to be released
295 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
296 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
297 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
298 :Raises DBusException: if the bus daemon cannot be contacted or
301 validate_bus_name(name
, allow_unique
=False)
302 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
303 BUS_DAEMON_IFACE
, 'ReleaseName',
306 def list_names(self
):
307 """Return a list of all currently-owned names on the bus.
309 :Returns: a dbus.Array of dbus.UTF8String
311 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
312 BUS_DAEMON_IFACE
, 'ListNames',
313 '', (), utf8_strings
=True)
315 def list_activatable_names(self
):
316 """Return a list of all names that can be activated on the bus.
318 :Returns: a dbus.Array of dbus.UTF8String
320 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
321 BUS_DAEMON_IFACE
, 'ListNames',
322 '', (), utf8_strings
=True)
324 def get_name_owner(self
, bus_name
):
325 """Return the unique connection name of the primary owner of the
328 :Raises DBusException: if the `bus_name` has no owner
330 validate_bus_name(bus_name
, allow_unique
=False)
331 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
332 BUS_DAEMON_IFACE
, 'GetNameOwner',
333 's', (bus_name
,), utf8_strings
=True)
335 def watch_name_owner(self
, bus_name
, callback
):
336 """Watch the unique connection name of the primary owner of the
339 `callback` will be called with one argument, which is either the
340 unique connection name, or the empty string (meaning the name is
343 return NameOwnerWatch(self
, bus_name
, callback
)
345 def name_has_owner(self
, bus_name
):
346 """Return True iff the given bus name has an owner on this bus.
350 The bus name to look up
353 return bool(self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
354 BUS_DAEMON_IFACE
, 'NameHasOwner',
357 def add_match_string(self
, rule
):
358 """Arrange for this application to receive messages on the bus that
359 match the given rule. This version will block.
364 :Raises: `DBusException` on error.
366 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
367 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,))
369 # FIXME: add an async success/error handler capability?
370 # (and the same for remove_...)
371 def add_match_string_non_blocking(self
, rule
):
372 """Arrange for this application to receive messages on the bus that
373 match the given rule. This version will not block, but any errors
380 :Raises: `DBusException` on error.
382 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
383 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,),
386 def remove_match_string(self
, rule
):
387 """Arrange for this application to receive messages on the bus that
388 match the given rule. This version will block.
393 :Raises: `DBusException` on error.
395 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
396 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,))
398 def remove_match_string_non_blocking(self
, rule
):
399 """Arrange for this application to receive messages on the bus that
400 match the given rule. This version will not block, but any errors
407 :Raises: `DBusException` on error.
409 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
410 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,),