From c5a24dbf023f063b757587642df8ce57a0cd64aa Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:48:07 -0500 Subject: [PATCH 1/8] fix(profiles): install power-profiles-daemon by default in the desktop profile --- archinstall/default_profiles/desktop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 9bd65b444b..9e3ee0c8c1 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -35,6 +35,7 @@ def packages(self) -> list[str]: 'wpa_supplicant', 'smartmontools', 'xdg-utils', + 'power-profiles-daemon' ] @property From a5573ce414e45e05d8f0661095368e8ee5730a1e Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:29:22 -0500 Subject: [PATCH 2/8] fix: only install power-profiles-daemon if a battery is detected --- archinstall/default_profiles/desktop.py | 5 ++++- archinstall/lib/hardware.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index 9e3ee0c8c1..eb5bd34626 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, override from archinstall.default_profiles.profile import GreeterType, Profile, ProfileType, SelectResult +from archinstall.lib.hardware import SysInfo from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.tui.curses_menu import SelectMenu @@ -35,7 +36,6 @@ def packages(self) -> list[str]: 'wpa_supplicant', 'smartmontools', 'xdg-utils', - 'power-profiles-daemon' ] @property @@ -100,6 +100,9 @@ def install(self, install_session: 'Installer') -> None: # Install common packages for all desktop environments install_session.add_additional_packages(self.packages) + if SysInfo.has_battery(): + install_session.add_additional_packages('power-profiles-daemon') + for profile in self.current_selection: info(f'Installing profile {profile.name}...') diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index e1278dfad4..0541396112 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -198,6 +198,19 @@ def loaded_modules(self) -> list[str]: class SysInfo: + @staticmethod + def has_battery(): + for device in os.listdir('/sys/class/power_supply/'): + device_type_path = os.path.join('/sys/class/power_supply/', device, 'type') + try: + with open(device_type_path) as f: + if f.read().strip() == 'Battery': + return True + except OSError: + continue + + return False + @staticmethod def has_wifi() -> bool: ifaces = list(list_interfaces().values()) From 9b12c6a6ab3dc565299511e03aeeb1a4e6b29d71 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 15:39:05 -0500 Subject: [PATCH 3/8] chore: clean up has_battery method --- archinstall/lib/hardware.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 0541396112..3bbe610daa 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -200,10 +200,9 @@ def loaded_modules(self) -> list[str]: class SysInfo: @staticmethod def has_battery(): - for device in os.listdir('/sys/class/power_supply/'): - device_type_path = os.path.join('/sys/class/power_supply/', device, 'type') + for type_path in Path('/sys/class/power_supply/').glob('*/type'): try: - with open(device_type_path) as f: + with open(type_path) as f: if f.read().strip() == 'Battery': return True except OSError: From 9b6765d785827f50d4e6311152b84a295cd513a7 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 15:55:14 -0500 Subject: [PATCH 4/8] fix: make power management daemon a configurable application --- archinstall/applications/power_management.py | 39 ++++++++++++++++ archinstall/default_profiles/desktop.py | 4 -- .../lib/applications/application_handler.py | 9 +++- .../lib/applications/application_menu.py | 46 ++++++++++++++++++- archinstall/lib/models/application.py | 34 ++++++++++++++ 5 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 archinstall/applications/power_management.py diff --git a/archinstall/applications/power_management.py b/archinstall/applications/power_management.py new file mode 100644 index 0000000000..94b67ebfc0 --- /dev/null +++ b/archinstall/applications/power_management.py @@ -0,0 +1,39 @@ +from typing import TYPE_CHECKING + +from archinstall.lib.models.application import PowerManagement, PowerManagementConfiguration +from archinstall.lib.output import debug + +if TYPE_CHECKING: + from archinstall.lib.installer import Installer + + +class PowerManagementApp: + @property + def ppd_packages(self) -> list[str]: + return [ + 'power-profiles-daemon', + ] + + @property + def tuned_packages(self) -> list[str]: + return [ + 'tuned', + 'tuned-ppd', + ] + + def install( + self, + install_session: 'Installer', + power_management_config: PowerManagementConfiguration, + ) -> None: + debug(f'Installing power management daemon: {power_management_config.power_management.value}') + + if power_management_config.power_management == PowerManagement.NO_POWER_MANAGEMENT: + debug('No power management daemon selected, skipping installation.') + return + + match power_management_config.power_management: + case PowerManagement.POWER_PROFILES_DAEMON: + install_session.add_additional_packages(self.ppd_packages) + case PowerManagement.TUNED: + install_session.add_additional_packages(self.tuned_packages) diff --git a/archinstall/default_profiles/desktop.py b/archinstall/default_profiles/desktop.py index eb5bd34626..9bd65b444b 100644 --- a/archinstall/default_profiles/desktop.py +++ b/archinstall/default_profiles/desktop.py @@ -1,7 +1,6 @@ from typing import TYPE_CHECKING, override from archinstall.default_profiles.profile import GreeterType, Profile, ProfileType, SelectResult -from archinstall.lib.hardware import SysInfo from archinstall.lib.output import info from archinstall.lib.profile.profiles_handler import profile_handler from archinstall.tui.curses_menu import SelectMenu @@ -100,9 +99,6 @@ def install(self, install_session: 'Installer') -> None: # Install common packages for all desktop environments install_session.add_additional_packages(self.packages) - if SysInfo.has_battery(): - install_session.add_additional_packages('power-profiles-daemon') - for profile in self.current_selection: info(f'Installing profile {profile.name}...') diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index 29e30f471a..90c6dc1de0 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -2,8 +2,9 @@ from archinstall.applications.audio import AudioApp from archinstall.applications.bluetooth import BluetoothApp +from archinstall.applications.power_management import PowerManagementApp from archinstall.lib.models import Audio -from archinstall.lib.models.application import ApplicationConfiguration +from archinstall.lib.models.application import ApplicationConfiguration, PowerManagement from archinstall.lib.models.users import User if TYPE_CHECKING: @@ -25,5 +26,11 @@ def install_applications(self, install_session: 'Installer', app_config: Applica users, ) + if app_config.power_management_config and app_config.power_management_config.power_management != PowerManagement.NO_POWER_MANAGEMENT: + PowerManagementApp().install( + install_session, + app_config.power_management_config, + ) + application_handler = ApplicationHandler() diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index ad20f6405e..3cd010f1ec 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -1,7 +1,15 @@ from typing import override +from archinstall.lib.hardware import SysInfo 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, + PowerManagement, + PowerManagementConfiguration, +) from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.menu_item import MenuItem, MenuItemGroup @@ -48,8 +56,21 @@ def _define_menu_options(self) -> list[MenuItem]: preview_action=self._prev_audio, key='audio_config', ), + MenuItem( + text=tr('Power management daemon'), + action=select_audio, + preview_action=self._prev_power_management, + enabled=SysInfo.has_battery(), + key='power_management_config', + ), ] + def _prev_power_management(self, item: MenuItem) -> str | None: + if item.value is not None: + config: PowerManagementConfiguration = item.value + return f'{tr("Power management daemon")}: {config.power_management.value}' + return None + def _prev_bluetooth(self, item: MenuItem) -> str | None: if item.value is not None: bluetooth_config: BluetoothConfiguration = item.value @@ -66,6 +87,29 @@ def _prev_audio(self, item: MenuItem) -> str | None: return None +def select_power_management(preset: PowerManagementConfiguration | None = None) -> PowerManagementConfiguration | None: + items = [MenuItem(a.value, value=a) for a in PowerManagement] + group = MenuItemGroup(items) + + if preset: + group.set_focus_by_value(preset.power_management) + + result = SelectMenu[PowerManagement]( + group, + allow_skip=True, + alignment=Alignment.CENTER, + frame=FrameProperties.min(tr('Power management daemon')), + ).run() + + match result.type_: + case ResultType.Skip: + return preset + case ResultType.Selection: + return PowerManagementConfiguration(power_management=result.get_value()) + case ResultType.Reset: + raise ValueError('Unhandled result type') + + def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None: group = MenuItemGroup.yes_no() group.focus_item = MenuItem.no() diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index eeced89881..92a621c715 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -3,6 +3,16 @@ from typing import Any, NotRequired, TypedDict +class PowerManagement(StrEnum): + NO_POWER_MANAGEMENT = 'No power management daemon' + POWER_PROFILES_DAEMON = 'power-profiles-daemon' + TUNED = auto() + + +class PowerManagementConfigSerialization(TypedDict): + power_management: str + + class BluetoothConfigSerialization(TypedDict): enabled: bool @@ -20,6 +30,7 @@ class AudioConfigSerialization(TypedDict): class ApplicationSerialization(TypedDict): bluetooth_config: NotRequired[BluetoothConfigSerialization] audio_config: NotRequired[AudioConfigSerialization] + power_management_config: NotRequired[PowerManagementConfigSerialization] @dataclass @@ -50,10 +61,27 @@ def parse_arg(arg: dict[str, Any]) -> 'BluetoothConfiguration': return BluetoothConfiguration(arg['enabled']) +@dataclass +class PowerManagementConfiguration: + power_management: PowerManagement + + def json(self) -> PowerManagementConfigSerialization: + return { + 'power_management': self.power_management.value, + } + + @staticmethod + def parse_arg(arg: dict[str, Any]) -> 'PowerManagementConfiguration': + return PowerManagementConfiguration( + PowerManagement(arg['power_management']), + ) + + @dataclass class ApplicationConfiguration: bluetooth_config: BluetoothConfiguration | None = None audio_config: AudioConfiguration | None = None + power_management_config: PowerManagementConfiguration | None = None @staticmethod def parse_arg( @@ -72,6 +100,9 @@ def parse_arg( if args and (audio_config := args.get('audio_config')) is not None: app_config.audio_config = AudioConfiguration.parse_arg(audio_config) + if args and (power_management_config := args.get('power_management_config')) is not None: + app_config.power_management_config = PowerManagementConfiguration.parse_arg(power_management_config) + return app_config def json(self) -> ApplicationSerialization: @@ -83,4 +114,7 @@ def json(self) -> ApplicationSerialization: if self.audio_config: config['audio_config'] = self.audio_config.json() + if self.power_management_config: + config['power_management_config'] = self.power_management_config.json() + return config From 42e91a7062867dbeca271ed4c6aa709ebdd1d52d Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:02:19 -0500 Subject: [PATCH 5/8] fix: make linter happy after merge --- archinstall/lib/applications/application_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/applications/application_handler.py b/archinstall/lib/applications/application_handler.py index cb1d9ff0a1..15fa539244 100644 --- a/archinstall/lib/applications/application_handler.py +++ b/archinstall/lib/applications/application_handler.py @@ -26,7 +26,7 @@ def install_applications(self, install_session: 'Installer', app_config: Applica app_config.audio_config, users, ) - + if app_config.power_management_config and app_config.power_management_config.power_management != PowerManagement.NO_POWER_MANAGEMENT: PowerManagementApp().install( install_session, From 41e0b911b7780e14cdfea5895cb63fc99c6b5a29 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:14:50 -0500 Subject: [PATCH 6/8] fix: fix merge issues --- archinstall/lib/applications/application_menu.py | 4 ++-- archinstall/lib/models/application.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/archinstall/lib/applications/application_menu.py b/archinstall/lib/applications/application_menu.py index 48ef157e55..774c7a57c5 100644 --- a/archinstall/lib/applications/application_menu.py +++ b/archinstall/lib/applications/application_menu.py @@ -9,7 +9,7 @@ BluetoothConfiguration, PowerManagement, PowerManagementConfiguration, - PrintServiceConfiguration, + PrintServiceConfiguration, ) from archinstall.lib.translationhandler import tr from archinstall.tui.curses_menu import SelectMenu @@ -65,7 +65,7 @@ def _define_menu_options(self) -> list[MenuItem]: ), MenuItem( text=tr('Power management daemon'), - action=select_audio, + action=select_power_management, preview_action=self._prev_power_management, enabled=SysInfo.has_battery(), key='power_management_config', diff --git a/archinstall/lib/models/application.py b/archinstall/lib/models/application.py index 614cb79a73..715155f8cd 100644 --- a/archinstall/lib/models/application.py +++ b/archinstall/lib/models/application.py @@ -82,6 +82,7 @@ def parse_arg(arg: dict[str, Any]) -> 'PowerManagementConfiguration': ) +@dataclass class PrintServiceConfiguration: enabled: bool From 2ae217b1505d115f012928c9073f4456527d40ae Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:17:19 -0500 Subject: [PATCH 7/8] fix: give has_battery a return type to make linter happy --- archinstall/lib/hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archinstall/lib/hardware.py b/archinstall/lib/hardware.py index 2cce830896..459a004149 100644 --- a/archinstall/lib/hardware.py +++ b/archinstall/lib/hardware.py @@ -211,7 +211,7 @@ def graphics_devices(self) -> dict[str, str]: class SysInfo: @staticmethod - def has_battery(): + def has_battery() -> bool: for type_path in Path('/sys/class/power_supply/').glob('*/type'): try: with open(type_path) as f: From 09cb91ab6b6092699a67aa90f9e09052665a92c2 Mon Sep 17 00:00:00 2001 From: "Mariya T." <235426764+sigreturn@users.noreply.github.com> Date: Fri, 26 Dec 2025 16:23:04 -0500 Subject: [PATCH 8/8] chore: add locale msgids for power management related strings --- archinstall/locales/base.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/archinstall/locales/base.pot b/archinstall/locales/base.pot index 4afd74ab7f..4facbf9201 100644 --- a/archinstall/locales/base.pot +++ b/archinstall/locales/base.pot @@ -1788,6 +1788,9 @@ msgstr "" msgid "Would you like to configure the print service?" msgstr "" +msgid "Power management daemon" +msgstr "" + msgid "Authentication" msgstr ""