Repository: archlinux/archinstall

Issue:
Sway Profile Changes Not Persisted to Configuration File 
## Description

When selecting `sway` profile, it properly asks for control preference between `seatd` and `polkit`, however, this selection isn't written to a configuration file.

## Reproduce

1. Launch archinstall on commit 00b0ae7ba439a5a420095175b3bedd52c569db51
2. Go to profile, select profile, select desktop, select sway, and select either polkit or seatd for your seat access.
3. Select back, then save your configuration to a file.
4. Abort and check the contents of the file. The seat access option is not persisted.

## Example Config File from steps above

Note that the polkit/seatd selection is not included, and therefore not recoverable.

```
...
"profile_config": {
    "gfx_driver": "All open-source (default)",
    "greeter": "gdm",
    "profile": {
        "details": [
            "Sway"
        ],
        "main": "Desktop"
    }
},
...
```


Discussion:
The problem with it is that it's a custom question in the sway profile :thinking: 
I will try to get something implemented that makes it possible to export/import such generic profile specific settings 
It would be nice if the profile could inject configuration options to the configuration.
Which is why it modified that magic global variable before hehe.

I know that looked and felt non-standard and a bit ugly, so a more "pythonic way" would be awesome

Resolving patch:
diff --git a/archinstall/default_profiles/desktops/sway.py b/archinstall/default_profiles/desktops/sway.py
--- a/archinstall/default_profiles/desktops/sway.py
+++ b/archinstall/default_profiles/desktops/sway.py
@@ -1,3 +1,4 @@
+from enum import Enum
 from typing import List, Optional, TYPE_CHECKING, Any
 
 from archinstall.default_profiles.profile import ProfileType, GreeterType
@@ -9,6 +10,11 @@
 	_: Any
 
 
+class SeatAccess(Enum):
+	seatd = 'seatd'
+	polkit = 'polkit'
+
+
 class SwayProfile(XorgProfile):
 	def __init__(self):
 		super().__init__(
@@ -16,10 +22,15 @@ def __init__(self):
 			ProfileType.WindowMgr,
 			description=''
 		)
-		self._control_preference = []
+
+		self.custom_settings = {'seat_access': None}
 
 	@property
 	def packages(self) -> List[str]:
+		additional = []
+		if seat := self.custom_settings.get('seat_access', None):
+			additional = [seat]
+
 		return [
 			"sway",
 			"swaybg",
@@ -33,7 +44,7 @@ def packages(self) -> List[str]:
 			"pavucontrol",
 			"foot",
 			"xorg-xwayland"
-		] + self._control_preference
+		] + additional
 
 	@property
 	def default_greeter_type(self) -> Optional[GreeterType]:
@@ -41,22 +52,26 @@ def default_greeter_type(self) -> Optional[GreeterType]:
 
 	@property
 	def services(self) -> List[str]:
-		if "seatd" in self._control_preference:
-			return ['seatd']
-		elif "polkit" in self._control_preference:
-			return ['polkit']
-
+		if pref := self.custom_settings.get('seat_access', None):
+			return [pref.value]
 		return []
 
-	def _get_system_privelege_control_preference(self):
+	def _ask_seat_access(self):
 		# need to activate seat service and add to seat group
 		title = str(_('Sway needs access to your seat (collection of hardware devices i.e. keyboard, mouse, etc)'))
 		title += str(_('\n\nChoose an option to give Sway access to your hardware'))
-		choice = Menu(title, ["polkit", "seatd"], skip=False).run()
-		self._control_preference = [choice.value]
+
+		options = [e.value for e in SeatAccess]
+		default = None
+
+		if seat := self.custom_settings.get('seat_access', None):
+			default = seat
+
+		choice = Menu(title, options, skip=False, preset_values=default).run()
+		self.custom_settings['seat_access'] = choice.single_value
 
 	def do_on_select(self):
-		self._get_system_privelege_control_preference()
+		self._ask_seat_access()
 
 	def preview_text(self) -> Optional[str]:
 		text = str(_('Environment type: {}')).format(self.profile_type.value)
diff --git a/archinstall/default_profiles/profile.py b/archinstall/default_profiles/profile.py
--- a/archinstall/default_profiles/profile.py
+++ b/archinstall/default_profiles/profile.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-from dataclasses import dataclass
 from enum import Enum, auto
 from typing import List, Optional, Any, Dict, TYPE_CHECKING, TypeVar
 
@@ -43,20 +42,6 @@ class SelectResult(Enum):
 	ResetCurrent = auto()
 
 
-@dataclass
-class ProfileInfo:
-	name: str
-	details: Optional[str]
-	gfx_driver: Optional[str] = None
-	greeter: Optional[str] = None
-
-	@property
-	def absolute_name(self) -> str:
-		if self.details is not None:
-			return self.details
-		return self.name
-
-
 class Profile:
 	def __init__(
 		self,
@@ -72,6 +57,8 @@ def __init__(
 		self.name = name
 		self.description = description
 		self.profile_type = profile_type
+		self.custom_settings: Dict[str, Any] = {}
+
 		self._support_gfx_driver = support_gfx_driver
 		self._support_greeter = support_greeter
 
@@ -135,6 +122,14 @@ def do_on_select(self) -> SelectResult:
 		"""
 		return SelectResult.NewSelection
 
+	def set_custom_settings(self, settings: Dict[str, Any]):
+		"""
+		Set the custom settings for the profile.
+		This is also called when the settings are parsed from the config
+		and can be overriden to perform further actions based on the profile
+		"""
+		self.custom_settings = settings
+
 	def current_selection_names(self) -> List[str]:
 		if self._current_selection:
 			return [s.name for s in self._current_selection]
diff --git a/archinstall/lib/profile/profiles_handler.py b/archinstall/lib/profile/profiles_handler.py
--- a/archinstall/lib/profile/profiles_handler.py
+++ b/archinstall/lib/profile/profiles_handler.py
@@ -43,6 +43,7 @@ def to_json(self, profile: Optional[Profile]) -> Dict[str, Any]:
 			data = {
 				'main': profile.name,
 				'details': [profile.name for profile in profile.current_selection],
+				'custom_settings': {profile.name: profile.custom_settings for profile in profile.current_selection}
 			}
 
 		if self._url_path is not None:
@@ -98,6 +99,7 @@ def parse_profile_config(self, profile_config: Dict[str, Any]) -> Optional[Profi
 			profile = self.get_profile_by_name(main) if main else None
 
 		valid: List[Profile] = []
+
 		if details := profile_config.get('details', []):
 			resolved = {detail: self.get_profile_by_name(detail) for detail in details if detail}
 			valid = [p for p in resolved.values() if p is not None]
@@ -106,6 +108,12 @@ def parse_profile_config(self, profile_config: Dict[str, Any]) -> Optional[Profi
 			if invalid:
 				log(f'No profile definition found: {invalid}')
 
+		custom_settings = profile_config.get('custom_settings', {})
+		for profile in valid:
+			profile.set_custom_settings(
+				custom_settings.get(profile.name, {})
+			)
+
 		if profile is not None:
 			profile.set_current_selection(valid)
 


Test patch: