Merge pull request #778 from mwhudson/ascii-toggle
start in a super basic mode on serial, offer upgrade immediately
This commit is contained in:
commit
3e881df107
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue