Compare commits
3 Commits
4ab078b93e
...
10d3fe8ab4
| Author | SHA1 | Date | |
|---|---|---|---|
|
10d3fe8ab4
|
|||
|
a568ad9ef8
|
|||
|
f074843fc8
|
@@ -7,12 +7,19 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- В настройки добавлен пункт для выбора типа геймпада для подсказок по управлению
|
- В настройки добавлен пункт для выбора типа геймпада для подсказок по управлению
|
||||||
|
- В настройки добавлен пункт для выбора сворачивать ли приложение в трей или нет
|
||||||
|
- К диалогу добавления игры, Winetricks, диалогу выбора файлов и виртуальной клавиатуре добавлены подсказки по управлению с геймпада
|
||||||
|
- Во вкладку автоустановок добавлен слайдер изменения размера карточек (они со слайдером в библиотеке независимы)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- При завершении автоустановки приложение больше не перезапускается
|
- При завершении автоустановки приложение больше не перезапускается
|
||||||
|
- Выбор exe в диалоге добавления игры больше не перезаписывает введенное в поле название
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Исправлено наложение карточек при смене фильтра игр
|
- Исправлено наложение карточек при смене фильтра игр
|
||||||
|
- Исправлена невозможность запуска приложения без подключёного геймпада
|
||||||
|
- Исправлена невозможность установки компонентов Winetricks через геймпад
|
||||||
|
- Ресиверы и виртуальные устройства больше не считаются за геймпад
|
||||||
|
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
|
|||||||
@@ -1034,8 +1034,8 @@ class AddGameDialog(QDialog):
|
|||||||
"""Обработчик выбора файла в FileExplorer"""
|
"""Обработчик выбора файла в FileExplorer"""
|
||||||
self.exeEdit.setText(file_path)
|
self.exeEdit.setText(file_path)
|
||||||
self.last_exe_path = file_path # Update last selected exe path
|
self.last_exe_path = file_path # Update last selected exe path
|
||||||
if not self.edit_mode:
|
if not self.edit_mode and not self.nameEdit.text().strip():
|
||||||
# Автоматически заполняем имя игры, если не в режиме редактирования
|
# Автоматически заполняем имя игры, если не в режиме редактирования или если оно не введено вручную
|
||||||
game_name = os.path.splitext(os.path.basename(file_path))[0]
|
game_name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
self.nameEdit.setText(game_name)
|
self.nameEdit.setText(game_name)
|
||||||
|
|
||||||
|
|||||||
@@ -1456,52 +1456,69 @@ class InputManager(QObject):
|
|||||||
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:
|
||||||
"""
|
"""
|
||||||
Неблокирующий опрос udev событий без MonitorObserver.
|
Безопасный неблокирующий udev monitor для геймпадов.
|
||||||
Использует monitor.poll() с таймаутом для корректного завершения.
|
Использует select.poll() вместо блокирующего monitor.poll().
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logger.info("Starting udev monitor...")
|
logger.info("Starting udev monitor...")
|
||||||
monitor = Monitor.from_netlink(self.udev_context)
|
monitor = Monitor.from_netlink(self.udev_context)
|
||||||
monitor.filter_by(subsystem='input')
|
monitor.filter_by(subsystem='input')
|
||||||
monitor.start()
|
|
||||||
logger.info("Monitor started, draining initial events...")
|
|
||||||
|
|
||||||
# КРИТИЧНО: При старте udev отправляет события о ВСЕХ существующих устройствах
|
try:
|
||||||
# Это может быть 10-50+ событий, которые блокируют инициализацию
|
monitor.start()
|
||||||
# Решение: дренируем (игнорируем) все события за первые 500ms
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to start udev monitor: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
import select
|
||||||
|
fd = monitor.fileno()
|
||||||
|
poller = select.poll()
|
||||||
|
poller.register(fd, select.POLLIN)
|
||||||
|
|
||||||
|
# Короткий дренаж событий при запуске (0.5 сек)
|
||||||
drain_start = time.time()
|
drain_start = time.time()
|
||||||
drained_count = 0
|
drained_count = 0
|
||||||
|
|
||||||
while time.time() - drain_start < 0.5:
|
while time.time() - drain_start < 0.5:
|
||||||
device = monitor.poll(timeout=0.1)
|
events = poller.poll(100)
|
||||||
if device is not None:
|
if not events:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
_ = monitor.poll(timeout=0) # просто читаем, не обрабатываем
|
||||||
drained_count += 1
|
drained_count += 1
|
||||||
|
except Exception:
|
||||||
|
break
|
||||||
|
|
||||||
self.monitor_ready = True
|
self.monitor_ready = True
|
||||||
logger.info(f"Drained {drained_count} initial events, now monitoring hotplug...")
|
logger.info(f"Drained {drained_count} initial events, now monitoring hotplug...")
|
||||||
|
|
||||||
# Основной цикл опроса с таймаутом 1 секунда
|
# Основной цикл
|
||||||
while self.running:
|
while self.running:
|
||||||
# poll() возвращает None при таймауте - не блокирует навсегда
|
events = poller.poll(1000) # 1 сек таймаут
|
||||||
device = monitor.poll(timeout=1.0)
|
if not events:
|
||||||
|
continue # просто ждём, не блокируем
|
||||||
|
|
||||||
if device is not None:
|
try:
|
||||||
action = device.action
|
device = monitor.poll(timeout=0)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Monitor poll failed: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
# Фильтруем только джойстики на уровне callback
|
if not device:
|
||||||
# Это предотвращает обработку мышей/клавиатур/и т.д.
|
continue
|
||||||
if action and self._is_joystick_device(device):
|
|
||||||
logger.info(f"Joystick hotplug event: {action} for {device.sys_name}")
|
action = device.action
|
||||||
self.handle_udev_event(action, device)
|
if action and self._is_joystick_device(device):
|
||||||
|
logger.info(f"Joystick hotplug event: {action} for {device.sys_name}")
|
||||||
|
# отправляем сигнал в Qt-поток
|
||||||
|
self.handle_udev_event(action, device)
|
||||||
|
|
||||||
logger.info("udev monitor stopped gracefully")
|
logger.info("udev monitor 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)
|
||||||
|
|
||||||
|
|
||||||
def _is_joystick_device(self, device: Device) -> bool:
|
def _is_joystick_device(self, device: Device) -> bool:
|
||||||
"""
|
"""
|
||||||
Быстрая проверка: является ли устройство джойстиком.
|
Быстрая проверка: является ли устройство джойстиком.
|
||||||
|
|||||||
Reference in New Issue
Block a user