Package dbus :: Module bus
[hide private]
[frames] | no frames]

Source Code for Module dbus.bus

  1  # Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/> 
  2  # 
  3  # Permission is hereby granted, free of charge, to any person 
  4  # obtaining a copy of this software and associated documentation 
  5  # files (the "Software"), to deal in the Software without 
  6  # restriction, including without limitation the rights to use, copy, 
  7  # modify, merge, publish, distribute, sublicense, and/or sell copies 
  8  # of the Software, and to permit persons to whom the Software is 
  9  # furnished to do so, subject to the following conditions: 
 10  # 
 11  # The above copyright notice and this permission notice shall be 
 12  # included in all copies or substantial portions of the Software. 
 13  # 
 14  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 15  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 16  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 17  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 18  # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 19  # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 21  # DEALINGS IN THE SOFTWARE. 
 22   
 23  __all__ = ('BusConnection',) 
 24  __docformat__ = 'reStructuredText' 
 25   
 26  import logging 
 27  import weakref 
 28   
 29  from _dbus_bindings import ( 
 30      BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION, 
 31      BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING, 
 32      DBUS_START_REPLY_SUCCESS, NAME_FLAG_ALLOW_REPLACEMENT, 
 33      NAME_FLAG_DO_NOT_QUEUE, NAME_FLAG_REPLACE_EXISTING, 
 34      RELEASE_NAME_REPLY_NON_EXISTENT, RELEASE_NAME_REPLY_NOT_OWNER, 
 35      RELEASE_NAME_REPLY_RELEASED, REQUEST_NAME_REPLY_ALREADY_OWNER, 
 36      REQUEST_NAME_REPLY_EXISTS, REQUEST_NAME_REPLY_IN_QUEUE, 
 37      REQUEST_NAME_REPLY_PRIMARY_OWNER, validate_bus_name, validate_error_name, 
 38      validate_interface_name, validate_member_name, validate_object_path) 
 39  from dbus.connection import Connection 
 40  from dbus.exceptions import DBusException 
 41  from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED 
 42  from dbus._compat import is_py2 
 43   
 44   
 45  _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s'," 
 46                              "interface='%s',member='NameOwnerChanged'," 
 47                              "path='%s',arg0='%%s'" 
 48                              % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE, 
 49                                 BUS_DAEMON_PATH)) 
 50  """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange 
 51  messages""" 
 52   
 53  _NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner' 
 54   
 55  _logger = logging.getLogger('dbus.bus') 
 56   
 57   
