Merge pull request #778 from mwhudson/ascii-toggle

start in a super basic mode on serial, offer upgrade immediately
This commit is contained in:
Michael Hudson-Doyle 2020-05-21 12:05:47 +12:00 committed by GitHub
commit 3e881df107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 72 deletions

View File

@ -178,7 +178,10 @@ GLOBAL_KEYS = (
(_('F1'), _('open help menu')),
(_('Control-Z, F2'), _('switch to shell')),
(_('Control-L, F3'), _('redraw screen')),
(_('Control-T, F4'), _('toggle color on and off')),
)
SERIAL_GLOBAL_HELP_KEYS = (
(_('Control-T, F4'), _('toggle rich mode (colour, unicode) on and off')),
)
DRY_RUN_KEYS = (
@ -194,7 +197,10 @@ class GlobalKeyStretchy(Stretchy):
def __init__(self, app):
rows = []
for key, text in GLOBAL_KEYS:
keys = GLOBAL_KEYS
if app.opts.run_on_serial:
keys += SERIAL_GLOBAL_HELP_KEYS
for key, text in keys:
rows.append(TableRow([Text(_(key)), Text(_(text))]))
if app.opts.dry_run:
dro = _('(dry-run only)')
@ -233,6 +239,31 @@ def menu_item(text, on_press=None):
return Color.frame_button(icon)
def get_global_addresses(app):
ips = []
net_model = app.base_model.network
for dev in net_model.get_all_netdevs():
ips.extend(dev.actual_global_ip_addresses)
return ips
def get_installer_password(dry_run=False):
if dry_run:
fp = io.StringIO('installer:rAnd0Mpass')
else:
try:
fp = open("/var/log/cloud-init-output.log")
except FileNotFoundError:
fp = io.StringIO('')
with fp:
for line in fp:
if line.startswith("installer:"):
return line[len("installer:"):].strip()
return None
class HelpMenu(WidgetWrap):
def __init__(self, parent):
@ -243,12 +274,9 @@ class HelpMenu(WidgetWrap):
_("Keyboard shortcuts"), on_press=self._shortcuts)
drop_to_shell = menu_item(
_("Enter shell"), on_press=self._debug_shell)
color = menu_item(
_("Toggle color on/off"), on_press=self._toggle_color)
buttons = {
about,
close,
color,
drop_to_shell,
keys,
}
@ -256,6 +284,10 @@ class HelpMenu(WidgetWrap):
ssh_help = menu_item(
_("Help on SSH access"), on_press=self._ssh_help)
buttons.add(ssh_help)
if self.parent.app.opts.run_on_serial:
rich = menu_item(
_("Toggle rich mode"), on_press=self._toggle_rich)
buttons.add(rich)
local_title, local_doc = parent.app.ui.body.local_help()
if local_title is not None:
local = menu_item(
@ -290,10 +322,11 @@ class HelpMenu(WidgetWrap):
if self.parent.ssh_password is not None:
entries.append(ssh_help)
entries.extend([
hline,
color,
])
if self.parent.app.opts.run_on_serial:
entries.extend([
hline,
rich,
])
rows = [
Columns([
@ -369,16 +402,9 @@ class HelpMenu(WidgetWrap):
_("About the installer"),
template.format(**info)))
def get_global_addresses(self):
ips = []
net_model = self.parent.app.base_model.network
for dev in net_model.get_all_netdevs():
ips.extend(dev.actual_global_ip_addresses)
return ips
def _ssh_help(self, sender=None):
texts = ssh_help_texts(
self.get_global_addresses(),
get_global_addresses(self.parent.app),
self.parent.ssh_password)
self._show_overlay(
@ -404,30 +430,13 @@ class HelpMenu(WidgetWrap):
def _debug_shell(self, sender):
self.parent.app.debug_shell()
def _toggle_color(self, sender):
self.parent.app.toggle_color()
def _toggle_rich(self, sender):
self.parent.app.toggle_rich()
def _show_errors(self, sender):
self._show_overlay(ErrorReportListStretchy(self.parent.app))
def get_installer_password(dry_run=False):
if dry_run:
fp = io.StringIO('installer:rAnd0Mpass')
else:
try:
fp = open("/var/log/cloud-init-output.log")
except FileNotFoundError:
fp = io.StringIO('')
with fp:
for line in fp:
if line.startswith("installer:"):
return line[len("installer:"):].strip()
return None
class HelpButton(PopUpLauncher):
def __init__(self, app):

View File

@ -1,6 +1,5 @@
import unittest
from unittest import mock
import urwid
from subiquitycore.testing import view_helpers
@ -10,10 +9,16 @@ from subiquity.models.locale import LocaleModel
from subiquity.ui.views.welcome import WelcomeView
class FakeApp:
class opts:
run_on_serial = False
class WelcomeViewTests(unittest.TestCase):
def make_view_with_languages(self, languages):
controller = mock.create_autospec(spec=WelcomeController)
controller.app = FakeApp()
model = mock.create_autospec(spec=LocaleModel)
model.get_languages.return_value = languages
return WelcomeView(model, controller)

View File

@ -20,11 +20,19 @@ Welcome provides user with language selection
"""
import logging
from subiquitycore.ui.buttons import forward_btn
from urwid import Text
from subiquitycore.ui.buttons import forward_btn, other_btn
from subiquitycore.ui.container import ListBox
from subiquitycore.ui.utils import screen
from subiquitycore.ui.utils import button_pile, rewrap, screen
from subiquitycore.view import BaseView
from subiquity.ui.views.help import (
get_installer_password,
get_global_addresses,
HelpMenu,
)
log = logging.getLogger("subiquity.views.welcome")
@ -33,6 +41,23 @@ Select the language to use for the installer and to be configured in the
installed system.
""")
SERIAL_TEXT = """
As the installer is running on a serial console, it has started in
basic mode, using only the ASCII character set and black and white
colours.
If you are connecting from a terminal emulator such as gnome-terminal
that supports unicode and rich colours you can switch to "rich mode"
which uses unicde, colours and supports many languages.
"""
SSH_TEXT = """
You can also connect to the installer over the network via SSH, which
will allow use of rich mode.
"""
class WelcomeView(BaseView):
title = "Willkommen! Bienvenue! Welcome! Добро пожаловать! Welkom!"
@ -40,27 +65,75 @@ class WelcomeView(BaseView):
def __init__(self, model, controller):
self.model = model
self.controller = controller
super().__init__(screen(
self._build_model_inputs(),
buttons=None,
narrow_rows=True,
excerpt=_("Use UP, DOWN and ENTER keys to select your language.")))
if controller.app.opts.run_on_serial and not controller.app.rich_mode:
s = self.make_serial_choices()
self.title = "Welcome!"
else:
s = self.make_language_choices()
super().__init__(s)
def _build_model_inputs(self):
def make_language_choices(self):
btns = []
current_index = None
for i, (code, native) in enumerate(self.model.get_languages()):
if code == self.model.selected_language:
current_index = i
btns.append(forward_btn(label=native, on_press=self.confirm,
user_arg=code))
btns.append(
forward_btn(
label=native,
on_press=self.choose_language,
user_arg=code))
lb = ListBox(btns)
if current_index is not None:
lb.base_widget.focus_position = current_index
return lb
return screen(
lb, buttons=None, narrow_rows=True,
excerpt=_("Use UP, DOWN and ENTER keys to select your language."))
def confirm(self, sender, code):
def make_serial_choices(self):
ssh_password = get_installer_password(self.controller.opts.dry_run)
ips = get_global_addresses(self.controller.app)
btns = [
other_btn(
label="Switch to rich mode",
on_press=self.enable_rich),
forward_btn(
label="Continue in basic mode",
on_press=self.choose_language,
user_arg='C'),
]
widgets = [
Text(""),
Text(rewrap(SERIAL_TEXT)),
Text(""),
]
if ssh_password and ips:
widgets.append(Text(rewrap(SSH_TEXT)))
widgets.append(Text(""))
btns.insert(1, other_btn(
label="View SSH instructions",
on_press=self.ssh_help,
user_arg=ssh_password))
widgets.extend([
button_pile(btns),
])
lb = ListBox(widgets)
return screen(lb, buttons=None)
def enable_rich(self, sender):
self.controller.app.toggle_rich()
self.title = self.__class__.title
self.controller.ui.set_header(self.title)
self._w = self.make_language_choices()
def ssh_help(self, sender, password):
# This is gross, make nicer before landing!
btn = self.controller.ui.right_icon
btn.ssh_password = password
HelpMenu(btn)._ssh_help()
def choose_language(self, sender, code):
log.debug('WelcomeView %s', code)
self.controller.done(code)

View File

@ -112,7 +112,7 @@ urwid_8_names = (
)
def make_palette(colors, styles, ascii):
def make_palette(colors, styles):
"""Return a palette to be passed to MainLoop.
colors is a list of exactly 8 tuples (name, (r, g, b))
@ -133,16 +133,6 @@ def make_palette(colors, styles, ascii):
urwid_palette = []
for name, fg, bg in styles:
urwid_fg, urwid_bg = urwid_name[fg], urwid_name[bg]
if ascii:
# 24bit grey on colored background looks good
# but in 16 colors it's unreadable
# hence add more contrast
if urwid_bg != 'black':
urwid_fg = 'black'
# Only frame_button doesn't match above rule
# fix it to be brown-on-black black-on-brown
if name == 'frame_button focus':
urwid_fg, urwid_bg = 'brown', 'black'
urwid_palette.append((name, urwid_fg, urwid_bg))
return urwid_palette
@ -348,8 +338,10 @@ class Application:
if not opts.dry_run:
open('/run/casper-no-prompt', 'w').close()
self.is_color = False
self.color_palette = make_palette(self.COLORS, self.STYLES, opts.ascii)
# 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.color_palette = make_palette(self.COLORS, self.STYLES)
self.is_linux_tty = is_linux_tty()
@ -557,13 +549,16 @@ class Application:
self.aio_loop.call_later(0.06, _run_script)
def toggle_color(self):
if self.is_color:
def toggle_rich(self):
if self.rich_mode:
urwid.util.set_encoding('ascii')
new_palette = self.STYLES_MONO
self.is_color = False
self.rich_mode = False
else:
urwid.util.set_encoding('utf-8')
new_palette = self.color_palette
self.is_color = True
self.rich_mode = True
urwid.CanvasCache.clear()
self.urwid_loop.screen.register_palette(new_palette)
self.urwid_loop.screen.clear()
@ -572,8 +567,8 @@ class Application:
self.exit()
elif key == 'f3':
self.urwid_loop.screen.clear()
elif key in ['ctrl t', 'f4']:
self.toggle_color()
elif self.opts.run_on_serial and key in ['ctrl t', 'f4']:
self.toggle_rich()
def start_controllers(self):
log.debug("starting controllers")
@ -655,12 +650,9 @@ class Application:
unhandled_input=self.unhandled_input,
event_loop=AsyncioEventLoop(loop=self.aio_loop))
if self.opts.ascii:
urwid.util.set_encoding('ascii')
extend_dec_special_charmap()
self.toggle_color()
self.toggle_rich()
self.base_model = self.make_model()
try: