forked from Boria138/PortProtonQt
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			596aed0077
			...
			cde92885d4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cde92885d4 | |||
| 120c7b319c | 
| @@ -4,7 +4,7 @@ import os | |||||||
| from typing import Protocol, cast | from typing import Protocol, cast | ||||||
| from evdev import InputDevice, InputEvent, ecodes, list_devices, ff | from evdev import InputDevice, InputEvent, ecodes, list_devices, ff | ||||||
| from enum import Enum | from enum import Enum | ||||||
| from pyudev import Context, Monitor, MonitorObserver, Device | from pyudev import Context, Monitor, MonitorObserver, Device, Devices | ||||||
| from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView, QTableWidgetItem | from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView, QTableWidgetItem | ||||||
| from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer | from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer | ||||||
| from PySide6.QtGui import QKeyEvent, QMouseEvent | from PySide6.QtGui import QKeyEvent, QMouseEvent | ||||||
| @@ -1436,19 +1436,31 @@ class InputManager(QObject): | |||||||
|         return super().eventFilter(obj, event) |         return super().eventFilter(obj, event) | ||||||
|  |  | ||||||
|     def init_gamepad(self) -> None: |     def init_gamepad(self) -> None: | ||||||
|         self.monitor_observer = None  # Добавляем атрибут для хранения observer |         self.monitor_observer = None | ||||||
|  |         self.udev_context = Context()  # Создаём context один раз | ||||||
|  |         self.Devices = Devices  # Сохраняем класс для использования в других методах | ||||||
|         self.check_gamepad() |         self.check_gamepad() | ||||||
|         threading.Thread(target=self.run_udev_monitor, daemon=True).start() |         threading.Thread(target=self.run_udev_monitor, daemon=True).start() | ||||||
|         logger.info("Gamepad support initialized with hotplug (evdev + pyudev)") |         logger.info("Gamepad support initialized with hotplug (evdev + pyudev)") | ||||||
|  |  | ||||||
|     def run_udev_monitor(self) -> None: |     def run_udev_monitor(self) -> None: | ||||||
|         try: |         try: | ||||||
|             context = Context() |             logger.info("Starting udev monitor...") | ||||||
|             monitor = Monitor.from_netlink(context) |             monitor = Monitor.from_netlink(self.udev_context) | ||||||
|             monitor.filter_by(subsystem='input') |             monitor.filter_by(subsystem='input') | ||||||
|  |             logger.info("Monitor created and filtered") | ||||||
|  |  | ||||||
|             observer = MonitorObserver(monitor, self.handle_udev_event) |             observer = MonitorObserver(monitor, self.handle_udev_event) | ||||||
|             self.monitor_observer = observer  # Сохраняем ссылку для остановки |             self.monitor_observer = observer | ||||||
|             observer.start()  # Это блокирует поток до вызова send_stop() |             logger.info("MonitorObserver created") | ||||||
|  |  | ||||||
|  |             observer.start() | ||||||
|  |             logger.info("MonitorObserver started") | ||||||
|  |  | ||||||
|  |             # Держим поток живым, пока не получим сигнал остановки | ||||||
|  |             while self.running: | ||||||
|  |                 time.sleep(1) | ||||||
|  |  | ||||||
|             logger.info("MonitorObserver stopped gracefully") |             logger.info("MonitorObserver stopped gracefully") | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             logger.error(f"Error in udev monitor: {e}", exc_info=True) |             logger.error(f"Error in udev monitor: {e}", exc_info=True) | ||||||
| @@ -1492,14 +1504,33 @@ class InputManager(QObject): | |||||||
|     def find_gamepad(self) -> InputDevice | None: |     def find_gamepad(self) -> InputDevice | None: | ||||||
|         try: |         try: | ||||||
|             devices = [InputDevice(path) for path in list_devices()] |             devices = [InputDevice(path) for path in list_devices()] | ||||||
|  |             logger.info(f"Checking {len(devices)} devices for gamepad...") | ||||||
|  |  | ||||||
|             for device in devices: |             for device in devices: | ||||||
|  |                 logger.debug(f"Checking device: {device.name} at {device.path}") | ||||||
|  |  | ||||||
|                 # Skip ASRock LED controller (vendor ID: 26ce, product ID: 01a2) |                 # Skip ASRock LED controller (vendor ID: 26ce, product ID: 01a2) | ||||||
|                 if device.info.vendor == 0x26ce and device.info.product == 0x01a2: |                 if device.info.vendor == 0x26ce and device.info.product == 0x01a2: | ||||||
|                     logger.debug(f"Skipping ASRock LED controller: {device.name}") |                     logger.debug(f"Skipping ASRock LED controller: {device.name}") | ||||||
|                     continue |                     continue | ||||||
|                 caps = device.capabilities() |  | ||||||
|                 if ecodes.EV_KEY in caps or ecodes.EV_ABS in caps: |                 # Получаем udev-устройство для проверки ID_INPUT_JOYSTICK | ||||||
|                     return device |                 try: | ||||||
|  |                     udev_device = self.Devices.from_device_file(self.udev_context, device.path) | ||||||
|  |                     is_joystick = udev_device.get('ID_INPUT_JOYSTICK') | ||||||
|  |  | ||||||
|  |                     logger.debug(f"Device {device.name}: ID_INPUT_JOYSTICK = {is_joystick}") | ||||||
|  |  | ||||||
|  |                     if is_joystick == '1': | ||||||
|  |                         logger.info(f"Found gamepad: {device.name}") | ||||||
|  |                         return device | ||||||
|  |                     else: | ||||||
|  |                         logger.debug(f"Skipping non-joystick device: {device.name}") | ||||||
|  |                 except Exception as e: | ||||||
|  |                     logger.warning(f"Could not check udev properties for {device.path}: {e}") | ||||||
|  |                     continue | ||||||
|  |  | ||||||
|  |             logger.warning("No gamepad found") | ||||||
|             return None |             return None | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             logger.error(f"Error finding gamepad: {e}", exc_info=True) |             logger.error(f"Error finding gamepad: {e}", exc_info=True) | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| from typing import cast | from typing import cast, Any | ||||||
| from PySide6.QtWidgets import (QFrame, QVBoxLayout, QPushButton, QGridLayout, | from PySide6.QtWidgets import (QFrame, QVBoxLayout, QPushButton, QGridLayout, | ||||||
|                                QSizePolicy, QWidget, QLineEdit) |                                QSizePolicy, QWidget, QLineEdit) | ||||||
| from PySide6.QtCore import Qt, Signal, QProcess | from PySide6.QtCore import Qt, Signal, QProcess, QSize | ||||||
|  | from PySide6.QtGui import QPixmap, QIcon | ||||||
| from portprotonqt.keyboard_layouts import keyboard_layouts | from portprotonqt.keyboard_layouts import keyboard_layouts | ||||||
| from portprotonqt.theme_manager import ThemeManager | from portprotonqt.theme_manager import ThemeManager | ||||||
| from portprotonqt.config_utils import read_theme_from_config | from portprotonqt.config_utils import read_theme_from_config | ||||||
| @@ -43,6 +44,18 @@ class VirtualKeyboard(QFrame): | |||||||
|         self.margins = 10 |         self.margins = 10 | ||||||
|         self.num_cols = 14 |         self.num_cols = 14 | ||||||
|  |  | ||||||
|  |         # Find input_manager and main_window | ||||||
|  |         self.input_manager: Any = None | ||||||
|  |         self.main_window: Any = None | ||||||
|  |         parent_widget: QWidget | None = self._parent | ||||||
|  |         while parent_widget: | ||||||
|  |             if hasattr(parent_widget, 'input_manager'): | ||||||
|  |                 self.input_manager = cast(Any, parent_widget).input_manager | ||||||
|  |                 self.main_window = cast(Any, parent_widget) | ||||||
|  |             parent_widget = cast(QWidget | None, parent_widget.parent()) | ||||||
|  |  | ||||||
|  |         self.current_theme_name = read_theme_from_config() | ||||||
|  |  | ||||||
|         self.initUI() |         self.initUI() | ||||||
|         self.hide() |         self.hide() | ||||||
|  |  | ||||||
| @@ -119,6 +132,28 @@ class VirtualKeyboard(QFrame): | |||||||
|         self.buttons: dict[str, QPushButton] = {} |         self.buttons: dict[str, QPushButton] = {} | ||||||
|         self.update_keyboard() |         self.update_keyboard() | ||||||
|  |  | ||||||
|  |     def set_gamepad_icon(self, button, icon_type, gtype='default'): | ||||||
|  |         """Set gamepad icon on button based on type. Now works even without gamepad by using 'default' gtype.""" | ||||||
|  |         if icon_type in ['back', 'add_game']: | ||||||
|  |             icon_name = self.main_window.get_button_icon(icon_type, gtype) if self.main_window else f"{icon_type}_default.png" | ||||||
|  |         else:  # nav left/right | ||||||
|  |             if icon_type in ['left', 'right']: | ||||||
|  |                 direction = icon_type | ||||||
|  |                 icon_name = self.main_window.get_nav_icon(direction, gtype) if self.main_window else f"{direction}_default.png" | ||||||
|  |             else: | ||||||
|  |                 direction = 'left' if icon_type == 'left' else 'right' | ||||||
|  |                 icon_name = self.main_window.get_nav_icon(direction, gtype) if self.main_window else f"{direction}_default.png" | ||||||
|  |  | ||||||
|  |         icon_path = theme_manager.get_theme_image(icon_name, self.current_theme_name) | ||||||
|  |         if icon_path: | ||||||
|  |             pixmap = QPixmap(str(icon_path)) | ||||||
|  |             if not pixmap.isNull(): | ||||||
|  |                 button.setIcon(QIcon(pixmap)) | ||||||
|  |                 button.setIconSize(QSize(20, 20)) | ||||||
|  |                 return | ||||||
|  |         # Fallback: if no icon found, try standard Qt icon or leave empty | ||||||
|  |         print(f"Warning: Icon {icon_name} not found for button {icon_type}") | ||||||
|  |  | ||||||
|     def update_keyboard(self): |     def update_keyboard(self): | ||||||
|         coords = self._save_focused_coords() |         coords = self._save_focused_coords() | ||||||
|  |  | ||||||
| @@ -151,6 +186,9 @@ class VirtualKeyboard(QFrame): | |||||||
|                     button.setCheckable(True) |                     button.setCheckable(True) | ||||||
|                     button.setChecked(self.shift_pressed) |                     button.setChecked(self.shift_pressed) | ||||||
|                     button.clicked.connect(lambda checked: self.on_shift_click(checked)) |                     button.clicked.connect(lambda checked: self.on_shift_click(checked)) | ||||||
|  |                     # Add gamepad icon for Shift (RB/R) | ||||||
|  |                     gtype = self.input_manager.gamepad_type if self.input_manager and self.input_manager.gamepad else 'default' | ||||||
|  |                     self.set_gamepad_icon(button, 'right', gtype) | ||||||
|                 else: |                 else: | ||||||
|                     button.clicked.connect(lambda checked=False, k=key: self.on_button_click(k)) |                     button.clicked.connect(lambda checked=False, k=key: self.on_button_click(k)) | ||||||
|  |  | ||||||
| @@ -163,6 +201,9 @@ class VirtualKeyboard(QFrame): | |||||||
|         shift.setCheckable(True) |         shift.setCheckable(True) | ||||||
|         shift.setChecked(self.shift_pressed) |         shift.setChecked(self.shift_pressed) | ||||||
|         shift.clicked.connect(lambda checked: self.on_shift_click(checked)) |         shift.clicked.connect(lambda checked: self.on_shift_click(checked)) | ||||||
|  |         # Add gamepad icon for Shift (RB/R) | ||||||
|  |         gtype = self.input_manager.gamepad_type if self.input_manager and self.input_manager.gamepad else 'default' | ||||||
|  |         self.set_gamepad_icon(shift, 'right', gtype) | ||||||
|         self.keyboard_layout.addWidget(shift, 3, 11, 1, 3) |         self.keyboard_layout.addWidget(shift, 3, 11, 1, 3) | ||||||
|  |  | ||||||
|         button = QPushButton('CAPS') |         button = QPushButton('CAPS') | ||||||
| @@ -179,6 +220,9 @@ class VirtualKeyboard(QFrame): | |||||||
|         backspace.setFixedSize(fixed_w, fixed_h) |         backspace.setFixedSize(fixed_w, fixed_h) | ||||||
|         backspace.pressed.connect(self.on_backspace_pressed) |         backspace.pressed.connect(self.on_backspace_pressed) | ||||||
|         backspace.released.connect(self.stop_backspace_repeat) |         backspace.released.connect(self.stop_backspace_repeat) | ||||||
|  |         # Add gamepad icon for Backspace (X/Triangle) | ||||||
|  |         gtype = self.input_manager.gamepad_type if self.input_manager and self.input_manager.gamepad else 'default' | ||||||
|  |         self.set_gamepad_icon(backspace, 'add_game', gtype) | ||||||
|         self.keyboard_layout.addWidget(backspace, 0, 13, 1, 1) |         self.keyboard_layout.addWidget(backspace, 0, 13, 1, 1) | ||||||
|  |  | ||||||
|         enter = QPushButton('Enter') |         enter = QPushButton('Enter') | ||||||
| @@ -189,6 +233,9 @@ class VirtualKeyboard(QFrame): | |||||||
|         lang = QPushButton('🌐') |         lang = QPushButton('🌐') | ||||||
|         lang.setFixedSize(fixed_w, fixed_h) |         lang.setFixedSize(fixed_w, fixed_h) | ||||||
|         lang.clicked.connect(self.on_lang_click) |         lang.clicked.connect(self.on_lang_click) | ||||||
|  |         # Add gamepad icon for Lang (LB/L) | ||||||
|  |         gtype = self.input_manager.gamepad_type if self.input_manager and self.input_manager.gamepad else 'default' | ||||||
|  |         self.set_gamepad_icon(lang, 'left', gtype) | ||||||
|         self.keyboard_layout.addWidget(lang, 4, 0, 1, 1) |         self.keyboard_layout.addWidget(lang, 4, 0, 1, 1) | ||||||
|  |  | ||||||
|         clear = QPushButton('Clear') |         clear = QPushButton('Clear') | ||||||
| @@ -219,6 +266,9 @@ class VirtualKeyboard(QFrame): | |||||||
|         hide_button = QPushButton('Hide') |         hide_button = QPushButton('Hide') | ||||||
|         hide_button.setFixedSize(fixed_w * 2 + self.spacing, fixed_h) |         hide_button.setFixedSize(fixed_w * 2 + self.spacing, fixed_h) | ||||||
|         hide_button.clicked.connect(self.hide) |         hide_button.clicked.connect(self.hide) | ||||||
|  |         # Add gamepad icon for Hide (B/Circle) | ||||||
|  |         gtype = self.input_manager.gamepad_type if self.input_manager and self.input_manager.gamepad else 'default' | ||||||
|  |         self.set_gamepad_icon(hide_button, 'back', gtype) | ||||||
|         self.keyboard_layout.addWidget(hide_button, 4, 12, 1, 2) |         self.keyboard_layout.addWidget(hide_button, 4, 12, 1, 2) | ||||||
|  |  | ||||||
|         if coords: |         if coords: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user