refactor: move plymouth to bootleader menu

This commit is contained in:
Mário Victor Ribeiro Silva 2026-05-25 12:57:48 -03:00
parent 1f66848b22
commit db71fb2735
11 changed files with 109 additions and 215 deletions

View File

@ -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):

View File

@ -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())

View File

@ -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)

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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)}'

View File

@ -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'])

View File

@ -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())

View File

@ -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: