From 82249d7eab11540b6c8f597bf80cfd0a8a9c377f Mon Sep 17 00:00:00 2001 From: Boris Yumankulov Date: Wed, 15 Oct 2025 15:30:31 +0500 Subject: [PATCH] feat(settings): Added Gamepad type settings Signed-off-by: Boris Yumankulov --- portprotonqt/config_utils.py | 19 +++++++++++++++ portprotonqt/input_manager.py | 45 +++++++---------------------------- portprotonqt/main_window.py | 42 +++++++++++++++++++++++++------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/portprotonqt/config_utils.py b/portprotonqt/config_utils.py index 999ba14..8ec3047 100644 --- a/portprotonqt/config_utils.py +++ b/portprotonqt/config_utils.py @@ -259,6 +259,25 @@ def save_rumble_config(rumble_enabled): with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: cp.write(configfile) +def read_gamepad_type(): + """Reads the gamepad type from the [Gamepad] section. + Returns 'xbox' if the parameter is missing. + """ + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "type"): + save_gamepad_type("xbox") + return "xbox" + return cp.get("Gamepad", "type", fallback="xbox").lower() + +def save_gamepad_type(gpad_type): + """Saves the gamepad type to the [Gamepad] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() + if "Gamepad" not in cp: + cp["Gamepad"] = {} + cp["Gamepad"]["type"] = gpad_type + with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: + cp.write(configfile) + def ensure_default_proxy_config(): """Ensures the [Proxy] section exists in the configuration file. Creates it with empty values if missing. diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py index 08cbd02..6120219 100644 --- a/portprotonqt/input_manager.py +++ b/portprotonqt/input_manager.py @@ -12,7 +12,7 @@ from portprotonqt.logger import get_logger from portprotonqt.image_utils import FullscreenDialog from portprotonqt.custom_widgets import NavLabel, AutoSizeButton from portprotonqt.game_card import GameCard -from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config +from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config, read_gamepad_type from portprotonqt.dialogs import AddGameDialog, WinetricksDialog from portprotonqt.virtual_keyboard import VirtualKeyboard @@ -87,8 +87,13 @@ class InputManager(QObject): super().__init__(cast(QObject, main_window)) self._parent = main_window self._gamepad_handling_enabled = True - self.gamepad_type = GamepadType.UNKNOWN - # Ensure attributes exist on main_window + type_str = read_gamepad_type() + if type_str == "playstation": + self.gamepad_type = GamepadType.PLAYSTATION + elif type_str == "xbox": + self.gamepad_type = GamepadType.XBOX + else: + self.gamepad_type = GamepadType.UNKNOWN self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None) self._parent.current_exec_line = getattr(self._parent, 'current_exec_line', None) self._parent.current_add_game_dialog = getattr(self._parent, 'current_add_game_dialog', None) @@ -271,38 +276,6 @@ class InputManager(QObject): elif current_row_idx == 0: self._parent.tabButtons[tab_index].setFocus(Qt.FocusReason.OtherFocusReason) - def detect_gamepad_type(self, device: InputDevice) -> GamepadType: - """ - Определяет тип геймпада по capabilities - """ - caps = device.capabilities() - keys = set(caps.get(ecodes.EV_KEY, [])) - - # Для EV_ABS вытаскиваем только коды (первый элемент кортежа) - abs_axes = {a if isinstance(a, int) else a[0] for a in caps.get(ecodes.EV_ABS, [])} - - # Xbox layout - if {ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_NORTH, ecodes.BTN_WEST}.issubset(keys): - if {ecodes.ABS_X, ecodes.ABS_Y, ecodes.ABS_RX, ecodes.ABS_RY}.issubset(abs_axes): - self.gamepad_type = GamepadType.XBOX - return GamepadType.XBOX - - # PlayStation layout - if ecodes.BTN_TOUCH in keys or (ecodes.BTN_DPAD_UP in keys and ecodes.BTN_EAST in keys): - self.gamepad_type = GamepadType.PLAYSTATION - logger.info(f"Detected {self.gamepad_type.value} controller: {device.name}") - return GamepadType.PLAYSTATION - - # Steam Controller / Deck (трекпады) - if any(a for a in abs_axes if a >= ecodes.ABS_MT_SLOT): - self.gamepad_type = GamepadType.XBOX - logger.info(f"Detected {self.gamepad_type.value} controller: {device.name}") - return GamepadType.XBOX - - # Fallback - self.gamepad_type = GamepadType.XBOX - return GamepadType.XBOX - def enable_file_explorer_mode(self, file_explorer): """Настройка обработки геймпада для FileExplorer""" try: @@ -1369,8 +1342,6 @@ class InputManager(QObject): new_gamepad = self.find_gamepad() if new_gamepad and new_gamepad != self.gamepad: logger.info(f"Gamepad connected: {new_gamepad.name}") - self.detect_gamepad_type(new_gamepad) - logger.info(f"Detected gamepad type: {self.gamepad_type.value}") self.stop_rumble() self.gamepad = new_gamepad if self.gamepad_thread: diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py index 7b48fca..da6c60e 100644 --- a/portprotonqt/main_window.py +++ b/portprotonqt/main_window.py @@ -29,7 +29,7 @@ from portprotonqt.config_utils import ( read_display_filter, read_favorites, save_favorites, save_time_config, save_sort_method, save_display_filter, save_proxy_config, read_proxy_config, read_fullscreen_config, save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config, - clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config + clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config, read_gamepad_type, save_gamepad_type ) from portprotonqt.localization import _, get_egs_language, read_metadata_translations from portprotonqt.howlongtobeat_api import HowLongToBeat @@ -1784,7 +1784,22 @@ class MainWindow(QMainWindow): self.gamesDisplayCombo.setCurrentIndex(idx) formLayout.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo) - # 4. Proxy settings + # 4 Gamepad Type + self.gamepadTypeCombo = QComboBox() + self.gamepadTypeCombo.addItems(["Xbox", "PlayStation"]) + self.gamepadTypeCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus) + self.gamepadTypeCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE) + self.gamepadTypeTitle = QLabel(_("Gamepad Type:")) + self.gamepadTypeTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE) + self.gamepadTypeTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) + current_type_str = read_gamepad_type() + if current_type_str == "playstation": + self.gamepadTypeCombo.setCurrentText("PlayStation") + else: + self.gamepadTypeCombo.setCurrentText("Xbox") + formLayout.addRow(self.gamepadTypeTitle, self.gamepadTypeCombo) + + # 5. Proxy settings self.proxyUrlEdit = CustomLineEdit(self, theme=self.theme) self.proxyUrlEdit.setPlaceholderText(_("Proxy URL")) self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE) @@ -1816,7 +1831,7 @@ class MainWindow(QMainWindow): self.proxyPasswordTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) formLayout.addRow(self.proxyPasswordTitle, self.proxyPasswordEdit) - # 5. Fullscreen setting for application + # 6. Fullscreen setting for application self.fullscreenCheckBox = QCheckBox(_("Launch Application in Fullscreen")) self.fullscreenCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) self.fullscreenCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) @@ -1827,7 +1842,7 @@ class MainWindow(QMainWindow): self.fullscreenCheckBox.setChecked(current_fullscreen) formLayout.addRow(self.fullscreenTitle, self.fullscreenCheckBox) - # 6. Automatic fullscreen on gamepad connection + # 7. Automatic fullscreen on gamepad connection self.autoFullscreenGamepadCheckBox = QCheckBox(_("Auto Fullscreen on Gamepad connected")) self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) @@ -1839,7 +1854,7 @@ class MainWindow(QMainWindow): self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen) formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox) - # 7. Gamepad haptic feedback config + # 8. Gamepad haptic feedback config self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback")) self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) @@ -1850,7 +1865,7 @@ class MainWindow(QMainWindow): self.gamepadRumbleCheckBox.setChecked(current_rumble_state) formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox) - # # 8. Legendary Authentication + # # 9. Legendary Authentication # self.legendaryAuthButton = AutoSizeButton( # _("Open Legendary Login"), # icon=self.theme_manager.get_icon("login")self.theme_manager.get_icon("login") @@ -2021,8 +2036,6 @@ class MainWindow(QMainWindow): old_filter = self.current_display_filter save_display_filter(filter_key) - save_display_filter(filter_key) - proxy_url = self.proxyUrlEdit.text().strip() proxy_user = self.proxyUserEdit.text().strip() proxy_password = self.proxyPasswordEdit.text().strip() @@ -2037,6 +2050,19 @@ class MainWindow(QMainWindow): rumble_enabled = self.gamepadRumbleCheckBox.isChecked() save_rumble_config(rumble_enabled) + gamepad_type_text = self.gamepadTypeCombo.currentText() + gpad_type = "playstation" if gamepad_type_text == "PlayStation" else "xbox" + save_gamepad_type(gpad_type) + + if hasattr(self, 'input_manager'): + if gpad_type == "playstation": + self.input_manager.gamepad_type = GamepadType.PLAYSTATION + elif gpad_type == "xbox": + self.input_manager.gamepad_type = GamepadType.XBOX + else: + self.input_manager.gamepad_type = GamepadType.UNKNOWN + self.updateControlHints() + if filter_key != old_filter: for card in self.game_library_manager.game_card_cache.values(): card.update_badge_visibility(filter_key)