process keyboard data into api friendly format when building snap

This is a bit sideways from the real thing I'm working on which is
moving most of the logic of keyboard handling to the server side but
anyway. This also lets me check some assumptions while processing the
data rather than in the view code.
This commit is contained in:
Michael Hudson-Doyle 2021-03-11 16:11:14 +13:00
parent da14af1c65
commit 5d93eb824a
5 changed files with 145 additions and 94 deletions

59
scripts/make-kbd-info.py Executable file
View File

@ -0,0 +1,59 @@
#!/usr/bin/python3
from collections import defaultdict
import os
import shutil
import subprocess
from subiquity.common.serialize import Serializer
from subiquity.common.types import (
KeyboardLayout,
KeyboardVariant,
)
tdir = os.path.join(os.environ.get('SNAPCRAFT_PART_INSTALL', '.'), 'kbds')
if os.path.exists(tdir):
shutil.rmtree(tdir)
os.mkdir(tdir)
p = subprocess.Popen(
['/usr/share/console-setup/kbdnames-maker',
'/usr/share/console-setup/KeyboardNames.pl'],
stdout=subprocess.PIPE, encoding='utf-8')
lang_to_layouts = defaultdict(dict)
for line in p.stdout:
lang, element, name, value = line.strip().split("*", 3)
if element == 'model':
continue
elif element == 'variant':
layout = lang_to_layouts[lang][name]
variant, value = value.split('*', 1)
if not layout.variants and variant != "":
raise Exception(
"subiquity assumes all keyboard layouts have the default "
"variant at index 0!")
layout.variants.append(KeyboardVariant(code=variant, name=value))
elif element == 'layout':
lang_to_layouts[lang][name] = KeyboardLayout(
code=name, name=value, variants=[])
s = Serializer(compact=True)
for lang, layouts in lang_to_layouts.items():
if 'us' not in layouts:
raise Exception("subiquity assumes there is always a us keyboard "
"layout")
outpath = os.path.join(tdir, lang + '.jsonl')
with open(outpath, 'w') as out:
for layout in layouts.values():
if len(layout.variants) == 0:
raise Exception(
"subiquity assumes all keyboard layouts have at least one "
"variant!")
out.write(s.to_json(KeyboardLayout, layout) + "\n")

View File

@ -122,19 +122,20 @@ parts:
stage:
- users-and-groups
- reserved-usernames
kbdnames:
keyboard-data:
plugin: nil
build-packages:
- console-setup
- locales
- xkb-data-i18n
override-build: |
/usr/share/console-setup/kbdnames-maker /usr/share/console-setup/KeyboardNames.pl > $SNAPCRAFT_PART_INSTALL/kbdnames.txt
- python3-attr
override-build: PYTHONPATH=$SNAPCRAFT_PROJECT_DIR/ $SNAPCRAFT_PROJECT_DIR/scripts/make-kbd-info.py
stage:
- kbdnames.txt
- kbds/
font:
plugin: dump
source: ./
source-type: git
organize:
font/subiquity.psf: subiquity.psf
stage:

View File

@ -13,10 +13,10 @@
# 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/>.
from collections import defaultdict
import os
from subiquity.common.types import KeyboardSetting
from subiquity.common.serialize import Serializer
from subiquity.common.types import KeyboardLayout, KeyboardSetting
# Non-latin keyboard layouts that are handled in a uniform way
@ -107,14 +107,15 @@ def for_ui(setting):
class KeyboardList:
def __init__(self):
self._kbnames_file = os.path.join(
os.environ.get("SNAP", '.'),
'kbdnames.txt')
self._kbnames_dir = os.path.join(os.environ.get("SNAP", '.'), 'kbds')
self.serializer = Serializer(compact=True)
self._clear()
def _file_for_lang(self, code):
return os.path.join(self._kbnames_dir, code + '.jsonl')
def has_language(self, code):
self.load_language(code)
return bool(self.layouts)
return os.path.exists(self._file_for_lang(code))
def load_language(self, code):
if code == self.current_lang:
@ -122,33 +123,13 @@ class KeyboardList:
self._clear()
with open(self._kbnames_file, encoding='utf-8') as kbdnames:
self._load_file(code, kbdnames)
with open(self._file_for_lang(code)) as kbdnames:
self.layouts = [
self.serializer.from_json(KeyboardLayout, line)
for line in kbdnames
]
self.current_lang = code
def _clear(self):
self.current_lang = None
self.layouts = {}
self.variants = defaultdict(dict)
def _load_file(self, code, kbdnames):
for line in kbdnames:
line = line.rstrip('\n')
got_lang, element, name, value = line.split("*", 3)
if got_lang != code:
continue
if element == "layout":
self.layouts[name] = value
elif element == "variant":
variantname, variantdesc = value.split("*", 1)
self.variants[name][variantname] = variantdesc
def lookup(self, code):
if ':' in code:
layout_code, variant_code = code.split(":", 1)
layout = self.layouts.get(layout_code, '?')
variant = self.variants.get(layout_code, {}).get(variant_code, '?')
return (layout, variant)
else:
return self.layouts.get(code, '?'), None
self.layouts = []

View File

@ -99,6 +99,19 @@ class KeyboardSetting:
toggle: Optional[str] = None
@attr.s(auto_attribs=True)
class KeyboardVariant:
code: str
name: str
@attr.s(auto_attribs=True)
class KeyboardLayout:
code: str
name: str
variants: List[KeyboardVariant]
@attr.s(auto_attribs=True)
class ZdevInfo:
id: str

