diff --git a/scripts/make-kbd-info.py b/scripts/make-kbd-info.py
new file mode 100755
index 00000000..9a6cb81d
--- /dev/null
+++ b/scripts/make-kbd-info.py
@@ -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")
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 2b019e20..4390dd51 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -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:
diff --git a/subiquity/client/keyboard.py b/subiquity/client/keyboard.py
index 12c9ba06..228c5ec7 100644
--- a/subiquity/client/keyboard.py
+++ b/subiquity/client/keyboard.py
@@ -13,10 +13,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-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 = []
diff --git a/subiquity/common/types.py b/subiquity/common/types.py
index 30b63f69..cea4712b 100644
--- a/subiquity/common/types.py
+++ b/subiquity/common/types.py
@@ -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
diff --git a/subiquity/ui/views/keyboard.py b/subiquity/ui/views/keyboard.py
index 90bdf983..eadf3f04 100644
--- a/subiquity/ui/views/keyboard.py
+++ b/subiquity/ui/views/keyboard.py
@@ -13,6 +13,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+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