diff --git a/archinstall/applications/audio.py b/archinstall/applications/audio.py new file mode 100644 index 0000000000..82f7af72a7 --- /dev/null +++ b/archinstall/applications/audio.py @@ -0,0 +1,80 @@ +from typing import TYPE_CHECKING + +from archinstall.lib.hardware import SysInfo +from archinstall.lib.models.application import Audio, AudioConfiguration +from archinstall.lib.models.users import User +from archinstall.lib.output import debug + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class AudioApp: + @property + def pulseaudio_packages(self) -> list[str]: + return [ + 'pulseaudio', + ] + + @property + def pipewire_packages(self) -> list[str]: + return [ + 'pipewire', + 'pipewire-alsa', + 'pipewire-jack', + 'pipewire-pulse', + 'gst-plugin-pipewire', + 'libpulse', + 'wireplumber', + ] + + def _enable_pipewire( + self, + install_session: 'Installer', + users: list['User'] | None = None, + ) -> None: + if users is None: + return + + for user in users: + # Create the full path for enabling the pipewire systemd items + service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants' + service_dir.mkdir(parents=True, exist_ok=True) + + # Set ownership of the entire user catalogue + install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}') + + # symlink in the correct pipewire systemd items + install_session.arch_chroot( + f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service', + run_as=user.username, + ) + install_session.arch_chroot( + f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket', + run_as=user.username, + ) + + def install( + self, + install_session: 'Installer', + audio_config: AudioConfiguration, + users: list[User] | None = None, + ) -> None: + debug(f'Installing audio server: {audio_config.audio.value}') + + if audio_config.audio == Audio.NO_AUDIO: + debug('No audio server selected, skipping installation.') + return + + if SysInfo.requires_sof_fw(): + install_session.add_additional_packages('sof-firmware') + + if SysInfo.requires_alsa_fw(): + install_session.add_additional_packages('alsa-firmware') + + match audio_config.audio: + case Audio.PIPEWIRE: + install_session.add_additional_packages(self.pipewire_packages) + self._enable_pipewire(install_session, users) + case Audio.PULSEAUDIO: + install_session.add_additional_packages(self.pulseaudio_packages) diff --git a/archinstall/applications/bluetooth.py b/archinstall/applications/bluetooth.py index 286b825089..bf05611cf5 100644 --- a/archinstall/applications/bluetooth.py +++ b/archinstall/applications/bluetooth.py @@ -6,7 +6,7 @@ from archinstall.lib.installer import Installer -class Bluetooth: +class BluetoothApp: @property def packages(self) -> list[str]: return [ diff --git a/archinstall/default_profiles/applications/__init__.py b/archinstall/default_profiles/applications/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/archinstall/default_profiles/applications/pipewire.py b/archinstall/default_profiles/applications/pipewire.py deleted file mode 100644 index 52c17038a4..0000000000 --- a/archinstall/default_profiles/applications/pipewire.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import TYPE_CHECKING, override - -from archinstall.default_profiles.profile import Profile, ProfileType - -if TYPE_CHECKING: - from archinstall.lib.installer import Installer - from archinstall.lib.models.users import User - - -class PipewireProfile(Profile): - def __init__(self) -> None: - super().__init__('Pipewire', ProfileType.Application) - - @property - @override - def packages(self) -> list[str]: - return [ - 'pipewire', - 'pipewire-alsa', - 'pipewire-jack', - 'pipewire-pulse', - 'gst-plugin-pipewire', - 'libpulse', - 'wireplumber', - ] - - def _enable_pipewire_for_all(self, install_session: 'Installer') -> None: - from archinstall.lib.args import arch_config_handler - - users: list[User] | None = arch_config_handler.config.users - - if users is None: - return - - for user in users: - # Create the full path for enabling the pipewire systemd items - service_dir = install_session.target / 'home' / user.username / '.config' / 'systemd' / 'user' / 'default.target.wants' - service_dir.mkdir(parents=True, exist_ok=True) - - # Set ownership of the entire user catalogue - install_session.arch_chroot(f'chown -R {user.username}:{user.username} /home/{user.username}') - - # symlink in the correct pipewire systemd items - install_session.arch_chroot( - f'ln -sf /usr/lib/systemd/user/pipewire-pulse.service /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.service', - run_as=user.username, - ) - install_session.arch_chroot( - f'ln -sf /usr/lib/systemd/user/pipewire-pulse.socket /home/{user.username}/.config/systemd/user/default.target.wants/pipewire-pulse.socket', - run_as=user.username, - ) - - @override - def install(self, install_session: 'Installer') -> None: - super().install(install_session) - install_session.add_additional_packages(self.packages) - self._enable_pipewire_for_all(install_session) diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index 7d124ea882..f488386180 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -1,7 +1,9 @@ from typing import TYPE_CHECKING -from archinstall.applications.bluetooth import Bluetooth +from archinstall.applications.audio import AudioApp +from archinstall.applications.bluetooth import BluetoothApp from archinstall.lib.models.application import ApplicationConfiguration +from archinstall.lib.models.users import User if TYPE_CHECKING: from archinstall.lib.installer import Installer @@ -11,13 +13,16 @@ class ApplicationHandler: def __init__(self) -> None: pass - def install_applications( - self, - install_session: 'Installer', - app_config: ApplicationConfiguration, - ) -> None: + def install_applications(self, install_session: 'Installer', app_config: ApplicationConfiguration, users: list['User'] | None = None) -> None: if app_config.bluetooth_config: - Bluetooth().install(install_session) + BluetoothApp().install(install_session) + + if app_config.audio_config: + AudioApp().install( + install_session, + app_config.audio_config, + users, + ) application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index deb31c018e..7c07b84c87 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -1,11 +1,12 @@ from typing import override from archinstall.lib.menu.abstract_menu import AbstractSubMenu -from archinstall.lib.models.application import ApplicationConfiguration, BluetoothConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.menu_item import MenuItem, MenuItemGroup -from archinstall.tui.types import Alignment, Orientation +from archinstall.tui.result import ResultType +from archinstall.tui.types import Alignment, FrameProperties, Orientation class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]): @@ -41,6 +42,12 @@ def _define_menu_options(self) -> list[MenuItem]: preview_action=self._prev_bluetooth, key='bluetooth_config', ), + MenuItem( + text=tr('Audio'), + action=select_audio, + preview_action=self._prev_audio, + key='audio_config', + ), ] def _prev_bluetooth(self, item: MenuItem) -> str | None: @@ -52,6 +59,12 @@ def _prev_bluetooth(self, item: MenuItem) -> str | None: return output return None + def _prev_audio(self, item: MenuItem) -> str | None: + if item.value is not None: + config: AudioConfiguration = item.value + return f'{tr("Audio")}: {config.audio.value}' + return None + def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None: group = MenuItemGroup.yes_no() @@ -74,3 +87,26 @@ def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfigur enabled = result.item() == MenuItem.yes() return BluetoothConfiguration(enabled) + + +def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None: + items = [MenuItem(a.value, value=a) for a in Audio] + group = MenuItemGroup(items) + + if preset: + group.set_focus_by_value(preset.audio) + + result = SelectMenu[Audio]( + group, + allow_skip=True, + alignment=Alignment.CENTER, + frame=FrameProperties.min(tr('Audio')), + ).run() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Selection: + return AudioConfiguration(audio=result.get_value()) + case ResultType.Reset: + raise ValueError('Unhandled result type') diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index f9f28dcea2..2aed561193 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -14,7 +14,6 @@ from archinstall.lib.crypt import decrypt from archinstall.lib.models.application import ApplicationConfiguration -from archinstall.lib.models.audio_configuration import AudioConfiguration from archinstall.lib.models.bootloader import Bootloader from archinstall.lib.models.device_model import DiskEncryption, DiskLayoutConfiguration from archinstall.lib.models.locale import LocaleConfiguration @@ -63,8 +62,7 @@ class ArchConfig: network_config: NetworkConfiguration | None = None bootloader: Bootloader = field(default=Bootloader.get_default()) uki: bool = False - audio_config: AudioConfiguration | None = None - application_config: ApplicationConfiguration | None = None + app_config: ApplicationConfiguration | None = None hostname: str = 'archlinux' kernels: list[str] = field(default_factory=lambda: ['linux']) ntp: bool = True @@ -106,8 +104,7 @@ def safe_json(self) -> dict[str, Any]: 'services': self.services, 'custom_commands': self.custom_commands, 'bootloader': self.bootloader.json(), - 'audio_config': self.audio_config.json() if self.audio_config else None, - 'app_config': self.application_config.json() if self.application_config else None, + 'app_config': self.app_config.json() if self.app_config else None, } if self.locale_config: @@ -184,11 +181,12 @@ def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig': if args_config.get('uki') and not arch_config.bootloader.has_uki_support(): arch_config.uki = False - if audio_config := args_config.get('audio_config', None): - arch_config.audio_config = AudioConfiguration.parse_arg(audio_config) + # deprecated: backwards compatibility + audio_config_args = args_config.get('audio_config', None) + app_config_args = args_config.get('app_config', None) - if app_config := args_config.get('app_config', None): - arch_config.application_config = ApplicationConfiguration.parse_arg(app_config) + if audio_config_args is not None or app_config_args is not None: + arch_config.app_config = ApplicationConfiguration.parse_arg(app_config_args, audio_config_args) if hostname := args_config.get('hostname', ''): arch_config.hostname = hostname diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 34159fd32f..87cf13a1f2 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -16,7 +16,6 @@ add_number_of_parallel_downloads, ask_additional_packages_to_install, ask_for_a_timezone, - ask_for_audio_selection, ask_hostname, ask_ntp, ) @@ -26,7 +25,6 @@ from .locale.locale_menu import LocaleMenu from .menu.abstract_menu import CONFIG_KEY, AbstractMenu from .mirrors import MirrorMenu -from .models.audio_configuration import AudioConfiguration from .models.bootloader import Bootloader from .models.locale import LocaleConfiguration from .models.mirrors import MirrorConfiguration @@ -128,12 +126,6 @@ def _get_menu_options(self) -> list[MenuItem]: preview_action=self._prev_profile, key='profile_config', ), - MenuItem( - text=tr('Audio'), - action=ask_for_audio_selection, - preview_action=self._prev_audio, - key='audio_config', - ), MenuItem( text=tr('Applications'), action=self._select_applications, @@ -311,7 +303,13 @@ def _prev_applications(self, item: MenuItem) -> str | None: if app_config.bluetooth_config: output += f'{tr("Bluetooth")}: ' - output += tr('Enabled') if app_config.bluetooth_config.enabled else tr('Disabled') + '\n' + output += tr('Enabled') if app_config.bluetooth_config.enabled else tr('Disabled') + output += '\n' + + if app_config.audio_config: + audio_config = app_config.audio_config + output += f'{tr("Audio")}: {audio_config.audio.value}' + output += '\n' return output @@ -378,12 +376,6 @@ def _prev_root_pwd(self, item: MenuItem) -> str | None: return f'{tr("Root password")}: {password.hidden()}' return None - def _prev_audio(self, item: MenuItem) -> str | None: - if item.value is not None: - config: AudioConfiguration = item.value - return f'{tr("Audio")}: {config.audio.value}' - return None - def _prev_parallel_dw(self, item: MenuItem) -> str | None: if item.value is not None: return f'{tr("Parallel Downloads")}: {item.value}' diff --git a/archinstall/lib/interactions/__init__.py b/archinstall/lib/interactions/__init__.py index b18ed7bd96..09b8347835 100644 --- a/archinstall/lib/interactions/__init__.py +++ b/archinstall/lib/interactions/__init__.py @@ -10,7 +10,6 @@ add_number_of_parallel_downloads, ask_additional_packages_to_install, ask_for_a_timezone, - ask_for_audio_selection, ask_hostname, ask_ntp, select_archinstall_language, @@ -26,7 +25,6 @@ 'ask_additional_packages_to_install', 'ask_for_a_timezone', 'ask_for_additional_users', - 'ask_for_audio_selection', 'ask_for_bootloader', 'ask_for_swap', 'ask_for_uki', diff --git a/archinstall/lib/interactions/general_conf.py b/archinstall/lib/interactions/general_conf.py index 8f360702f0..fed77b5e93 100644 --- a/archinstall/lib/interactions/general_conf.py +++ b/archinstall/lib/interactions/general_conf.py @@ -13,7 +13,6 @@ from archinstall.tui.types import Alignment, FrameProperties, Orientation, PreviewStyle from ..locale.utils import list_timezones -from ..models.audio_configuration import Audio, AudioConfiguration from ..models.packages import AvailablePackage, PackageGroup from ..output import warn from ..translationhandler import Language @@ -102,29 +101,6 @@ def ask_for_a_timezone(preset: str | None = None) -> str | None: return result.get_value() -def ask_for_audio_selection(preset: AudioConfiguration | None = None) -> AudioConfiguration | None: - items = [MenuItem(a.value, value=a) for a in Audio] - group = MenuItemGroup(items) - - if preset: - group.set_focus_by_value(preset.audio) - - result = SelectMenu[Audio]( - group, - allow_skip=True, - alignment=Alignment.CENTER, - frame=FrameProperties.min(tr('Audio')), - ).run() - - match result.type_: - case ResultType.Skip: - return preset - case ResultType.Selection: - return AudioConfiguration(audio=result.get_value()) - case ResultType.Reset: - raise ValueError('Unhandled result type') - - def select_language(preset: str | None = None) -> str | None: from ..locale.locale_menu import select_kb_layout diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py index 00c748902f..74ca3b67cc 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -1,4 +1,9 @@ -from .audio_configuration import Audio, AudioConfiguration +from .application import ( + ApplicationConfiguration, + Audio, + AudioConfiguration, + BluetoothConfiguration, +) from .bootloader import Bootloader from .device_model import ( BDevice, @@ -35,9 +40,11 @@ from .users import PasswordStrength, User __all__ = [ + 'ApplicationConfiguration', 'Audio', 'AudioConfiguration', 'BDevice', + 'BluetoothConfiguration', 'Bootloader', 'CustomRepository', 'DeviceGeometry', diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index d2fc34f161..eeced89881 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -1,13 +1,41 @@ from dataclasses import dataclass -from typing import NotRequired, TypedDict +from enum import StrEnum, auto +from typing import Any, NotRequired, TypedDict class BluetoothConfigSerialization(TypedDict): enabled: bool +class Audio(StrEnum): + NO_AUDIO = 'No audio server' + PIPEWIRE = auto() + PULSEAUDIO = auto() + + +class AudioConfigSerialization(TypedDict): + audio: str + + class ApplicationSerialization(TypedDict): bluetooth_config: NotRequired[BluetoothConfigSerialization] + audio_config: NotRequired[AudioConfigSerialization] + + +@dataclass +class AudioConfiguration: + audio: Audio + + def json(self) -> AudioConfigSerialization: + return { + 'audio': self.audio.value, + } + + @staticmethod + def parse_arg(arg: dict[str, Any]) -> 'AudioConfiguration': + return AudioConfiguration( + Audio(arg['audio']), + ) @dataclass @@ -18,23 +46,33 @@ def json(self) -> BluetoothConfigSerialization: return {'enabled': self.enabled} @staticmethod - def parse_arg(arg: BluetoothConfigSerialization) -> 'BluetoothConfiguration': + def parse_arg(arg: dict[str, Any]) -> 'BluetoothConfiguration': return BluetoothConfiguration(arg['enabled']) @dataclass class ApplicationConfiguration: bluetooth_config: BluetoothConfiguration | None = None + audio_config: AudioConfiguration | None = None @staticmethod - def parse_arg(args: ApplicationSerialization) -> 'ApplicationConfiguration': - bluetooth_config: BluetoothConfiguration | None = None - if 'bluetooth_config' in args: - bluetooth_config = BluetoothConfiguration.parse_arg(args['bluetooth_config']) + def parse_arg( + args: dict[str, Any] | None = None, + old_audio_config: dict[str, Any] | None = None, + ) -> 'ApplicationConfiguration': + app_config = ApplicationConfiguration() - return ApplicationConfiguration( - bluetooth_config=bluetooth_config, - ) + if args and (bluetooth_config := args.get('bluetooth_config')) is not None: + app_config.bluetooth_config = BluetoothConfiguration.parse_arg(bluetooth_config) + + # deprecated: backwards compatibility + if old_audio_config is not None: + app_config.audio_config = AudioConfiguration.parse_arg(old_audio_config) + + if args and (audio_config := args.get('audio_config')) is not None: + app_config.audio_config = AudioConfiguration.parse_arg(audio_config) + + return app_config def json(self) -> ApplicationSerialization: config: ApplicationSerialization = {} @@ -42,4 +80,7 @@ def json(self) -> ApplicationSerialization: if self.bluetooth_config: config['bluetooth_config'] = self.bluetooth_config.json() + if self.audio_config: + config['audio_config'] = self.audio_config.json() + return config diff --git a/archinstall/lib/models/audio_configuration.py b/archinstall/lib/models/audio_configuration.py deleted file mode 100644 index a3c804c29a..0000000000 --- a/archinstall/lib/models/audio_configuration.py +++ /dev/null @@ -1,52 +0,0 @@ -from dataclasses import dataclass -from enum import StrEnum, auto -from typing import TYPE_CHECKING - -from ..hardware import SysInfo -from ..output import info - -if TYPE_CHECKING: - from archinstall.lib.installer import Installer - - -class Audio(StrEnum): - NO_AUDIO = 'No audio server' - PIPEWIRE = auto() - PULSEAUDIO = auto() - - -@dataclass -class AudioConfiguration: - audio: Audio - - def json(self) -> dict[str, str]: - return { - 'audio': self.audio.value, - } - - @staticmethod - def parse_arg(arg: dict[str, str]) -> 'AudioConfiguration': - return AudioConfiguration( - Audio(arg['audio']), - ) - - def install_audio_config( - self, - installation: 'Installer', - ) -> None: - info(f'Installing audio server: {self.audio.name}') - - from ...default_profiles.applications.pipewire import PipewireProfile - - match self.audio: - case Audio.PIPEWIRE: - PipewireProfile().install(installation) - case Audio.PULSEAUDIO: - installation.add_additional_packages('pulseaudio') - - if self.audio != Audio.NO_AUDIO: - if SysInfo.requires_sof_fw(): - installation.add_additional_packages('sof-firmware') - - if SysInfo.requires_alsa_fw(): - installation.add_additional_packages('alsa-firmware') diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index d0025da32e..e2266e892b 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -116,19 +116,13 @@ def perform_installation(mountpoint: Path) -> None: if users := config.users: installation.create_users(users) - audio_config = config.audio_config - if audio_config: - audio_config.install_audio_config(installation) - else: - info('No audio server will be installed') - if config.packages and config.packages[0] != '': installation.add_additional_packages(config.packages) if profile_config := config.profile_config: profile_handler.install_profile_config(installation, profile_config) - if app_config := config.application_config: + if app_config := config.app_config: application_handler.install_applications(installation, app_config) if timezone := config.timezone: diff --git a/tests/conftest.py b/tests/conftest.py index 6e94f06cda..819c839716 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,11 @@ def deprecated_mirror_config() -> Path: return Path(__file__).parent / 'data' / 'test_deprecated_mirror_config.json' +@pytest.fixture(scope='session') +def deprecated_audio_config() -> Path: + return Path(__file__).parent / 'data' / 'test_deprecated_audio_config.json' + + @pytest.fixture(scope='session') def mirrorlist_no_country_fixture() -> Path: return Path(__file__).parent / 'data' / 'mirrorlists' / 'test_no_country' diff --git a/tests/data/test_config.json b/tests/data/test_config.json index f7502e7391..b7fef9e0af 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -3,6 +3,9 @@ "app_config": { "bluetooth_config": { "enabled": true + }, + "audio_config": { + "audio": "pipewire" } }, "audio_config": { diff --git a/tests/data/test_deprecated_audio_config.json b/tests/data/test_deprecated_audio_config.json new file mode 100644 index 0000000000..6e02c385e5 --- /dev/null +++ b/tests/data/test_deprecated_audio_config.json @@ -0,0 +1,5 @@ +{ + "audio_config": { + "audio": "pipewire" + } +} diff --git a/tests/test_args.py b/tests/test_args.py index 81cd1afc95..329ca0c602 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -6,8 +6,7 @@ from archinstall.default_profiles.profile import GreeterType from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments from archinstall.lib.hardware import GfxDriver -from archinstall.lib.models.application import ApplicationConfiguration, BluetoothConfiguration -from archinstall.lib.models.audio_configuration import Audio, AudioConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration from archinstall.lib.models.bootloader import Bootloader from archinstall.lib.models.device_model import DiskLayoutConfiguration, DiskLayoutType from archinstall.lib.models.locale import LocaleConfiguration @@ -128,8 +127,9 @@ def test_config_file_parsing( assert arch_config == ArchConfig( version='3.0.2', - application_config=ApplicationConfiguration( + app_config=ApplicationConfiguration( bluetooth_config=BluetoothConfiguration(enabled=True), + audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), ), locale_config=LocaleConfiguration( kb_layout='us', @@ -199,7 +199,6 @@ def test_config_file_parsing( ), bootloader=Bootloader.Systemd, uki=False, - audio_config=AudioConfiguration(Audio.PIPEWIRE), hostname='archy', kernels=['linux-zen'], ntp=True, @@ -285,6 +284,27 @@ def test_deprecated_creds_config_parsing( ] +def test_deprecated_audio_config_parsing( + monkeypatch: MonkeyPatch, + deprecated_audio_config: Path, +) -> None: + monkeypatch.setattr( + 'sys.argv', + [ + 'archinstall', + '--config', + str(deprecated_audio_config), + ], + ) + + handler = ArchConfigHandler() + arch_config = handler.config + + assert arch_config.app_config == ApplicationConfiguration( + audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), + ) + + def test_encrypted_creds_with_arg( monkeypatch: MonkeyPatch, encrypted_creds_fixture: Path,