Add bluetooth support (#3604)

* Add bluetooth option
This commit is contained in:
Daniel Girtler 2025-06-18 17:57:54 +10:00 committed by GitHub
parent f0cb3ad77a
commit 27f53717b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 215 additions and 2 deletions

View File

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

View File

@ -16,8 +16,6 @@ class CinnamonProfile(XorgProfile):
'system-config-printer',
'gnome-keyring',
'gnome-terminal',
'blueman',
'bluez-utils',
'engrampa',
'gnome-screenshot',
'gvfs-smb',

View File

View File

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

View File

@ -0,0 +1,76 @@
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:
bluetooth_config: BluetoothConfiguration = item.value
output = 'Bluetooth: '
output += tr('Enabled') if bluetooth_config.enabled 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)

View File

@ -13,6 +13,7 @@ from urllib.request import Request, urlopen
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 @@ class ArchConfig:
'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 @@ class 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

View File

@ -3,10 +3,12 @@ from __future__ import annotations
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 @@ class GlobalMenu(AbstractMenu[None]):
preview_action=self._prev_audio,
key='audio_config',
),
MenuItem(
text=tr('Applications'),
action=self._select_applications,
value=[],
preview_action=self._prev_applications,
key='application_config',
),
MenuItem(
text=tr('Kernels'),
value=['linux'],
@ -252,6 +261,10 @@ class GlobalMenu(AbstractMenu[None]):
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 @@ class GlobalMenu(AbstractMenu[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}'

View File

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

View File

@ -2,6 +2,7 @@ import os
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)

View File

@ -1,5 +1,10 @@
{
"archinstall-language": "English",
"app_config": {
"bluetooth_config": {
"enabled": true
}
},
"audio_config": {
"audio": "pipewire"
},

View File

@ -6,6 +6,7 @@ from pytest import MonkeyPatch
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',