load keyboard layouts on the server side

this means decoding a fairly large (40k) blob of JSON in the client, but
it seems to be OK.
This commit is contained in:
Michael Hudson-Doyle 2021-03-16 14:26:53 +13:00
parent 697949c1d2
commit 622aec8abf
7 changed files with 64 additions and 85 deletions

View File

@ -15,7 +15,6 @@ subiquity/client/controllers/ssh.py
subiquity/client/controllers/welcome.py subiquity/client/controllers/welcome.py
subiquity/client/controllers/zdev.py subiquity/client/controllers/zdev.py
subiquity/client/__init__.py subiquity/client/__init__.py
subiquity/client/keyboard.py
subiquity/client/keycodes.py subiquity/client/keycodes.py
subiquity/cmd/common.py subiquity/cmd/common.py
subiquity/cmd/__init__.py subiquity/cmd/__init__.py

View File

@ -17,7 +17,6 @@
import logging import logging
from subiquity.client.controller import SubiquityTuiController from subiquity.client.controller import SubiquityTuiController
from subiquity.client.keyboard import KeyboardList
from subiquity.common.types import KeyboardSetting from subiquity.common.types import KeyboardSetting
from subiquity.ui.views import KeyboardView from subiquity.ui.views import KeyboardView
@ -28,29 +27,9 @@ class KeyboardController(SubiquityTuiController):
endpoint_name = 'keyboard' endpoint_name = 'keyboard'
signals = [
('l10n:language-selected', 'language_selected'),
]
def __init__(self, app):
super().__init__(app)
self.keyboard_list = KeyboardList()
def language_selected(self, code):
log.debug("language_selected %s", code)
if not self.keyboard_list.has_language(code):
code = code.split('_')[0]
if not self.keyboard_list.has_language(code):
code = 'C'
log.debug("loading language %s", code)
self.keyboard_list.load_language(code)
async def make_ui(self): async def make_ui(self):
if self.keyboard_list.current_lang is None: setup = await self.endpoint.GET()
self.keyboard_list.load_language('C') return KeyboardView(self, setup)
initial_setting = await self.endpoint.GET()
view = KeyboardView(self, initial_setting)
return view
async def run_answers(self): async def run_answers(self):
if 'layout' in self.answers: if 'layout' in self.answers:

View File

@ -1,52 +0,0 @@
# 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 <http://www.gnu.org/licenses/>.
import os
from subiquity.common.serialize import Serializer
from subiquity.common.types import (
KeyboardLayout,
)
class KeyboardList:
def __init__(self):
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):
return os.path.exists(self._file_for_lang(code))
def load_language(self, code):
if code == self.current_lang:
return
self._clear()
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 = []

View File

