From 1f66848b22f99e2fe1d91ec20586bc568bf80d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Victor=20Ribeiro=20Silva?= Date: Sun, 24 May 2026 23:10:33 -0300 Subject: [PATCH] feat: add plymouth config --- archinstall/lib/args.py | 11 +++ archinstall/lib/global_menu.py | 18 ++++ archinstall/lib/models/__init__.py | 3 + archinstall/lib/models/plymouth.py | 57 +++++++++++++ archinstall/lib/plymouth/__init__.py | 0 archinstall/lib/plymouth/plymouth_handler.py | 87 ++++++++++++++++++++ archinstall/lib/plymouth/plymouth_menu.py | 26 ++++++ archinstall/scripts/guided.py | 4 + 8 files changed, 206 insertions(+) create mode 100644 archinstall/lib/models/plymouth.py create mode 100644 archinstall/lib/plymouth/__init__.py create mode 100644 archinstall/lib/plymouth/plymouth_handler.py create mode 100644 archinstall/lib/plymouth/plymouth_menu.py diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index e3729504..97dc019d 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -16,6 +16,7 @@ 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 @@ -88,6 +89,7 @@ class ArchConfigType(StrEnum): SERVICES = 'services' PACKAGES = 'packages' PACMAN_CONFIG = 'pacman_config' + PLYMOUTH_CONFIG = 'plymouth_config' CUSTOM_COMMANDS = 'custom_commands' def text(self) -> str: @@ -130,6 +132,8 @@ 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: @@ -159,6 +163,7 @@ 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) @@ -240,6 +245,9 @@ 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 @@ -336,6 +344,9 @@ 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/global_menu.py b/archinstall/lib/global_menu.py index 45a0e3b4..cc62ae9c 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -25,11 +25,13 @@ 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 @@ -150,6 +152,13 @@ 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, @@ -432,6 +441,15 @@ 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/models/__init__.py b/archinstall/lib/models/__init__.py index 5f808c96..3f446fd9 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -30,6 +30,7 @@ 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 @@ -69,6 +70,8 @@ __all__ = [ 'PartitionTable', 'PartitionType', 'PasswordStrength', + 'PlymouthConfiguration', + 'PlymouthTheme', 'PrintServiceConfiguration', 'ProfileConfiguration', 'Repository', diff --git a/archinstall/lib/models/plymouth.py b/archinstall/lib/models/plymouth.py new file mode 100644 index 00000000..c2aa69b6 --- /dev/null +++ b/archinstall/lib/models/plymouth.py @@ -0,0 +1,57 @@ +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 new file mode 100644 index 00000000..e69de29b diff --git a/archinstall/lib/plymouth/plymouth_handler.py b/archinstall/lib/plymouth/plymouth_handler.py new file mode 100644 index 00000000..5fd8a55c --- /dev/null +++ b/archinstall/lib/plymouth/plymouth_handler.py @@ -0,0 +1,87 @@ +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 new file mode 100644 index 00000000..ed1d6d94 --- /dev/null +++ b/archinstall/lib/plymouth/plymouth_menu.py @@ -0,0 +1,26 @@ +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 6f484910..2eb7cf1b 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -20,6 +20,7 @@ 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 @@ -114,6 +115,9 @@ 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,