Add optional "Additional fonts" selection to Applications menu (#4420)

* Add Fonts application with multi-select for emoji and CJK packages

* Rename to Additional fonts and add package descriptions

* Add noto-fonts to font package selection

* Move font descriptions into FontPackage enum method

* ci: trigger tests

* Add ttf-liberation and ttf-dejavu to font selection
This commit is contained in:
Softer 2026-04-18 05:02:47 +03:00 committed by GitHub
parent 9fdd7eb12e
commit 4fcef35af0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 142 additions and 0 deletions

View File

@ -0,0 +1,14 @@
from typing import TYPE_CHECKING
from archinstall.lib.models.application import FontsConfiguration
from archinstall.lib.output import debug
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class FontsApp:
def install(self, install_session: Installer, fonts_config: FontsConfiguration) -> None:
packages = [f.value for f in fonts_config.fonts]
debug(f'Installing fonts: {packages}')
install_session.add_additional_packages(packages)

View File

@ -3,6 +3,7 @@ from typing import TYPE_CHECKING
from archinstall.applications.audio import AudioApp from archinstall.applications.audio import AudioApp
from archinstall.applications.bluetooth import BluetoothApp from archinstall.applications.bluetooth import BluetoothApp
from archinstall.applications.firewall import FirewallApp from archinstall.applications.firewall import FirewallApp
from archinstall.applications.fonts import FontsApp
from archinstall.applications.power_management import PowerManagementApp from archinstall.applications.power_management import PowerManagementApp
from archinstall.applications.print_service import PrintServiceApp from archinstall.applications.print_service import PrintServiceApp
from archinstall.lib.models import Audio from archinstall.lib.models import Audio
@ -42,3 +43,9 @@ class ApplicationHandler:
install_session, install_session,
app_config.firewall_config, app_config.firewall_config,
) )
if app_config.fonts_config:
FontsApp().install(
install_session,
app_config.fonts_config,
)

View File

@ -10,6 +10,8 @@ from archinstall.lib.models.application import (
BluetoothConfiguration, BluetoothConfiguration,
Firewall, Firewall,
FirewallConfiguration, FirewallConfiguration,
FontPackage,
FontsConfiguration,
PowerManagement, PowerManagement,
PowerManagementConfiguration, PowerManagementConfiguration,
PrintServiceConfiguration, PrintServiceConfiguration,
@ -77,6 +79,13 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
preview_action=self._prev_firewall, preview_action=self._prev_firewall,
key='firewall_config', key='firewall_config',
), ),
MenuItem(
text=tr('Additional fonts'),
action=select_fonts,
value=self._app_config.fonts_config,
preview_action=self._prev_fonts,
key='fonts_config',
),
] ]
def _prev_power_management(self, item: MenuItem) -> str | None: def _prev_power_management(self, item: MenuItem) -> str | None:
@ -115,6 +124,13 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
return f'{tr("Firewall")}: {config.firewall.value}' return f'{tr("Firewall")}: {config.firewall.value}'
return None return None
def _prev_fonts(self, item: MenuItem) -> str | None:
if item.value is not None:
config: FontsConfiguration = item.value
packages = ', '.join(f.value for f in config.fonts)
return f'{tr("Additional fonts")}: {packages}'
return None
async def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None: async def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None:
group = MenuItemGroup.from_enum(PowerManagement) group = MenuItemGroup.from_enum(PowerManagement)
@ -217,3 +233,31 @@ async def select_firewall(preset: FirewallConfiguration | None = None) -> Firewa
return FirewallConfiguration(firewall=result.get_value()) return FirewallConfiguration(firewall=result.get_value())
case ResultType.Reset: case ResultType.Reset:
return None return None
async def select_fonts(preset: FontsConfiguration | None = None) -> FontsConfiguration | None:
items = [MenuItem(f'{f.value} ({f.description()})', value=f) for f in FontPackage]
group = MenuItemGroup(items)
if preset:
for f in preset.fonts:
group.set_selected_by_value(f)
result = await Selection[FontPackage](
group,
header=tr('Select font packages to install'),
allow_skip=True,
allow_reset=True,
multi=True,
).show()
match result.type_:
case ResultType.Skip:
return preset
case ResultType.Selection:
selected = result.get_values()
if selected:
return FontsConfiguration(fonts=selected)
return None
case ResultType.Reset:
return None

View File

