This commit is contained in:
cheykrym 2026-04-01 05:28:03 +03:00
parent ae558f1362
commit 9bb3c1b2df

View File

@ -1,10 +1,11 @@
from __future__ import annotations from __future__ import annotations
import os import os
import select import subprocess
import struct import re
import threading import threading
import logging import logging
import time
from pathlib import Path from pathlib import Path
from typing import Callable from typing import Callable
@ -122,70 +123,110 @@ class IrRemoteService(QObject):
self._thread = None self._thread = None
def _event_loop(self) -> None: def _event_loop(self) -> None:
"""Цикл обработки событий.""" """Цикл обработки событий через ir-keytable."""
logger.info(f"Event loop started for {self._device_path}") logger.info(f"Starting ir-keytable event loop")
last_scancode = None
last_time = 0
min_delay = 0.3 # Минимальная задержка между нажатиями (сек)
key_held = False # Флаг: кнопка зажата
while self._running: while self._running:
try: try:
with open(self._device_path, "rb") as f: # Запускаем ir-keytable -t для чтения событий
logger.info(f"Opened {self._device_path} successfully") # Пробуем без sudo, если не работает - добавь правило в sudoers
while self._running: proc = subprocess.Popen(
# Используем select для неблокирующего чтения ["ir-keytable", "-t"],
ready, _, _ = select.select([f], [], [], 0.5) stdout=subprocess.PIPE,
if not ready: stderr=subprocess.PIPE,
text=True,
bufsize=1
)
logger.info("ir-keytable started")
while self._running and proc.poll() is None:
line = proc.stdout.readline()
if not line:
break
line = line.strip()
# Игнорируем EV_SYN события (это просто разделители)
if "EV_SYN" in line:
continue
logger.debug(f"ir-keytable output: {line}")
# Проверяем отпускание кнопки (EV_KEY с value=0)
if "EV_KEY" in line and "value=0" in line:
logger.debug("Key release detected")
key_held = False
last_scancode = None
self.reset_button_state()
continue
# Парсим строку вида:
# 23567.604034: lirc protocol(nec): scancode = 0x19
# 23567.604034: event type EV_MSC(0x04): scancode = 0x19
match = re.search(r'scancode\s*=\s*(0x[0-9a-fA-F]+)', line)
if match:
current_time = time.time()
scancode = match.group(1).lower()
# Проверяем есть ли "repeat" в строке
is_repeat = "repeat" in line.lower()
# Если пришла другая кнопка — сбрасываем состояние предыдущей
if last_scancode and scancode != last_scancode and not is_repeat:
logger.debug(f"New button pressed, resetting previous: {last_scancode} -> {scancode}")
key_held = False
self._repeat_counts.pop(last_scancode, None)
self._hold_triggered.pop(last_scancode, None)
# Если кнопка была отпущена и пришло новое нажатие
if not key_held and not is_repeat:
key_held = True
self._repeat_counts[scancode] = 1
logger.debug(f"New key press: {scancode}")
# Если кнопка зажата и пришло repeat
elif key_held and is_repeat:
self._repeat_counts[scancode] = self._repeat_counts.get(scancode, 1) + 1
logger.debug(f"Key repeat: {scancode}, count={self._repeat_counts[scancode]}")
# Игнорируем дубли
else:
logger.debug(f"Skipping: key_held={key_held}, is_repeat={is_repeat}")
continue continue
# Читаем событие (24 байта: tv_sec, tv_usec, type, code, value) # Проверяем задержку между разными нажатиями
data = f.read(24) if scancode != last_scancode and (current_time - last_time) < min_delay:
if len(data) < 24: logger.debug(f"Skipping too fast: {current_time - last_time:.3f}s < {min_delay}s")
logger.warning(f"Read incomplete data: {len(data)} bytes")
continue continue
# Логируем сырые байты для отладки logger.debug(f"Processing scancode: {scancode}, is_repeat: {is_repeat}")
logger.debug(f"Raw bytes: {data.hex()}") self._handle_scancode(scancode, is_repeat)
last_scancode = scancode
tv_sec, tv_usec, ev_type, ev_code, ev_value = struct.unpack("llHHI", data) last_time = current_time
logger.debug(f"Raw event: type={ev_type} (0x{ev_type:02x}), code={ev_code} (0x{ev_code:02x}), value={ev_value} (0x{ev_value:02x})") proc.terminate()
proc.wait(timeout=1)
# Обрабатываем MSC_SCAN события (скан-код)
if ev_type == EV_MSC and ev_code == MSC_SCAN: except Exception as e:
scancode = f"0x{ev_value:02x}" logger.warning(f"ir-keytable error: {e}, retrying in 1s...")
logger.debug(f"MSC_SCAN event: scancode={scancode}")
self._handle_scancode(scancode)
# Обрабатываем EV_KEY события (отпускание кнопки)
elif ev_type == EV_KEY and ev_value == KEY_UP:
logger.debug(f"KEY_UP event: code={ev_code}")
# Кнопка отпущена - сбрасываем состояние
self.reset_button_state()
except (OSError, IOError) as e:
# Устройство недоступно, ждём и пробуем снова
logger.warning(f"Device error: {e}, retrying...")
import time
time.sleep(1.0) time.sleep(1.0)
def _handle_scancode(self, scancode: str) -> None: def _handle_scancode(self, scancode: str, is_repeat: bool = False) -> None:
"""Обработать скан-код кнопки.""" """Обработать скан-код кнопки."""
action = self._scancode_to_action.get(scancode) action = self._scancode_to_action.get(scancode)
if not action: if not action:
logger.debug(f"Unknown scancode: {scancode}") logger.debug(f"Unknown scancode: {scancode}")
return return
logger.debug(f"Scancode: {scancode}, action: {action}") repeat_count = self._repeat_counts.get(scancode, 1)
# Увеличиваем счётчик повторений
self._repeat_counts[scancode] = self._repeat_counts.get(scancode, 0) + 1
repeat_count = self._repeat_counts[scancode]
# Проверяем, было ли зажатие уже обработано
hold_triggered = self._hold_triggered.get(scancode, False) hold_triggered = self._hold_triggered.get(scancode, False)
# Определяем тип события
is_repeat = repeat_count > 1
is_hold = repeat_count >= build_info.IR_REPEAT_COUNT_FOR_HOLD is_hold = repeat_count >= build_info.IR_REPEAT_COUNT_FOR_HOLD
logger.debug(f" repeat_count={repeat_count}, is_repeat={is_repeat}, is_hold={is_hold}") logger.debug(f"Scancode: {scancode}, action: {action}, repeat_count={repeat_count}, is_hold={is_hold}")
# Отправляем сигналы # Отправляем сигналы
if action == "play_pause": if action == "play_pause":