diff --git a/libervia/tui/base.py b/libervia/tui/base.py index 7b3a4daa..2f2a7724 100644 --- a/libervia/tui/base.py +++ b/libervia/tui/base.py @@ -28,9 +28,10 @@ from libervia.backend.core import log as logging log = logging.getLogger(__name__) from libervia.backend.tools import config as tools_config import urwid -from urwid.util import is_wide_char +from urwid import is_wide_char from urwid_satext import sat_widgets from libervia.frontends.quick_frontend.quick_app import QuickApp +from libervia.frontends.quick_frontend import quick_bridge from libervia.frontends.quick_frontend import quick_utils from libervia.frontends.quick_frontend import quick_chat from libervia.tui.profile_manager import ProfileManager @@ -45,6 +46,7 @@ from libervia.tui.keys import action_key_map as a_key from libervia.frontends.tools.misc import InputHistory from libervia.backend.tools.common import dynamic_import from libervia.frontends.tools import jid +import asyncio import signal import sys @@ -67,10 +69,19 @@ class TUISettings(tools_config.FrontendSettings, key="tui"): ## bridge handling # we get bridge name from conf and initialise the right class accordingly main_config = tools_config.parse_main_conf(ignore_cache=True) -bridge_name = next(iter(main_config.common.bridges)).type +bridge_name = next(iter(main_config.bridges)).type if "dbus" not in bridge_name: print("only D-Bus bridge is currently supported") sys.exit(3) +# install the asyncio twisted reactor +try: + asyncio.get_event_loop() +except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) +from twisted.internet import asyncioreactor + +asyncioreactor.install() class EditBar(sat_widgets.ModalEdit): @@ -338,9 +349,7 @@ class LiberviaTUIApp(QuickApp, InputHistory): AVATARS_HANDLER = False def __init__(self): - bridge_module = dynamic_import.import_bridge( - bridge_name, "libervia.frontends.bridge" - ) + bridge_module = dynamic_import.import_bridge(bridge_name) if bridge_module is None: log.error("Can't import {} bridge".format(bridge_name)) sys.exit(3) @@ -348,16 +357,18 @@ class LiberviaTUIApp(QuickApp, InputHistory): log.debug("Loading {} bridge".format(bridge_name)) QuickApp.__init__( self, - bridge_factory=bridge_module.bridge, + # AIOBridge is the only bridge compatible with the asyncio loop + bridge_factory=lambda: quick_bridge.QuickBridgeAdapter( + bridge_module.AIOBridge() + ), xmlui=xmlui, check_options=quick_utils.check_options, connect_bridge=False, ) self.tui_settings = main_config.get_settings(TUISettings) ## main loop setup ## - event_loop = ( - urwid.GLibEventLoop if "dbus" in bridge_name else urwid.TwistedEventLoop - ) + # the new D-Bus bridge is twisted based (txdbus) + event_loop = urwid.TwistedEventLoop self.loop = urwid.MainLoop( urwid.SolidFill(), C.PALETTE, @@ -373,6 +384,10 @@ class LiberviaTUIApp(QuickApp, InputHistory): def on_bridge_connected(self): ##misc setup## + self._version = None + self.bridge.version_get( + callback=self._version_get_cb, errback=lambda __: None + ) self._visible_widgets = set() self.notif_bar = sat_widgets.NotificationBar() urwid.connect_signal(self.notif_bar, "change", self.on_notification) @@ -400,6 +415,9 @@ class LiberviaTUIApp(QuickApp, InputHistory): self.loop.widget = self.main_widget = ProfileManager(self) self.post_init() + def _version_get_cb(self, version): + self._version = version + @property def visible_widgets(self): return self._visible_widgets @@ -579,7 +597,7 @@ class LiberviaTUIApp(QuickApp, InputHistory): self.__saved_overlay = None elif ( - input_ == a_key["DEBUG"] and ".dev0" in self.bridge.version_get() + input_ == a_key["DEBUG"] and ".dev0" in (self._version or "") ): # Debug only for dev versions self.debug() elif input_ == a_key["CONTACTS_HIDE"]: # user wants to (un)hide the contact lists @@ -614,6 +632,9 @@ class LiberviaTUIApp(QuickApp, InputHistory): def add_menus(self, menu, type_filter, menu_data=None): """Add cached menus to instance + + The menus are retrieved asynchronously from the bridge. + @param menu: sat_widgets.Menu instance @param type_filter: menu type like is sat.core.sat_main.import_menu @param menu_data: data to send with these menus @@ -623,16 +644,24 @@ class LiberviaTUIApp(QuickApp, InputHistory): def add_menu_cb(callback_id): self.action_launch(callback_id, menu_data, profile=self.current_profile) - for id_, type_, path, path_i18n, extra in self.bridge.menus_get( - "", C.NO_SECURITY_LIMIT - ): # TODO: manage extra - if type_ != type_filter: - continue - if len(path) != 2: - raise NotImplementedError("Menu with a path != 2 are not implemented yet") - menu.add_menu( - path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) - ) + def menus_get_cb(menus): + for id_, type_, path, path_i18n, extra in menus: # TODO: manage extra + if type_ != type_filter: + continue + if len(path) != 2: + raise NotImplementedError( + "Menu with a path != 2 are not implemented yet" + ) + menu.add_menu( + path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) + ) + + def menus_get_eb(failure): + log.error("Can't get menus: {}".format(failure)) + + self.bridge.menus_get( + "", C.NO_SECURITY_LIMIT, callback=menus_get_cb, errback=menus_get_eb + ) def _build_menu_roller(self): menu = sat_widgets.Menu(self.loop) @@ -1016,19 +1045,25 @@ class LiberviaTUIApp(QuickApp, InputHistory): def on_join_room_request(self, menu): """User wants to join a MUC room""" - pop_up_widget = sat_widgets.InputDialog( - _("Entering a MUC room"), - _("Please enter MUC's JID"), - default_txt=self.bridge.muc_get_default_service(), - ok_cb=self.on_join_room, + + def show_join_dialog(default_service): + pop_up_widget = sat_widgets.InputDialog( + _("Entering a MUC room"), + _("Please enter MUC's JID"), + default_txt=default_service, + ok_cb=self.on_join_room, + ) + pop_up_widget.set_callback( + "cancel", lambda dummy: self.remove_pop_up(pop_up_widget) + ) + self.show_pop_up(pop_up_widget) + + self.bridge.muc_get_default_service( + callback=show_join_dialog, errback=lambda __: show_join_dialog("") ) - pop_up_widget.set_callback( - "cancel", lambda dummy: self.remove_pop_up(pop_up_widget) - ) - self.show_pop_up(pop_up_widget) def on_about_request(self, menu): - self.alert(_("About"), C.APP_NAME + " v" + self.bridge.version_get()) + self.alert(_("About"), C.APP_NAME + " v" + (self._version or "")) # MISC CALLBACKS# diff --git a/libervia/tui/game_tarot.py b/libervia/tui/game_tarot.py index c303ff61..82d4c1f9 100644 --- a/libervia/tui/game_tarot.py +++ b/libervia/tui/game_tarot.py @@ -174,10 +174,13 @@ class Card(TarotCard): return CardDisplayer(self) -class Table(urwid.FlowWidget): +class Table(urwid.Widget): """Represent the cards currently on the table""" + _sizing = frozenset([urwid.Sizing.FLOW]) + def __init__(self): + super().__init__() self.top = self.left = self.bottom = self.right = None def put_card(self, location, card): @@ -313,7 +316,7 @@ class TarotGame(QuickTarotGame, urwid.WidgetWrap): if not winners and not loosers: title = _("Draw game") else: - title = _("You win \o/") if self.player_nick in winners else _("You loose :(") + title = _("You win \\o/") if self.player_nick in winners else _("You loose :(") form = xmlui.create( self.parent.host, xml_data, diff --git a/libervia/tui/notify.py b/libervia/tui/notify.py index f34b7c17..096ac031 100644 --- a/libervia/tui/notify.py +++ b/libervia/tui/notify.py @@ -17,7 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import dbus +try: + import dbus +except ImportError: + dbus = None class Notify(object): diff --git a/libervia/tui/profile_manager.py b/libervia/tui/profile_manager.py index 46ab7411..7d4cac61 100644 --- a/libervia/tui/profile_manager.py +++ b/libervia/tui/profile_manager.py @@ -37,11 +37,11 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap): self.pass_wid = sat_widgets.Password(_("Password:"), align="center") style = ["no_first_select"] - profiles = host.bridge.profiles_list_get() - profiles.sort() self.list_profile = sat_widgets.List( - profiles, style=style, align="center", on_change=self.on_profile_change + [], style=style, align="center", on_change=self.on_profile_change ) + # the profiles list is retrieved asynchronously + self.refill_profiles() # new & delete buttons buttons = [ diff --git a/libervia/tui/progress.py b/libervia/tui/progress.py index 9614d8dd..88ef0c3a 100644 --- a/libervia/tui/progress.py +++ b/libervia/tui/progress.py @@ -54,24 +54,31 @@ class Progress(urwid.WidgetWrap, quick_widgets.QuickWidget): def progress_cb(self, loop, data): progress_id, message, profile = data - data = self.host.bridge.progress_get(progress_id, profile) - pbar = self.progress_dict[(progress_id, profile)]["progress"] - if data: - if self.progress_dict[(progress_id, profile)]["state"] == "init": - # first answer, we must construct the bar - self.progress_dict[(progress_id, profile)]["state"] = "progress" - pbar.done = float(data["size"]) - pbar.set_completion(float(data["position"])) - self.update_not_bar() - else: - if self.progress_dict[(progress_id, profile)]["state"] == "progress": - self.progress_dict[(progress_id, profile)]["state"] = "done" - pbar.set_completion(pbar.done) - self.update_not_bar() + def progress_get_cb(data): + entry = self.progress_dict.get((progress_id, profile)) + if entry is None: + # the bar has been removed in the meantime return + pbar = entry["progress"] + if data: + if entry["state"] == "init": + # first answer, we must construct the bar + entry["state"] = "progress" + pbar.done = float(data["size"]) - loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile)) + pbar.set_completion(float(data["position"])) + self.update_not_bar() + else: + if entry["state"] == "progress": + entry["state"] = "done" + pbar.set_completion(pbar.done) + self.update_not_bar() + return + + loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile)) + + self.host.bridge.progress_get(progress_id, profile, callback=progress_get_cb) def _remove_bar(self, progress_id, profile): wid = self.progress_dict[(progress_id, profile)]["full"] diff --git a/libervia/tui/status.py b/libervia/tui/status.py index 2aafca1d..94228701 100644 --- a/libervia/tui/status.py +++ b/libervia/tui/status.py @@ -45,34 +45,44 @@ class StatusBar(urwid.Columns): urwid.connect_signal(self.status, "click", self.on_status_click) def on_presence_click(self, sender=None): - if not self.host.bridge.is_connected( - self.host.current_profile - ): # FIXME: manage multi-profiles - return - options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE] - list_widget = sat_widgets.GenericList( - options=options, - option_type=sat_widgets.ClickableText, - on_click=self.on_change, + def show_presence_popup(connected): + if not connected: + return + options = [ + commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE + ] + list_widget = sat_widgets.GenericList( + options=options, + option_type=sat_widgets.ClickableText, + on_click=self.on_change, + ) + decorated = sat_widgets.LabelLine( + list_widget, sat_widgets.SurroundedText(_("Set your presence")) + ) + self.host.show_pop_up(decorated) + + # FIXME: manage multi-profiles + self.host.bridge.is_connected( + self.host.current_profile, callback=show_presence_popup ) - decorated = sat_widgets.LabelLine( - list_widget, sat_widgets.SurroundedText(_("Set your presence")) - ) - self.host.show_pop_up(decorated) def on_status_click(self, sender=None): - if not self.host.bridge.is_connected( - self.host.current_profile - ): # FIXME: manage multi-profiles - return - pop_up_widget = sat_widgets.InputDialog( - _("Set your status"), - _("New status"), - default_txt=self.status.get_text(), - cancel_cb=lambda _: self.host.remove_pop_up(), - ok_cb=self.on_change, + def show_status_popup(connected): + if not connected: + return + pop_up_widget = sat_widgets.InputDialog( + _("Set your status"), + _("New status"), + default_txt=self.status.get_text(), + cancel_cb=lambda _: self.host.remove_pop_up(), + ok_cb=self.on_change, + ) + self.host.show_pop_up(pop_up_widget) + + # FIXME: manage multi-profiles + self.host.bridge.is_connected( + self.host.current_profile, callback=show_status_popup ) - self.host.show_pop_up(pop_up_widget) def on_change(self, sender=None, user_data=None): new_value = user_data.get_text() diff --git a/pyproject.toml b/pyproject.toml index b9474811..4fa94510 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,7 +57,7 @@ dependencies = [ "twisted[tls] >= 22.10", "twomemo >= 1.0.0, < 2", "txdbus", - "urwid >= 1.2.0, < 3", + "urwid >= 4.2.5, < 5", "urwid-satext", "xmlschema < 4.3.1", "tx-xmpp>=0.15",