@ -29,6 +29,7 @@ from subiquity.common.types import (
ApplicationStatus, ApplicationStatus,
ErrorReportRef, ErrorReportRef,
KeyboardSetting, KeyboardSetting,
KeyboardSetup,
IdentityData, IdentityData,
RefreshStatus, RefreshStatus,
SnapInfo, SnapInfo,
@ -91,7 +92,7 @@ class API:
def GET(change_id: str) -> dict: ... def GET(change_id: str) -> dict: ...
class keyboard: class keyboard:
def GET() -> KeyboardSetting: ... def GET() -> KeyboardSetup: ...
def POST(data: Payload[KeyboardSetting]): ... def POST(data: Payload[KeyboardSetting]): ...
class needs_toggle: class needs_toggle:

View File

@ -137,6 +137,12 @@ class KeyboardLayout:
variants: List[KeyboardVariant] variants: List[KeyboardVariant]
@attr.s(auto_attribs=True)
class KeyboardSetup:
setting: KeyboardSetting
layouts: List[KeyboardLayout]
@attr.s(auto_attribs=True) @attr.s(auto_attribs=True)
class ZdevInfo: class ZdevInfo:
id: str id: str

View File

@ -26,7 +26,9 @@ from subiquity.common.apidef import API
from subiquity.common.serialize import Serializer from subiquity.common.serialize import Serializer
from subiquity.common.types import ( from subiquity.common.types import (
AnyStep, AnyStep,
KeyboardLayout,
KeyboardSetting, KeyboardSetting,
KeyboardSetup,
) )
from subiquity.server.controller import SubiquityController from subiquity.server.controller import SubiquityController
@ -112,6 +114,44 @@ def for_ui(setting):
layout=layout, variant=variant, toggle=setting.toggle) layout=layout, variant=variant, toggle=setting.toggle)
class KeyboardList:
def __init__(self):
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):
return os.path.exists(self._file_for_lang(code))
def load_language(self, code):
if '.' in code:
code = code.split('.')[0]
if not self._has_language(code):
code = code.split('_')[0]
if not self._has_language(code):
code = 'C'
if code == self.current_lang:
return
self._clear()
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 = []
class KeyboardController(SubiquityController): class KeyboardController(SubiquityController):
endpoint = API.keyboard endpoint = API.keyboard
@ -133,6 +173,7 @@ class KeyboardController(SubiquityController):
self.serializer = Serializer(compact=True) self.serializer = Serializer(compact=True)
self.pc105_steps = None self.pc105_steps = None
self.needs_set_keyboard = False self.needs_set_keyboard = False
self.keyboard_list = KeyboardList()
super().__init__(app) super().__init__(app)
def load_autoinstall_data(self, data): def load_autoinstall_data(self, data):
@ -166,8 +207,12 @@ class KeyboardController(SubiquityController):
for cmd in cmds: for cmd in cmds:
await arun_command(cmd) await arun_command(cmd)
async def GET(self) -> KeyboardSetting: async def GET(self) -> KeyboardSetup:
return for_ui(self.model.setting) self.keyboard_list.load_language(
self.app.base_model.locale.selected_language)
return KeyboardSetup(
setting=for_ui(self.model.setting),
layouts=self.keyboard_list.layouts)
async def POST(self, data: KeyboardSetting): async def POST(self, data: KeyboardSetting):
new = latinizable(data.layout, data.variant) new = latinizable(data.layout, data.variant)

View File

@ -360,21 +360,22 @@ class KeyboardView(BaseView):
title = _("Keyboard configuration") title = _("Keyboard configuration")
def __init__(self, controller, setting): def __init__(self, controller, setup):
self.controller = controller self.controller = controller
self.keyboard_list = controller.keyboard_list self.initial_setting = setup.setting
self.initial_setting = setting self.layouts = setup.layouts
self.form = KeyboardForm() self.form = KeyboardForm()
opts = [] opts = []
for layout in self.keyboard_list.layouts: for layout in self.layouts:
opts.append(Option((layout.name, True, layout))) opts.append(Option((layout.name, True, layout)))
opts.sort(key=lambda o: locale.strxfrm(o.label.text)) opts.sort(key=lambda o: locale.strxfrm(o.label.text))
connect_signal(self.form, 'submit', self.done) connect_signal(self.form, 'submit', self.done)
connect_signal(self.form, 'cancel', self.cancel) connect_signal(self.form, 'cancel', self.cancel)
connect_signal(self.form.layout.widget, "select", self.select_layout) connect_signal(self.form.layout.widget, "select", self.select_layout)
self.form.layout.widget.options = opts self.form.layout.widget.options = opts
layout, variant = self.lookup(setting.layout, setting.variant) layout, variant = self.lookup(
setup.setting.layout, setup.setting.variant)
self.set_values(layout, variant) self.set_values(layout, variant)
if self.controller.opts.run_on_serial: if self.controller.opts.run_on_serial:
@ -448,7 +449,7 @@ class KeyboardView(BaseView):
self.form.variant.enabled = len(opts) > 1 self.form.variant.enabled = len(opts) > 1
def lookup(self, layout_code, variant_code): def lookup(self, layout_code, variant_code):
for layout in self.keyboard_list.layouts: for layout in self.layouts:
if layout.code == layout_code: if layout.code == layout_code:
break break
if layout.code == "us": if layout.code == "us":