2020-07-31 00:50:59 +00:00
|
|
|
# Copyright 2020 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
2020-09-18 09:16:59 +00:00
|
|
|
import asyncio
|
2020-09-17 23:52:19 +00:00
|
|
|
import inspect
|
2020-07-31 00:50:59 +00:00
|
|
|
import logging
|
2021-04-01 03:27:01 +00:00
|
|
|
import os
|
|
|
|
import signal
|
ui: introduce an optional level of indirection in make_ui
The make_ui() function / coroutine returns a BaseView (i.e., a
screen to display to the user).
That being said, when the application calls, make_ui(), it does not come
with a guarantee that the view returned will be displayed to the user
immediately.
One of the reason is that there are multiple await statements before the
we call the ui.set_body function. Therefore, tasks running concurrently
cannot reliably expect that they execute after the display is refreshed.
Perhaps more importantly, when the make_ui() function takes more than .1
second to execute, we display a "Progress" screen that stays visible for
at least one second. This can effectively delay a lot the moment when
the view returned by make_ui() is shown to the user. A lot can happen in
the meantime.
As the result, the view returned by make_ui can be outdated by the time
we show it on the screen.
One way to work around this problem is to store in the controller a
reference to the view that it returns in make_ui(). This way, the
controller can modify the view and keep it up-to-date until it gets
shown to the user.
Unfortunately, some controllers (e.g., the storage controller) do not
modify / mutate the existing view object when a modification is needed ;
but instead instantiate a new view object.
This patch introduces a level of indirection that can be used by these
controllers. Instead of returning a view object from make_ui(), the
controllers are now allowed to return a callback ; which in turn will
return a view object.
https://bugs.launchpad.net/subiquity/+bug/1968161
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-04-12 17:17:09 +00:00
|
|
|
from typing import Callable, Optional, Union
|
2020-07-31 00:50:59 +00:00
|
|
|
|
|
|
|
import urwid
|
2021-04-01 03:27:01 +00:00
|
|
|
import yaml
|
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
from subiquitycore.async_helpers import run_bg_task, schedule_task
|
2020-07-31 00:50:59 +00:00
|
|
|
from subiquitycore.core import Application
|
2023-07-25 21:26:25 +00:00
|
|
|
from subiquitycore.palette import PALETTE_COLOR, PALETTE_MONO
|
2020-07-31 00:50:59 +00:00
|
|
|
from subiquitycore.screen import make_screen
|
2020-07-31 00:14:51 +00:00
|
|
|
from subiquitycore.tuicontroller import Skip
|
2020-07-31 00:50:59 +00:00
|
|
|
from subiquitycore.ui.frame import SubiquityCoreUI
|
2023-07-25 21:26:25 +00:00
|
|
|
from subiquitycore.ui.utils import LoadingDialog
|
2021-04-01 03:13:34 +00:00
|
|
|
from subiquitycore.utils import astart_command
|
ui: introduce an optional level of indirection in make_ui
The make_ui() function / coroutine returns a BaseView (i.e., a
screen to display to the user).
That being said, when the application calls, make_ui(), it does not come
with a guarantee that the view returned will be displayed to the user
immediately.
One of the reason is that there are multiple await statements before the
we call the ui.set_body function. Therefore, tasks running concurrently
cannot reliably expect that they execute after the display is refreshed.
Perhaps more importantly, when the make_ui() function takes more than .1
second to execute, we display a "Progress" screen that stays visible for
at least one second. This can effectively delay a lot the moment when
the view returned by make_ui() is shown to the user. A lot can happen in
the meantime.
As the result, the view returned by make_ui can be outdated by the time
we show it on the screen.
One way to work around this problem is to store in the controller a
reference to the view that it returns in make_ui(). This way, the
controller can modify the view and keep it up-to-date until it gets
shown to the user.
Unfortunately, some controllers (e.g., the storage controller) do not
modify / mutate the existing view object when a modification is needed ;
but instead instantiate a new view object.
This patch introduces a level of indirection that can be used by these
controllers. Instead of returning a view object from make_ui(), the
controllers are now allowed to return a callback ; which in turn will
return a view object.
https://bugs.launchpad.net/subiquity/+bug/1968161
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-04-12 17:17:09 +00:00
|
|
|
from subiquitycore.view import BaseView
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
log = logging.getLogger("subiquitycore.tui")
|
2020-07-31 00:50:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
def extend_dec_special_charmap():
|
2023-07-25 21:26:25 +00:00
|
|
|
urwid.escape.DEC_SPECIAL_CHARMAP.update(
|
|
|
|
{
|
|
|
|
ord("\N{BLACK RIGHT-POINTING SMALL TRIANGLE}"): ">",
|
|
|
|
ord("\N{BLACK LEFT-POINTING SMALL TRIANGLE}"): "<",
|
|
|
|
ord("\N{BLACK DOWN-POINTING SMALL TRIANGLE}"): "v",
|
|
|
|
ord("\N{BLACK UP-POINTING SMALL TRIANGLE}"): "^",
|
|
|
|
ord("\N{check mark}"): "*",
|
|
|
|
ord("\N{circled white star}"): "*",
|
|
|
|
ord("\N{bullet}"): "*",
|
|
|
|
ord("\N{lower half block}"): "=",
|
|
|
|
ord("\N{upper half block}"): "=",
|
|
|
|
ord("\N{FULL BLOCK}"): urwid.escape.DEC_SPECIAL_CHARMAP[
|
|
|
|
ord("\N{BOX DRAWINGS LIGHT VERTICAL}")
|
|
|
|
],
|
|
|
|
}
|
|
|
|
)
|
2020-07-31 00:50:59 +00:00
|
|
|
|
|
|
|
|
2020-09-18 09:16:59 +00:00
|
|
|
# When waiting for something of unknown duration, block the UI for at
|
|
|
|
# most this long before showing an indication of progress.
|
|
|
|
MAX_BLOCK_TIME = 0.1
|
|
|
|
# If an indication of progress is shown, show it for at least this
|
|
|
|
# long to avoid excessive flicker in the UI.
|
|
|
|
MIN_SHOW_PROGRESS_TIME = 1.0
|
|
|
|
|
|
|
|
|
2020-07-31 00:50:59 +00:00
|
|
|
class TuiApplication(Application):
|
|
|
|
make_ui = SubiquityCoreUI
|
|
|
|
|
|
|
|
def __init__(self, opts):
|
|
|
|
super().__init__(opts)
|
|
|
|
self.ui = self.make_ui()
|
|
|
|
|
|
|
|
self.answers = {}
|
|
|
|
if opts.answers is not None:
|
|
|
|
self.answers = yaml.safe_load(opts.answers.read())
|
|
|
|
log.debug("Loaded answers %s", self.answers)
|
|
|
|
if not opts.dry_run:
|
2023-07-25 21:26:25 +00:00
|
|
|
open("/run/casper-no-prompt", "w").close()
|
2020-07-31 00:50:59 +00:00
|
|
|
|
|
|
|
# Set rich_mode to the opposite of what we want, so we can
|
|
|
|
# call toggle_rich to get the right things set up.
|
|
|
|
self.rich_mode = opts.run_on_serial
|
|
|
|
self.urwid_loop = None
|
2020-07-31 00:14:51 +00:00
|
|
|
self.cur_screen = None
|
2021-04-01 03:13:34 +00:00
|
|
|
self.fg_proc = None
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
def run_command_in_foreground(self, cmd, before_hook=None, after_hook=None, **kw):
|
2021-04-01 03:13:34 +00:00
|
|
|
if self.fg_proc is not None:
|
|
|
|
raise Exception("cannot run two fg processes at once")
|
2020-07-31 00:50:59 +00:00
|
|
|
screen = self.urwid_loop.screen
|
|
|
|
|
|
|
|
async def _run():
|
2021-04-01 03:13:34 +00:00
|
|
|
self.fg_proc = proc = await astart_command(
|
2023-07-25 21:26:25 +00:00
|
|
|
cmd, stdin=None, stdout=None, stderr=None, **kw
|
|
|
|
)
|
2021-04-01 03:13:34 +00:00
|
|
|
await proc.communicate()
|
|
|
|
self.fg_proc = None
|
2021-04-01 03:27:01 +00:00
|
|
|
# One of the main use cases for this function is to run interactive
|
|
|
|
# bash in a subshell. Interactive bash of course creates a process
|
|
|
|
# group for itself and sets it as the foreground process group for
|
|
|
|
# the controlling terminal. Usually on exit, our process group
|
|
|
|
# becomes the foreground process group again but when the subshell
|
|
|
|
# is killed for some reason it does not. This causes the tcsetattr
|
|
|
|
# that screen.start() does to either cause SIGTTOU to be sent, or
|
|
|
|
# if that is ignored (either because there is no shell around to do
|
|
|
|
# job control things or we are ignoring it) fail with EIO. So we
|
|
|
|
# force our process group back into the foreground with this
|
|
|
|
# call. That's not quite enough though because tcsetpgrp *also*
|
|
|
|
# causes SIGTTOU to be sent to a background process that calls it,
|
|
|
|
# but fortunately if we ignore that (done in start_urwid below),
|
|
|
|
# the call still succeeds.
|
|
|
|
#
|
|
|
|
# I would now like a drink.
|
|
|
|
os.tcsetpgrp(0, os.getpgrp())
|
2020-07-31 00:50:59 +00:00
|
|
|
screen.start()
|
|
|
|
if after_hook is not None:
|
|
|
|
after_hook()
|
|
|
|
|
|
|
|
screen.stop()
|
2023-07-25 21:26:25 +00:00
|
|
|
urwid.emit_signal(screen, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
|
2020-07-31 00:50:59 +00:00
|
|
|
if before_hook is not None:
|
|
|
|
before_hook()
|
|
|
|
schedule_task(_run())
|
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
async def make_view_for_controller(
|
|
|
|
self, new
|
|
|
|
) -> Union[BaseView, Callable[[], BaseView]]:
|
2020-07-31 00:50:59 +00:00
|
|
|
new.context.enter("starting UI")
|
|
|
|
if self.opts.screens and new.name not in self.opts.screens:
|
|
|
|
raise Skip
|
|
|
|
try:
|
2020-09-17 23:52:19 +00:00
|
|
|
maybe_view = new.make_ui()
|
|
|
|
if inspect.iscoroutine(maybe_view):
|
|
|
|
view = await maybe_view
|
|
|
|
else:
|
|
|
|
view = maybe_view
|
2020-07-31 00:50:59 +00:00
|
|
|
except Skip:
|
|
|
|
new.context.exit("(skipped)")
|
|
|
|
raise
|
2020-09-17 23:52:19 +00:00
|
|
|
else:
|
|
|
|
self.cur_screen = new
|
2020-09-18 09:16:59 +00:00
|
|
|
return view
|
|
|
|
|
|
|
|
async def _wait_with_indication(self, awaitable, show, hide=None):
|
|
|
|
"""Wait for something but tell the user if it takes a while.
|
|
|
|
|
|
|
|
When waiting for something that can take an unknown length of
|
|
|
|
time, we want to tell the user if it takes more than a moment
|
|
|
|
(defined as MAX_BLOCK_TIME) but make sure that we display any
|
|
|
|
indication for long enough that the UI is not flickering
|
2020-09-18 10:01:36 +00:00
|
|
|
incomprehensibly (MIN_SHOW_PROGRESS_TIME).
|
2020-09-18 09:16:59 +00:00
|
|
|
"""
|
|
|
|
min_show_task = None
|
|
|
|
|
2022-12-07 01:59:26 +00:00
|
|
|
async def _show():
|
|
|
|
await asyncio.sleep(MAX_BLOCK_TIME)
|
2020-09-18 09:16:59 +00:00
|
|
|
self.ui.block_input = False
|
|
|
|
nonlocal min_show_task
|
2023-07-25 21:26:25 +00:00
|
|
|
min_show_task = asyncio.create_task(asyncio.sleep(MIN_SHOW_PROGRESS_TIME))
|
2020-09-18 09:16:59 +00:00
|
|
|
show()
|
|
|
|
|
|
|
|
self.ui.block_input = True
|
2022-12-07 01:59:26 +00:00
|
|
|
show_task = asyncio.create_task(_show())
|
2020-09-18 09:16:59 +00:00
|
|
|
try:
|
|
|
|
result = await awaitable
|
|
|
|
finally:
|
|
|
|
if min_show_task:
|
|
|
|
await min_show_task
|
|
|
|
if hide is not None:
|
|
|
|
hide()
|
|
|
|
else:
|
|
|
|
self.ui.block_input = False
|
2022-12-07 01:59:26 +00:00
|
|
|
show_task.cancel()
|
2020-09-18 09:16:59 +00:00
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
def show_progress(self):
|
|
|
|
raise NotImplementedError
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
async def wait_with_text_dialog(self, awaitable, message, *, can_cancel=False):
|
2020-09-18 09:51:14 +00:00
|
|
|
ld = None
|
|
|
|
|
|
|
|
task_to_cancel = None
|
|
|
|
if can_cancel:
|
|
|
|
if not isinstance(awaitable, asyncio.Task):
|
2021-01-25 23:54:35 +00:00
|
|
|
orig = awaitable
|
|
|
|
|
2020-09-18 09:51:14 +00:00
|
|
|
async def w():
|
2021-01-25 23:54:35 +00:00
|
|
|
return await orig
|
2023-07-25 21:26:25 +00:00
|
|
|
|
2022-11-07 21:08:46 +00:00
|
|
|
awaitable = task_to_cancel = asyncio.create_task(w())
|
2020-09-18 09:51:14 +00:00
|
|
|
else:
|
2021-01-25 23:54:35 +00:00
|
|
|
task_to_cancel = None
|
2020-09-18 09:51:14 +00:00
|
|
|
|
|
|
|
def show_load():
|
|
|
|
nonlocal ld
|
2022-12-07 01:19:27 +00:00
|
|
|
ld = LoadingDialog(self.ui.body, message, task_to_cancel)
|
2020-09-18 09:51:14 +00:00
|
|
|
self.ui.body.show_overlay(ld, width=ld.width)
|
|
|
|
|
|
|
|
def hide_load():
|
|
|
|
ld.close()
|
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
return await self._wait_with_indication(awaitable, show_load, hide_load)
|
2020-09-18 09:51:14 +00:00
|
|
|
|
2021-03-01 21:58:31 +00:00
|
|
|
async def wait_with_progress(self, awaitable):
|
|
|
|
return await self._wait_with_indication(awaitable, self.show_progress)
|
|
|
|
|
2023-07-25 21:26:25 +00:00
|
|
|
async def _move_screen(
|
|
|
|
self, increment, coro
|
|
|
|
) -> Optional[Union[BaseView, Callable[[], BaseView]]]:
|
2020-09-17 23:52:19 +00:00
|
|
|
if coro is not None:
|
|
|
|
await coro
|
2020-07-31 00:14:51 +00:00
|
|
|
old, self.cur_screen = self.cur_screen, None
|
2020-07-31 00:50:59 +00:00
|
|
|
if old is not None:
|
|
|
|
old.context.exit("completed")
|
|
|
|
old.end_ui()
|
|
|
|
cur_index = self.controllers.index
|
|
|
|
while True:
|
|
|
|
self.controllers.index += increment
|
|
|
|
if self.controllers.index < 0:
|
|
|
|
self.controllers.index = cur_index
|
2020-09-18 09:16:59 +00:00
|
|
|
return None
|
2020-07-31 00:50:59 +00:00
|
|
|
if self.controllers.index >= len(self.controllers.instances):
|
|
|
|
self.exit()
|
2020-09-18 09:16:59 +00:00
|
|
|
return None
|
2020-07-31 00:50:59 +00:00
|
|
|
new = self.controllers.cur
|
|
|
|
try:
|
2020-09-18 09:16:59 +00:00
|
|
|
return await self.make_view_for_controller(new)
|
2020-07-31 00:50:59 +00:00
|
|
|
except Skip:
|
|
|
|
log.debug("skipping screen %s", new.name)
|
|
|
|
continue
|
2020-09-18 09:16:59 +00:00
|
|
|
except Exception:
|
|
|
|
self.controllers.index = cur_index
|
|
|
|
raise
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2020-09-18 09:16:59 +00:00
|
|
|
async def move_screen(self, increment, coro):
|
ui: introduce an optional level of indirection in make_ui
The make_ui() function / coroutine returns a BaseView (i.e., a
screen to display to the user).
That being said, when the application calls, make_ui(), it does not come
with a guarantee that the view returned will be displayed to the user
immediately.
One of the reason is that there are multiple await statements before the
we call the ui.set_body function. Therefore, tasks running concurrently
cannot reliably expect that they execute after the display is refreshed.
Perhaps more importantly, when the make_ui() function takes more than .1
second to execute, we display a "Progress" screen that stays visible for
at least one second. This can effectively delay a lot the moment when
the view returned by make_ui() is shown to the user. A lot can happen in
the meantime.
As the result, the view returned by make_ui can be outdated by the time
we show it on the screen.
One way to work around this problem is to store in the controller a
reference to the view that it returns in make_ui(). This way, the
controller can modify the view and keep it up-to-date until it gets
shown to the user.
Unfortunately, some controllers (e.g., the storage controller) do not
modify / mutate the existing view object when a modification is needed ;
but instead instantiate a new view object.
This patch introduces a level of indirection that can be used by these
controllers. Instead of returning a view object from make_ui(), the
controllers are now allowed to return a callback ; which in turn will
return a view object.
https://bugs.launchpad.net/subiquity/+bug/1968161
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-04-12 17:17:09 +00:00
|
|
|
view_or_callable = await self.wait_with_progress(
|
2023-07-25 21:26:25 +00:00
|
|
|
self._move_screen(increment, coro)
|
|
|
|
)
|
ui: introduce an optional level of indirection in make_ui
The make_ui() function / coroutine returns a BaseView (i.e., a
screen to display to the user).
That being said, when the application calls, make_ui(), it does not come
with a guarantee that the view returned will be displayed to the user
immediately.
One of the reason is that there are multiple await statements before the
we call the ui.set_body function. Therefore, tasks running concurrently
cannot reliably expect that they execute after the display is refreshed.
Perhaps more importantly, when the make_ui() function takes more than .1
second to execute, we display a "Progress" screen that stays visible for
at least one second. This can effectively delay a lot the moment when
the view returned by make_ui() is shown to the user. A lot can happen in
the meantime.
As the result, the view returned by make_ui can be outdated by the time
we show it on the screen.
One way to work around this problem is to store in the controller a
reference to the view that it returns in make_ui(). This way, the
controller can modify the view and keep it up-to-date until it gets
shown to the user.
Unfortunately, some controllers (e.g., the storage controller) do not
modify / mutate the existing view object when a modification is needed ;
but instead instantiate a new view object.
This patch introduces a level of indirection that can be used by these
controllers. Instead of returning a view object from make_ui(), the
controllers are now allowed to return a callback ; which in turn will
return a view object.
https://bugs.launchpad.net/subiquity/+bug/1968161
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
2022-04-12 17:17:09 +00:00
|
|
|
if view_or_callable is not None:
|
|
|
|
if callable(view_or_callable):
|
|
|
|
view = view_or_callable()
|
|
|
|
else:
|
|
|
|
view = view_or_callable
|
2020-09-18 09:16:59 +00:00
|
|
|
self.ui.set_body(view)
|
|
|
|
|
2020-09-17 23:52:19 +00:00
|
|
|
def next_screen(self, coro=None):
|
2023-02-13 21:45:56 +00:00
|
|
|
run_bg_task(self.move_screen(1, coro))
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2020-09-17 23:52:19 +00:00
|
|
|
def prev_screen(self):
|
2023-02-13 21:45:56 +00:00
|
|
|
run_bg_task(self.move_screen(-1, None))
|
2020-07-31 00:50:59 +00:00
|
|
|
|
2020-10-08 22:07:10 +00:00
|
|
|
def select_initial_screen(self):
|
2020-07-31 00:50:59 +00:00
|
|
|
self.next_screen()
|
|
|
|
|
2021-04-21 16:11:27 +00:00
|
|
|
def set_rich(self, rich):
|
|
|
|
if rich == self.rich_mode:
|
|
|
|
return
|
|
|
|
self.toggle_rich()
|
|
|
|
|
2020-07-31 00:50:59 +00:00
|
|
|
def toggle_rich(self):
|
|
|
|
if self.rich_mode:
|
2023-07-25 21:26:25 +00:00
|
|
|
urwid.util.set_encoding("ascii")
|
2020-07-31 00:50:59 +00:00
|
|
|
new_palette = PALETTE_MONO
|
|
|
|
self.rich_mode = False
|
|
|
|
else:
|
2023-07-25 21:26:25 +00:00
|
|
|
urwid.util.set_encoding("utf-8")
|
2020-07-31 00:50:59 +00:00
|
|
|
new_palette = PALETTE_COLOR
|
|
|
|
self.rich_mode = True
|
|
|
|
urwid.CanvasCache.clear()
|
|
|
|
self.urwid_loop.screen.register_palette(new_palette)
|
|
|
|
self.urwid_loop.screen.clear()
|
|
|
|
|
|
|
|
def unhandled_input(self, key):
|
2023-07-25 21:26:25 +00:00
|
|
|
if self.opts.dry_run and key == "ctrl x":
|
2020-07-31 00:50:59 +00:00
|
|
|
self.exit()
|
2023-07-25 21:26:25 +00:00
|
|
|
elif key == "f3":
|
2020-07-31 00:50:59 +00:00
|
|
|
self.urwid_loop.screen.clear()
|
2023-07-25 21:26:25 +00:00
|
|
|
elif self.opts.run_on_serial and key in ["ctrl t", "f4"]:
|
2020-07-31 00:50:59 +00:00
|
|
|
self.toggle_rich()
|
|
|
|
|
|
|
|
def extra_urwid_loop_args(self):
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def make_screen(self, inputf=None, outputf=None):
|
|
|
|
return make_screen(self.opts.ascii, inputf, outputf)
|
|
|
|
|
|
|
|
def start_urwid(self, input=None, output=None):
|
2021-04-01 03:27:01 +00:00
|
|
|
# This stops the tcsetpgrp call in run_command_in_foreground from
|
|
|
|
# suspending us. See the rant there for more details.
|
|
|
|
signal.signal(signal.SIGTTOU, signal.SIG_IGN)
|
2020-07-31 00:50:59 +00:00
|
|
|
screen = self.make_screen(input, output)
|
|
|
|
screen.register_palette(PALETTE_COLOR)
|
|
|
|
self.urwid_loop = urwid.MainLoop(
|
2023-07-25 21:26:25 +00:00
|
|
|
self.ui,
|
|
|
|
screen=screen,
|
|
|
|
handle_mouse=False,
|
|
|
|
pop_ups=True,
|
2020-07-31 00:50:59 +00:00
|
|
|
unhandled_input=self.unhandled_input,
|
2022-12-07 01:59:26 +00:00
|
|
|
event_loop=urwid.AsyncioEventLoop(loop=asyncio.get_running_loop()),
|
2023-07-25 21:26:25 +00:00
|
|
|
**self.extra_urwid_loop_args(),
|
|
|
|
)
|
2020-07-31 00:50:59 +00:00
|
|
|
extend_dec_special_charmap()
|
|
|
|
self.toggle_rich()
|
|
|
|
self.urwid_loop.start()
|
2020-10-08 22:07:10 +00:00
|
|
|
self.select_initial_screen()
|
2020-07-30 22:48:17 +00:00
|
|
|
|
2020-09-16 01:06:03 +00:00
|
|
|
async def start(self, start_urwid=True):
|
2020-09-16 01:03:21 +00:00
|
|
|
await super().start()
|
2020-09-16 01:06:03 +00:00
|
|
|
if start_urwid:
|
|
|
|
self.start_urwid()
|
2020-09-16 01:03:21 +00:00
|
|
|
|
2022-10-27 10:43:36 +00:00
|
|
|
async def run(self):
|
2020-07-31 00:50:59 +00:00
|
|
|
try:
|
2022-10-27 10:43:36 +00:00
|
|
|
await super().run()
|
2020-07-31 00:50:59 +00:00
|
|
|
finally:
|
2020-09-16 01:03:21 +00:00
|
|
|
if self.urwid_loop is not None:
|
|
|
|
self.urwid_loop.stop()
|