Move locales and cleanup menu (#1814)
* Cleanup imports and unused code * Cleanup imports and unused code * Update build check * Keep deprecation exception * Simplify logging * Move locale into new sub-menu --------- Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
This commit is contained in:
parent
5276d95339
commit
06eadb31d4
|
|
@ -213,8 +213,7 @@ def load_config():
|
|||
"""
|
||||
from .lib.models import NetworkConfiguration
|
||||
|
||||
arguments.setdefault('sys-language', 'en_US')
|
||||
arguments.setdefault('sys-encoding', 'utf-8')
|
||||
arguments['locale_config'] = locale.LocaleConfiguration.parse_arg(arguments)
|
||||
|
||||
if (archinstall_lang := arguments.get('archinstall-language', None)) is not None:
|
||||
arguments['archinstall-language'] = TranslationHandler().get_language_by_name(archinstall_lang)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from enum import Enum, auto
|
||||
from typing import List, Optional, Any, Dict, TYPE_CHECKING, TypeVar
|
||||
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.utils.util import format_cols
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
@ -185,17 +185,6 @@ class Profile:
|
|||
return None
|
||||
|
||||
def packages_text(self) -> str:
|
||||
text = str(_('Installed packages')) + ':\n'
|
||||
|
||||
nr_packages = len(self.packages)
|
||||
if nr_packages <= 5:
|
||||
col = 1
|
||||
elif nr_packages <= 10:
|
||||
col = 2
|
||||
elif nr_packages <= 15:
|
||||
col = 3
|
||||
else:
|
||||
col = 4
|
||||
|
||||
text += FormattedOutput.as_columns(self.packages, col)
|
||||
return text
|
||||
header = str(_('Installed packages'))
|
||||
output = format_cols(self.packages, header)
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Any, List, Optional, Union, Dict, TYPE_CHECKING
|
|||
|
||||
from . import disk
|
||||
from .general import secret
|
||||
from .locale.locale_menu import LocaleConfiguration, LocaleMenu
|
||||
from .menu import Selector, AbstractMenu
|
||||
from .mirrors import MirrorConfiguration, MirrorMenu
|
||||
from .models import NetworkConfiguration
|
||||
|
|
@ -24,9 +25,7 @@ from .interactions import ask_to_configure_network
|
|||
from .interactions import get_password, ask_for_a_timezone
|
||||
from .interactions import select_additional_repositories
|
||||
from .interactions import select_kernel
|
||||
from .interactions import select_language
|
||||
from .interactions import select_locale_enc
|
||||
from .interactions import select_locale_lang
|
||||
from .utils.util import format_cols
|
||||
from .interactions import ask_ntp
|
||||
from .interactions.disk_conf import select_disk_config
|
||||
|
||||
|
|
@ -36,6 +35,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class GlobalMenu(AbstractMenu):
|
||||
def __init__(self, data_store: Dict[str, Any]):
|
||||
self._defined_text = str(_('Defined'))
|
||||
super().__init__(data_store=data_store, auto_cursor=True, preview_size=0.3)
|
||||
|
||||
def setup_selection_menu_options(self):
|
||||
|
|
@ -46,28 +46,19 @@ class GlobalMenu(AbstractMenu):
|
|||
lambda x: self._select_archinstall_language(x),
|
||||
display_func=lambda x: x.display_name,
|
||||
default=self.translation_handler.get_language_by_abbr('en'))
|
||||
self._menu_options['keyboard-layout'] = \
|
||||
self._menu_options['locale_config'] = \
|
||||
Selector(
|
||||
_('Keyboard layout'),
|
||||
lambda preset: select_language(preset),
|
||||
default='us')
|
||||
_('Locales'),
|
||||
lambda preset: self._locale_selection(preset),
|
||||
preview_func=self._prev_locale,
|
||||
display_func=lambda x: self._defined_text if x else '')
|
||||
self._menu_options['mirror_config'] = \
|
||||
Selector(
|
||||
_('Mirrors'),
|
||||
lambda preset: self._mirror_configuration(preset),
|
||||
display_func=lambda x: str(_('Defined')) if x else '',
|
||||
display_func=lambda x: self._defined_text if x else '',
|
||||
preview_func=self._prev_mirror_config
|
||||
)
|
||||
self._menu_options['sys-language'] = \
|
||||
Selector(
|
||||
_('Locale language'),
|
||||
lambda preset: select_locale_lang(preset),
|
||||
default='en_US')
|
||||
self._menu_options['sys-encoding'] = \
|
||||
Selector(
|
||||
_('Locale encoding'),
|
||||
lambda preset: select_locale_enc(preset),
|
||||
default='UTF-8')
|
||||
self._menu_options['disk_config'] = \
|
||||
Selector(
|
||||
_('Disk configuration'),
|
||||
|
|
@ -103,32 +94,32 @@ class GlobalMenu(AbstractMenu):
|
|||
Selector(
|
||||
_('Root password'),
|
||||
lambda preset:self._set_root_password(),
|
||||
display_func=lambda x: secret(x) if x else 'None')
|
||||
display_func=lambda x: secret(x) if x else '')
|
||||
self._menu_options['!users'] = \
|
||||
Selector(
|
||||
_('User account'),
|
||||
lambda x: self._create_user_account(x),
|
||||
default=[],
|
||||
display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else None,
|
||||
display_func=lambda x: f'{len(x)} {_("User(s)")}' if len(x) > 0 else '',
|
||||
preview_func=self._prev_users)
|
||||
self._menu_options['profile_config'] = \
|
||||
Selector(
|
||||
_('Profile'),
|
||||
lambda preset: self._select_profile(preset),
|
||||
display_func=lambda x: x.profile.name if x else 'None',
|
||||
display_func=lambda x: x.profile.name if x else '',
|
||||
preview_func=self._prev_profile
|
||||
)
|
||||
self._menu_options['audio'] = \
|
||||
Selector(
|
||||
_('Audio'),
|
||||
lambda preset: self._select_audio(preset),
|
||||
display_func=lambda x: x if x else 'None',
|
||||
display_func=lambda x: x if x else '',
|
||||
default=None
|
||||
)
|
||||
self._menu_options['parallel downloads'] = \
|
||||
Selector(
|
||||
_('Parallel Downloads'),
|
||||
add_number_of_parrallel_downloads,
|
||||
lambda preset: add_number_of_parrallel_downloads(preset),
|
||||
display_func=lambda x: x if x else '0',
|
||||
default=0
|
||||
)
|
||||
|
|
@ -141,19 +132,20 @@ class GlobalMenu(AbstractMenu):
|
|||
self._menu_options['packages'] = \
|
||||
Selector(
|
||||
_('Additional packages'),
|
||||
# lambda x: ask_additional_packages_to_install(storage['arguments'].get('packages', None)),
|
||||
ask_additional_packages_to_install,
|
||||
lambda preset: ask_additional_packages_to_install(preset),
|
||||
display_func=lambda x: self._defined_text if x else '',
|
||||
preview_func=self._prev_additional_pkgs,
|
||||
default=[])
|
||||
self._menu_options['additional-repositories'] = \
|
||||
Selector(
|
||||
_('Optional repositories'),
|
||||
select_additional_repositories,
|
||||
lambda preset: select_additional_repositories(preset),
|
||||
display_func=lambda x: ', '.join(x) if x else None,
|
||||
default=[])
|
||||
self._menu_options['nic'] = \
|
||||
Selector(
|
||||
_('Network configuration'),
|
||||
ask_to_configure_network,
|
||||
lambda preset: ask_to_configure_network(preset),
|
||||
display_func=lambda x: self._display_network_conf(x),
|
||||
preview_func=self._prev_network_config,
|
||||
default={})
|
||||
|
|
@ -177,12 +169,37 @@ class GlobalMenu(AbstractMenu):
|
|||
self._menu_options['install'] = \
|
||||
Selector(
|
||||
self._install_text(),
|
||||
exec_func=lambda n,v: True if len(self._missing_configs()) == 0 else False,
|
||||
exec_func=lambda n, v: True if len(self._missing_configs()) == 0 else False,
|
||||
preview_func=self._prev_install_missing_config,
|
||||
no_store=True)
|
||||
|
||||
self._menu_options['abort'] = Selector(_('Abort'), exec_func=lambda n,v:exit(1))
|
||||
|
||||
def _missing_configs(self) -> List[str]:
|
||||
def check(s):
|
||||
return self._menu_options.get(s).has_selection()
|
||||
|
||||
def has_superuser() -> bool:
|
||||
sel = self._menu_options['!users']
|
||||
if sel.current_selection:
|
||||
return any([u.sudo for u in sel.current_selection])
|
||||
return False
|
||||
|
||||
mandatory_fields = dict(filter(lambda x: x[1].is_mandatory(), self._menu_options.items()))
|
||||
missing = set()
|
||||
|
||||
for key, selector in mandatory_fields.items():
|
||||
if key in ['!root-password', '!users']:
|
||||
if not check('!root-password') and not has_superuser():
|
||||
missing.add(
|
||||
str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
|
||||
)
|
||||
elif key == 'disk_config':
|
||||
if not check('disk_config'):
|
||||
missing.add(self._menu_options['disk_config'].description)
|
||||
|
||||
return list(missing)
|
||||
|
||||
def _update_install_text(self, name: str, value: str):
|
||||
text = self._install_text()
|
||||
self._menu_options['install'].update_description(text)
|
||||
|
|
@ -216,6 +233,21 @@ class GlobalMenu(AbstractMenu):
|
|||
disk_encryption = disk.DiskEncryptionMenu(mods, data_store, preset=preset).run()
|
||||
return disk_encryption
|
||||
|
||||
def _locale_selection(self, preset: LocaleConfiguration) -> LocaleConfiguration:
|
||||
data_store: Dict[str, Any] = {}
|
||||
locale_config = LocaleMenu(data_store, preset).run()
|
||||
return locale_config
|
||||
|
||||
def _prev_locale(self) -> Optional[str]:
|
||||
selector = self._menu_options['locale_config']
|
||||
if selector.has_selection():
|
||||
config: LocaleConfiguration = selector.current_selection # type: ignore
|
||||
output = '{}: {}\n'.format(str(_('Keyboard layout')), config.kb_layout)
|
||||
output += '{}: {}\n'.format(str(_('Locale language')), config.sys_lang)
|
||||
output += '{}: {}'.format(str(_('Locale encoding')), config.sys_enc)
|
||||
return output
|
||||
return None
|
||||
|
||||
def _prev_network_config(self) -> Optional[str]:
|
||||
selector = self._menu_options['nic']
|
||||
if selector.has_selection():
|
||||
|
|
@ -224,6 +256,13 @@ class GlobalMenu(AbstractMenu):
|
|||
return FormattedOutput.as_table(ifaces)
|
||||
return None
|
||||
|
||||
def _prev_additional_pkgs(self):
|
||||
selector = self._menu_options['packages']
|
||||
if selector.has_selection():
|
||||
packages: List[str] = selector.current_selection
|
||||
return format_cols(packages, None)
|
||||
return None
|
||||
|
||||
def _prev_disk_layouts(self) -> Optional[str]:
|
||||
selector = self._menu_options['disk_config']
|
||||
disk_layout_conf: Optional[disk.DiskLayoutConfiguration] = selector.current_selection
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ from functools import cached_property
|
|||
from pathlib import Path
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
from .exceptions import SysCallError
|
||||
from .general import SysCommand
|
||||
from .networking import list_interfaces, enrich_iface_types
|
||||
from .exceptions import SysCallError
|
||||
from .output import debug
|
||||
|
||||
AVAILABLE_GFX_DRIVERS = {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from . import disk
|
|||
from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError, SysCallError
|
||||
from .general import SysCommand
|
||||
from .hardware import SysInfo
|
||||
from .locale import LocaleConfiguration
|
||||
from .locale import verify_keyboard_layout, verify_x11_keyboard_layout
|
||||
from .luks import Luks2
|
||||
from .mirrors import use_mirrors, MirrorConfiguration, add_custom_mirrors
|
||||
|
|
@ -457,37 +458,36 @@ class Installer:
|
|||
with open(f'{self.target}/etc/hostname', 'w') as fh:
|
||||
fh.write(hostname + '\n')
|
||||
|
||||
def set_locale(self, locale :str, encoding :str = 'UTF-8', *args :str, **kwargs :str) -> bool:
|
||||
if not len(locale):
|
||||
return True
|
||||
|
||||
def set_locale(self, locale_config: LocaleConfiguration):
|
||||
modifier = ''
|
||||
lang = locale_config.sys_lang
|
||||
encoding = locale_config.sys_enc
|
||||
|
||||
# This is a temporary patch to fix #1200
|
||||
if '.' in locale:
|
||||
locale, potential_encoding = locale.split('.', 1)
|
||||
if '.' in locale_config.sys_lang:
|
||||
lang, potential_encoding = locale_config.sys_lang.split('.', 1)
|
||||
|
||||
# Override encoding if encoding is set to the default parameter
|
||||
# and the "found" encoding differs.
|
||||
if encoding == 'UTF-8' and encoding != potential_encoding:
|
||||
if locale_config.sys_enc == 'UTF-8' and locale_config.sys_enc != potential_encoding:
|
||||
encoding = potential_encoding
|
||||
|
||||
# Make sure we extract the modifier, that way we can put it in if needed.
|
||||
if '@' in locale:
|
||||
locale, modifier = locale.split('@', 1)
|
||||
if '@' in locale_config.sys_lang:
|
||||
lang, modifier = locale_config.sys_lang.split('@', 1)
|
||||
modifier = f"@{modifier}"
|
||||
# - End patch
|
||||
|
||||
with open(f'{self.target}/etc/locale.gen', 'a') as fh:
|
||||
fh.write(f'{locale}.{encoding}{modifier} {encoding}\n')
|
||||
fh.write(f'{lang}.{encoding}{modifier} {encoding}\n')
|
||||
|
||||
with open(f'{self.target}/etc/locale.conf', 'w') as fh:
|
||||
fh.write(f'LANG={locale}.{encoding}{modifier}\n')
|
||||
fh.write(f'LANG={lang}.{encoding}{modifier}\n')
|
||||
|
||||
try:
|
||||
SysCommand(f'/usr/bin/arch-chroot {self.target} locale-gen')
|
||||
return True
|
||||
except SysCallError:
|
||||
return False
|
||||
except SysCallError as e:
|
||||
error(f'Failed to run locale-gen on target: {e}')
|
||||
|
||||
def set_timezone(self, zone :str, *args :str, **kwargs :str) -> bool:
|
||||
if not zone:
|
||||
|
|
@ -620,7 +620,7 @@ class Installer:
|
|||
|
||||
return True
|
||||
|
||||
def mkinitcpio(self, *flags :str) -> bool:
|
||||
def mkinitcpio(self, flags: List[str], locale_config: LocaleConfiguration) -> bool:
|
||||
for plugin in plugins.values():
|
||||
if hasattr(plugin, 'on_mkinitcpio'):
|
||||
# Allow plugins to override the usage of mkinitcpio altogether.
|
||||
|
|
@ -630,7 +630,7 @@ class Installer:
|
|||
# mkinitcpio will error out if there's no vconsole.
|
||||
if (vconsole := Path(f"{self.target}/etc/vconsole.conf")).exists() is False:
|
||||
with vconsole.open('w') as fh:
|
||||
fh.write(f"KEYMAP={storage['arguments']['keyboard-layout']}\n")
|
||||
fh.write(f"KEYMAP={locale_config.kb_layout}\n")
|
||||
|
||||
with open(f'{self.target}/etc/mkinitcpio.conf', 'w') as mkinit:
|
||||
mkinit.write(f"MODULES=({' '.join(self.modules)})\n")
|
||||
|
|
@ -658,7 +658,7 @@ class Installer:
|
|||
testing: bool = False,
|
||||
multilib: bool = False,
|
||||
hostname: str = 'archinstall',
|
||||
locales: List[str] = ['en_US.UTF-8 UTF-8']
|
||||
locale_config: LocaleConfiguration = LocaleConfiguration.default()
|
||||
):
|
||||
for mod in self._disk_config.device_modifications:
|
||||
for part in mod.partitions:
|
||||
|
|
@ -734,12 +734,12 @@ class Installer:
|
|||
# sys_command(f'/usr/bin/arch-chroot {self.target} ln -s /usr/share/zoneinfo/{localtime} /etc/localtime')
|
||||
# sys_command('/usr/bin/arch-chroot /mnt hwclock --hctosys --localtime')
|
||||
self.set_hostname(hostname)
|
||||
self.set_locale(*locales[0].split())
|
||||
self.set_locale(locale_config)
|
||||
|
||||
# TODO: Use python functions for this
|
||||
SysCommand(f'/usr/bin/arch-chroot {self.target} chmod 700 /root')
|
||||
|
||||
self.mkinitcpio('-P')
|
||||
self.mkinitcpio(['-P'], locale_config)
|
||||
|
||||
self.helper_flags['base'] = True
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from .locale_conf import select_locale_lang, select_locale_enc
|
||||
from .manage_users_conf import UserList, ask_for_additional_users
|
||||
from .network_conf import ManualNetworkConfig, ask_to_configure_network
|
||||
from .utils import get_password
|
||||
|
|
@ -10,7 +9,7 @@ from .disk_conf import (
|
|||
)
|
||||
|
||||
from .general_conf import (
|
||||
ask_ntp, ask_hostname, ask_for_a_timezone, ask_for_audio_selection, select_language,
|
||||
ask_ntp, ask_hostname, ask_for_a_timezone, ask_for_audio_selection,
|
||||
select_archinstall_language, ask_additional_packages_to_install,
|
||||
add_number_of_parrallel_downloads, select_additional_repositories
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import pathlib
|
||||
from typing import List, Any, Optional, TYPE_CHECKING
|
||||
|
||||
from ..locale import list_keyboard_languages, list_timezones
|
||||
from ..locale import list_timezones, list_keyboard_languages
|
||||
from ..menu import MenuSelectionType, Menu, TextInput
|
||||
from ..output import warn
|
||||
from ..packages.packages import validate_package_list
|
||||
|
|
@ -119,18 +119,18 @@ def select_archinstall_language(languages: List[Language], preset: Language) ->
|
|||
raise ValueError('Language selection not handled')
|
||||
|
||||
|
||||
def ask_additional_packages_to_install(pre_set_packages: List[str] = []) -> List[str]:
|
||||
def ask_additional_packages_to_install(preset: List[str] = []) -> List[str]:
|
||||
# Additional packages (with some light weight error handling for invalid package names)
|
||||
print(_('Only packages such as base, base-devel, linux, linux-firmware, efibootmgr and optional profile packages are installed.'))
|
||||
print(_('If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt.'))
|
||||
|
||||
def read_packages(already_defined: list = []) -> list:
|
||||
display = ' '.join(already_defined)
|
||||
def read_packages(p: List = []) -> list:
|
||||
display = ' '.join(p)
|
||||
input_packages = TextInput(_('Write additional packages to install (space separated, leave blank to skip): '), display).run().strip()
|
||||
return input_packages.split() if input_packages else []
|
||||
|
||||
pre_set_packages = pre_set_packages if pre_set_packages else []
|
||||
packages = read_packages(pre_set_packages)
|
||||
preset = preset if preset else []
|
||||
packages = read_packages(preset)
|
||||
|
||||
if not storage['arguments']['offline'] and not storage['arguments']['no_pkg_lookups']:
|
||||
while True:
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
from typing import Any, TYPE_CHECKING, Optional
|
||||
|
||||
from ..locale import list_locales
|
||||
from ..menu import Menu, MenuSelectionType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_: Any
|
||||
|
||||
|
||||
def select_locale_lang(preset: Optional[str] = None) -> Optional[str]:
|
||||
locales = list_locales()
|
||||
locale_lang = set([locale.split()[0] for locale in locales])
|
||||
|
||||
choice = Menu(
|
||||
_('Choose which locale language to use'),
|
||||
list(locale_lang),
|
||||
sort=True,
|
||||
preset_values=preset
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
case MenuSelectionType.Skip: return preset
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def select_locale_enc(preset: Optional[str] = None) -> Optional[str]:
|
||||
locales = list_locales()
|
||||
locale_enc = set([locale.split()[1] for locale in locales])
|
||||
|
||||
choice = Menu(
|
||||
_('Choose which locale encoding to use'),
|
||||
list(locale_enc),
|
||||
sort=True,
|
||||
preset_values=preset
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
case MenuSelectionType.Skip: return preset
|
||||
|
||||
return None
|
||||
|
|
@ -29,14 +29,14 @@ def select_kernel(preset: List[str] = []) -> List[str]:
|
|||
sort=True,
|
||||
multi=True,
|
||||
preset_values=preset,
|
||||
allow_reset=True,
|
||||
allow_reset_warning_msg=warning
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Skip: return preset
|
||||
case MenuSelectionType.Reset: return []
|
||||
case MenuSelectionType.Selection: return choice.value # type: ignore
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def ask_for_bootloader(preset: Bootloader) -> Bootloader:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
from .locale_menu import LocaleConfiguration
|
||||
from .locale import (
|
||||
list_keyboard_languages, list_locales, list_x11_keyboard_languages,
|
||||
verify_keyboard_layout, verify_x11_keyboard_layout, set_kb_layout,
|
||||
list_timezones
|
||||
)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
from typing import Iterator, List
|
||||
|
||||
from ..exceptions import ServiceException, SysCallError
|
||||
from ..general import SysCommand
|
||||
from ..output import error
|
||||
|
||||
|
||||
def list_keyboard_languages() -> Iterator[str]:
|
||||
for line in SysCommand("localectl --no-pager list-keymaps", environment_vars={'SYSTEMD_COLORS': '0'}):
|
||||
yield line.decode('UTF-8').strip()
|
||||
|
||||
|
||||
def list_locales() -> List[str]:
|
||||
with open('/etc/locale.gen', 'r') as fp:
|
||||
locales = []
|
||||
# before the list of locales begins there's an empty line with a '#' in front
|
||||
# so we'll collect the localels from bottom up and halt when we're donw
|
||||
entries = fp.readlines()
|
||||
entries.reverse()
|
||||
|
||||
for entry in entries:
|
||||
text = entry.replace('#', '').strip()
|
||||
if text == '':
|
||||
break
|
||||
locales.append(text)
|
||||
|
||||
locales.reverse()
|
||||
return locales
|
||||
|
||||
|
||||
def list_x11_keyboard_languages() -> Iterator[str]:
|
||||
for line in SysCommand("localectl --no-pager list-x11-keymap-layouts", environment_vars={'SYSTEMD_COLORS': '0'}):
|
||||
yield line.decode('UTF-8').strip()
|
||||
|
||||
|
||||
def verify_keyboard_layout(layout :str) -> bool:
|
||||
for language in list_keyboard_languages():
|
||||
if layout.lower() == language.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def verify_x11_keyboard_layout(layout :str) -> bool:
|
||||
for language in list_x11_keyboard_languages():
|
||||
if layout.lower() == language.lower():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def set_kb_layout(locale :str) -> bool:
|
||||
if len(locale.strip()):
|
||||
if not verify_keyboard_layout(locale):
|
||||
error(f"Invalid keyboard locale specified: {locale}")
|
||||
return False
|
||||
|
||||
try:
|
||||
SysCommand(f'localectl set-keymap {locale}')
|
||||
except SysCallError as err:
|
||||
raise ServiceException(f"Unable to set locale '{locale}' for console: {err}")
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def list_timezones() -> Iterator[str]:
|
||||
for line in SysCommand("timedatectl --no-pager list-timezones", environment_vars={'SYSTEMD_COLORS': '0'}):
|
||||
yield line.decode('UTF-8').strip()
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Dict, Any, TYPE_CHECKING, Optional
|
||||
|
||||
from .locale import set_kb_layout, list_keyboard_languages, list_locales
|
||||
from ..menu import Selector, AbstractSubMenu, MenuSelectionType, Menu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
_: Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocaleConfiguration:
|
||||
kb_layout: str
|
||||
sys_lang: str
|
||||
sys_enc: str
|
||||
|
||||
@staticmethod
|
||||
def default() -> 'LocaleConfiguration':
|
||||
return LocaleConfiguration('us', 'en_US', 'UTF-8')
|
||||
|
||||
def json(self) -> Dict[str, str]:
|
||||
return {
|
||||
'kb_layout': self.kb_layout,
|
||||
'sys_lang': self.sys_lang,
|
||||
'sys_enc': self.sys_enc
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _load_config(cls, config: 'LocaleConfiguration', args: Dict[str, Any]) -> 'LocaleConfiguration':
|
||||
if 'sys_lang' in args:
|
||||
config.sys_lang = args['sys_lang']
|
||||
if 'sys_enc' in args:
|
||||
config.sys_enc = args['sys_enc']
|
||||
if 'kb_layout' in args:
|
||||
config.kb_layout = args['kb_layout']
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def parse_arg(cls, args: Dict[str, Any]) -> 'LocaleConfiguration':
|
||||
default = cls.default()
|
||||
|
||||
if 'locale_config' in args:
|
||||
default = cls._load_config(default, args['locale_config'])
|
||||
else:
|
||||
default = cls._load_config(default, args)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
class LocaleMenu(AbstractSubMenu):
|
||||
def __init__(
|
||||
self,
|
||||
data_store: Dict[str, Any],
|
||||
locele_conf: LocaleConfiguration
|
||||
):
|
||||
self._preset = locele_conf
|
||||
super().__init__(data_store=data_store)
|
||||
|
||||
def setup_selection_menu_options(self):
|
||||
self._menu_options['keyboard-layout'] = \
|
||||
Selector(
|
||||
_('Keyboard layout'),
|
||||
lambda preset: self._select_kb_layout(preset),
|
||||
default='us',
|
||||
enabled=True)
|
||||
self._menu_options['sys-language'] = \
|
||||
Selector(
|
||||
_('Locale language'),
|
||||
lambda preset: select_locale_lang(preset),
|
||||
default='en_US',
|
||||
enabled=True)
|
||||
self._menu_options['sys-encoding'] = \
|
||||
Selector(
|
||||
_('Locale encoding'),
|
||||
lambda preset: select_locale_enc(preset),
|
||||
default='UTF-8',
|
||||
enabled=True)
|
||||
|
||||
def run(self, allow_reset: bool = True) -> LocaleConfiguration:
|
||||
super().run(allow_reset=allow_reset)
|
||||
|
||||
return LocaleConfiguration(
|
||||
self._data_store['keyboard-layout'],
|
||||
self._data_store['sys-language'],
|
||||
self._data_store['sys-encoding']
|
||||
)
|
||||
|
||||
def _select_kb_layout(self, preset: Optional[str]) -> Optional[str]:
|
||||
kb_lang = select_kb_layout(preset)
|
||||
if kb_lang:
|
||||
set_kb_layout(kb_lang)
|
||||
return kb_lang
|
||||
|
||||
|
||||
def select_locale_lang(preset: Optional[str] = None) -> Optional[str]:
|
||||
locales = list_locales()
|
||||
locale_lang = set([locale.split()[0] for locale in locales])
|
||||
|
||||
choice = Menu(
|
||||
_('Choose which locale language to use'),
|
||||
list(locale_lang),
|
||||
sort=True,
|
||||
preset_values=preset
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
case MenuSelectionType.Skip: return preset
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def select_locale_enc(preset: Optional[str] = None) -> Optional[str]:
|
||||
locales = list_locales()
|
||||
locale_enc = set([locale.split()[1] for locale in locales])
|
||||
|
||||
choice = Menu(
|
||||
_('Choose which locale encoding to use'),
|
||||
list(locale_enc),
|
||||
sort=True,
|
||||
preset_values=preset
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
case MenuSelectionType.Skip: return preset
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def select_kb_layout(preset: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
Asks the user to select a language
|
||||
Usually this is combined with :ref:`archinstall.list_keyboard_languages`.
|
||||
|
||||
:return: The language/dictionary key of the selected language
|
||||
:rtype: str
|
||||
"""
|
||||
kb_lang = list_keyboard_languages()
|
||||
# sort alphabetically and then by length
|
||||
sorted_kb_lang = sorted(sorted(list(kb_lang)), key=len)
|
||||
|
||||
choice = Menu(
|
||||
_('Select keyboard layout'),
|
||||
sorted_kb_lang,
|
||||
preset_values=preset,
|
||||
sort=False
|
||||
).run()
|
||||
|
||||
match choice.type_:
|
||||
case MenuSelectionType.Skip: return preset
|
||||
case MenuSelectionType.Selection: return choice.single_value
|
||||
|
||||
return None
|
||||
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||
from typing import Callable, Any, List, Iterator, Tuple, Optional, Dict, TYPE_CHECKING
|
||||
|
||||
from .menu import Menu, MenuSelectionType
|
||||
from ..locale import set_keyboard_language
|
||||
from ..output import error
|
||||
from ..translationhandler import TranslationHandler, Language
|
||||
|
||||
|
|
@ -130,7 +129,7 @@ class Selector:
|
|||
if current:
|
||||
padding += 5
|
||||
description = str(self._description).ljust(padding, ' ')
|
||||
current = str(_('set: {}').format(current))
|
||||
current = current
|
||||
else:
|
||||
description = self._description
|
||||
current = ''
|
||||
|
|
@ -243,31 +242,6 @@ class AbstractMenu:
|
|||
elif selector is not None and selector.has_selection():
|
||||
self._data_store[selector_name] = selector.current_selection
|
||||
|
||||
def _missing_configs(self) -> List[str]:
|
||||
def check(s):
|
||||
return self._menu_options.get(s).has_selection()
|
||||
|
||||
def has_superuser() -> bool:
|
||||
sel = self._menu_options['!users']
|
||||
if sel.current_selection:
|
||||
return any([u.sudo for u in sel.current_selection])
|
||||
return False
|
||||
|
||||
mandatory_fields = dict(filter(lambda x: x[1].is_mandatory(), self._menu_options.items()))
|
||||
missing = set()
|
||||
|
||||
for key, selector in mandatory_fields.items():
|
||||
if key in ['!root-password', '!users']:
|
||||
if not check('!root-password') and not has_superuser():
|
||||
missing.add(
|
||||
str(_('Either root-password or at least 1 user with sudo privileges must be specified'))
|
||||
)
|
||||
elif key == 'disk_config':
|
||||
if not check('disk_config'):
|
||||
missing.add(self._menu_options['disk_config'].description)
|
||||
|
||||
return list(missing)
|
||||
|
||||
def setup_selection_menu_options(self):
|
||||
""" Define the menu options.
|
||||
Menu options can be defined here in a subclass or done per program calling self.set_option()
|
||||
|
|
@ -328,9 +302,6 @@ class AbstractMenu:
|
|||
cursor_pos = None
|
||||
|
||||
while True:
|
||||
# Before continuing, set the preferred keyboard layout/language in the current terminal.
|
||||
# This will just help the user with the next following questions.
|
||||
self._set_kb_language()
|
||||
enabled_menus = self._menus_to_enable()
|
||||
|
||||
padding = self._get_menu_text_padding(list(enabled_menus.values()))
|
||||
|
|
@ -425,13 +396,6 @@ class AbstractMenu:
|
|||
|
||||
return True
|
||||
|
||||
def _set_kb_language(self):
|
||||
""" general for ArchInstall"""
|
||||
# Before continuing, set the preferred keyboard layout/language in the current terminal.
|
||||
# This will just help the user with the next following questions.
|
||||
if self._data_store.get('keyboard-layout', None) and len(self._data_store['keyboard-layout']):
|
||||
set_keyboard_language(self._data_store['keyboard-layout'])
|
||||
|
||||
def _verify_selection_enabled(self, selection_name: str) -> bool:
|
||||
""" general """
|
||||
if selection := self._menu_options.get(selection_name, None):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from pathlib import Path
|
||||
from typing import Any, TYPE_CHECKING, Optional
|
||||
from typing import Any, TYPE_CHECKING, Optional, List
|
||||
|
||||
from ..output import FormattedOutput
|
||||
from ..output import info
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -28,3 +29,23 @@ def is_subpath(first: Path, second: Path):
|
|||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def format_cols(items: List[str], header: Optional[str]) -> str:
|
||||
if header:
|
||||
text = f'{header}:\n'
|
||||
else:
|
||||
text = ''
|
||||
|
||||
nr_items = len(items)
|
||||
if nr_items <= 5:
|
||||
col = 1
|
||||
elif nr_items <= 10:
|
||||
col = 2
|
||||
elif nr_items <= 15:
|
||||
col = 3
|
||||
else:
|
||||
col = 4
|
||||
|
||||
text += FormattedOutput.as_columns(items, col)
|
||||
return text
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import Any, TYPE_CHECKING
|
|||
import archinstall
|
||||
from archinstall import info, debug
|
||||
from archinstall import SysInfo
|
||||
from archinstall.lib import locale
|
||||
from archinstall.lib import disk
|
||||
from archinstall.lib.global_menu import GlobalMenu
|
||||
from archinstall.default_profiles.applications.pipewire import PipewireProfile
|
||||
|
|
@ -42,14 +43,10 @@ def ask_user_questions():
|
|||
|
||||
global_menu.enable('archinstall-language')
|
||||
|
||||
global_menu.enable('keyboard-layout')
|
||||
|
||||
# Set which region to download packages from during the installation
|
||||
global_menu.enable('mirror_config')
|
||||
|
||||
global_menu.enable('sys-language')
|
||||
|
||||
global_menu.enable('sys-encoding')
|
||||
global_menu.enable('locale_config')
|
||||
|
||||
global_menu.enable('disk_config', mandatory=True)
|
||||
|
||||
|
|
@ -76,7 +73,7 @@ def ask_user_questions():
|
|||
global_menu.enable('audio')
|
||||
|
||||
# Ask for preferred kernel:
|
||||
global_menu.enable('kernels')
|
||||
global_menu.enable('kernels', mandatory=True)
|
||||
|
||||
global_menu.enable('packages')
|
||||
|
||||
|
|
@ -114,9 +111,7 @@ def perform_installation(mountpoint: Path):
|
|||
# Retrieve list of additional repositories and set boolean values appropriately
|
||||
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
|
||||
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
|
||||
|
||||
locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
|
||||
|
||||
locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
|
||||
with Installer(
|
||||
|
|
@ -147,7 +142,7 @@ def perform_installation(mountpoint: Path):
|
|||
testing=enable_testing,
|
||||
multilib=enable_multilib,
|
||||
hostname=archinstall.arguments.get('hostname', 'archlinux'),
|
||||
locales=[locale]
|
||||
locale_config=locale_config
|
||||
)
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
|
|
@ -210,7 +205,7 @@ def perform_installation(mountpoint: Path):
|
|||
|
||||
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
|
||||
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
|
||||
installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
|
||||
installation.set_keyboard_language(locale_config.kb_layout)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
profile_config.profile.post_install(installation)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from archinstall import SysInfo, info, debug
|
|||
from archinstall.lib import mirrors
|
||||
from archinstall.lib import models
|
||||
from archinstall.lib import disk
|
||||
from archinstall.lib import locale
|
||||
from archinstall.lib.networking import check_mirror_reachable
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib import menu
|
||||
|
|
@ -92,14 +93,14 @@ class SwissMainMenu(GlobalMenu):
|
|||
match self._execution_mode:
|
||||
case ExecutionMode.Full | ExecutionMode.Lineal:
|
||||
options_list = [
|
||||
'keyboard-layout', 'mirror_config', 'disk_config',
|
||||
'mirror_config', 'disk_config',
|
||||
'disk_encryption', 'swap', 'bootloader', 'hostname', '!root-password',
|
||||
'!users', 'profile_config', 'audio', 'kernels', 'packages', 'additional-repositories', 'nic',
|
||||
'timezone', 'ntp'
|
||||
]
|
||||
|
||||
if archinstall.arguments.get('advanced', False):
|
||||
options_list.extend(['sys-language', 'sys-encoding'])
|
||||
options_list.extend(['locale_config'])
|
||||
|
||||
mandatory_list = ['disk_config', 'bootloader', 'hostname']
|
||||
case ExecutionMode.Only_HD:
|
||||
|
|
@ -107,7 +108,7 @@ class SwissMainMenu(GlobalMenu):
|
|||
mandatory_list = ['disk_config']
|
||||
case ExecutionMode.Only_OS:
|
||||
options_list = [
|
||||
'keyboard-layout', 'mirror_config','bootloader', 'hostname',
|
||||
'mirror_config','bootloader', 'hostname',
|
||||
'!root-password', '!users', 'profile_config', 'audio', 'kernels',
|
||||
'packages', 'additional-repositories', 'nic', 'timezone', 'ntp'
|
||||
]
|
||||
|
|
@ -115,7 +116,7 @@ class SwissMainMenu(GlobalMenu):
|
|||
mandatory_list = ['hostname']
|
||||
|
||||
if archinstall.arguments.get('advanced', False):
|
||||
options_list += ['sys-language','sys-encoding']
|
||||
options_list += ['locale_config']
|
||||
case ExecutionMode.Minimal:
|
||||
pass
|
||||
case _:
|
||||
|
|
@ -176,8 +177,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
|
|||
|
||||
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
|
||||
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
|
||||
|
||||
locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
|
||||
locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
|
||||
|
||||
with Installer(
|
||||
mountpoint,
|
||||
|
|
@ -206,7 +206,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
|
|||
testing=enable_testing,
|
||||
multilib=enable_multilib,
|
||||
hostname=archinstall.arguments.get('hostname', 'archlinux'),
|
||||
locales=[locale]
|
||||
locale_config=locale_config
|
||||
)
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
|
|
@ -263,7 +263,7 @@ def perform_installation(mountpoint: Path, exec_mode: ExecutionMode):
|
|||
|
||||
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
|
||||
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
|
||||
installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
|
||||
installation.set_keyboard_language(locale_config.kb_layout)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
profile_config.profile.post_install(installation)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from archinstall.default_profiles.applications.pipewire import PipewireProfile
|
|||
from archinstall import disk
|
||||
from archinstall import menu
|
||||
from archinstall import models
|
||||
from archinstall import locale
|
||||
from archinstall import info, debug
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -21,14 +22,10 @@ def ask_user_questions():
|
|||
|
||||
global_menu.enable('archinstall-language')
|
||||
|
||||
global_menu.enable('keyboard-layout')
|
||||
|
||||
# Set which region to download packages from during the installation
|
||||
global_menu.enable('mirror_config')
|
||||
|
||||
global_menu.enable('sys-language')
|
||||
|
||||
global_menu.enable('sys-encoding')
|
||||
global_menu.enable('locale_config')
|
||||
|
||||
global_menu.enable('disk_config', mandatory=True)
|
||||
|
||||
|
|
@ -55,7 +52,7 @@ def ask_user_questions():
|
|||
global_menu.enable('audio')
|
||||
|
||||
# Ask for preferred kernel:
|
||||
global_menu.enable('kernels')
|
||||
global_menu.enable('kernels', mandatory=True)
|
||||
|
||||
global_menu.enable('packages')
|
||||
|
||||
|
|
@ -93,9 +90,7 @@ def perform_installation(mountpoint: Path):
|
|||
# Retrieve list of additional repositories and set boolean values appropriately
|
||||
enable_testing = 'testing' in archinstall.arguments.get('additional-repositories', [])
|
||||
enable_multilib = 'multilib' in archinstall.arguments.get('additional-repositories', [])
|
||||
|
||||
locale = f"{archinstall.arguments.get('sys-language', 'en_US')} {archinstall.arguments.get('sys-encoding', 'UTF-8').upper()}"
|
||||
|
||||
locale_config: locale.LocaleConfiguration = archinstall.arguments['locale_config']
|
||||
disk_encryption: disk.DiskEncryption = archinstall.arguments.get('disk_encryption', None)
|
||||
|
||||
with Installer(
|
||||
|
|
@ -126,7 +121,7 @@ def perform_installation(mountpoint: Path):
|
|||
testing=enable_testing,
|
||||
multilib=enable_multilib,
|
||||
hostname=archinstall.arguments.get('hostname', 'archlinux'),
|
||||
locales=[locale]
|
||||
locale_config=locale_config
|
||||
)
|
||||
|
||||
if mirror_config := archinstall.arguments.get('mirror_config', None):
|
||||
|
|
@ -189,7 +184,7 @@ def perform_installation(mountpoint: Path):
|
|||
|
||||
# This step must be after profile installs to allow profiles_bck to install language pre-requisits.
|
||||
# After which, this step will set the language both for console and x11 if x11 was installed for instance.
|
||||
installation.set_keyboard_language(archinstall.arguments['keyboard-layout'])
|
||||
installation.set_keyboard_language(locale_config.kb_layout)
|
||||
|
||||
if profile_config := archinstall.arguments.get('profile_config', None):
|
||||
profile_config.profile.post_install(installation)
|
||||
|
|
|
|||
Loading…
Reference in New Issue