feat(applications): add CUPS installation support (#4013)

* feat(applications): add CUPS installation support

* fix: use translation for print service preview_action

* fix: incorrect action for print service menu item

* chore: refactor naming, printer -> print service

* fix: commit untracked file

* chore: fix formatting to make linter happy
This commit is contained in:
Mariya 2025-12-20 22:30:11 -05:00 committed by GitHub
parent cf31148e46
commit d7b559c67e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 113 additions and 8 deletions

View File

@ -0,0 +1,23 @@
from typing import TYPE_CHECKING
from archinstall.lib.output import debug
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class PrintServiceApp:
@property
def packages(self) -> list[str]:
return ['cups', 'system-config-printer', 'cups-pk-helper']
@property
def services(self) -> list[str]:
return [
'cups.service',
]
def install(self, install_session: 'Installer') -> None:
debug('Installing print service')
install_session.add_additional_packages(self.packages)
install_session.enable_service(self.services)

View File

@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
from archinstall.applications.audio import AudioApp
from archinstall.applications.bluetooth import BluetoothApp
from archinstall.applications.print_service import PrintServiceApp
from archinstall.lib.models import Audio
from archinstall.lib.models.application import ApplicationConfiguration
from archinstall.lib.models.users import User
@ -25,5 +26,8 @@ class ApplicationHandler:
users,
)
if app_config.print_service_config and app_config.print_service_config.enabled:
PrintServiceApp().install(install_session)
application_handler = ApplicationHandler()

View File

@ -1,7 +1,7 @@
from typing import override
from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
from archinstall.lib.translationhandler import tr
from archinstall.tui.curses_menu import SelectMenu
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
@ -48,6 +48,12 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
preview_action=self._prev_audio,
key='audio_config',
),
MenuItem(
text=tr('Print service'),
action=select_print_service,
preview_action=self._prev_print_service,
key='print_service_config',
),
]
def _prev_bluetooth(self, item: MenuItem) -> str | None:
@ -65,6 +71,15 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
return f'{tr("Audio")}: {config.audio.value}'
return None
def _prev_print_service(self, item: MenuItem) -> str | None:
if item.value is not None:
print_service_config: PrintServiceConfiguration = item.value
output = f'{tr("Print service")}: '
output += tr('Enabled') if print_service_config.enabled else tr('Disabled')
return output
return None
def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None:
group = MenuItemGroup.yes_no()
@ -94,6 +109,34 @@ def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfigur
raise ValueError('Unhandled result type')
def select_print_service(preset: PrintServiceConfiguration | None) -> PrintServiceConfiguration | 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 the print service?') + '\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 PrintServiceConfiguration(enabled)
case ResultType.Skip:
return preset
case _:
raise ValueError('Unhandled result type')
def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None:
items = [MenuItem(a.value, value=a) for a in Audio]
group = MenuItemGroup(items)

View File

@ -324,6 +324,11 @@ class GlobalMenu(AbstractMenu[None]):
output += f'{tr("Audio")}: {audio_config.audio.value}'
output += '\n'
if app_config.print_service_config:
output += f'{tr("Print service")}: '
output += tr('Enabled') if app_config.print_service_config.enabled else tr('Disabled')
output += '\n'
return output
return None

View File

@ -1,9 +1,4 @@
from .application import (
ApplicationConfiguration,
Audio,
AudioConfiguration,
BluetoothConfiguration,
)
from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
from .bootloader import Bootloader
from .device import (
BDevice,
@ -76,6 +71,7 @@ __all__ = [
'PartitionTable',
'PartitionType',
'PasswordStrength',
'PrintServiceConfiguration',
'ProfileConfiguration',
'Repository',
'SectorSize',

View File

@ -17,9 +17,14 @@ class AudioConfigSerialization(TypedDict):
audio: str
class PrintServiceConfigSerialization(TypedDict):
enabled: bool
class ApplicationSerialization(TypedDict):
bluetooth_config: NotRequired[BluetoothConfigSerialization]
audio_config: NotRequired[AudioConfigSerialization]
print_service_config: NotRequired[PrintServiceConfigSerialization]
@dataclass
@ -50,10 +55,23 @@ class BluetoothConfiguration:
return BluetoothConfiguration(arg['enabled'])
@dataclass
class PrintServiceConfiguration:
enabled: bool
def json(self) -> PrintServiceConfigSerialization:
return {'enabled': self.enabled}
@staticmethod
def parse_arg(arg: dict[str, Any]) -> 'PrintServiceConfiguration':
return PrintServiceConfiguration(arg['enabled'])
@dataclass
class ApplicationConfiguration:
bluetooth_config: BluetoothConfiguration | None = None
audio_config: AudioConfiguration | None = None
print_service_config: PrintServiceConfiguration | None = None
@staticmethod
def parse_arg(
@ -72,6 +90,9 @@ class ApplicationConfiguration:
if args and (audio_config := args.get('audio_config')) is not None:
app_config.audio_config = AudioConfiguration.parse_arg(audio_config)
if args and (print_service_config := args.get('print_service_config')) is not None:
app_config.print_service_config = PrintServiceConfiguration.parse_arg(print_service_config)
return app_config
def json(self) -> ApplicationSerialization:
@ -83,4 +104,7 @@ class ApplicationConfiguration:
if self.audio_config:
config['audio_config'] = self.audio_config.json()
if self.print_service_config:
config['print_service_config'] = self.print_service_config.json()
return config

View File

@ -1779,6 +1779,12 @@ msgstr ""
msgid "Would you like to configure Bluetooth?"
msgstr ""
msgid "Print service"
msgstr ""
msgid "Would you like to configure the print service?"
msgstr ""
msgid "Authentication"
msgstr ""

View File

@ -7,6 +7,9 @@
},
"audio_config": {
"audio": "pipewire"
},
"print_service_config": {
"enabled": true
}
},
"auth_config": {

View File

@ -7,7 +7,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, Audio, AudioConfiguration, BluetoothConfiguration
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType
@ -132,6 +132,7 @@ def test_config_file_parsing(
app_config=ApplicationConfiguration(
bluetooth_config=BluetoothConfiguration(enabled=True),
audio_config=AudioConfiguration(audio=Audio.PIPEWIRE),
print_service_config=PrintServiceConfiguration(enabled=True),
),
auth_config=AuthenticationConfiguration(
root_enc_password=Password(enc_password='password_hash'),