Mercurial > libervia-backend
comparison sat/plugins/plugin_adhoc_dbus.py @ 2667:8dd9db785ac8
plugin XEP-0050, adhoc D-Bus: Ad-Hoc improvment + remote media control:
- "commands" namespace is now registered
- added "do" and "getCommandElt" methods to XEP-0050 to run ad-hoc commands from backend
- commands for a profile are now stored in client._XEP_0050_commands
- Ad-Hoc D-Bus plugin can now be run without lxml or dbus (degraded)
- use MPRIS to control media players
- new adHocRemotesGet bridge method retrieve media players announced in all devices of the profile
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 31 Aug 2018 15:47:00 +0200 |
parents | 56f94936df1e |
children | 003b8b4b56a7 |
comparison
equal
deleted
inserted
replaced
2666:bc122b68eacd | 2667:8dd9db785ac8 |
---|---|
15 # GNU Affero General Public License for more details. | 15 # GNU Affero General Public License for more details. |
16 | 16 |
17 # You should have received a copy of the GNU Affero General Public License | 17 # You should have received a copy of the GNU Affero General Public License |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 # along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 |
20 from sat.core.i18n import _ | 20 from sat.core.i18n import D_, _ |
21 from sat.core.constants import Const as C | 21 from sat.core.constants import Const as C |
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 | 23 |
24 log = getLogger(__name__) | 24 log = getLogger(__name__) |
25 from sat.core import exceptions | |
26 from twisted.internet import defer | 25 from twisted.internet import defer |
26 from twisted.words.protocols.jabber import jid | |
27 from wokkel import data_form | 27 from wokkel import data_form |
28 | 28 |
29 try: | 29 try: |
30 from lxml import etree | 30 from lxml import etree |
31 except ImportError: | 31 except ImportError: |
32 raise exceptions.MissingModule( | 32 etree = None |
33 u"Missing module lxml, please download/install it from http://lxml.de/" | 33 log.warning(u"Missing module lxml, please download/install it from http://lxml.de/" |
34 ) | 34 u"auto D-Bus discovery will be disabled") |
35 from collections import OrderedDict | |
35 import os.path | 36 import os.path |
36 import uuid | 37 import uuid |
37 import dbus | 38 try: |
38 from dbus.mainloop.glib import DBusGMainLoop | 39 import dbus |
39 | 40 from dbus.mainloop.glib import DBusGMainLoop |
40 DBusGMainLoop(set_as_default=True) | 41 except ImportError: |
41 | 42 dbus = None |
43 log.warning(u"Missing module dbus, please download/install it" | |
44 u"auto D-Bus discovery will be disabled") | |
45 | |
46 else: | |
47 DBusGMainLoop(set_as_default=True) | |
48 | |
49 NS_MEDIA_PLAYER = "org.salutatoi.mediaplayer" | |
42 FD_NAME = "org.freedesktop.DBus" | 50 FD_NAME = "org.freedesktop.DBus" |
43 FD_PATH = "/org/freedekstop/DBus" | 51 FD_PATH = "/org/freedekstop/DBus" |
44 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" | 52 INTROSPECT_IFACE = "org.freedesktop.DBus.Introspectable" |
53 MPRIS_PREFIX = u"org.mpris.MediaPlayer2" | |
54 CMD_GO_BACK = u"GoBack" | |
55 CMD_GO_FWD = u"GoFW" | |
56 SEEK_OFFSET = 5 * 1000 * 1000 | |
57 MPRIS_COMMANDS = [u"org.mpris.MediaPlayer2.Player." + cmd for cmd in ( | |
58 u"Previous", CMD_GO_BACK, u"PlayPause", CMD_GO_FWD, u"Next")] | |
59 MPRIS_PATH = u"/org/mpris/MediaPlayer2" | |
60 MPRIS_PROPERTIES = OrderedDict(( | |
61 (u"org.mpris.MediaPlayer2", ( | |
62 "Identity", | |
63 )), | |
64 (u"org.mpris.MediaPlayer2.Player", ( | |
65 "Metadata", | |
66 "PlaybackStatus", | |
67 "Volume", | |
68 )), | |
69 )) | |
70 MPRIS_METADATA_KEY = "Metadata" | |
71 MPRIS_METADATA_MAP = OrderedDict(( | |
72 ("xesam:title", u"Title"), | |
73 )) | |
45 | 74 |
46 INTROSPECT_METHOD = "Introspect" | 75 INTROSPECT_METHOD = "Introspect" |
47 IGNORED_IFACES_START = ( | 76 IGNORED_IFACES_START = ( |
48 "org.freedesktop", | 77 "org.freedesktop", |
49 "org.qtproject", | 78 "org.qtproject", |
57 C.PI_TYPE: "Misc", | 86 C.PI_TYPE: "Misc", |
58 C.PI_PROTOCOLS: [], | 87 C.PI_PROTOCOLS: [], |
59 C.PI_DEPENDENCIES: ["XEP-0050"], | 88 C.PI_DEPENDENCIES: ["XEP-0050"], |
60 C.PI_MAIN: "AdHocDBus", | 89 C.PI_MAIN: "AdHocDBus", |
61 C.PI_HANDLER: "no", | 90 C.PI_HANDLER: "no", |
62 C.PI_DESCRIPTION: _("""Add D-Bus management to Ad-Hoc commands"""), | 91 C.PI_DESCRIPTION: _(u"""Add D-Bus management to Ad-Hoc commands"""), |
63 } | 92 } |
64 | 93 |
65 | 94 |
66 class AdHocDBus(object): | 95 class AdHocDBus(object): |
96 | |
67 def __init__(self, host): | 97 def __init__(self, host): |
68 log.info(_("plugin Ad-Hoc D-Bus initialization")) | 98 log.info(_("plugin Ad-Hoc D-Bus initialization")) |
69 self.host = host | 99 self.host = host |
100 if etree is not None: | |
101 host.bridge.addMethod( | |
102 "adHocDBusAddAuto", | |
103 ".plugin", | |
104 in_sign="sasasasasasass", | |
105 out_sign="(sa(sss))", | |
106 method=self._adHocDBusAddAuto, | |
107 async=True, | |
108 ) | |
70 host.bridge.addMethod( | 109 host.bridge.addMethod( |
71 "adHocDBusAddAuto", | 110 "adHocRemotesGet", |
72 ".plugin", | 111 ".plugin", |
73 in_sign="sasasasasasass", | 112 in_sign="s", |
74 out_sign="(sa(sss))", | 113 out_sign="a(sss)", |
75 method=self._adHocDBusAddAuto, | 114 method=self._adHocRemotesGet, |
76 async=True, | 115 async=True, |
77 ) | 116 ) |
78 self.session_bus = dbus.SessionBus() | 117 self._c = host.plugins["XEP-0050"] |
79 self.fd_object = self.session_bus.get_object(FD_NAME, FD_PATH, introspect=False) | 118 host.registerNamespace(u"mediaplayer", NS_MEDIA_PLAYER) |
80 self.XEP_0050 = host.plugins["XEP-0050"] | 119 if dbus is not None: |
120 self.session_bus = dbus.SessionBus() | |
121 self.fd_object = self.session_bus.get_object( | |
122 FD_NAME, FD_PATH, introspect=False) | |
123 | |
124 def profileConnected(self, client): | |
125 if dbus is not None: | |
126 self._c.addAdHocCommand( | |
127 client, self.localMediaCb, D_(u"Media Players"), | |
128 node=NS_MEDIA_PLAYER, | |
129 timeout=60*60*6 # 6 hours timeout, to avoid breaking remote | |
130 # in the middle of a movie | |
131 ) | |
81 | 132 |
82 def _DBusAsyncCall(self, proxy, method, *args, **kwargs): | 133 def _DBusAsyncCall(self, proxy, method, *args, **kwargs): |
83 """ Call a DBus method asynchronously and return a deferred | 134 """ Call a DBus method asynchronously and return a deferred |
135 | |
84 @param proxy: DBus object proxy, as returner by get_object | 136 @param proxy: DBus object proxy, as returner by get_object |
85 @param method: name of the method to call | 137 @param method: name of the method to call |
86 @param args: will be transmitted to the method | 138 @param args: will be transmitted to the method |
87 @param kwargs: will be transmetted to the method, except for the following poped values: | 139 @param kwargs: will be transmetted to the method, except for the following poped |
140 values: | |
88 - interface: name of the interface to use | 141 - interface: name of the interface to use |
89 @return: a deferred | 142 @return: a deferred |
90 | 143 |
91 """ | 144 """ |
92 d = defer.Deferred() | 145 d = defer.Deferred() |
93 interface = kwargs.pop("interface", None) | 146 interface = kwargs.pop("interface", None) |
94 kwargs["reply_handler"] = lambda ret=None: d.callback(ret) | 147 kwargs["reply_handler"] = lambda ret=None: d.callback(ret) |
95 kwargs["error_handler"] = d.errback | 148 kwargs["error_handler"] = d.errback |
96 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) | 149 proxy.get_dbus_method(method, dbus_interface=interface)(*args, **kwargs) |
97 return d | 150 return d |
151 | |
152 def _DBusGetProperty(self, proxy, interface, name): | |
153 return self._DBusAsyncCall( | |
154 proxy, u"Get", interface, name, interface=u"org.freedesktop.DBus.Properties") | |
155 | |
98 | 156 |
99 def _DBusListNames(self): | 157 def _DBusListNames(self): |
100 return self._DBusAsyncCall(self.fd_object, "ListNames") | 158 return self._DBusAsyncCall(self.fd_object, "ListNames") |
101 | 159 |
102 def _DBusIntrospect(self, proxy): | 160 def _DBusIntrospect(self, proxy): |
136 if self._acceptMethod(method): | 194 if self._acceptMethod(method): |
137 method_name = method.get("name") | 195 method_name = method.get("name") |
138 log.debug("method accepted: [%s]" % method_name) | 196 log.debug("method accepted: [%s]" % method_name) |
139 methods.add((proxy.object_path, name, method_name)) | 197 methods.add((proxy.object_path, name, method_name)) |
140 | 198 |
141 def _adHocDBusAddAuto( | 199 def _adHocDBusAddAuto(self, prog_name, allowed_jids, allowed_groups, allowed_magics, |
142 self, | 200 forbidden_jids, forbidden_groups, flags, profile_key): |
143 prog_name, | 201 client = self.host.getClient(profile_key) |
144 allowed_jids, | |
145 allowed_groups, | |
146 allowed_magics, | |
147 forbidden_jids, | |
148 forbidden_groups, | |
149 flags, | |
150 profile_key, | |
151 ): | |
152 return self.adHocDBusAddAuto( | 202 return self.adHocDBusAddAuto( |
153 prog_name, | 203 client, prog_name, allowed_jids, allowed_groups, allowed_magics, |
154 allowed_jids, | 204 forbidden_jids, forbidden_groups, flags) |
155 allowed_groups, | |
156 allowed_magics, | |
157 forbidden_jids, | |
158 forbidden_groups, | |
159 flags, | |
160 profile_key, | |
161 ) | |
162 | 205 |
163 @defer.inlineCallbacks | 206 @defer.inlineCallbacks |
164 def adHocDBusAddAuto( | 207 def adHocDBusAddAuto(self, client, prog_name, allowed_jids=None, allowed_groups=None, |
165 self, | 208 allowed_magics=None, forbidden_jids=None, forbidden_groups=None, |
166 prog_name, | 209 flags=None): |
167 allowed_jids=None, | |
168 allowed_groups=None, | |
169 allowed_magics=None, | |
170 forbidden_jids=None, | |
171 forbidden_groups=None, | |
172 flags=None, | |
173 profile_key=C.PROF_KEY_NONE, | |
174 ): | |
175 bus_names = yield self._DBusListNames() | 210 bus_names = yield self._DBusListNames() |
176 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] | 211 bus_names = [bus_name for bus_name in bus_names if "." + prog_name in bus_name] |
177 if not bus_names: | 212 if not bus_names: |
178 log.info("Can't find any bus for [%s]" % prog_name) | 213 log.info("Can't find any bus for [%s]" % prog_name) |
179 defer.returnValue(("", [])) | 214 defer.returnValue(("", [])) |
187 | 222 |
188 yield self._introspect(methods, bus_name, proxy) | 223 yield self._introspect(methods, bus_name, proxy) |
189 | 224 |
190 if methods: | 225 if methods: |
191 self._addCommand( | 226 self._addCommand( |
227 client, | |
192 prog_name, | 228 prog_name, |
193 bus_name, | 229 bus_name, |
194 methods, | 230 methods, |
195 allowed_jids=allowed_jids, | 231 allowed_jids=allowed_jids, |
196 allowed_groups=allowed_groups, | 232 allowed_groups=allowed_groups, |
197 allowed_magics=allowed_magics, | 233 allowed_magics=allowed_magics, |
198 forbidden_jids=forbidden_jids, | 234 forbidden_jids=forbidden_jids, |
199 forbidden_groups=forbidden_groups, | 235 forbidden_groups=forbidden_groups, |
200 flags=flags, | 236 flags=flags, |
201 profile_key=profile_key, | |
202 ) | 237 ) |
203 | 238 |
204 defer.returnValue((bus_name, methods)) | 239 defer.returnValue((bus_name, methods)) |
205 | 240 |
206 def _addCommand( | 241 def _addCommand(self, client, adhoc_name, bus_name, methods, allowed_jids=None, |
207 self, | 242 allowed_groups=None, allowed_magics=None, forbidden_jids=None, |
208 adhoc_name, | 243 forbidden_groups=None, flags=None): |
209 bus_name, | |
210 methods, | |
211 allowed_jids=None, | |
212 allowed_groups=None, | |
213 allowed_magics=None, | |
214 forbidden_jids=None, | |
215 forbidden_groups=None, | |
216 flags=None, | |
217 profile_key=C.PROF_KEY_NONE, | |
218 ): | |
219 if flags is None: | 244 if flags is None: |
220 flags = set() | 245 flags = set() |
221 | 246 |
222 def DBusCallback(command_elt, session_data, action, node, profile): | 247 def DBusCallback(client, command_elt, session_data, action, node): |
223 actions = session_data.setdefault("actions", []) | 248 actions = session_data.setdefault("actions", []) |
224 names_map = session_data.setdefault("names_map", {}) | 249 names_map = session_data.setdefault("names_map", {}) |
225 actions.append(action) | 250 actions.append(action) |
226 | 251 |
227 if len(actions) == 1: | 252 if len(actions) == 1: |
228 # it's our first request, we ask the desired new status | 253 # it's our first request, we ask the desired new status |
229 status = self.XEP_0050.STATUS.EXECUTING | 254 status = self._c.STATUS.EXECUTING |
230 form = data_form.Form("form", title=_("Command selection")) | 255 form = data_form.Form("form", title=_("Command selection")) |
231 options = [] | 256 options = [] |
232 for path, iface, command in methods: | 257 for path, iface, command in methods: |
233 label = command.rsplit(".", 1)[-1] | 258 label = command.rsplit(".", 1)[-1] |
234 name = str(uuid.uuid4()) | 259 name = str(uuid.uuid4()) |
248 try: | 273 try: |
249 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() | 274 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() |
250 answer_form = data_form.Form.fromElement(x_elt) | 275 answer_form = data_form.Form.fromElement(x_elt) |
251 command = answer_form["command"] | 276 command = answer_form["command"] |
252 except (KeyError, StopIteration): | 277 except (KeyError, StopIteration): |
253 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) | 278 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD) |
254 | 279 |
255 if command not in names_map: | 280 if command not in names_map: |
256 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.BAD_PAYLOAD) | 281 raise self._c.AdHocError(self._c.ERROR.BAD_PAYLOAD) |
257 | 282 |
258 path, iface, command = names_map[command] | 283 path, iface, command = names_map[command] |
259 proxy = self.session_bus.get_object(bus_name, path) | 284 proxy = self.session_bus.get_object(bus_name, path) |
260 | 285 |
261 self._DBusAsyncCall(proxy, command, interface=iface) | 286 self._DBusAsyncCall(proxy, command, interface=iface) |
262 | 287 |
263 # job done, we can end the session, except if we have FLAG_LOOP | 288 # job done, we can end the session, except if we have FLAG_LOOP |
264 if FLAG_LOOP in flags: | 289 if FLAG_LOOP in flags: |
265 # We have a loop, so we clear everything and we execute again the command as we had a first call (command_elt is not used, so None is OK) | 290 # We have a loop, so we clear everything and we execute again the |
291 # command as we had a first call (command_elt is not used, so None | |
292 # is OK) | |
266 del actions[:] | 293 del actions[:] |
267 names_map.clear() | 294 names_map.clear() |
268 return DBusCallback( | 295 return DBusCallback( |
269 None, session_data, self.XEP_0050.ACTION.EXECUTE, node, profile | 296 client, None, session_data, self._c.ACTION.EXECUTE, node |
270 ) | 297 ) |
271 form = data_form.Form("form", title=_(u"Updated")) | 298 form = data_form.Form("form", title=_(u"Updated")) |
272 form.addField(data_form.Field("fixed", u"Command sent")) | 299 form.addField(data_form.Field("fixed", u"Command sent")) |
273 status = self.XEP_0050.STATUS.COMPLETED | 300 status = self._c.STATUS.COMPLETED |
274 payload = None | 301 payload = None |
275 note = (self.XEP_0050.NOTE.INFO, _(u"Command sent")) | 302 note = (self._c.NOTE.INFO, _(u"Command sent")) |
276 else: | 303 else: |
277 raise self.XEP_0050.AdHocError(self.XEP_0050.ERROR.INTERNAL) | 304 raise self._c.AdHocError(self._c.ERROR.INTERNAL) |
278 | 305 |
279 return (payload, status, None, note) | 306 return (payload, status, None, note) |
280 | 307 |
281 self.XEP_0050.addAdHocCommand( | 308 self._c.addAdHocCommand( |
309 client, | |
282 DBusCallback, | 310 DBusCallback, |
283 adhoc_name, | 311 adhoc_name, |
284 allowed_jids=allowed_jids, | 312 allowed_jids=allowed_jids, |
285 allowed_groups=allowed_groups, | 313 allowed_groups=allowed_groups, |
286 allowed_magics=allowed_magics, | 314 allowed_magics=allowed_magics, |
287 forbidden_jids=forbidden_jids, | 315 forbidden_jids=forbidden_jids, |
288 forbidden_groups=forbidden_groups, | 316 forbidden_groups=forbidden_groups, |
289 profile_key=profile_key, | |
290 ) | 317 ) |
318 | |
319 ## Local media ## | |
320 | |
321 def _adHocRemotesGet(self, profile): | |
322 return self.adHocRemotesGet(self.host.getClient(profile)) | |
323 | |
324 @defer.inlineCallbacks | |
325 def adHocRemotesGet(self, client): | |
326 """Retrieve available remote media controlers in our devices | |
327 @return (list[tuple[unicode, unicode, unicode]]): list of devices with: | |
328 - entity full jid | |
329 - device name | |
330 - device label | |
331 """ | |
332 found_data = yield self.host.findByFeatures( | |
333 client, [self.host.ns_map['commands']], service=False, roster=False, | |
334 own_jid=True, local_device=True) | |
335 | |
336 remotes = [] | |
337 | |
338 for found in found_data: | |
339 for device_jid_s in found: | |
340 device_jid = jid.JID(device_jid_s) | |
341 cmd_list = yield self._c.list(client, device_jid) | |
342 for cmd in cmd_list: | |
343 if cmd.nodeIdentifier == NS_MEDIA_PLAYER: | |
344 try: | |
345 result_elt = yield self._c.do(client, device_jid, | |
346 NS_MEDIA_PLAYER, timeout=5) | |
347 command_elt = self._c.getCommandElt(result_elt) | |
348 form = data_form.findForm(command_elt, NS_MEDIA_PLAYER) | |
349 mp_options = form.fields['media_player'].options | |
350 session_id = command_elt.getAttribute('sessionid') | |
351 if mp_options and session_id: | |
352 # we just want to discover player, so we cancel the | |
353 # session | |
354 self._c.do(client, device_jid, NS_MEDIA_PLAYER, | |
355 action=self._c.ACTION.CANCEL, | |
356 session_id=session_id) | |
357 | |
358 for opt in mp_options: | |
359 remotes.append((device_jid_s, | |
360 opt.value, | |
361 opt.label or opt.value)) | |
362 except Exception as e: | |
363 log.warning(_( | |
364 u"Can't retrieve remote controllers on {device_jid}: " | |
365 u"{reason}".format(device_jid=device_jid, reason=e))) | |
366 break | |
367 defer.returnValue(remotes) | |
368 | |
369 def doMPRISCommand(self, proxy, command): | |
370 iface, command = command.rsplit(u".", 1) | |
371 if command == CMD_GO_BACK: | |
372 command = u'Seek' | |
373 args = [-SEEK_OFFSET] | |
374 elif command == CMD_GO_FWD: | |
375 command = u'Seek' | |
376 args = [SEEK_OFFSET] | |
377 else: | |
378 args = [] | |
379 return self._DBusAsyncCall(proxy, command, *args, interface=iface) | |
380 | |
381 def addMPRISMetadata(self, form, metadata): | |
382 """Serialise MRPIS Metadata according to MPRIS_METADATA_MAP""" | |
383 for mpris_key, name in MPRIS_METADATA_MAP.iteritems(): | |
384 if mpris_key in metadata: | |
385 value = unicode(metadata[mpris_key]) | |
386 form.addField(data_form.Field(fieldType=u"fixed", | |
387 var=name, | |
388 value=value)) | |
389 | |
390 @defer.inlineCallbacks | |
391 def localMediaCb(self, client, command_elt, session_data, action, node): | |
392 try: | |
393 x_elt = command_elt.elements(data_form.NS_X_DATA, "x").next() | |
394 command_form = data_form.Form.fromElement(x_elt) | |
395 except StopIteration: | |
396 command_form = None | |
397 | |
398 if command_form is None or len(command_form.fields) == 0: | |
399 # root request, we looks for media players | |
400 bus_names = yield self._DBusListNames() | |
401 bus_names = [b for b in bus_names if b.startswith(MPRIS_PREFIX)] | |
402 if len(bus_names) == 0: | |
403 note = (self._c.NOTE.INFO, D_(u"No media player found.")) | |
404 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) | |
405 options = [] | |
406 status = self._c.STATUS.EXECUTING | |
407 form = data_form.Form("form", title=D_(u"Media Player Selection"), | |
408 formNamespace=NS_MEDIA_PLAYER) | |
409 for bus in bus_names: | |
410 player_name = bus[len(MPRIS_PREFIX)+1:] | |
411 if not player_name: | |
412 log.warning(_(u"Ignoring MPRIS bus without suffix")) | |
413 continue | |
414 options.append(data_form.Option(bus, player_name)) | |
415 field = data_form.Field( | |
416 "list-single", "media_player", options=options, required=True | |
417 ) | |
418 form.addField(field) | |
419 payload = form.toElement() | |
420 defer.returnValue((payload, status, None, None)) | |
421 else: | |
422 # player request | |
423 try: | |
424 bus_name = command_form[u"media_player"] | |
425 except KeyError: | |
426 raise ValueError(_(u"missing media_player value")) | |
427 | |
428 if not bus_name.startswith(MPRIS_PREFIX): | |
429 log.warning(_(u"Media player ad-hoc command trying to use non MPRIS bus. " | |
430 u"Hack attempt? Refused bus: {bus_name}").format( | |
431 bus_name=bus_name)) | |
432 note = (self._c.NOTE.ERROR, D_(u"Invalid player name.")) | |
433 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) | |
434 | |
435 try: | |
436 proxy = self.session_bus.get_object(bus_name, MPRIS_PATH) | |
437 except dbus.exceptions.DBusException as e: | |
438 log.warning(_(u"Can't get D-Bus proxy: {reason}").format(reason=e)) | |
439 note = (self._c.NOTE.ERROR, D_(u"Media player is not available anymore")) | |
440 defer.returnValue((None, self._c.STATUS.COMPLETED, None, note)) | |
441 try: | |
442 command = command_form[u"command"] | |
443 except KeyError: | |
444 pass | |
445 else: | |
446 yield self.doMPRISCommand(proxy, command) | |
447 | |
448 # we construct the remote control form | |
449 form = data_form.Form("form", title=D_(u"Media Player Selection")) | |
450 form.addField(data_form.Field(fieldType=u"hidden", | |
451 var=u"media_player", | |
452 value=bus_name)) | |
453 for iface, properties_names in MPRIS_PROPERTIES.iteritems(): | |
454 for name in properties_names: | |
455 try: | |
456 value = yield self._DBusGetProperty(proxy, iface, name) | |
457 except Exception as e: | |
458 log.warning(_(u"Can't retrieve attribute {name}: {reason}") | |
459 .format(name=name, reason=e)) | |
460 continue | |
461 if name == MPRIS_METADATA_KEY: | |
462 self.addMPRISMetadata(form, value) | |
463 else: | |
464 form.addField(data_form.Field(fieldType=u"fixed", | |
465 var=name, | |
466 value=unicode(value))) | |
467 | |
468 commands = [data_form.Option(c, c.rsplit(u".", 1)[1]) for c in MPRIS_COMMANDS] | |
469 form.addField(data_form.Field(fieldType=u"list-single", | |
470 var=u"command", | |
471 options=commands, | |
472 required=True)) | |
473 | |
474 payload = form.toElement() | |
475 status = self._c.STATUS.EXECUTING | |
476 defer.returnValue((payload, status, None, None)) | |
477 | |
478 |