diff --git a/archinstall/applications/camera.py b/archinstall/applications/camera.py new file mode 100644 index 0000000000..eebf2acf6f --- /dev/null +++ b/archinstall/applications/camera.py @@ -0,0 +1,16 @@ +from typing import TYPE_CHECKING + +from archinstall.lib.output import debug + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class CameraApp: + @property + def packages(self) -> list[str]: + return ['libcamera', 'libcamera-ipa', 'libcamera-tools', 'gst-plugin-libcamera', 'pipewire-libcamera'] + + def install(self, install_session: 'Installer') -> None: + debug('Installing libcamera') + install_session.add_additional_packages(self.packages) diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index e7d16058d5..6defbbba81 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -2,6 +2,7 @@ from archinstall.applications.audio import AudioApp from archinstall.applications.bluetooth import BluetoothApp +from archinstall.applications.camera import CameraApp from archinstall.applications.firewall import FirewallApp from archinstall.applications.power_management import PowerManagementApp from archinstall.applications.print_service import PrintServiceApp @@ -43,5 +44,7 @@ def install_applications(self, install_session: 'Installer', app_config: Applica app_config.firewall_config, ) + if app_config.camera_config and app_config.camera_config.enabled: + CameraApp().install(install_session) application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 7dd56e9871..be2fbefa41 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -6,6 +6,7 @@ ApplicationConfiguration, Audio, AudioConfiguration, + CameraConfiguration, BluetoothConfiguration, Firewall, FirewallConfiguration, @@ -78,6 +79,12 @@ def _define_menu_options(self) -> list[MenuItem]: preview_action=self._prev_firewall, key='firewall_config', ), + MenuItem( + text=tr('Camera'), + action=select_camera, + preview_action=self._prev_camera, + key='camera_config', + ), ] def _prev_power_management(self, item: MenuItem) -> str | None: @@ -116,6 +123,15 @@ def _prev_firewall(self, item: MenuItem) -> str | None: return f'{tr("Firewall")}: {config.firewall.value}' return None + def _prev_camera(self, item: MenuItem) -> str | None: + if item.value is not None: + camera_config: CameraConfiguration = item.value + + output = f'{tr("Camera")}: ' + output += tr('Enabled') if camera_config.enabled else tr('Disabled') + return output + return None + def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None: group = MenuItemGroup.from_enum(PowerManagement) @@ -240,3 +256,30 @@ def select_firewall(preset: FirewallConfiguration | None = None) -> FirewallConf return FirewallConfiguration(firewall=result.get_value()) case ResultType.Reset: return None + +def select_camera(preset: CameraConfiguration | None) -> CameraConfiguration | 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 install libcamera?') + '\n' + + result = SelectMenu[bool]( + group, + header=header, + alignment=Alignment.CENTER, + columns=2, + orientation=Orientation.HORIZONTAL, + allow_skip=True, + ).run() + + match result.type_: + case ResultType.Selection: + enabled = result.item() == MenuItem.yes() + return CameraConfiguration(enabled) + case ResultType.Skip: + return preset + case _: + raise ValueError('Unhandled result type') diff --git a/archinstall/lib/global_menu.py b/archinstall/lib/global_menu.py index 0230f73b6e..3e691caafe 100644 --- a/archinstall/lib/global_menu.py +++ b/archinstall/lib/global_menu.py @@ -345,6 +345,11 @@ def _prev_applications(self, item: MenuItem) -> str | None: output += f'{tr("Firewall")}: {firewall_config.firewall.value}' output += '\n' + if app_config.camera_config: + output += f'{tr("Camera")}: ' + output += tr('Enabled') if app_config.camera_config.enabled else tr('Disabled') + output += '\n' + return output return None diff --git a/archinstall/lib/models/__init__.py b/archinstall/lib/models/__init__.py index d742bc9fe5..95672055e8 100644 --- a/archinstall/lib/models/__init__.py +++ b/archinstall/lib/models/__init__.py @@ -1,4 +1,4 @@ -from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration +from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, CameraConfiguration, PrintServiceConfiguration from .bootloader import Bootloader from .device import ( BDevice, @@ -41,6 +41,7 @@ 'BDevice', 'BluetoothConfiguration', 'Bootloader', + 'CameraConfiguration', 'CustomRepository', 'DeviceGeometry', 'DeviceModification', diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index d32042ff2f..2a96a9815b 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -38,6 +38,8 @@ class Firewall(StrEnum): class FirewallConfigSerialization(TypedDict): firewall: str +class CameraConfigSerialization(TypedDict): + enabled: bool class ZramAlgorithm(StrEnum): ZSTD = 'zstd' @@ -53,7 +55,7 @@ class ApplicationSerialization(TypedDict): power_management_config: NotRequired[PowerManagementConfigSerialization] print_service_config: NotRequired[PrintServiceConfigSerialization] firewall_config: NotRequired[FirewallConfigSerialization] - + camera_config: NotRequired[CameraConfigSerialization] @dataclass class AudioConfiguration: @@ -141,6 +143,16 @@ def parse_arg(cls, arg: bool | dict[str, Any]) -> Self: algo = arg.get('algorithm', arg.get('algo', ZramAlgorithm.ZSTD.value)) return cls(enabled=enabled, algorithm=ZramAlgorithm(algo)) +@dataclass +class CameraConfiguration: + enabled: bool + + def json(self) -> CameraConfigSerialization: + return {'enabled': self.enabled} + + @classmethod + def parse_arg(cls, arg: CameraConfigSerialization) -> Self: + return cls(arg['enabled']) @dataclass class ApplicationConfiguration: @@ -149,6 +161,7 @@ class ApplicationConfiguration: power_management_config: PowerManagementConfiguration | None = None print_service_config: PrintServiceConfiguration | None = None firewall_config: FirewallConfiguration | None = None + camera_config: CameraConfiguration | None = None @classmethod def parse_arg( @@ -177,6 +190,10 @@ def parse_arg( if args and (firewall_config := args.get('firewall_config')) is not None: app_config.firewall_config = FirewallConfiguration.parse_arg(firewall_config) + if args and (camera_config := args.get('camera_config')) is not None: + app_config.camera_config = CameraConfiguration.parse_arg(camera_config) + + return app_config def json(self) -> ApplicationSerialization: @@ -197,4 +214,7 @@ def json(self) -> ApplicationSerialization: if self.firewall_config: config['firewall_config'] = self.firewall_config.json() + if self.camera_config: + config['camera_config'] = self.camera_config.json() + return config diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index f365e04fb5..0f0e101189 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1790,6 +1790,12 @@ msgstr "" msgid "Would you like to configure the print service?" msgstr "" +msgid "Camera" +msgstr "" + +msgid "Would you like to install libcamera?" +msgstr "" + msgid "Power management" msgstr "" diff --git a/tests/data/test_config.json b/tests/data/test_config.json index 618bd8e9c7..c72c7338c7 100644 --- a/tests/data/test_config.json +++ b/tests/data/test_config.json @@ -10,6 +10,9 @@ }, "print_service_config": { "enabled": true + }, + "camera_config": { + "enabled": true } }, "auth_config": { diff --git a/tests/test_args.py b/tests/test_args.py index 275ffd86f0..f957f85652 100644 --- a/tests/test_args.py +++ b/tests/test_args.py @@ -12,6 +12,7 @@ Audio, AudioConfiguration, BluetoothConfiguration, + CameraConfiguration, PrintServiceConfiguration, ZramConfiguration, ) @@ -140,6 +141,7 @@ def test_config_file_parsing( bluetooth_config=BluetoothConfiguration(enabled=True), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), print_service_config=PrintServiceConfiguration(enabled=True), + camera_config=CameraConfiguration(enabled=True), ), auth_config=AuthenticationConfiguration( root_enc_password=Password(enc_password='password_hash'),