diff --git a/console_conf/core.py b/console_conf/core.py
index 9540ef5f..fb621ff6 100644
--- a/console_conf/core.py
+++ b/console_conf/core.py
@@ -25,8 +25,6 @@ log = logging.getLogger("console_conf.core")
class ConsoleConf(Application):
- from subiquitycore.palette import COLORS, STYLES, STYLES_MONO
-
project = "console_conf"
make_model = ConsoleConfModel
@@ -40,8 +38,6 @@ class ConsoleConf(Application):
class RecoveryChooser(Application):
- from subiquitycore.palette import COLORS, STYLES, STYLES_MONO
-
project = "console_conf"
controllers = [
diff --git a/subiquity/controllers/welcome.py b/subiquity/controllers/welcome.py
index eac965be..54fcd627 100644
--- a/subiquity/controllers/welcome.py
+++ b/subiquity/controllers/welcome.py
@@ -16,6 +16,8 @@
import logging
import os
+from subiquitycore.screen import is_linux_tty
+
from subiquity.controller import SubiquityController
from subiquity.ui.views import WelcomeView
@@ -39,7 +41,7 @@ class WelcomeController(SubiquityController):
lang = os.environ.get("LANG")
if lang is not None and lang.endswith(".UTF-8"):
lang = lang.rsplit('.', 1)[0]
- for code, name in self.model.get_languages(self.app.is_linux_tty):
+ for code, name in self.model.get_languages(is_linux_tty()):
if code == lang:
self.model.switch_language(code)
break
diff --git a/subiquity/core.py b/subiquity/core.py
index 65b086c2..fdd65478 100644
--- a/subiquity/core.py
+++ b/subiquity/core.py
@@ -85,8 +85,6 @@ class Subiquity(Application):
'additionalProperties': True,
}
- from subiquitycore.palette import COLORS, STYLES, STYLES_MONO
-
project = "subiquity"
def make_model(self):
diff --git a/subiquity/ui/views/welcome.py b/subiquity/ui/views/welcome.py
index a6c6e1b0..207a881f 100644
--- a/subiquity/ui/views/welcome.py
+++ b/subiquity/ui/views/welcome.py
@@ -25,6 +25,7 @@ from urwid import Text
from subiquitycore.ui.buttons import forward_btn, other_btn
from subiquitycore.ui.container import ListBox
from subiquitycore.ui.utils import button_pile, rewrap, screen
+from subiquitycore.screen import is_linux_tty
from subiquitycore.view import BaseView
from subiquity.ui.views.help import (
@@ -64,7 +65,6 @@ class WelcomeView(BaseView):
def __init__(self, model, controller):
self.model = model
self.controller = controller
- self.is_linux_tty = controller.app.is_linux_tty
if controller.app.opts.run_on_serial and not controller.app.rich_mode:
s = self.make_serial_choices()
self.title = "Welcome!"
@@ -75,7 +75,7 @@ class WelcomeView(BaseView):
def make_language_choices(self):
btns = []
current_index = None
- langs = self.model.get_languages(self.is_linux_tty)
+ langs = self.model.get_languages(is_linux_tty())
cur = self.model.selected_language
log.debug("_build_model_inputs selected_language=%s", cur)
if cur in ["C", None]:
diff --git a/subiquitycore/core.py b/subiquitycore/core.py
index e5a6aaa4..91768eba 100644
--- a/subiquitycore/core.py
+++ b/subiquitycore/core.py
@@ -20,7 +20,6 @@ import logging
import os
import struct
import sys
-import tty
import urwid
import yaml
@@ -32,21 +31,16 @@ from subiquitycore.context import (
from subiquitycore.controller import (
Skip,
)
-from subiquitycore.signals import Signal
+from subiquitycore.palette import PALETTE_COLOR, PALETTE_MONO
from subiquitycore.prober import Prober
+from subiquitycore.screen import is_linux_tty, make_screen
+from subiquitycore.signals import Signal
from subiquitycore.ui.frame import SubiquityCoreUI
from subiquitycore.utils import arun_command
log = logging.getLogger('subiquitycore.core')
-# From uapi/linux/kd.h:
-KDGKBTYPE = 0x4B33 # get keyboard type
-
-GIO_CMAP = 0x4B70 # gets colour palette on VGA+
-PIO_CMAP = 0x4B71 # sets colour palette on VGA+
-UO_R, UO_G, UO_B = 0xe9, 0x54, 0x20
-
# /usr/include/linux/kd.h
K_RAW = 0x00
K_XLATE = 0x01
@@ -58,86 +52,6 @@ KDGKBMODE = 0x4B44 # gets current keyboard mode
KDSKBMODE = 0x4B45 # sets current keyboard mode
-class TwentyFourBitScreen(urwid.raw_display.Screen):
-
- def __init__(self, _urwid_name_to_rgb, **kwargs):
- self._urwid_name_to_rgb = _urwid_name_to_rgb
- super().__init__(**kwargs)
-
- def _cc(self, color):
- """Return the "SGR" parameter for selecting color.
-
- See https://en.wikipedia.org/wiki/ANSI_escape_code#SGR for an
- explanation. We use the basic codes for black/white/default for
- maximum compatibility; they are the only colors used when the
- mono palette is selected.
- """
- if color == 'white':
- return '7'
- elif color == 'black':
- return '0'
- elif color == 'default':
- return '9'
- else:
- # This is almost but not quite a ISO 8613-3 code -- that
- # would use colons to separate the rgb values instead. But
- # it's what xterm, and hence everything else, supports.
- return '8;2;{};{};{}'.format(*self._urwid_name_to_rgb[color])
-
- def _attrspec_to_escape(self, a):
- return '\x1b[0;3{};4{}m'.format(
- self._cc(a.foreground),
- self._cc(a.background))
-
-
-def is_linux_tty():
- try:
- r = fcntl.ioctl(sys.stdout.fileno(), KDGKBTYPE, ' ')
- except IOError as e:
- log.debug("KDGKBTYPE failed %r", e)
- return False
- log.debug("KDGKBTYPE returned %r, is_linux_tty %s", r, r == b'\x02')
- return r == b'\x02'
-
-
-urwid_8_names = (
- 'black',
- 'dark red',
- 'dark green',
- 'brown',
- 'dark blue',
- 'dark magenta',
- 'dark cyan',
- 'light gray',
-)
-
-
-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))
-
- styles is a list of tuples (stylename, fg_color, bg_color) where
- fg_color and bg_color are defined in 'colors'
- """
- # The part that makes this "fun" is that urwid insists on referring
- # to the basic colors by their "standard" names but we overwrite
- # these colors to mean different things. So we convert styles into
- # an urwid palette by mapping the names in colors to the standard
- # name.
- if len(colors) != 8:
- raise Exception(
- "make_palette must be passed a list of exactly 8 colors")
- urwid_name = dict(zip([c[0] for c in colors], urwid_8_names))
-
- urwid_palette = []
- for name, fg, bg in styles:
- urwid_fg, urwid_bg = urwid_name[fg], urwid_name[bg]
- urwid_palette.append((name, urwid_fg, urwid_bg))
-
- return urwid_palette
-
-
def extend_dec_special_charmap():
urwid.escape.DEC_SPECIAL_CHARMAP.update({
ord('\N{BLACK RIGHT-POINTING SMALL TRIANGLE}'): '>',
@@ -341,11 +255,8 @@ class Application:
# 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()
-
- if self.is_linux_tty:
+ if is_linux_tty():
self.input_filter = KeyCodesFilter()
else:
self.input_filter = DummyKeycodesFilter()
@@ -369,29 +280,10 @@ class Application:
**kw):
screen = self.urwid_loop.screen
- # Calling screen.stop() sends the INPUT_DESCRIPTORS_CHANGED
- # signal. This calls _reset_input_descriptors() which calls
- # unhook_event_loop / hook_event_loop on the screen. But this all
- # happens before _started is set to False on the screen and so this
- # does not actually do anything -- we end up attempting to read from
- # stdin while in a background process group, something that gets the
- # kernel upset at us.
- #
- # The cleanest fix seems to be to just send the signal again once
- # stop() has returned which, now that screen._started is False,
- # correctly stops listening from stdin.
- #
- # There is an exactly analagous problem with screen.start() except
- # there the symptom is that we are running in the foreground but not
- # listening to stdin! The fix is the same.
-
async def _run():
await arun_command(
cmd, stdin=None, stdout=None, stderr=None, **kw)
screen.start()
- urwid.emit_signal(
- screen, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
- self.setraw()
if after_hook is not None:
after_hook()
@@ -552,11 +444,11 @@ class Application:
def toggle_rich(self):
if self.rich_mode:
urwid.util.set_encoding('ascii')
- new_palette = self.STYLES_MONO
+ new_palette = PALETTE_MONO
self.rich_mode = False
else:
urwid.util.set_encoding('utf-8')
- new_palette = self.color_palette
+ new_palette = PALETTE_COLOR
self.rich_mode = True
urwid.CanvasCache.clear()
self.urwid_loop.screen.register_palette(new_palette)
@@ -598,53 +490,14 @@ class Application:
controller.configured()
return controller_index
- def setraw(self):
- fd = self.urwid_loop.screen._term_input_file.fileno()
- if os.isatty(fd):
- tty.setraw(fd)
-
def make_screen(self, inputf=None, outputf=None):
- """Return a screen to be passed to MainLoop.
-
- colors is a list of exactly 8 tuples (name, (r, g, b)), the same as
- passed to make_palette.
- """
- # On the linux console, we overwrite the first 8 colors to be those
- # defined by colors. Otherwise, we return a screen that uses ISO
- # 8613-3ish codes to display the colors.
- if inputf is None:
- inputf = sys.stdin
- if outputf is None:
- outputf = sys.stdout
-
- if len(self.COLORS) != 8:
- raise Exception(
- "make_screen must be passed a list of exactly 8 colors")
- if self.is_linux_tty:
- # Perhaps we ought to return a screen subclass that does this
- # ioctl-ing in .start() and undoes it in .stop() but well.
- curpal = bytearray(16*3)
- fcntl.ioctl(sys.stdout.fileno(), GIO_CMAP, curpal)
- for i in range(8):
- for j in range(3):
- curpal[i*3+j] = self.COLORS[i][1][j]
- fcntl.ioctl(sys.stdout.fileno(), PIO_CMAP, curpal)
- return urwid.raw_display.Screen(input=inputf, output=outputf)
- elif self.opts.ascii:
- return urwid.raw_display.Screen(input=inputf, output=outputf)
- else:
- _urwid_name_to_rgb = {}
- for i, n in enumerate(urwid_8_names):
- _urwid_name_to_rgb[n] = self.COLORS[i][1]
- return TwentyFourBitScreen(_urwid_name_to_rgb,
- input=inputf, output=outputf)
+ return make_screen(self.opts.ascii, inputf, outputf)
def run(self, input=None, output=None):
log.debug("Application.run")
- screen = self.make_screen(input, output)
self.urwid_loop = urwid.MainLoop(
- self.ui, palette=self.color_palette, screen=screen,
+ self.ui, screen=self.make_screen(input, output),
handle_mouse=False, pop_ups=True,
input_filter=self.input_filter.filter,
unhandled_input=self.unhandled_input,
@@ -666,7 +519,6 @@ class Application:
if self.updated:
initial_controller_index = self.load_serialized_state()
- self.aio_loop.call_soon(self.setraw)
self.aio_loop.call_soon(
self.select_initial_screen, initial_controller_index)
self._connect_base_signals()
diff --git a/subiquitycore/palette.py b/subiquitycore/palette.py
index 4759d822..c7e1be4c 100644
--- a/subiquitycore/palette.py
+++ b/subiquitycore/palette.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Canonical, Ltd.
+# 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
@@ -13,13 +13,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-""" Palette definitions """
-
COLORS = [
# black
("bg", (0x11, 0x11, 0x11)),
- # dark read
+ # dark red
("danger", (0xff, 0x00, 0x00)),
# dark green
("good", (0x0e, 0x84, 0x20)),
@@ -35,7 +33,7 @@ COLORS = [
("fg", (0xff, 0xff, 0xff)),
]
-STYLES = [
+PALETTE_COLOR = [
('frame_header_fringe', 'orange', 'bg'),
('frame_header', 'fg', 'orange'),
('body', 'fg', 'bg'),
@@ -71,8 +69,7 @@ STYLES = [
('verified focus', 'good', 'gray'),
]
-
-STYLES_MONO = [
+PALETTE_MONO = [
('frame_header_fringe', 'white', 'black'),
('frame_header', 'black', 'white'),
('body', 'white', 'black'),
@@ -102,3 +99,43 @@ STYLES_MONO = [
('scrollbar_fg', 'white', 'black'),
('scrollbar_bg', 'white', 'black'),
]
+
+urwid_8_names = (
+ 'black',
+ 'dark red',
+ 'dark green',
+ 'brown',
+ 'dark blue',
+ 'dark magenta',
+ 'dark cyan',
+ 'light gray',
+)
+
+
+def _urwidize_palette(colors, styles):
+ """Return a palette to be passed to MainLoop.
+
+ colors is a list of exactly 8 tuples (name, (r, g, b))
+
+ styles is a list of tuples (stylename, fg_color, bg_color) where
+ fg_color and bg_color are defined in 'colors'
+ """
+ # The part that makes this "fun" is that urwid insists on referring
+ # to the basic colors by their "standard" names but we overwrite
+ # these colors to mean different things. So we convert styles into
+ # an urwid palette by mapping the names in colors to the standard
+ # name.
+ if len(colors) != 8:
+ raise Exception(
+ "make_palette must be passed a list of exactly 8 colors")
+ urwid_name = dict(zip([c[0] for c in colors], urwid_8_names))
+
+ urwid_palette = []
+ for name, fg, bg in styles:
+ urwid_fg, urwid_bg = urwid_name[fg], urwid_name[bg]
+ urwid_palette.append((name, urwid_fg, urwid_bg))
+
+ return urwid_palette
+
+
+PALETTE_COLOR = _urwidize_palette(COLORS, PALETTE_COLOR)
diff --git a/subiquitycore/screen.py b/subiquitycore/screen.py
new file mode 100644
index 00000000..43672cf2
--- /dev/null
+++ b/subiquitycore/screen.py
@@ -0,0 +1,151 @@
+# 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 .
+
+import fcntl
+import logging
+import os
+import sys
+import tty
+
+import urwid
+
+from subiquitycore.palette import COLORS, urwid_8_names
+
+log = logging.getLogger('subiquitycore.screen')
+
+
+# From uapi/linux/kd.h:
+KDGKBTYPE = 0x4B33 # get keyboard type
+
+GIO_CMAP = 0x4B70 # gets colour palette on VGA+
+PIO_CMAP = 0x4B71 # sets colour palette on VGA+
+UO_R, UO_G, UO_B = 0xe9, 0x54, 0x20
+
+
+class SubiquityScreen(urwid.raw_display.Screen):
+
+ # This class fixes a bug in urwid's screen:
+ #
+ # Calling screen.stop() sends the INPUT_DESCRIPTORS_CHANGED signal. This
+ # calls _reset_input_descriptors() which calls unhook_event_loop /
+ # hook_event_loop on the screen. But this all happens before _started is
+ # set to False on the screen and so this does not actually do anything. If
+ # we call stop and then, for example, run bash, we end up attempting to
+ # read from stdin while in a background process group and that gets the
+ # kernel upset at us.
+ #
+ # The cleanest fix seems to be to just send the signal again once stop()
+ # has returned which, now that screen._started is False, correctly stops
+ # listening from stdin.
+ #
+ # There is an exactly analagous problem with screen.start() except there
+ # the symptom is that we are running in the foreground but not listening to
+ # stdin! The fix is the same.
+
+ def start(self):
+ super().start()
+ urwid.emit_signal(self, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
+ # We run the terminal in raw, not cbreak mode.
+ fd = self._term_input_file.fileno()
+ if os.isatty(fd):
+ tty.setraw(fd)
+
+ def stop(self):
+ super().stop()
+ urwid.emit_signal(self, urwid.display_common.INPUT_DESCRIPTORS_CHANGED)
+
+
+class LinuxScreen(SubiquityScreen):
+
+ def __init__(self, colors, **kwargs):
+ self._colors = colors
+ super().__init__(**kwargs)
+
+ def start(self):
+ self.curpal = bytearray(16*3)
+ fcntl.ioctl(sys.stdout.fileno(), GIO_CMAP, self.curpal)
+ newpal = self.curpal.copy()
+ for i in range(8):
+ for j in range(3):
+ newpal[i*3+j] = self._colors[i][1][j]
+ fcntl.ioctl(self._term_input_file.fileno(), PIO_CMAP, newpal)
+ super().start()
+
+ def stop(self):
+ fcntl.ioctl(self._term_input_file.fileno(), PIO_CMAP, self.curpal)
+ super().stop()
+
+
+class TwentyFourBitScreen(SubiquityScreen):
+
+ def __init__(self, colors, **kwargs):
+ self._urwid_name_to_rgb = {
+ n: colors[i][1] for i, n in enumerate(urwid_8_names)}
+ super().__init__(**kwargs)
+
+ def _cc(self, color):
+ """Return the "SGR" parameter for selecting color.
+
+ See https://en.wikipedia.org/wiki/ANSI_escape_code#SGR for an
+ explanation. We use the basic codes for black/white/default for
+ maximum compatibility; they are the only colors used when the
+ mono palette is selected.
+ """
+ if color == 'white':
+ return '7'
+ elif color == 'black':
+ return '0'
+ elif color == 'default':
+ return '9'
+ else:
+ # This is almost but not quite a ISO 8613-3 code -- that
+ # would use colons to separate the rgb values instead. But
+ # it's what xterm, and hence everything else, supports.
+ return '8;2;{};{};{}'.format(*self._urwid_name_to_rgb[color])
+
+ def _attrspec_to_escape(self, a):
+ return '\x1b[0;3{};4{}m'.format(
+ self._cc(a.foreground),
+ self._cc(a.background))
+
+
+_is_linux_tty = None
+
+
+def is_linux_tty():
+ global _is_linux_tty
+ if _is_linux_tty is None:
+ try:
+ r = fcntl.ioctl(sys.stdout.fileno(), KDGKBTYPE, ' ')
+ except IOError as e:
+ log.debug("KDGKBTYPE failed %r", e)
+ return False
+ log.debug("KDGKBTYPE returned %r, is_linux_tty %s", r, r == b'\x02')
+ _is_linux_tty = r == b'\x02'
+ return _is_linux_tty
+
+
+def make_screen(ascii=False, inputf=None, outputf=None):
+ """ """
+ if inputf is None:
+ inputf = sys.stdin
+ if outputf is None:
+ outputf = sys.stdout
+ if is_linux_tty():
+ return LinuxScreen(COLORS, input=inputf, output=outputf)
+ elif ascii:
+ return SubiquityScreen(input=inputf, output=outputf)
+ else:
+ return TwentyFourBitScreen(COLORS, input=inputf, output=outputf)