Mercurial > libervia-desktop-kivy
comparison cagou/plugins/plugin_wid_remote.py @ 238:7918a5668304
plugin remote: remote controllers plugin first draft:
this plugin handle ad-hoc commands and media controllers in a easy way.
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 31 Aug 2018 17:01:31 +0200 |
parents | |
children | a2af4c1e9c6f |
comparison
equal
deleted
inserted
replaced
237:059c5b39032d | 238:7918a5668304 |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client | |
5 # Copyright (C) 2016-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
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/>. | |
19 | |
20 | |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
23 from sat.core.i18n import _ | |
24 from sat_frontends.quick_frontend import quick_widgets | |
25 from cagou.core import cagou_widget | |
26 from cagou.core.constants import Const as C | |
27 from cagou.core.menu import TouchMenuBehaviour | |
28 from cagou.core.utils import FilterBehavior | |
29 from cagou.core.common_widgets import (Identities, ItemWidget, DeviceWidget, | |
30 CategorySeparator) | |
31 from sat.tools.common import template_xmlui | |
32 from cagou.core import xmlui | |
33 from sat_frontends.tools import jid | |
34 from kivy import properties | |
35 from kivy.uix.label import Label | |
36 from kivy.uix.boxlayout import BoxLayout | |
37 from kivy.core.window import Window | |
38 from cagou import G | |
39 from functools import partial | |
40 | |
41 | |
42 PLUGIN_INFO = { | |
43 "name": _(u"remote control"), | |
44 "main": "RemoteControl", | |
45 "description": _(u"universal remote control"), | |
46 "icon_symbol": u"signal", | |
47 } | |
48 | |
49 NOTE_TITLE = _(u"Media Player Remote Control") | |
50 | |
51 | |
52 class RemoteItemWidget(ItemWidget): | |
53 | |
54 def __init__(self, device_jid, node, name, main_wid, **kw): | |
55 self.device_jid = device_jid | |
56 self.node = node | |
57 super(RemoteItemWidget, self).__init__(name=name, main_wid=main_wid, **kw) | |
58 | |
59 def do_item_action(self, touch): | |
60 self.main_wid.layout.clear_widgets() | |
61 player_wid = MediaPlayerControlWidget(main_wid=self.main_wid, remote_item=self) | |
62 self.main_wid.layout.add_widget(player_wid) | |
63 | |
64 | |
65 class MediaPlayerControlWidget(BoxLayout): | |
66 main_wid = properties.ObjectProperty() | |
67 remote_item = properties.ObjectProperty() | |
68 status = properties.OptionProperty(u"play", options=(u"play", u"pause", u"stop")) | |
69 title = properties.StringProperty() | |
70 identity = properties.StringProperty() | |
71 command = properties.DictProperty() | |
72 ui_tpl = properties.ObjectProperty() | |
73 | |
74 @property | |
75 def profile(self): | |
76 return self.main_wid.profile | |
77 | |
78 def updateUI(self, action_data): | |
79 xmlui_raw = action_data['xmlui'] | |
80 ui_tpl = template_xmlui.create(G.host, xmlui_raw) | |
81 self.ui_tpl = ui_tpl | |
82 for prop in ('Title', 'Identity'): | |
83 try: | |
84 setattr(self, prop.lower(), ui_tpl.widgets[prop].value) | |
85 except KeyError: | |
86 log.warning(_(u"Missing field: {name}").format(name=prop)) | |
87 playback_status = self.ui_tpl.widgets['PlaybackStatus'].value | |
88 if playback_status == u"Playing": | |
89 self.status = u"pause" | |
90 elif playback_status == u"Paused": | |
91 self.status = u"play" | |
92 elif playback_status == u"Stopped": | |
93 self.status = u"play" | |
94 else: | |
95 G.host.addNote( | |
96 title=NOTE_TITLE, | |
97 message=_(u"Unknown playback status: playback_status") | |
98 .format(playback_status=playback_status), | |
99 level=C.XMLUI_DATA_LVL_WARNING) | |
100 self.commands = {v:k for k,v in ui_tpl.widgets['command'].options} | |
101 | |
102 def adHocRunCb(self, xmlui_raw): | |
103 ui_tpl = template_xmlui.create(G.host, xmlui_raw) | |
104 data = {xmlui.XMLUIPanel.escape(u"media_player"): self.remote_item.node, | |
105 u"session_id": ui_tpl.session_id} | |
106 G.host.bridge.launchAction( | |
107 ui_tpl.submit_id, data, self.profile, | |
108 callback=self.updateUI, | |
109 errback=self.main_wid.errback) | |
110 | |
111 def on_remote_item(self, __, remote): | |
112 NS_MEDIA_PLAYER = G.host.ns_map[u"mediaplayer"] | |
113 G.host.bridge.adHocRun(remote.device_jid, NS_MEDIA_PLAYER, self.profile, | |
114 callback=self.adHocRunCb, | |
115 errback=self.main_wid.errback) | |
116 | |
117 def do_cmd(self, command): | |
118 try: | |
119 cmd_value = self.commands[command] | |
120 except KeyError: | |
121 G.host.addNote( | |
122 title=NOTE_TITLE, | |
123 message=_(u"{command} command is not managed").format(command=command), | |
124 level=C.XMLUI_DATA_LVL_WARNING) | |
125 else: | |
126 data = {xmlui.XMLUIPanel.escape(u"command"): cmd_value, | |
127 u"session_id": self.ui_tpl.session_id} | |
128 # hidden values are normally transparently managed by XMLUIPanel | |
129 # but here we have to add them by hand | |
130 hidden = {xmlui.XMLUIPanel.escape(k):v | |
131 for k,v in self.ui_tpl.hidden.iteritems()} | |
132 data.update(hidden) | |
133 G.host.bridge.launchAction( | |
134 self.ui_tpl.submit_id, data, self.profile, | |
135 callback=self.updateUI, | |
136 errback=self.main_wid.errback) | |
137 | |
138 | |
139 class RemoteDeviceWidget(DeviceWidget): | |
140 | |
141 def xmluiCb(self, data, cb_id, profile): | |
142 if u'xmlui' in data: | |
143 xml_ui = xmlui.create( | |
144 G.host, data[u'xmlui'], callback=self.xmluiCb, profile=profile) | |
145 if isinstance(xml_ui, xmlui.XMLUIDialog): | |
146 self.main_wid.showRootWidget() | |
147 xml_ui.show() | |
148 else: | |
149 xml_ui.size_hint_y = None | |
150 self.main_wid.layout.clear_widgets() | |
151 self.main_wid.layout.add_widget(xml_ui) | |
152 else: | |
153 if data: | |
154 log.warning(_(u"Unhandled data: {data}").format(data=data)) | |
155 self.main_wid.showRootWidget() | |
156 | |
157 def adHocRunCb(self, data): | |
158 xml_ui = xmlui.create(G.host, data, callback=self.xmluiCb, profile=self.profile) | |
159 xml_ui.size_hint_y = None | |
160 self.main_wid.layout.add_widget(xml_ui) | |
161 | |
162 def do_item_action(self, touch): | |
163 self.main_wid.layout.clear_widgets() | |
164 G.host.bridge.adHocRun(self.entity_jid, u'', self.profile, | |
165 callback=self.adHocRunCb, errback=self.main_wid.errback) | |
166 | |
167 | |
168 class RemoteControl(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior, | |
169 TouchMenuBehaviour): | |
170 SINGLE=False | |
171 layout = properties.ObjectProperty() | |
172 | |
173 def __init__(self, host, target, profiles): | |
174 quick_widgets.QuickWidget.__init__(self, host, target, profiles) | |
175 cagou_widget.CagouWidget.__init__(self) | |
176 FilterBehavior.__init__(self) | |
177 TouchMenuBehaviour.__init__(self) | |
178 Window.bind(on_keyboard=self.key_input) | |
179 self.showRootWidget() | |
180 | |
181 def errback(self, failure_): | |
182 """Generic errback which add a warning note and go back to root widget""" | |
183 G.host.addNote( | |
184 title=NOTE_TITLE, | |
185 message=_(u"Can't use remote control: {reason}").format(reason=failure_), | |
186 level=C.XMLUI_DATA_LVL_WARNING) | |
187 self.showRootWidget() | |
188 | |
189 def key_input(self, window, key, scancode, codepoint, modifier): | |
190 if key == 27: | |
191 self.showRootWidget() | |
192 return True | |
193 | |
194 def showRootWidget(self): | |
195 self.layout.clear_widgets() | |
196 found = [] | |
197 self.get_remotes(found) | |
198 self.discover_devices(found) | |
199 | |
200 def adHocRemotesGetCb(self, remotes_data, found): | |
201 found.insert(0, remotes_data) | |
202 if len(found) == 2: | |
203 self.show_devices(found) | |
204 | |
205 def adHocRemotesGetEb(self, failure_, found): | |
206 G.host.errback(failure_, title=_(u"discovery error"), | |
207 message=_(u"can't check remote controllers: {msg}")) | |
208 found.insert(0, []) | |
209 if len(found) == 2: | |
210 self.show_devices(found) | |
211 | |
212 def get_remotes(self, found): | |
213 self.host.bridge.adHocRemotesGet( | |
214 self.profile, | |
215 callback=partial(self.adHocRemotesGetCb, found=found), | |
216 errback=partial(self.adHocRemotesGetEb,found=found)) | |
217 | |
218 def _discoFindByFeaturesCb(self, data, found): | |
219 found.append(data) | |
220 if len(found) == 2: | |
221 self.show_devices(found) | |
222 | |
223 def _discoFindByFeaturesEb(self, failure_, found): | |
224 G.host.errback(failure_, title=_(u"discovery error"), | |
225 message=_(u"can't check devices: {msg}")) | |
226 found.append(({}, {}, {})) | |
227 if len(found) == 2: | |
228 self.show_devices(found) | |
229 | |
230 def discover_devices(self, found): | |
231 """Looks for devices handling file "File Information Sharing" and display them""" | |
232 try: | |
233 namespace = self.host.ns_map['commands'] | |
234 except KeyError: | |
235 msg = _(u"can't find file information sharing namespace, " | |
236 u"is the plugin running?") | |
237 log.warning(msg) | |
238 G.host.addNote(_(u"missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR) | |
239 return | |
240 self.host.bridge.discoFindByFeatures( | |
241 [namespace], [], False, True, True, True, False, self.profile, | |
242 callback=partial(self._discoFindByFeaturesCb, found=found), | |
243 errback=partial(self._discoFindByFeaturesEb, found=found)) | |
244 | |
245 def show_devices(self, found): | |
246 remotes_data, (entities_services, entities_own, entities_roster) = found | |
247 if remotes_data: | |
248 title = _(u"media players remote controls") | |
249 self.layout.add_widget(CategorySeparator(text=title)) | |
250 | |
251 for remote_data in remotes_data: | |
252 device_jid, node, name = remote_data | |
253 wid = RemoteItemWidget(device_jid, node, name, self) | |
254 self.layout.add_widget(wid) | |
255 | |
256 for entities_map, title in ((entities_services, | |
257 _(u'services')), | |
258 (entities_own, | |
259 _(u'your devices')), | |
260 (entities_roster, | |
261 _(u'your contacts devices'))): | |
262 if entities_map: | |
263 self.layout.add_widget(CategorySeparator(text=title)) | |
264 for entity_str, entity_ids in entities_map.iteritems(): | |
265 entity_jid = jid.JID(entity_str) | |
266 item = RemoteDeviceWidget( | |
267 self, entity_jid, Identities(entity_ids)) | |
268 self.layout.add_widget(item) | |
269 if (not remotes_data and not entities_services and not entities_own | |
270 and not entities_roster): | |
271 self.layout.add_widget(Label( | |
272 size_hint=(1, 1), | |
273 halign='center', | |
274 text_size=self.size, | |
275 text=_(u"No sharing device found"))) |