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
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")))