Merge remote-tracking branch 'origin/master' into refactor-profiles
This commit is contained in:
commit
94e5eee2f2
|
|
@ -1,28 +0,0 @@
|
|||
#on:
|
||||
# push:
|
||||
# paths:
|
||||
# - 'archinstall/locales/**'
|
||||
# pull_request:
|
||||
# paths:
|
||||
# - 'archinstall/locales/**'
|
||||
#name: Verify local_generate script was run on translation changes
|
||||
#jobs:
|
||||
# translation-check:
|
||||
# runs-on: ubuntu-latest
|
||||
# container:
|
||||
# image: archlinux/archlinux:latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - run: pacman --noconfirm -Syu python git diffutils
|
||||
# - name: Verify all translation scripts are up to date
|
||||
# run: |
|
||||
# cd ..
|
||||
# cp -r archinstall archinstall_orig
|
||||
# cd archinstall/archinstall/locales
|
||||
# bash locales_generator.sh 1> /dev/null
|
||||
# cd ../../..
|
||||
# git diff \
|
||||
# --quiet --no-index --name-only \
|
||||
# archinstall_orig/archinstall/locales \
|
||||
# archinstall/archinstall/locales \
|
||||
# || (echo "Translation files have not been updated after translation, please run ./locales_generator.sh once more and commit" && exit 1)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
default_stages: ['pre-commit']
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.15.11
|
||||
rev: v0.15.12
|
||||
hooks:
|
||||
# fix unused imports and sort them
|
||||
- id: ruff
|
||||
|
|
@ -31,7 +31,7 @@ repos:
|
|||
args: [--config=.flake8]
|
||||
fail_fast: true
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.20.1
|
||||
rev: v1.20.2
|
||||
hooks:
|
||||
- id: mypy
|
||||
args: [
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -179,7 +179,7 @@ This can be done by installing `pacman -S arch-install-scripts util-linux` local
|
|||
# losetup --partscan --show ./testimage.img
|
||||
# pip install --upgrade archinstall
|
||||
# python -m archinstall --script guided
|
||||
# qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd
|
||||
# qemu-system-x86_64 -enable-kvm -machine q35,accel=kvm -device intel-iommu -cpu host -m 4096 -boot order=d -drive file=./testimage.img,format=raw -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd
|
||||
|
||||
This will create a *20 GB* `testimage.img` and create a loop device which we can use to format and install to.<br>
|
||||
`archinstall` is installed and executed in [guided mode](#docs-todo). Once the installation is complete, ~~you can use qemu/kvm to boot the test media.~~<br>
|
||||
|
|
@ -199,8 +199,8 @@ You may want to boot an ISO image in a VM to test `archinstall` in there.
|
|||
qemu-system-x86_64 -enable-kvm \
|
||||
-machine q35,accel=kvm -device intel-iommu \
|
||||
-cpu host -m 4096 -boot order=d \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
|
||||
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw
|
||||
```
|
||||
|
||||
|
|
@ -209,8 +209,8 @@ HINT: For espeakup support
|
|||
qemu-system-x86_64 -enable-kvm \
|
||||
-machine q35,accel=kvm -device intel-iommu \
|
||||
-cpu host -m 4096 -boot order=d \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/ovmf/x64/OVMF.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \
|
||||
-drive if=pflash,format=raw,readonly,file=/usr/share/edk2/x64/OVMF_VARS.4m.fd \
|
||||
-drive file=./archlinux-2025.12.01-x86_64.iso,format=raw \
|
||||
-device intel-hda -device hda-duplex,audiodev=snd0 \
|
||||
-audiodev pa,id=snd0,server=/run/user/1000/pulse/native
|
||||
|
|
@ -219,6 +219,10 @@ qemu-system-x86_64 -enable-kvm \
|
|||
|
||||
# FAQ
|
||||
|
||||
## AUR
|
||||
|
||||
`archinstall` will not offer or bundle AUR helpers or AUR packages due to a current consensus. This is not any individual developers decision. The reasons and discussions for this stance on the topic can be found on our mailing list thread: [(optional) AUR helper in archinstall](https://lists.archlinux.org/archives/list/arch-dev-public@lists.archlinux.org/thread/VYOULH2GOJLFM2BXOFLWH3D754YXFPSL/).
|
||||
|
||||
## Keyring out-of-date
|
||||
For a description of the problem see https://archinstall.archlinux.page/help/known_issues.html#keyring-is-out-of-date-2213 and discussion in issue https://github.com/archlinux/archinstall/issues/2213.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from archinstall.default_profiles.profile import DisplayServerType, GreeterType,
|
|||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.output import info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class BudgieProfile(Profile):
|
|||
'Budgie',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
@ -18,12 +18,12 @@ class BudgieProfile(Profile):
|
|||
return [
|
||||
'materia-gtk-theme',
|
||||
'budgie',
|
||||
'mate-terminal',
|
||||
'nemo',
|
||||
'konsole',
|
||||
'dolphin',
|
||||
'papirus-icon-theme',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.LightdmSlick
|
||||
return GreeterType.Sddm
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
|
||||
|
||||
class CutefishProfile(Profile):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
'Cutefish',
|
||||
ProfileType.DesktopEnv,
|
||||
support_gfx_driver=True,
|
||||
display_server=DisplayServerType.Xorg,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'cutefish',
|
||||
'noto-fonts',
|
||||
]
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.Sddm
|
||||
|
|
@ -5,8 +5,8 @@ from archinstall.default_profiles.profile import CustomSetting, DisplayServerTyp
|
|||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.packages.packages import available_package, package_group_info
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class PlasmaFlavor(StrEnum):
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from enum import Enum
|
|||
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class SeatAccess(Enum):
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from archinstall.default_profiles.profile import Profile, ProfileType, SelectRes
|
|||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.output import info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from archinstall.lib.installer import Installer
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ from archinstall.lib.models.application import (
|
|||
PrintServiceConfiguration,
|
||||
)
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from archinstall.lib.models.device import DiskEncryption, DiskLayoutConfiguratio
|
|||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models.network import NetworkConfiguration
|
||||
from archinstall.lib.models.package_types import DEFAULT_KERNEL
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
|
|
@ -29,7 +30,7 @@ from archinstall.lib.output import debug, error, logger, warn
|
|||
from archinstall.lib.plugins import load_plugin
|
||||
from archinstall.lib.translationhandler import Language, tr, translation_handler
|
||||
from archinstall.lib.version import get_version
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
@p_dataclass
|
||||
|
|
@ -71,7 +72,7 @@ class ArchConfig:
|
|||
auth_config: AuthenticationConfiguration | None = None
|
||||
swap: ZramConfiguration | None = None
|
||||
hostname: str = 'archlinux'
|
||||
kernels: list[str] = field(default_factory=lambda: ['linux'])
|
||||
kernels: list[str] = field(default_factory=lambda: [DEFAULT_KERNEL.value])
|
||||
ntp: bool = True
|
||||
packages: list[str] = field(default_factory=list)
|
||||
pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration.default)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class AuthenticationHandler:
|
|||
def _add_u2f_entry(self, file: Path, entry: str) -> None:
|
||||
if not file.exists():
|
||||
debug(f'File does not exist: {file}')
|
||||
return None
|
||||
return
|
||||
|
||||
content = file.read_text().splitlines()
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class AuthenticationHandler:
|
|||
|
||||
install_session.pacman.strap('pam-u2f')
|
||||
|
||||
print(tr(f'Setting up U2F login: {u2f_config.u2f_login_method.value}'))
|
||||
print(tr('Setting up U2F login: {}').format(u2f_config.u2f_login_method.value))
|
||||
|
||||
# https://developers.yubico.com/pam-u2f/
|
||||
u2f_auth_file = install_session.target / 'etc/u2f_mappings'
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ from archinstall.lib.models.users import Password, User
|
|||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.user.user_menu import select_users
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class AuthenticationMenu(AbstractSubMenu[AuthenticationConfiguration]):
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
|||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.hardware import SysInfo
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import DiskLayoutConfiguration
|
||||
|
||||
|
||||
class BootloaderValidationFailureKind(Enum):
|
||||
LimineNonFatBoot = auto()
|
||||
LimineLayout = auto()
|
||||
BootloaderRequiresUefi = auto()
|
||||
EfistubNonFatBoot = auto()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BootloaderValidationFailure:
|
||||
kind: BootloaderValidationFailureKind
|
||||
description: str
|
||||
|
||||
|
||||
def validate_bootloader_layout(
|
||||
bootloader_config: BootloaderConfiguration | None,
|
||||
disk_config: DiskLayoutConfiguration | None,
|
||||
) -> BootloaderValidationFailure | None:
|
||||
"""Validate bootloader configuration against disk layout.
|
||||
|
||||
Returns a failure with a human-readable description if the configuration
|
||||
would produce an unbootable system, or None if it is valid.
|
||||
"""
|
||||
if not (bootloader_config and disk_config):
|
||||
return None
|
||||
|
||||
bootloader = bootloader_config.bootloader
|
||||
|
||||
if bootloader == Bootloader.NO_BOOTLOADER:
|
||||
return None
|
||||
|
||||
if bootloader.is_uefi_only() and not SysInfo.has_uefi():
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.BootloaderRequiresUefi,
|
||||
description=f'{bootloader.value} requires a UEFI system.',
|
||||
)
|
||||
|
||||
boot_part = next(
|
||||
(p for m in disk_config.device_modifications if (p := m.get_boot_partition())),
|
||||
None,
|
||||
)
|
||||
|
||||
if bootloader == Bootloader.Efistub:
|
||||
# The UEFI firmware reads the kernel directly from the boot partition,
|
||||
# which must be FAT.
|
||||
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.EfistubNonFatBoot,
|
||||
description='Efistub does not support booting with a non-FAT boot partition.',
|
||||
)
|
||||
|
||||
if bootloader == Bootloader.Limine:
|
||||
# Limine reads its config and kernels from the boot partition, which
|
||||
# must be FAT.
|
||||
if boot_part and (boot_part.fs_type is None or not boot_part.fs_type.is_fat()):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.LimineNonFatBoot,
|
||||
description='Limine does not support booting with a non-FAT boot partition.',
|
||||
)
|
||||
|
||||
# When the ESP is the boot partition but mounted outside /boot and
|
||||
# UKI is disabled, kernels end up on the root filesystem which
|
||||
# Limine cannot access.
|
||||
if not bootloader_config.uki:
|
||||
efi_part = next(
|
||||
(p for m in disk_config.device_modifications if (p := m.get_efi_partition())),
|
||||
None,
|
||||
)
|
||||
if efi_part and efi_part == boot_part and efi_part.mountpoint != Path('/boot'):
|
||||
return BootloaderValidationFailure(
|
||||
kind=BootloaderValidationFailureKind.LimineLayout,
|
||||
description=(
|
||||
f'Limine requires kernels on a FAT partition. The ESP is mounted at {efi_part.mountpoint}, '
|
||||
'enable UKI or add a separate /boot partition to install Limine.'
|
||||
),
|
||||
)
|
||||
|
||||
return None
|
||||
|
|
@ -44,8 +44,8 @@ class SysCommandWorker:
|
|||
self._trace_log_pos = 0
|
||||
self.poll_object = epoll()
|
||||
self.child_fd: int | None = None
|
||||
self.started: float | None = None
|
||||
self.ended: float | None = None
|
||||
self.started = False
|
||||
self.ended = False
|
||||
self.remove_vt100_escape_codes_from_lines: bool = remove_vt100_escape_codes_from_lines
|
||||
|
||||
def __contains__(self, key: bytes) -> bool:
|
||||
|
|
@ -117,7 +117,7 @@ class SysCommandWorker:
|
|||
def is_alive(self) -> bool:
|
||||
self.poll()
|
||||
|
||||
if self.started and self.ended is None:
|
||||
if self.started and not self.ended:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
@ -173,11 +173,11 @@ class SysCommandWorker:
|
|||
self.peak(output)
|
||||
self._trace_log += output
|
||||
except OSError:
|
||||
self.ended = time.time()
|
||||
self.ended = True
|
||||
break
|
||||
|
||||
if self.ended or (not got_output and not _pid_exists(self.pid)):
|
||||
self.ended = time.time()
|
||||
self.ended = True
|
||||
try:
|
||||
wait_status = os.waitpid(self.pid, 0)[1]
|
||||
self.exit_code = os.waitstatus_to_exitcode(wait_status)
|
||||
|
|
@ -215,7 +215,7 @@ class SysCommandWorker:
|
|||
# Only parent process moves back to the original working directory
|
||||
os.chdir(old_dir)
|
||||
|
||||
self.started = time.time()
|
||||
self.started = True
|
||||
self.poll_object.register(self.child_fd, EPOLLIN | EPOLLHUP)
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ from archinstall.lib.args import ArchConfig
|
|||
from archinstall.lib.crypt import encrypt
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.menu.util import get_password, prompt_dir
|
||||
from archinstall.lib.models.bootloader import Bootloader
|
||||
from archinstall.lib.models.network import NetworkConfiguration
|
||||
from archinstall.lib.output import debug, logger, warn
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ConfigurationOutput:
|
||||
|
|
@ -58,10 +60,77 @@ class ConfigurationOutput:
|
|||
debug(' -- Chosen configuration --')
|
||||
debug(self.user_config_to_json())
|
||||
|
||||
async def confirm_config(self) -> bool:
|
||||
def as_summary(self) -> str:
|
||||
"""
|
||||
Render a concise two-column summary of the current configuration.
|
||||
|
||||
The left column holds section labels, the right column holds values.
|
||||
Column width adapts to the longest translated label so translations
|
||||
do not break the alignment. Rows whose underlying config is not set
|
||||
are skipped.
|
||||
|
||||
Returns an empty string if nothing meaningful to show.
|
||||
"""
|
||||
rows: list[tuple[str, str]] = []
|
||||
|
||||
disk_config = self._config.disk_config
|
||||
if disk_config and disk_config.device_modifications:
|
||||
disk_parts: list[str] = []
|
||||
for mod in disk_config.device_modifications:
|
||||
path = str(mod.device_path)
|
||||
root_part = mod.get_root_partition()
|
||||
flags: list[str] = []
|
||||
if root_part and root_part.fs_type:
|
||||
flags.append(root_part.fs_type.value)
|
||||
if disk_config.disk_encryption:
|
||||
flags.append(tr('LUKS'))
|
||||
disk_parts.append(f'{path} ({" + ".join(flags)})' if flags else path)
|
||||
rows.append((tr('Disks'), ', '.join(disk_parts)))
|
||||
|
||||
bl_config = self._config.bootloader_config
|
||||
if bl_config and bl_config.bootloader != Bootloader.NO_BOOTLOADER:
|
||||
rows.append((tr('Bootloader'), bl_config.bootloader.value))
|
||||
|
||||
kernels = self._config.kernels
|
||||
if kernels:
|
||||
rows.append((tr('Kernel'), ', '.join(kernels)))
|
||||
|
||||
profile_config = self._config.profile_config
|
||||
if profile_config and profile_config.profile:
|
||||
names = profile_config.profile.current_selection_names()
|
||||
rows.append((tr('Profile'), ', '.join(names) if names else profile_config.profile.name))
|
||||
if profile_config.greeter:
|
||||
rows.append((tr('Greeter'), profile_config.greeter.value))
|
||||
|
||||
packages = self._config.packages
|
||||
if packages:
|
||||
rows.append((tr('Packages'), str(len(packages))))
|
||||
|
||||
net_config = self._config.network_config
|
||||
if isinstance(net_config, NetworkConfiguration):
|
||||
rows.append((tr('Network'), net_config.type.display_msg()))
|
||||
|
||||
locale_config = self._config.locale_config
|
||||
if locale_config:
|
||||
rows.append((tr('Locale'), locale_config.sys_lang))
|
||||
|
||||
tz = self._config.timezone
|
||||
if tz:
|
||||
rows.append((tr('Timezone'), tz))
|
||||
|
||||
if not rows:
|
||||
return ''
|
||||
|
||||
label_width = max(len(label) for label, _ in rows) + 2
|
||||
return '\n'.join(f'{label:<{label_width}}{value}' for label, value in rows)
|
||||
|
||||
async def confirm_config(self, show_install_warnings: bool = False) -> bool:
|
||||
header = f'{tr("The specified configuration will be applied")}. '
|
||||
header += tr('Would you like to continue?') + '\n'
|
||||
|
||||
if show_install_warnings:
|
||||
header += self._render_install_warnings()
|
||||
|
||||
group = MenuItemGroup.yes_no()
|
||||
group.set_preview_for_all(lambda x: self.user_config_to_json())
|
||||
|
||||
|
|
@ -79,6 +148,22 @@ class ConfigurationOutput:
|
|||
|
||||
return True
|
||||
|
||||
def get_install_warnings(self) -> list[str]:
|
||||
warnings: list[str] = []
|
||||
|
||||
if not isinstance(self._config.network_config, NetworkConfiguration):
|
||||
warnings.append(tr('Warning: no network configuration selected. Network will need to be set up manually on the installed system.'))
|
||||
|
||||
return warnings
|
||||
|
||||
def _render_install_warnings(self) -> str:
|
||||
warnings = self.get_install_warnings()
|
||||
|
||||
if not warnings:
|
||||
return ''
|
||||
|
||||
return '\n' + '\n'.join(f'[yellow]{w}[/]' for w in warnings) + '\n'
|
||||
|
||||
def _is_valid_path(self, dest_path: Path) -> bool:
|
||||
dest_path_ok = dest_path.exists() and dest_path.is_dir()
|
||||
if not dest_path_ok:
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class DeviceHandler:
|
|||
case FilesystemType.EXT2 | FilesystemType.EXT3 | FilesystemType.EXT4:
|
||||
# Force create
|
||||
options.append('-F')
|
||||
case FilesystemType.FAT12 | FilesystemType.FAT16 | FilesystemType.FAT32:
|
||||
case _ if fs_type.is_fat():
|
||||
mkfs_type = 'fat'
|
||||
# Set FAT size
|
||||
options.extend(('-F', fs_type.value.removeprefix(mkfs_type)))
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ from archinstall.lib.models.device import (
|
|||
)
|
||||
from archinstall.lib.output import FormattedOutput, debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ from archinstall.lib.models.device import (
|
|||
from archinstall.lib.models.users import Password
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class DiskEncryptionMenu(AbstractSubMenu[DiskEncryption]):
|
||||
|
|
@ -375,7 +375,7 @@ async def select_lvm_vols_to_encrypt(
|
|||
async def select_iteration_time(preset: int | None = None) -> int | None:
|
||||
header = tr('Enter iteration time for LUKS encryption (in milliseconds)') + '\n'
|
||||
header += tr('Higher values increase security but slow down boot time') + '\n'
|
||||
header += tr(f'Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000') + '\n'
|
||||
header += tr('Default: {}ms, Recommended range: 1000-60000').format(DEFAULT_ITER_TIME) + '\n'
|
||||
|
||||
def validate_iter_time(value: str) -> str | None:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ from archinstall.lib.models.device import (
|
|||
)
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class FreeSpace:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from archinstall.lib.menu.list_manager import ListManager
|
|||
from archinstall.lib.menu.util import prompt_dir
|
||||
from archinstall.lib.models.device import SubvolumeModification
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class SubvolumeMenu(ListManager[SubvolumeModification]):
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from archinstall.lib.locale.utils import list_timezones
|
|||
from archinstall.lib.menu.helpers import Confirmation, Input, Selection
|
||||
from archinstall.lib.output import warn
|
||||
from archinstall.lib.translationhandler import Language, tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class PostInstallationAction(Enum):
|
||||
|
|
|
|||
|
|
@ -3,29 +3,24 @@ from typing import assert_never
|
|||
from archinstall.lib.hardware import GfxDriver, SysInfo
|
||||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.models.application import ZramAlgorithm, ZramConfiguration
|
||||
from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
async def select_kernel(preset: list[str] = []) -> list[str]:
|
||||
async def select_kernel(preset: list[Kernel] = []) -> list[Kernel]:
|
||||
"""
|
||||
Asks the user to select a kernel for system.
|
||||
|
||||
:return: The string as a selected kernel
|
||||
:rtype: string
|
||||
"""
|
||||
kernels = ['linux', 'linux-lts', 'linux-zen', 'linux-hardened']
|
||||
default_kernel = 'linux'
|
||||
group = MenuItemGroup.from_enum(Kernel, sort_items=True, preset=preset)
|
||||
group.set_default_by_value(DEFAULT_KERNEL)
|
||||
group.set_focus_by_value(DEFAULT_KERNEL)
|
||||
|
||||
items = [MenuItem(k, value=k) for k in kernels]
|
||||
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
group.set_default_by_value(default_kernel)
|
||||
group.set_focus_by_value(default_kernel)
|
||||
group.set_selected_by_value(preset)
|
||||
|
||||
result = await Selection[str](
|
||||
result = await Selection[Kernel](
|
||||
group,
|
||||
header=tr('Select which kernel(s) to install'),
|
||||
allow_skip=True,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ from archinstall.lib.applications.application_menu import ApplicationMenu
|
|||
from archinstall.lib.args import ArchConfig
|
||||
from archinstall.lib.authentication.authentication_menu import AuthenticationMenu
|
||||
from archinstall.lib.bootloader.bootloader_menu import BootloaderMenu
|
||||
from archinstall.lib.configuration import save_config
|
||||
from archinstall.lib.bootloader.utils import validate_bootloader_layout
|
||||
from archinstall.lib.configuration import ConfigurationOutput, save_config
|
||||
from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
|
||||
from archinstall.lib.general.general_menu import select_hostname, select_ntp, select_timezone
|
||||
from archinstall.lib.general.system_menu import select_kernel, select_swap
|
||||
|
|
@ -17,10 +18,11 @@ from archinstall.lib.mirror.mirror_menu import MirrorMenu
|
|||
from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration
|
||||
from archinstall.lib.models.authentication import AuthenticationConfiguration
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType, FilesystemType, PartitionModification
|
||||
from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType, PartitionModification
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models.network import NetworkConfiguration, NicType
|
||||
from archinstall.lib.models.package_types import DEFAULT_KERNEL
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
|
|
@ -30,8 +32,8 @@ from archinstall.lib.packages.packages import list_available_packages, select_ad
|
|||
from archinstall.lib.pacman.config import PacmanConfig
|
||||
from archinstall.lib.pacman.pacman_menu import PacmanMenu
|
||||
from archinstall.lib.translationhandler import Language, tr, translation_handler
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.components import tui
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
||||
|
||||
class GlobalMenu(AbstractMenu[None]):
|
||||
|
|
@ -102,7 +104,7 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
),
|
||||
MenuItem(
|
||||
text=tr('Kernels'),
|
||||
value=['linux'],
|
||||
value=[DEFAULT_KERNEL],
|
||||
action=select_kernel,
|
||||
preview_action=self._prev_kernel,
|
||||
mandatory=True,
|
||||
|
|
@ -460,8 +462,6 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
if not bootloader_config or bootloader_config.bootloader == Bootloader.NO_BOOTLOADER:
|
||||
return None
|
||||
|
||||
bootloader = bootloader_config.bootloader
|
||||
|
||||
if disk_config := self._item_group.find_by_key('disk_config').value:
|
||||
for layout in disk_config.device_modifications:
|
||||
if root_partition := layout.get_root_partition():
|
||||
|
|
@ -486,16 +486,11 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
if efi_partition is None:
|
||||
return 'EFI system partition (ESP) not found'
|
||||
|
||||
if efi_partition.fs_type not in [FilesystemType.FAT12, FilesystemType.FAT16, FilesystemType.FAT32]:
|
||||
if efi_partition.fs_type is None or not efi_partition.fs_type.is_fat():
|
||||
return 'ESP must be formatted as a FAT filesystem'
|
||||
|
||||
if bootloader == Bootloader.Limine:
|
||||
if boot_partition.fs_type not in [FilesystemType.FAT12, FilesystemType.FAT16, FilesystemType.FAT32]:
|
||||
return 'Limine does not support booting with a non-FAT boot partition'
|
||||
|
||||
elif bootloader == Bootloader.Refind:
|
||||
if not self._uefi:
|
||||
return 'rEFInd can only be used on UEFI systems'
|
||||
if failure := validate_bootloader_layout(bootloader_config, disk_config):
|
||||
return failure.description
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -509,7 +504,11 @@ class GlobalMenu(AbstractMenu[None]):
|
|||
if error := self._validate_bootloader():
|
||||
return tr(f'Invalid configuration: {error}')
|
||||
|
||||
return None
|
||||
self.sync_all_to_config()
|
||||
summary = ConfigurationOutput(self._arch_config).as_summary()
|
||||
if summary:
|
||||
return f'{tr("Ready to install")}\n\n{summary}'
|
||||
return tr('Ready to install')
|
||||
|
||||
def _prev_profile(self, item: MenuItem) -> str | None:
|
||||
profile_config: ProfileConfiguration | None = item.value
|
||||
|
|
|
|||
|
|
@ -68,6 +68,19 @@ class GfxDriver(Enum):
|
|||
case _:
|
||||
return False
|
||||
|
||||
def is_nvidia_proprietary(self) -> bool:
|
||||
"""
|
||||
True for Nvidia drivers that ship proprietary userspace components.
|
||||
Currently only NvidiaOpenKernel (nvidia-open-dkms): open kernel module
|
||||
paired with proprietary userspace. NvidiaOpenSource (nouveau) is fully
|
||||
open and works with Sway, so it is excluded.
|
||||
"""
|
||||
match self:
|
||||
case GfxDriver.NvidiaOpenKernel:
|
||||
return True
|
||||
case _:
|
||||
return False
|
||||
|
||||
def packages_text(self) -> str:
|
||||
pkg_names = [p.value for p in self.gfx_packages()]
|
||||
text = tr('Installed packages') + ':\n'
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from types import TracebackType
|
|||
from typing import Any, Self
|
||||
|
||||
from archinstall.lib.boot import Boot
|
||||
from archinstall.lib.bootloader.utils import validate_bootloader_layout
|
||||
from archinstall.lib.command import SysCommand, run
|
||||
from archinstall.lib.disk.fido import Fido2
|
||||
from archinstall.lib.disk.luks import Luks2, unlock_luks2_dev
|
||||
|
|
@ -30,7 +31,7 @@ from archinstall.lib.linux_path import LPath
|
|||
from archinstall.lib.locale.utils import verify_keyboard_layout, verify_x11_keyboard_layout
|
||||
from archinstall.lib.mirror.mirror_handler import MirrorListHandler
|
||||
from archinstall.lib.models.application import ZramAlgorithm
|
||||
from archinstall.lib.models.bootloader import Bootloader
|
||||
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
|
||||
from archinstall.lib.models.device import (
|
||||
DiskEncryption,
|
||||
DiskLayoutConfiguration,
|
||||
|
|
@ -47,6 +48,7 @@ from archinstall.lib.models.device import (
|
|||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.models.mirrors import MirrorConfiguration
|
||||
from archinstall.lib.models.network import Nic
|
||||
from archinstall.lib.models.package_types import DEFAULT_KERNEL, Kernel
|
||||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.models.users import User
|
||||
|
|
@ -59,7 +61,12 @@ from archinstall.lib.plugins import plugins
|
|||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
# Any package that the Installer() is responsible for (optional and the default ones)
|
||||
__packages__ = ['base', 'sudo', 'linux-firmware', 'linux', 'linux-lts', 'linux-zen', 'linux-hardened']
|
||||
# https://github.com/archlinux/archinstall/issues/4368
|
||||
# mkinitcpio is listed explicitly so pacstrap installs it deterministically. Otherwise
|
||||
# pacman picks the first initramfs provider from the host's pacman.conf, which on non-Arch
|
||||
# hosts (EndeavourOS prefers dracut, etc.) breaks the installer's mkinitcpio() and
|
||||
# _config_uki() methods that assume mkinitcpio is present in the chroot.
|
||||
__packages__ = ['base', 'sudo', 'linux-firmware', 'mkinitcpio'] + [k.value for k in Kernel]
|
||||
|
||||
# Additional packages that are installed if the user is running the Live ISO with accessibility tools enabled
|
||||
__accessibility_packages__ = ['brltty', 'espeakup', 'alsa-utils']
|
||||
|
|
@ -78,8 +85,8 @@ class Installer:
|
|||
`Installer()` is the wrapper for most basic installation steps.
|
||||
It also wraps :py:func:`~archinstall.Installer.pacstrap` among other things.
|
||||
"""
|
||||
self._base_packages = base_packages or __packages__[:3]
|
||||
self.kernels = kernels or ['linux']
|
||||
self._base_packages = base_packages or __packages__[:4]
|
||||
self.kernels = kernels or [DEFAULT_KERNEL.value]
|
||||
self._disk_config = disk_config
|
||||
|
||||
self._disk_encryption = disk_config.disk_encryption or DiskEncryption(EncryptionType.NO_ENCRYPTION)
|
||||
|
|
@ -182,10 +189,10 @@ class Installer:
|
|||
if not skip_ntp:
|
||||
info(tr('Waiting for time sync (timedatectl show) to complete.'))
|
||||
|
||||
started_wait = time.time()
|
||||
started_wait = time.monotonic()
|
||||
notified = False
|
||||
while True:
|
||||
if not notified and time.time() - started_wait > 5:
|
||||
if not notified and time.monotonic() - started_wait > 5:
|
||||
notified = True
|
||||
warn(tr('Time synchronization not completing, while you wait - check the docs for workarounds: https://archinstall.readthedocs.io/'))
|
||||
|
||||
|
|
@ -395,7 +402,7 @@ class Installer:
|
|||
|
||||
def _mount_luks_partition(self, part_mod: PartitionModification, luks_handler: Luks2) -> None:
|
||||
if not luks_handler.mapper_dev:
|
||||
return None
|
||||
return
|
||||
|
||||
if part_mod.fs_type == FilesystemType.BTRFS and part_mod.btrfs_subvols:
|
||||
# Only mount BTRFS subvolumes that have mountpoints specified
|
||||
|
|
@ -738,6 +745,9 @@ class Installer:
|
|||
|
||||
return self.run_command(cmd, peek_output=peek_output)
|
||||
|
||||
def _chroot_argv(self, *args: str) -> list[str]:
|
||||
return ['arch-chroot', '-S', str(self.target), *args]
|
||||
|
||||
def drop_to_shell(self) -> None:
|
||||
subprocess.check_call(f'arch-chroot {self.target}', shell=True)
|
||||
|
||||
|
|
@ -986,17 +996,7 @@ class Installer:
|
|||
}
|
||||
|
||||
for config_name, mountpoint in snapper.items():
|
||||
command = [
|
||||
'arch-chroot',
|
||||
'-S',
|
||||
str(self.target),
|
||||
'snapper',
|
||||
'--no-dbus',
|
||||
'-c',
|
||||
config_name,
|
||||
'create-config',
|
||||
mountpoint,
|
||||
]
|
||||
command = self._chroot_argv('snapper', '--no-dbus', '-c', config_name, 'create-config', mountpoint)
|
||||
|
||||
try:
|
||||
SysCommand(command, peek_output=True)
|
||||
|
|
@ -1335,13 +1335,7 @@ class Installer:
|
|||
|
||||
boot_dir = Path('/boot')
|
||||
|
||||
command = [
|
||||
'arch-chroot',
|
||||
'-S',
|
||||
str(self.target),
|
||||
'grub-install',
|
||||
'--debug',
|
||||
]
|
||||
command = self._chroot_argv('grub-install', '--debug')
|
||||
|
||||
if SysInfo.has_uefi():
|
||||
if not efi_partition:
|
||||
|
|
@ -1466,6 +1460,14 @@ class Installer:
|
|||
elif not efi_partition.mountpoint:
|
||||
raise ValueError('EFI partition is not mounted')
|
||||
|
||||
# Safety net for programmatic callers that bypass GlobalMenu and
|
||||
# guided.py validation.
|
||||
if failure := validate_bootloader_layout(
|
||||
BootloaderConfiguration(bootloader=Bootloader.Limine, uki=uki_enabled),
|
||||
self._disk_config,
|
||||
):
|
||||
raise DiskError(failure.description)
|
||||
|
||||
info(f'Limine EFI partition: {efi_partition.dev_path}')
|
||||
|
||||
parent_dev_path = get_parent_device_path(efi_partition.safe_dev_path)
|
||||
|
|
@ -1476,15 +1478,11 @@ class Installer:
|
|||
if bootloader_removable:
|
||||
efi_dir_path = efi_dir_path / 'BOOT'
|
||||
efi_dir_path_target = efi_dir_path_target / 'BOOT'
|
||||
|
||||
boot_limine_path = self.target / 'boot' / 'limine'
|
||||
boot_limine_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = boot_limine_path / 'limine.conf'
|
||||
else:
|
||||
efi_dir_path = efi_dir_path / 'arch-limine'
|
||||
efi_dir_path_target = efi_dir_path_target / 'arch-limine'
|
||||
|
||||
config_path = efi_dir_path / 'limine.conf'
|
||||
config_path = efi_dir_path / 'limine.conf'
|
||||
|
||||
efi_dir_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
|
@ -1917,16 +1915,17 @@ class Installer:
|
|||
if not handled_by_plugin:
|
||||
info(f'Creating user {user.username}')
|
||||
|
||||
cmd = 'useradd -m'
|
||||
cmd = self._chroot_argv('useradd', '-m')
|
||||
|
||||
if user.sudo:
|
||||
cmd += ' -G wheel'
|
||||
cmd += ['-G', 'wheel']
|
||||
|
||||
cmd += f' {user.username}'
|
||||
cmd += ['--', user.username]
|
||||
|
||||
try:
|
||||
self.arch_chroot(cmd)
|
||||
except SysCallError as err:
|
||||
run(cmd)
|
||||
except CalledProcessError as err:
|
||||
debug(f'Error creating user {user.username}: {err}')
|
||||
raise SystemError(f'Could not create user inside installation: {err}')
|
||||
|
||||
for plugin in plugins.values():
|
||||
|
|
@ -1937,7 +1936,11 @@ class Installer:
|
|||
self.set_user_password(user)
|
||||
|
||||
for group in user.groups:
|
||||
self.arch_chroot(f'gpasswd -a {user.username} {group}')
|
||||
cmd = self._chroot_argv('gpasswd', '-a', user.username, group)
|
||||
try:
|
||||
run(cmd)
|
||||
except CalledProcessError as err:
|
||||
warn(f'Failed to add {user.username} to group {group}: {err}')
|
||||
|
||||
if user.sudo:
|
||||
self.enable_sudo(user)
|
||||
|
|
@ -1952,7 +1955,7 @@ class Installer:
|
|||
return False
|
||||
|
||||
input_data = f'{user.username}:{enc_password}'.encode()
|
||||
cmd = ['arch-chroot', '-S', str(self.target), 'chpasswd', '--encrypted']
|
||||
cmd = self._chroot_argv('chpasswd', '--encrypted')
|
||||
|
||||
try:
|
||||
run(cmd, input_data=input_data)
|
||||
|
|
@ -1964,7 +1967,7 @@ class Installer:
|
|||
def user_set_shell(self, user: str, shell: str) -> bool:
|
||||
info(f'Setting shell for {user} to {shell}')
|
||||
|
||||
cmd = ['arch-chroot', '-S', str(self.target), 'chsh', '-s', shell, user]
|
||||
cmd = self._chroot_argv('chsh', '-s', shell, user)
|
||||
try:
|
||||
run(cmd)
|
||||
return True
|
||||
|
|
@ -1974,7 +1977,7 @@ class Installer:
|
|||
|
||||
def chown(self, owner: str, path: str, options: list[str] | None = None) -> bool:
|
||||
options = options or []
|
||||
cmd = ['arch-chroot', '-S', str(self.target), 'chown', *options, owner, path]
|
||||
cmd = self._chroot_argv('chown', *options, '--', owner, path)
|
||||
try:
|
||||
run(cmd)
|
||||
return True
|
||||
|
|
@ -1985,12 +1988,10 @@ class Installer:
|
|||
def set_vconsole(self, locale_config: LocaleConfiguration) -> None:
|
||||
# use the already set kb layout
|
||||
kb_vconsole: str = locale_config.kb_layout
|
||||
# this is the default used in ISO other option for hdpi screens TER16x32
|
||||
# can be checked using
|
||||
# zgrep "CONFIG_FONT" /proc/config.gz
|
||||
# https://wiki.archlinux.org/title/Linux_console#Fonts
|
||||
font_vconsole = locale_config.console_font
|
||||
|
||||
font_vconsole = 'default8x16'
|
||||
if font_vconsole.startswith('ter-'):
|
||||
self.pacman.strap(['terminus-font'])
|
||||
|
||||
# Ensure /etc exists
|
||||
vconsole_dir: Path = self.target / 'etc'
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.lib.locale.utils import list_keyboard_languages, list_locales, set_kb_layout
|
||||
from archinstall.lib.locale.utils import list_console_fonts, list_keyboard_languages, list_locales, set_kb_layout
|
||||
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.models.locale import LocaleConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
|
||||
|
|
@ -47,6 +47,13 @@ class LocaleMenu(AbstractSubMenu[LocaleConfiguration]):
|
|||
preview_action=lambda item: item.get_value(),
|
||||
key='sys_enc',
|
||||
),
|
||||
MenuItem(
|
||||
text=tr('Console font'),
|
||||
action=select_console_font,
|
||||
value=self._locale_conf.console_font,
|
||||
preview_action=lambda item: item.get_value(),
|
||||
key='console_font',
|
||||
),
|
||||
]
|
||||
|
||||
@override
|
||||
|
|
@ -140,3 +147,25 @@ async def select_kb_layout(preset: str | None = None) -> str | None:
|
|||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
|
||||
|
||||
async def select_console_font(preset: str | None = None) -> str | None:
|
||||
fonts = list_console_fonts()
|
||||
|
||||
items = [MenuItem(f, value=f) for f in fonts]
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
group.set_focus_by_value(preset)
|
||||
|
||||
result = await Selection[str](
|
||||
header=tr('Console font'),
|
||||
group=group,
|
||||
enable_filter=True,
|
||||
).show()
|
||||
|
||||
match result.type_:
|
||||
case ResultType.Selection:
|
||||
return result.get_value()
|
||||
case ResultType.Skip:
|
||||
return preset
|
||||
case _:
|
||||
raise ValueError('Unhandled return type')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from archinstall.lib.command import SysCommand
|
||||
from archinstall.lib.exceptions import ServiceException, SysCallError
|
||||
from archinstall.lib.output import error
|
||||
|
|
@ -26,6 +29,13 @@ def list_locales() -> list[str]:
|
|||
return locales
|
||||
|
||||
|
||||
@lru_cache
|
||||
def list_console_fonts() -> list[str]:
|
||||
directory = Path('/usr/share/kbd/consolefonts')
|
||||
fonts = {path.name.split('.')[0] for path in directory.glob('*.gz')}
|
||||
return sorted(fonts)
|
||||
|
||||
|
||||
def list_x11_keyboard_languages() -> list[str]:
|
||||
return (
|
||||
SysCommand(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@ from typing import Any, Self, override
|
|||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.output import error
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.types import Chars
|
||||
from archinstall.tui.ui.components import InstanceRunnable
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.components import InstanceRunnable
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
CONFIG_KEY = '__config__'
|
||||
|
||||
|
|
@ -154,7 +153,7 @@ class AbstractSubMenu[ValueT](AbstractMenu[ValueT]):
|
|||
auto_cursor: bool = True,
|
||||
allow_reset: bool = False,
|
||||
):
|
||||
back_text = f'{Chars.Right_arrow} ' + tr('Back')
|
||||
back_text = '← ' + tr('Back')
|
||||
item_group.add_item(MenuItem(text=back_text))
|
||||
|
||||
super().__init__(
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from typing import Any, Literal, override
|
|||
from textual.validation import ValidationResult, Validator
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
|
||||
from archinstall.tui.ui.menu_item import MenuItemGroup
|
||||
from archinstall.tui.ui.result import Result, ResultType
|
||||
from archinstall.tui.components import InputInfo, InputScreen, LoadingScreen, NotifyScreen, OptionListScreen, SelectListScreen, TableSelectionScreen
|
||||
from archinstall.tui.menu_item import MenuItemGroup
|
||||
from archinstall.tui.result import Result, ResultType
|
||||
|
||||
|
||||
class Selection[ValueT]:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from typing import cast
|
|||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.menu.menu_helper import MenuHelper
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ListManager[ValueT]:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
|
||||
|
||||
class MenuHelper[ValueT]:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from pathlib import Path
|
|||
from archinstall.lib.menu.helpers import Confirmation, Input
|
||||
from archinstall.lib.models.users import Password, PasswordStrength
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import InputInfo, InputInfoType, tui
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.components import InputInfo, InputInfoType, tui
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
async def get_password(
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ from archinstall.lib.models.mirrors import (
|
|||
from archinstall.lib.models.packages import Repository
|
||||
from archinstall.lib.output import FormattedOutput
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class CustomMirrorRepositoriesList(ListManager[CustomRepository]):
|
||||
|
|
|
|||
|
|
@ -25,6 +25,13 @@ class Bootloader(Enum):
|
|||
case _:
|
||||
return False
|
||||
|
||||
def is_uefi_only(self) -> bool:
|
||||
match self:
|
||||
case Bootloader.Systemd | Bootloader.Efistub | Bootloader.Refind:
|
||||
return True
|
||||
case _:
|
||||
return False
|
||||
|
||||
def json(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
|
|
|||
|
|
@ -802,6 +802,9 @@ class FilesystemType(StrEnum):
|
|||
def is_crypto(self) -> bool:
|
||||
return self == FilesystemType.CRYPTO_LUKS
|
||||
|
||||
def is_fat(self) -> bool:
|
||||
return self in (FilesystemType.FAT12, FilesystemType.FAT16, FilesystemType.FAT32)
|
||||
|
||||
@property
|
||||
def parted_value(self) -> str:
|
||||
return self.value + '(v1)' if self == FilesystemType.LINUX_SWAP else self.value
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ class LocaleConfiguration:
|
|||
kb_layout: str
|
||||
sys_lang: str
|
||||
sys_enc: str
|
||||
# this is the default used in ISO other option for hdpi screens TER16x32
|
||||
# can be checked using
|
||||
# zgrep "CONFIG_FONT" /proc/config.gz
|
||||
# https://wiki.archlinux.org/title/Linux_console#Font
|
||||
console_font: str = 'default8x16'
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> Self:
|
||||
|
|
@ -23,12 +28,14 @@ class LocaleConfiguration:
|
|||
'kb_layout': self.kb_layout,
|
||||
'sys_lang': self.sys_lang,
|
||||
'sys_enc': self.sys_enc,
|
||||
'console_font': self.console_font,
|
||||
}
|
||||
|
||||
def preview(self) -> str:
|
||||
output = '{}: {}\n'.format(tr('Keyboard layout'), self.kb_layout)
|
||||
output += '{}: {}\n'.format(tr('Locale language'), self.sys_lang)
|
||||
output += '{}: {}'.format(tr('Locale encoding'), self.sys_enc)
|
||||
output += '{}: {}\n'.format(tr('Locale encoding'), self.sys_enc)
|
||||
output += '{}: {}'.format(tr('Console font'), self.console_font)
|
||||
return output
|
||||
|
||||
def _load_config(self, args: dict[str, str]) -> None:
|
||||
|
|
@ -38,6 +45,8 @@ class LocaleConfiguration:
|
|||
self.sys_enc = args['sys_enc']
|
||||
if 'kb_layout' in args:
|
||||
self.kb_layout = args['kb_layout']
|
||||
if 'console_font' in args:
|
||||
self.console_font = args['console_font']
|
||||
|
||||
@classmethod
|
||||
def parse_arg(cls, args: dict[str, Any]) -> Self:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
from enum import StrEnum, auto
|
||||
from typing import Final
|
||||
|
||||
|
||||
class Kernel(StrEnum):
|
||||
LINUX = auto()
|
||||
LINUX_LTS = 'linux-lts'
|
||||
LINUX_ZEN = 'linux-zen'
|
||||
LINUX_HARDENED = 'linux-hardened'
|
||||
|
||||
|
||||
DEFAULT_KERNEL: Final = Kernel.LINUX
|
||||
|
|
@ -36,67 +36,66 @@ class PasswordStrength(Enum):
|
|||
case PasswordStrength.STRONG:
|
||||
return 'green'
|
||||
|
||||
@classmethod
|
||||
def strength(cls, password: str) -> Self:
|
||||
@staticmethod
|
||||
def strength(password: str) -> PasswordStrength:
|
||||
digit = any(character.isdigit() for character in password)
|
||||
upper = any(character.isupper() for character in password)
|
||||
lower = any(character.islower() for character in password)
|
||||
symbol = any(not character.isalnum() for character in password)
|
||||
return cls._check_password_strength(digit, upper, lower, symbol, len(password))
|
||||
return PasswordStrength._check_password_strength(digit, upper, lower, symbol, len(password))
|
||||
|
||||
@classmethod
|
||||
@staticmethod
|
||||
def _check_password_strength(
|
||||
cls,
|
||||
digit: bool,
|
||||
upper: bool,
|
||||
lower: bool,
|
||||
symbol: bool,
|
||||
length: int,
|
||||
) -> Self:
|
||||
) -> PasswordStrength:
|
||||
# suggested evaluation
|
||||
# https://github.com/archlinux/archinstall/issues/1304#issuecomment-1146768163
|
||||
if digit and upper and lower and symbol:
|
||||
match length:
|
||||
case num if 13 <= num:
|
||||
return cls.STRONG
|
||||
return PasswordStrength.STRONG
|
||||
case num if 11 <= num <= 12:
|
||||
return cls.MODERATE
|
||||
return PasswordStrength.MODERATE
|
||||
case num if 7 <= num <= 10:
|
||||
return cls.WEAK
|
||||
return PasswordStrength.WEAK
|
||||
case num if num <= 6:
|
||||
return cls.VERY_WEAK
|
||||
return PasswordStrength.VERY_WEAK
|
||||
elif digit and upper and lower:
|
||||
match length:
|
||||
case num if 14 <= num:
|
||||
return cls.STRONG
|
||||
return PasswordStrength.STRONG
|
||||
case num if 11 <= num <= 13:
|
||||
return cls.MODERATE
|
||||
return PasswordStrength.MODERATE
|
||||
case num if 7 <= num <= 10:
|
||||
return cls.WEAK
|
||||
return PasswordStrength.WEAK
|
||||
case num if num <= 6:
|
||||
return cls.VERY_WEAK
|
||||
return PasswordStrength.VERY_WEAK
|
||||
elif upper and lower:
|
||||
match length:
|
||||
case num if 15 <= num:
|
||||
return cls.STRONG
|
||||
return PasswordStrength.STRONG
|
||||
case num if 12 <= num <= 14:
|
||||
return cls.MODERATE
|
||||
return PasswordStrength.MODERATE
|
||||
case num if 7 <= num <= 11:
|
||||
return cls.WEAK
|
||||
return PasswordStrength.WEAK
|
||||
case num if num <= 6:
|
||||
return cls.VERY_WEAK
|
||||
return PasswordStrength.VERY_WEAK
|
||||
elif lower or upper:
|
||||
match length:
|
||||
case num if 18 <= num:
|
||||
return cls.STRONG
|
||||
return PasswordStrength.STRONG
|
||||
case num if 14 <= num <= 17:
|
||||
return cls.MODERATE
|
||||
return PasswordStrength.MODERATE
|
||||
case num if 9 <= num <= 13:
|
||||
return cls.WEAK
|
||||
return PasswordStrength.WEAK
|
||||
case num if num <= 8:
|
||||
return cls.VERY_WEAK
|
||||
return PasswordStrength.VERY_WEAK
|
||||
|
||||
return cls.VERY_WEAK
|
||||
return PasswordStrength.VERY_WEAK
|
||||
|
||||
|
||||
UserSerialization = TypedDict(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from archinstall.lib.menu.list_manager import ListManager
|
|||
from archinstall.lib.models.network import NetworkConfiguration, Nic, NicType
|
||||
from archinstall.lib.networking import list_interfaces
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ManualNetworkConfig(ListManager[Nic]):
|
||||
|
|
@ -172,14 +172,17 @@ async def select_network(preset: NetworkConfiguration | None) -> NetworkConfigur
|
|||
"""
|
||||
|
||||
items = [MenuItem(n.display_msg(), value=n) for n in NicType]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
|
||||
if preset:
|
||||
group.set_selected_by_value(preset.type)
|
||||
|
||||
header = tr('Choose network configuration') + '\n'
|
||||
header += tr('Recommended: Network Manager for desktop, Manual for server') + '\n'
|
||||
|
||||
result = await Selection[NicType](
|
||||
group,
|
||||
header=tr('Choose network configuration'),
|
||||
header=header,
|
||||
allow_reset=True,
|
||||
allow_skip=True,
|
||||
).show()
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ from archinstall.lib.models.network import WifiConfiguredNetwork, WifiNetwork
|
|||
from archinstall.lib.network.wpa_supplicant import WpaSupplicantConfig
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import Result, ResultType
|
||||
from archinstall.tui.components import ConfirmationScreen, InputScreen, InstanceRunnable, LoadingScreen, NotifyScreen, TableSelectionScreen, tui
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import Result, ResultType
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ class DownloadTimer:
|
|||
self.previous_handler = signal.signal(signal.SIGALRM, self.raise_timeout) # type: ignore[assignment]
|
||||
self.previous_timer = signal.alarm(self.timeout)
|
||||
|
||||
self.start_time = time.time()
|
||||
self.start_time = time.monotonic()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None) -> None:
|
||||
if self.start_time:
|
||||
time_delta = time.time() - self.start_time
|
||||
time_delta = time.monotonic() - self.start_time
|
||||
signal.alarm(0)
|
||||
self.time = time_delta
|
||||
if self.timeout > 0:
|
||||
|
|
@ -165,7 +165,7 @@ def build_icmp(payload: bytes) -> bytes:
|
|||
|
||||
def ping(hostname: str, timeout: int = 5) -> int:
|
||||
watchdog = select.epoll()
|
||||
started = time.time()
|
||||
started = time.monotonic()
|
||||
random_identifier = f'archinstall-{random.randint(1000, 9999)}'.encode()
|
||||
|
||||
# Create a raw socket (requires root, which should be fine on archiso)
|
||||
|
|
@ -180,7 +180,7 @@ def ping(hostname: str, timeout: int = 5) -> int:
|
|||
|
||||
# Gracefully wait for X amount of time
|
||||
# for a ICMP response or exit with no latency
|
||||
while latency == -1 and time.time() - started < timeout:
|
||||
while latency == -1 and time.monotonic() - started < timeout:
|
||||
try:
|
||||
for _fileno, _event in watchdog.poll(0.1):
|
||||
response, _ = icmp_socket.recvfrom(1024)
|
||||
|
|
@ -188,7 +188,7 @@ def ping(hostname: str, timeout: int = 5) -> int:
|
|||
|
||||
# Check if it's an Echo Reply (ICMP type 0)
|
||||
if icmp_type == 0 and response[-len(random_identifier) :] == random_identifier:
|
||||
latency = round((time.time() - started) * 1000)
|
||||
latency = round((time.monotonic() - started) * 1000)
|
||||
break
|
||||
except OSError as e:
|
||||
debug(f'Error: {e}')
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class Journald:
|
|||
try:
|
||||
import systemd.journal # type: ignore[import-not-found]
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
return
|
||||
|
||||
log_adapter = logging.getLogger('archinstall')
|
||||
log_fmt = logging.Formatter('[%(levelname)s]: %(message)s')
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from archinstall.lib.models.packages import AvailablePackage, LocalPackage, Pack
|
|||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.pacman.pacman import Pacman
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
def installed_package(package: str) -> LocalPackage | None:
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ class Pacman:
|
|||
if pacman_db_lock.exists():
|
||||
warn(tr('Pacman is already running, waiting maximum 10 minutes for it to terminate.'))
|
||||
|
||||
started = time.time()
|
||||
started = time.monotonic()
|
||||
while pacman_db_lock.exists():
|
||||
time.sleep(0.25)
|
||||
|
||||
if time.time() - started > (60 * 10):
|
||||
if time.monotonic() - started > (60 * 10):
|
||||
error(tr('Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall.'))
|
||||
sys.exit(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from archinstall.lib.menu.helpers import Confirmation, Input
|
|||
from archinstall.lib.models.pacman import PacmanConfiguration
|
||||
from archinstall.lib.pathnames import PACMAN_CONF
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class PacmanMenu(AbstractSubMenu[PacmanConfiguration]):
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ from archinstall.lib.menu.abstract_menu import AbstractSubMenu
|
|||
from archinstall.lib.menu.helpers import Confirmation, Selection
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
||||
|
|
@ -95,7 +95,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
driver = await select_driver(preset=preset)
|
||||
|
||||
if driver and 'Sway' in profile.current_selection_names():
|
||||
if driver.is_nvidia():
|
||||
if driver.is_nvidia_proprietary():
|
||||
header = tr('The proprietary Nvidia driver is not supported by Sway.') + '\n'
|
||||
header += tr('It is likely that you will run into issues, are you okay with that?') + '\n'
|
||||
|
||||
|
|
@ -105,8 +105,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
preset=False,
|
||||
).show()
|
||||
|
||||
if result.get_value():
|
||||
return preset
|
||||
return driver if result.get_value() else preset
|
||||
|
||||
return driver
|
||||
|
||||
|
|
@ -114,7 +113,7 @@ class ProfileMenu(AbstractSubMenu[ProfileConfiguration]):
|
|||
if item.value:
|
||||
driver = item.get_value().value
|
||||
packages = item.get_value().packages_text()
|
||||
return f'Driver: {driver}\n{packages}'
|
||||
return f'{tr("Graphics driver")}: {driver}\n{packages}'
|
||||
return None
|
||||
|
||||
def _prev_greeter(self, item: MenuItem) -> str | None:
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from archinstall.lib.menu.list_manager import ListManager
|
|||
from archinstall.lib.menu.util import get_password
|
||||
from archinstall.lib.models.users import User
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
from archinstall.tui.menu_item import MenuItem
|
||||
from archinstall.tui.result import ResultType
|
||||
|
||||
|
||||
class UserList(ListManager[User]):
|
||||
|
|
|
|||
|
|
@ -252,6 +252,9 @@ msgstr ""
|
|||
msgid "Locale encoding"
|
||||
msgstr ""
|
||||
|
||||
msgid "Console font"
|
||||
msgstr ""
|
||||
|
||||
msgid "Drive(s)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1245,6 +1248,21 @@ msgstr ""
|
|||
msgid "Invalid configuration: {error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Ready to install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disks"
|
||||
msgstr ""
|
||||
|
||||
msgid "Packages"
|
||||
msgstr ""
|
||||
|
||||
msgid "Network"
|
||||
msgstr ""
|
||||
|
||||
msgid "Locale"
|
||||
msgstr ""
|
||||
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2174,6 +2192,14 @@ msgstr ""
|
|||
msgid "Choose network configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Recommended: Network Manager for desktop, Manual for server"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Warning: no network configuration selected. Network will need to be set up "
|
||||
"manually on the installed system."
|
||||
msgstr ""
|
||||
|
||||
msgid "No packages found"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -2251,3 +2277,19 @@ msgstr ""
|
|||
|
||||
msgid "wide Unicode coverage, good fallback font"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr ""
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -54,7 +54,7 @@ msgid "Only packages such as base, base-devel, linux, linux-firmware, efibootmgr
|
|||
msgstr "Solo paquetes como base, base-devel, linux, linux-firmware, efibootmgr y paquetes opcionales de perfil se instalan."
|
||||
|
||||
msgid "Note: base-devel is no longer installed by default. Add it here if you need build tools."
|
||||
msgstr "Nota: base-devel ya no viene instalado por defecto. Agreguelo aquí si necesita herramientas de build."
|
||||
msgstr "Nota: base-devel ya no viene instalado por defecto. Agreguelo aquí si necesita herramientas de compilación."
|
||||
|
||||
msgid "If you desire a web browser, such as firefox or chromium, you may specify it in the following prompt."
|
||||
msgstr "Si desea un navegador web, como firefox o chromium, puede especificarlo en el siguiente mensaje."
|
||||
|
|
@ -173,7 +173,7 @@ msgid "Enter a desired filesystem type for the partition: "
|
|||
msgstr "Ingrese un tipo de sistema de archivos deseado para la partición: "
|
||||
|
||||
msgid "Archinstall language"
|
||||
msgstr "Idioma de Archinstall"
|
||||
msgstr "Idioma de archinstall"
|
||||
|
||||
msgid "Wipe all selected drives and use a best-effort default partition layout"
|
||||
msgstr "Borrar todas las unidades seleccionadas y usar un diseño de partición predeterminado de mejor esfuerzo"
|
||||
|
|
@ -197,7 +197,7 @@ msgid "Select one or more hard drives to use and configure"
|
|||
msgstr "Seleccione uno o más discos duros para usar y configurar"
|
||||
|
||||
msgid "For the best compatibility with your AMD hardware, you may want to use either the all open-source or AMD / ATI options."
|
||||
msgstr "Para obtener la mejor compatibilidad con su hardware AMD, es posible que desee utilizar las opciones de código abierto o AMD / ATI."
|
||||
msgstr "Para obtener la mejor compatibilidad con su hardware AMD, es posible que desee utilizar las opciones de código abierto o AMD/ATI."
|
||||
|
||||
msgid "For the best compatibility with your Intel hardware, you may want to use either the all open-source or Intel options.\n"
|
||||
msgstr "Para obtener la mejor compatibilidad con su hardware Intel, es posible que desee utilizar las opciones de código abierto o de Intel.\n"
|
||||
|
|
@ -218,7 +218,7 @@ msgid "All open-source (default)"
|
|||
msgstr "Todos de código abierto (predeterminado)"
|
||||
|
||||
msgid "Choose which kernels to use or leave blank for default \"{}\""
|
||||
msgstr "Elija qué kernel usar o déjelo en blanco para usar el kernel \"{}\" predeterminado"
|
||||
msgstr "Elija qué núcleo usar o deje en blanco para usar el núcleo \"{}\" predeterminado"
|
||||
|
||||
msgid "Choose which locale language to use"
|
||||
msgstr "Elija qué idioma local usar"
|
||||
|
|
@ -382,7 +382,7 @@ msgid "Create a required super-user with sudo privileges: "
|
|||
msgstr "Crear un super-usuario requerido con privilegios sudo: "
|
||||
|
||||
msgid "Enter root password (leave blank to disable root): "
|
||||
msgstr "Ingrese la contraseña de root (deje en blanco para deshabilitar root): "
|
||||
msgstr "Ingrese la contraseña de root (deje en blanco para desactivar root): "
|
||||
|
||||
msgid "Password for user \"{}\": "
|
||||
msgstr "Contraseña para el usuario “{}”: "
|
||||
|
|
@ -454,7 +454,7 @@ msgid "Pre-existing pacman lock never exited. Please clean up any existing pacma
|
|||
msgstr "El bloqueo de pacman preexistente nunca se cerró. Limpie cualquier sesión de pacman existente antes de usar archinstall."
|
||||
|
||||
msgid "Choose which optional additional repositories to enable"
|
||||
msgstr "Elija qué repositorios adicionales opcionales habilitar"
|
||||
msgstr "Elija qué repositorios adicionales opcionales activar"
|
||||
|
||||
msgid "Add a user"
|
||||
msgstr "Añadir un usuario"
|
||||
|
|
@ -489,7 +489,7 @@ msgid "No network configuration"
|
|||
msgstr "Sin configuración de red"
|
||||
|
||||
msgid "Set desired subvolumes on a btrfs partition"
|
||||
msgstr "Establecer los subvolúmenes deseados en una partición btrfs"
|
||||
msgstr "Establecer los subvolúmenes deseados en una partición BTRFS"
|
||||
|
||||
msgid ""
|
||||
"{}\n"
|
||||
|
|
@ -501,7 +501,7 @@ msgstr ""
|
|||
"Seleccione en qué partición configurar los subvolúmenes"
|
||||
|
||||
msgid "Manage btrfs subvolumes for current partition"
|
||||
msgstr "Administrar subvolúmenes btrfs para la partición actual"
|
||||
msgstr "Administrar subvolúmenes BTRFS para la partición actual"
|
||||
|
||||
msgid "No configuration"
|
||||
msgstr "Sin configuración"
|
||||
|
|
@ -650,7 +650,7 @@ msgid "Manual configuration"
|
|||
msgstr "Configuración manual"
|
||||
|
||||
msgid "Mark/Unmark a partition as compressed (btrfs only)"
|
||||
msgstr "Marcar/Desmarcar una partición como comprimida (solo btrfs)"
|
||||
msgstr "Marcar/Desmarcar una partición como comprimida (solo BTRFS)"
|
||||
|
||||
msgid "The password you are using seems to be weak, are you sure you want to use it?"
|
||||
msgstr "La contraseña que está utilizando parece ser débil, ¿está seguro de que desea usarla?"
|
||||
|
|
@ -665,7 +665,7 @@ msgid "A very basic installation that allows you to customize Arch Linux as you
|
|||
msgstr "Una instalación muy básica que te permite personalizar Arch Linux como mejor te parezca."
|
||||
|
||||
msgid "Provides a selection of various server packages to install and enable, e.g. httpd, nginx, mariadb"
|
||||
msgstr "Proporciona una selección de varios paquetes de servidor para instalar y habilitar, p.e. httpd, nginx, mariadb"
|
||||
msgstr "Proporciona una selección de varios paquetes de servidor para instalar y activar, p.ej. httpd, nginx, mariadb"
|
||||
|
||||
msgid "Choose which servers to install, if none then a minimal installation will be done"
|
||||
msgstr "Elija qué servidores instalar, si no hay ninguno, se realizará una instalación mínima"
|
||||
|
|
@ -799,45 +799,45 @@ msgid "Configured {} interfaces"
|
|||
msgstr "Interfaces {} configuradas"
|
||||
|
||||
msgid "This option enables the number of parallel downloads that can occur during installation"
|
||||
msgstr "Esta opción habilita la cantidad de descargas paralelas que pueden ocurrir durante la instalación"
|
||||
msgstr "Esta opción activa la cantidad de descargas paralelas que pueden ocurrir durante la instalación"
|
||||
|
||||
msgid ""
|
||||
"Enter the number of parallel downloads to be enabled.\n"
|
||||
" (Enter a value between 1 to {})\n"
|
||||
"Note:"
|
||||
msgstr ""
|
||||
"Ingrese el número de descargas paralelas que se habilitarán.\n"
|
||||
"Ingrese el número de descargas paralelas que se activarán.\n"
|
||||
" (Ingrese un valor entre 1 y {})\n"
|
||||
"Nota:"
|
||||
|
||||
msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {} downloads at a time )"
|
||||
msgstr " - Valor máximo : {} ( Habilita {} descargas paralelas, permite {} descargas simultáneas )"
|
||||
msgstr " - Valor máximo : {} (Permitir {} descargas paralelas, permite {} descargas simultáneas)"
|
||||
|
||||
msgid " - Minimum value : 1 ( Allows 1 parallel download, allows 2 downloads at a time )"
|
||||
msgstr " - Valor mínimo : 1 ( Habilita 1 descarga paralela, permite 2 descargas simultáneas )"
|
||||
msgstr " - Valor mínimo : 1 (Permitir 1 descarga paralela, permite 2 descargas simultáneas)"
|
||||
|
||||
msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )"
|
||||
msgstr " - Deshabilitar/Predeterminado : 0 ( Deshabilita la descarga paralela, permite solo 1 descarga simultánea )"
|
||||
msgstr " - Desactivar/Predeterminado : 0 (Desactiva la descarga paralela, permite solo 1 descarga simultánea)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Invalid input! Try again with a valid input [1 to {max_downloads}, or 0 to disable]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [1 a {max_downloads}, o 0 para deshabilitar]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [1 a {max_downloads}, o 0 para desactivar]"
|
||||
|
||||
msgid "Parallel Downloads"
|
||||
msgstr "Descargas paralelas"
|
||||
|
||||
msgid "Pacman"
|
||||
msgstr ""
|
||||
msgstr "Pacman"
|
||||
|
||||
msgid "Color"
|
||||
msgstr ""
|
||||
msgstr "Color"
|
||||
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Enter the number of parallel downloads (1-{})"
|
||||
msgstr "Ingrese el número de descargas paralelas que desea habilitar"
|
||||
msgstr "Ingrese el número de descargas paralelas (1-{})"
|
||||
|
||||
msgid "Enable colored output for pacman"
|
||||
msgstr ""
|
||||
msgstr "Activa salida a color para pacman"
|
||||
|
||||
msgid "ESC to skip"
|
||||
msgstr "ESC para omitir"
|
||||
|
|
@ -975,7 +975,7 @@ msgid "Higher values increase security but slow down boot time"
|
|||
msgstr "Valores más grandes mejoran la seguridad pero aumentan el tiempo de arranque"
|
||||
|
||||
msgid "Default: 10000ms, Recommended range: 1000-60000"
|
||||
msgstr "Por defecto: 10000ms, Rango recomendado 1000-60000"
|
||||
msgstr "Predeterminado: 10000ms, Rango recomendado 1000-60000"
|
||||
|
||||
msgid "Iteration time cannot be empty"
|
||||
msgstr "Tiempo de iteración no puede estar vacío"
|
||||
|
|
@ -1073,10 +1073,10 @@ msgid "Packages to be install with this profile (space separated, leave blank to
|
|||
msgstr "Paquetes que se instalarán con este perfil (separados por espacios, déjelo en blanco para omitir): "
|
||||
|
||||
msgid "Services to be enabled with this profile (space separated, leave blank to skip): "
|
||||
msgstr "Servicios que se habilitarán con este perfil (separados por espacios, deje en blanco para omitir): "
|
||||
msgstr "Servicios que se activarán con este perfil (separados por espacios, deje en blanco para omitir): "
|
||||
|
||||
msgid "Should this profile be enabled for installation?"
|
||||
msgstr "¿Debería habilitarse este perfil para la instalación?"
|
||||
msgstr "¿Debería activarse este perfil para la instalación?"
|
||||
|
||||
msgid "Create your own"
|
||||
msgstr "Crear tu propio"
|
||||
|
|
@ -1158,7 +1158,7 @@ msgid ""
|
|||
"Enter a directory for the configuration(s) to be saved (tab completion enabled)\n"
|
||||
"Save directory: "
|
||||
msgstr ""
|
||||
"Ingrese un directorio para guardar las configuraciones (completar con tabulación habilitado)\n"
|
||||
"Ingrese un directorio para guardar las configuraciones (completar con tabulación activado)\n"
|
||||
"Guardar directorio: "
|
||||
|
||||
msgid ""
|
||||
|
|
@ -1180,10 +1180,10 @@ msgid "Mirror regions"
|
|||
msgstr "Regiones de espejos"
|
||||
|
||||
msgid " - Maximum value : {} ( Allows {} parallel downloads, allows {max_downloads+1} downloads at a time )"
|
||||
msgstr " - Valor máximo : {} ( Habilita {} descargas paralelas, permite {max_downloads+1} descargas simultáneas )"
|
||||
msgstr " - Valor máximo : {} (Permitir {} descargas paralelas, permite {max_downloads+1} descargas simultáneas)"
|
||||
|
||||
msgid "Invalid input! Try again with a valid input [1 to {}, or 0 to disable]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [1 a {}, o 0 para deshabilitar]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [1 a {}, o 0 para desactivar]"
|
||||
|
||||
msgid "Locales"
|
||||
msgstr "Localidades"
|
||||
|
|
@ -1226,26 +1226,26 @@ msgid "Type"
|
|||
msgstr "Tipo"
|
||||
|
||||
msgid "This option enables the number of parallel downloads that can occur during package downloads"
|
||||
msgstr "Esta opción habilita la cantidad de descargas paralelas que pueden ocurrir durante las descargas de paquetes"
|
||||
msgstr "Esta opción activa la cantidad de descargas paralelas que pueden ocurrir durante las descargas de paquetes"
|
||||
|
||||
msgid ""
|
||||
"Enter the number of parallel downloads to be enabled.\n"
|
||||
"\n"
|
||||
"Note:\n"
|
||||
msgstr ""
|
||||
"Ingrese el número de descargas paralelas que se habilitarán.\n"
|
||||
"Ingrese el número de descargas paralelas que se activarán.\n"
|
||||
"\n"
|
||||
"Nota:\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid " - Maximum recommended value : {} ( Allows {} parallel downloads at a time )"
|
||||
msgstr " - Valor máximo recomendado : {} ( Permite {} descargas paralelas simultáneas )"
|
||||
msgstr " - Valor máximo recomendado : {} (Permite {} descargas paralelas simultáneas)"
|
||||
|
||||
msgid " - Disable/Default : 0 ( Disables parallel downloading, allows only 1 download at a time )\n"
|
||||
msgstr " - Deshabilitar/Predeterminado : 0 ( Deshabilita la descarga paralela, permite solo 1 descarga simultánea )\n"
|
||||
msgstr " - Desactivar/Predeterminado : 0 (Desactiva la descarga paralela, permite solo 1 descarga simultánea)\n"
|
||||
|
||||
msgid "Invalid input! Try again with a valid input [or 0 to disable]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [o 0 para deshabilitar]"
|
||||
msgstr "¡Entrada no válida! Intente nuevamente con una entrada válida [o 0 para desactivar]"
|
||||
|
||||
msgid "Hyprland needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "Hyprland necesita acceso a su asiento (colección de dispositivos de hardware, es decir, teclado, mouse, etc.)"
|
||||
|
|
@ -1263,10 +1263,10 @@ msgid "All entered values can be suffixed with a unit: %, B, KB, KiB, MB, MiB...
|
|||
msgstr "Todos los valores introducidos pueden tener como sufijo una unidad: %, B, KB, KiB, MB, MiB..."
|
||||
|
||||
msgid "Would you like to use unified kernel images?"
|
||||
msgstr "¿Le gustaría utilizar imágenes del kernel unificadas?"
|
||||
msgstr "¿Le gustaría utilizar imágenes del núcleo unificadas?"
|
||||
|
||||
msgid "Unified kernel images"
|
||||
msgstr "Imágenes del kernel unificadas"
|
||||
msgstr "Imágenes del núcleo unificadas"
|
||||
|
||||
msgid "Waiting for time sync (timedatectl show) to complete."
|
||||
msgstr "Esperando a que se complete la sincronización de la hora (timedatectl show)."
|
||||
|
|
@ -1449,7 +1449,7 @@ msgid "Enter your gateway (router) IP address (leave blank for none)"
|
|||
msgstr "Ingrese la dirección IP de su puerta de enlace (enrutador) (deje en blanco si no hay ninguna)"
|
||||
|
||||
msgid "Gateway address"
|
||||
msgstr "Dirección de puerta de enlace"
|
||||
msgstr "Dirección de la puerta de enlace"
|
||||
|
||||
msgid "Enter your DNS servers with space separated (leave blank for none)"
|
||||
msgstr "Ingrese sus servidores DNS separados por espacios (deje en blanco si no hay ninguno)"
|
||||
|
|
@ -1464,7 +1464,7 @@ msgid "Kernel"
|
|||
msgstr "Núcleo"
|
||||
|
||||
msgid "UEFI is not detected and some options are disabled"
|
||||
msgstr "No se detecta UEFI y algunas opciones están deshabilitadas"
|
||||
msgstr "No se detecta UEFI y algunas opciones están desactivadas"
|
||||
|
||||
msgid "Info"
|
||||
msgstr "Info"
|
||||
|
|
@ -1494,17 +1494,17 @@ msgid "Directory"
|
|||
msgstr "Directorio"
|
||||
|
||||
msgid "Enter a directory for the configuration(s) to be saved (tab completion enabled)"
|
||||
msgstr "Ingrese un directorio para guardar las configuraciones (autocompletado de tabulación habilitado)"
|
||||
msgstr "Ingrese un directorio para guardar las configuraciones (autocompletado de tabulación activado)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Do you want to save the configuration file(s) to {}?"
|
||||
msgstr "¿Desea guardar los archivos de configuración en {}?"
|
||||
|
||||
msgid "Enabled"
|
||||
msgstr "Habilitado"
|
||||
msgstr "Activado"
|
||||
|
||||
msgid "Disabled"
|
||||
msgstr "Deshabilitado"
|
||||
msgstr "Desactivado"
|
||||
|
||||
msgid "Please submit this issue (and file) to https://github.com/archlinux/archinstall/issues"
|
||||
msgstr "Envíe este problema (y archivo) a https://github.com/archlinux/archinstall/issues"
|
||||
|
|
@ -1681,137 +1681,121 @@ msgid "Exit search mode"
|
|||
msgstr "Salir del modo de búsqueda"
|
||||
|
||||
msgid "General"
|
||||
msgstr ""
|
||||
msgstr "General"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Navigation"
|
||||
msgstr "Guardar configuración"
|
||||
msgstr "Navegación"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Selection"
|
||||
msgstr "Seleccione regiones"
|
||||
msgstr "Selección"
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
msgstr "Buscar"
|
||||
|
||||
msgid "Down"
|
||||
msgstr ""
|
||||
msgstr "Abajo"
|
||||
|
||||
msgid "Up"
|
||||
msgstr ""
|
||||
msgstr "Arriba"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmar y salir"
|
||||
msgstr "Confirmar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus right"
|
||||
msgstr "Mover a la derecha"
|
||||
msgstr "Enfocar a la derecha"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Focus left"
|
||||
msgstr "Mover a la izquierda"
|
||||
msgstr "Enfocar a la izquierda"
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
msgstr "Alternar"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Show/Hide help"
|
||||
msgstr "Mostrar ayuda"
|
||||
msgstr "Mostrar/Ocultar ayuda"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
msgstr "Salir"
|
||||
|
||||
msgid "First"
|
||||
msgstr ""
|
||||
msgstr "Primero"
|
||||
|
||||
msgid "Last"
|
||||
msgstr ""
|
||||
msgstr "Último"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select"
|
||||
msgstr "Seleccione regiones"
|
||||
msgstr "Seleccionar"
|
||||
|
||||
msgid "Page Up"
|
||||
msgstr ""
|
||||
msgstr "Subir página"
|
||||
|
||||
msgid "Page Down"
|
||||
msgstr ""
|
||||
msgstr "Bajar página"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Page up"
|
||||
msgstr "Grupo de paquetes:"
|
||||
msgstr "Subir página"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Page down"
|
||||
msgstr "Bajar"
|
||||
msgstr "Bajar página"
|
||||
|
||||
msgid "Page Left"
|
||||
msgstr ""
|
||||
msgstr "Página izquierda"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Page Right"
|
||||
msgstr "Mover a la derecha"
|
||||
msgstr "Página derecha"
|
||||
|
||||
msgid "Cursor up"
|
||||
msgstr ""
|
||||
msgstr "Subir"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Cursor down"
|
||||
msgstr "Bajar"
|
||||
msgstr "Cursor hacia abajo"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Cursor right"
|
||||
msgstr "Mover a la derecha"
|
||||
msgstr "Cursor hacia la derecha"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Cursor left"
|
||||
msgstr "Mover a la izquierda"
|
||||
msgstr "Cursor hacia la izquierda"
|
||||
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
msgstr "Arriba"
|
||||
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
msgstr "Abajo"
|
||||
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
msgstr "Inicio"
|
||||
|
||||
#, fuzzy
|
||||
msgid "End"
|
||||
msgstr "Habilitado"
|
||||
msgstr "Final"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Toggle option"
|
||||
msgstr "Opciones del subvolumen"
|
||||
msgstr "Opción de alternancia"
|
||||
|
||||
msgid "Scroll Up"
|
||||
msgstr ""
|
||||
msgstr "Mover Arriba"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Scroll Down"
|
||||
msgstr "Bajar en vista previa"
|
||||
msgstr "Deslizar hacia abajo"
|
||||
|
||||
msgid "Scroll Left"
|
||||
msgstr ""
|
||||
msgstr "Mover hacia la izquierda"
|
||||
|
||||
msgid "Scroll Right"
|
||||
msgstr ""
|
||||
msgstr "Mover hacia la derecha"
|
||||
|
||||
msgid "Scroll Home"
|
||||
msgstr ""
|
||||
msgstr "Ir al Inicio"
|
||||
|
||||
msgid "Scroll End"
|
||||
msgstr ""
|
||||
msgstr "Ir al Final"
|
||||
|
||||
msgid "Focus Next"
|
||||
msgstr ""
|
||||
msgstr "Enfocar a la siguiente"
|
||||
|
||||
msgid "Focus Previous"
|
||||
msgstr ""
|
||||
msgstr "Enfocar a la anterior"
|
||||
|
||||
msgid "Copy selected text"
|
||||
msgstr ""
|
||||
msgstr "Copiar texto seleccionado"
|
||||
|
||||
msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "labwc necesita acceso a su asiento (colección de dispositivos de hardware, es decir, teclado, ratón, etc.)"
|
||||
|
|
@ -1838,7 +1822,7 @@ msgid "Reboot system"
|
|||
msgstr "Reiniciar el sistema"
|
||||
|
||||
msgid "chroot into installation for post-installation configurations"
|
||||
msgstr "chroot en la instalación para configuraciones posteriores a la instalación"
|
||||
msgstr "Hacer chroot en la instalación para configuraciones posteriores a la instalación"
|
||||
|
||||
msgid "Installation completed"
|
||||
msgstr "Instalación completada"
|
||||
|
|
@ -1907,7 +1891,7 @@ msgstr "Sudo sin contraseña: "
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Btrfs snapshot type: {}"
|
||||
msgstr "Tipo de snapshot Btrfs: {}"
|
||||
msgstr "Tipo de instantánea BTRFS: {}"
|
||||
|
||||
msgid "Syncing the system..."
|
||||
msgstr "Sincronizando el sistema..."
|
||||
|
|
@ -1932,7 +1916,7 @@ msgid "U2F Login Method"
|
|||
msgstr "Método de inicio de sesión U2F"
|
||||
|
||||
msgid "Enable passwordless sudo?"
|
||||
msgstr "Habilitar sudo sin contraseña?"
|
||||
msgstr "¿Activar sudo sin contraseña?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F device for user: {}"
|
||||
|
|
@ -1948,25 +1932,25 @@ msgid "No network connection found"
|
|||
msgstr "No se encontró una conección de red"
|
||||
|
||||
msgid "Would you like to connect to a Wifi?"
|
||||
msgstr "¿Desea conectarse a Wifi?"
|
||||
msgstr "¿Desea conectarse al Wi-Fi?"
|
||||
|
||||
msgid "No wifi interface found"
|
||||
msgstr "No se encontró ninguna interfaz de wifi"
|
||||
msgstr "No se encontró ninguna interfaz Wi-Fi"
|
||||
|
||||
msgid "Select wifi network to connect to"
|
||||
msgstr "Seleccione una red de wifi para conectarse"
|
||||
msgstr "Seleccione una red Wi-Fi para conectarse"
|
||||
|
||||
msgid "Scanning wifi networks..."
|
||||
msgstr "Buscando redes de wifi..."
|
||||
msgstr "Buscando redes Wi-Fi..."
|
||||
|
||||
msgid "No wifi networks found"
|
||||
msgstr "No se encontró redes de wifi"
|
||||
msgstr "No se encontró redes Wi-Fi"
|
||||
|
||||
msgid "Failed setting up wifi"
|
||||
msgstr "Error configurando wifi"
|
||||
msgstr "Error al configurar el Wi-Fi"
|
||||
|
||||
msgid "Enter wifi password"
|
||||
msgstr "Ingrese contraseña de wifi"
|
||||
msgstr "Ingrese la contraseña del Wi-Fi"
|
||||
|
||||
msgid "Ok"
|
||||
msgstr "Ok"
|
||||
|
|
@ -2017,7 +2001,7 @@ msgid "Compression algorithm"
|
|||
msgstr "Algoritmo de compresión"
|
||||
|
||||
msgid "Only packages such as base, sudo, linux, linux-firmware, efibootmgr and optional profile packages are installed."
|
||||
msgstr "Solo paquetes como base, base-devel, linux, linux-firmware, efibootmgr y paquetes opcionales de perfil se instalan."
|
||||
msgstr "Solo paquetes como base, sudo, linux, linux-firmware, efibootmgr y paquetes opcionales de perfil se instalan."
|
||||
|
||||
msgid "Select zram compression algorithm:"
|
||||
msgstr "Seleccione algorimto de compresión zram:"
|
||||
|
|
@ -2031,22 +2015,20 @@ msgstr "Usar gestor de Red (backend iwd)"
|
|||
msgid "Firewall"
|
||||
msgstr "Cortafuegos"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Additional fonts"
|
||||
msgstr "Repositorios adicionales"
|
||||
msgstr "Fuentes adicionales"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select font packages to install"
|
||||
msgstr "Seleccione el gestor de arranque que desea instalar"
|
||||
msgstr "Seleccione los paquetes de fuentes que desea instalar"
|
||||
|
||||
msgid "Unicode font coverage for most languages"
|
||||
msgstr ""
|
||||
msgstr "Fuente unicode cubierta para la mayoría de idiomas"
|
||||
|
||||
msgid "color emoji for browsers and apps"
|
||||
msgstr ""
|
||||
msgstr "Emoji de colores para navegadores y aplicaciones"
|
||||
|
||||
msgid "Chinese, Japanese, Korean characters"
|
||||
msgstr ""
|
||||
msgstr "Carácteres Chino, Japonés, Koreano"
|
||||
|
||||
msgid "Select audio configuration"
|
||||
msgstr "Seleccionar configuración de audio"
|
||||
|
|
@ -2107,7 +2089,7 @@ msgid "Select timezone"
|
|||
msgstr "Seleccione una zona horaria"
|
||||
|
||||
msgid "Enter the number of parallel downloads to be enabled"
|
||||
msgstr "Ingrese el número de descargas paralelas que desea habilitar"
|
||||
msgstr "Ingrese el número de descargas paralelas que desea activar"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Value must be between 1 and {}"
|
||||
|
|
@ -2126,10 +2108,10 @@ msgid "Enter server url"
|
|||
msgstr "Ingrese la URL del servidor"
|
||||
|
||||
msgid "Select mirror regions to be enabled"
|
||||
msgstr "Seleccione las regiones espejo que se habilitarán"
|
||||
msgstr "Seleccione las regiones espejo que se activarán"
|
||||
|
||||
msgid "Select optional repositories to be enabled"
|
||||
msgstr "Seleccione repositorios opcionales para habilitar"
|
||||
msgstr "Seleccione repositorios opcionales para activar"
|
||||
|
||||
msgid "Select an interface"
|
||||
msgstr "Seleccione una interfaz"
|
||||
|
|
@ -2170,54 +2152,56 @@ msgstr "Seguridad de la contraseña: Fuerte"
|
|||
msgid "The selected desktop profile requires a regular user to log in via the greeter"
|
||||
msgstr "El perfil de escritorio seleccionado requiere que un usuario estándar inicie sesión a través de la pantalla de bienvenida"
|
||||
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {} {}"
|
||||
msgstr "Tipo de entorno: {}"
|
||||
msgstr "Tipo de entorno: {} {}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "El valor no puede estár vacío"
|
||||
msgstr "La entrada no debe estar vacía"
|
||||
|
||||
msgid "Recommended"
|
||||
msgstr ""
|
||||
msgstr "Recomendado"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Package"
|
||||
msgstr "Grupo de paquetes:"
|
||||
msgstr "Paquete"
|
||||
|
||||
msgid "Curated selection of KDE Plasma packages"
|
||||
msgstr ""
|
||||
msgstr "Selección curada de paquetes KDE Plasma"
|
||||
|
||||
msgid "Dependencies"
|
||||
msgstr ""
|
||||
msgstr "Dependencias"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Package group"
|
||||
msgstr "Grupo de paquetes:"
|
||||
msgstr "Grupo de paquetes"
|
||||
|
||||
msgid "Extensive KDE Plasma installation"
|
||||
msgstr ""
|
||||
msgstr "Instalación KDE Plasma Extensible"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Packages in group"
|
||||
msgstr "Grupo de paquetes:"
|
||||
msgstr "Paquetes en grupo"
|
||||
|
||||
msgid "Minimal KDE Plasma installation"
|
||||
msgstr ""
|
||||
msgstr "Instalación minima de KDE Plasma"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Description"
|
||||
msgstr "Cifrado de disco"
|
||||
msgstr "Descripción"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select a flavor of KDE Plasma to install"
|
||||
msgstr "Seleccione el gestor de arranque que desea instalar"
|
||||
msgstr "Seleccione una variante de KDE Plasma para instalar"
|
||||
|
||||
msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games"
|
||||
msgstr ""
|
||||
msgstr "Sustitución de Arial/Times/Courier, compatibilidad con caracteres cirílicos para Steam/juegos"
|
||||
|
||||
msgid "wide Unicode coverage, good fallback font"
|
||||
msgstr ""
|
||||
msgstr "Amplia cobertura Unicode, buena fuente de reserva"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}"
|
||||
msgstr "Configurando Inicio U2F: {u2f_config.u2f_login_method.value}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000"
|
||||
msgstr "Predeterminado: {DEFAULT_ITER_TIME}ms, Rango recomendado: 1000-60000"
|
||||
|
||||
#~ msgid "Add :"
|
||||
#~ msgstr "Añadir :"
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -63,10 +63,10 @@ msgid "Write additional packages to install (space separated, leave blank to ski
|
|||
msgstr "追加でインストールするパッケージを書く(スペースで区切る。無記入でスキップ): "
|
||||
|
||||
msgid "Copy ISO network configuration to installation"
|
||||
msgstr "ISO ネットワーク設定をインストール環境にコピー"
|
||||
msgstr "ISO のネットワーク設定をインストール環境にコピー"
|
||||
|
||||
msgid "Use NetworkManager (necessary for configuring internet graphically in GNOME and KDE)"
|
||||
msgstr "NetworkManager を使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)"
|
||||
msgstr "ネットワークマネージャーを使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)"
|
||||
|
||||
msgid "Select one network interface to configure"
|
||||
msgstr "設定するネットワークインターフェイスを 1 つ選択"
|
||||
|
|
@ -224,7 +224,7 @@ msgid "Choose which locale language to use"
|
|||
msgstr "使用するロケール言語を選択"
|
||||
|
||||
msgid "Choose which locale encoding to use"
|
||||
msgstr "使用するロケール エンコーディングを選択"
|
||||
msgstr "使用するロケールエンコーディングを選択"
|
||||
|
||||
msgid "Select one of the values shown below: "
|
||||
msgstr "以下の値のいずれかを選択: "
|
||||
|
|
@ -256,6 +256,9 @@ msgstr "ロケール言語"
|
|||
msgid "Locale encoding"
|
||||
msgstr "ロケールエンコーディング"
|
||||
|
||||
msgid "Console font"
|
||||
msgstr "コンソールフォント"
|
||||
|
||||
msgid "Drive(s)"
|
||||
msgstr "ドライブ"
|
||||
|
||||
|
|
@ -449,7 +452,7 @@ msgid "Pacman is already running, waiting maximum 10 minutes for it to terminate
|
|||
msgstr "Pacman はすでに実行されており、終了するまで最大 10 分間待機します。"
|
||||
|
||||
msgid "Pre-existing pacman lock never exited. Please clean up any existing pacman sessions before using archinstall."
|
||||
msgstr "既存の pacman のロックが終了しませんでした。Archinstall を使用する前に、既存の pacman セッションをクリーンアップしてください。"
|
||||
msgstr "既存の pacman のロックが終了しませんでした。archinstall を使用する前に、既存の pacman セッションをすべてクリーンアップしてください。"
|
||||
|
||||
msgid "Choose which optional additional repositories to enable"
|
||||
msgstr "オプションで有効にする追加リポジトリを選択"
|
||||
|
|
@ -823,6 +826,19 @@ msgstr "無効な入力です!有効な入力を使用して再試行してく
|
|||
msgid "Parallel Downloads"
|
||||
msgstr "並行ダウンロード"
|
||||
|
||||
msgid "Pacman"
|
||||
msgstr "Pacman"
|
||||
|
||||
msgid "Color"
|
||||
msgstr "カラー"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Enter the number of parallel downloads (1-{})"
|
||||
msgstr "並列ダウンロード数を入力 (1-{})"
|
||||
|
||||
msgid "Enable colored output for pacman"
|
||||
msgstr "pacman でカラー出力を有効にする"
|
||||
|
||||
msgid "ESC to skip"
|
||||
msgstr "Esc でスキップ"
|
||||
|
||||
|
|
@ -950,7 +966,7 @@ msgid "Encryption type"
|
|||
msgstr "暗号化のタイプ"
|
||||
|
||||
msgid "Iteration time"
|
||||
msgstr "反復時間"
|
||||
msgstr "イテレーション時間"
|
||||
|
||||
msgid "Enter iteration time for LUKS encryption (in milliseconds)"
|
||||
msgstr "LUKS 暗号化のイテレーション時間(ミリ秒単位)を入力"
|
||||
|
|
@ -1030,7 +1046,7 @@ msgstr "選択したプロファイルにインストールするグリーター
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {}"
|
||||
msgstr "環境のタイプ: {}"
|
||||
msgstr "環境タイプ: {}"
|
||||
|
||||
msgid "The proprietary Nvidia driver is not supported by Sway. It is likely that you will run into issues, are you okay with that?"
|
||||
msgstr "プロプライエタリの Nvidia ドライバーは Sway ではサポートされていません。問題が発生する可能性がありますが、よろしいですか?"
|
||||
|
|
@ -1173,7 +1189,7 @@ msgid "Locales"
|
|||
msgstr "ロケール"
|
||||
|
||||
msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE)"
|
||||
msgstr "NetworkManager を使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)"
|
||||
msgstr "ネットワークマネージャーを使用(GNOME と KDE でインターネットをグラフィカルに設定するのに必要)"
|
||||
|
||||
msgid "Total: {} / {}"
|
||||
msgstr "全体: {} / {}"
|
||||
|
|
@ -1206,6 +1222,21 @@ msgstr "製品"
|
|||
msgid "Invalid configuration: {error}"
|
||||
msgstr "無効な設定: {error}"
|
||||
|
||||
msgid "Ready to install"
|
||||
msgstr "インストール準備完了"
|
||||
|
||||
msgid "Disks"
|
||||
msgstr "ディスク"
|
||||
|
||||
msgid "Packages"
|
||||
msgstr "パッケージ"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "ネットワーク"
|
||||
|
||||
msgid "Locale"
|
||||
msgstr "ロケール"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "タイプ"
|
||||
|
||||
|
|
@ -1296,7 +1327,7 @@ msgid "LVM disk encryption with more than 2 partitions is currently not supporte
|
|||
msgstr "パーティションが2個を超える場合の LVM ディスク暗号化は、現在サポートしていません"
|
||||
|
||||
msgid "Use NetworkManager (necessary to configure internet graphically in GNOME and KDE Plasma)"
|
||||
msgstr "NetworkManager を使用する(GNOME と KDE Plasma でインターネットをグラフィカルに設定するのに必要)"
|
||||
msgstr "ネットワークマネージャーを使用(GNOME と KDE Plasma でインターネットをグラフィカルに設定するのに必要)"
|
||||
|
||||
msgid "Select a LVM option"
|
||||
msgstr "LVM のオプションを選択"
|
||||
|
|
@ -1550,7 +1581,7 @@ msgid "The specified configuration will be applied"
|
|||
msgstr "指定された設定が適用されます"
|
||||
|
||||
msgid "Wipe"
|
||||
msgstr "ワイプ"
|
||||
msgstr "消去"
|
||||
|
||||
msgid "Mark/Unmark as XBOOTLDR"
|
||||
msgstr "XBOOTLDR としてマーク/マーク解除"
|
||||
|
|
@ -1663,6 +1694,123 @@ msgstr "検索モードを開始"
|
|||
msgid "Exit search mode"
|
||||
msgstr "検索モードを終了"
|
||||
|
||||
msgid "General"
|
||||
msgstr "一般"
|
||||
|
||||
msgid "Navigation"
|
||||
msgstr "ナビゲーション"
|
||||
|
||||
msgid "Selection"
|
||||
msgstr "選択"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "検索"
|
||||
|
||||
msgid "Down"
|
||||
msgstr "下へ"
|
||||
|
||||
msgid "Up"
|
||||
msgstr "上へ"
|
||||
|
||||
msgid "Confirm"
|
||||
msgstr "確認"
|
||||
|
||||
msgid "Focus right"
|
||||
msgstr "右にフォーカス"
|
||||
|
||||
msgid "Focus left"
|
||||
msgstr "左にフォーカス"
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr "切り替え"
|
||||
|
||||
msgid "Show/Hide help"
|
||||
msgstr "ヘルプを表示/隠す"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "終了"
|
||||
|
||||
msgid "First"
|
||||
msgstr "最初"
|
||||
|
||||
msgid "Last"
|
||||
msgstr "最後"
|
||||
|
||||
msgid "Select"
|
||||
msgstr "選択"
|
||||
|
||||
msgid "Page Up"
|
||||
msgstr "上のページへ"
|
||||
|
||||
msgid "Page Down"
|
||||
msgstr "下のページへ"
|
||||
|
||||
msgid "Page up"
|
||||
msgstr "上のページへ"
|
||||
|
||||
msgid "Page down"
|
||||
msgstr "下のページへ"
|
||||
|
||||
msgid "Page Left"
|
||||
msgstr "左のページへ"
|
||||
|
||||
msgid "Page Right"
|
||||
msgstr "右のページへ"
|
||||
|
||||
msgid "Cursor up"
|
||||
msgstr "カーソルを上へ"
|
||||
|
||||
msgid "Cursor down"
|
||||
msgstr "カースルを下へ"
|
||||
|
||||
msgid "Cursor right"
|
||||
msgstr "カーソルを右へ"
|
||||
|
||||
msgid "Cursor left"
|
||||
msgstr "カーソルを左へ"
|
||||
|
||||
msgid "Top"
|
||||
msgstr "一番上へ"
|
||||
|
||||
msgid "Bottom"
|
||||
msgstr "一番下へ"
|
||||
|
||||
msgid "Home"
|
||||
msgstr "ホーム"
|
||||
|
||||
msgid "End"
|
||||
msgstr "最後へ"
|
||||
|
||||
msgid "Toggle option"
|
||||
msgstr "オプションを切り替え"
|
||||
|
||||
msgid "Scroll Up"
|
||||
msgstr "上にスクロール"
|
||||
|
||||
msgid "Scroll Down"
|
||||
msgstr "下にスクロール"
|
||||
|
||||
msgid "Scroll Left"
|
||||
msgstr "左にスクロール"
|
||||
|
||||
msgid "Scroll Right"
|
||||
msgstr "右にスクロール"
|
||||
|
||||
msgid "Scroll Home"
|
||||
msgstr "ホームにスクロール"
|
||||
|
||||
msgid "Scroll End"
|
||||
msgstr "一番下にスクロール"
|
||||
|
||||
msgid "Focus Next"
|
||||
msgstr "次にフォーカス"
|
||||
|
||||
msgid "Focus Previous"
|
||||
msgstr "前にフォーカス"
|
||||
|
||||
msgid "Copy selected text"
|
||||
msgstr "選択したテキストをコピー"
|
||||
|
||||
msgid "labwc needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)"
|
||||
msgstr "labwc はお使いの Seat(キーボード、マウスなどのハードウェアデバイス群)にアクセスする必要があります"
|
||||
|
||||
|
|
@ -1873,14 +2021,29 @@ msgid "Select zram compression algorithm:"
|
|||
msgstr "zram 圧縮アルゴリズムを選択:"
|
||||
|
||||
msgid "Use Network Manager (default backend)"
|
||||
msgstr "NetworkManager(デフォルトのバックエンド)を使用する"
|
||||
msgstr "Network Manager を使用(デフォルトのバックエンド)"
|
||||
|
||||
msgid "Use Network Manager (iwd backend)"
|
||||
msgstr "NetworkManager (iwd バックエンド) を使用する"
|
||||
msgstr "Network Manager を使用(iwd バックエンド)"
|
||||
|
||||
msgid "Firewall"
|
||||
msgstr "ファイアウォール"
|
||||
|
||||
msgid "Additional fonts"
|
||||
msgstr "追加フォント"
|
||||
|
||||
msgid "Select font packages to install"
|
||||
msgstr "インストールするフォントパッケージを選択"
|
||||
|
||||
msgid "Unicode font coverage for most languages"
|
||||
msgstr "ほとんどの言語をカバーする Unicode フォント"
|
||||
|
||||
msgid "color emoji for browsers and apps"
|
||||
msgstr "ブラウザやアプリ用のカラー絵文字"
|
||||
|
||||
msgid "Chinese, Japanese, Korean characters"
|
||||
msgstr "日本語、中国語、韓国語の文字"
|
||||
|
||||
msgid "Select audio configuration"
|
||||
msgstr "オーディオ設定を選択"
|
||||
|
||||
|
|
@ -1888,7 +2051,7 @@ msgid "Enter credentials file decryption password"
|
|||
msgstr "認証情報ファイルの復号パスワードを入力"
|
||||
|
||||
msgid "Enter root password"
|
||||
msgstr "ルートパスワードを入力"
|
||||
msgstr "root パスワードを入力"
|
||||
|
||||
msgid "Select bootloader to install"
|
||||
msgstr "インストールするブートローダーを選択"
|
||||
|
|
@ -1970,6 +2133,12 @@ msgstr "インターフェースを選択"
|
|||
msgid "Choose network configuration"
|
||||
msgstr "ネットワーク設定を選択"
|
||||
|
||||
msgid "Recommended: Network Manager for desktop, Manual for server"
|
||||
msgstr "推奨: デスクトップのネットワークマネージャー、サーバーのマニュアル"
|
||||
|
||||
msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system."
|
||||
msgstr "警告: ネットワーク設定が選択されていません。インストール後のシステムで、ネットワークを手動で設定する必要があります。"
|
||||
|
||||
msgid "No packages found"
|
||||
msgstr "パッケージが見つかりません"
|
||||
|
||||
|
|
@ -1990,3 +2159,74 @@ msgstr "パスワードを入力"
|
|||
|
||||
msgid "The password did not match, please try again"
|
||||
msgstr "パスワードが一致しません。もう一度試してください"
|
||||
|
||||
msgid "Password strength: Weak"
|
||||
msgstr "パスワードの強度: 弱い"
|
||||
|
||||
msgid "Password strength: Moderate"
|
||||
msgstr "パスワードの強度: 中程度"
|
||||
|
||||
msgid "Password strength: Strong"
|
||||
msgstr "パスワードの強度: 強い"
|
||||
|
||||
msgid "The selected desktop profile requires a regular user to log in via the greeter"
|
||||
msgstr "選択されたデスクトッププロファイルでは、一般ユーザーがグリーター経由でログインする必要があります"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {} {}"
|
||||
msgstr "環境タイプ: {} {}"
|
||||
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "入力は空にできません"
|
||||
|
||||
msgid "Recommended"
|
||||
msgstr "推奨"
|
||||
|
||||
msgid "Package"
|
||||
msgstr "パッケージ"
|
||||
|
||||
msgid "Curated selection of KDE Plasma packages"
|
||||
msgstr "厳選した KDE Plasma パッケージ"
|
||||
|
||||
msgid "Dependencies"
|
||||
msgstr "依存関係"
|
||||
|
||||
msgid "Package group"
|
||||
msgstr "パッケージグループ"
|
||||
|
||||
msgid "Extensive KDE Plasma installation"
|
||||
msgstr "広範囲の KDE Plasma インストール"
|
||||
|
||||
msgid "Packages in group"
|
||||
msgstr "グループ内のパッケージ"
|
||||
|
||||
msgid "Minimal KDE Plasma installation"
|
||||
msgstr "最小限の KDE Plasma インストール"
|
||||
|
||||
msgid "Description"
|
||||
msgstr "説明"
|
||||
|
||||
msgid "Select a flavor of KDE Plasma to install"
|
||||
msgstr "インストールする KDE Plasma のバージョンを選択"
|
||||
|
||||
msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games"
|
||||
msgstr "Arial/Times/Courier フォントの代替、Steam/ゲームにおけるキリル文字のサポート"
|
||||
|
||||
msgid "wide Unicode coverage, good fallback font"
|
||||
msgstr "Unicode を広くカバーする良い代替フォント"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}"
|
||||
msgstr "U2F ログインの設定: {u2f_config.u2f_login_method.value}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000"
|
||||
msgstr "デフォルト: {DEFAULT_ITER_TIME}ms, 推奨範囲: 1000-60000"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {}"
|
||||
msgstr "U2F ログインの設定: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr "デフォルト: {}ms, 推奨範囲: 1000-60000"
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
{"abbr": "fo", "lang": "Faroese"},
|
||||
{"abbr": "fa", "lang": "Persian"},
|
||||
{"abbr": "fj", "lang": "Fijian"},
|
||||
{"abbr": "fi", "lang": "Finnish"},
|
||||
{"abbr": "fi", "lang": "Finnish", "translated_lang": "Suomi"},
|
||||
{"abbr": "fr", "lang": "French", "translated_lang": "Français"},
|
||||
{"abbr": "fy", "lang": "Western Frisian"},
|
||||
{"abbr": "ff", "lang": "Fulah"},
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -256,6 +256,9 @@ msgstr "Мова локалізації"
|
|||
msgid "Locale encoding"
|
||||
msgstr "Кодування локалізації"
|
||||
|
||||
msgid "Console font"
|
||||
msgstr "Шрифт консолі"
|
||||
|
||||
msgid "Drive(s)"
|
||||
msgstr "Диск(и)"
|
||||
|
||||
|
|
@ -1039,7 +1042,7 @@ msgid "Back"
|
|||
msgstr "Назад"
|
||||
|
||||
msgid "Please chose which greeter to install for the chosen profiles: {}"
|
||||
msgstr "Будь ласка, виберіть, який екран привітання встановити для обраних профілів: {}"
|
||||
msgstr "Будь ласка, виберіть, який менеджер входу встановити для обраних профілів: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {}"
|
||||
|
|
@ -1101,10 +1104,10 @@ msgid "Graphics driver"
|
|||
msgstr "Графічні драйвери"
|
||||
|
||||
msgid "Greeter"
|
||||
msgstr "Дисплейний менеджер"
|
||||
msgstr "Менеджер входу"
|
||||
|
||||
msgid "Please chose which greeter to install"
|
||||
msgstr "Виберіть, який дисплейний менеджер встановлювати"
|
||||
msgstr "Виберіть, який менеджер входу встановлювати"
|
||||
|
||||
msgid "This is a list of pre-programmed default_profiles"
|
||||
msgstr "Список попередньо налаштованих default_profiles"
|
||||
|
|
@ -1219,6 +1222,21 @@ msgstr "Модель"
|
|||
msgid "Invalid configuration: {error}"
|
||||
msgstr "Неправильна конфігурація: {error}"
|
||||
|
||||
msgid "Ready to install"
|
||||
msgstr "Готово до встановлення"
|
||||
|
||||
msgid "Disks"
|
||||
msgstr "Диски"
|
||||
|
||||
msgid "Packages"
|
||||
msgstr "Пакети"
|
||||
|
||||
msgid "Network"
|
||||
msgstr "Мережа"
|
||||
|
||||
msgid "Locale"
|
||||
msgstr "Локаль"
|
||||
|
||||
msgid "Type"
|
||||
msgstr "Тип"
|
||||
|
||||
|
|
@ -1903,7 +1921,7 @@ msgid "Snapshot type: {}"
|
|||
msgstr "Тип знімка: {}"
|
||||
|
||||
msgid "U2F login setup"
|
||||
msgstr "Налаштування входу з пристроєм двофакторної авторизації"
|
||||
msgstr "Налаштування входу з пристроєм двофакторної автентифікації"
|
||||
|
||||
msgid "No U2F devices found"
|
||||
msgstr "Не знайдено жодного пристрою двофакторної автентифікації"
|
||||
|
|
@ -1919,7 +1937,7 @@ msgid "Setting up U2F device for user: {}"
|
|||
msgstr "Налаштування пристроя двофакторної автентифікації для користувача: {}"
|
||||
|
||||
msgid "You may need to enter the PIN and then touch your U2F device to register it"
|
||||
msgstr "Ви повинні написати PIN та потім торкнутися вашого пристрою двофакторної авторизації, щоб зареєструвати його"
|
||||
msgstr "Ви повинні написати PIN та потім торкнутися вашого пристрою двофакторної автентифікації, щоб зареєструвати його"
|
||||
|
||||
msgid "Starting device modifications in "
|
||||
msgstr "Початок модифікацій пристрою в "
|
||||
|
|
@ -2115,6 +2133,12 @@ msgstr "Оберіть інтерфейс"
|
|||
msgid "Choose network configuration"
|
||||
msgstr "Оберіть конфігурацію мережі"
|
||||
|
||||
msgid "Recommended: Network Manager for desktop, Manual for server"
|
||||
msgstr "Рекомендовано: Network Manager для робочого столу, ручне налаштування для сервера"
|
||||
|
||||
msgid "Warning: no network configuration selected. Network will need to be set up manually on the installed system."
|
||||
msgstr "Попередження: конфігурацію мережі не обрано. Мережу доведеться налаштувати вручну на встановленій системі."
|
||||
|
||||
msgid "No packages found"
|
||||
msgstr "Пакети не знайдено"
|
||||
|
||||
|
|
@ -2148,45 +2172,61 @@ msgstr "Надійність пароля: Сильна"
|
|||
msgid "The selected desktop profile requires a regular user to log in via the greeter"
|
||||
msgstr "Обраний профіль робочого столу вимагає входу звичайного користувача через менеджер входу"
|
||||
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Environment type: {} {}"
|
||||
msgstr "Тип середовища: {}"
|
||||
msgstr "Тип середовища: {} {}"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Input cannot be empty"
|
||||
msgstr "Значення не може бути пустим"
|
||||
|
||||
msgid "Recommended"
|
||||
msgstr ""
|
||||
msgstr "Рекомендовано"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Package"
|
||||
msgstr "Група пакетів:"
|
||||
msgstr "Пакет"
|
||||
|
||||
msgid "Curated selection of KDE Plasma packages"
|
||||
msgstr ""
|
||||
msgstr "Підібраний набір пакетів KDE Plasma"
|
||||
|
||||
msgid "Dependencies"
|
||||
msgstr ""
|
||||
msgstr "Залежності"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Package group"
|
||||
msgstr "Група пакетів:"
|
||||
msgstr "Група пакетів"
|
||||
|
||||
msgid "Extensive KDE Plasma installation"
|
||||
msgstr ""
|
||||
msgstr "Розширене встановлення KDE Plasma"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Packages in group"
|
||||
msgstr "Група пакетів:"
|
||||
msgstr "Пакети у групі"
|
||||
|
||||
msgid "Minimal KDE Plasma installation"
|
||||
msgstr ""
|
||||
msgstr "Мінімальне встановлення KDE Plasma"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Description"
|
||||
msgstr "Шифрування диска"
|
||||
msgstr "Опис"
|
||||
|
||||
#, fuzzy
|
||||
msgid "Select a flavor of KDE Plasma to install"
|
||||
msgstr "Оберіть завантажувач для встановлення"
|
||||
msgstr "Оберіть варіант KDE Plasma для встановлення"
|
||||
|
||||
msgid "Arial/Times/Courier replacement, Cyrillic support for Steam/games"
|
||||
msgstr "заміна Arial/Times/Courier, підтримка кирилиці для Steam та ігор"
|
||||
|
||||
msgid "wide Unicode coverage, good fallback font"
|
||||
msgstr "широке покриття Unicode, хороший резервний шрифт"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {u2f_config.u2f_login_method.value}"
|
||||
msgstr "Налаштування входу з пристроєм двофакторної автентифікації: {u2f_config.u2f_login_method.value}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {DEFAULT_ITER_TIME}ms, Recommended range: 1000-60000"
|
||||
msgstr "Стандартно: {DEFAULT_ITER_TIME}мс, Рекомендований діапазон - 1000-60000"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Setting up U2F login: {}"
|
||||
msgstr "Налаштування входу з пристроєм двофакторної автентифікації: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Default: {}ms, Recommended range: 1000-60000"
|
||||
msgstr "Стандартно: {}мс, Рекомендований діапазон - 1000-60000"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from archinstall.lib.packages.util import check_version_upgrade
|
|||
from archinstall.lib.pacman.pacman import Pacman
|
||||
from archinstall.lib.translationhandler import tr, translation_handler
|
||||
from archinstall.lib.utils.util import running_from_iso
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
def _log_sys_info() -> None:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import time
|
|||
from archinstall.lib.applications.application_handler import ApplicationHandler
|
||||
from archinstall.lib.args import ArchConfig, ArchConfigHandler
|
||||
from archinstall.lib.authentication.authentication_handler import AuthenticationHandler
|
||||
from archinstall.lib.bootloader.utils import validate_bootloader_layout
|
||||
from archinstall.lib.configuration import ConfigurationOutput
|
||||
from archinstall.lib.disk.filesystem import FilesystemHandler
|
||||
from archinstall.lib.disk.utils import disk_layouts
|
||||
|
|
@ -21,7 +22,7 @@ from archinstall.lib.output import debug, error, info
|
|||
from archinstall.lib.packages.util import check_version_upgrade
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
def show_menu(
|
||||
|
|
@ -211,12 +212,21 @@ def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
|
|||
config.write_debug()
|
||||
config.save()
|
||||
|
||||
# Safety net for silent/config-file flow. The TUI menu blocks Install via
|
||||
# GlobalMenu._validate_bootloader() before reaching this point.
|
||||
if failure := validate_bootloader_layout(
|
||||
arch_config_handler.config.bootloader_config,
|
||||
arch_config_handler.config.disk_config,
|
||||
):
|
||||
error(failure.description)
|
||||
return
|
||||
|
||||
if arch_config_handler.args.dry_run:
|
||||
return
|
||||
|
||||
if not arch_config_handler.args.silent:
|
||||
aborted = False
|
||||
res: bool = tui.run(config.confirm_config)
|
||||
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
|
||||
|
||||
if not res:
|
||||
debug('Installation aborted')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from archinstall.lib.network.network_handler import install_network_config
|
|||
from archinstall.lib.output import debug, error, info
|
||||
from archinstall.lib.profile.profiles_handler import profile_handler
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
def perform_installation(arch_config_handler: ArchConfigHandler) -> None:
|
||||
|
|
@ -77,7 +77,7 @@ async def main(arch_config_handler: ArchConfigHandler | None = None) -> None:
|
|||
|
||||
if not arch_config_handler.args.silent:
|
||||
aborted = False
|
||||
res: bool = tui.run(config.confirm_config)
|
||||
res: bool = tui.run(lambda: config.confirm_config(show_install_warnings=True))
|
||||
|
||||
if not res:
|
||||
debug('Installation aborted')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from archinstall.lib.installer import Installer
|
|||
from archinstall.lib.menu.util import delayed_warning
|
||||
from archinstall.lib.output import debug, error
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.components import tui
|
||||
from archinstall.tui.components import tui
|
||||
|
||||
|
||||
def show_menu(arch_config_handler: ArchConfigHandler) -> None:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ from textual.worker import WorkerCancelled
|
|||
|
||||
from archinstall.lib.output import debug
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import Result, ResultType
|
||||
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.result import Result, ResultType
|
||||
|
||||
ValueT = TypeVar('ValueT')
|
||||
|
||||
|
|
@ -350,7 +350,7 @@ class OptionListScreen(BaseScreen[ValueT]):
|
|||
|
||||
def _set_preview(self, item_id: str) -> None:
|
||||
if self._preview_location is None:
|
||||
return None
|
||||
return
|
||||
|
||||
preview_widget = self.query_one('#preview_content', Label)
|
||||
item = self._group.find_by_id(item_id)
|
||||
|
|
@ -530,7 +530,7 @@ class SelectListScreen(BaseScreen[ValueT]):
|
|||
selection_list = self.query_one(SelectionList)
|
||||
|
||||
if not selection_list.has_focus or event.key != 'enter':
|
||||
return None
|
||||
return
|
||||
|
||||
if len(self._selected_items) < 1:
|
||||
index = selection_list.highlighted
|
||||
|
|
@ -743,7 +743,7 @@ class ConfirmationScreen(BaseScreen[ValueT]):
|
|||
if self._is_btn_focus():
|
||||
item = self._group.focus_item
|
||||
if not item:
|
||||
return None
|
||||
return
|
||||
_ = self.dismiss(Result(ResultType.Selection, _item=item))
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,109 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
class HelpTextGroupId(Enum):
|
||||
GENERAL = 'General'
|
||||
NAVIGATION = 'Navigation'
|
||||
SELECTION = 'Selection'
|
||||
SEARCH = 'Search'
|
||||
|
||||
|
||||
@dataclass
|
||||
class HelpText:
|
||||
description: str
|
||||
keys: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HelpGroup:
|
||||
group_id: HelpTextGroupId
|
||||
group_entries: list[HelpText]
|
||||
|
||||
def get_desc_width(self) -> int:
|
||||
return max([len(e.description) for e in self.group_entries])
|
||||
|
||||
def get_key_width(self) -> int:
|
||||
return max([len(', '.join(e.keys)) for e in self.group_entries])
|
||||
|
||||
|
||||
class Help:
|
||||
# the groups needs to be classmethods not static methods
|
||||
# because they rely on the DeferredTranslation setup first;
|
||||
# if they are static methods, they will be called before the
|
||||
# translation setup is done
|
||||
|
||||
@staticmethod
|
||||
def general() -> HelpGroup:
|
||||
return HelpGroup(
|
||||
group_id=HelpTextGroupId.GENERAL,
|
||||
group_entries=[
|
||||
HelpText(tr('Show help'), ['Ctrl+h']),
|
||||
HelpText(tr('Exit help'), ['Esc']),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def navigation() -> HelpGroup:
|
||||
return HelpGroup(
|
||||
group_id=HelpTextGroupId.NAVIGATION,
|
||||
group_entries=[
|
||||
HelpText(tr('Preview scroll up'), ['PgUp']),
|
||||
HelpText(tr('Preview scroll down'), ['PgDown']),
|
||||
HelpText(tr('Move up'), ['k', '↑']),
|
||||
HelpText(tr('Move down'), ['j', '↓']),
|
||||
HelpText(tr('Move right'), ['l', '→']),
|
||||
HelpText(tr('Move left'), ['h', '←']),
|
||||
HelpText(tr('Jump to entry'), ['1..9']),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def selection() -> HelpGroup:
|
||||
return HelpGroup(
|
||||
group_id=HelpTextGroupId.SELECTION,
|
||||
group_entries=[
|
||||
HelpText(tr('Skip selection (if available)'), ['Esc']),
|
||||
HelpText(tr('Reset selection (if available)'), ['Ctrl+c']),
|
||||
HelpText(tr('Select on single select'), ['Enter']),
|
||||
HelpText(tr('Select on multi select'), ['Space', 'Tab']),
|
||||
HelpText(tr('Reset'), ['Ctrl-C']),
|
||||
HelpText(tr('Skip selection menu'), ['Esc']),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def search() -> HelpGroup:
|
||||
return HelpGroup(
|
||||
group_id=HelpTextGroupId.SEARCH,
|
||||
group_entries=[
|
||||
HelpText(tr('Start search mode'), ['/']),
|
||||
HelpText(tr('Exit search mode'), ['Esc']),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_help_text() -> str:
|
||||
help_output = ''
|
||||
help_texts = [
|
||||
Help.general(),
|
||||
Help.navigation(),
|
||||
Help.selection(),
|
||||
Help.search(),
|
||||
]
|
||||
max_desc_width = max([help.get_desc_width() for help in help_texts]) + 2
|
||||
max_key_width = max([help.get_key_width() for help in help_texts])
|
||||
|
||||
for help_group in help_texts:
|
||||
help_output += f'{help_group.group_id.value}\n'
|
||||
divider_len = max_desc_width + max_key_width
|
||||
help_output += '-' * divider_len + '\n'
|
||||
|
||||
for entry in help_group.group_entries:
|
||||
help_output += entry.description.ljust(max_desc_width, ' ') + ', '.join(entry.keys) + '\n'
|
||||
|
||||
help_output += '\n'
|
||||
|
||||
return help_output
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
from collections.abc import Callable
|
||||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from functools import cached_property
|
||||
from typing import Any, ClassVar, Self, override
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.lib.utils.encoding import unicode_ljust
|
||||
|
||||
|
||||
@dataclass
|
||||
class MenuItem:
|
||||
text: str
|
||||
value: Any | None = None
|
||||
action: Callable[[Any], Any] | None = None
|
||||
action: Callable[[Any], Awaitable[Any]] | None = None
|
||||
enabled: bool = True
|
||||
read_only: bool = False
|
||||
mandatory: bool = False
|
||||
|
|
@ -23,6 +22,7 @@ class MenuItem:
|
|||
key: str | None = None
|
||||
|
||||
_id: str = ''
|
||||
|
||||
_yes: ClassVar[Self | None] = None
|
||||
_no: ClassVar[Self | None] = None
|
||||
|
||||
|
|
@ -110,6 +110,11 @@ class MenuItemGroup:
|
|||
if self.focus_item not in self.items:
|
||||
raise ValueError(f'Selected item not in menu: {self.focus_item}')
|
||||
|
||||
@classmethod
|
||||
def from_objects(cls, items: list[Any]) -> Self:
|
||||
items = [MenuItem(str(id(item)), value=item) for item in items]
|
||||
return cls(items)
|
||||
|
||||
def add_item(self, item: MenuItem) -> None:
|
||||
self._menu_items.append(item)
|
||||
del self.items # resetting the cache
|
||||
|
|
@ -143,7 +148,7 @@ class MenuItemGroup:
|
|||
cls,
|
||||
enum_cls: type[Enum],
|
||||
sort_items: bool = False,
|
||||
preset: Enum | None = None,
|
||||
preset: Iterable[Enum] | Enum | None = None,
|
||||
) -> Self:
|
||||
items = [MenuItem(elem.value, value=elem) for elem in enum_cls]
|
||||
group = cls(items, sort_items=sort_items)
|
||||
|
|
@ -198,60 +203,10 @@ class MenuItemGroup:
|
|||
|
||||
return None
|
||||
|
||||
def index_focus(self) -> int | None:
|
||||
if self.focus_item and self.items:
|
||||
try:
|
||||
return self.items.index(self.focus_item)
|
||||
except ValueError:
|
||||
# on large menus (15k+) when filtering very quickly
|
||||
# the index search is too slow while the items are reduced
|
||||
# by the filter and it will blow up as it cannot find the
|
||||
# focus item
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def size(self) -> int:
|
||||
return len(self.items)
|
||||
|
||||
def get_max_width(self) -> int:
|
||||
# use the menu_items not the items here otherwise the preview
|
||||
# will get resized all the time when a filter is applied
|
||||
return max([len(self.get_item_text(item)) for item in self._menu_items])
|
||||
|
||||
@cached_property
|
||||
def _max_items_text_width(self) -> int:
|
||||
return max([len(item.text) for item in self._menu_items])
|
||||
|
||||
def get_item_text(self, item: MenuItem) -> str:
|
||||
if item.is_empty():
|
||||
return ''
|
||||
|
||||
max_width = self._max_items_text_width
|
||||
display_text = item.get_display_value()
|
||||
|
||||
default_text = self._default_suffix(item)
|
||||
|
||||
text = unicode_ljust(str(item.text), max_width, ' ')
|
||||
spacing = ' ' * 4
|
||||
|
||||
if display_text:
|
||||
text = f'{text}{spacing}{display_text}'
|
||||
elif self._checkmarks:
|
||||
from archinstall.tui.types import Chars
|
||||
|
||||
if item.has_value():
|
||||
if item.get_value() is not False:
|
||||
text = f'{text}{spacing}{Chars.Check}'
|
||||
else:
|
||||
text = item.text
|
||||
|
||||
if default_text:
|
||||
text = f'{text} {default_text}'
|
||||
|
||||
return text.rstrip(' ')
|
||||
|
||||
def _default_suffix(self, item: MenuItem) -> str:
|
||||
if self.default_item == item:
|
||||
return tr(' (default)')
|
||||
|
|
@ -274,45 +229,11 @@ class MenuItemGroup:
|
|||
return 0
|
||||
return 1
|
||||
|
||||
@property
|
||||
def filter_pattern(self) -> str:
|
||||
return self._filter_pattern
|
||||
|
||||
def has_filter(self) -> bool:
|
||||
return self._filter_pattern != ''
|
||||
|
||||
def set_filter_pattern(self, pattern: str) -> None:
|
||||
self._filter_pattern = pattern
|
||||
del self.items # resetting the cache
|
||||
self.focus_first()
|
||||
|
||||
def append_filter(self, pattern: str) -> None:
|
||||
self._filter_pattern += pattern
|
||||
del self.items # resetting the cache
|
||||
self.focus_first()
|
||||
|
||||
def reduce_filter(self) -> None:
|
||||
self._filter_pattern = self._filter_pattern[:-1]
|
||||
del self.items # resetting the cache
|
||||
self.focus_first()
|
||||
|
||||
def _reload_focus_item(self) -> None:
|
||||
if len(self.items) > 0:
|
||||
if self.focus_item not in self.items:
|
||||
self.focus_first()
|
||||
else:
|
||||
self.focus_item = None
|
||||
|
||||
def is_item_selected(self, item: MenuItem) -> bool:
|
||||
return item in self.selected_items
|
||||
|
||||
def select_current_item(self) -> None:
|
||||
if self.focus_item:
|
||||
if self.focus_item in self.selected_items:
|
||||
self.selected_items.remove(self.focus_item)
|
||||
else:
|
||||
self.selected_items.append(self.focus_item)
|
||||
|
||||
def focus_index(self, index: int) -> None:
|
||||
enabled = self.get_enabled_items()
|
||||
self.focus_item = enabled[index]
|
||||
|
|
@ -379,12 +300,6 @@ class MenuItemGroup:
|
|||
|
||||
return None
|
||||
|
||||
def is_mandatory_fulfilled(self) -> bool:
|
||||
for item in self._menu_items:
|
||||
if item.mandatory and not item.value:
|
||||
return False
|
||||
return True
|
||||
|
||||
def max_item_width(self) -> int:
|
||||
spaces = [len(str(it.text)) for it in self.items]
|
||||
if spaces:
|
||||
|
|
@ -417,99 +332,3 @@ class MenuItemGroup:
|
|||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class MenuItemsState:
|
||||
def __init__(
|
||||
self,
|
||||
item_group: MenuItemGroup,
|
||||
total_cols: int,
|
||||
total_rows: int,
|
||||
with_frame: bool,
|
||||
) -> None:
|
||||
self._item_group = item_group
|
||||
self._total_cols = total_cols
|
||||
self._total_rows = total_rows - 2 if with_frame else total_rows
|
||||
|
||||
self._prev_row_idx: int = -1
|
||||
self._prev_visible_rows: list[int] = []
|
||||
self._view_items: list[list[MenuItem]] = []
|
||||
|
||||
def _determine_focus_row(self) -> int | None:
|
||||
focus_index = self._item_group.index_focus()
|
||||
|
||||
if focus_index is None:
|
||||
return None
|
||||
|
||||
row_index = focus_index // self._total_cols
|
||||
return row_index
|
||||
|
||||
def get_view_items(self) -> list[list[MenuItem]]:
|
||||
enabled_items = self._item_group.get_enabled_items()
|
||||
focus_row_idx = self._determine_focus_row()
|
||||
|
||||
if focus_row_idx is None:
|
||||
return []
|
||||
|
||||
start, end = 0, 0
|
||||
|
||||
if len(self._view_items) == 0 or self._prev_row_idx == -1 or focus_row_idx == 0: # initial setup
|
||||
if focus_row_idx < self._total_rows:
|
||||
start = 0
|
||||
end = self._total_rows
|
||||
elif focus_row_idx > len(enabled_items) - self._total_rows:
|
||||
start = len(enabled_items) - self._total_rows
|
||||
end = len(enabled_items)
|
||||
else:
|
||||
start = focus_row_idx
|
||||
end = focus_row_idx + self._total_rows
|
||||
elif len(enabled_items) <= self._total_rows: # the view can handle all items
|
||||
start = 0
|
||||
end = self._total_rows
|
||||
elif not self._item_group.has_filter() and focus_row_idx in self._prev_visible_rows: # focus is in the same view
|
||||
self._prev_row_idx = focus_row_idx
|
||||
return self._view_items
|
||||
else:
|
||||
if self._item_group.has_filter():
|
||||
start = focus_row_idx
|
||||
end = focus_row_idx + self._total_rows
|
||||
else:
|
||||
delta = focus_row_idx - self._prev_row_idx
|
||||
|
||||
if delta > 0: # cursor is on the bottom most row
|
||||
start = focus_row_idx - self._total_rows + 1
|
||||
end = focus_row_idx + 1
|
||||
else: # focus is on the top most row
|
||||
start = focus_row_idx
|
||||
end = focus_row_idx + self._total_rows
|
||||
|
||||
self._view_items = self._get_view_items(enabled_items, start, end)
|
||||
self._prev_visible_rows = list(range(start, end))
|
||||
self._prev_row_idx = focus_row_idx
|
||||
|
||||
return self._view_items
|
||||
|
||||
def _get_view_items(
|
||||
self,
|
||||
items: list[MenuItem],
|
||||
start_row: int,
|
||||
total_rows: int,
|
||||
) -> list[list[MenuItem]]:
|
||||
groups: list[list[MenuItem]] = []
|
||||
nr_items = self._total_cols * min(total_rows, len(items))
|
||||
|
||||
for x in range(start_row, nr_items, self._total_cols):
|
||||
groups.append(
|
||||
items[x : x + self._total_cols],
|
||||
)
|
||||
|
||||
return groups
|
||||
|
||||
def _max_visible_items(self) -> int:
|
||||
return self._total_cols * self._total_rows
|
||||
|
||||
def _remaining_next_spots(self) -> int:
|
||||
return self._max_visible_items() - self._prev_row_idx
|
||||
|
||||
def _remaining_prev_spots(self) -> int:
|
||||
return self._max_visible_items() - self._remaining_next_spots()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Self, cast
|
||||
|
||||
from archinstall.tui.menu_item import MenuItem
|
||||
|
||||
|
|
@ -13,25 +14,58 @@ class ResultType(Enum):
|
|||
@dataclass
|
||||
class Result[ValueT]:
|
||||
type_: ResultType
|
||||
_item: MenuItem | list[MenuItem] | str | None
|
||||
_data: ValueT | list[ValueT] | None = None
|
||||
_item: MenuItem | list[MenuItem] | None = None
|
||||
|
||||
def has_item(self) -> bool:
|
||||
@classmethod
|
||||
def true(cls) -> Self:
|
||||
return cls(ResultType.Selection, _data=True) # type: ignore[arg-type]
|
||||
|
||||
@classmethod
|
||||
def false(cls) -> Self:
|
||||
return cls(ResultType.Selection, _data=False) # type: ignore[arg-type]
|
||||
|
||||
@classmethod
|
||||
def reset(cls) -> Self:
|
||||
return cls(ResultType.Reset)
|
||||
|
||||
@classmethod
|
||||
def selection(cls, value: ValueT | list[ValueT] | None) -> Self:
|
||||
return cls(ResultType.Selection, _data=value)
|
||||
|
||||
@classmethod
|
||||
def skip(cls) -> Self:
|
||||
return cls(ResultType.Skip)
|
||||
|
||||
def has_data(self) -> bool:
|
||||
return self._data is not None
|
||||
|
||||
def has_value(self) -> bool:
|
||||
return self._item is not None
|
||||
|
||||
def get_value(self) -> ValueT:
|
||||
return self.item().get_value() # type: ignore[no-any-return]
|
||||
|
||||
def get_values(self) -> list[ValueT]:
|
||||
return [i.get_value() for i in self.items()]
|
||||
|
||||
def item(self) -> MenuItem:
|
||||
assert self._item is not None and isinstance(self._item, MenuItem)
|
||||
if isinstance(self._item, list) or self._item is None:
|
||||
raise ValueError('Invalid item type')
|
||||
return self._item
|
||||
|
||||
def items(self) -> list[MenuItem]:
|
||||
assert self._item is not None and isinstance(self._item, list)
|
||||
return self._item
|
||||
if isinstance(self._item, list):
|
||||
return self._item
|
||||
|
||||
def text(self) -> str:
|
||||
assert self._item is not None and isinstance(self._item, str)
|
||||
return self._item
|
||||
raise ValueError('Invalid item type')
|
||||
|
||||
def get_value(self) -> ValueT:
|
||||
if self._item is not None:
|
||||
return self.item().get_value() # type: ignore[no-any-return]
|
||||
|
||||
if type(self._data) is not list and self._data is not None:
|
||||
return cast(ValueT, self._data)
|
||||
|
||||
raise ValueError('No value found')
|
||||
|
||||
def get_values(self) -> list[ValueT]:
|
||||
if self._item is not None:
|
||||
return [i.get_value() for i in self.items()]
|
||||
|
||||
assert type(self._data) is list
|
||||
return cast(list[ValueT], self._data)
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
import curses
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Self
|
||||
|
||||
SCROLL_INTERVAL = 10
|
||||
|
||||
|
||||
class STYLE(Enum):
|
||||
NORMAL = 1
|
||||
CURSOR_STYLE = 2
|
||||
MENU_STYLE = 3
|
||||
HELP = 4
|
||||
ERROR = 5
|
||||
|
||||
|
||||
class MenuKeys(Enum):
|
||||
# latin keys
|
||||
STD_KEYS = frozenset(range(32, 127))
|
||||
# numbers
|
||||
NUM_KEYS = frozenset(range(49, 58))
|
||||
# Menu up: up, k
|
||||
MENU_UP = frozenset({259, 107})
|
||||
# Menu down: down, j
|
||||
MENU_DOWN = frozenset({258, 106})
|
||||
# Menu left: left, h
|
||||
MENU_LEFT = frozenset({260, 104})
|
||||
# Menu right: right, l
|
||||
MENU_RIGHT = frozenset({261, 108})
|
||||
# Menu start: home CTRL-a
|
||||
MENU_START = frozenset({262, 1})
|
||||
# Menu end: end CTRL-e
|
||||
MENU_END = frozenset({360, 5})
|
||||
# Enter
|
||||
ACCEPT = frozenset({10})
|
||||
# Selection: space, tab
|
||||
MULTI_SELECT = frozenset({32, 9})
|
||||
# Search: /
|
||||
ENABLE_SEARCH = frozenset({47})
|
||||
# ESC
|
||||
ESC = frozenset({27})
|
||||
# BACKSPACE (search)
|
||||
BACKSPACE = frozenset({127, 263})
|
||||
# Help view: ctrl+h
|
||||
HELP = frozenset({8})
|
||||
# Scroll up: PGUP
|
||||
SCROLL_UP = frozenset({339})
|
||||
# Scroll down: PGDOWN
|
||||
SCROLL_DOWN = frozenset({338})
|
||||
|
||||
@classmethod
|
||||
def from_ord(cls, key: int) -> list[Self]:
|
||||
matches: list[Self] = []
|
||||
|
||||
for group in cls:
|
||||
if key in group.value:
|
||||
matches.append(group)
|
||||
|
||||
return matches
|
||||
|
||||
@classmethod
|
||||
def decode(cls, key: int) -> str:
|
||||
byte_str = curses.keyname(key)
|
||||
return byte_str.decode('utf-8')
|
||||
|
||||
|
||||
class FrameStyle(Enum):
|
||||
MAX = auto()
|
||||
MIN = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrameProperties:
|
||||
header: str
|
||||
w_frame_style: FrameStyle = FrameStyle.MAX
|
||||
h_frame_style: FrameStyle = FrameStyle.MAX
|
||||
|
||||
@classmethod
|
||||
def max(cls, header: str) -> Self:
|
||||
return cls(
|
||||
header,
|
||||
FrameStyle.MAX,
|
||||
FrameStyle.MAX,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def min(cls, header: str) -> Self:
|
||||
return cls(
|
||||
header,
|
||||
FrameStyle.MIN,
|
||||
FrameStyle.MIN,
|
||||
)
|
||||
|
||||
|
||||
class Orientation(Enum):
|
||||
VERTICAL = auto()
|
||||
HORIZONTAL = auto()
|
||||
|
||||
|
||||
class PreviewStyle(Enum):
|
||||
NONE = auto()
|
||||
BOTTOM = auto()
|
||||
RIGHT = auto()
|
||||
TOP = auto()
|
||||
|
||||
|
||||
# https://www.compart.com/en/unicode/search?q=box+drawings#characters
|
||||
# https://en.wikipedia.org/wiki/Box-drawing_characters
|
||||
class Chars:
|
||||
Horizontal = '─'
|
||||
Vertical = '│'
|
||||
Upper_left = '┌'
|
||||
Upper_right = '┐'
|
||||
Lower_left = '└'
|
||||
Lower_right = '┘'
|
||||
Block = '█'
|
||||
Triangle_up = '▲'
|
||||
Triangle_down = '▼'
|
||||
Check = '+'
|
||||
Cross = 'x'
|
||||
Right_arrow = '←'
|
||||
|
||||
|
||||
@dataclass
|
||||
class ViewportEntry:
|
||||
text: str
|
||||
row: int
|
||||
col: int
|
||||
style: STYLE
|
||||
|
||||
|
||||
class Alignment(Enum):
|
||||
LEFT = auto()
|
||||
CENTER = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrameDim:
|
||||
x_start: int
|
||||
x_end: int
|
||||
height: int
|
||||
|
||||
def x_delta(self) -> int:
|
||||
return self.x_end - self.x_start
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from functools import cached_property
|
||||
from typing import Any, ClassVar, Self, override
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
||||
|
||||
@dataclass
|
||||
class MenuItem:
|
||||
text: str
|
||||
value: Any | None = None
|
||||
action: Callable[[Any], Awaitable[Any]] | None = None
|
||||
enabled: bool = True
|
||||
read_only: bool = False
|
||||
mandatory: bool = False
|
||||
dependencies: list[str | Callable[[], bool]] = field(default_factory=list)
|
||||
dependencies_not: list[str] = field(default_factory=list)
|
||||
display_action: Callable[[Any], str] | None = None
|
||||
preview_action: Callable[[Self], str | None] | None = None
|
||||
key: str | None = None
|
||||
|
||||
_id: str = ''
|
||||
|
||||
_yes: ClassVar[Self | None] = None
|
||||
_no: ClassVar[Self | None] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.key is not None:
|
||||
self._id = self.key
|
||||
else:
|
||||
self._id = str(id(self))
|
||||
|
||||
@override
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._id)
|
||||
|
||||
def get_id(self) -> str:
|
||||
return self._id
|
||||
|
||||
def get_value(self) -> Any:
|
||||
assert self.value is not None
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def yes(cls, action: Callable[[Any], Any] | None = None) -> Self:
|
||||
if cls._yes is None:
|
||||
cls._yes = cls(tr('Yes'), value=True, key='yes', action=action)
|
||||
|
||||
return cls._yes
|
||||
|
||||
@classmethod
|
||||
def no(cls, action: Callable[[Any], Any] | None = None) -> Self:
|
||||
if cls._no is None:
|
||||
cls._no = cls(tr('No'), value=False, key='no', action=action)
|
||||
|
||||
return cls._no
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
return self.text == '' or self.text is None
|
||||
|
||||
def has_value(self) -> bool:
|
||||
if self.value is None:
|
||||
return False
|
||||
elif isinstance(self.value, list) and len(self.value) == 0:
|
||||
return False
|
||||
elif isinstance(self.value, dict) and len(self.value) == 0:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_display_value(self) -> str | None:
|
||||
if self.display_action is not None:
|
||||
return self.display_action(self.value)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MenuItemGroup:
|
||||
def __init__(
|
||||
self,
|
||||
menu_items: list[MenuItem],
|
||||
focus_item: MenuItem | None = None,
|
||||
default_item: MenuItem | None = None,
|
||||
sort_items: bool = False,
|
||||
sort_case_sensitive: bool = True,
|
||||
checkmarks: bool = False,
|
||||
) -> None:
|
||||
if len(menu_items) < 1:
|
||||
raise ValueError('Menu must have at least one item')
|
||||
|
||||
if sort_items:
|
||||
if sort_case_sensitive:
|
||||
menu_items = sorted(menu_items, key=lambda x: x.text)
|
||||
else:
|
||||
menu_items = sorted(menu_items, key=lambda x: x.text.lower())
|
||||
|
||||
self._filter_pattern: str = ''
|
||||
self._checkmarks: bool = checkmarks
|
||||
|
||||
self._menu_items: list[MenuItem] = menu_items
|
||||
self.focus_item: MenuItem | None = focus_item
|
||||
self.selected_items: list[MenuItem] = []
|
||||
self.default_item: MenuItem | None = default_item
|
||||
|
||||
if not focus_item:
|
||||
self.focus_first()
|
||||
|
||||
if self.focus_item not in self.items:
|
||||
raise ValueError(f'Selected item not in menu: {self.focus_item}')
|
||||
|
||||
@classmethod
|
||||
def from_objects(cls, items: list[Any]) -> Self:
|
||||
items = [MenuItem(str(id(item)), value=item) for item in items]
|
||||
return cls(items)
|
||||
|
||||
def add_item(self, item: MenuItem) -> None:
|
||||
self._menu_items.append(item)
|
||||
del self.items # resetting the cache
|
||||
|
||||
def find_by_id(self, item_id: str) -> MenuItem:
|
||||
for item in self._menu_items:
|
||||
if item.get_id() == item_id:
|
||||
return item
|
||||
|
||||
raise ValueError(f'No item found for id: {item_id}')
|
||||
|
||||
def find_by_key(self, key: str) -> MenuItem:
|
||||
for item in self._menu_items:
|
||||
if item.key == key:
|
||||
return item
|
||||
|
||||
raise ValueError(f'No item found for key: {key}')
|
||||
|
||||
def get_enabled_items(self) -> list[MenuItem]:
|
||||
return [it for it in self.items if self.is_enabled(it)]
|
||||
|
||||
@classmethod
|
||||
def yes_no(cls) -> Self:
|
||||
return cls(
|
||||
[MenuItem.yes(), MenuItem.no()],
|
||||
sort_items=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_enum(
|
||||
cls,
|
||||
enum_cls: type[Enum],
|
||||
sort_items: bool = False,
|
||||
preset: Enum | None = None,
|
||||
) -> Self:
|
||||
items = [MenuItem(elem.value, value=elem) for elem in enum_cls]
|
||||
group = cls(items, sort_items=sort_items)
|
||||
|
||||
if preset is not None:
|
||||
group.set_selected_by_value(preset)
|
||||
|
||||
return group
|
||||
|
||||
def set_preview_for_all(self, action: Callable[[Any], str | None]) -> None:
|
||||
for item in self.items:
|
||||
item.preview_action = action
|
||||
|
||||
def set_focus_by_value(self, value: Any) -> None:
|
||||
for item in self._menu_items:
|
||||
if item.value == value:
|
||||
self.focus_item = item
|
||||
break
|
||||
|
||||
def set_default_by_value(self, value: Any) -> None:
|
||||
for item in self._menu_items:
|
||||
if item.value == value:
|
||||
self.default_item = item
|
||||
break
|
||||
|
||||
def set_selected_by_value(self, values: Any | list[Any] | None) -> None:
|
||||
if values is None:
|
||||
return
|
||||
|
||||
if not isinstance(values, list):
|
||||
values = [values]
|
||||
|
||||
for item in self._menu_items:
|
||||
if item.value in values:
|
||||
self.selected_items.append(item)
|
||||
|
||||
if values:
|
||||
self.set_focus_by_value(values[0])
|
||||
|
||||
def get_focused_index(self) -> int | None:
|
||||
items = self.get_enabled_items()
|
||||
|
||||
if self.focus_item and items:
|
||||
try:
|
||||
return items.index(self.focus_item)
|
||||
except ValueError:
|
||||
# on large menus (15k+) when filtering very quickly
|
||||
# the index search is too slow while the items are reduced
|
||||
# by the filter and it will blow up as it cannot find the
|
||||
# focus item
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def _max_items_text_width(self) -> int:
|
||||
return max([len(item.text) for item in self._menu_items])
|
||||
|
||||
def _default_suffix(self, item: MenuItem) -> str:
|
||||
if self.default_item == item:
|
||||
return tr(' (default)')
|
||||
return ''
|
||||
|
||||
def set_action_for_all(self, action: Callable[[Any], Any]) -> None:
|
||||
for item in self.items:
|
||||
item.action = action
|
||||
|
||||
@cached_property
|
||||
def items(self) -> list[MenuItem]:
|
||||
pattern = self._filter_pattern.lower()
|
||||
items = filter(lambda item: item.is_empty() or pattern in item.text.lower(), self._menu_items)
|
||||
l_items = sorted(items, key=self._items_score)
|
||||
return l_items
|
||||
|
||||
def _items_score(self, item: MenuItem) -> int:
|
||||
pattern = self._filter_pattern.lower()
|
||||
if item.text.lower().startswith(pattern):
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def set_filter_pattern(self, pattern: str) -> None:
|
||||
self._filter_pattern = pattern
|
||||
del self.items # resetting the cache
|
||||
self.focus_first()
|
||||
|
||||
def focus_index(self, index: int) -> None:
|
||||
enabled = self.get_enabled_items()
|
||||
self.focus_item = enabled[index]
|
||||
|
||||
def focus_first(self) -> None:
|
||||
if len(self.items) == 0:
|
||||
return
|
||||
|
||||
first_item: MenuItem | None = self.items[0]
|
||||
|
||||
if first_item and not self._is_selectable(first_item):
|
||||
first_item = self._find_next_selectable_item(self.items, first_item, 1)
|
||||
|
||||
if first_item is not None:
|
||||
self.focus_item = first_item
|
||||
|
||||
def focus_last(self) -> None:
|
||||
if len(self.items) == 0:
|
||||
return
|
||||
|
||||
last_item: MenuItem | None = self.items[-1]
|
||||
|
||||
if last_item and not self._is_selectable(last_item):
|
||||
last_item = self._find_next_selectable_item(self.items, last_item, -1)
|
||||
|
||||
if last_item is not None:
|
||||
self.focus_item = last_item
|
||||
|
||||
def focus_prev(self, skip_empty: bool = True) -> None:
|
||||
# e.g. when filter shows no items
|
||||
if self.focus_item is None:
|
||||
return
|
||||
|
||||
item = self._find_next_selectable_item(self.items, self.focus_item, -1)
|
||||
|
||||
if item is not None:
|
||||
self.focus_item = item
|
||||
|
||||
def focus_next(self, skip_not_enabled: bool = True) -> None:
|
||||
# e.g. when filter shows no items
|
||||
if self.focus_item is None:
|
||||
return
|
||||
|
||||
item = self._find_next_selectable_item(self.items, self.focus_item, 1)
|
||||
|
||||
if item is not None:
|
||||
self.focus_item = item
|
||||
|
||||
def _find_next_selectable_item(
|
||||
self,
|
||||
items: list[MenuItem],
|
||||
start_item: MenuItem,
|
||||
direction: int,
|
||||
) -> MenuItem | None:
|
||||
start_index = self.items.index(start_item)
|
||||
n = len(items)
|
||||
|
||||
current_index = start_index
|
||||
for _ in range(n):
|
||||
current_index = (current_index + direction) % n
|
||||
|
||||
if self._is_selectable(items[current_index]):
|
||||
return items[current_index]
|
||||
|
||||
return None
|
||||
|
||||
def max_item_width(self) -> int:
|
||||
spaces = [len(str(it.text)) for it in self.items]
|
||||
if spaces:
|
||||
return max(spaces)
|
||||
return 0
|
||||
|
||||
def _is_selectable(self, item: MenuItem) -> bool:
|
||||
if item.is_empty():
|
||||
return False
|
||||
elif item.read_only:
|
||||
return False
|
||||
|
||||
return self.is_enabled(item)
|
||||
|
||||
def is_enabled(self, item: MenuItem) -> bool:
|
||||
if not item.enabled:
|
||||
return False
|
||||
|
||||
for dep in item.dependencies:
|
||||
if isinstance(dep, str):
|
||||
item = self.find_by_key(dep)
|
||||
if not item.value or not self.is_enabled(item):
|
||||
return False
|
||||
else:
|
||||
return dep()
|
||||
|
||||
for dep_not in item.dependencies_not:
|
||||
item = self.find_by_key(dep_not)
|
||||
if item.value is not None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
from dataclasses import dataclass
|
||||
from enum import Enum, auto
|
||||
from typing import Self, cast
|
||||
|
||||
from archinstall.tui.ui.menu_item import MenuItem
|
||||
|
||||
|
||||
class ResultType(Enum):
|
||||
Selection = auto()
|
||||
Skip = auto()
|
||||
Reset = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Result[ValueT]:
|
||||
type_: ResultType
|
||||
_data: ValueT | list[ValueT] | None = None
|
||||
_item: MenuItem | list[MenuItem] | None = None
|
||||
|
||||
@classmethod
|
||||
def true(cls) -> Self:
|
||||
return cls(ResultType.Selection, _data=True) # type: ignore[arg-type]
|
||||
|
||||
@classmethod
|
||||
def false(cls) -> Self:
|
||||
return cls(ResultType.Selection, _data=False) # type: ignore[arg-type]
|
||||
|
||||
@classmethod
|
||||
def reset(cls) -> Self:
|
||||
return cls(ResultType.Reset)
|
||||
|
||||
@classmethod
|
||||
def selection(cls, value: ValueT | list[ValueT] | None) -> Self:
|
||||
return cls(ResultType.Selection, _data=value)
|
||||
|
||||
@classmethod
|
||||
def skip(cls) -> Self:
|
||||
return cls(ResultType.Skip)
|
||||
|
||||
def has_data(self) -> bool:
|
||||
return self._data is not None
|
||||
|
||||
def has_value(self) -> bool:
|
||||
return self._item is not None
|
||||
|
||||
def item(self) -> MenuItem:
|
||||
if isinstance(self._item, list) or self._item is None:
|
||||
raise ValueError('Invalid item type')
|
||||
return self._item
|
||||
|
||||
def items(self) -> list[MenuItem]:
|
||||
if isinstance(self._item, list):
|
||||
return self._item
|
||||
|
||||
raise ValueError('Invalid item type')
|
||||
|
||||
def get_value(self) -> ValueT:
|
||||
if self._item is not None:
|
||||
return self.item().get_value() # type: ignore[no-any-return]
|
||||
|
||||
if type(self._data) is not list and self._data is not None:
|
||||
return cast(ValueT, self._data)
|
||||
|
||||
raise ValueError('No value found')
|
||||
|
||||
def get_values(self) -> list[ValueT]:
|
||||
if self._item is not None:
|
||||
return [i.get_value() for i in self.items()]
|
||||
|
||||
assert type(self._data) is list
|
||||
return cast(list[ValueT], self._data)
|
||||
|
|
@ -22,7 +22,7 @@ Restarting ``systemd-timesyncd.service`` might work but most often you need to c
|
|||
Waiting for Arch Linux keyring sync (archlinux-keyring-wkd-sync) to complete. `#2679`_
|
||||
------------------------------
|
||||
|
||||
The ``archlinux-keyring-wkd-sync.service`` or ``archlinux-keyring-wkd-sync.timer`` can hang "indefinitely" some times.
|
||||
The ``archlinux-keyring-wkd-sync.service`` or ``archlinux-keyring-wkd-sync.timer`` can hang "indefinitely" sometimes.
|
||||
This is usually due to an inability to reach the key servers, or a slow connection towards key servers.
|
||||
|
||||
The script ``/usr/bin/archlinux-keyring-wkd-sync`` can be run manually, to verify if it's executing slowly or not.
|
||||
|
|
@ -65,7 +65,7 @@ ARM, 32bit and other CPU types error out `#1686`_, `#2185`_
|
|||
|
||||
This is a bit of a catch-all known issue.
|
||||
Officially `x86_64`_ is only supported by Arch Linux.
|
||||
Hence little effort have been put into supporting other platforms.
|
||||
Hence little effort has been put into supporting other platforms.
|
||||
|
||||
In theory, other architectures should work but small quirks might arise.
|
||||
|
||||
|
|
@ -79,10 +79,10 @@ Missing key-issues tend to be that the `archlinux-keyring`_ package is out of da
|
|||
There is an attempt from upstream to fix this issue, and it's the `archlinux-keyring-wkd-sync.service`_
|
||||
|
||||
The service starts almost immediately during boot, and if network is not configured in time — the service will fail.
|
||||
Subsequently the ``archinstall`` run might operate on a old keyring despite there being an update service for this.
|
||||
Subsequently the ``archinstall`` run might operate on an old keyring despite there being an update service for this.
|
||||
|
||||
There is really no way to reliably over time work around this issue in ``archinstall``.
|
||||
Instead, efforts to the upstream service should be considered the way forward. And/or keys not expiring between a sane amount of ISO's.
|
||||
Instead, efforts to the upstream service should be considered the way forward. And/or keys not expiring between a sane amount of ISOs.
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ classifiers = [
|
|||
dependencies = [
|
||||
"pyparted==3.13.0",
|
||||
"pydantic==2.12.5",
|
||||
"cryptography==46.0.7",
|
||||
"textual==8.2.4",
|
||||
"cryptography==47.0.0",
|
||||
"textual==8.2.5",
|
||||
"markdown-it-py==4.0.0",
|
||||
"linkify-it-py==2.1.0",
|
||||
]
|
||||
|
|
@ -34,10 +34,10 @@ Source = "https://github.com/archlinux/archinstall"
|
|||
[project.optional-dependencies]
|
||||
log = ["systemd_python==235"]
|
||||
dev = [
|
||||
"mypy==1.20.1",
|
||||
"mypy==1.20.2",
|
||||
"flake8==7.3.0",
|
||||
"pre-commit==4.5.1",
|
||||
"ruff==0.15.11",
|
||||
"pre-commit==4.6.0",
|
||||
"ruff==0.15.12",
|
||||
"pylint==4.0.5",
|
||||
"pytest==9.0.3",
|
||||
]
|
||||
|
|
@ -172,6 +172,23 @@ score = false
|
|||
[tool.pylint.variables]
|
||||
init-import = true
|
||||
|
||||
[tool.pyrefly]
|
||||
python-version = "3.14"
|
||||
|
||||
replace-imports-with-any = [
|
||||
"parted", # pyparted doesn't have type hints
|
||||
]
|
||||
|
||||
[tool.pyrefly.errors]
|
||||
# Enable some additional rules that are disabled by default
|
||||
implicit-abstract-class = true
|
||||
missing-override-decorator = true
|
||||
missing-source = true
|
||||
not-required-key-access = true
|
||||
open-unpacking = true
|
||||
unannotated-parameter = true
|
||||
unused-ignore = true
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py314"
|
||||
line-length = 160
|
||||
|
|
|
|||
Loading…
Reference in New Issue