AnalogSensePy 1.0.4__tar.gz → 1.0.6__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.
- analogsensepy-1.0.6/AnalogSensePy.egg-info/PKG-INFO +128 -0
- analogsensepy-1.0.6/AnalogSensePy.egg-info/requires.txt +6 -0
- analogsensepy-1.0.6/PKG-INFO +128 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/README.md +16 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/analogsense/analogsense.py +26 -9
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/analogsense/providers.py +42 -29
- analogsensepy-1.0.6/pyproject.toml +17 -0
- analogsensepy-1.0.4/AnalogSensePy.egg-info/PKG-INFO +0 -7
- analogsensepy-1.0.4/AnalogSensePy.egg-info/requires.txt +0 -1
- analogsensepy-1.0.4/PKG-INFO +0 -7
- analogsensepy-1.0.4/pyproject.toml +0 -9
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/AnalogSensePy.egg-info/SOURCES.txt +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/AnalogSensePy.egg-info/dependency_links.txt +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/AnalogSensePy.egg-info/top_level.txt +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/LICENSE +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/analogsense/__init__.py +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/analogsense/keymaps.py +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/analogsense/layouts.py +0 -0
- {analogsensepy-1.0.4 → analogsensepy-1.0.6}/setup.cfg +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: AnalogSensePy
|
|
3
|
+
Version: 1.0.6
|
|
4
|
+
License: MIT
|
|
5
|
+
Project-URL: Homepage, https://github.com/girlglock/AnalogSense-Python-SDK
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: hid; sys_platform == "linux"
|
|
10
|
+
Requires-Dist: hidapi; sys_platform != "linux"
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# AnalogSense Python SDK
|
|
14
|
+
Python port of [AnalogSense.js](https://github.com/AnalogSense/JavaScript-SDK/) for analog keyboard input.
|
|
15
|
+
## Supported Keyboards/Devices
|
|
16
|
+
- Everything by Wooting
|
|
17
|
+
- Everything by NuPhy
|
|
18
|
+
- Everything by DrunkDeer
|
|
19
|
+
- Razer Huntsman V2 Analog<sup>R</sup>
|
|
20
|
+
- Razer Huntsman Mini Analog<sup>R</sup>
|
|
21
|
+
- Razer Huntsman V3 Pro<sup>R</sup>
|
|
22
|
+
- Razer Huntsman V3 Pro Mini<sup>R</sup>
|
|
23
|
+
- Razer Huntsman V3 Pro Tenkeyless<sup>R</sup>
|
|
24
|
+
- Keychron Q1 HE<sup>P, F</sup>
|
|
25
|
+
- Keychron Q3 HE<sup>P, F</sup>
|
|
26
|
+
- Keychron Q5 HE<sup>P, F</sup>
|
|
27
|
+
- Keychron K2 HE<sup>P, F</sup>
|
|
28
|
+
- Lemokey P1 HE<sup>P, F</sup>
|
|
29
|
+
- Madlions MAD60HE<sup>P</sup>
|
|
30
|
+
- Madlions MAD68HE<sup>P</sup>
|
|
31
|
+
- Madlions MAD68R<sup>P</sup>
|
|
32
|
+
- Redragon K709HE<sup>P</sup>
|
|
33
|
+
|
|
34
|
+
<sup>R</sup> Razer Synapse needs to be installed and running for analogue inputs to be received from this keyboard.
|
|
35
|
+
<sup>P</sup> The official firmware only supports polling, which can lead to lag and missed inputs.
|
|
36
|
+
<sup>F</sup> [Custom firmware with full analog report functionality is available](https://analogsense.org/firmware/).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
```bash
|
|
40
|
+
pip install AnalogSensePy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
On Linux you may need udev rules or `sudo` for hid
|
|
44
|
+
```bash
|
|
45
|
+
printf '%s\n' \
|
|
46
|
+
'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0660", GROUP="input"' \
|
|
47
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="31e3", MODE="0660", GROUP="input"' \
|
|
48
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", MODE="0660", GROUP="input"' \
|
|
49
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="1532", MODE="0660", GROUP="input"' \
|
|
50
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="19f5", MODE="0660", GROUP="input"' \
|
|
51
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="352d", MODE="0660", GROUP="input"' \
|
|
52
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="3434", MODE="0660", GROUP="input"' \
|
|
53
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="362d", MODE="0660", GROUP="input"' \
|
|
54
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="373b", MODE="0660", GROUP="input"' \
|
|
55
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="372e", MODE="0660", GROUP="input"' \
|
|
56
|
+
| sudo tee /etc/udev/rules.d/99-analogsense.rules
|
|
57
|
+
|
|
58
|
+
sudo udevadm control --reload-rules && sudo udevadm trigger
|
|
59
|
+
```
|
|
60
|
+
## Usage
|
|
61
|
+
```python
|
|
62
|
+
from analogsense import AnalogSense
|
|
63
|
+
|
|
64
|
+
as_ = AnalogSense()
|
|
65
|
+
devices = as_.get_devices()
|
|
66
|
+
|
|
67
|
+
if not devices:
|
|
68
|
+
print("no supported analog keyboards found.")
|
|
69
|
+
exit()
|
|
70
|
+
|
|
71
|
+
if len(devices) == 1:
|
|
72
|
+
dev = devices[0]
|
|
73
|
+
else:
|
|
74
|
+
for i, d in enumerate(devices):
|
|
75
|
+
print(f" [{i}] {d.product_name}")
|
|
76
|
+
dev = devices[int(input("select device index: "))]
|
|
77
|
+
|
|
78
|
+
def on_keys(active_keys):
|
|
79
|
+
for k in active_keys:
|
|
80
|
+
# converting the scancode to a nice readable string
|
|
81
|
+
name = as_.scancode_to_string(k["scancode"])
|
|
82
|
+
|
|
83
|
+
# some providers can return the digital state of the key
|
|
84
|
+
# (what the system perceives the key as pressed or not)
|
|
85
|
+
# other providers that do not support this will return None
|
|
86
|
+
if k["digital"] is not None:
|
|
87
|
+
digital = " [down]" if k["digital"] else " [up]"
|
|
88
|
+
else:
|
|
89
|
+
digital = ""
|
|
90
|
+
|
|
91
|
+
print(f"{name:20s} {k['value']:.4f}{digital}")
|
|
92
|
+
|
|
93
|
+
dev.start_listening(on_keys)
|
|
94
|
+
input("press keys... enter to stop.\n")
|
|
95
|
+
dev.stop_listening()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The following functions are available on `AnalogSense`:
|
|
99
|
+
- `get_devices() -> list[Device]`
|
|
100
|
+
- `open_device(vendor_id, product_id) -> Device | None`
|
|
101
|
+
- `scancode_to_string(scancode: int) -> str`
|
|
102
|
+
|
|
103
|
+
A device instance has the following members:
|
|
104
|
+
- `start_listening(handler: Callable[[list[{"scancode": int, "value": float}]], None])`
|
|
105
|
+
- `stop_listening()`
|
|
106
|
+
- `product_name: str`
|
|
107
|
+
- `forget()`
|
|
108
|
+
- `dev: DeviceHandle`
|
|
109
|
+
|
|
110
|
+
### Scancodes
|
|
111
|
+
The scancodes provided by this library are primarily HID scancodes; most keys are mapped as seen on usage page 0x07 (A = 0x04, B = 0x05, ...).
|
|
112
|
+
|
|
113
|
+
Control keys (usage page 0x0C) are mapped in the `0x3__` range, modulo 0x100:
|
|
114
|
+
- `0x3B5` = Next Track
|
|
115
|
+
- `0x3B6` = Previous Track
|
|
116
|
+
- `0x3B7` = Stop Media
|
|
117
|
+
- `0x3CD` = Play/Pause
|
|
118
|
+
- `0x394` = Open File Explorer
|
|
119
|
+
- `0x323` = Open Browser Home Page
|
|
120
|
+
|
|
121
|
+
OEM-specific keys are mapped in the `0x4__` range:
|
|
122
|
+
- `0x401` = Brightness Up
|
|
123
|
+
- `0x402` = Brightness Down
|
|
124
|
+
- `0x403` = Profile 1
|
|
125
|
+
- `0x404` = Profile 2
|
|
126
|
+
- `0x405` = Profile 3
|
|
127
|
+
- `0x408` = Profile Switch
|
|
128
|
+
- `0x409` = Function Key (Fn)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: AnalogSensePy
|
|
3
|
+
Version: 1.0.6
|
|
4
|
+
License: MIT
|
|
5
|
+
Project-URL: Homepage, https://github.com/girlglock/AnalogSense-Python-SDK
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: hid; sys_platform == "linux"
|
|
10
|
+
Requires-Dist: hidapi; sys_platform != "linux"
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# AnalogSense Python SDK
|
|
14
|
+
Python port of [AnalogSense.js](https://github.com/AnalogSense/JavaScript-SDK/) for analog keyboard input.
|
|
15
|
+
## Supported Keyboards/Devices
|
|
16
|
+
- Everything by Wooting
|
|
17
|
+
- Everything by NuPhy
|
|
18
|
+
- Everything by DrunkDeer
|
|
19
|
+
- Razer Huntsman V2 Analog<sup>R</sup>
|
|
20
|
+
- Razer Huntsman Mini Analog<sup>R</sup>
|
|
21
|
+
- Razer Huntsman V3 Pro<sup>R</sup>
|
|
22
|
+
- Razer Huntsman V3 Pro Mini<sup>R</sup>
|
|
23
|
+
- Razer Huntsman V3 Pro Tenkeyless<sup>R</sup>
|
|
24
|
+
- Keychron Q1 HE<sup>P, F</sup>
|
|
25
|
+
- Keychron Q3 HE<sup>P, F</sup>
|
|
26
|
+
- Keychron Q5 HE<sup>P, F</sup>
|
|
27
|
+
- Keychron K2 HE<sup>P, F</sup>
|
|
28
|
+
- Lemokey P1 HE<sup>P, F</sup>
|
|
29
|
+
- Madlions MAD60HE<sup>P</sup>
|
|
30
|
+
- Madlions MAD68HE<sup>P</sup>
|
|
31
|
+
- Madlions MAD68R<sup>P</sup>
|
|
32
|
+
- Redragon K709HE<sup>P</sup>
|
|
33
|
+
|
|
34
|
+
<sup>R</sup> Razer Synapse needs to be installed and running for analogue inputs to be received from this keyboard.
|
|
35
|
+
<sup>P</sup> The official firmware only supports polling, which can lead to lag and missed inputs.
|
|
36
|
+
<sup>F</sup> [Custom firmware with full analog report functionality is available](https://analogsense.org/firmware/).
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
```bash
|
|
40
|
+
pip install AnalogSensePy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
On Linux you may need udev rules or `sudo` for hid
|
|
44
|
+
```bash
|
|
45
|
+
printf '%s\n' \
|
|
46
|
+
'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0660", GROUP="input"' \
|
|
47
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="31e3", MODE="0660", GROUP="input"' \
|
|
48
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", MODE="0660", GROUP="input"' \
|
|
49
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="1532", MODE="0660", GROUP="input"' \
|
|
50
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="19f5", MODE="0660", GROUP="input"' \
|
|
51
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="352d", MODE="0660", GROUP="input"' \
|
|
52
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="3434", MODE="0660", GROUP="input"' \
|
|
53
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="362d", MODE="0660", GROUP="input"' \
|
|
54
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="373b", MODE="0660", GROUP="input"' \
|
|
55
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="372e", MODE="0660", GROUP="input"' \
|
|
56
|
+
| sudo tee /etc/udev/rules.d/99-analogsense.rules
|
|
57
|
+
|
|
58
|
+
sudo udevadm control --reload-rules && sudo udevadm trigger
|
|
59
|
+
```
|
|
60
|
+
## Usage
|
|
61
|
+
```python
|
|
62
|
+
from analogsense import AnalogSense
|
|
63
|
+
|
|
64
|
+
as_ = AnalogSense()
|
|
65
|
+
devices = as_.get_devices()
|
|
66
|
+
|
|
67
|
+
if not devices:
|
|
68
|
+
print("no supported analog keyboards found.")
|
|
69
|
+
exit()
|
|
70
|
+
|
|
71
|
+
if len(devices) == 1:
|
|
72
|
+
dev = devices[0]
|
|
73
|
+
else:
|
|
74
|
+
for i, d in enumerate(devices):
|
|
75
|
+
print(f" [{i}] {d.product_name}")
|
|
76
|
+
dev = devices[int(input("select device index: "))]
|
|
77
|
+
|
|
78
|
+
def on_keys(active_keys):
|
|
79
|
+
for k in active_keys:
|
|
80
|
+
# converting the scancode to a nice readable string
|
|
81
|
+
name = as_.scancode_to_string(k["scancode"])
|
|
82
|
+
|
|
83
|
+
# some providers can return the digital state of the key
|
|
84
|
+
# (what the system perceives the key as pressed or not)
|
|
85
|
+
# other providers that do not support this will return None
|
|
86
|
+
if k["digital"] is not None:
|
|
87
|
+
digital = " [down]" if k["digital"] else " [up]"
|
|
88
|
+
else:
|
|
89
|
+
digital = ""
|
|
90
|
+
|
|
91
|
+
print(f"{name:20s} {k['value']:.4f}{digital}")
|
|
92
|
+
|
|
93
|
+
dev.start_listening(on_keys)
|
|
94
|
+
input("press keys... enter to stop.\n")
|
|
95
|
+
dev.stop_listening()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The following functions are available on `AnalogSense`:
|
|
99
|
+
- `get_devices() -> list[Device]`
|
|
100
|
+
- `open_device(vendor_id, product_id) -> Device | None`
|
|
101
|
+
- `scancode_to_string(scancode: int) -> str`
|
|
102
|
+
|
|
103
|
+
A device instance has the following members:
|
|
104
|
+
- `start_listening(handler: Callable[[list[{"scancode": int, "value": float}]], None])`
|
|
105
|
+
- `stop_listening()`
|
|
106
|
+
- `product_name: str`
|
|
107
|
+
- `forget()`
|
|
108
|
+
- `dev: DeviceHandle`
|
|
109
|
+
|
|
110
|
+
### Scancodes
|
|
111
|
+
The scancodes provided by this library are primarily HID scancodes; most keys are mapped as seen on usage page 0x07 (A = 0x04, B = 0x05, ...).
|
|
112
|
+
|
|
113
|
+
Control keys (usage page 0x0C) are mapped in the `0x3__` range, modulo 0x100:
|
|
114
|
+
- `0x3B5` = Next Track
|
|
115
|
+
- `0x3B6` = Previous Track
|
|
116
|
+
- `0x3B7` = Stop Media
|
|
117
|
+
- `0x3CD` = Play/Pause
|
|
118
|
+
- `0x394` = Open File Explorer
|
|
119
|
+
- `0x323` = Open Browser Home Page
|
|
120
|
+
|
|
121
|
+
OEM-specific keys are mapped in the `0x4__` range:
|
|
122
|
+
- `0x401` = Brightness Up
|
|
123
|
+
- `0x402` = Brightness Down
|
|
124
|
+
- `0x403` = Profile 1
|
|
125
|
+
- `0x404` = Profile 2
|
|
126
|
+
- `0x405` = Profile 3
|
|
127
|
+
- `0x408` = Profile Switch
|
|
128
|
+
- `0x409` = Function Key (Fn)
|
|
@@ -27,8 +27,24 @@ Python port of [AnalogSense.js](https://github.com/AnalogSense/JavaScript-SDK/)
|
|
|
27
27
|
```bash
|
|
28
28
|
pip install AnalogSensePy
|
|
29
29
|
```
|
|
30
|
+
|
|
30
31
|
On Linux you may need udev rules or `sudo` for hid
|
|
32
|
+
```bash
|
|
33
|
+
printf '%s\n' \
|
|
34
|
+
'KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0660", GROUP="input"' \
|
|
35
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="31e3", MODE="0660", GROUP="input"' \
|
|
36
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="03eb", MODE="0660", GROUP="input"' \
|
|
37
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="1532", MODE="0660", GROUP="input"' \
|
|
38
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="19f5", MODE="0660", GROUP="input"' \
|
|
39
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="352d", MODE="0660", GROUP="input"' \
|
|
40
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="3434", MODE="0660", GROUP="input"' \
|
|
41
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="362d", MODE="0660", GROUP="input"' \
|
|
42
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="373b", MODE="0660", GROUP="input"' \
|
|
43
|
+
'SUBSYSTEM=="usb", ATTRS{idVendor}=="372e", MODE="0660", GROUP="input"' \
|
|
44
|
+
| sudo tee /etc/udev/rules.d/99-analogsense.rules
|
|
31
45
|
|
|
46
|
+
sudo udevadm control --reload-rules && sudo udevadm trigger
|
|
47
|
+
```
|
|
32
48
|
## Usage
|
|
33
49
|
```python
|
|
34
50
|
from analogsense import AnalogSense
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
import sys
|
|
2
3
|
import warnings
|
|
3
4
|
|
|
4
5
|
from .providers import ALL_PROVIDERS, AsProvider
|
|
@@ -10,7 +11,10 @@ try:
|
|
|
10
11
|
_HID_AVAILABLE = True
|
|
11
12
|
except ImportError:
|
|
12
13
|
_HID_AVAILABLE = False
|
|
13
|
-
|
|
14
|
+
if sys.platform == "linux":
|
|
15
|
+
warnings.warn("'hid' is not installed. you can install it with: pip install hid")
|
|
16
|
+
else:
|
|
17
|
+
warnings.warn("'hidapi' is not installed. you can install it with: pip install hidapi")
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
class DeviceHandle:
|
|
@@ -44,14 +48,14 @@ class AnalogSense:
|
|
|
44
48
|
seen = set()
|
|
45
49
|
for provider_cls in self.providers:
|
|
46
50
|
for f in provider_cls.FILTERS:
|
|
47
|
-
kwargs = {}
|
|
48
|
-
if "vendor_id" in f: kwargs["vendor_id"] = f["vendor_id"]
|
|
49
|
-
if "product_id" in f: kwargs["product_id"] = f["product_id"]
|
|
50
51
|
try:
|
|
51
|
-
|
|
52
|
+
vendor_id = f.get("vendor_id", 0)
|
|
53
|
+
product_id = f.get("product_id", 0)
|
|
54
|
+
for info in _hid.enumerate(vendor_id, product_id):
|
|
52
55
|
dedup_key = (info["vendor_id"], info["product_id"], info.get("usage_page", 0), info.get("usage", 0))
|
|
53
56
|
if dedup_key in seen: continue
|
|
54
57
|
if "usage_page" in f and info.get("usage_page") != f["usage_page"]: continue
|
|
58
|
+
if "usage" in f and info.get("usage") != f["usage"]: continue
|
|
55
59
|
seen.add(dedup_key)
|
|
56
60
|
candidates.append((info, provider_cls))
|
|
57
61
|
except Exception as e:
|
|
@@ -59,13 +63,26 @@ class AnalogSense:
|
|
|
59
63
|
return candidates
|
|
60
64
|
|
|
61
65
|
def _open(self, info, provider_cls):
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
try:
|
|
67
|
+
if sys.platform == "linux":
|
|
68
|
+
dev = _hid.Device(path=info["path"])
|
|
69
|
+
else:
|
|
70
|
+
dev = _hid.device()
|
|
71
|
+
dev.open_path(info["path"])
|
|
72
|
+
return provider_cls(DeviceHandle(dev, info))
|
|
73
|
+
except Exception as e:
|
|
74
|
+
if "Permission denied" in str(e):
|
|
75
|
+
warnings.warn(
|
|
76
|
+
f"Permission denied opening {info.get('product_string', info['path'])}. "
|
|
77
|
+
"On Linux, udev rules are required. See: https://github.com/girlglock/AnalogSense-Python-SDK#installation"
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
warnings.warn(f"Failed to open device: {e}")
|
|
81
|
+
return None
|
|
65
82
|
|
|
66
83
|
def get_devices(self):
|
|
67
84
|
if not _HID_AVAILABLE: return []
|
|
68
|
-
return [self._open(info, cls) for info, cls in self._collect_candidates()]
|
|
85
|
+
return [dev for dev in (self._open(info, cls) for info, cls in self._collect_candidates()) if dev is not None]
|
|
69
86
|
|
|
70
87
|
def open_device(self, vendor_id, product_id):
|
|
71
88
|
if not _HID_AVAILABLE: return None
|
|
@@ -133,10 +133,12 @@ class AsProviderWootingV2(AsProvider):
|
|
|
133
133
|
|
|
134
134
|
class AsProviderRazerHuntsman(AsProvider):
|
|
135
135
|
FILTERS = [
|
|
136
|
-
{"vendor_id": 0x1532, "product_id": 0x0266},
|
|
137
|
-
{"vendor_id": 0x1532, "product_id": 0x0282},
|
|
136
|
+
{"vendor_id": 0x1532, "product_id": 0x0266, "usage_page": 0x0001}, #Razer Huntsman V2
|
|
137
|
+
{"vendor_id": 0x1532, "product_id": 0x0282, "usage_page": 0x0001}, #Razer Huntsman Mini
|
|
138
138
|
]
|
|
139
139
|
|
|
140
|
+
_REPORT_ID = 7
|
|
141
|
+
|
|
140
142
|
def start_listening(self, handler: Handler):
|
|
141
143
|
self._running = True
|
|
142
144
|
self._prev_scancodes: set[int] = set()
|
|
@@ -144,7 +146,10 @@ class AsProviderRazerHuntsman(AsProvider):
|
|
|
144
146
|
self._thread.start()
|
|
145
147
|
|
|
146
148
|
def _handle_report(self, data: bytes, handler: Handler):
|
|
147
|
-
if data[0]
|
|
149
|
+
if data[0] == self._REPORT_ID:
|
|
150
|
+
data = data[1:]
|
|
151
|
+
elif len(data) > 1 and data[0] != 0:
|
|
152
|
+
return
|
|
148
153
|
active_keys = []
|
|
149
154
|
current_scancodes = set()
|
|
150
155
|
i = 1
|
|
@@ -164,10 +169,11 @@ class AsProviderRazerHuntsman(AsProvider):
|
|
|
164
169
|
|
|
165
170
|
class AsProviderRazerHuntsmanV3(AsProvider):
|
|
166
171
|
FILTERS = [
|
|
167
|
-
{"vendor_id": 0x1532, "product_id": 0x02A6},
|
|
168
|
-
{"vendor_id": 0x1532, "product_id": 0x02A7},
|
|
169
|
-
{"vendor_id": 0x1532, "product_id": 0x02B0},
|
|
172
|
+
{"vendor_id": 0x1532, "product_id": 0x02A6, "usage_page": 0x0001}, #Razer Huntsman V3 Pro
|
|
173
|
+
{"vendor_id": 0x1532, "product_id": 0x02A7, "usage_page": 0x0001}, #Razer Huntsman V3 Pro Tenkeyless
|
|
174
|
+
{"vendor_id": 0x1532, "product_id": 0x02B0, "usage_page": 0x0001}, #Razer Huntsman V3 Pro Mini
|
|
170
175
|
]
|
|
176
|
+
_REPORT_ID = 11
|
|
171
177
|
|
|
172
178
|
def start_listening(self, handler: Handler):
|
|
173
179
|
self._running = True
|
|
@@ -176,7 +182,10 @@ class AsProviderRazerHuntsmanV3(AsProvider):
|
|
|
176
182
|
self._thread.start()
|
|
177
183
|
|
|
178
184
|
def _handle_report(self, data: bytes, handler: Handler):
|
|
179
|
-
if data[0]
|
|
185
|
+
if data[0] == self._REPORT_ID:
|
|
186
|
+
data = data[1:]
|
|
187
|
+
elif len(data) > 1 and data[0] != 0:
|
|
188
|
+
return
|
|
180
189
|
active_keys = []
|
|
181
190
|
current_scancodes = set()
|
|
182
191
|
i = 1
|
|
@@ -194,10 +203,10 @@ class AsProviderRazerHuntsmanV3(AsProvider):
|
|
|
194
203
|
self._prev_scancodes = current_scancodes
|
|
195
204
|
handler(active_keys)
|
|
196
205
|
|
|
197
|
-
|
|
206
|
+
#this one needs testing
|
|
198
207
|
class AsProviderNuphy(AsProvider):
|
|
199
208
|
FILTERS = [
|
|
200
|
-
{"vendor_id": 0x19F5},
|
|
209
|
+
{"vendor_id": 0x19F5, "usage_page": 0x0001, "usage": 0x0000},
|
|
201
210
|
]
|
|
202
211
|
|
|
203
212
|
def start_listening(self, handler: Handler):
|
|
@@ -264,15 +273,15 @@ class AsProviderDrunkdeer(AsProvider):
|
|
|
264
273
|
|
|
265
274
|
class AsProviderKeychron(AsProvider):
|
|
266
275
|
FILTERS = [
|
|
267
|
-
{"vendor_id": 0x3434, "product_id": 0x0B10},
|
|
268
|
-
{"vendor_id": 0x3434, "product_id": 0x0B11},
|
|
269
|
-
{"vendor_id": 0x3434, "product_id": 0x0B12},
|
|
270
|
-
{"vendor_id": 0x3434, "product_id": 0x0B30},
|
|
271
|
-
{"vendor_id": 0x3434, "product_id": 0x0B50},
|
|
272
|
-
{"vendor_id": 0x3434, "product_id": 0x0E20},
|
|
273
|
-
{"vendor_id": 0x3434, "product_id": 0x0E21},
|
|
274
|
-
{"vendor_id": 0x3434, "product_id": 0x0E22},
|
|
275
|
-
{"vendor_id": 0x362D, "product_id": 0x0610},
|
|
276
|
+
{"vendor_id": 0x3434, "product_id": 0x0B10, "usage_page": 0xFF60, "usage": 0x61}, #Q1 HE ANSI
|
|
277
|
+
{"vendor_id": 0x3434, "product_id": 0x0B11, "usage_page": 0xFF60, "usage": 0x61}, #Q1 HE ISO
|
|
278
|
+
{"vendor_id": 0x3434, "product_id": 0x0B12, "usage_page": 0xFF60, "usage": 0x61}, #Q1 HE JIS
|
|
279
|
+
{"vendor_id": 0x3434, "product_id": 0x0B30, "usage_page": 0xFF60, "usage": 0x61}, #Q3 HE ANSI
|
|
280
|
+
{"vendor_id": 0x3434, "product_id": 0x0B50, "usage_page": 0xFF60, "usage": 0x61}, #Q5 HE ANSI
|
|
281
|
+
{"vendor_id": 0x3434, "product_id": 0x0E20, "usage_page": 0xFF60, "usage": 0x61}, #K2 HE ANSI
|
|
282
|
+
{"vendor_id": 0x3434, "product_id": 0x0E21, "usage_page": 0xFF60, "usage": 0x61}, #K2 HE ISO
|
|
283
|
+
{"vendor_id": 0x3434, "product_id": 0x0E22, "usage_page": 0xFF60, "usage": 0x61}, #K2 HE JIS
|
|
284
|
+
{"vendor_id": 0x362D, "product_id": 0x0610, "usage_page": 0xFF60, "usage": 0x61}, #Lemokey P1 HE ANSI
|
|
276
285
|
]
|
|
277
286
|
|
|
278
287
|
_INIT_PAYLOAD = bytes([
|
|
@@ -373,15 +382,19 @@ class AsProviderKeychron(AsProvider):
|
|
|
373
382
|
|
|
374
383
|
class AsProviderMadlions(AsProvider):
|
|
375
384
|
FILTERS = [
|
|
376
|
-
|
|
377
|
-
{"vendor_id": 0x373B, "product_id":
|
|
378
|
-
{"vendor_id": 0x373B, "product_id":
|
|
379
|
-
{"vendor_id": 0x373B, "product_id":
|
|
380
|
-
{"vendor_id": 0x373B, "product_id":
|
|
381
|
-
{"vendor_id": 0x373B, "product_id":
|
|
382
|
-
|
|
383
|
-
{"vendor_id": 0x373B, "product_id":
|
|
384
|
-
{"vendor_id": 0x373B, "product_id":
|
|
385
|
+
#MAD60HE
|
|
386
|
+
{"vendor_id": 0x373B, "product_id": 0x1053, "usage_page": 0xFF60, "usage": 0x61},
|
|
387
|
+
{"vendor_id": 0x373B, "product_id": 0x1054, "usage_page": 0xFF60, "usage": 0x61},
|
|
388
|
+
{"vendor_id": 0x373B, "product_id": 0x1055, "usage_page": 0xFF60, "usage": 0x61},
|
|
389
|
+
{"vendor_id": 0x373B, "product_id": 0x1056, "usage_page": 0xFF60, "usage": 0x61},
|
|
390
|
+
{"vendor_id": 0x373B, "product_id": 0x105D, "usage_page": 0xFF60, "usage": 0x61},
|
|
391
|
+
#MAD68HE
|
|
392
|
+
{"vendor_id": 0x373B, "product_id": 0x1058, "usage_page": 0xFF60, "usage": 0x61},
|
|
393
|
+
{"vendor_id": 0x373B, "product_id": 0x1059, "usage_page": 0xFF60, "usage": 0x61},
|
|
394
|
+
{"vendor_id": 0x373B, "product_id": 0x105A, "usage_page": 0xFF60, "usage": 0x61},
|
|
395
|
+
{"vendor_id": 0x373B, "product_id": 0x105C, "usage_page": 0xFF60, "usage": 0x61},
|
|
396
|
+
#MAD68R
|
|
397
|
+
{"vendor_id": 0x373B, "product_id": 0x10A7, "usage_page": 0xFF60, "usage": 0x61},
|
|
385
398
|
]
|
|
386
399
|
|
|
387
400
|
_INIT_PAYLOAD = bytes([
|
|
@@ -391,7 +404,7 @@ class AsProviderMadlions(AsProvider):
|
|
|
391
404
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
392
405
|
])
|
|
393
406
|
|
|
394
|
-
_MAD60HE_PIDS = {0x1053, 0x1055, 0x1056, 0x105D}
|
|
407
|
+
_MAD60HE_PIDS = {0x1053, 0x1054, 0x1055, 0x1056, 0x105D}
|
|
395
408
|
|
|
396
409
|
def start_listening(self, handler: Handler):
|
|
397
410
|
pid = self.dev.product_id
|
|
@@ -426,7 +439,7 @@ class AsProviderMadlions(AsProvider):
|
|
|
426
439
|
|
|
427
440
|
class AsProviderBytech(AsProvider):
|
|
428
441
|
FILTERS = [
|
|
429
|
-
{"vendor_id": 0x372E, "product_id": 0x105B},
|
|
442
|
+
{"vendor_id": 0x372E, "product_id": 0x105B, "usage_page": 0xFF00}, #Redragon K709 HE
|
|
430
443
|
]
|
|
431
444
|
|
|
432
445
|
def _build_payload(self, cmd, sub):
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "AnalogSensePy"
|
|
7
|
+
version = "1.0.6"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = { text = "MIT" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"hid; sys_platform == 'linux'",
|
|
13
|
+
"hidapi; sys_platform != 'linux'",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/girlglock/AnalogSense-Python-SDK"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
hidapi
|
analogsensepy-1.0.4/PKG-INFO
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|