From 76815d6fa1196c973db2a875886fea3901a3c2c3 Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Mon, 18 Mar 2024 15:42:21 +0100 Subject: [PATCH] views: redraw screen after receiving event Views that are dynamically updated using asynchronous events that are handled by urwid need to be redrawn. Ensure that such events trigger a redraw. Signed-off-by: Olivier Gayot --- subiquity/client/client.py | 4 ++-- subiquity/client/controllers/filesystem.py | 20 +++++++++++++++++--- subiquity/client/controllers/network.py | 7 +++++++ subiquity/client/controllers/ssh.py | 3 +++ subiquity/ui/views/drivers.py | 1 + subiquity/ui/views/error.py | 13 +++++++++++-- subiquity/ui/views/help.py | 12 ++++++++++-- subiquity/ui/views/installprogress.py | 2 ++ subiquity/ui/views/keyboard.py | 7 ++++++- subiquity/ui/views/mirror.py | 1 + subiquity/ui/views/refresh.py | 1 + subiquity/ui/views/snaplist.py | 13 +++++++++++-- subiquity/ui/views/ubuntu_pro.py | 3 +++ subiquity/ui/views/zdev.py | 1 + subiquitycore/tui.py | 11 +++++++---- subiquitycore/ui/utils.py | 2 +- subiquitycore/ui/views/network.py | 18 +++++++++++------- system_setup/client/controllers/summary.py | 2 ++ 18 files changed, 97 insertions(+), 24 deletions(-) diff --git a/subiquity/client/client.py b/subiquity/client/client.py index 6714eb78..04c31d78 100644 --- a/subiquity/client/client.py +++ b/subiquity/client/client.py @@ -585,14 +585,14 @@ class SubiquityClient(TuiApplication): else: super().unhandled_input(key) - def debug_shell(self, after_hook=None): + async def debug_shell(self, after_hook=None): def _before(): os.system("clear") print(DEBUG_SHELL_INTRO) env = orig_environ(os.environ) cmd = ["bash"] - self.run_command_in_foreground( + await self.run_command_in_foreground( cmd, env=env, before_hook=_before, after_hook=after_hook, cwd="/" ) diff --git a/subiquity/client/controllers/filesystem.py b/subiquity/client/controllers/filesystem.py index 31019f48..a088dbcf 100644 --- a/subiquity/client/controllers/filesystem.py +++ b/subiquity/client/controllers/filesystem.py @@ -81,6 +81,7 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator): self.current_view = await self.make_guided_ui(status) if isinstance(self.ui.body, SlowProbing): self.ui.set_body(self.current_view) + await self.app.redraw_screen() else: log.debug("not refreshing the display. Current display is %r", self.ui.body) @@ -273,18 +274,31 @@ class FilesystemController(SubiquityTuiController, FilesystemManipulator): self.ui.set_body(FilesystemView(self.model, self)) def guided_choice(self, choice): - run_bg_task(self._guided_choice(choice)) + async def show_guided_and_redraw(): + await self._guided_choice(choice) + await self.app.redraw_screen() + + run_bg_task(show_guided_and_redraw()) async def _guided(self): self.ui.set_body((await self.make_ui())()) def guided(self): - run_bg_task(self._guided()) + async def guided_and_redraw(): + await self._guided() + await self.app.redraw_screen() + + run_bg_task(guided_and_redraw()) def reset(self, refresh_view): + async def reset_and_redraw(): + await self._reset(refresh_view) + await self.app.redraw_screen() + log.info("Resetting Filesystem model") self.app.ui.block_input = True - run_bg_task(self._reset(refresh_view)) + + run_bg_task(reset_and_redraw()) async def _reset(self, refresh_view): status = await self.endpoint.reset.POST() diff --git a/subiquity/client/controllers/network.py b/subiquity/client/controllers/network.py index e4a92501..edd28843 100644 --- a/subiquity/client/controllers/network.py +++ b/subiquity/client/controllers/network.py @@ -69,27 +69,34 @@ class NetworkController(SubiquityTuiController, NetworkAnswersMixin): if act == LinkAction.DEL: self.view.del_link(info) + self.view.request_redraw_if_visible() + async def route_watch_POST(self, has_default_route: bool) -> None: if self.view is not None: self.view.update_has_default_route(has_default_route) + self.view.request_redraw_if_visible() async def apply_starting_POST(self) -> None: if self.view is not None: self.view.show_apply_spinner() + self.view.request_redraw_if_visible() async def apply_stopping_POST(self) -> None: if self.view is not None: self.view.hide_apply_spinner() + self.view.request_redraw_if_visible() async def apply_error_POST(self, stage: str) -> None: if self.view is not None: self.view.show_network_error(stage) + self.view.request_redraw_if_visible() async def wlan_support_install_finished_POST( self, state: PackageInstallState ) -> None: if self.view: self.view.update_for_wlan_support_install_state(state.name) + self.view.request_redraw_if_visible() async def subscribe(self): self.tdir = tempfile.mkdtemp() diff --git a/subiquity/client/controllers/ssh.py b/subiquity/client/controllers/ssh.py index 49db6597..c40a250d 100644 --- a/subiquity/client/controllers/ssh.py +++ b/subiquity/client/controllers/ssh.py @@ -100,6 +100,7 @@ class SSHController(SubiquityTuiController): self.ui.body.fetching_ssh_keys_failed( _("Importing keys failed:"), response.error ) + self.ui.body.request_redraw() return elif response.status == SSHFetchIdStatus.FINGERPRINT_ERROR: if isinstance(self.ui.body, SSHView): @@ -107,12 +108,14 @@ class SSHController(SubiquityTuiController): _("ssh-keygen failed to show fingerprint of downloaded keys:"), response.error, ) + self.ui.body.request_redraw() return identities = response.identities if isinstance(self.ui.body, SSHView): self.ui.body.confirm_ssh_keys(ssh_import_id, identities) + self.ui.body.request_redraw() else: log.debug("ui.body of unexpected instance: %s", type(self.ui.body).__name__) diff --git a/subiquity/ui/views/drivers.py b/subiquity/ui/views/drivers.py index 4a28cc03..fa150b79 100644 --- a/subiquity/ui/views/drivers.py +++ b/subiquity/ui/views/drivers.py @@ -120,6 +120,7 @@ class DriversView(BaseView): self.make_main(install, drivers) else: self.make_no_drivers() + self.request_redraw_if_visible() def make_no_drivers(self) -> None: """Change the view into an information page that shows that no diff --git a/subiquity/ui/views/error.py b/subiquity/ui/views/error.py index a5acaa7e..2eb30700 100644 --- a/subiquity/ui/views/error.py +++ b/subiquity/ui/views/error.py @@ -336,15 +336,24 @@ class ErrorReportStretchy(Stretchy): if self.pile.selectable(): while not self.pile.focus.selectable(): self.pile.focus_position += 1 + await self.app.redraw_screen() def debug_shell(self, sender): - self.app.debug_shell() + async def debug_shell_and_redraw(): + await self.app.debug_shell() + await self.app.redraw_screen() + + run_bg_task(debug_shell_and_redraw()) def restart(self, sender): self.app.restart(restart_server=True) def view_report(self, sender): - self.app.run_command_in_foreground(["less", self.report.path]) + async def run_less_and_redraw(): + await self.app.run_command_in_foreground(["less", self.report.path]) + await self.app.redraw_screen() + + run_bg_task(run_less_and_redraw()) def submit(self, sender): self.report.upload() diff --git a/subiquity/ui/views/help.py b/subiquity/ui/views/help.py index f3c5f74f..2ae767b9 100644 --- a/subiquity/ui/views/help.py +++ b/subiquity/ui/views/help.py @@ -429,8 +429,12 @@ class HelpMenu(PopUpLauncher): self.open_pop_up() def _open(self, sender): + async def get_ssh_info_and_redraw(): + await self._get_ssh_info() + await self.app.redraw_screen() + log.debug("open help menu") - run_bg_task(self._get_ssh_info()) + run_bg_task(get_ssh_info_and_redraw()) def create_pop_up(self): self._menu = OpenHelpMenu(self) @@ -505,7 +509,11 @@ class HelpMenu(PopUpLauncher): self._show_overlay(GlobalKeyStretchy(self.app)) def debug_shell(self, sender): - self.app.debug_shell() + async def debug_shell_and_redraw(): + await self.app.debug_shell() + await self.app.redraw_screen() + + run_bg_task(debug_shell_and_redraw()) def toggle_rich(self, sender): self.app.toggle_rich() diff --git a/subiquity/ui/views/installprogress.py b/subiquity/ui/views/installprogress.py index 89c7b009..9a9e3ff9 100644 --- a/subiquity/ui/views/installprogress.py +++ b/subiquity/ui/views/installprogress.py @@ -101,6 +101,7 @@ class ProgressView(BaseView): ) self.ongoing[context_id] = len(walker) self._add_line(self.event_listbox, new_line) + self.request_redraw_if_visible() def event_finish(self, context_id): index = self.ongoing.pop(context_id, None) @@ -110,6 +111,7 @@ class ProgressView(BaseView): spinner = walker[index][1] spinner.stop() walker[index] = walker[index][0] + self.request_redraw_if_visible() def event_other(self, message: str, event_type: str) -> None: """Print events that aren't start or finish events""" diff --git a/subiquity/ui/views/keyboard.py b/subiquity/ui/views/keyboard.py index f16934b8..a6db7426 100644 --- a/subiquity/ui/views/keyboard.py +++ b/subiquity/ui/views/keyboard.py @@ -270,8 +270,12 @@ class Detector: self.do_step(step_index) def do_step(self, step_index): + async def do_step_and_redraw(): + await self._do_step(step_index) + self.keyboard_view.request_redraw_if_visible() + self.abort() - run_bg_task(self._do_step(step_index)) + run_bg_task(do_step_and_redraw()) async def _do_step(self, step_index): log.debug("moving to step %s", step_index) @@ -433,6 +437,7 @@ class KeyboardView(BaseView): ) if needs_toggle: self.show_stretchy_overlay(ToggleQuestion(self, setting)) + self.request_redraw_if_visible() else: self.really_done(setting) diff --git a/subiquity/ui/views/mirror.py b/subiquity/ui/views/mirror.py index 7a581aa1..1ee84ca8 100644 --- a/subiquity/ui/views/mirror.py +++ b/subiquity/ui/views/mirror.py @@ -190,6 +190,7 @@ class MirrorView(BaseView): await asyncio.sleep(1 / self.controller.app.scale_factor) status = await self.controller.endpoint.check_mirror.progress.GET() self.update_status(status) + self.request_redraw_if_visible() if check_state.status == MirrorCheckStatus.FAILED: self.output_wrap._w = Pile( diff --git a/subiquity/ui/views/refresh.py b/subiquity/ui/views/refresh.py index bf3723ec..ca7462d3 100644 --- a/subiquity/ui/views/refresh.py +++ b/subiquity/ui/views/refresh.py @@ -262,6 +262,7 @@ class RefreshView(BaseView): self.update_failed(err) return self.update_progress(change) + self.request_redraw_if_visible() await asyncio.sleep(0.1) def try_update_again(self, sender=None): diff --git a/subiquity/ui/views/snaplist.py b/subiquity/ui/views/snaplist.py index b874c34f..f9fc11e1 100644 --- a/subiquity/ui/views/snaplist.py +++ b/subiquity/ui/views/snaplist.py @@ -350,7 +350,12 @@ class SnapCheckBox(CheckBox): def keypress(self, size, key): if key.startswith("enter"): - schedule_task(self.load_info()) + + async def load_info_and_redraw(): + await self.load_info() + self.parent.request_redraw_if_visible() + + schedule_task(load_info_and_redraw()) else: return super().keypress(size, key) @@ -379,6 +384,10 @@ class SnapListView(BaseView): self.loaded(data) def wait_load(self): + async def wait_load_and_redraw(): + await self._wait_load(spinner) + self.request_redraw_if_visible() + spinner = Spinner(style="dots") spinner.start() self._w = screen( @@ -386,7 +395,7 @@ class SnapListView(BaseView): [ok_btn(label=_("Continue"), on_press=self.done)], excerpt=_("Loading server snaps from store, please wait..."), ) - schedule_task(self._wait_load(spinner)) + schedule_task(wait_load_and_redraw()) async def _wait_load(self, spinner): # If we show the loading screen at all, we want to show it for diff --git a/subiquity/ui/views/ubuntu_pro.py b/subiquity/ui/views/ubuntu_pro.py index 3fb602b5..84ea2459 100644 --- a/subiquity/ui/views/ubuntu_pro.py +++ b/subiquity/ui/views/ubuntu_pro.py @@ -535,6 +535,7 @@ class UbuntuProView(BaseView): self.remove_overlay() widget = TokenAddedWidget(parent=self, on_continue=show_subscription) self.show_stretchy_overlay(widget) + self.request_redraw_if_visible() def upgrade_mode_done(self, form: UpgradeModeForm) -> None: """Open the loading dialog and asynchronously check if the token is @@ -563,6 +564,7 @@ class UbuntuProView(BaseView): form.validated() elif status == UbuntuProCheckTokenStatus.UNKNOWN_ERROR: self.show_unknown_error() + self.request_redraw_if_visible() token: str = form.with_contract_token_subform.value["token"] checking_token_overlay = CheckingContractToken(self) @@ -580,6 +582,7 @@ class UbuntuProView(BaseView): self.controller.contract_selection_initiate(on_initiated=self.cs_initiated) self.upgrade_mode_form.set_user_code(user_code) + self.request_redraw_if_visible() self.controller.contract_selection_wait( on_contract_selected=self.on_contract_selected, on_timeout=reinitiate, diff --git a/subiquity/ui/views/zdev.py b/subiquity/ui/views/zdev.py index 4eec5c6e..f572232c 100644 --- a/subiquity/ui/views/zdev.py +++ b/subiquity/ui/views/zdev.py @@ -67,6 +67,7 @@ class ZdevList(WidgetWrap): self.parent.controller.chzdev(action, zdevinfo), "Updating..." ) self.update(new_zdevinfos) + self.parent.request_redraw_if_visible() def zdev_action(self, sender, action, zdevinfo): run_bg_task(self._zdev_action(action, zdevinfo)) diff --git a/subiquitycore/tui.py b/subiquitycore/tui.py index 02455462..f8cd5e7b 100644 --- a/subiquitycore/tui.py +++ b/subiquitycore/tui.py @@ -24,7 +24,7 @@ from typing import Callable, Optional, Union import urwid import yaml -from subiquitycore.async_helpers import run_bg_task, schedule_task +from subiquitycore.async_helpers import run_bg_task from subiquitycore.core import Application from subiquitycore.palette import PALETTE_COLOR, PALETTE_MONO from subiquitycore.screen import make_screen @@ -83,7 +83,9 @@ class TuiApplication(Application): self.cur_screen = None self.fg_proc = None - def run_command_in_foreground(self, cmd, before_hook=None, after_hook=None, **kw): + async def run_command_in_foreground( + self, cmd, before_hook=None, after_hook=None, **kw + ): if self.fg_proc is not None: raise Exception("cannot run two fg processes at once") screen = self.urwid_loop.screen @@ -119,7 +121,8 @@ class TuiApplication(Application): urwid.emit_signal(screen, urwid.display_common.INPUT_DESCRIPTORS_CHANGED) if before_hook is not None: before_hook() - schedule_task(_run()) + + await _run() async def make_view_for_controller( self, new @@ -193,7 +196,7 @@ class TuiApplication(Application): async def show_load(): nonlocal ld - ld = LoadingDialog(self.ui.body, message, task_to_cancel) + ld = LoadingDialog(self, message, task_to_cancel) self.ui.body.show_overlay(ld, width=ld.width) await self.redraw_screen() diff --git a/subiquitycore/ui/utils.py b/subiquitycore/ui/utils.py index 6d0b4c66..d589eac7 100644 --- a/subiquitycore/ui/utils.py +++ b/subiquitycore/ui/utils.py @@ -380,4 +380,4 @@ class LoadingDialog(WidgetWrap): self.task_to_cancel.cancel() self.closed = True self.spinner.stop() - self.parent.remove_overlay() + self.parent.ui.body.remove_overlay() diff --git a/subiquitycore/ui/views/network.py b/subiquitycore/ui/views/network.py index 27ed0f63..0832c8ff 100644 --- a/subiquitycore/ui/views/network.py +++ b/subiquitycore/ui/views/network.py @@ -66,6 +66,8 @@ class NetworkDeviceTable(WidgetWrap): def __init__(self, parent, dev_info): self.parent = parent self.dev_info = dev_info + self.dhcp_spinner = Spinner(align="left") + self.dhcp_spinner.rate = 0.3 super().__init__(self._create()) def _create(self): @@ -163,10 +165,8 @@ class NetworkDeviceTable(WidgetWrap): if addrs: address_info.extend([(label, Text(addr)) for addr in addrs]) elif dhcp_status.state == DHCPState.PENDING: - s = Spinner(align="left") - s.rate = 0.3 - s.start() - address_info.append((label, s)) + self.dhcp_spinner.start() + address_info.append((label, self.dhcp_spinner)) elif dhcp_status.state == DHCPState.TIMED_OUT: address_info.append((label, Text(_("timed out")))) elif dhcp_status.state == DHCPState.RECONFIGURE: @@ -301,6 +301,8 @@ class NetworkView(BaseView): self.wlan_support_install_state_showing = False self.error_showing = False + self.apply_spinner = Spinner() + self.update_for_wlan_support_install_state(wlan_support_install_state) super().__init__( @@ -319,6 +321,7 @@ class NetworkView(BaseView): stretchy = ViewInterfaceInfo(self, name, info) stretchy.attach_context(self.controller.context.child("INFO")) self.show_stretchy_overlay(stretchy) + self.request_redraw_if_visible() def _action_INFO(self, name, dev_info): run_bg_task(self._show_INFO(dev_info.name)) @@ -361,14 +364,13 @@ class NetworkView(BaseView): ) def show_apply_spinner(self): - s = Spinner() - s.start() + self.apply_spinner.start() c = TablePile( [ TableRow( [ Text(_("Applying changes")), - s, + self.apply_spinner, ] ), ], @@ -378,10 +380,12 @@ class NetworkView(BaseView): (c, self.bottom.options()), (Text(""), self.bottom.options()), ] + self.request_redraw_if_visible() def hide_apply_spinner(self): if len(self.bottom.contents) > 2: self.bottom.contents[0:2] = [] + self.request_redraw_if_visible() def new_link(self, new_dev_info): log.debug( diff --git a/system_setup/client/controllers/summary.py b/system_setup/client/controllers/summary.py index 9222fef5..07ec70d6 100644 --- a/system_setup/client/controllers/summary.py +++ b/system_setup/client/controllers/summary.py @@ -60,6 +60,8 @@ class SummaryController(SubiquityTuiController): if self.app_state == ApplicationState.DONE: if self.answers.get("reboot", False): self.click_reboot() + if self.summary_view: + self.summary_view.request_redraw_if_visible() async def make_ui(self): real_name = ""