@ -2,6 +2,8 @@ from dataclasses import dataclass
from enum import StrEnum, auto from enum import StrEnum, auto
from typing import Any, NotRequired, Self, TypedDict from typing import Any, NotRequired, Self, TypedDict
from archinstall.lib.translationhandler import tr
class PowerManagement(StrEnum): class PowerManagement(StrEnum):
POWER_PROFILES_DAEMON = 'power-profiles-daemon' POWER_PROFILES_DAEMON = 'power-profiles-daemon'
@ -39,6 +41,31 @@ class FirewallConfigSerialization(TypedDict):
firewall: str firewall: str
class FontPackage(StrEnum):
NOTO = 'noto-fonts'
EMOJI = 'noto-fonts-emoji'
CJK = 'noto-fonts-cjk'
LIBERATION = 'ttf-liberation'
DEJAVU = 'ttf-dejavu'
def description(self) -> str:
match self:
case FontPackage.NOTO:
return tr('Unicode font coverage for most languages')
case FontPackage.EMOJI:
return tr('color emoji for browsers and apps')
case FontPackage.CJK:
return tr('Chinese, Japanese, Korean characters')
case FontPackage.LIBERATION:
return tr('Arial/Times/Courier replacement, Cyrillic support for Steam/games')
case FontPackage.DEJAVU:
return tr('wide Unicode coverage, good fallback font')
class FontsConfigSerialization(TypedDict):
fonts: list[str]
class ZramAlgorithm(StrEnum): class ZramAlgorithm(StrEnum):
ZSTD = auto() ZSTD = auto()
LZO_RLE = 'lzo-rle' LZO_RLE = 'lzo-rle'
@ -53,6 +80,7 @@ class ApplicationSerialization(TypedDict):
power_management_config: NotRequired[PowerManagementConfigSerialization] power_management_config: NotRequired[PowerManagementConfigSerialization]
print_service_config: NotRequired[PrintServiceConfigSerialization] print_service_config: NotRequired[PrintServiceConfigSerialization]
firewall_config: NotRequired[FirewallConfigSerialization] firewall_config: NotRequired[FirewallConfigSerialization]
fonts_config: NotRequired[FontsConfigSerialization]
@dataclass @dataclass
@ -127,6 +155,18 @@ class FirewallConfiguration:
) )
@dataclass
class FontsConfiguration:
fonts: list[FontPackage]
def json(self) -> FontsConfigSerialization:
return {'fonts': [f.value for f in self.fonts]}
@classmethod
def parse_arg(cls, arg: FontsConfigSerialization) -> Self:
return cls(fonts=[FontPackage(f) for f in arg['fonts']])
@dataclass(frozen=True) @dataclass(frozen=True)
class ZramConfiguration: class ZramConfiguration:
enabled: bool enabled: bool
@ -149,6 +189,7 @@ class ApplicationConfiguration:
power_management_config: PowerManagementConfiguration | None = None power_management_config: PowerManagementConfiguration | None = None
print_service_config: PrintServiceConfiguration | None = None print_service_config: PrintServiceConfiguration | None = None
firewall_config: FirewallConfiguration | None = None firewall_config: FirewallConfiguration | None = None
fonts_config: FontsConfiguration | None = None
@classmethod @classmethod
def parse_arg( def parse_arg(
@ -177,6 +218,9 @@ class ApplicationConfiguration:
if args and (firewall_config := args.get('firewall_config')) is not None: if args and (firewall_config := args.get('firewall_config')) is not None:
app_config.firewall_config = FirewallConfiguration.parse_arg(firewall_config) app_config.firewall_config = FirewallConfiguration.parse_arg(firewall_config)
if args and (fonts_config := args.get('fonts_config')) is not None:
app_config.fonts_config = FontsConfiguration.parse_arg(fonts_config)
return app_config return app_config
def json(self) -> ApplicationSerialization: def json(self) -> ApplicationSerialization:
@ -197,4 +241,7 @@ class ApplicationConfiguration:
if self.firewall_config: if self.firewall_config:
config['firewall_config'] = self.firewall_config.json() config['firewall_config'] = self.firewall_config.json()
if self.fonts_config:
config['fonts_config'] = self.fonts_config.json()
return config return config

View File

@ -2070,6 +2070,21 @@ msgstr ""
msgid "Firewall" msgid "Firewall"
msgstr "" msgstr ""
msgid "Additional fonts"
msgstr ""
msgid "Select font packages to install"
msgstr ""
msgid "Unicode font coverage for most languages"
msgstr ""
msgid "color emoji for browsers and apps"
msgstr ""
msgid "Chinese, Japanese, Korean characters"
msgstr ""
msgid "Select audio configuration" msgid "Select audio configuration"
msgstr "" msgstr ""

View File

@ -2011,6 +2011,21 @@ msgstr "Використовувати NetworkManager (з iwd)"
msgid "Firewall" msgid "Firewall"
msgstr "Брандмауер" 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" msgid "Select audio configuration"
msgstr "Оберіть конфігурацію аудіо" msgstr "Оберіть конфігурацію аудіо"