Allow granular plasma configuration (#4389)
* Flexible plasma profile selection * Update * Update * Update * Update
This commit is contained in:
parent
101f647319
commit
b2f413124b
|
|
@ -1,7 +1,7 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops import SeatAccess
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
@ -17,7 +17,7 @@ class HyprlandProfile(Profile):
|
|||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
|
|
@ -45,7 +45,7 @@ class HyprlandProfile(Profile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ class HyprlandProfile(Profile):
|
|||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[SeatAccess](
|
||||
|
|
@ -67,7 +67,7 @@ class HyprlandProfile(Profile):
|
|||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops import SeatAccess
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
@ -17,13 +17,13 @@ class LabwcProfile(Profile):
|
|||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -39,7 +39,7 @@ class LabwcProfile(Profile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ class LabwcProfile(Profile):
|
|||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[SeatAccess](
|
||||
|
|
@ -61,7 +61,7 @@ class LabwcProfile(Profile):
|
|||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops import SeatAccess
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
@ -17,13 +17,13 @@ class NiriProfile(Profile):
|
|||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -47,7 +47,7 @@ class NiriProfile(Profile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ class NiriProfile(Profile):
|
|||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[SeatAccess](
|
||||
|
|
@ -69,7 +69,7 @@ class NiriProfile(Profile):
|
|||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,67 @@
|
|||
from enum import StrEnum
|
||||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.packages.packages import available_package, package_group_info
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
from archinstall.tui.ui.result import ResultType
|
||||
|
||||
|
||||
class PlasmaFlavor(StrEnum):
|
||||
Meta = 'plasma-meta'
|
||||
Plasma = 'plasma'
|
||||
Desktop = 'plasma-desktop'
|
||||
|
||||
def show(self) -> str:
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
return f'{self.value} ({tr("Recommended")})'
|
||||
case PlasmaFlavor.Plasma | PlasmaFlavor.Desktop:
|
||||
return self.value
|
||||
|
||||
def package_details(self) -> str:
|
||||
ty = ''
|
||||
details = ''
|
||||
desc = ''
|
||||
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
ty = tr('Package')
|
||||
desc = tr('Curated selection of KDE Plasma packages')
|
||||
info = available_package(self.value)
|
||||
|
||||
if info is not None:
|
||||
details = tr('Dependencies') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
|
||||
case PlasmaFlavor.Plasma:
|
||||
ty = tr('Package group')
|
||||
desc = tr('Extensive KDE Plasma installation')
|
||||
group = package_group_info(self.value)
|
||||
|
||||
if group is not None:
|
||||
details = tr('Packages in group') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in group.packages)
|
||||
case PlasmaFlavor.Desktop:
|
||||
ty = tr('Package group')
|
||||
desc = tr('Minimal KDE Plasma installation')
|
||||
info = available_package(self.value)
|
||||
|
||||
if info is not None:
|
||||
details = tr('Dependencies') + '\n'
|
||||
details += '\n'.join(f'- {entry}' for entry in info.get_depends_on)
|
||||
|
||||
return f'{tr("Type")}: {ty}\n{tr("Description")}: {desc}\n\n{details}'
|
||||
|
||||
def packages(self) -> list[str]:
|
||||
match self:
|
||||
case PlasmaFlavor.Meta:
|
||||
return ['plasma-meta']
|
||||
case PlasmaFlavor.Plasma:
|
||||
return ['plasma']
|
||||
case PlasmaFlavor.Desktop:
|
||||
return ['plasma-desktop']
|
||||
|
||||
|
||||
class PlasmaProfile(Profile):
|
||||
|
|
@ -15,18 +76,45 @@ class PlasmaProfile(Profile):
|
|||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
return [
|
||||
'plasma-desktop',
|
||||
'kscreen',
|
||||
'plasma-pa',
|
||||
'konsole',
|
||||
'kate',
|
||||
'dolphin',
|
||||
'ark',
|
||||
'plasma-workspace',
|
||||
]
|
||||
flavor_str = self.custom_settings.get(CustomSetting.PlasmaFlavor)
|
||||
|
||||
if flavor_str is not None:
|
||||
flavor = PlasmaFlavor(flavor_str)
|
||||
return flavor.packages()
|
||||
else:
|
||||
return PlasmaFlavor.Meta.packages() # use plasma-meta as the recommended default
|
||||
|
||||
@property
|
||||
@override
|
||||
def default_greeter_type(self) -> GreeterType:
|
||||
return GreeterType.PlasmaLoginManager
|
||||
|
||||
async def _select_flavor(self) -> None:
|
||||
header = tr('Select a flavor of KDE Plasma to install') + '\n'
|
||||
|
||||
items = [
|
||||
MenuItem(
|
||||
s.show(),
|
||||
value=s,
|
||||
preview_action=lambda x: x.value.package_details() if x.value else None,
|
||||
)
|
||||
for s in PlasmaFlavor
|
||||
]
|
||||
group = MenuItemGroup(items, sort_items=False)
|
||||
|
||||
default = self.custom_settings.get(CustomSetting.PlasmaFlavor, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[PlasmaFlavor](
|
||||
group,
|
||||
header=header,
|
||||
allow_skip=False,
|
||||
preview_location='right',
|
||||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings[CustomSetting.PlasmaFlavor] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
await self._select_flavor()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import override
|
||||
|
||||
from archinstall.default_profiles.desktops import SeatAccess
|
||||
from archinstall.default_profiles.profile import DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.default_profiles.profile import CustomSetting, DisplayServerType, GreeterType, Profile, ProfileType
|
||||
from archinstall.lib.menu.helpers import Selection
|
||||
from archinstall.lib.translationhandler import tr
|
||||
from archinstall.tui.ui.menu_item import MenuItem, MenuItemGroup
|
||||
|
|
@ -17,13 +17,13 @@ class SwayProfile(Profile):
|
|||
display_server=DisplayServerType.Wayland,
|
||||
)
|
||||
|
||||
self.custom_settings = {'seat_access': None}
|
||||
self.custom_settings = {CustomSetting.SeatAccess: None}
|
||||
|
||||
@property
|
||||
@override
|
||||
def packages(self) -> list[str]:
|
||||
additional = []
|
||||
if seat := self.custom_settings.get('seat_access', None):
|
||||
if seat := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
additional = [seat]
|
||||
|
||||
return [
|
||||
|
|
@ -49,7 +49,7 @@ class SwayProfile(Profile):
|
|||
@property
|
||||
@override
|
||||
def services(self) -> list[str]:
|
||||
if pref := self.custom_settings.get('seat_access', None):
|
||||
if pref := self.custom_settings.get(CustomSetting.SeatAccess, None):
|
||||
return [pref]
|
||||
return []
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ class SwayProfile(Profile):
|
|||
items = [MenuItem(s.value, value=s) for s in SeatAccess]
|
||||
group = MenuItemGroup(items, sort_items=True)
|
||||
|
||||
default = self.custom_settings.get('seat_access', None)
|
||||
default = self.custom_settings.get(CustomSetting.SeatAccess, None)
|
||||
group.set_default_by_value(default)
|
||||
|
||||
result = await Selection[SeatAccess](
|
||||
|
|
@ -71,7 +71,7 @@ class SwayProfile(Profile):
|
|||
).show()
|
||||
|
||||
if result.type_ == ResultType.Selection:
|
||||
self.custom_settings['seat_access'] = result.get_value().value
|
||||
self.custom_settings[CustomSetting.SeatAccess] = result.get_value().value
|
||||
|
||||
@override
|
||||
async def do_on_select(self) -> None:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from enum import Enum, auto
|
||||
from enum import Enum, StrEnum, auto
|
||||
from typing import TYPE_CHECKING, Self
|
||||
|
||||
from archinstall.lib.translationhandler import tr
|
||||
|
|
@ -45,6 +45,11 @@ class SelectResult(Enum):
|
|||
ResetCurrent = auto()
|
||||
|
||||
|
||||
class CustomSetting(StrEnum):
|
||||
SeatAccess = 'seat_access'
|
||||
PlasmaFlavor = 'plasma_flavor'
|
||||
|
||||
|
||||
class Profile:
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -59,7 +64,7 @@ class Profile:
|
|||
) -> None:
|
||||
self.name = name
|
||||
self.profile_type = profile_type
|
||||
self.custom_settings: dict[str, str | None] = {}
|
||||
self.custom_settings: dict[CustomSetting, str | None] = {}
|
||||
|
||||
self._support_gfx_driver = support_gfx_driver
|
||||
self._support_greeter = support_greeter
|
||||
|
|
@ -128,7 +133,7 @@ class Profile:
|
|||
"""
|
||||
return SelectResult.NewSelection
|
||||
|
||||
def set_custom_settings(self, settings: dict[str, str | None]) -> None:
|
||||
def set_custom_settings(self, settings: dict[CustomSetting, str | None]) -> None:
|
||||
"""
|
||||
Set the custom settings for the profile.
|
||||
This is also called when the settings are parsed from the config
|
||||
|
|
|
|||
|
|
@ -142,12 +142,26 @@ class AvailablePackage(BaseModel):
|
|||
|
||||
return output
|
||||
|
||||
@cached_property
|
||||
def get_depends_on(self) -> list[str]:
|
||||
return [entry.strip() for entry in self.depends_on.split(' ') if entry.strip()]
|
||||
|
||||
@cached_property
|
||||
def get_optional_deps(self) -> list[str]:
|
||||
return [entry.strip() for entry in self.optional_deps.split(' ') if entry.strip()]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PackageGroup:
|
||||
name: str
|
||||
packages: list[str] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def from_package_group_output(cls, data: list[str]) -> Self:
|
||||
name = data[0].split()[0].strip()
|
||||
packages = [line.split()[1].strip() for line in data if line.strip()]
|
||||
return cls(name, packages)
|
||||
|
||||
@classmethod
|
||||
def from_available_packages(
|
||||
cls,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,34 @@ def check_package_upgrade(package: str) -> str | None:
|
|||
return None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def package_group_info(package: str) -> PackageGroup | None:
|
||||
try:
|
||||
package_info: list[str] = []
|
||||
for line in Pacman.run(f'-Sg {package}'):
|
||||
package_info.append(line.decode().strip())
|
||||
group = PackageGroup.from_package_group_output(package_info)
|
||||
return group
|
||||
except SysCallError:
|
||||
debug(f'Failed to get package info: {package}')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def available_package(package: str) -> AvailablePackage | None:
|
||||
try:
|
||||
package_info: list[str] = []
|
||||
for line in Pacman.run(f'-S --info {package}'):
|
||||
package_info.append(line.decode().strip())
|
||||
|
||||
return _parse_package_output(package_info, AvailablePackage)
|
||||
except SysCallError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@lru_cache
|
||||
def list_available_packages(
|
||||
repositories: tuple[Repository, ...],
|
||||
|
|
@ -74,12 +102,18 @@ def _parse_package_output[PackageType: (AvailablePackage, LocalPackage)](
|
|||
cls: type[PackageType],
|
||||
) -> PackageType:
|
||||
package = {}
|
||||
current_key = None
|
||||
|
||||
for line in package_meta:
|
||||
if ':' in line:
|
||||
key, value = line.split(':', 1)
|
||||
key = _normalize_key_name(key)
|
||||
package[key] = value.strip()
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
if ':' in line and not line.startswith(' '):
|
||||
key_raw, value = line.split(':', 1)
|
||||
current_key = _normalize_key_name(key_raw)
|
||||
package[current_key] = value.strip()
|
||||
elif current_key:
|
||||
package[current_key] += ' ' + line.strip()
|
||||
|
||||
return cls.model_validate(package)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from tempfile import NamedTemporaryFile
|
|||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, NotRequired, TypedDict
|
||||
|
||||
from archinstall.default_profiles.profile import GreeterType, Profile
|
||||
from archinstall.default_profiles.profile import CustomSetting, GreeterType, Profile
|
||||
from archinstall.lib.hardware import GfxDriver, GfxPackage
|
||||
from archinstall.lib.models.profile import ProfileConfiguration
|
||||
from archinstall.lib.networking import fetch_data_from_url
|
||||
|
|
@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|||
class ProfileSerialization(TypedDict):
|
||||
main: NotRequired[str]
|
||||
details: NotRequired[list[str]]
|
||||
custom_settings: NotRequired[dict[str, dict[str, str | None]]]
|
||||
custom_settings: NotRequired[dict[str, dict[CustomSetting, str | None]]]
|
||||
path: NotRequired[str]
|
||||
|
||||
|
||||
|
|
@ -73,29 +73,6 @@ class ProfileHandler:
|
|||
else:
|
||||
self._import_profile_from_url(url_path)
|
||||
|
||||
# if custom := profile_config.get('custom', None):
|
||||
# from archinstall.default_profiles.custom import CustomTypeProfile
|
||||
# custom_types = []
|
||||
#
|
||||
# for entry in custom:
|
||||
# custom_types.append(
|
||||
# CustomTypeProfile(
|
||||
# entry['name'],
|
||||
# entry['enabled'],
|
||||
# entry.get('packages', []),
|
||||
# entry.get('services', [])
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# self.remove_custom_profiles(custom_types)
|
||||
# self.add_custom_profiles(custom_types)
|
||||
#
|
||||
# # this doesn't mean it's actual going to be set as a selection
|
||||
# # but we are simply populating the custom profile with all
|
||||
# # possible custom definitions
|
||||
# if custom_profile := self.get_profile_by_name('Custom'):
|
||||
# custom_profile.set_current_selection(custom_types)
|
||||
|
||||
if main := profile_config.get('main', None):
|
||||
profile = self.get_profile_by_name(main) if main else None
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
from archinstall.default_profiles.profile import GreeterType
|
||||
from archinstall.default_profiles.profile import CustomSetting, GreeterType
|
||||
from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments
|
||||
from archinstall.lib.hardware import GfxDriver
|
||||
from archinstall.lib.models.application import (
|
||||
|
|
@ -173,10 +173,13 @@ def test_config_file_parsing(
|
|||
{
|
||||
'custom_settings': {
|
||||
'Hyprland': {
|
||||
'seat_access': 'polkit',
|
||||
CustomSetting.SeatAccess: 'polkit',
|
||||
},
|
||||
'Sway': {
|
||||
'seat_access': 'seatd',
|
||||
CustomSetting.SeatAccess: 'seatd',
|
||||
},
|
||||
'KDE Plasma': {
|
||||
CustomSetting.PlasmaFlavor: 'plasma-meta',
|
||||
},
|
||||
},
|
||||
'details': [
|
||||
|
|
|
|||
Loading…
Reference in New Issue