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 BusConnection(Connection
):
49 """A connection to a D-Bus daemon that implements the
50 ``org.freedesktop.DBus`` pseudo-service.
53 TYPE_SESSION
= BUS_SESSION
54 """Represents a session bus (same as the global dbus.BUS_SESSION)"""
56 TYPE_SYSTEM
= BUS_SYSTEM
57 """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
59 TYPE_STARTER
= BUS_STARTER
60 """Represents the bus that started this service by activation (same as
61 the global dbus.BUS_STARTER)"""
63 START_REPLY_SUCCESS
= DBUS_START_REPLY_SUCCESS
64 START_REPLY_ALREADY_RUNNING
= DBUS_START_REPLY_ALREADY_RUNNING
66 def __new__(cls
, address_or_type
=TYPE_SESSION
, mainloop
=None):
67 bus
= cls
._new
_for
_bus
(address_or_type
, mainloop
=mainloop
)
69 # _bus_names is used by dbus.service.BusName!
70 bus
._bus
_names
= weakref
.WeakValueDictionary()
72 bus
._signal
_sender
_matches
= {}
73 """Map from sender well-known name to list of match rules for all
74 signal handlers that match on sender well-known name."""
76 bus
.add_message_filter(bus
.__class
__._noc
_signal
_func
)
80 def _noc_signal_func(self
, message
):
81 # If it's NameOwnerChanged, we'll need to update our
82 # sender well-known name -> sender unique name mappings
83 if (message
.is_signal(BUS_DAEMON_IFACE
, 'NameOwnerChanged')
84 and message
.has_sender(BUS_DAEMON_NAME
)
85 and message
.has_path(BUS_DAEMON_PATH
)):
86 name
, unused
, new
= message
.get_args_list()
87 for match
in self
._signal
_sender
_matches
.get(name
, (None,)):
88 match
.sender_unique
= new
90 return HANDLER_RESULT_NOT_YET_HANDLED
92 def add_signal_receiver(self
, handler_function
, signal_name
=None,
93 dbus_interface
=None, named_service
=None,
94 path
=None, **keywords
):
95 match
= super(BusConnection
, self
).add_signal_receiver(
96 handler_function
, signal_name
, dbus_interface
, named_service
,
99 # The bus daemon is special - its unique-name is org.freedesktop.DBus
100 # rather than starting with :
101 if (named_service
is not None
102 and named_service
[:1] != ':'
103 and named_service
!= BUS_DAEMON_NAME
):
105 match
.sender_unique
= self
.get_name_owner(named_service
)
106 except DBusException
:
107 # if the desired sender isn't actually running, we'll get
108 # notified by NameOwnerChanged when it appears
110 notification
= self
._signal
_sender
_matches
.setdefault(
113 self
.add_match_string(_NAME_OWNER_CHANGE_MATCH
% named_service
)
114 notification
.append(match
)
116 self
.add_match_string(str(match
))
120 def _clean_up_signal_match(self
, match
):
121 # The signals lock must be held.
122 self
.remove_match_string(str(match
))
123 notification
= self
._signal
_sender
_matches
.get(match
.sender
, False)
126 notification
.remove(match
)
130 # nobody cares any more, so remove the match rule from the bus
131 self
.remove_match_string(_NAME_OWNER_CHANGE_MATCH
134 def activate_name_owner(self
, bus_name
):
135 if (bus_name
is not None and bus_name
[:1] != ':'
136 and bus_name
!= BUS_DAEMON_NAME
):
138 return self
.get_name_owner(bus_name
)
139 except DBusException
, e
:
140 # FIXME: detect whether it's NameHasNoOwner, but properly
141 #if not str(e).startswith('org.freedesktop.DBus.Error.NameHasNoOwner:'):
143 # it might not exist: try to start it
144 self
.start_service_by_name(bus_name
)
145 return self
.get_name_owner(bus_name
)
150 def get_object(self
, named_service
, object_path
, introspect
=True,
151 follow_name_owner_changes
=False):
152 """Return a local proxy for the given remote object.
154 Method calls on the proxy are translated into method calls on the
158 `named_service` : str
159 A bus name (either the unique name or a well-known name)
160 of the application owning the object
162 The object path of the desired object
164 If true (default), attempt to introspect the remote
165 object to find out supported methods and their signatures
166 `follow_name_owner_changes` : bool
167 If the object path is a well-known name and this parameter
168 is false (default), resolve the well-known name to the unique
169 name of its current owner and bind to that instead; if the
170 ownership of the well-known name changes in future,
171 keep communicating with the original owner.
172 This is necessary if the D-Bus API used is stateful.
174 If the object path is a well-known name and this parameter
175 is true, whenever the well-known name changes ownership in
176 future, bind to the new owner, if any.
178 If the given object path is a unique name, this parameter
181 :Returns: a `dbus.proxies.ProxyObject`
182 :Raises `DBusException`: if resolving the well-known name to a
185 if follow_name_owner_changes
:
186 self
._require
_main
_loop
() # we don't get the signals otherwise
187 return self
.ProxyObjectClass(self
, named_service
, object_path
,
188 introspect
=introspect
,
189 follow_name_owner_changes
=follow_name_owner_changes
)
191 def get_unix_user(self
, bus_name
):
192 """Get the numeric uid of the process owning the given bus name.
196 A bus name, either unique or well-known
197 :Returns: a `dbus.UInt32`
199 validate_bus_name(bus_name
)
200 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
201 BUS_DAEMON_IFACE
, 'GetConnectionUnixUser',
204 def start_service_by_name(self
, bus_name
, flags
=0):
205 """Start a service which will implement the given bus name on this Bus.
209 The well-known bus name to be activated.
210 `flags` : dbus.UInt32
211 Flags to pass to StartServiceByName (currently none are
214 :Returns: A tuple of 2 elements. The first is always True, the
215 second is either START_REPLY_SUCCESS or
216 START_REPLY_ALREADY_RUNNING.
218 :Raises DBusException: if the service could not be started.
220 validate_bus_name(bus_name
)
221 return (True, self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
223 'StartServiceByName',
224 'su', (bus_name
, flags
)))
226 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
227 # but this would not be backwards-compatible
228 def request_name(self
, name
, flags
=0):
229 """Request a bus name.
233 The well-known name to be requested
234 `flags` : dbus.UInt32
235 A bitwise-OR of 0 or more of the flags
236 `DBUS_NAME_FLAG_ALLOW_REPLACEMENT`,
237 `DBUS_NAME_FLAG_REPLACE_EXISTING`
238 and `DBUS_NAME_FLAG_DO_NOT_QUEUE`
239 :Returns: `DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER`,
240 `DBUS_REQUEST_NAME_REPLY_IN_QUEUE`,
241 `DBUS_REQUEST_NAME_REPLY_EXISTS` or
242 `DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER`
243 :Raises DBusException: if the bus daemon cannot be contacted or
246 validate_bus_name(name
, allow_unique
=False)
247 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
248 BUS_DAEMON_IFACE
, 'RequestName',
251 def release_name(self
, name
):
252 """Release a bus name.
256 The well-known name to be released
257 :Returns: `DBUS_RELEASE_NAME_REPLY_RELEASED`,
258 `DBUS_RELEASE_NAME_REPLY_NON_EXISTENT`
259 or `DBUS_RELEASE_NAME_REPLY_NOT_OWNER`
260 :Raises DBusException: if the bus daemon cannot be contacted or
263 validate_bus_name(name
, allow_unique
=False)
264 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
265 BUS_DAEMON_IFACE
, 'ReleaseName',
268 def list_names(self
):
269 """Return a list of all currently-owned names on the bus.
271 :Returns: a dbus.Array of dbus.UTF8String
273 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
274 BUS_DAEMON_IFACE
, 'ListNames',
275 '', (), utf8_strings
=True)
277 def list_activatable_names(self
):
278 """Return a list of all names that can be activated on the bus.
280 :Returns: a dbus.Array of dbus.UTF8String
282 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
283 BUS_DAEMON_IFACE
, 'ListNames',
284 '', (), utf8_strings
=True)
286 def get_name_owner(self
, bus_name
):
287 """Return the unique connection name of the primary owner of the
290 :Raises DBusException: if the `bus_name` has no owner
292 validate_bus_name(bus_name
, allow_unique
=False)
293 return self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
294 BUS_DAEMON_IFACE
, 'GetNameOwner',
295 's', (bus_name
,), utf8_strings
=True)
297 def name_has_owner(self
, bus_name
):
298 """Return True iff the given bus name has an owner on this bus.
302 The bus name to look up
305 return bool(self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
306 BUS_DAEMON_IFACE
, 'NameHasOwner',
309 def add_match_string(self
, rule
):
310 """Arrange for this application to receive messages on the bus that
311 match the given rule. This version will block.
316 :Raises: `DBusException` on error.
318 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
319 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,))
321 # FIXME: add an async success/error handler capability?
322 # (and the same for remove_...)
323 def add_match_string_non_blocking(self
, rule
):
324 """Arrange for this application to receive messages on the bus that
325 match the given rule. This version will not block, but any errors
332 :Raises: `DBusException` on error.
334 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
335 BUS_DAEMON_IFACE
, 'AddMatch', 's', (rule
,),
338 def remove_match_string(self
, rule
):
339 """Arrange for this application to receive messages on the bus that
340 match the given rule. This version will block.
345 :Raises: `DBusException` on error.
347 self
.call_blocking(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
348 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,))
350 def remove_match_string_non_blocking(self
, rule
):
351 """Arrange for this application to receive messages on the bus that
352 match the given rule. This version will not block, but any errors
359 :Raises: `DBusException` on error.
361 self
.call_async(BUS_DAEMON_NAME
, BUS_DAEMON_PATH
,
362 BUS_DAEMON_IFACE
, 'RemoveMatch', 's', (rule
,),