View File

@ -13,6 +13,7 @@
# 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/>.
import locale
import logging
from urwid import (
@ -44,7 +45,7 @@ from subiquitycore.ui.stretchy import (
from subiquitycore.ui.utils import button_pile, Color, Padding, screen
from subiquitycore.view import BaseView
from subiquity.client.keyboard import latinizable, for_ui
from subiquity.client.keyboard import for_ui, latinizable
from subiquity.common.types import KeyboardSetting
from subiquity.ui.views import pc105
@ -127,21 +128,23 @@ another layout or run the automated detection again.
""")
def ok(self, sender):
self.keyboard_detector.keyboard_view.found_layout(self.step.result)
self.keyboard_detector.keyboard_view.found_layout(
self.layout, self.variant)
def make_body(self):
kl = self.keyboard_detector.keyboard_view.keyboard_list
layout, variant = kl.lookup(self.step.result)
var_desc = []
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)
layout_text = _("Layout")
var_text = _("Variant")
width = max(len(layout_text), len(var_text), 12)
if variant is not None:
var_desc = [Text("%*s: %s" % (width, var_text, variant))]
return Pile([
Text(_(self.preamble)),
Text("%*s: %s" % (width, layout_text, layout)),
] + var_desc + [
Text("%*s: %s" % (width, layout_text, self.layout.name)),
Text("%*s: %s" % (width, var_text, self.variant.name)),
Text(_(self.postamble)),
button_pile([ok_btn(label=_("OK"), on_press=self.ok)]),
])
@ -394,25 +397,16 @@ class KeyboardView(BaseView):
self.form = KeyboardForm()
opts = []
for layout, desc in self.keyboard_list.layouts.items():
opts.append(Option((desc, True, layout)))
opts.sort(key=lambda o: o.label.text)
for layout in self.keyboard_list.layouts:
opts.append(Option((layout.name, True, layout)))
opts.sort(key=lambda o: locale.strxfrm(o.label.text))
connect_signal(self.form, 'submit', self.done)
connect_signal(self.form, 'cancel', self.cancel)
connect_signal(self.form.layout.widget, "select", self.select_layout)
self.form.layout.widget.options = opts
setting = for_ui(initial_setting)
try:
self.form.layout.widget.value = setting.layout
except AttributeError:
# Don't crash on pre-existing invalid config.
pass
self.select_layout(None, setting.layout)
try:
self.form.variant.widget.value = setting.variant
except AttributeError:
# Don't crash on pre-existing invalid config.
pass
layout, variant = self.lookup(setting.layout, setting.variant)
self.set_values(layout, variant)
if self.controller.opts.run_on_serial:
excerpt = _('Please select the layout of the keyboard directly '
@ -437,32 +431,24 @@ class KeyboardView(BaseView):
narrow_rows=True))
def detect(self, sender):
detector = Detector(self)
detector.start()
Detector(self).start()
def found_layout(self, result):
def found_layout(self, layout, variant):
self.remove_overlay()
log.debug("found_layout %s", result)
if ':' in result:
layout, variant = result.split(':')
else:
layout, variant = result, ""
self.form.layout.widget.value = layout
self.select_layout(None, layout)
self.form.variant.widget.value = variant
log.debug("found_layout %r %r", layout.code, variant.code)
self.set_values(layout, variant)
self._w.base_widget.focus_position = 4
def done(self, result):
layout = self.form.layout.widget.value
variant = ''
if self.form.variant.widget.value is not None:
variant = self.form.variant.widget.value
setting = KeyboardSetting(layout=layout, variant=variant)
new_setting = latinizable(setting)
if new_setting != setting:
self.show_stretchy_overlay(ToggleQuestion(self, new_setting))
return
self.really_done(setting)
data = result.as_data()
layout = data['layout']
variant = data.get('variant', layout.variants[0])
setting = KeyboardSetting(layout=layout.code, variant=variant.code)
other = latinizable(setting)
if setting != other:
self.show_stretchy_overlay(ToggleQuestion(self, other))
else:
self.really_done(setting)
def really_done(self, setting):
apply = False
@ -479,18 +465,29 @@ class KeyboardView(BaseView):
if sender is not None:
log.debug("select_layout %s", layout)
opts = []
default_i = -1
layout_items = enumerate(self.keyboard_list.variants[layout].items())
for i, (variant, variant_desc) in layout_items:
if variant == "":
default_i = i
opts.append(Option((variant_desc, True, variant)))
opts.sort(key=lambda o: o.label.text)
if default_i < 0:
opts.insert(0, Option(("default", True, "")))
for variant in layout.variants:
opts.append(Option((variant.name, True, variant)))
# ./scripts/make-kbd-info.py checks that the default is always
# at index 0
opts[1:] = sorted(opts[1:], key=lambda o: locale.strxfrm(o.label.text))
self.form.variant.widget.options = opts
if default_i < 0:
self.form.variant.widget.index = 0
else:
self.form.variant.widget.index = default_i
self.form.variant.widget.index = 0
self.form.variant.enabled = len(opts) > 1
def lookup(self, layout_code, variant_code):
for layout in self.keyboard_list.layouts:
if layout.code == layout_code:
break
if layout.code == "us":
default = layout
else:
layout = default
for variant in layout.variants:
if variant.code == variant_code:
return layout, variant
return layout, layout.variants[0]
def set_values(self, layout, variant):
self.form.layout.widget.value = layout
self.select_layout(None, layout)
self.form.variant.widget.value = variant