comparison libervia/backend/plugins/plugin_adhoc_dbus.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents 50c919dfe61b
children
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
35 35
36 try: 36 try:
37 from lxml import etree 37 from lxml import etree
38 except ImportError: 38 except ImportError:
39 etree = None 39 etree = None
40 log.warning("Missing module lxml, please download/install it from http://lxml.de/ ." 40 log.warning(
41 "Auto D-Bus discovery will be disabled") 41 "Missing module lxml, please download/install it from http://lxml.de/ ."
42 "Auto D-Bus discovery will be disabled"
43 )
42 44
43 try: 45 try:
44 import txdbus 46 import txdbus
45 from txdbus import client as dbus_client 47 from txdbus import client as dbus_client
46 except ImportError: 48 except ImportError:
57 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" 59 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable"
58 MPRIS_PREFIX = "org.mpris.MediaPlayer2" 60 MPRIS_PREFIX = "org.mpris.MediaPlayer2"
59 CMD_GO_BACK = "GoBack" 61 CMD_GO_BACK = "GoBack"
60 CMD_GO_FWD = "GoFW" 62 CMD_GO_FWD = "GoFW"
61 SEEK_OFFSET = 5 * 1000 * 1000 63 SEEK_OFFSET = 5 * 1000 * 1000
62 MPRIS_COMMANDS = ["org.mpris.MediaPlayer2.Player." + cmd for cmd in ( 64 MPRIS_COMMANDS = [
63 "Previous", CMD_GO_BACK, "PlayPause", CMD_GO_FWD, "Next")] 65 "org.mpris.MediaPlayer2.Player." + cmd
66 for cmd in ("Previous", CMD_GO_BACK, "PlayPause", CMD_GO_FWD, "Next")
67 ]
64 MPRIS_PATH = "/org/mpris/MediaPlayer2" 68 MPRIS_PATH = "/org/mpris/MediaPlayer2"
65 MPRIS_PROPERTIES = OrderedDict(( 69 MPRIS_PROPERTIES = OrderedDict(
66 ("org.mpris.MediaPlayer2", ( 70 (
67 "Identity", 71 ("org.mpris.MediaPlayer2", ("Identity",)),
68 )), 72 (
69 ("org.mpris.MediaPlayer2.Player", ( 73 "org.mpris.MediaPlayer2.Player",
70 "Metadata", 74 (
71 "PlaybackStatus", 75 "Metadata",
72 "Volume", 76 "PlaybackStatus",
73 )), 77 "Volume",
74 )) 78 ),
79 ),
80 )
81 )
75 MPRIS_METADATA_KEY = "Metadata" 82 MPRIS_METADATA_KEY = "Metadata"
76 MPRIS_METADATA_MAP = OrderedDict(( 83 MPRIS_METADATA_MAP = OrderedDict((("xesam:title", "Title"),))
77 ("xesam:title", "Title"),
78 ))
79 84
80 INTROSPECT_METHOD = "Introspect" 85 INTROSPECT_METHOD = "Introspect"
81 IGNORED_IFACES_START = ( 86 IGNORED_IFACES_START = (
82 "org.freedesktop", 87 "org.freedesktop",
83 "org.qtproject", 88 "org.qtproject",
124 self.session_con = None 129 self.session_con = None
125 130
126 async def profile_connected(self, client): 131 async def profile_connected(self, client):
127 if txdbus is not None: 132 if txdbus is not None:
128 if self.session_con is None: 133 if self.session_con is None:
129 self.session_con = await dbus_client.connect(reactor, 'session') 134 self.session_con = await dbus_client.connect(reactor, "session")
130 self.fd_object = await self.session_con.getRemoteObject(FD_NAME, FD_PATH) 135 self.fd_object = await self.session_con.getRemoteObject(FD_NAME, FD_PATH)
131 136
132 self._c.add_ad_hoc_command( 137 self._c.add_ad_hoc_command(
133 client, self.local_media_cb, D_("Media Players"), 138 client,
139 self.local_media_cb,
140 D_("Media Players"),
134 node=NS_MEDIA_PLAYER, 141 node=NS_MEDIA_PLAYER,
135 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote 142 timeout=60 * 60 * 6, # 6 hours timeout, to avoid breaking remote
136 # in the middle of a movie 143 # in the middle of a movie
137 ) 144 )
138 145
139 async def _dbus_async_call(self, proxy, method, *args, **kwargs): 146 async def _dbus_async_call(self, proxy, method, *args, **kwargs):
140 """ Call a DBus method asynchronously and return a deferred 147 """Call a DBus method asynchronously and return a deferred
141 148
142 @param proxy: DBus object proxy, as returner by get_object 149 @param proxy: DBus object proxy, as returner by get_object
143 @param method: name of the method to call 150 @param method: name of the method to call
144 @param args: will be transmitted to the method 151 @param args: will be transmitted to the method
145 @param kwargs: will be transmetted to the method, except for the following poped 152 @param kwargs: will be transmetted to the method, except for the following poped
150 """ 157 """
151 return await proxy.callRemote(method, *args, **kwargs) 158 return await proxy.callRemote(method, *args, **kwargs)
152 159
153 async def _dbus_get_property(self, proxy, interface, name): 160 async def _dbus_get_property(self, proxy, interface, name):
154 return await self._dbus_async_call( 161 return await self._dbus_async_call(
155 proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties") 162 proxy, "Get", interface, name, interface="org.freedesktop.DBus.Properties"
156 163 )
157 164
158 async def _dbus_list_names(self): 165 async def _dbus_list_names(self):
159 return await self.fd_object.callRemote("ListNames") 166 return await self.fd_object.callRemote("ListNames")
160 167
161 async def _dbus_introspect(self, proxy): 168 async def _dbus_introspect(self, proxy):
162 return await self._dbus_async_call(proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE) 169 return await self._dbus_async_call(
170 proxy, INTROSPECT_METHOD, interface=INTROSPECT_IFACE
171 )
163 172
164 def _accept_method(self, method): 173 def _accept_method(self, method):
165 """ Return True if we accept the method for a command 174 """Return True if we accept the method for a command
166 @param method: etree.Element 175 @param method: etree.Element
167 @return: True if the method is acceptable 176 @return: True if the method is acceptable
168 177
169 """ 178 """
170 if method.xpath( 179 if method.xpath(
179 introspect_xml = await self._dbus_introspect(proxy) 188 introspect_xml = await self._dbus_introspect(proxy)
180 el = etree.fromstring(introspect_xml) 189 el = etree.fromstring(introspect_xml)
181 for node in el.iterchildren("node", "interface"): 190 for node in el.iterchildren("node", "interface"):
182 if node.tag == "node": 191 if node.tag == "node":
183 new_path = os.path.join(proxy.object_path, node.get("name")) 192 new_path = os.path.join(proxy.object_path, node.get("name"))
184 new_proxy = await self.session_con.getRemoteObject( 193 new_proxy = await self.session_con.getRemoteObject(bus_name, new_path)
185 bus_name, new_path
186 )
187 await self._introspect(methods, bus_name, new_proxy) 194 await self._introspect(methods, bus_name, new_proxy)
188 elif node.tag == "interface": 195 elif node.tag == "interface":
189 name = node.get("name") 196 name = node.get("name")
190 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START): 197 if any(name.startswith(ignored) for ignored in IGNORED_IFACES_START):
191 log.debug("interface [%s] is ignored" % name) 198 log.debug("interface [%s] is ignored" % name)
195 if self._accept_method(method): 202 if self._accept_method(method):
196 method_name = method.get("name") 203 method_name = method.get("name")
197 log.debug("method accepted: [%s]" % method_name) 204 log.debug("method accepted: [%s]" % method_name)
198 methods.add((proxy.object_path, name, method_name)) 205 methods.add((proxy.object_path, name, method_name))
199 206
200 def _ad_hoc_dbus_add_auto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, 207 def _ad_hoc_dbus_add_auto(
201 forbidden_jids, forbidden_groups, flags, profile_key): 208 self,
209 prog_name,
210 allowed_jids,
211 allowed_groups,
212 allowed_magics,
213 forbidden_jids,
214 forbidden_groups,
215 flags,
216 profile_key,
217 ):
202 client = self.host.get_client(profile_key) 218 client = self.host.get_client(profile_key)
203 return self.ad_hoc_dbus_add_auto( 219 return self.ad_hoc_dbus_add_auto(
204 client, prog_name, allowed_jids, allowed_groups, allowed_magics, 220 client,
205 forbidden_jids, forbidden_groups, flags) 221 prog_name,
206 222 allowed_jids,
207 async def ad_hoc_dbus_add_auto(self, client, prog_name, allowed_jids=None, allowed_groups=None, 223 allowed_groups,
208 allowed_magics=None, forbidden_jids=None, forbidden_groups=None, 224 allowed_magics,
209 flags=None): 225 forbidden_jids,
226 forbidden_groups,
227 flags,
228 )
229
230 async def ad_hoc_dbus_add_auto(
231 self,
232 client,
233 prog_name,
234 allowed_jids=None,
235 allowed_groups=None,
236 allowed_magics=None,
237 forbidden_jids=None,
238 forbidden_groups=None,
239 flags=None,
240 ):
210 bus_names = await self._dbus_list_names() 241 bus_names = await self._dbus_list_names()
211 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] 242 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name]
212 if not bus_names: 243 if not bus_names:
213 log.info("Can't find any bus for [%s]" % prog_name) 244 log.info("Can't find any bus for [%s]" % prog_name)
214 return ("", []) 245 return ("", [])
239 flags=flags, 270 flags=flags,
240 ) 271 )
241 272
242 return (str(bus_name), methods) 273 return (str(bus_name), methods)
243 274
244 def _add_command(self, client, adhoc_name, bus_name, methods, allowed_jids=None, 275 def _add_command(
245 allowed_groups=None, allowed_magics=None, forbidden_jids=None, 276 self,
246 forbidden_groups=None, flags=None): 277 client,
278 adhoc_name,
279 bus_name,
280 methods,
281 allowed_jids=None,
282 allowed_groups=None,
283 allowed_magics=None,
284 forbidden_jids=None,
285 forbidden_groups=None,
286 flags=None,
287 ):
247 if flags is None: 288 if flags is None:
248 flags = set() 289 flags = set()
249 290
250 async def d_bus_callback(client, command_elt, session_data, action, node): 291 async def d_bus_callback(client, command_elt, session_data, action, node):
251 actions = session_data.setdefault("actions", []) 292 actions = session_data.setdefault("actions", [])
329 @return (list[tuple[unicode, unicode, unicode]]): list of devices with: 370 @return (list[tuple[unicode, unicode, unicode]]): list of devices with:
330 - entity full jid 371 - entity full jid
331 - device name 372 - device name
332 - device label 373 - device label
333 """ 374 """
334 found_data = await defer.ensureDeferred(self.host.find_by_features( 375 found_data = await defer.ensureDeferred(
335 client, [self.host.ns_map['commands']], service=False, roster=False, 376 self.host.find_by_features(
336 own_jid=True, local_device=True)) 377 client,
378 [self.host.ns_map["commands"]],
379 service=False,
380 roster=False,
381 own_jid=True,
382 local_device=True,
383 )
384 )
337 385
338 remotes = [] 386 remotes = []
339 387
340 for found in found_data: 388 for found in found_data:
341 for device_jid_s in found: 389 for device_jid_s in found:
342 device_jid = jid.JID(device_jid_s) 390 device_jid = jid.JID(device_jid_s)
343 cmd_list = await self._c.list_commands(client, device_jid) 391 cmd_list = await self._c.list_commands(client, device_jid)
344 for cmd in cmd_list: 392 for cmd in cmd_list:
345 if cmd.nodeIdentifier == NS_MEDIA_PLAYER: 393 if cmd.nodeIdentifier == NS_MEDIA_PLAYER:
346 try: 394 try:
347 result_elt = await self._c.do(client, device_jid, 395 result_elt = await self._c.do(
348 NS_MEDIA_PLAYER, timeout=5) 396 client, device_jid, NS_MEDIA_PLAYER, timeout=5
397 )
349 command_elt = self._c.get_command_elt(result_elt) 398 command_elt = self._c.get_command_elt(result_elt)
350 form = data_form.findForm(command_elt, NS_MEDIA_PLAYER) 399 form = data_form.findForm(command_elt, NS_MEDIA_PLAYER)
351 if form is None: 400 if form is None:
352 continue 401 continue
353 mp_options = form.fields['media_player'].options 402 mp_options = form.fields["media_player"].options
354 session_id = command_elt.getAttribute('sessionid') 403 session_id = command_elt.getAttribute("sessionid")
355 if mp_options and session_id: 404 if mp_options and session_id:
356 # we just want to discover player, so we cancel the 405 # we just want to discover player, so we cancel the
357 # session 406 # session
358 self._c.do(client, device_jid, NS_MEDIA_PLAYER, 407 self._c.do(
359 action=self._c.ACTION.CANCEL, 408 client,
360 session_id=session_id) 409 device_jid,
410 NS_MEDIA_PLAYER,
411 action=self._c.ACTION.CANCEL,
412 session_id=session_id,
413 )
361 414
362 for opt in mp_options: 415 for opt in mp_options:
363 remotes.append((device_jid_s, 416 remotes.append(
364 opt.value, 417 (device_jid_s, opt.value, opt.label or opt.value)
365 opt.label or opt.value)) 418 )
366 except Exception as e: 419 except Exception as e:
367 log.warning(_( 420 log.warning(
368 "Can't retrieve remote controllers on {device_jid}: " 421 _(
369 "{reason}".format(device_jid=device_jid, reason=e))) 422 "Can't retrieve remote controllers on {device_jid}: "
423 "{reason}".format(device_jid=device_jid, reason=e)
424 )
425 )
370 break 426 break
371 return remotes 427 return remotes
372 428
373 async def do_mpris_command(self, proxy, command): 429 async def do_mpris_command(self, proxy, command):
374 iface, command = command.rsplit(".", 1) 430 iface, command = command.rsplit(".", 1)
375 if command == CMD_GO_BACK: 431 if command == CMD_GO_BACK:
376 command = 'Seek' 432 command = "Seek"
377 args = [-SEEK_OFFSET] 433 args = [-SEEK_OFFSET]
378 elif command == CMD_GO_FWD: 434 elif command == CMD_GO_FWD:
379 command = 'Seek' 435 command = "Seek"
380 args = [SEEK_OFFSET] 436 args = [SEEK_OFFSET]
381 else: 437 else:
382 args = [] 438 args = []
383 return await self._dbus_async_call(proxy, command, *args, interface=iface) 439 return await self._dbus_async_call(proxy, command, *args, interface=iface)
384 440
385 def add_mpris_metadata(self, form, metadata): 441 def add_mpris_metadata(self, form, metadata):
386 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP""" 442 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP"""
387 for mpris_key, name in MPRIS_METADATA_MAP.items(): 443 for mpris_key, name in MPRIS_METADATA_MAP.items():
388 if mpris_key in metadata: 444 if mpris_key in metadata:
389 value = str(metadata[mpris_key]) 445 value = str(metadata[mpris_key])
390 form.addField(data_form.Field(fieldType="fixed", 446 form.addField(data_form.Field(fieldType="fixed", var=name, value=value))
391 var=name,
392 value=value))
393 447
394 async def local_media_cb(self, client, command_elt, session_data, action, node): 448 async def local_media_cb(self, client, command_elt, session_data, action, node):
395 assert txdbus is not None 449 assert txdbus is not None
396 try: 450 try:
397 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x")) 451 x_elt = next(command_elt.elements(data_form.NS_X_DATA, "x"))
406 if len(bus_names) == 0: 460 if len(bus_names) == 0:
407 note = (self._c.NOTE.INFO, D_("No media player found.")) 461 note = (self._c.NOTE.INFO, D_("No media player found."))
408 return (None, self._c.STATUS.COMPLETED, None, note) 462 return (None, self._c.STATUS.COMPLETED, None, note)
409 options = [] 463 options = []
410 status = self._c.STATUS.EXECUTING 464 status = self._c.STATUS.EXECUTING
411 form = data_form.Form("form", title=D_("Media Player Selection"), 465 form = data_form.Form(
412 formNamespace=NS_MEDIA_PLAYER) 466 "form", title=D_("Media Player Selection"), formNamespace=NS_MEDIA_PLAYER
467 )
413 for bus in bus_names: 468 for bus in bus_names:
414 player_name = bus[len(MPRIS_PREFIX)+1:] 469 player_name = bus[len(MPRIS_PREFIX) + 1 :]
415 if not player_name: 470 if not player_name:
416 log.warning(_("Ignoring MPRIS bus without suffix")) 471 log.warning(_("Ignoring MPRIS bus without suffix"))
417 continue 472 continue
418 options.append(data_form.Option(bus, player_name)) 473 options.append(data_form.Option(bus, player_name))
419 field = data_form.Field( 474 field = data_form.Field(
428 bus_name = command_form["media_player"] 483 bus_name = command_form["media_player"]
429 except KeyError: 484 except KeyError:
430 raise ValueError(_("missing media_player value")) 485 raise ValueError(_("missing media_player value"))
431 486
432 if not bus_name.startswith(MPRIS_PREFIX): 487 if not bus_name.startswith(MPRIS_PREFIX):
433 log.warning(_("Media player ad-hoc command trying to use non MPRIS bus. " 488 log.warning(
434 "Hack attempt? Refused bus: {bus_name}").format( 489 _(
435 bus_name=bus_name)) 490 "Media player ad-hoc command trying to use non MPRIS bus. "
491 "Hack attempt? Refused bus: {bus_name}"
492 ).format(bus_name=bus_name)
493 )
436 note = (self._c.NOTE.ERROR, D_("Invalid player name.")) 494 note = (self._c.NOTE.ERROR, D_("Invalid player name."))
437 return (None, self._c.STATUS.COMPLETED, None, note) 495 return (None, self._c.STATUS.COMPLETED, None, note)
438 496
439 try: 497 try:
440 proxy = await self.session_con.getRemoteObject( 498 proxy = await self.session_con.getRemoteObject(
451 else: 509 else:
452 await self.do_mpris_command(proxy, command) 510 await self.do_mpris_command(proxy, command)
453 511
454 # we construct the remote control form 512 # we construct the remote control form
455 form = data_form.Form("form", title=D_("Media Player Selection")) 513 form = data_form.Form("form", title=D_("Media Player Selection"))
456 form.addField(data_form.Field(fieldType="hidden", 514 form.addField(
457 var="media_player", 515 data_form.Field(fieldType="hidden", var="media_player", value=bus_name)
458 value=bus_name)) 516 )
459 for iface, properties_names in MPRIS_PROPERTIES.items(): 517 for iface, properties_names in MPRIS_PROPERTIES.items():
460 for name in properties_names: 518 for name in properties_names:
461 try: 519 try:
462 value = await self._dbus_get_property(proxy, iface, name) 520 value = await self._dbus_get_property(proxy, iface, name)
463 except Exception as e: 521 except Exception as e:
464 log.warning(_("Can't retrieve attribute {name}: {reason}") 522 log.warning(
465 .format(name=name, reason=e)) 523 _("Can't retrieve attribute {name}: {reason}").format(
524 name=name, reason=e
525 )
526 )
466 continue 527 continue
467 if name == MPRIS_METADATA_KEY: 528 if name == MPRIS_METADATA_KEY:
468 self.add_mpris_metadata(form, value) 529 self.add_mpris_metadata(form, value)
469 else: 530 else:
470 form.addField(data_form.Field(fieldType="fixed", 531 form.addField(
471 var=name, 532 data_form.Field(fieldType="fixed", var=name, value=str(value))
472 value=str(value))) 533 )
473 534
474 commands = [data_form.Option(c, c.rsplit(".", 1)[1]) for c in MPRIS_COMMANDS] 535 commands = [data_form.Option(c, c.rsplit(".", 1)[1]) for c in MPRIS_COMMANDS]
475 form.addField(data_form.Field(fieldType="list-single", 536 form.addField(
476 var="command", 537 data_form.Field(
477 options=commands, 538 fieldType="list-single",
478 required=True)) 539 var="command",
540 options=commands,
541 required=True,
542 )
543 )
479 544
480 payload = form.toElement() 545 payload = form.toElement()
481 status = self._c.STATUS.EXECUTING 546 status = self._c.STATUS.EXECUTING
482 return (payload, status, None, None) 547 return (payload, status, None, None)