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
,\
29 BUS_SESSION
, BUS_STARTER
, BUS_SYSTEM
, \
30 DBUS_START_REPLY_SUCCESS
, \
31 DBUS_START_REPLY_ALREADY_RUNNING
, \
32 BUS_DAEMON_NAME
, BUS_DAEMON_PATH
, BUS_DAEMON_IFACE
,\
33 HANDLER_RESULT_NOT_YET_HANDLED
34 from dbus
.connection
import Connection
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, named_service
=None,
119 path
=None, **keywords
):
120 match
= super(BusConnection
, self
).add_signal_receiver(
121 handler_function
, signal_name
, dbus_interface
, named_service
,
124 # The bus daemon is special - its unique-name is org.freedesktop.DBus
125 # rather than starting with :
126 if (named_service
is not None
127 and named_service
[:1] != ':'
128 and named_service
!= BUS_DAEMON_NAME
):
129 watch
= self
.watch_name_owner(named_service
,
130 match
.set_sender_name_owner
)
131 self
._signal
_sender
_matches
[match
] = watch
133 self
.add_match_string(str(match
))
137 def _clean_up_signal_match(self
, match
):
138 # The signals lock must be held.
139 self
.remove_match_string(str(match
))
140 watch
= self
._signal
_sender
_matches
.pop(match
, None)
141 if watch
is not None:
144 def activate_name_owner(self
, bus_name
):
145 if (bus_name
is not None and bus_name
[:1] != ':'
146 and bus_name
!= BUS_DAEMON_NAME
):
148 return self
.get_name_owner(bus_name
)
149 except DBusException
, e
:
150 # FIXME: detect whether it's NameHasNoOwner, but properly
151 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
153 # it might not exist: try to start it
154 self
.start_service_by_name(bus_name
)
155 return self
.get_name_owner(bus_name
)
160 def get_object(self
, named_service
, object_path
, introspect
=True,
161 follow_name_owner_changes
=False):
162 """Return a local proxy for the given remote object.
164 Method calls on the proxy are translated into method calls on the
168 `named_service` : str
169 A bus name (either the unique name or a well-known name)
170 of the application owning the object
172 The object path of the desired object
174 If true (default), attempt to introspect the remote
175 object to find out supported methods and their signatures
176 `follow_name_owner_changes` : bool
177 If the object path is a well-known name and this parameter
178 is false (default), resolve the well-known name to the unique
179 name of its current owner and bind to that instead; if the
180 ownership of the well-known name changes in future,
181 keep communicating with the original owner.
182 This is necessary if the D-Bus API used is stateful.
184 If the object path is a well-known name and this parameter
185 is true, whenever the well-known name changes ownership in
186 future, bind to the new owner, if any.
188 If the given object path is a unique name, this parameter
191 :Returns: a `dbus.proxies.ProxyObject`
192 :Raises `DBusException`: if resolving the well-known name to a
195 if follow_name_owner_changes
:
196 self
._require
_main
_loop
() # we don't get the signals otherwise
197 return self
.ProxyObjectClass(self
, named_service
, object_path
,
198 introspect
=introspect
,
199 follow_name_owner_changes
=follow_name_owner_changes
)
201 def get_unix_user(self
, bus_name
):
202 """Get the numeric uid of the process owning the given bus name.
206 A bus name, either unique or well-known
207 :Returns: a `dbus.UInt32`
209 validate_bus_name(bus_name
)
210 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
211 BUS_DAEMON_IFACE
, 'GetConnectionUnixUser',
214 def start_service_by_name(self
, bus_name
, flags
=0):
215 """Start a service which will implement the given bus name on this Bus.
219 The well-known bus name to be activated.
220 `flags` : dbus.UInt32
221 Flags to pass to StartServiceByName (currently none are
224 :Returns: A tuple of 2 elements. The first is always True, the
225 second is either START_REPLY_SUCCESS or
226 START_REPLY_ALREADY_RUNNING.
228 :Raises DBusException: if the service could not be started.
230 validate_bus_name(bus_name
)
231 return (True, self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
233 'StartServiceByName',
234 'su', (bus_name
, flags
)))
236 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
237 # but this would not be backwards-compatible
238 def request_name(self
, name
, flags
=0):
239 """Request a bus name.
243 The well-known name to be requested
244 `flags` : dbus.UInt32
245 A bitwise-OR of 0 or more of the flags
246 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
247 `DBUS_NAME_FLAG_REPLACE_EXISTING`
248 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
249 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
250 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
251 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
252 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
253 :Raises DBusException: if the bus daemon cannot be contacted or
256 validate_bus_name(name
, allow_unique
=False)
257 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
258 BUS_DAEMON_IFACE
, 'RequestName',
261 def release_name(self
, name
):
262 """Release a bus name.
266 The well-known name to be released
267 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
268 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
269 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
270 :Raises DBusException: if the bus daemon cannot be contacted or
273 validate_bus_name(name
, allow_unique
=False)
274 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
275 BUS_DAEMON_IFACE
, 'ReleaseName',
278 def list_names(self
):
279 """Return a list of all currently-owned names on the bus.
281 :Returns: a dbus.Array of dbus.UTF8String
283 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
284 BUS_DAEMON_IFACE
, 'ListNames',
285 '', (), utf8_strings
=True)
287 def list_activatable_names(self
):
288 """Return a list of all names that can be activated on the bus.
290 :Returns: a dbus.Array of dbus.UTF8String
292 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
293 BUS_DAEMON_IFACE
, 'ListNames',
294 '', (), utf8_strings
=True)
296 def get_name_owner(self
, bus_name
):
297 """Return the unique connection name of the primary owner of the
300 :Raises DBusException: if the `bus_name` has no owner
302 validate_bus_name(bus_name
, allow_unique
=False)
303 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
304 BUS_DAEMON_IFACE
, 'GetNameOwner',
305 's', (bus_name
,), utf8_strings
=True)
307 def watch_name_owner(self
, bus_name
, callback
):
308 """Watch the unique connection name of the primary owner of the
311 `callback` will be called with one argument, which is either the
312 unique connection name, or the empty string (meaning the name is
315 return NameOwnerWatch(self
, bus_name
, callback
)
317 def name_has_owner(self
, bus_name
):
318 """Return True iff the given bus name has an owner on this bus.
322 The bus name to look up
325 return bool(self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
326 BUS_DAEMON_IFACE
, 'NameHasOwner',
329 def add_match_string(self
, rule
):
330 """Arrange for this application to receive messages on the bus that
331 match the given rule. This version will block.
336 :Raises: `DBusException` on error.
338 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
339 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,))
341 # FIXME: add an async success/error handler capability?
342 # (and the same for remove_...)
343 def add_match_string_non_blocking(self
, rule
):
344 """Arrange for this application to receive messages on the bus that
345 match the given rule. This version will not block, but any errors
352 :Raises: `DBusException` on error.
354 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
355 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,),
358 def remove_match_string(self
, rule
):
359 """Arrange for this application to receive messages on the bus that
360 match the given rule. This version will block.
365 :Raises: `DBusException` on error.
367 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
368 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,))
370 def remove_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
, 'RemoveMatch', 's', (rule
,),