58 -class NameOwnerWatch(object):
59 __slots__ = ('_match', '_pending_call') 60
61 - def __init__(self, bus_conn, bus_name, callback):
62 validate_bus_name(bus_name) 63 64 def signal_cb(owned, old_owner, new_owner): 65 callback(new_owner)
66 67 def error_cb(e): 68 if e.get_dbus_name() == _NAME_HAS_NO_OWNER: 69 callback('') 70 else: 71 logging.basicConfig() 72 _logger.debug('GetNameOwner(%s) failed:', bus_name, 73 exc_info=(e.__class__, e, None))
74 75 self._match = bus_conn.add_signal_receiver(signal_cb, 76 'NameOwnerChanged', 77 BUS_DAEMON_IFACE, 78 BUS_DAEMON_NAME, 79 BUS_DAEMON_PATH, 80 arg0=bus_name) 81 keywords = {} 82 if is_py2: 83 keywords['utf8_strings'] = True 84 self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME, 85 BUS_DAEMON_PATH, 86 BUS_DAEMON_IFACE, 87 'GetNameOwner', 88 's', (bus_name,), 89 callback, error_cb, 90 **keywords) 91
92 - def cancel(self):
93 if self._match is not None: 94 self._match.remove() 95 if self._pending_call is not None: 96 self._pending_call.cancel() 97 self._match = None 98 self._pending_call = None
99 100
101 -class BusConnection(Connection):
102 """A connection to a D-Bus daemon that implements the 103 ``org.freedesktop.DBus`` pseudo-service. 104 105 :Since: 0.81.0 106 """ 107 108 TYPE_SESSION = BUS_SESSION 109 """Represents a session bus (same as the global dbus.BUS_SESSION)""" 110 111 TYPE_SYSTEM = BUS_SYSTEM 112 """Represents the system bus (same as the global dbus.BUS_SYSTEM)""" 113 114 TYPE_STARTER = BUS_STARTER 115 """Represents the bus that started this service by activation (same as 116 the global dbus.BUS_STARTER)""" 117 118 START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS 119 START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING 120
121 - def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
122 bus = cls._new_for_bus(address_or_type, mainloop=mainloop) 123 124 # _bus_names is used by dbus.service.BusName! 125 bus._bus_names = weakref.WeakValueDictionary() 126 127 bus._signal_sender_matches = {} 128 """Map from SignalMatch to NameOwnerWatch.""" 129 130 return bus
131
132 - def add_signal_receiver(self, handler_function, signal_name=None, 133 dbus_interface=None, bus_name=None, 134 path=None, **keywords):
135 named_service = keywords.pop('named_service', None) 136 if named_service is not None: 137 if bus_name is not None: 138 raise TypeError('bus_name and named_service cannot both be ' 139 'specified') 140 bus_name = named_service 141 from warnings import warn 142 warn('Passing the named_service parameter to add_signal_receiver ' 143 'by name is deprecated: please use positional parameters', 144 DeprecationWarning, stacklevel=2) 145 146 match = super(BusConnection, self).add_signal_receiver( 147 handler_function, signal_name, dbus_interface, bus_name, 148 path, **keywords) 149 150 if (bus_name is not None and bus_name != BUS_DAEMON_NAME): 151 if bus_name[:1] == ':': 152 def callback(new_owner): 153 if new_owner == '': 154 match.remove()
155 else: 156 callback = match.set_sender_name_owner 157 watch = self.watch_name_owner(bus_name, callback) 158 self._signal_sender_matches[match] = watch 159 160 self.add_match_string(str(match)) 161 162 return match
163
164 - def _clean_up_signal_match(self, match):
165 # The signals lock is no longer held here (it was in <= 0.81.0) 166 self.remove_match_string_non_blocking(str(match)) 167 watch = self._signal_sender_matches.pop(match, None) 168 if watch is not None: 169 watch.cancel()
170
171 - def activate_name_owner(self, bus_name):
172 if (bus_name is not None and bus_name[:1] != ':' 173 and bus_name != BUS_DAEMON_NAME): 174 try: 175 return self.get_name_owner(bus_name) 176 except DBusException as e: 177 if e.get_dbus_name() != _NAME_HAS_NO_OWNER: 178 raise 179 # else it doesn't exist: try to start it 180 self.start_service_by_name(bus_name) 181 return self.get_name_owner(bus_name) 182 else: 183 # already unique 184 return bus_name
185
186 - def get_object(self, bus_name, object_path, introspect=True, 187 follow_name_owner_changes=False, **kwargs):
188 """Return a local proxy for the given remote object. 189 190 Method calls on the proxy are translated into method calls on the 191 remote object. 192 193 :Parameters: 194 `bus_name` : str 195 A bus name (either the unique name or a well-known name) 196 of the application owning the object. The keyword argument 197 named_service is a deprecated alias for this. 198 `object_path` : str 199 The object path of the desired object 200 `introspect` : bool 201 If true (default), attempt to introspect the remote 202 object to find out supported methods and their signatures 203 `follow_name_owner_changes` : bool 204 If the object path is a well-known name and this parameter 205 is false (default), resolve the well-known name to the unique 206 name of its current owner and bind to that instead; if the 207 ownership of the well-known name changes in future, 208 keep communicating with the original owner. 209 This is necessary if the D-Bus API used is stateful. 210 211 If the object path is a well-known name and this parameter 212 is true, whenever the well-known name changes ownership in 213 future, bind to the new owner, if any. 214 215 If the given object path is a unique name, this parameter 216 has no effect. 217 218 :Returns: a `dbus.proxies.ProxyObject` 219 :Raises `DBusException`: if resolving the well-known name to a 220 unique name fails 221 """ 222 if follow_name_owner_changes: 223 self._require_main_loop() # we don't get the signals otherwise 224 225 named_service = kwargs.pop('named_service', None) 226 if named_service is not None: 227 if bus_name is not None: 228 raise TypeError('bus_name and named_service cannot both ' 229 'be specified') 230 from warnings import warn 231 warn('Passing the named_service parameter to get_object by name ' 232 'is deprecated: please use positional parameters', 233 DeprecationWarning, stacklevel=2) 234 bus_name = named_service 235 if kwargs: 236 raise TypeError('get_object does not take these keyword ' 237 'arguments: %s' % ', '.join(kwargs.keys())) 238 239 return self.ProxyObjectClass(self, bus_name, object_path, 240 introspect=introspect, 241 follow_name_owner_changes=follow_name_owner_changes)
242
243 - def get_unix_user(self, bus_name):
244 """Get the numeric uid of the process owning the given bus name. 245 246 :Parameters: 247 `bus_name` : str 248 A bus name, either unique or well-known 249 :Returns: a `dbus.UInt32` 250 :Since: 0.80.0 251 """ 252 validate_bus_name(bus_name) 253 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 254 BUS_DAEMON_IFACE, 'GetConnectionUnixUser', 255 's', (bus_name,))
256
257 - def start_service_by_name(self, bus_name, flags=0):
258 """Start a service which will implement the given bus name on this Bus. 259 260 :Parameters: 261 `bus_name` : str 262 The well-known bus name to be activated. 263 `flags` : dbus.UInt32 264 Flags to pass to StartServiceByName (currently none are 265 defined) 266 267 :Returns: A tuple of 2 elements. The first is always True, the 268 second is either START_REPLY_SUCCESS or 269 START_REPLY_ALREADY_RUNNING. 270 271 :Raises `DBusException`: if the service could not be started. 272 :Since: 0.80.0 273 """ 274 validate_bus_name(bus_name) 275 return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 276 BUS_DAEMON_IFACE, 277 'StartServiceByName', 278 'su', (bus_name, flags)))
279 280 # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception, 281 # but this would not be backwards-compatible
282 - def request_name(self, name, flags=0):
283 """Request a bus name. 284 285 :Parameters: 286 `name` : str 287 The well-known name to be requested 288 `flags` : dbus.UInt32 289 A bitwise-OR of 0 or more of the flags 290 `NAME_FLAG_ALLOW_REPLACEMENT`, 291 `NAME_FLAG_REPLACE_EXISTING` 292 and `NAME_FLAG_DO_NOT_QUEUE` 293 :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`, 294 `REQUEST_NAME_REPLY_IN_QUEUE`, 295 `REQUEST_NAME_REPLY_EXISTS` or 296 `REQUEST_NAME_REPLY_ALREADY_OWNER` 297 :Raises `DBusException`: if the bus daemon cannot be contacted or 298 returns an error. 299 """ 300 validate_bus_name(name, allow_unique=False) 301 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 302 BUS_DAEMON_IFACE, 'RequestName', 303 'su', (name, flags))
304
305 - def release_name(self, name):
306 """Release a bus name. 307 308 :Parameters: 309 `name` : str 310 The well-known name to be released 311 :Returns: `RELEASE_NAME_REPLY_RELEASED`, 312 `RELEASE_NAME_REPLY_NON_EXISTENT` 313 or `RELEASE_NAME_REPLY_NOT_OWNER` 314 :Raises `DBusException`: if the bus daemon cannot be contacted or 315 returns an error. 316 """ 317 validate_bus_name(name, allow_unique=False) 318 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 319 BUS_DAEMON_IFACE, 'ReleaseName', 320 's', (name,))
321
322 - def list_names(self):
323 """Return a list of all currently-owned names on the bus. 324 325 :Returns: a dbus.Array of dbus.UTF8String 326 :Since: 0.81.0 327 """ 328 keywords = {} 329 if is_py2: 330 keywords['utf8_strings'] = True 331 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 332 BUS_DAEMON_IFACE, 'ListNames', 333 '', (), **keywords)
334
335 - def list_activatable_names(self):
336 """Return a list of all names that can be activated on the bus. 337 338 :Returns: a dbus.Array of dbus.UTF8String 339 :Since: 0.81.0 340 """ 341 keywords = {} 342 if is_py2: 343 keywords['utf8_strings'] = True 344 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 345 BUS_DAEMON_IFACE, 'ListActivatableNames', 346 '', (), **keywords)
347
348 - def get_name_owner(self, bus_name):
349 """Return the unique connection name of the primary owner of the 350 given name. 351 352 :Raises `DBusException`: if the `bus_name` has no owner 353 :Since: 0.81.0 354 """ 355 keywords = {} 356 if is_py2: 357 keywords['utf8_strings'] = True 358 validate_bus_name(bus_name, allow_unique=False) 359 return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 360 BUS_DAEMON_IFACE, 'GetNameOwner', 361 's', (bus_name,), **keywords)
362
363 - def watch_name_owner(self, bus_name, callback):
364 """Watch the unique connection name of the primary owner of the 365 given name. 366 367 `callback` will be called with one argument, which is either the 368 unique connection name, or the empty string (meaning the name is 369 not owned). 370 371 :Since: 0.81.0 372 """ 373 return NameOwnerWatch(self, bus_name, callback)
374
375 - def name_has_owner(self, bus_name):
376 """Return True iff the given bus name has an owner on this bus. 377 378 :Parameters: 379 `bus_name` : str 380 The bus name to look up 381 :Returns: a `bool` 382 """ 383 return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 384 BUS_DAEMON_IFACE, 'NameHasOwner', 385 's', (bus_name,)))
386
387 - def add_match_string(self, rule):
388 """Arrange for this application to receive messages on the bus that 389 match the given rule. This version will block. 390 391 :Parameters: 392 `rule` : str 393 The match rule 394 :Raises `DBusException`: on error. 395 :Since: 0.80.0 396 """ 397 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 398 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
399 400 # FIXME: add an async success/error handler capability? 401 # (and the same for remove_...)
402 - def add_match_string_non_blocking(self, rule):
403 """Arrange for this application to receive messages on the bus that 404 match the given rule. This version will not block, but any errors 405 will be ignored. 406 407 408 :Parameters: 409 `rule` : str 410 The match rule 411 :Raises `DBusException`: on error. 412 :Since: 0.80.0 413 """ 414 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 415 BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,), 416 None, None)
417
418 - def remove_match_string(self, rule):
419 """Arrange for this application to receive messages on the bus that 420 match the given rule. This version will block. 421 422 :Parameters: 423 `rule` : str 424 The match rule 425 :Raises `DBusException`: on error. 426 :Since: 0.80.0 427 """ 428 self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 429 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
430
431 - def remove_match_string_non_blocking(self, rule):
432 """Arrange for this application to receive messages on the bus that 433 match the given rule. This version will not block, but any errors 434 will be ignored. 435 436 437 :Parameters: 438 `rule` : str 439 The match rule 440 :Raises `DBusException`: on error. 441 :Since: 0.80.0 442 """ 443 self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH, 444 BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,), 445 None, None)
446