difonlib 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "difonlib"
3
- version = "0.3.0"
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
- async def bt_scan_devs( # universally version for classic and ble devices
173
+ # Universal Bluetooth scanner for classic and BLE devices.
174
+ async def bt_scan_devs(
174
175
  dev_type: str = "HID Device",
175
- inquiry_warmup: float = 4.0, # время для Classic Inquiry
176
- scan_duration: float = 4.0, # время bleak сканирования
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(inquiry_warmup)
184
+ await asyncio.sleep(scan_duration_classic)
184
185
  try:
185
- bt_scanner = BluetoothScanner(duration=scan_duration, dev_type=dev_type)
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 is_remote_ctrl(dev: InputDevice) -> bool:
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 idev_get_dev_by_uniq(uniq: str) -> Optional[InputDevice]:
62
- devs = idev_get_connected_devs()
63
- matched = [dev for dev in devs if dev.uniq.upper() == uniq.upper() and is_remote_ctrl(dev)]
64
- return matched[0] if matched else None
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 = idev_get_dev_by_uniq("40:B4:cd:CE:31:d6")
241
+ # devs = idev_kbd_get_by_uniq("40:B4:cd:CE:31:d6")
224
242
  # print(f"devs: {devs}")
225
243
  # exit()
226
-
227
- devs = idev_get_by_field("Name", "Keychron Keychron K5")
228
- if devs:
229
- for dev in devs:
230
- dbg(f"dev: {dev.__dict__}") # //Dima
231
-
232
- devs = idev_get_by_field(field="Uniq", field_value="40:b4:cd:ce:31:d6")
233
- # print(repr(f"dev: {dev.__dict__}"))
234
- if devs:
235
- dev = devs[0]
236
- dbg(f"Input dev: {dev.__dict__}")
237
-
238
- key = idev_key_monitor(dev.event)
239
- dbg(f"Pressed key: {key.__dict__}")
240
-
241
- key = asyncio.run(idev_get_pressed_key(dev.event))
242
- if key:
243
- dbg(f"Pressed key: {key}")
244
- dbg(f"Pressed key: {key.__dict__}")
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
@@ -45,7 +45,10 @@ class CardTable:
45
45
  ).classes("w-full shadow-lg bg-black-900 text-gray-200")
46
46
 
47
47
  # --- Диалог подтверждения ---
48
- with ui.dialog() as self.confirm_dialog, ui.card().classes("p-4"):
48
+ with (
49
+ ui.dialog().props("persistent") as self.confirm_dialog,
50
+ ui.card().classes("p-4"),
51
+ ):
49
52
  self.dialog_title = ui.label().classes("text-lg font-bold mb-4")
50
53
  with ui.row().classes("justify-end w-full gap-2"):
51
54
  ui.button("No", color="gray", on_click=self.confirm_dialog.close)
@@ -53,7 +56,7 @@ class CardTable:
53
56
 
54
57
  # --- Диалог "Processing..." ---
55
58
  with (
56
- ui.dialog() as self.processing_dialog,
59
+ ui.dialog().props("persistent") as self.processing_dialog,
57
60
  ui.card().classes("p-4 items-center justify-center"),
58
61
  ):
59
62
  with ui.row().classes("items-center gap-3"):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: difonlib
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: python libraries
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -3,6 +3,7 @@ pyproject.toml
3
3
  src/difonlib/__init__.py
4
4
  src/difonlib/bt_scanner.py
5
5
  src/difonlib/bt_utils.py
6
+ src/difonlib/conn_kbd_devs.py
6
7
  src/difonlib/input_devs.py
7
8
  src/difonlib/ng_lib.py
8
9
  src/difonlib/py.typed
File without changes
File without changes
File without changes
File without changes
File without changes