difonlib 0.3.1__tar.gz → 0.3.2__tar.gz
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-0.3.1 → difonlib-0.3.2}/PKG-INFO +1 -1
- {difonlib-0.3.1 → difonlib-0.3.2}/pyproject.toml +2 -1
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/bt_utils.py +6 -5
- difonlib-0.3.2/src/difonlib/conn_kbd_devs.py +65 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/input_devs.py +104 -36
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib.egg-info/PKG-INFO +1 -1
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib.egg-info/SOURCES.txt +1 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/README.md +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/setup.cfg +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/__init__.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/bt_scanner.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/ng_lib.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/py.typed +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/remctrl.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/tuya_devs.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib/utils.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib.egg-info/dependency_links.txt +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib.egg-info/requires.txt +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/src/difonlib.egg-info/top_level.txt +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/tests/test_bt_utils.py +0 -0
- {difonlib-0.3.1 → difonlib-0.3.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "difonlib"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.2"
|
|
4
4
|
description = "python libraries"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -51,6 +51,7 @@ dev = [
|
|
|
51
51
|
"types-pexpect>=4.9.0.20250916",
|
|
52
52
|
"types-PyYAML>=6.0.12.20250915",
|
|
53
53
|
"types-xmltodict>=1.0.1.20250920",
|
|
54
|
+
"ipython>=8.39.0",
|
|
54
55
|
]
|
|
55
56
|
|
|
56
57
|
[tool.ruff]
|
|
@@ -170,19 +170,20 @@ def bt_scan_hid_devs() -> list:
|
|
|
170
170
|
return hid_devs
|
|
171
171
|
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
# Universal Bluetooth scanner for classic and BLE devices.
|
|
174
|
+
async def bt_scan_devs(
|
|
174
175
|
dev_type: str = "HID Device",
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
scan_duration_classic: float = 4.0, # scan time for Classic Inquiry
|
|
177
|
+
scan_duration_ble: float = 4.0, # scan time for BLE devs
|
|
177
178
|
adapter_path: str = "/org/bluez/hci0",
|
|
178
179
|
) -> list[dict]:
|
|
179
180
|
bus = pydbus.SystemBus()
|
|
180
181
|
adapter = bus.get("org.bluez", adapter_path)
|
|
181
182
|
adapter.SetDiscoveryFilter({})
|
|
182
183
|
adapter.StartDiscovery()
|
|
183
|
-
await asyncio.sleep(
|
|
184
|
+
await asyncio.sleep(scan_duration_classic)
|
|
184
185
|
try:
|
|
185
|
-
bt_scanner = BluetoothScanner(duration=
|
|
186
|
+
bt_scanner = BluetoothScanner(duration=scan_duration_ble, dev_type=dev_type)
|
|
186
187
|
bt_devices = await bt_scanner.run()
|
|
187
188
|
except Exception as e:
|
|
188
189
|
print(f" Bluetooth scan failed: {e}")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import evdev
|
|
2
|
+
from evdev import ecodes, InputDevice
|
|
3
|
+
|
|
4
|
+
NOT_KBD_KEYWORDS = [
|
|
5
|
+
"mouse",
|
|
6
|
+
"hd-audio",
|
|
7
|
+
"headset",
|
|
8
|
+
# "headphone",
|
|
9
|
+
"system control",
|
|
10
|
+
"video bus",
|
|
11
|
+
"power button",
|
|
12
|
+
"avrcp",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_keyboard_like_devices() -> list[InputDevice]:
|
|
17
|
+
devices = [evdev.InputDevice(path) for path in evdev.list_devices()]
|
|
18
|
+
keyboard_likes = []
|
|
19
|
+
for device in devices:
|
|
20
|
+
caps = device.capabilities()
|
|
21
|
+
if evdev.ecodes.EV_KEY not in caps:
|
|
22
|
+
continue
|
|
23
|
+
name_lower = device.name.lower()
|
|
24
|
+
if any(kw in name_lower for kw in NOT_KBD_KEYWORDS):
|
|
25
|
+
continue
|
|
26
|
+
keyboard_likes.append(device)
|
|
27
|
+
return keyboard_likes
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_remote_ctrl(dev: InputDevice) -> bool:
|
|
31
|
+
caps = dev.capabilities()
|
|
32
|
+
if ecodes.EV_KEY not in caps:
|
|
33
|
+
return False
|
|
34
|
+
keys = set(caps[ecodes.EV_KEY])
|
|
35
|
+
remote_keys = {
|
|
36
|
+
ecodes.KEY_UP,
|
|
37
|
+
ecodes.KEY_DOWN,
|
|
38
|
+
ecodes.KEY_LEFT,
|
|
39
|
+
ecodes.KEY_RIGHT,
|
|
40
|
+
# ecodes.KEY_OK,
|
|
41
|
+
# ecodes.KEY_SELECT,
|
|
42
|
+
# ecodes.KEY_BACK,
|
|
43
|
+
# ecodes.KEY_PLAYPAUSE,
|
|
44
|
+
# ecodes.KEY_VOLUMEUP,
|
|
45
|
+
# ecodes.KEY_VOLUMEDOWN,
|
|
46
|
+
# ecodes.KEY_HOME,
|
|
47
|
+
# ecodes.KEY_MENU,
|
|
48
|
+
# ecodes.KEY_NEXT,
|
|
49
|
+
# ecodes.KEY_PREVIOUS,
|
|
50
|
+
}
|
|
51
|
+
return bool(remote_keys & keys)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
print(f"{'Path':<18} | {'Name'}")
|
|
56
|
+
print("-" * 60)
|
|
57
|
+
|
|
58
|
+
idevs = [evdev.InputDevice(path) for path in evdev.list_devices()]
|
|
59
|
+
for dev in idevs:
|
|
60
|
+
if is_remote_ctrl(dev):
|
|
61
|
+
print(f"{dev.path:<18} | {dev.name}")
|
|
62
|
+
print("-" * 60)
|
|
63
|
+
print("-" * 60)
|
|
64
|
+
for dev in get_keyboard_like_devices():
|
|
65
|
+
print(f"{dev.path:<18} | {dev.name}")
|
|
@@ -12,9 +12,6 @@ dbg = logdbg
|
|
|
12
12
|
|
|
13
13
|
# import asyncio
|
|
14
14
|
|
|
15
|
-
KEY_LONG_PRESSED = 1000
|
|
16
|
-
KEY_LONG_TIME_HOLD = 0.7
|
|
17
|
-
|
|
18
15
|
|
|
19
16
|
@dataclass
|
|
20
17
|
class IDevKbd:
|
|
@@ -36,7 +33,7 @@ def idev_get_connected_devs() -> List[InputDevice]:
|
|
|
36
33
|
return [InputDevice(p) for p in list_devices()]
|
|
37
34
|
|
|
38
35
|
|
|
39
|
-
def
|
|
36
|
+
def has_keys(dev: InputDevice) -> bool:
|
|
40
37
|
caps = dev.capabilities()
|
|
41
38
|
if ecodes.EV_KEY not in caps:
|
|
42
39
|
return False
|
|
@@ -46,22 +43,43 @@ def is_remote_ctrl(dev: InputDevice) -> bool:
|
|
|
46
43
|
ecodes.KEY_DOWN,
|
|
47
44
|
ecodes.KEY_LEFT,
|
|
48
45
|
ecodes.KEY_RIGHT,
|
|
49
|
-
ecodes.KEY_OK,
|
|
50
|
-
ecodes.KEY_SELECT,
|
|
51
|
-
ecodes.KEY_BACK,
|
|
52
|
-
ecodes.KEY_PLAYPAUSE,
|
|
53
|
-
ecodes.KEY_VOLUMEUP,
|
|
54
|
-
ecodes.KEY_VOLUMEDOWN,
|
|
55
|
-
ecodes.KEY_HOME,
|
|
56
|
-
ecodes.KEY_MENU,
|
|
46
|
+
# ecodes.KEY_OK,
|
|
47
|
+
# ecodes.KEY_SELECT,
|
|
48
|
+
# ecodes.KEY_BACK,
|
|
49
|
+
# ecodes.KEY_PLAYPAUSE,
|
|
50
|
+
# ecodes.KEY_VOLUMEUP,
|
|
51
|
+
# ecodes.KEY_VOLUMEDOWN,
|
|
52
|
+
# ecodes.KEY_HOME,
|
|
53
|
+
# ecodes.KEY_MENU,
|
|
54
|
+
# ecodes.KEY_NEXT,
|
|
55
|
+
# ecodes.KEY_PREVIOUS,
|
|
57
56
|
}
|
|
58
57
|
return bool(remote_keys & keys)
|
|
59
58
|
|
|
60
59
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
def idev_get_connected_kbds(uniqs: list[str] | None = None) -> list[InputDevice]:
|
|
61
|
+
"""Use in asyc:
|
|
62
|
+
1. connected_hid_devs = await asyncio.to_thread(idev_get_connected_kbds)
|
|
63
|
+
2. connected_hid_devs = await asyncio.to_thread(lambda: idev_get_connected_kbds(['11:2a:3b:44:55:6c', 'F1:ba:3b:41:52:6e']))
|
|
64
|
+
"""
|
|
65
|
+
result = []
|
|
66
|
+
_uniqs = [u.lower() for u in uniqs] if uniqs else []
|
|
67
|
+
for path in list_devices():
|
|
68
|
+
try:
|
|
69
|
+
dev = InputDevice(path)
|
|
70
|
+
if has_keys(dev):
|
|
71
|
+
if uniqs:
|
|
72
|
+
if dev.uniq.lower() in _uniqs:
|
|
73
|
+
result.append(dev)
|
|
74
|
+
else:
|
|
75
|
+
dev.close()
|
|
76
|
+
else:
|
|
77
|
+
result.append(dev)
|
|
78
|
+
else:
|
|
79
|
+
dev.close()
|
|
80
|
+
except (OSError, PermissionError):
|
|
81
|
+
continue
|
|
82
|
+
return result
|
|
65
83
|
|
|
66
84
|
|
|
67
85
|
def get_connected_input_devices() -> List[Dict[str, Any]]:
|
|
@@ -220,27 +238,77 @@ if __name__ == "__main__":
|
|
|
220
238
|
# print_dicts_list(devs)
|
|
221
239
|
# dbg(f"---------------------------------------------") # //Dima
|
|
222
240
|
# exit()
|
|
223
|
-
# devs =
|
|
241
|
+
# devs = idev_kbd_get_by_uniq("40:B4:cd:CE:31:d6")
|
|
224
242
|
# print(f"devs: {devs}")
|
|
225
243
|
# exit()
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
# monitored_devs = [
|
|
245
|
+
# "40:b4:cd:ce:31:d6", # Amazon Fire TV Remote
|
|
246
|
+
# "ff:23:05:30:30:8c", # HID Remote01
|
|
247
|
+
# "c1:03:01:5a:02:95", # BOXPUT BPR1 c1:03:01:5c:02:95
|
|
248
|
+
# ]
|
|
249
|
+
|
|
250
|
+
for dev in idev_get_connected_kbds():
|
|
251
|
+
print(f"{dev.path:<18} | {dev.name}")
|
|
252
|
+
|
|
253
|
+
import asyncio
|
|
254
|
+
import functools
|
|
255
|
+
|
|
256
|
+
async def test_idev_get_connected_kbds(
|
|
257
|
+
uniqs: list[str] | None = None,
|
|
258
|
+
) -> list[InputDevice]:
|
|
259
|
+
return await asyncio.to_thread(lambda: idev_get_connected_kbds(uniqs))
|
|
260
|
+
|
|
261
|
+
async def testA_idev_get_connected_kbds(
|
|
262
|
+
uniqs: list[str] | None = None,
|
|
263
|
+
) -> list[InputDevice]:
|
|
264
|
+
return await asyncio.to_thread(functools.partial(idev_get_connected_kbds, uniqs))
|
|
265
|
+
|
|
266
|
+
async def test2_idev_get_connected_kbds() -> list[InputDevice]:
|
|
267
|
+
return await asyncio.get_running_loop().run_in_executor(None, idev_get_connected_kbds)
|
|
268
|
+
|
|
269
|
+
print("-" * 65)
|
|
270
|
+
con_devs = asyncio.run(test_idev_get_connected_kbds())
|
|
271
|
+
print(f"con_devs: {[dev.name for dev in con_devs]}")
|
|
272
|
+
print("-" * 55)
|
|
273
|
+
con_devs = asyncio.run(test2_idev_get_connected_kbds())
|
|
274
|
+
print(f"con_devs: {[dev.name for dev in con_devs]}")
|
|
275
|
+
print("-" * 55)
|
|
276
|
+
con_devs = asyncio.run(
|
|
277
|
+
testA_idev_get_connected_kbds(uniqs=["40:b4:cd:ce:31:d6", "c1:03:01:5c:02:95"])
|
|
278
|
+
)
|
|
279
|
+
print(f"con_devs: {[(dev.name, dev.uniq) for dev in con_devs]}")
|
|
280
|
+
"""
|
|
281
|
+
https://claude.ai/share/342dbe4e-3eff-4732-8249-bd68837c6fba
|
|
282
|
+
Oба варианта корректны и делают одно и то же. Разница чисто косметическая:
|
|
283
|
+
asyncio.to_thread — это обёртка над run_in_executor(None, ...),
|
|
284
|
+
добавленная в Python 3.9 именно чтобы не писать get_running_loop() вручную.
|
|
285
|
+
Используй to_thread — он чище и современнее.
|
|
286
|
+
run_in_executor оставь для случаев когда нужен кастомный executor (например ThreadPoolExecutor с лимитом потоков).
|
|
287
|
+
|
|
288
|
+
Передача параметров:
|
|
289
|
+
await asyncio.to_thread(
|
|
290
|
+
lambda: idev_get_connected_kbds(uniqs=["aa:bb:cc:dd:ee:ff"])
|
|
291
|
+
)
|
|
292
|
+
import functools
|
|
293
|
+
await asyncio.to_thread(functools.partial(idev_get_connected_kbds, uniqs=["aa:bb:cc:dd:ee:ff"]))
|
|
294
|
+
"""
|
|
295
|
+
# devs = idev_get_by_field("Name", "Keychron Keychron K5")
|
|
296
|
+
# if devs:
|
|
297
|
+
# for dev in devs:
|
|
298
|
+
# dbg(f"dev: {dev.__dict__}") # //Dima
|
|
299
|
+
|
|
300
|
+
# devs = idev_get_by_field(field="Uniq", field_value="40:b4:cd:ce:31:d6")
|
|
301
|
+
# # print(repr(f"dev: {dev.__dict__}"))
|
|
302
|
+
# if devs:
|
|
303
|
+
# dev = devs[0]
|
|
304
|
+
# dbg(f"Input dev: {dev.__dict__}")
|
|
305
|
+
|
|
306
|
+
# key = idev_key_monitor(dev.event)
|
|
307
|
+
# dbg(f"Pressed key: {key.__dict__}")
|
|
308
|
+
|
|
309
|
+
# key = asyncio.run(idev_get_pressed_key(dev.event))
|
|
310
|
+
# if key:
|
|
311
|
+
# dbg(f"Pressed key: {key}")
|
|
312
|
+
# dbg(f"Pressed key: {key.__dict__}")
|
|
245
313
|
|
|
246
314
|
dbg(" == FINISH ==") # //Dima
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|