From 6d2c86acbbd6f9426ac3a4a1bf739e3cc67a2bea Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 17 Jun 2025 20:31:54 +1000 Subject: [PATCH 1/2] Add bluetooth option --- archinstall/applications/bluetooth.py | 26 +++++++ .../default_profiles/desktops/cinnamon.py | 2 - archinstall/lib/applications/__init__.py | 0 .../lib/applications/application_handler.py | 23 ++++++ .../lib/applications/application_menu.py | 74 +++++++++++++++++++ archinstall/lib/args.py | 6 ++ archinstall/lib/global_menu.py | 26 +++++++ archinstall/lib/models/application.py | 45 +++++++++++ archinstall/scripts/guided.py | 4 + tests/data/test_config.json | 5 ++ tests/test_args.py | 4 + 11 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 archinstall/applications/bluetooth.py create mode 100644 archinstall/lib/applications/__init__.py create mode 100644 archinstall/lib/applications/application_handler.py create mode 100644 archinstall/lib/applications/application_menu.py create mode 100644 archinstall/lib/models/application.py diff --git a/archinstall/applications/bluetooth.py b/archinstall/applications/bluetooth.py new file mode 100644 index 0000000000..286b825089 --- /dev/null +++ b/archinstall/applications/bluetooth.py @@ -0,0 +1,26 @@ +from typing import TYPE_CHECKING + +from archinstall.lib.output import debug + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class Bluetooth: + @property + def packages(self) -> list[str]: + return [ + 'bluez', + 'bluez-utils', + ] + + @property + def services(self) -> list[str]: + return [ + 'bluetooth.service', + ] + + def install(self, install_session: 'Installer') -> None: + debug('Installing Bluetooth') + install_session.add_additional_packages(self.packages) + install_session.enable_service(self.services) diff --git a/archinstall/default_profiles/desktops/cinnamon.py b/archinstall/default_profiles/desktops/cinnamon.py index 47d45742e5..797f73fe80 100644 --- a/archinstall/default_profiles/desktops/cinnamon.py +++ b/archinstall/default_profiles/desktops/cinnamon.py @@ -16,8 +16,6 @@ def packages(self) -> list[str]: 'system-config-printer', 'gnome-keyring', 'gnome-terminal', - 'blueman', - 'bluez-utils', 'engrampa', 'gnome-screenshot', 'gvfs-smb', diff --git a/archinstall/lib/applications/__init__.py b/archinstall/lib/applications/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py new file mode 100644 index 0000000000..7d124ea882 --- /dev/null +++ b/archinstall/lib/applications/application_handler.py @@ -0,0 +1,23 @@ +from typing import TYPE_CHECKING + +from archinstall.applications.bluetooth import Bluetooth +from archinstall.lib.models.application import ApplicationConfiguration + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class ApplicationHandler: + def __init__(self) -> None: + pass + + def install_applications( + self, + install_session: 'Installer', + app_config: ApplicationConfiguration, + ) -> None: + if app_config.bluetooth_config: + Bluetooth().install(install_session) + + +application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py new file mode 100644 index 0000000000..7fdb3794ff --- /dev/null +++ b/archinstall/lib/applications/application_menu.py @@ -0,0 +1,74 @@ +from typing import override + +from archinstall.lib.menu.abstract_menu import AbstractSubMenu +from archinstall.lib.models.application import ApplicationConfiguration, 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 + + +class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]): + def __init__( + self, + preset: ApplicationConfiguration | None = None, + ): + if preset: + self._app_config = preset + else: + self._app_config = ApplicationConfiguration() + + menu_optioons = self._define_menu_options() + self._item_group = MenuItemGroup(menu_optioons, checkmarks=True) + + super().__init__( + self._item_group, + config=self._app_config, + allow_reset=True, + ) + + @override + def run(self, additional_title: str | None = None) -> ApplicationConfiguration: + super().run(additional_title=additional_title) + return self._app_config + + def _define_menu_options(self) -> list[MenuItem]: + return [ + MenuItem( + text=tr('Bluetooth'), + action=select_bluetooth, + value=self._app_config.bluetooth_config, + preview_action=self._prev_bluetooth, + key='bluetooth_config', + ), + ] + + def _prev_bluetooth(self, item: MenuItem) -> str | None: + if item.value is not None: + output = 'Bluetooth ' + output += tr('Enabled') if item.value else tr('Disabled') + return output + return None + + +def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None: + group = MenuItemGroup.yes_no() + group.focus_item = MenuItem.no() + + if preset is not None: + group.set_selected_by_value(preset.enabled) + + header = tr('Would you like to configure Bluetooth?') + '\n' + + result = SelectMenu[bool]( + group, + header=header, + alignment=Alignment.CENTER, + columns=2, + orientation=Orientation.HORIZONTAL, + allow_skip=True, + ).run() + + enabled = result.item() == MenuItem.yes() + + return BluetoothConfiguration(enabled) diff --git a/archinstall/lib/args.py b/archinstall/lib/args.py index ee1a571466..f9f28dcea2 100644 --- a/archinstall/lib/args.py +++ b/archinstall/lib/args.py @@ -13,6 +13,7 @@ from pydantic.dataclasses import dataclass as p_dataclass 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 @@ -63,6 +64,7 @@ class ArchConfig: bootloader: Bootloader = field(default=Bootloader.get_default()) uki: bool = False audio_config: AudioConfiguration | None = None + application_config: ApplicationConfiguration | None = None hostname: str = 'archlinux' kernels: list[str] = field(default_factory=lambda: ['linux']) ntp: bool = True @@ -105,6 +107,7 @@ def safe_json(self) -> dict[str, Any]: '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, } if self.locale_config: @@ -184,6 +187,9 @@ def from_config(cls, args_config: dict[str, Any]) -> 'ArchConfig': if audio_config := args_config.get('audio_config', None): arch_config.audio_config = AudioConfiguration.parse_arg(audio_config) + if app_config := args_config.get('app_config', None): + arch_config.application_config = ApplicationConfiguration.parse_arg(app_config) + 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 fbc3e3109d..e6b29dbc3e 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -3,10 +3,12 @@ from typing import override from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu +from archinstall.lib.models.application import ApplicationConfiguration from archinstall.lib.models.device_model import DiskLayoutConfiguration, DiskLayoutType, EncryptionType, FilesystemType, PartitionModification from archinstall.lib.packages import list_available_packages from archinstall.tui.menu_item import MenuItem, MenuItemGroup +from .applications.application_menu import ApplicationMenu from .args import ArchConfig from .configuration import save_config from .hardware import SysInfo @@ -132,6 +134,13 @@ def _get_menu_options(self) -> list[MenuItem]: preview_action=self._prev_audio, key='audio_config', ), + MenuItem( + text=tr('Applications'), + action=self._select_applications, + value=[], + preview_action=self._prev_applications, + key='app_config', + ), MenuItem( text=tr('Kernels'), value=['linux'], @@ -252,6 +261,10 @@ def _select_archinstall_language(self, preset: Language) -> Language: return language + def _select_applications(self, preset: ApplicationConfiguration | None) -> ApplicationConfiguration | None: + app_config = ApplicationMenu(preset).run() + return app_config + def _update_lang_text(self) -> None: """ The options for the global menu are generated with a static text; @@ -291,6 +304,19 @@ def _prev_additional_pkgs(self, item: MenuItem) -> str | None: return output return None + def _prev_applications(self, item: MenuItem) -> str | None: + if item.value: + app_config: ApplicationConfiguration = item.value + output = '' + + if app_config.bluetooth_config: + output += f'{tr("Bluetooth")}: ' + output += tr('Enabled') if app_config.bluetooth_config.enabled else tr('Disabled') + '\n' + + return output + + return None + def _prev_tz(self, item: MenuItem) -> str | None: if item.value: return f'{tr("Timezone")}: {item.value}' diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py new file mode 100644 index 0000000000..d2fc34f161 --- /dev/null +++ b/archinstall/lib/models/application.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass +from typing import NotRequired, TypedDict + + +class BluetoothConfigSerialization(TypedDict): + enabled: bool + + +class ApplicationSerialization(TypedDict): + bluetooth_config: NotRequired[BluetoothConfigSerialization] + + +@dataclass +class BluetoothConfiguration: + enabled: bool + + def json(self) -> BluetoothConfigSerialization: + return {'enabled': self.enabled} + + @staticmethod + def parse_arg(arg: BluetoothConfigSerialization) -> 'BluetoothConfiguration': + return BluetoothConfiguration(arg['enabled']) + + +@dataclass +class ApplicationConfiguration: + bluetooth_config: BluetoothConfiguration | 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']) + + return ApplicationConfiguration( + bluetooth_config=bluetooth_config, + ) + + def json(self) -> ApplicationSerialization: + config: ApplicationSerialization = {} + + if self.bluetooth_config: + config['bluetooth_config'] = self.bluetooth_config.json() + + return config diff --git a/archinstall/scripts/guided.py b/archinstall/scripts/guided.py index 8c29ce0941..d0025da32e 100644 --- a/archinstall/scripts/guided.py +++ b/archinstall/scripts/guided.py @@ -2,6 +2,7 @@ from pathlib import Path from archinstall import SysInfo +from archinstall.lib.applications.application_handler import application_handler from archinstall.lib.args import arch_config_handler from archinstall.lib.configuration import ConfigurationOutput from archinstall.lib.disk.filesystem import FilesystemHandler @@ -127,6 +128,9 @@ def perform_installation(mountpoint: Path) -> None: if profile_config := config.profile_config: profile_handler.install_profile_config(installation, profile_config) + if app_config := config.application_config: + application_handler.install_applications(installation, app_config) + if timezone := config.timezone: installation.set_timezone(timezone) diff --git a/tests/data/test_config.json b/tests/data/test_config.json index 70c5c5c333..f7502e7391 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -1,5 +1,10 @@ { "archinstall-language": "English", + "app_config": { + "bluetooth_config": { + "enabled": true + } + }, "audio_config": { "audio": "pipewire" }, diff --git a/tests/test_args.py b/tests/test_args.py index 772e4d00c6..81cd1afc95 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -6,6 +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.bootloader import Bootloader from archinstall.lib.models.device_model import DiskLayoutConfiguration, DiskLayoutType @@ -127,6 +128,9 @@ def test_config_file_parsing( assert arch_config == ArchConfig( version='3.0.2', + application_config=ApplicationConfiguration( + bluetooth_config=BluetoothConfiguration(enabled=True), + ), locale_config=LocaleConfiguration( kb_layout='us', sys_lang='en_US', From 472023a0269d81ee8b40db0a3fe0d56d83ff023b Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Tue, 17 Jun 2025 20:44:26 +1000 Subject: [PATCH 2/2] Update --- archinstall/lib/applications/application_menu.py | 6 ++++-- archinstall/lib/global_menu.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 7fdb3794ff..deb31c018e 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -45,8 +45,10 @@ def _define_menu_options(self) -> list[MenuItem]: def _prev_bluetooth(self, item: MenuItem) -> str | None: if item.value is not None: - output = 'Bluetooth ' - output += tr('Enabled') if item.value else tr('Disabled') + bluetooth_config: BluetoothConfiguration = item.value + + output = 'Bluetooth: ' + output += tr('Enabled') if bluetooth_config.enabled else tr('Disabled') return output return None diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index e6b29dbc3e..34159fd32f 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -139,7 +139,7 @@ def _get_menu_options(self) -> list[MenuItem]: action=self._select_applications, value=[], preview_action=self._prev_applications, - key='app_config', + key='application_config', ), MenuItem( text=tr('Kernels'),