Mercurial > libervia-desktop-kivy
comparison libervia/desktop_kivy/plugins/plugin_wid_remote.py @ 493:b3cedbee561d
refactoring: rename `cagou` to `libervia.desktop_kivy` + update imports and names following backend changes
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 02 Jun 2023 18:26:16 +0200 |
parents | cagou/plugins/plugin_wid_remote.py@5114bbb5daa3 |
children | 196483685a63 |
comparison
equal
deleted
inserted
replaced
492:5114bbb5daa3 | 493:b3cedbee561d |
---|---|
1 #!/usr/bin/env python3 | |
2 | |
3 | |
4 #Libervia Desktop-Kivy | |
5 # Copyright (C) 2016-2021 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 libervia.backend.core import log as logging | |
22 from libervia.backend.core.i18n import _ | |
23 from libervia.frontends.quick_frontend import quick_widgets | |
24 from ..core import cagou_widget | |
25 from ..core.constants import Const as C | |
26 from ..core.behaviors import TouchMenuBehavior, FilterBehavior | |
27 from ..core.common_widgets import (Identities, ItemWidget, DeviceWidget, | |
28 CategorySeparator) | |
29 from libervia.backend.tools.common import template_xmlui | |
30 from libervia.backend.tools.common import data_format | |
31 from libervia.desktop_kivy.core import xmlui | |
32 from libervia.frontends.tools import jid | |
33 from kivy import properties | |
34 from kivy.uix.label import Label | |
35 from kivy.uix.boxlayout import BoxLayout | |
36 from kivy.uix.floatlayout import FloatLayout | |
37 from libervia.desktop_kivy import G | |
38 from functools import partial | |
39 | |
40 | |
41 log = logging.getLogger(__name__) | |
42 | |
43 PLUGIN_INFO = { | |
44 "name": _("remote control"), | |
45 "main": "RemoteControl", | |
46 "description": _("universal remote control"), | |
47 "icon_symbol": "signal", | |
48 } | |
49 | |
50 NOTE_TITLE = _("Media Player Remote Control") | |
51 | |
52 | |
53 class RemoteItemWidget(ItemWidget): | |
54 | |
55 def __init__(self, device_jid, node, name, main_wid, **kw): | |
56 self.device_jid = device_jid | |
57 self.node = node | |
58 super(RemoteItemWidget, self).__init__(name=name, main_wid=main_wid, **kw) | |
59 | |
60 def do_item_action(self, touch): | |
61 self.main_wid.layout.clear_widgets() | |
62 player_wid = MediaPlayerControlWidget(main_wid=self.main_wid, remote_item=self) | |
63 self.main_wid.layout.add_widget(player_wid) | |
64 | |
65 | |
66 class MediaPlayerControlWidget(BoxLayout): | |
67 main_wid = properties.ObjectProperty() | |
68 remote_item = properties.ObjectProperty() | |
69 status = properties.OptionProperty("play", options=("play", "pause", "stop")) | |
70 title = properties.StringProperty() | |
71 identity = properties.StringProperty() | |
72 command = properties.DictProperty() | |
73 ui_tpl = properties.ObjectProperty() | |
74 | |
75 @property | |
76 def profile(self): | |
77 return self.main_wid.profile | |
78 | |
79 def update_ui(self, action_data_s): | |
80 action_data = data_format.deserialise(action_data_s) | |
81 xmlui_raw = action_data['xmlui'] | |
82 ui_tpl = template_xmlui.create(G.host, xmlui_raw) | |
83 self.ui_tpl = ui_tpl | |
84 for prop in ('Title', 'Identity'): | |
85 try: | |
86 setattr(self, prop.lower(), ui_tpl.widgets[prop].value) | |
87 except KeyError: | |
88 log.warning(_("Missing field: {name}").format(name=prop)) | |
89 playback_status = self.ui_tpl.widgets['PlaybackStatus'].value | |
90 if playback_status == "Playing": | |
91 self.status = "pause" | |
92 elif playback_status == "Paused": | |
93 self.status = "play" | |
94 elif playback_status == "Stopped": | |
95 self.status = "play" | |
96 else: | |
97 G.host.add_note( | |
98 title=NOTE_TITLE, | |
99 message=_("Unknown playback status: playback_status") | |
100 .format(playback_status=playback_status), | |
101 level=C.XMLUI_DATA_LVL_WARNING) | |
102 self.commands = {v:k for k,v in ui_tpl.widgets['command'].options} | |
103 | |
104 def ad_hoc_run_cb(self, xmlui_raw): | |
105 ui_tpl = template_xmlui.create(G.host, xmlui_raw) | |
106 data = {xmlui.XMLUIPanel.escape("media_player"): self.remote_item.node, | |
107 "session_id": ui_tpl.session_id} | |
108 G.host.bridge.action_launch( | |
109 ui_tpl.submit_id, data_format.serialise(data), | |
110 self.profile, callback=self.update_ui, | |
111 errback=self.main_wid.errback | |
112 ) | |
113 | |
114 def on_remote_item(self, __, remote): | |
115 NS_MEDIA_PLAYER = G.host.ns_map["mediaplayer"] | |
116 G.host.bridge.ad_hoc_run(str(remote.device_jid), NS_MEDIA_PLAYER, self.profile, | |
117 callback=self.ad_hoc_run_cb, | |
118 errback=self.main_wid.errback) | |
119 | |
120 def do_cmd(self, command): | |
121 try: | |
122 cmd_value = self.commands[command] | |
123 except KeyError: | |
124 G.host.add_note( | |
125 title=NOTE_TITLE, | |
126 message=_("{command} command is not managed").format(command=command), | |
127 level=C.XMLUI_DATA_LVL_WARNING) | |
128 else: | |
129 data = {xmlui.XMLUIPanel.escape("command"): cmd_value, | |
130 "session_id": self.ui_tpl.session_id} | |
131 # hidden values are normally transparently managed by XMLUIPanel | |
132 # but here we have to add them by hand | |
133 hidden = {xmlui.XMLUIPanel.escape(k):v | |
134 for k,v in self.ui_tpl.hidden.items()} | |
135 data.update(hidden) | |
136 G.host.bridge.action_launch( | |
137 self.ui_tpl.submit_id, data_format.serialise(data), self.profile, | |
138 callback=self.update_ui, errback=self.main_wid.errback | |
139 ) | |
140 | |
141 | |
142 class RemoteDeviceWidget(DeviceWidget): | |
143 | |
144 def xmlui_cb(self, data, cb_id, profile): | |
145 if 'xmlui' in data: | |
146 xml_ui = xmlui.create( | |
147 G.host, data['xmlui'], callback=self.xmlui_cb, profile=profile) | |
148 if isinstance(xml_ui, xmlui.XMLUIDialog): | |
149 self.main_wid.show_root_widget() | |
150 xml_ui.show() | |
151 else: | |
152 xml_ui.set_close_cb(self.on_close) | |
153 self.main_wid.layout.add_widget(xml_ui) | |
154 else: | |
155 if data: | |
156 log.warning(_("Unhandled data: {data}").format(data=data)) | |
157 self.main_wid.show_root_widget() | |
158 | |
159 def on_close(self, __, reason): | |
160 if reason == C.XMLUI_DATA_CANCELLED: | |
161 self.main_wid.show_root_widget() | |
162 else: | |
163 self.main_wid.layout.clear_widgets() | |
164 | |
165 def ad_hoc_run_cb(self, data): | |
166 xml_ui = xmlui.create(G.host, data, callback=self.xmlui_cb, profile=self.profile) | |
167 xml_ui.set_close_cb(self.on_close) | |
168 self.main_wid.layout.add_widget(xml_ui) | |
169 | |
170 def do_item_action(self, touch): | |
171 self.main_wid.layout.clear_widgets() | |
172 G.host.bridge.ad_hoc_run(str(self.entity_jid), '', self.profile, | |
173 callback=self.ad_hoc_run_cb, errback=self.main_wid.errback) | |
174 | |
175 | |
176 class DevicesLayout(FloatLayout): | |
177 """Layout used to show devices""" | |
178 layout = properties.ObjectProperty() | |
179 | |
180 | |
181 class RemoteControl(quick_widgets.QuickWidget, cagou_widget.LiberviaDesktopKivyWidget, FilterBehavior, | |
182 TouchMenuBehavior): | |
183 SINGLE=False | |
184 layout = properties.ObjectProperty() | |
185 | |
186 def __init__(self, host, target, profiles): | |
187 quick_widgets.QuickWidget.__init__(self, host, target, profiles) | |
188 cagou_widget.LiberviaDesktopKivyWidget.__init__(self) | |
189 FilterBehavior.__init__(self) | |
190 TouchMenuBehavior.__init__(self) | |
191 self.stack_layout = None | |
192 self.show_root_widget() | |
193 | |
194 def errback(self, failure_): | |
195 """Generic errback which add a warning note and go back to root widget""" | |
196 G.host.add_note( | |
197 title=NOTE_TITLE, | |
198 message=_("Can't use remote control: {reason}").format(reason=failure_), | |
199 level=C.XMLUI_DATA_LVL_WARNING) | |
200 self.show_root_widget() | |
201 | |
202 def key_input(self, window, key, scancode, codepoint, modifier): | |
203 if key == 27: | |
204 self.show_root_widget() | |
205 return True | |
206 | |
207 def show_root_widget(self): | |
208 self.layout.clear_widgets() | |
209 devices_layout = DevicesLayout() | |
210 self.stack_layout = devices_layout.layout | |
211 self.layout.add_widget(devices_layout) | |
212 found = [] | |
213 self.get_remotes(found) | |
214 self.discover_devices(found) | |
215 | |
216 def ad_hoc_remotes_get_cb(self, remotes_data, found): | |
217 found.insert(0, remotes_data) | |
218 if len(found) == 2: | |
219 self.show_devices(found) | |
220 | |
221 def ad_hoc_remotes_get_eb(self, failure_, found): | |
222 G.host.errback(failure_, title=_("discovery error"), | |
223 message=_("can't check remote controllers: {msg}")) | |
224 found.insert(0, []) | |
225 if len(found) == 2: | |
226 self.show_devices(found) | |
227 | |
228 def get_remotes(self, found): | |
229 self.host.bridge.ad_hoc_remotes_get( | |
230 self.profile, | |
231 callback=partial(self.ad_hoc_remotes_get_cb, found=found), | |
232 errback=partial(self.ad_hoc_remotes_get_eb,found=found)) | |
233 | |
234 def _disco_find_by_features_cb(self, data, found): | |
235 found.append(data) | |
236 if len(found) == 2: | |
237 self.show_devices(found) | |
238 | |
239 def _disco_find_by_features_eb(self, failure_, found): | |
240 G.host.errback(failure_, title=_("discovery error"), | |
241 message=_("can't check devices: {msg}")) | |
242 found.append(({}, {}, {})) | |
243 if len(found) == 2: | |
244 self.show_devices(found) | |
245 | |
246 def discover_devices(self, found): | |
247 """Looks for devices handling file "File Information Sharing" and display them""" | |
248 try: | |
249 namespace = self.host.ns_map['commands'] | |
250 except KeyError: | |
251 msg = _("can't find ad-hoc commands namespace, is the plugin running?") | |
252 log.warning(msg) | |
253 G.host.add_note(_("missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR) | |
254 return | |
255 self.host.bridge.disco_find_by_features( | |
256 [namespace], [], False, True, True, True, False, self.profile, | |
257 callback=partial(self._disco_find_by_features_cb, found=found), | |
258 errback=partial(self._disco_find_by_features_eb, found=found)) | |
259 | |
260 def show_devices(self, found): | |
261 remotes_data, (entities_services, entities_own, entities_roster) = found | |
262 if remotes_data: | |
263 title = _("media players remote controls") | |
264 self.stack_layout.add_widget(CategorySeparator(text=title)) | |
265 | |
266 for remote_data in remotes_data: | |
267 device_jid, node, name = remote_data | |
268 wid = RemoteItemWidget(device_jid, node, name, self) | |
269 self.stack_layout.add_widget(wid) | |
270 | |
271 for entities_map, title in ((entities_services, | |
272 _('services')), | |
273 (entities_own, | |
274 _('your devices')), | |
275 (entities_roster, | |
276 _('your contacts devices'))): | |
277 if entities_map: | |
278 self.stack_layout.add_widget(CategorySeparator(text=title)) | |
279 for entity_str, entity_ids in entities_map.items(): | |
280 entity_jid = jid.JID(entity_str) | |
281 item = RemoteDeviceWidget( | |
282 self, entity_jid, Identities(entity_ids)) | |
283 self.stack_layout.add_widget(item) | |
284 if (not remotes_data and not entities_services and not entities_own | |
285 and not entities_roster): | |
286 self.stack_layout.add_widget(Label( | |
287 size_hint=(1, 1), | |
288 halign='center', | |
289 text_size=self.size, | |
290 text=_("No sharing device found"))) |