bus.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. __all__ = ('BusConnection',)
  23. __docformat__ = 'reStructuredText'
  24. import logging
  25. import weakref
  26. from _dbus_bindings import (
  27. BUS_DAEMON_IFACE, BUS_DAEMON_NAME, BUS_DAEMON_PATH, BUS_SESSION,
  28. BUS_STARTER, BUS_SYSTEM, DBUS_START_REPLY_ALREADY_RUNNING,
  29. DBUS_START_REPLY_SUCCESS, NAME_FLAG_ALLOW_REPLACEMENT,
  30. NAME_FLAG_DO_NOT_QUEUE, NAME_FLAG_REPLACE_EXISTING,
  31. RELEASE_NAME_REPLY_NON_EXISTENT, RELEASE_NAME_REPLY_NOT_OWNER,
  32. RELEASE_NAME_REPLY_RELEASED, REQUEST_NAME_REPLY_ALREADY_OWNER,
  33. REQUEST_NAME_REPLY_EXISTS, REQUEST_NAME_REPLY_IN_QUEUE,
  34. REQUEST_NAME_REPLY_PRIMARY_OWNER, validate_bus_name, validate_error_name,
  35. validate_interface_name, validate_member_name, validate_object_path)
  36. from dbus.connection import Connection
  37. from dbus.exceptions import DBusException
  38. from dbus.lowlevel import HANDLER_RESULT_NOT_YET_HANDLED
  39. from dbus._compat import is_py2
  40. _NAME_OWNER_CHANGE_MATCH = ("type='signal',sender='%s',"
  41. "interface='%s',member='NameOwnerChanged',"
  42. "path='%s',arg0='%%s'"
  43. % (BUS_DAEMON_NAME, BUS_DAEMON_IFACE,
  44. BUS_DAEMON_PATH))
  45. """(_NAME_OWNER_CHANGE_MATCH % sender) matches relevant NameOwnerChange
  46. messages"""
  47. _NAME_HAS_NO_OWNER = 'org.freedesktop.DBus.Error.NameHasNoOwner'
  48. _logger = logging.getLogger('dbus.bus')
  49. class NameOwnerWatch(object):
  50. __slots__ = ('_match', '_pending_call')
  51. def __init__(self, bus_conn, bus_name, callback):
  52. validate_bus_name(bus_name)
  53. def signal_cb(owned, old_owner, new_owner):
  54. callback(new_owner)
  55. def error_cb(e):
  56. if e.get_dbus_name() == _NAME_HAS_NO_OWNER:
  57. callback('')
  58. else:
  59. logging.basicConfig()
  60. _logger.debug('GetNameOwner(%s) failed:', bus_name,
  61. exc_info=(e.__class__, e, None))
  62. self._match = bus_conn.add_signal_receiver(signal_cb,
  63. 'NameOwnerChanged',
  64. BUS_DAEMON_IFACE,
  65. BUS_DAEMON_NAME,
  66. BUS_DAEMON_PATH,
  67. arg0=bus_name)
  68. keywords = {}
  69. if is_py2:
  70. keywords['utf8_strings'] = True
  71. self._pending_call = bus_conn.call_async(BUS_DAEMON_NAME,
  72. BUS_DAEMON_PATH,
  73. BUS_DAEMON_IFACE,
  74. 'GetNameOwner',
  75. 's', (bus_name,),
  76. callback, error_cb,
  77. **keywords)
  78. def cancel(self):
  79. if self._match is not None:
  80. self._match.remove()
  81. if self._pending_call is not None:
  82. self._pending_call.cancel()
  83. self._match = None
  84. self._pending_call = None
  85. class BusConnection(Connection):
  86. """A connection to a D-Bus daemon that implements the
  87. ``org.freedesktop.DBus`` pseudo-service.
  88. :Since: 0.81.0
  89. """
  90. TYPE_SESSION = BUS_SESSION
  91. """Represents a session bus (same as the global dbus.BUS_SESSION)"""
  92. TYPE_SYSTEM = BUS_SYSTEM
  93. """Represents the system bus (same as the global dbus.BUS_SYSTEM)"""
  94. TYPE_STARTER = BUS_STARTER
  95. """Represents the bus that started this service by activation (same as
  96. the global dbus.BUS_STARTER)"""
  97. START_REPLY_SUCCESS = DBUS_START_REPLY_SUCCESS
  98. START_REPLY_ALREADY_RUNNING = DBUS_START_REPLY_ALREADY_RUNNING
  99. def __new__(cls, address_or_type=TYPE_SESSION, mainloop=None):
  100. bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
  101. # _bus_names is used by dbus.service.BusName!
  102. bus._bus_names = weakref.WeakValueDictionary()
  103. bus._signal_sender_matches = {}
  104. """Map from SignalMatch to NameOwnerWatch."""
  105. return bus
  106. def add_signal_receiver(self, handler_function, signal_name=None,
  107. dbus_interface=None, bus_name=None,
  108. path=None, **keywords):
  109. named_service = keywords.pop('named_service', None)
  110. if named_service is not None:
  111. if bus_name is not None:
  112. raise TypeError('bus_name and named_service cannot both be '
  113. 'specified')
  114. bus_name = named_service
  115. from warnings import warn
  116. warn('Passing the named_service parameter to add_signal_receiver '
  117. 'by name is deprecated: please use positional parameters',
  118. DeprecationWarning, stacklevel=2)
  119. match = super(BusConnection, self).add_signal_receiver(
  120. handler_function, signal_name, dbus_interface, bus_name,
  121. path, **keywords)
  122. if (bus_name is not None and bus_name != BUS_DAEMON_NAME):
  123. if bus_name[:1] == ':':
  124. def callback(new_owner):
  125. if new_owner == '':
  126. match.remove()
  127. else:
  128. callback = match.set_sender_name_owner
  129. watch = self.watch_name_owner(bus_name, callback)
  130. self._signal_sender_matches[match] = watch
  131. self.add_match_string(str(match))
  132. return match
  133. def _clean_up_signal_match(self, match):
  134. # The signals lock is no longer held here (it was in <= 0.81.0)
  135. self.remove_match_string_non_blocking(str(match))
  136. watch = self._signal_sender_matches.pop(match, None)
  137. if watch is not None:
  138. watch.cancel()
  139. def activate_name_owner(self, bus_name):
  140. if (bus_name is not None and bus_name[:1] != ':'
  141. and bus_name != BUS_DAEMON_NAME):
  142. try:
  143. return self.get_name_owner(bus_name)
  144. except DBusException as e:
  145. if e.get_dbus_name() != _NAME_HAS_NO_OWNER:
  146. raise
  147. # else it doesn't exist: try to start it
  148. self.start_service_by_name(bus_name)
  149. return self.get_name_owner(bus_name)
  150. else:
  151. # already unique
  152. return bus_name
  153. def get_object(self, bus_name, object_path, introspect=True,
  154. follow_name_owner_changes=False, **kwargs):
  155. """Return a local proxy for the given remote object.
  156. Method calls on the proxy are translated into method calls on the
  157. remote object.
  158. :Parameters:
  159. `bus_name` : str
  160. A bus name (either the unique name or a well-known name)
  161. of the application owning the object. The keyword argument
  162. named_service is a deprecated alias for this.
  163. `object_path` : str
  164. The object path of the desired object
  165. `introspect` : bool
  166. If true (default), attempt to introspect the remote
  167. object to find out supported methods and their signatures
  168. `follow_name_owner_changes` : bool
  169. If the object path is a well-known name and this parameter
  170. is false (default), resolve the well-known name to the unique
  171. name of its current owner and bind to that instead; if the
  172. ownership of the well-known name changes in future,
  173. keep communicating with the original owner.
  174. This is necessary if the D-Bus API used is stateful.
  175. If the object path is a well-known name and this parameter
  176. is true, whenever the well-known name changes ownership in
  177. future, bind to the new owner, if any.
  178. If the given object path is a unique name, this parameter
  179. has no effect.
  180. :Returns: a `dbus.proxies.ProxyObject`
  181. :Raises `DBusException`: if resolving the well-known name to a
  182. unique name fails
  183. """
  184. if follow_name_owner_changes:
  185. self._require_main_loop() # we don't get the signals otherwise
  186. named_service = kwargs.pop('named_service', None)
  187. if named_service is not None:
  188. if bus_name is not None:
  189. raise TypeError('bus_name and named_service cannot both '
  190. 'be specified')
  191. from warnings import warn
  192. warn('Passing the named_service parameter to get_object by name '
  193. 'is deprecated: please use positional parameters',
  194. DeprecationWarning, stacklevel=2)
  195. bus_name = named_service
  196. if kwargs:
  197. raise TypeError('get_object does not take these keyword '
  198. 'arguments: %s' % ', '.join(kwargs.keys()))
  199. return self.ProxyObjectClass(self, bus_name, object_path,
  200. introspect=introspect,
  201. follow_name_owner_changes=follow_name_owner_changes)
  202. def get_unix_user(self, bus_name):
  203. """Get the numeric uid of the process owning the given bus name.
  204. :Parameters:
  205. `bus_name` : str
  206. A bus name, either unique or well-known
  207. :Returns: a `dbus.UInt32`
  208. :Since: 0.80.0
  209. """
  210. validate_bus_name(bus_name)
  211. return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  212. BUS_DAEMON_IFACE, 'GetConnectionUnixUser',
  213. 's', (bus_name,))
  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.
  216. :Parameters:
  217. `bus_name` : str
  218. The well-known bus name to be activated.
  219. `flags` : dbus.UInt32
  220. Flags to pass to StartServiceByName (currently none are
  221. defined)
  222. :Returns: A tuple of 2 elements. The first is always True, the
  223. second is either START_REPLY_SUCCESS or
  224. START_REPLY_ALREADY_RUNNING.
  225. :Raises `DBusException`: if the service could not be started.
  226. :Since: 0.80.0
  227. """
  228. validate_bus_name(bus_name)
  229. return (True, self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  230. BUS_DAEMON_IFACE,
  231. 'StartServiceByName',
  232. 'su', (bus_name, flags)))
  233. # XXX: it might be nice to signal IN_QUEUE, EXISTS by exception,
  234. # but this would not be backwards-compatible
  235. def request_name(self, name, flags=0):
  236. """Request a bus name.
  237. :Parameters:
  238. `name` : str
  239. The well-known name to be requested
  240. `flags` : dbus.UInt32
  241. A bitwise-OR of 0 or more of the flags
  242. `NAME_FLAG_ALLOW_REPLACEMENT`,
  243. `NAME_FLAG_REPLACE_EXISTING`
  244. and `NAME_FLAG_DO_NOT_QUEUE`
  245. :Returns: `REQUEST_NAME_REPLY_PRIMARY_OWNER`,
  246. `REQUEST_NAME_REPLY_IN_QUEUE`,
  247. `REQUEST_NAME_REPLY_EXISTS` or
  248. `REQUEST_NAME_REPLY_ALREADY_OWNER`
  249. :Raises `DBusException`: if the bus daemon cannot be contacted or
  250. returns an error.
  251. """
  252. validate_bus_name(name, allow_unique=False)
  253. return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  254. BUS_DAEMON_IFACE, 'RequestName',
  255. 'su', (name, flags))
  256. def release_name(self, name):
  257. """Release a bus name.
  258. :Parameters:
  259. `name` : str
  260. The well-known name to be released
  261. :Returns: `RELEASE_NAME_REPLY_RELEASED`,
  262. `RELEASE_NAME_REPLY_NON_EXISTENT`
  263. or `RELEASE_NAME_REPLY_NOT_OWNER`
  264. :Raises `DBusException`: if the bus daemon cannot be contacted or
  265. returns an error.
  266. """
  267. validate_bus_name(name, allow_unique=False)
  268. return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  269. BUS_DAEMON_IFACE, 'ReleaseName',
  270. 's', (name,))
  271. def list_names(self):
  272. """Return a list of all currently-owned names on the bus.
  273. :Returns: a dbus.Array of dbus.UTF8String
  274. :Since: 0.81.0
  275. """
  276. keywords = {}
  277. if is_py2:
  278. keywords['utf8_strings'] = True
  279. return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  280. BUS_DAEMON_IFACE, 'ListNames',
  281. '', (), **keywords)
  282. def list_activatable_names(self):
  283. """Return a list of all names that can be activated on the bus.
  284. :Returns: a dbus.Array of dbus.UTF8String
  285. :Since: 0.81.0
  286. """
  287. keywords = {}
  288. if is_py2:
  289. keywords['utf8_strings'] = True
  290. return self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  291. BUS_DAEMON_IFACE, 'ListActivatableNames',
  292. '', (), **keywords)
  293. def get_name_owner(self, bus_name):
  294. """Return the unique connection name of the primary owner of the
  295. given name.
  296. :Raises `DBusException`: if the `bus_name` has no owner
  297. :Since: 0.81.0
  298. """
  299. keywords = {}
  300. if is_py2:
  301. keywords['utf8_strings'] = True
  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,), **keywords)
  306. def watch_name_owner(self, bus_name, callback):
  307. """Watch the unique connection name of the primary owner of the
  308. given name.
  309. `callback` will be called with one argument, which is either the
  310. unique connection name, or the empty string (meaning the name is
  311. not owned).
  312. :Since: 0.81.0
  313. """
  314. return NameOwnerWatch(self, bus_name, callback)
  315. def name_has_owner(self, bus_name):
  316. """Return True iff the given bus name has an owner on this bus.
  317. :Parameters:
  318. `bus_name` : str
  319. The bus name to look up
  320. :Returns: a `bool`
  321. """
  322. return bool(self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  323. BUS_DAEMON_IFACE, 'NameHasOwner',
  324. 's', (bus_name,)))
  325. def add_match_string(self, rule):
  326. """Arrange for this application to receive messages on the bus that
  327. match the given rule. This version will block.
  328. :Parameters:
  329. `rule` : str
  330. The match rule
  331. :Raises `DBusException`: on error.
  332. :Since: 0.80.0
  333. """
  334. self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  335. BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,))
  336. # FIXME: add an async success/error handler capability?
  337. # (and the same for remove_...)
  338. def add_match_string_non_blocking(self, rule):
  339. """Arrange for this application to receive messages on the bus that
  340. match the given rule. This version will not block, but any errors
  341. will be ignored.
  342. :Parameters:
  343. `rule` : str
  344. The match rule
  345. :Raises `DBusException`: on error.
  346. :Since: 0.80.0
  347. """
  348. self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  349. BUS_DAEMON_IFACE, 'AddMatch', 's', (rule,),
  350. None, None)
  351. def remove_match_string(self, rule):
  352. """Arrange for this application to receive messages on the bus that
  353. match the given rule. This version will block.
  354. :Parameters:
  355. `rule` : str
  356. The match rule
  357. :Raises `DBusException`: on error.
  358. :Since: 0.80.0
  359. """
  360. self.call_blocking(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  361. BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,))
  362. def remove_match_string_non_blocking(self, rule):
  363. """Arrange for this application to receive messages on the bus that
  364. match the given rule. This version will not block, but any errors
  365. will be ignored.
  366. :Parameters:
  367. `rule` : str
  368. The match rule
  369. :Raises `DBusException`: on error.
  370. :Since: 0.80.0
  371. """
  372. self.call_async(BUS_DAEMON_NAME, BUS_DAEMON_PATH,
  373. BUS_DAEMON_IFACE, 'RemoveMatch', 's', (rule,),
  374. None, None)