diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index 97dc019d..e3729504 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -16,7 +16,6 @@ from pydantic.dataclasses import dataclass as p_dataclass from archinstall.lib.crypt import decrypt from archinstall.lib.log import debug, error, logger, warn from archinstall.lib.menu.util import get_password -from archinstall.lib.models import PlymouthConfiguration from archinstall.lib.models.application import ApplicationConfiguration, ZramConfiguration from archinstall.lib.models.authentication import AuthenticationConfiguration from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration @@ -89,7 +88,6 @@ class ArchConfigType(StrEnum): SERVICES = 'services' PACKAGES = 'packages' PACMAN_CONFIG = 'pacman_config' - PLYMOUTH_CONFIG = 'plymouth_config' CUSTOM_COMMANDS = 'custom_commands' def text(self) -> str: @@ -132,8 +130,6 @@ class ArchConfigType(StrEnum): return tr('Additional packages') case ArchConfigType.PACMAN_CONFIG: return tr('Pacman') - case ArchConfigType.PLYMOUTH_CONFIG: - return tr('Plymouth') case ArchConfigType.CUSTOM_COMMANDS: return tr('Custom commands') case ArchConfigType.USERS: @@ -163,7 +159,6 @@ class ArchConfig: ntp: bool = True packages: list[str] = field(default_factory=list) pacman_config: PacmanConfiguration = field(default_factory=PacmanConfiguration.default) - plymouth_config: PlymouthConfiguration | None = None timezone: str = 'UTC' services: list[str] = field(default_factory=list) custom_commands: list[str] = field(default_factory=list) @@ -245,9 +240,6 @@ class ArchConfig: if self.app_config: cfg[ArchConfigType.APP_CONFIG] = self.app_config - if self.plymouth_config: - cfg[ArchConfigType.PLYMOUTH_CONFIG] = self.plymouth_config - return cfg @classmethod @@ -344,9 +336,6 @@ class ArchConfig: if services := args_config.get('services', []): arch_config.services = services - if plymouth_config := args_config.get('plymouth_config', None): - arch_config.plymouth_config = PlymouthConfiguration.parse_arg(plymouth_config) - # DEPRECATED: backwards compatibility root_password = None if root_password := args_config.get('!root-password', None): diff --git a/archinstall/lib/bootloader/bootloader_menu.py b/archinstall/lib/bootloader/bootloader_menu.py index d32a5a21..64ad4c8d 100644 --- a/archinstall/lib/bootloader/bootloader_menu.py +++ b/archinstall/lib/bootloader/bootloader_menu.py @@ -3,7 +3,7 @@ from typing import override 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.models.bootloader import Bootloader, BootloaderConfiguration, PlymouthTheme from archinstall.lib.translationhandler import tr from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.result import ResultType @@ -66,6 +66,13 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]): key='removable', enabled=removable_enabled, ), + MenuItem( + text=tr('Plymouth'), + action=self._select_plymouth, + value=self._bootloader_conf.plymouth, + preview_action=self._prev_plymouth, + key='plymouth', + ), ] def _prev_bootloader(self, item: MenuItem) -> str | None: @@ -85,6 +92,11 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]): return tr('Will install to /EFI/BOOT/ (removable location, safe default)') return tr('Will install to custom location with NVRAM entry') + def _prev_plymouth(self, item: MenuItem) -> str | None: + if item.value: + return f'{tr("Plymouth")}: {item.value.value}' + return None + @override async def show(self) -> BootloaderConfiguration: _ = await super().show() @@ -117,6 +129,9 @@ class BootloaderMenu(AbstractSubMenu[BootloaderConfiguration]): return bootloader + async def _select_plymouth(self, preset: PlymouthTheme | None) -> PlymouthTheme | None: + return await select_plymouth_theme(preset) + async def _select_uki(self, preset: bool) -> bool: prompt = tr('Would you like to use unified kernel images?') + '\n' @@ -215,3 +230,24 @@ async def select_bootloader( return result.get_value() case ResultType.Reset: raise ValueError('Unhandled result type') + + +async def select_plymouth_theme(preset: PlymouthTheme | None = None) -> PlymouthTheme | None: + items = [MenuItem(t.value, value=t) for t in PlymouthTheme] + group = MenuItemGroup(items, sort_items=False) + group.set_selected_by_value(preset.value if preset else None) + + result = await Selection[PlymouthTheme]( + group, + header=tr('Select Plymouth theme'), + allow_reset=True, + allow_skip=True, + ).show() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Reset: + return None + case ResultType.Selection: + return PlymouthTheme(result.get_value()) diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index cc62ae9c..45a0e3b4 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -25,13 +25,11 @@ 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.plymouth import PlymouthConfiguration from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.network.network_menu import select_network from archinstall.lib.packages.packages import list_available_packages, select_additional_packages from archinstall.lib.pacman.config import PacmanConfig from archinstall.lib.pacman.pacman_menu import PacmanMenu -from archinstall.lib.plymouth.plymouth_menu import select_plymouth_theme from archinstall.lib.translationhandler import Language, tr, translation_handler from archinstall.lib.utils.format import as_table from archinstall.tui.components import tui @@ -152,13 +150,6 @@ class GlobalMenu(AbstractMenu[None]): preview_action=self._prev_pacman_config, key='pacman_config', ), - MenuItem( - text=tr('Plymouth'), - action=self._plymouth_configuration, - value=None, - preview_action=self._prev_plymouth_config, - key='plymouth_config', - ), MenuItem( text=tr('Additional packages'), action=self._select_additional_packages, @@ -441,15 +432,6 @@ class GlobalMenu(AbstractMenu[None]): output += '{}: {}'.format(tr('Color'), config.color) return output - async def _plymouth_configuration(self, preset: PlymouthConfiguration | None) -> PlymouthConfiguration | None: - return await select_plymouth_theme(preset) - - def _prev_plymouth_config(self, item: MenuItem) -> str | None: - if not item.value: - return None - config: PlymouthConfiguration = item.value - return config.preview() - def _prev_kernel(self, item: MenuItem) -> str | None: if item.value: kernel = ', '.join(item.value) diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index bf7d91ba..f53dcda4 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -32,7 +32,7 @@ from archinstall.lib.locale.utils import verify_keyboard_layout, verify_x11_keyb from archinstall.lib.log import debug, error, info, log, logger, warn from archinstall.lib.mirror.mirror_handler import MirrorListHandler from archinstall.lib.models.application import ZramAlgorithm -from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration +from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration, PlymouthTheme from archinstall.lib.models.device import ( DiskEncryption, DiskLayoutConfiguration, @@ -1755,6 +1755,28 @@ class Installer: self._helper_flags['bootloader'] = 'refind' + def _install_plymouth(self, plymouth: PlymouthTheme) -> None: + debug(f'Installing plymouth with theme: {plymouth.value}') + self.add_additional_packages(['plymouth']) + + for param in ('quiet', 'splash'): + if param not in self._kernel_params: + self._kernel_params.append(param) + + if 'plymouth' not in self._hooks: + for hook, insert_after in [('encrypt', False), ('sd-encrypt', False), ('systemd', True), ('filesystems', False), ('keyboard', True)]: + try: + idx = self._hooks.index(hook) + self._hooks.insert(idx + (1 if insert_after else 0), 'plymouth') + break + except ValueError: + continue + else: + self._hooks.append('plymouth') + + self.arch_chroot(f'plymouth-set-default-theme {plymouth.value}') + self.mkinitcpio(['-P']) + def _config_uki( self, root: PartitionModification | LvmVolume, @@ -1807,10 +1829,7 @@ class Installer: error('Error generating initramfs (continuing anyway)') def add_bootloader( - self, - bootloader: Bootloader, - uki_enabled: bool = False, - bootloader_removable: bool = False, + self, bootloader: Bootloader, uki_enabled: bool = False, bootloader_removable: bool = False, plymouth: PlymouthTheme | None = None ) -> None: """ Adds a bootloader to the installation instance. @@ -1824,6 +1843,7 @@ class Installer: :param bootloader: Type of bootloader to be added :param uki_enabled: Whether to use unified kernel images :param bootloader_removable: Whether to install to removable media location (UEFI only, for GRUB and Limine) + :param plymouth: Optional Plymouth theme to install and configure """ for plugin in plugins.values(): @@ -1859,6 +1879,9 @@ class Installer: warn(f'Bootloader {bootloader.value} lacks removable support; disabling.') bootloader_removable = False + if plymouth is not None: + self._install_plymouth(plymouth) + if uki_enabled: keep_initramfs = ( bootloader == Bootloader.Grub diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py index 3f446fd9..5f808c96 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -30,7 +30,6 @@ from archinstall.lib.models.locale import LocaleConfiguration from archinstall.lib.models.mirrors import CustomRepository, MirrorConfiguration, MirrorRegion from archinstall.lib.models.network import NetworkConfiguration, Nic, NicType from archinstall.lib.models.packages import LocalPackage, PackageSearch, PackageSearchResult, Repository -from archinstall.lib.models.plymouth import PlymouthConfiguration, PlymouthTheme from archinstall.lib.models.profile import ProfileConfiguration from archinstall.lib.models.users import PasswordStrength, User @@ -70,8 +69,6 @@ __all__ = [ 'PartitionTable', 'PartitionType', 'PasswordStrength', - 'PlymouthConfiguration', - 'PlymouthTheme', 'PrintServiceConfiguration', 'ProfileConfiguration', 'Repository', diff --git a/archinstall/lib/models/bootloader.py b/archinstall/lib/models/bootloader.py index bd6bce7e..040b6746 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -60,15 +60,48 @@ class Bootloader(Enum): return cls(bootloader) +class PlymouthTheme(Enum): + BGRT = 'bgrt' + FADE = 'fade-in' + GLOW = 'glow' + SCRIPT = 'script' + SOLAR = 'solar' + SPINNER = 'spinner' + SPINFINITY = 'spinfinity' + TRIBAR = 'tribar' + TEXT = 'text' + DETAILS = 'details' + + @classmethod + def from_arg(cls, plymouth: str | None) -> Self | None: + if plymouth is None: + return None + + plymouth = plymouth.lower() + + values = [e.value for e in cls] + + if plymouth not in values: + warn(f'Invalid plymouth value "{plymouth}". Allowed values: {", ".join(values)}') + sys.exit(1) + + return cls(plymouth) + + @dataclass class BootloaderConfiguration(SubConfig): bootloader: Bootloader uki: bool = False removable: bool = True + plymouth: PlymouthTheme | None = None @override def json(self) -> dict[str, Any]: - return {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable} + data = {'bootloader': self.bootloader.json(), 'uki': self.uki, 'removable': self.removable} + + if self.plymouth is not None: + data['plymouth'] = self.plymouth.value + return data @override def summary(self) -> list[str]: @@ -78,6 +111,8 @@ class BootloaderConfiguration(SubConfig): out.append(tr('UKI enabled')) if self.removable: out.append(tr('Removable')) + if self.plymouth is not None: + out.append(tr('Plymouth "{}"').format(self.plymouth.value)) return out @@ -86,14 +121,16 @@ class BootloaderConfiguration(SubConfig): bootloader = Bootloader.from_arg(config.get('bootloader', ''), skip_boot) uki = config.get('uki', False) removable = config.get('removable', True) - return cls(bootloader=bootloader, uki=uki, removable=removable) + plymouth = PlymouthTheme.from_arg(config.get('plymouth', None)) + return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth) @classmethod def get_default(cls, uefi: bool, skip_boot: bool = False) -> Self: bootloader = Bootloader.get_default(uefi, skip_boot) removable = uefi and bootloader.has_removable_support() uki = uefi and bootloader.has_uki_support() - return cls(bootloader=bootloader, uki=uki, removable=removable) + plymouth = None + return cls(bootloader=bootloader, uki=uki, removable=removable, plymouth=plymouth) def preview(self, uefi: bool) -> str: text = f'{tr("Bootloader")}: {self.bootloader.value}' @@ -112,4 +149,7 @@ class BootloaderConfiguration(SubConfig): removable_string = tr('Disabled') text += f'{tr("Removable")}: {removable_string}' text += '\n' + if self.plymouth is not None: + text += f'{tr("Plymouth")}: {self.plymouth.value}' + text += '\n' return text diff --git a/archinstall/lib/models/plymouth.py b/archinstall/lib/models/plymouth.py deleted file mode 100644 index c2aa69b6..00000000 --- a/archinstall/lib/models/plymouth.py +++ /dev/null @@ -1,57 +0,0 @@ -from dataclasses import dataclass -from enum import Enum -from typing import Self, TypedDict, override - -from archinstall.lib.models.config import SubConfig -from archinstall.lib.translationhandler import tr - - -class PlymouthConfigSerialization(TypedDict): - plymouth: str - - -class PlymouthTheme(Enum): - DISABLED = 'Disabled' - BGRT = 'bgrt' - FADE_IN = 'fade-in' - GLOW = 'glow' - SCRIPT = 'script' - SOLAR = 'solar' - SPINNER = 'spinner' - SPINFINITY = 'spinfinity' - TRIBAR = 'tribar' - TEXT = 'text' - DETAILS = 'details' - - -@dataclass -class PlymouthConfiguration(SubConfig): - plymouth: PlymouthTheme = PlymouthTheme.DISABLED - - @override - def json(self) -> PlymouthConfigSerialization: - return { - 'plymouth': self.plymouth.value, - } - - @classmethod - def default(cls) -> Self: - return cls() - - @classmethod - def parse_arg(cls, arg: PlymouthConfigSerialization) -> Self: - config = cls.default() - - if 'plymouth' in arg: - config.plymouth = PlymouthTheme(arg['plymouth']) - - return config - - @override - def summary(self) -> str: - if self.plymouth == PlymouthTheme.DISABLED: - return tr('Disabled') - return tr('{} Selected').format(self.plymouth.value) - - def preview(self) -> str: - return f'Plymouth: {tr(self.plymouth.value)}' diff --git a/archinstall/lib/plymouth/__init__.py b/archinstall/lib/plymouth/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/archinstall/lib/plymouth/plymouth_handler.py b/archinstall/lib/plymouth/plymouth_handler.py deleted file mode 100644 index 5fd8a55c..00000000 --- a/archinstall/lib/plymouth/plymouth_handler.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import TYPE_CHECKING - -from archinstall.lib.log import debug -from archinstall.lib.models.plymouth import PlymouthConfiguration, PlymouthTheme - -if TYPE_CHECKING: - from archinstall.lib.installer import Installer - - -class PlymouthHandler: - @property - def plymouth_packages(self) -> list[str]: - return [ - 'plymouth', - ] - - @property - def kernel_params(self) -> list[str]: - return [ - 'quiet', - 'splash', - ] - - @property - def plymouth_hook(self) -> str: - return 'plymouth' - - @property - def _hook_anchors(self) -> list[tuple[str, bool]]: - return [ - # hook_name, insert_after - ('encrypt', False), - ('sd-encrypt', False), - ('systemd', True), - ('systemd', True), - ('filesystems', False), - ('keyboard', True), - ] - - def install(self, install_session: Installer, plymouth_config: PlymouthConfiguration) -> None: - debug(f'Installing plymouth with theme: {plymouth_config.plymouth.value}') - - if plymouth_config.plymouth == PlymouthTheme.DISABLED: - debug('No plymouth theme selected, skipping installation.') - return - - install_session.add_additional_packages(self.plymouth_packages) - - self._add_kernel_params(install_session) - self._add_hooks(install_session) - self._set_theme(install_session, plymouth_config.plymouth.value) - self._regenerate_mkinitcpio(install_session) - - debug('Plymouth install completed') - - def _add_kernel_params(self, install_session: Installer) -> None: - debug(f'Adding kernel params for plymouth {self.kernel_params}') - for param in self.kernel_params: - if param not in install_session._kernel_params: - install_session._kernel_params.append(param) - - def _add_hooks(self, install_session: Installer) -> None: - debug('Adding Plymouth hook') - - if self.plymouth_hook in install_session._hooks: - debug(f'{self.plymouth_hook} hook already present') - return - - for hook, insert_after in self._hook_anchors: - try: - index = install_session._hooks.index(hook) - position = index + (1 if insert_after else 0) - install_session._hooks.insert(position, self.plymouth_hook) - debug(f'Plymouth hook inserted {["before", "after"][insert_after]} {hook!r}') - return - except ValueError: - continue - - install_session._hooks.append(self.plymouth_hook) - debug('Plymouth hook appended at end (no anchor hook found)') - - def _set_theme(self, install_session: Installer, theme: str) -> None: - debug(f'Set plymouth theme to {theme}') - install_session.arch_chroot(f'plymouth-set-default-theme {theme}') - - def _regenerate_mkinitcpio(self, install_session: Installer) -> None: - install_session.mkinitcpio(['-P']) diff --git a/archinstall/lib/plymouth/plymouth_menu.py b/archinstall/lib/plymouth/plymouth_menu.py deleted file mode 100644 index ed1d6d94..00000000 --- a/archinstall/lib/plymouth/plymouth_menu.py +++ /dev/null @@ -1,26 +0,0 @@ -from archinstall.lib.menu.helpers import Selection -from archinstall.lib.models.plymouth import PlymouthConfiguration, PlymouthTheme -from archinstall.lib.translationhandler import tr -from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.result import ResultType - - -async def select_plymouth_theme(preset: PlymouthConfiguration | None = None) -> PlymouthConfiguration | None: - items = [MenuItem(t.value, value=t) for t in PlymouthTheme] - group = MenuItemGroup(items, sort_items=False) - group.set_selected_by_value(preset.plymouth if preset else PlymouthTheme.DISABLED) - - result = await Selection[PlymouthTheme]( - group, - header=tr('Select Plymouth theme'), - allow_reset=True, - allow_skip=True, - ).show() - - match result.type_: - case ResultType.Skip: - return preset - case ResultType.Reset: - return None - case ResultType.Selection: - return PlymouthConfiguration(result.get_value()) diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 2eb7cf1b..efc22fc4 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -20,7 +20,6 @@ from archinstall.lib.models.device import DiskLayoutType, EncryptionType from archinstall.lib.models.users import User from archinstall.lib.network.network_handler import install_network_config from archinstall.lib.packages.util import check_version_upgrade -from archinstall.lib.plymouth.plymouth_handler import PlymouthHandler from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.lib.translationhandler import tr from archinstall.tui.components import tui @@ -115,14 +114,12 @@ def perform_installation( if config.swap and config.swap.enabled: installation.setup_swap(algo=config.swap.algorithm) - if config.plymouth_config: - PlymouthHandler().install(installation, config.plymouth_config) - if config.bootloader_config and config.bootloader_config.bootloader != Bootloader.NO_BOOTLOADER: installation.add_bootloader( config.bootloader_config.bootloader, config.bootloader_config.uki, config.bootloader_config.removable, + config.bootloader_config.plymouth, ) if config.network_config: