Mercurial > libervia-desktop-kivy
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libervia/desktop_kivy/plugins/plugin_wid_remote.py Fri Jun 02 18:26:16 2023 +0200 @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 + + +#Libervia Desktop-Kivy +# Copyright (C) 2016-2021 Jérôme Poisson (goffi@goffi.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +from libervia.backend.core import log as logging +from libervia.backend.core.i18n import _ +from libervia.frontends.quick_frontend import quick_widgets +from ..core import cagou_widget +from ..core.constants import Const as C +from ..core.behaviors import TouchMenuBehavior, FilterBehavior +from ..core.common_widgets import (Identities, ItemWidget, DeviceWidget, + CategorySeparator) +from libervia.backend.tools.common import template_xmlui +from libervia.backend.tools.common import data_format +from libervia.desktop_kivy.core import xmlui +from libervia.frontends.tools import jid +from kivy import properties +from kivy.uix.label import Label +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.floatlayout import FloatLayout +from libervia.desktop_kivy import G +from functools import partial + + +log = logging.getLogger(__name__) + +PLUGIN_INFO = { + "name": _("remote control"), + "main": "RemoteControl", + "description": _("universal remote control"), + "icon_symbol": "signal", +} + +NOTE_TITLE = _("Media Player Remote Control") + + +class RemoteItemWidget(ItemWidget): + + def __init__(self, device_jid, node, name, main_wid, **kw): + self.device_jid = device_jid + self.node = node + super(RemoteItemWidget, self).__init__(name=name, main_wid=main_wid, **kw) + + def do_item_action(self, touch): + self.main_wid.layout.clear_widgets() + player_wid = MediaPlayerControlWidget(main_wid=self.main_wid, remote_item=self) + self.main_wid.layout.add_widget(player_wid) + + +class MediaPlayerControlWidget(BoxLayout): + main_wid = properties.ObjectProperty() + remote_item = properties.ObjectProperty() + status = properties.OptionProperty("play", options=("play", "pause", "stop")) + title = properties.StringProperty() + identity = properties.StringProperty() + command = properties.DictProperty() + ui_tpl = properties.ObjectProperty() + + @property + def profile(self): + return self.main_wid.profile + + def update_ui(self, action_data_s): + action_data = data_format.deserialise(action_data_s) + xmlui_raw = action_data['xmlui'] + ui_tpl = template_xmlui.create(G.host, xmlui_raw) + self.ui_tpl = ui_tpl + for prop in ('Title', 'Identity'): + try: + setattr(self, prop.lower(), ui_tpl.widgets[prop].value) + except KeyError: + log.warning(_("Missing field: {name}").format(name=prop)) + playback_status = self.ui_tpl.widgets['PlaybackStatus'].value + if playback_status == "Playing": + self.status = "pause" + elif playback_status == "Paused": + self.status = "play" + elif playback_status == "Stopped": + self.status = "play" + else: + G.host.add_note( + title=NOTE_TITLE, + message=_("Unknown playback status: playback_status") + .format(playback_status=playback_status), + level=C.XMLUI_DATA_LVL_WARNING) + self.commands = {v:k for k,v in ui_tpl.widgets['command'].options} + + def ad_hoc_run_cb(self, xmlui_raw): + ui_tpl = template_xmlui.create(G.host, xmlui_raw) + data = {xmlui.XMLUIPanel.escape("media_player"): self.remote_item.node, + "session_id": ui_tpl.session_id} + G.host.bridge.action_launch( + ui_tpl.submit_id, data_format.serialise(data), + self.profile, callback=self.update_ui, + errback=self.main_wid.errback + ) + + def on_remote_item(self, __, remote): + NS_MEDIA_PLAYER = G.host.ns_map["mediaplayer"] + G.host.bridge.ad_hoc_run(str(remote.device_jid), NS_MEDIA_PLAYER, self.profile, + callback=self.ad_hoc_run_cb, + errback=self.main_wid.errback) + + def do_cmd(self, command): + try: + cmd_value = self.commands[command] + except KeyError: + G.host.add_note( + title=NOTE_TITLE, + message=_("{command} command is not managed").format(command=command), + level=C.XMLUI_DATA_LVL_WARNING) + else: + data = {xmlui.XMLUIPanel.escape("command"): cmd_value, + "session_id": self.ui_tpl.session_id} + # hidden values are normally transparently managed by XMLUIPanel + # but here we have to add them by hand + hidden = {xmlui.XMLUIPanel.escape(k):v + for k,v in self.ui_tpl.hidden.items()} + data.update(hidden) + G.host.bridge.action_launch( + self.ui_tpl.submit_id, data_format.serialise(data), self.profile, + callback=self.update_ui, errback=self.main_wid.errback + ) + + +class RemoteDeviceWidget(DeviceWidget): + + def xmlui_cb(self, data, cb_id, profile): + if 'xmlui' in data: + xml_ui = xmlui.create( + G.host, data['xmlui'], callback=self.xmlui_cb, profile=profile) + if isinstance(xml_ui, xmlui.XMLUIDialog): + self.main_wid.show_root_widget() + xml_ui.show() + else: + xml_ui.set_close_cb(self.on_close) + self.main_wid.layout.add_widget(xml_ui) + else: + if data: + log.warning(_("Unhandled data: {data}").format(data=data)) + self.main_wid.show_root_widget() + + def on_close(self, __, reason): + if reason == C.XMLUI_DATA_CANCELLED: + self.main_wid.show_root_widget() + else: + self.main_wid.layout.clear_widgets() + + def ad_hoc_run_cb(self, data): + xml_ui = xmlui.create(G.host, data, callback=self.xmlui_cb, profile=self.profile) + xml_ui.set_close_cb(self.on_close) + self.main_wid.layout.add_widget(xml_ui) + + def do_item_action(self, touch): + self.main_wid.layout.clear_widgets() + G.host.bridge.ad_hoc_run(str(self.entity_jid), '', self.profile, + callback=self.ad_hoc_run_cb, errback=self.main_wid.errback) + + +class DevicesLayout(FloatLayout): + """Layout used to show devices""" + layout = properties.ObjectProperty() + + +class RemoteControl(quick_widgets.QuickWidget, cagou_widget.LiberviaDesktopKivyWidget, FilterBehavior, + TouchMenuBehavior): + SINGLE=False + layout = properties.ObjectProperty() + + def __init__(self, host, target, profiles): + quick_widgets.QuickWidget.__init__(self, host, target, profiles) + cagou_widget.LiberviaDesktopKivyWidget.__init__(self) + FilterBehavior.__init__(self) + TouchMenuBehavior.__init__(self) + self.stack_layout = None + self.show_root_widget() + + def errback(self, failure_): + """Generic errback which add a warning note and go back to root widget""" + G.host.add_note( + title=NOTE_TITLE, + message=_("Can't use remote control: {reason}").format(reason=failure_), + level=C.XMLUI_DATA_LVL_WARNING) + self.show_root_widget() + + def key_input(self, window, key, scancode, codepoint, modifier): + if key == 27: + self.show_root_widget() + return True + + def show_root_widget(self): + self.layout.clear_widgets() + devices_layout = DevicesLayout() + self.stack_layout = devices_layout.layout + self.layout.add_widget(devices_layout) + found = [] + self.get_remotes(found) + self.discover_devices(found) + + def ad_hoc_remotes_get_cb(self, remotes_data, found): + found.insert(0, remotes_data) + if len(found) == 2: + self.show_devices(found) + + def ad_hoc_remotes_get_eb(self, failure_, found): + G.host.errback(failure_, title=_("discovery error"), + message=_("can't check remote controllers: {msg}")) + found.insert(0, []) + if len(found) == 2: + self.show_devices(found) + + def get_remotes(self, found): + self.host.bridge.ad_hoc_remotes_get( + self.profile, + callback=partial(self.ad_hoc_remotes_get_cb, found=found), + errback=partial(self.ad_hoc_remotes_get_eb,found=found)) + + def _disco_find_by_features_cb(self, data, found): + found.append(data) + if len(found) == 2: + self.show_devices(found) + + def _disco_find_by_features_eb(self, failure_, found): + G.host.errback(failure_, title=_("discovery error"), + message=_("can't check devices: {msg}")) + found.append(({}, {}, {})) + if len(found) == 2: + self.show_devices(found) + + def discover_devices(self, found): + """Looks for devices handling file "File Information Sharing" and display them""" + try: + namespace = self.host.ns_map['commands'] + except KeyError: + msg = _("can't find ad-hoc commands namespace, is the plugin running?") + log.warning(msg) + G.host.add_note(_("missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR) + return + self.host.bridge.disco_find_by_features( + [namespace], [], False, True, True, True, False, self.profile, + callback=partial(self._disco_find_by_features_cb, found=found), + errback=partial(self._disco_find_by_features_eb, found=found)) + + def show_devices(self, found): + remotes_data, (entities_services, entities_own, entities_roster) = found + if remotes_data: + title = _("media players remote controls") + self.stack_layout.add_widget(CategorySeparator(text=title)) + + for remote_data in remotes_data: + device_jid, node, name = remote_data + wid = RemoteItemWidget(device_jid, node, name, self) + self.stack_layout.add_widget(wid) + + for entities_map, title in ((entities_services, + _('services')), + (entities_own, + _('your devices')), + (entities_roster, + _('your contacts devices'))): + if entities_map: + self.stack_layout.add_widget(CategorySeparator(text=title)) + for entity_str, entity_ids in entities_map.items(): + entity_jid = jid.JID(entity_str) + item = RemoteDeviceWidget( + self, entity_jid, Identities(entity_ids)) + self.stack_layout.add_widget(item) + if (not remotes_data and not entities_services and not entities_own + and not entities_roster): + self.stack_layout.add_widget(Label( + size_hint=(1, 1), + halign='center', + text_size=self.size, + text=_("No sharing device found")))