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