difonlib 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- difonlib/__init__.py +0 -0
- difonlib/bt_utils.py +553 -0
- difonlib/input_devs.py +209 -0
- difonlib/ng_lib.py +151 -0
- difonlib/py.typed +0 -0
- difonlib/remctrl.py +157 -0
- difonlib/tuya_devs.py +196 -0
- difonlib/utils.py +200 -0
- difonlib-0.2.2.dist-info/METADATA +17 -0
- difonlib-0.2.2.dist-info/RECORD +12 -0
- difonlib-0.2.2.dist-info/WHEEL +5 -0
- difonlib-0.2.2.dist-info/top_level.txt +1 -0
difonlib/input_devs.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from evdev import InputDevice, categorize
|
|
3
|
+
from evdev.events import KeyEvent
|
|
4
|
+
from typing import Dict, Any, List, Optional
|
|
5
|
+
from difonlib.utils import logdbg
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
import re
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
dbg = logdbg
|
|
11
|
+
|
|
12
|
+
# import asyncio
|
|
13
|
+
|
|
14
|
+
KEY_LONG_PRESSED = 1000
|
|
15
|
+
KEY_LONG_TIME_HOLD = 0.7
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class IDevKbd:
|
|
20
|
+
"""event: /dev/input/eventX"""
|
|
21
|
+
|
|
22
|
+
name: str = ""
|
|
23
|
+
uniq: str = ""
|
|
24
|
+
event = ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class IDevKbdKey:
|
|
29
|
+
scancode: int = 0
|
|
30
|
+
hold_time: float = 0
|
|
31
|
+
keycode: str | tuple = ""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_connected_input_devices() -> List[Dict[str, Any]]:
|
|
35
|
+
path = Path("/proc/bus/input/devices")
|
|
36
|
+
devices: List[Dict[str, Any]] = []
|
|
37
|
+
dev: Dict[str, Any] = {}
|
|
38
|
+
|
|
39
|
+
with open(path, "r") as f:
|
|
40
|
+
for line in f:
|
|
41
|
+
line = line.strip()
|
|
42
|
+
if not line: # пустая строка -> новый девайс
|
|
43
|
+
if dev:
|
|
44
|
+
devices.append(dev)
|
|
45
|
+
dev = {}
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
key = line[0]
|
|
49
|
+
value = line[3:] # пропускаем "X: "
|
|
50
|
+
|
|
51
|
+
if key == "I":
|
|
52
|
+
# I: Bus=0003 Vendor=05ac Product=024f Version=0111
|
|
53
|
+
dev["I"] = dict(item.split("=") for item in value.split())
|
|
54
|
+
elif key in ("N", "P", "S", "U", "H"):
|
|
55
|
+
# Строковые поля
|
|
56
|
+
k, v = value.strip().split("=", 1)
|
|
57
|
+
dev[k] = v.strip('"')
|
|
58
|
+
elif key == "B":
|
|
59
|
+
# B: PROP=0 или B: KEY=... B: EV=...
|
|
60
|
+
bkey, bval = value.split("=", 1)
|
|
61
|
+
if "B" not in dev:
|
|
62
|
+
dev["B"] = {}
|
|
63
|
+
dev["B"][bkey] = bval
|
|
64
|
+
else:
|
|
65
|
+
dev[key] = value
|
|
66
|
+
|
|
67
|
+
# не забыть последний блок, так, на всякий случай. Обычно его там нет.
|
|
68
|
+
if dev:
|
|
69
|
+
devices.append(dev)
|
|
70
|
+
|
|
71
|
+
return devices
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def idev_get_by_field(field: str, field_value: str) -> Optional[List[IDevKbd]]:
|
|
75
|
+
"""If device connected by bluetooth - field 'Uniq' is mac address"""
|
|
76
|
+
conn_devs = get_connected_input_devices()
|
|
77
|
+
# dbg(f" = conn_devs: {conn_devs}") # //Dima
|
|
78
|
+
try:
|
|
79
|
+
devs = [dev for dev in conn_devs if dev[field] == field_value]
|
|
80
|
+
except Exception:
|
|
81
|
+
return None
|
|
82
|
+
kdevs = None
|
|
83
|
+
if devs:
|
|
84
|
+
kdevs = []
|
|
85
|
+
for dev in devs:
|
|
86
|
+
kdev = IDevKbd()
|
|
87
|
+
kdev.name = dev["Name"]
|
|
88
|
+
kdev.uniq = dev["Uniq"]
|
|
89
|
+
kdev.event = re.findall(r"event\d+", dev["Handlers"])[0]
|
|
90
|
+
kdevs.append(kdev)
|
|
91
|
+
return kdevs
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def idev_key_monitor(dev_event: str) -> Optional[IDevKbdKey]:
|
|
95
|
+
"""
|
|
96
|
+
Wait for any pressed key on dev_event
|
|
97
|
+
Return: ( key_event.scancode, hold_time, key_event.keycode )"""
|
|
98
|
+
|
|
99
|
+
press_time: float | None = None # timer key down timestamp
|
|
100
|
+
key: IDevKbdKey | None = None
|
|
101
|
+
|
|
102
|
+
dev = InputDevice(f"/dev/input/{dev_event}")
|
|
103
|
+
dbg(f"Listening on: {dev.name}")
|
|
104
|
+
|
|
105
|
+
for event in dev.read_loop():
|
|
106
|
+
# if event.type == ecodes.EV_KEY:
|
|
107
|
+
key_event = categorize(event)
|
|
108
|
+
|
|
109
|
+
if not isinstance(key_event, KeyEvent):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
if key_event.keystate == KeyEvent.key_down:
|
|
113
|
+
press_time = key_event.event.timestamp()
|
|
114
|
+
|
|
115
|
+
elif key_event.keystate == KeyEvent.key_up and press_time is not None:
|
|
116
|
+
key = IDevKbdKey()
|
|
117
|
+
key.hold_time = round(key_event.event.timestamp() - press_time, 2)
|
|
118
|
+
key.scancode = key_event.scancode
|
|
119
|
+
key.keycode = (
|
|
120
|
+
key_event.keycode
|
|
121
|
+
if not isinstance(key_event.keycode, list)
|
|
122
|
+
else key_event.keycode[0]
|
|
123
|
+
)
|
|
124
|
+
break
|
|
125
|
+
dev.close()
|
|
126
|
+
return key
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def idev_get_pressed_key(dev_event: str, timeout: int = 5) -> Optional[IDevKbdKey]:
|
|
130
|
+
"""
|
|
131
|
+
Wait timeout seconds for pressed key on dev_event
|
|
132
|
+
"""
|
|
133
|
+
dev = InputDevice(f"/dev/input/{dev_event}")
|
|
134
|
+
dbg(f" - Listening on: {dev.name}")
|
|
135
|
+
# key = await _get_first_key_event(dev)
|
|
136
|
+
try:
|
|
137
|
+
# asyncio.wait_for ограничивает выполнение асинхронной задачи по времени
|
|
138
|
+
key = await asyncio.wait_for(
|
|
139
|
+
# Вложенная функция, которая читает loop и возвращает первое событие
|
|
140
|
+
_get_first_key_event(dev),
|
|
141
|
+
timeout=timeout,
|
|
142
|
+
)
|
|
143
|
+
return key
|
|
144
|
+
except asyncio.CancelledError:
|
|
145
|
+
print(" =!= idev_get_pressed_key cancelled")
|
|
146
|
+
raise
|
|
147
|
+
except asyncio.TimeoutError:
|
|
148
|
+
print(f" =!= Timeout ({timeout} sec)")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
print(f" =!= Error: {e}")
|
|
151
|
+
|
|
152
|
+
finally:
|
|
153
|
+
dev.close()
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def _get_first_key_event(dev: InputDevice) -> Optional[IDevKbdKey]:
|
|
158
|
+
press_time: float | None = None
|
|
159
|
+
|
|
160
|
+
async for event in dev.async_read_loop():
|
|
161
|
+
key_event = categorize(event)
|
|
162
|
+
|
|
163
|
+
if not isinstance(key_event, KeyEvent):
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
if key_event.keystate == KeyEvent.key_down:
|
|
167
|
+
press_time = key_event.event.timestamp()
|
|
168
|
+
|
|
169
|
+
elif key_event.keystate == KeyEvent.key_up and press_time is not None:
|
|
170
|
+
key = IDevKbdKey()
|
|
171
|
+
key.hold_time = round(key_event.event.timestamp() - press_time, 2)
|
|
172
|
+
key.scancode = key_event.scancode
|
|
173
|
+
key.keycode = (
|
|
174
|
+
key_event.keycode
|
|
175
|
+
if not isinstance(key_event.keycode, list)
|
|
176
|
+
else key_event.keycode[0]
|
|
177
|
+
)
|
|
178
|
+
return key
|
|
179
|
+
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# U: Uniq=40:b4:cd:ce:31:d6
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
dbg(" == START ==") # //Dima
|
|
186
|
+
# devs = get_connected_input_devices()
|
|
187
|
+
# print_dicts_list(devs)
|
|
188
|
+
# dbg(f"---------------------------------------------") # //Dima
|
|
189
|
+
# exit()
|
|
190
|
+
devs = idev_get_by_field("Name", "Keychron Keychron K5")
|
|
191
|
+
if devs:
|
|
192
|
+
for dev in devs:
|
|
193
|
+
dbg(f"dev: {dev.__dict__}") # //Dima
|
|
194
|
+
|
|
195
|
+
devs = idev_get_by_field(field="Uniq", field_value="40:b4:cd:ce:31:d6")
|
|
196
|
+
# print(repr(f"dev: {dev.__dict__}"))
|
|
197
|
+
if devs:
|
|
198
|
+
dev = devs[0]
|
|
199
|
+
dbg(f"Input dev: {dev.__dict__}")
|
|
200
|
+
|
|
201
|
+
key = idev_key_monitor(dev.event)
|
|
202
|
+
dbg(f"Pressed key: {key.__dict__}")
|
|
203
|
+
|
|
204
|
+
key = asyncio.run(idev_get_pressed_key(dev.event))
|
|
205
|
+
if key:
|
|
206
|
+
dbg(f"Pressed key: {key}")
|
|
207
|
+
dbg(f"Pressed key: {key.__dict__}")
|
|
208
|
+
|
|
209
|
+
dbg(" == FINISH ==") # //Dima
|
difonlib/ng_lib.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from nicegui import ui
|
|
2
|
+
import inspect
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Callable, Awaitable, Dict, List, Optional, Union, Literal
|
|
5
|
+
|
|
6
|
+
from difonlib.utils import logdbg
|
|
7
|
+
|
|
8
|
+
dbg = logdbg
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CardTable:
|
|
12
|
+
_current_yes_handler: Optional[Callable[[], Union[None, Awaitable[None]]]] = None
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
title: str,
|
|
17
|
+
columns: list,
|
|
18
|
+
rows: list = [],
|
|
19
|
+
selection: Optional[Literal["single", "multiple"]] = None, # single, multiple or None
|
|
20
|
+
on_selection_change: List[Callable] = [], # Handlers on_selection_change event
|
|
21
|
+
):
|
|
22
|
+
self.on_selection_change: list = on_selection_change
|
|
23
|
+
# A list of buttons that change state (active, inactive) when table rows are selected or deselected.
|
|
24
|
+
self.buttons_on_row_select_changed: list = []
|
|
25
|
+
|
|
26
|
+
"""Карточка с таблицей и встроенным диалогом подтверждения."""
|
|
27
|
+
with ui.card().classes("p-4 shadow-lg") as self.card:
|
|
28
|
+
# --- Заголовок ---
|
|
29
|
+
with ui.row().classes("items-center justify-between w-full mb-2"):
|
|
30
|
+
ui.label(f"📋 {title}").classes("text-green-700 text-lg font-bold")
|
|
31
|
+
with ui.row().classes("gap-2") as self.top_table:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# --- Таблица ---
|
|
35
|
+
self.table = ui.table(
|
|
36
|
+
columns=columns,
|
|
37
|
+
rows=self.enum_data(rows),
|
|
38
|
+
row_key="sn",
|
|
39
|
+
selection=selection,
|
|
40
|
+
on_select=self._on_selection_change,
|
|
41
|
+
column_defaults={
|
|
42
|
+
"align": "left",
|
|
43
|
+
"headerClasses": "uppercase",
|
|
44
|
+
},
|
|
45
|
+
).classes("w-full shadow-lg bg-black-900 text-gray-200")
|
|
46
|
+
|
|
47
|
+
# --- Диалог подтверждения ---
|
|
48
|
+
with ui.dialog() as self.confirm_dialog, ui.card().classes("p-4"):
|
|
49
|
+
self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
|
|
50
|
+
with ui.row().classes("justify-end w-full gap-2"):
|
|
51
|
+
ui.button("No", color="gray", on_click=self.confirm_dialog.close)
|
|
52
|
+
ui.button("Yes", color="red", on_click=self._on_yes_clicked)
|
|
53
|
+
|
|
54
|
+
# --- Диалог "Processing..." ---
|
|
55
|
+
with (
|
|
56
|
+
ui.dialog() as self.processing_dialog,
|
|
57
|
+
ui.card().classes("p-4 items-center justify-center"),
|
|
58
|
+
):
|
|
59
|
+
with ui.row().classes("items-center gap-3"):
|
|
60
|
+
self.processing_spinner = ui.spinner(size="md")
|
|
61
|
+
self.processing_label = ui.label("Processing...").classes("text-base")
|
|
62
|
+
|
|
63
|
+
def visible(self, state: Literal[True, False]) -> None:
|
|
64
|
+
if state:
|
|
65
|
+
self.table.visible = True
|
|
66
|
+
self.card.visible = True
|
|
67
|
+
else:
|
|
68
|
+
self.table.visible = False
|
|
69
|
+
self.card.visible = False
|
|
70
|
+
|
|
71
|
+
async def _on_selection_change(self, e: Any) -> None:
|
|
72
|
+
for btn in self.buttons_on_row_select_changed:
|
|
73
|
+
if e.selection:
|
|
74
|
+
dbg("Da") # //Dima
|
|
75
|
+
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
76
|
+
else:
|
|
77
|
+
dbg("Net") # //Dima
|
|
78
|
+
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
79
|
+
dbg(f"** self.on_selection_change: {self.on_selection_change}") # //Dima
|
|
80
|
+
for handler in self.on_selection_change:
|
|
81
|
+
await handler(e)
|
|
82
|
+
|
|
83
|
+
async def _on_yes_clicked(self) -> None:
|
|
84
|
+
"""Обработка подтверждения с показом диалога 'Processing...'."""
|
|
85
|
+
handler: Optional[Callable[[], Union[None, Awaitable[None]]]] = self._current_yes_handler
|
|
86
|
+
self._current_yes_handler = None
|
|
87
|
+
self.confirm_dialog.close()
|
|
88
|
+
|
|
89
|
+
if not handler:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# --- Показать диалог "Processing..." ---
|
|
93
|
+
self.processing_dialog.open()
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
if inspect.iscoroutinefunction(handler):
|
|
97
|
+
await handler()
|
|
98
|
+
await asyncio.sleep(0.3) # плавность UX
|
|
99
|
+
else:
|
|
100
|
+
handler()
|
|
101
|
+
finally:
|
|
102
|
+
self.processing_dialog.close()
|
|
103
|
+
|
|
104
|
+
def enum_data(self, data: List[Dict]) -> List[Dict]:
|
|
105
|
+
return [{"sn": i + 1, **row} for i, row in enumerate(data)]
|
|
106
|
+
|
|
107
|
+
def set_rows(self, rows: List[Dict]) -> None:
|
|
108
|
+
self.table.rows = self.enum_data(rows)
|
|
109
|
+
self.table.update()
|
|
110
|
+
|
|
111
|
+
# def add_handler_on_selection_change(self, handler: Callable[[Any]]) -> None:
|
|
112
|
+
# if handler in self.on_selection_change:
|
|
113
|
+
# return
|
|
114
|
+
# self.on_selection_change.append(handler)
|
|
115
|
+
|
|
116
|
+
def add_button(
|
|
117
|
+
self,
|
|
118
|
+
btn_txt: str,
|
|
119
|
+
on_click: Callable,
|
|
120
|
+
default_enable: bool = True, # enable the added button by default
|
|
121
|
+
color: str = "blue",
|
|
122
|
+
active_on_rows_selected: bool = False, # activate button when row(s) is selected, if True - always actived
|
|
123
|
+
use_dialog_confirm: bool = False,
|
|
124
|
+
confirm_title: Optional[str] = None,
|
|
125
|
+
) -> ui.button:
|
|
126
|
+
"""Добавляет кнопку в заголовок таблицы, с опциональным подтверждением."""
|
|
127
|
+
with self.top_table:
|
|
128
|
+
btn = ui.button(btn_txt, color=color)
|
|
129
|
+
|
|
130
|
+
if active_on_rows_selected:
|
|
131
|
+
self.buttons_on_row_select_changed.append(btn)
|
|
132
|
+
if default_enable:
|
|
133
|
+
btn.classes("!bg-blue-500", remove="!bg-gray-500").enable()
|
|
134
|
+
else:
|
|
135
|
+
btn.classes("!bg-gray-500", remove="!bg-blue-500").disable()
|
|
136
|
+
|
|
137
|
+
dbg(f"btns: {self.buttons_on_row_select_changed}") # //Dima
|
|
138
|
+
|
|
139
|
+
# --- Без диалога ---
|
|
140
|
+
if not use_dialog_confirm:
|
|
141
|
+
btn.on_click(on_click)
|
|
142
|
+
return btn
|
|
143
|
+
|
|
144
|
+
# --- С подтверждением ---
|
|
145
|
+
async def handle_click() -> None:
|
|
146
|
+
self.dialog_title.text = confirm_title or f"Confirm {btn_txt}?"
|
|
147
|
+
self._current_yes_handler = on_click
|
|
148
|
+
self.confirm_dialog.open()
|
|
149
|
+
|
|
150
|
+
btn.on_click(handle_click)
|
|
151
|
+
return btn
|
difonlib/py.typed
ADDED
|
File without changes
|
difonlib/remctrl.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from difonlib.input_devs import idev_get_pressed_key, IDevKbdKey
|
|
3
|
+
from difonlib.utils import logdbg, YamlConfig
|
|
4
|
+
from typing import List, Optional # Dict, Any, List
|
|
5
|
+
|
|
6
|
+
dbg = logdbg
|
|
7
|
+
|
|
8
|
+
KEY_LONG_PRESSED_TIME = 0.7
|
|
9
|
+
KEY_LONG_PRESSED_CONST = 1000
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RemoteControls:
|
|
13
|
+
"""
|
|
14
|
+
Remote Control.
|
|
15
|
+
1. Learn buttons
|
|
16
|
+
Bind func_keys with remote control buttons
|
|
17
|
+
2. Run monitor service of remote control pressed button (async mode)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config_file_name: str) -> None:
|
|
21
|
+
# Load(create) config file
|
|
22
|
+
self.config = YamlConfig(config_file_name)
|
|
23
|
+
if self.config.config == {}:
|
|
24
|
+
self.config.config["infrared_devs"] = {} # {dev_id:xxx, dev_local_key:xxx}
|
|
25
|
+
self.config.config["remctrl_devs"] = {}
|
|
26
|
+
self.config.config["func_keys"] = []
|
|
27
|
+
self.config.save()
|
|
28
|
+
|
|
29
|
+
self.cfg_ir_devs = self.config.config["infrared_devs"]
|
|
30
|
+
self.cfg_rc_devs = self.config.config["remctrl_devs"]
|
|
31
|
+
self.cfg_func_keys = self.config.config["func_keys"]
|
|
32
|
+
|
|
33
|
+
# if self.func_keys != self.cfg_func_keys:
|
|
34
|
+
# self.cfg_func_keys = self.func_keys
|
|
35
|
+
# self.config.save()
|
|
36
|
+
|
|
37
|
+
def set_func_keys(self, func_keys: List[str]) -> None:
|
|
38
|
+
if func_keys != self.cfg_func_keys:
|
|
39
|
+
self.config.config.update({"func_keys": func_keys})
|
|
40
|
+
self.cfg_func_keys = self.config.config["func_keys"]
|
|
41
|
+
self.config.save()
|
|
42
|
+
|
|
43
|
+
def remove_func_btn(self, remctrl_name: str, func_key: Optional[str] = None) -> None:
|
|
44
|
+
if not func_key:
|
|
45
|
+
# remove remctrl_name from config
|
|
46
|
+
try:
|
|
47
|
+
self.config.config.pop(remctrl_name)
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
else:
|
|
51
|
+
self.config.config[remctrl_name][func_key] = None
|
|
52
|
+
self.config.save()
|
|
53
|
+
|
|
54
|
+
def get_func_btn(self, rem_ctrl_name: Optional[str] = None) -> list:
|
|
55
|
+
rem_ctrl = self.cfg_rc_devs.get(rem_ctrl_name)
|
|
56
|
+
if rem_ctrl:
|
|
57
|
+
dbg(f" * rem_ctrl: {rem_ctrl}") # //Dima
|
|
58
|
+
fs = [{f: rem_ctrl.get(f)} for f in self.cfg_func_keys]
|
|
59
|
+
return fs
|
|
60
|
+
fs = [{f: None} for f in self.cfg_func_keys]
|
|
61
|
+
return fs
|
|
62
|
+
|
|
63
|
+
def get_func_btn_format(
|
|
64
|
+
self,
|
|
65
|
+
func_field_name: str,
|
|
66
|
+
btn_field_name: str,
|
|
67
|
+
rem_ctrl_name: Optional[str] = None,
|
|
68
|
+
) -> list:
|
|
69
|
+
_rows = self.get_func_btn(rem_ctrl_name)
|
|
70
|
+
rows = [
|
|
71
|
+
{
|
|
72
|
+
func_field_name: list(row.keys())[0],
|
|
73
|
+
btn_field_name: list(row.values())[0],
|
|
74
|
+
}
|
|
75
|
+
for row in _rows
|
|
76
|
+
]
|
|
77
|
+
return rows
|
|
78
|
+
|
|
79
|
+
async def devs_monitor(self) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Monitor input event devices for pressed key
|
|
82
|
+
Detect pressed key and call handler('KEY_XXXXXX')
|
|
83
|
+
"""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
async def get_pressed_button(self, dev_event: str, timeout: int = 7) -> Optional[IDevKbdKey]:
|
|
87
|
+
key = await idev_get_pressed_key(dev_event, timeout=timeout)
|
|
88
|
+
dbg(f" * Pressed KEY: {key}") # //Dima
|
|
89
|
+
return key
|
|
90
|
+
|
|
91
|
+
async def learn_button(
|
|
92
|
+
self, idev_name: str, idev_event: str, func_key: str, timeout: int = 10
|
|
93
|
+
) -> Optional[IDevKbdKey]:
|
|
94
|
+
# dbg(f"self.cfg_func_keys: {self.cfg_func_keys}") # //Dima
|
|
95
|
+
if func_key not in self.cfg_func_keys:
|
|
96
|
+
print(f" =!= func_key: '{func_key}' is not exist")
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
# dbg(f"*** CONFIG: {self.config.config}") # //Dima
|
|
100
|
+
# idev_name = idev["name"]
|
|
101
|
+
# # dev_uniq = idev["mac"]
|
|
102
|
+
# idev_event = idev["event"]
|
|
103
|
+
key = await idev_get_pressed_key(idev_event, timeout=timeout)
|
|
104
|
+
dbg(f" * Pressed KEY: {key}") # //Dima
|
|
105
|
+
if key:
|
|
106
|
+
key_scan_code = key.scancode
|
|
107
|
+
if key.hold_time > KEY_LONG_PRESSED_TIME:
|
|
108
|
+
key_scan_code += KEY_LONG_PRESSED_CONST
|
|
109
|
+
if not self.cfg_rc_devs.get(idev_name):
|
|
110
|
+
self.cfg_rc_devs[idev_name] = {}
|
|
111
|
+
# self.config.config["remctrl_devs"].add(dev_uniq)
|
|
112
|
+
self.cfg_rc_devs[idev_name].update({func_key: key_scan_code})
|
|
113
|
+
self.config.save()
|
|
114
|
+
|
|
115
|
+
return key
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
|
|
120
|
+
REMOTE_CTRL_CONFIG = "./___remctrl_devs_config.yaml"
|
|
121
|
+
func_keys = [
|
|
122
|
+
"KEY_PREV",
|
|
123
|
+
"KEY_PLAY_ALBUM",
|
|
124
|
+
"KEY_PAUSE_PLAY",
|
|
125
|
+
"KEY_NEXT",
|
|
126
|
+
"KEY_SEEKCUR",
|
|
127
|
+
"KEY_RADIO",
|
|
128
|
+
"KEY_JUKEBOX_ON",
|
|
129
|
+
"KEY_PLAY_LIKE",
|
|
130
|
+
"KEY_SET_LIKE",
|
|
131
|
+
"KEY_VOL+",
|
|
132
|
+
"KEY_VOL-",
|
|
133
|
+
"KEY_OnOff",
|
|
134
|
+
"KEY_EXT_DISP",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
rc = RemoteControls(REMOTE_CTRL_CONFIG)
|
|
138
|
+
rc.set_func_keys(func_keys)
|
|
139
|
+
|
|
140
|
+
from difonlib.input_devs import idev_get_by_field
|
|
141
|
+
|
|
142
|
+
remctrl_name = "Amazon Fire TV Remote Keyboard"
|
|
143
|
+
idev_kbd = idev_get_by_field(field="Name", field_value=remctrl_name)
|
|
144
|
+
|
|
145
|
+
if idev_kbd:
|
|
146
|
+
key = asyncio.run(
|
|
147
|
+
rc.learn_button(
|
|
148
|
+
idev_name=remctrl_name,
|
|
149
|
+
idev_event=idev_kbd[0].event,
|
|
150
|
+
func_key="KEY_PAUSE_PLAY",
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
dbg(f"key: {key}") # //Dima
|
|
154
|
+
else:
|
|
155
|
+
dbg(f"The Input Device '{remctrl_name}' is not connected.") # //Dima
|
|
156
|
+
fb = rc.get_func_btn(remctrl_name)
|
|
157
|
+
dbg(f"Function-Button: {fb}") # //Dima
|