diff --git a/archinstall/lib/bootloader/bootloader_menu.py b/archinstall/lib/bootloader/bootloader_menu.py index d32a5a2132..64ad4c8d6f 100644 --- a/archinstall/lib/bootloader/bootloader_menu.py +++ b/archinstall/lib/bootloader/bootloader_menu.py @@ -3,7 +3,7 @@ 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 @@ def _define_menu_options(self) -> list[MenuItem]: 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 @@ def _prev_removable(self, item: MenuItem) -> str | None: 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 @@ async def _select_bootloader(self, preset: Bootloader | None) -> Bootloader | No 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/installer.py b/archinstall/lib/installer.py index bf7d91ba31..f53dcda458 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -32,7 +32,7 @@ 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 @@ def _add_refind_bootloader( 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 @@ def _config_uki( 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 @@ def add_bootloader( :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 @@ def add_bootloader( 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/bootloader.py b/archinstall/lib/models/bootloader.py index bd6bce7ed1..040b6746c7 100644 --- a/archinstall/lib/models/bootloader.py +++ b/archinstall/lib/models/bootloader.py @@ -60,15 +60,48 @@ def from_arg(cls, bootloader: str, skip_boot: bool) -> Self: 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 @@ def summary(self) -> list[str]: 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 @@ def parse_arg(cls, config: dict[str, Any], skip_boot: bool) -> Self: 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 @@ def preview(self, uefi: bool) -> str: 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/scripts/guided.py b/archinstall/scripts/guided.py index 6f484910de..efc22fc4c9 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -119,6 +119,7 @@ def perform_installation( config.bootloader_config.bootloader, config.bootloader_config.uki, config.bootloader_config.removable, + config.bootloader_config.plymouth, ) if config.network_config: