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 <olivier.gayot@canonical.com>
This commit is contained in:
Olivier Gayot 2024-03-18 15:42:21 +01:00
parent e129c13590
commit 76815d6fa1
18 changed files with 97 additions and 24 deletions

View File

@ -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="/"
)

View File

@ -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()

View File

@ -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()

View File

@ -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__)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

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

View File

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

View File

@ -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(

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

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

View File

@ -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()

View File

@ -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()

View File

@ -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(

View File

@ -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 = ""