From e4970d2477d5561bbd53edb1fb7f7e16574f118c Mon Sep 17 00:00:00 2001 From: Boris Yumankulov Date: Tue, 28 Jul 2026 15:14:25 +0500 Subject: [PATCH] chore: use systemctl and loginctl for system actions Signed-off-by: Boris Yumankulov --- CHANGELOG.md | 2 +- portprotonqt/tabs/system_tab.py | 25 ++++++++++------- tests/test_main_window.py | 50 +++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42ff38b..22f3fd49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ - Обновлён перевод на Китайский (упрощённый) `https://translate.codeberg.org/projects/portprotonqt/portprotonqt/zh_Hans/` - Возвращено отображение нативных Linux-игр в библиотеке Steam без отображения саундтреков - После создания или удаления ярлыков меню обновляется база desktop-файлов - ### Fixed +- Системные действия в режиме `--ppqtos` переведены на `systemctl` и `loginctl` - Исправлен ввод кириллицы при поиске игр - Поиск игр ограничен названиями без совпадений по описаниям - Исправлено состояние кнопки остановки при переключении детальной страницы на компактный вид diff --git a/portprotonqt/tabs/system_tab.py b/portprotonqt/tabs/system_tab.py index f02d6128..84e7c0b1 100644 --- a/portprotonqt/tabs/system_tab.py +++ b/portprotonqt/tabs/system_tab.py @@ -46,6 +46,7 @@ logger = get_logger(__name__) AUDIO_MAX_VOLUME = 150 + if TYPE_CHECKING: from PySide6.QtWidgets import QMainWindow @@ -896,25 +897,29 @@ class MainWindowSystemTabMixin(_MainWindowTypingBase): layout.addLayout(buttons_layout) layout.addStretch() - def _runLoginctlAction(self, action: str) -> None: - args = [action] - if action == "terminate-session": - args.append("self") - if QProcess.startDetached("loginctl", args): + def _runSystemAction(self, action: str) -> None: + command = "systemctl" if os.path.isdir("/run/systemd/system") else "loginctl" + if QProcess.startDetached(command, [action]): return - logger.error("Failed to execute loginctl %s", " ".join(args)) + logger.error("Failed to execute %s %s", command, action) def rebootSystem(self) -> None: - self._runLoginctlAction("reboot") + self._runSystemAction("reboot") def shutdownSystem(self) -> None: - self._runLoginctlAction("poweroff") + self._runSystemAction("poweroff") def suspendSystem(self) -> None: - self._runLoginctlAction("suspend") + self._runSystemAction("suspend") def logoutSystem(self) -> None: - self._runLoginctlAction("terminate-session") + session_id = os.getenv("XDG_SESSION_ID") + if not session_id: + logger.error("Cannot log out: XDG_SESSION_ID is not set") + return + if QProcess.startDetached("loginctl", ["terminate-session", session_id]): + return + logger.error("Failed to execute loginctl terminate-session %s", session_id) def loadSystemNetworks(self) -> None: self.runNetworkOperation("load") diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 8a44935b..d011c248 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -20,10 +20,12 @@ from portprotonqt.portproton_api import remove_empty_custom_data_dirs import portprotonqt.tabs.autoinstall_tab as autoinstall_tab_module import portprotonqt.tabs.gog_tab as gog_tab_module import portprotonqt.tabs.library_tab as library_tab_module +import portprotonqt.tabs.system_tab as system_tab_module from portprotonqt.tabs import ( MainWindowAutoInstallTabMixin, MainWindowLibraryTabMixin, MainWindowSettingsTabMixin, + MainWindowSystemTabMixin, MainWindowThemeTabMixin, MainWindowWineTabMixin, ) @@ -1294,3 +1296,51 @@ def test_update_delete_missing_exe_button_visibility(tmp_path: Path) -> None: MainWindowLibraryTabMixin.updateDeleteMissingExeButton(window) assert button.visible is True + + +def test_system_action_uses_systemctl_with_systemd(monkeypatch: MonkeyPatch) -> None: + calls: list[tuple[str, list[str]]] = [] + process = SimpleNamespace(startDetached=lambda *args: calls.append(args) or True) + window = cast(MainWindowSystemTabMixin, SimpleNamespace()) + monkeypatch.setattr(system_tab_module.os.path, "isdir", lambda path: True) + monkeypatch.setattr(system_tab_module, "QProcess", process) + + MainWindowSystemTabMixin._runSystemAction(window, "reboot") + + assert calls == [("systemctl", ["reboot"])] + + +def test_system_action_uses_loginctl_with_elogind(monkeypatch: MonkeyPatch) -> None: + calls: list[tuple[str, list[str]]] = [] + process = SimpleNamespace(startDetached=lambda *args: calls.append(args) or True) + window = cast(MainWindowSystemTabMixin, SimpleNamespace()) + monkeypatch.setattr(system_tab_module.os.path, "isdir", lambda path: False) + monkeypatch.setattr(system_tab_module, "QProcess", process) + + MainWindowSystemTabMixin._runSystemAction(window, "suspend") + + assert calls == [("loginctl", ["suspend"])] + + +def test_logout_uses_current_session_id(monkeypatch: MonkeyPatch) -> None: + calls: list[tuple[str, list[str]]] = [] + process = SimpleNamespace(startDetached=lambda *args: calls.append(args) or True) + window = cast(MainWindowSystemTabMixin, SimpleNamespace()) + monkeypatch.setenv("XDG_SESSION_ID", "session-1") + monkeypatch.setattr(system_tab_module, "QProcess", process) + + MainWindowSystemTabMixin.logoutSystem(window) + + assert calls == [("loginctl", ["terminate-session", "session-1"])] + + +def test_logout_skips_without_session_id(monkeypatch: MonkeyPatch) -> None: + calls: list[tuple[str, list[str]]] = [] + process = SimpleNamespace(startDetached=lambda *args: calls.append(args) or True) + window = cast(MainWindowSystemTabMixin, SimpleNamespace()) + monkeypatch.delenv("XDG_SESSION_ID", raising=False) + monkeypatch.setattr(system_tab_module, "QProcess", process) + + MainWindowSystemTabMixin.logoutSystem(window) + + assert calls == [] -- 2.54.0