Mercurial > libervia-backend
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) |