diff --git a/po/POTFILES.in b/po/POTFILES.in index 4fa2ed51..2e736f61 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -39,6 +39,7 @@ subiquity/common/serialize.py subiquity/common/tests/__init__.py subiquity/common/tests/test_filesystem.py subiquity/common/tests/test_keyboard.py +subiquity/common/tests/test_serialization.py subiquity/common/types.py subiquitycore/async_helpers.py subiquitycore/contextlib38.py @@ -165,7 +166,6 @@ subiquity/ui/views/__init__.py subiquity/ui/views/installprogress.py subiquity/ui/views/keyboard.py subiquity/ui/views/mirror.py -subiquity/ui/views/pc105.py subiquity/ui/views/proxy.py subiquity/ui/views/refresh.py subiquity/ui/views/snaplist.py diff --git a/scripts/make-kbd-info.py b/scripts/make-kbd-info.py index 9a6cb81d..a60aa72d 100755 --- a/scripts/make-kbd-info.py +++ b/scripts/make-kbd-info.py @@ -4,13 +4,23 @@ from collections import defaultdict import os import shutil import subprocess +import sys +from typing import Dict from subiquity.common.serialize import Serializer from subiquity.common.types import ( + AnyStep, KeyboardLayout, KeyboardVariant, + StepPressKey, + StepKeyPresent, ) +sys.path.insert(0, os.path.dirname(__file__)) + +import pc105 # noqa + + tdir = os.path.join(os.environ.get('SNAPCRAFT_PART_INSTALL', '.'), 'kbds') if os.path.exists(tdir): shutil.rmtree(tdir) @@ -57,3 +67,27 @@ for lang, layouts in lang_to_layouts.items(): "subiquity assumes all keyboard layouts have at least one " "variant!") out.write(s.to_json(KeyboardLayout, layout) + "\n") + + +pc105tree = pc105.PC105Tree() +pc105tree.read_steps() + +if '0' not in pc105tree.steps: + raise Exception("subiquity assumes there is a step '0' in the pc105 steps") + +for index, step in pc105tree.steps.items(): + if isinstance(step, StepPressKey): + if len(step.symbols) == 0 or len(step.keycodes) == 0: + raise Exception(f"step {index} {step} appears to be incomplete") + for v in step.keycodes.values(): + if v not in pc105tree.steps: + raise Exception( + f"step {index} {step} references missing step {v}") + elif isinstance(step, StepKeyPresent): + for v in step.yes, step.no: + if v not in pc105tree.steps: + raise Exception( + f"step {index} {step} references missing step {v}") + +with open(os.path.join(tdir, 'pc105.json'), 'w') as out: + out.write(s.to_json(Dict[str, AnyStep], pc105tree.steps)) diff --git a/subiquity/ui/views/pc105.py b/scripts/pc105.py similarity index 60% rename from subiquity/ui/views/pc105.py rename to scripts/pc105.py index ac27b128..56052c72 100644 --- a/subiquity/ui/views/pc105.py +++ b/scripts/pc105.py @@ -17,113 +17,94 @@ # /usr/share/console-setup/pc105.tree. This code parses that data into # subclasses of Step. +"""Parses the pc105.tree file into Steps""" -class Step: - def __repr__(self): - kvs = [] - for k, v in self.__dict__.items(): - kvs.append("%s=%r" % (k, v)) - return "%s(%s)" % (self.__class__.__name__, ", ".join(sorted(kvs))) - - def check(self): - pass - - -class StepPressKey(Step): - # "Press one of the following keys" - def __init__(self): - self.symbols = [] - self.keycodes = {} - - def check(self): - if len(self.symbols) == 0 or len(self.keycodes) == 0: - raise Exception - - -class StepKeyPresent(Step): - # "Is this symbol present on your keyboard" - def __init__(self, symbol): - self.symbol = symbol - self.yes = None - self.no = None - - def check(self): - if self.yes is None or self.no is None: - raise Exception - - -class StepResult(Step): - # "This is the autodetected layout" - def __init__(self, result): - self.result = result +from subiquity.common.types import ( + StepKeyPresent, + StepPressKey, + StepResult, + ) class PC105Tree: - """Parses the pc105.tree file into subclasses of Step""" # This is adapted (quite heavily) from the code in ubiquity. def __init__(self): self.steps = {} def _add_step_from_lines(self, lines): - step = None - step_index = -1 + step_cls = None + args = None + step_index = None for line in lines: if line.startswith('STEP '): - step_index = int(line[5:]) + step_index = line[5:].strip() elif line.startswith('PRESS '): # Ask the user to press a character on the keyboard. - if step is None: - step = StepPressKey() - elif not isinstance(step, StepPressKey): + if step_cls is None: + step_cls = StepPressKey + args = { + 'symbols': [], + 'keycodes': {}, + } + elif step_cls is not StepPressKey: raise Exception - step.symbols.append(line[6:].strip()) + args['symbols'].append(line[6:].strip()) elif line.startswith('CODE '): # Direct the evaluating code to process step ## next if the # user has pressed a key which returned that keycode. - if not isinstance(step, StepPressKey): + if step_cls is not StepPressKey: raise Exception keycode = int(line[5:line.find(' ', 5)]) - s = int(line[line.find(' ', 5) + 1:]) - step.keycodes[keycode] = s + s = line[line.find(' ', 5) + 1:].strip() + args['keycodes'][keycode] = s elif line.startswith('FIND '): # Ask the user whether that character is present on their # keyboard. - if step is None: - step = StepKeyPresent(line[5:].strip()) + if step_cls is None: + step_cls = StepKeyPresent + args = { + 'symbol': line[5:].strip(), + } else: raise Exception elif line.startswith('FINDP '): # Equivalent to FIND, except that the user is asked to consider # only the primary symbols (i.e. Plain and Shift). - if step is None: - step = StepKeyPresent(line[6:].strip()) + if step_cls is None: + step_cls = StepKeyPresent + args = {'symbol': line[5:].strip()} else: raise Exception elif line.startswith('YES '): # Direct the evaluating code to process step ## next if the # user does have this key. - if not isinstance(step, StepKeyPresent): + if step_cls is not StepKeyPresent: raise Exception - step.yes = int(line[4:].strip()) + args['yes'] = line[4:].strip() elif line.startswith('NO '): # Direct the evaluating code to process step ## next if the # user does not have this key. - if not isinstance(step, StepKeyPresent): + if step_cls is not StepKeyPresent: raise Exception - step.no = int(line[3:].strip()) + args['no'] = line[3:].strip() elif line.startswith('MAP '): # This step uniquely identifies a keymap. - if step is None: - step = StepResult(line[4:].strip()) + if step_cls is None: + step_cls = StepResult + arg = line[4:].strip() + if ':' in arg: + layout, variant = arg.split(':') + else: + layout, variant = arg, '' + args = {'layout': layout, 'variant': variant} else: raise Exception else: raise Exception - if step is None or step_index == -1: + if step_cls is None or step_index is None: raise Exception - step.check() - self.steps[step_index] = step + self.steps[step_index] = step_cls(**args) def read_steps(self): cur_step_lines = [] diff --git a/subiquity/client/keyboard.py b/subiquity/client/keyboard.py index 228c5ec7..fd88e009 100644 --- a/subiquity/client/keyboard.py +++ b/subiquity/client/keyboard.py @@ -14,9 +14,14 @@ # along with this program. If not, see . import os +from typing import Dict from subiquity.common.serialize import Serializer -from subiquity.common.types import KeyboardLayout, KeyboardSetting +from subiquity.common.types import ( + AnyStep, + KeyboardLayout, + KeyboardSetting, + ) # Non-latin keyboard layouts that are handled in a uniform way @@ -133,3 +138,8 @@ class KeyboardList: def _clear(self): self.current_lang = None self.layouts = [] + + def load_pc105(self): + path = os.path.join(self._kbnames_dir, 'pc105.json') + with open(path) as fp: + return self.serializer.from_json(Dict[str, AnyStep], fp.read()) diff --git a/subiquity/common/types.py b/subiquity/common/types.py index cea4712b..66b5ef7c 100644 --- a/subiquity/common/types.py +++ b/subiquity/common/types.py @@ -20,7 +20,7 @@ import datetime import enum import shlex -from typing import List, Optional +from typing import Dict, List, Optional, Union import attr @@ -92,6 +92,31 @@ class RefreshStatus: new_snap_version: str = '' +@attr.s(auto_attribs=True) +class StepPressKey: + # "Press a key with one of the following symbols" + symbols: List[str] + keycodes: Dict[int, str] + + +@attr.s(auto_attribs=True) +class StepKeyPresent: + # "Is this symbol present on your keyboard" + symbol: str + yes: str + no: str + + +@attr.s(auto_attribs=True) +class StepResult: + # "This is the autodetected layout" + layout: str + variant: str + + +AnyStep = Union[StepPressKey, StepKeyPresent, StepResult] + + @attr.s(auto_attribs=True) class KeyboardSetting: layout: str diff --git a/subiquity/ui/views/keyboard.py b/subiquity/ui/views/keyboard.py index eadf3f04..eb235515 100644 --- a/subiquity/ui/views/keyboard.py +++ b/subiquity/ui/views/keyboard.py @@ -46,15 +46,18 @@ from subiquitycore.ui.utils import button_pile, Color, Padding, screen from subiquitycore.view import BaseView from subiquity.client.keyboard import for_ui, latinizable -from subiquity.common.types import KeyboardSetting -from subiquity.ui.views import pc105 +from subiquity.common.types import ( + KeyboardSetting, + StepKeyPresent, + StepPressKey, + StepResult, + ) log = logging.getLogger("subiquity.ui.views.keyboard") class AutoDetectBase(WidgetWrap): def __init__(self, keyboard_detector, step): - # step is an instance of pc105.Step self.keyboard_detector = keyboard_detector self.step = step lb = LineBox( @@ -82,7 +85,7 @@ class AutoDetectBase(WidgetWrap): class AutoDetectIntro(AutoDetectBase): def ok(self, sender): - self.keyboard_detector.do_step(0) + self.keyboard_detector.do_step("0") def cancel(self, sender): self.keyboard_detector.abort() @@ -100,19 +103,6 @@ class AutoDetectIntro(AutoDetectBase): ]) -class AutoDetectFailed(AutoDetectBase): - - def ok(self, sender): - self.keyboard_detector.abort() - - def make_body(self): - return Pile([ - Text(_("Keyboard auto detection failed, sorry")), - Text(""), - button_pile([ok_btn(label="OK", on_press=self.ok)]), - ]) - - class AutoDetectResult(AutoDetectBase): preamble = _("""\ @@ -127,17 +117,16 @@ another layout or run the automated detection again. """) + @property + def _kview(self): + return self.keyboard_detector.keyboard_view + def ok(self, sender): - self.keyboard_detector.keyboard_view.found_layout( - self.layout, self.variant) + self._kview.found_layout(self.layout, self.variant) def make_body(self): - if ':' in self.step.result: - layout_code, variant_code = self.step.result.split(':') - else: - layout_code, variant_code = self.step.result, "" - view = self.keyboard_detector.keyboard_view - self.layout, self.variant = view.lookup(layout_code, variant_code) + self.layout, self.variant = self._kview.lookup( + self.step.layout, self.step.variant) layout_text = _("Layout") var_text = _("Variant") width = max(len(layout_text), len(var_text), 12) @@ -239,8 +228,7 @@ class Detector: def __init__(self, kview): self.keyboard_view = kview - self.pc105tree = pc105.PC105Tree() - self.pc105tree.read_steps() + self.pc105_steps = kview.keyboard_list.load_pc105() self.seen_steps = [] def start(self): @@ -252,9 +240,9 @@ class Detector: self.keyboard_view.remove_overlay() step_cls_to_view_cls = { - pc105.StepResult: AutoDetectResult, - pc105.StepPressKey: AutoDetectPressKey, - pc105.StepKeyPresent: AutoDetectKeyPresent, + StepResult: AutoDetectResult, + StepPressKey: AutoDetectPressKey, + StepKeyPresent: AutoDetectKeyPresent, } def backup(self): @@ -275,14 +263,10 @@ class Detector: self.abort() log.debug("moving to step %s", step_index) - try: - step = self.pc105tree.steps[step_index] - except KeyError: - self.overlay = AutoDetectFailed(self, None) - else: - self.seen_steps.append(step_index) - log.debug("step: %s", repr(step)) - self.overlay = self.step_cls_to_view_cls[type(step)](self, step) + step = self.pc105_steps[step_index] + self.seen_steps.append(step_index) + log.debug("step: %s", repr(step)) + self.overlay = self.step_cls_to_view_cls[type(step)](self, step) self.overlay.start() self.keyboard_view.show_overlay(self.overlay)