AnalogSensePy 1.0.0__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.
- analogsense/__init__.py +4 -0
- analogsense/analogsense.py +100 -0
- analogsense/keymaps.py +261 -0
- analogsense/layouts.py +104 -0
- analogsense/providers.py +501 -0
- analogsense/setup.py +10 -0
- analogsensepy-1.0.0.dist-info/METADATA +7 -0
- analogsensepy-1.0.0.dist-info/RECORD +11 -0
- analogsensepy-1.0.0.dist-info/WHEEL +5 -0
- analogsensepy-1.0.0.dist-info/licenses/LICENSE +22 -0
- analogsensepy-1.0.0.dist-info/top_level.txt +1 -0
analogsense/__init__.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
from .providers import ALL_PROVIDERS, AsProvider
|
|
5
|
+
from .keymaps import wooting_to_name, razer_to_wooting, nuphy_to_wooting, bytech_to_wooting
|
|
6
|
+
from .layouts import drunkdeer_index_to_hid_scancode
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import hid as _hid
|
|
10
|
+
_HID_AVAILABLE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
_HID_AVAILABLE = False
|
|
13
|
+
warnings.warn("'hid' is not installed. you can install it with: pip install hid")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeviceHandle:
|
|
17
|
+
def __init__(self, hid_dev, info: dict):
|
|
18
|
+
self._dev = hid_dev
|
|
19
|
+
self.vendor_id = info["vendor_id"]
|
|
20
|
+
self.product_id = info["product_id"]
|
|
21
|
+
self.product_string = info.get("product_string") or f"{info['vendor_id']:#06x}:{info['product_id']:#06x}"
|
|
22
|
+
|
|
23
|
+
def __getattr__(self, name):
|
|
24
|
+
return getattr(self._dev, name)
|
|
25
|
+
|
|
26
|
+
def read(self, size, timeout_ms=100):
|
|
27
|
+
return self._dev.read(size, timeout_ms)
|
|
28
|
+
|
|
29
|
+
def write(self, data):
|
|
30
|
+
return self._dev.write(data)
|
|
31
|
+
|
|
32
|
+
def close(self):
|
|
33
|
+
return self._dev.close()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AnalogSense:
|
|
37
|
+
def __init__(self, extra_providers=None):
|
|
38
|
+
self.providers = list(ALL_PROVIDERS)
|
|
39
|
+
if extra_providers:
|
|
40
|
+
self.providers.extend(extra_providers)
|
|
41
|
+
|
|
42
|
+
def _collect_candidates(self):
|
|
43
|
+
candidates = []
|
|
44
|
+
seen = set()
|
|
45
|
+
for provider_cls in self.providers:
|
|
46
|
+
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
|
+
try:
|
|
51
|
+
for info in _hid.enumerate(**kwargs):
|
|
52
|
+
dedup_key = (info["vendor_id"], info["product_id"], info.get("usage_page", 0), info.get("usage", 0))
|
|
53
|
+
if dedup_key in seen: continue
|
|
54
|
+
if "usage_page" in f and info.get("usage_page") != f["usage_page"]: continue
|
|
55
|
+
seen.add(dedup_key)
|
|
56
|
+
candidates.append((info, provider_cls))
|
|
57
|
+
except Exception as e:
|
|
58
|
+
warnings.warn(f"Error enumerating HID devices: {e}")
|
|
59
|
+
return candidates
|
|
60
|
+
|
|
61
|
+
def _open(self, info, provider_cls):
|
|
62
|
+
dev = _hid.device()
|
|
63
|
+
dev.open_path(info["path"])
|
|
64
|
+
return provider_cls(DeviceHandle(dev, info))
|
|
65
|
+
|
|
66
|
+
def get_devices(self):
|
|
67
|
+
if not _HID_AVAILABLE: return []
|
|
68
|
+
return [self._open(info, cls) for info, cls in self._collect_candidates()]
|
|
69
|
+
|
|
70
|
+
def open_device(self, vendor_id, product_id):
|
|
71
|
+
if not _HID_AVAILABLE: return None
|
|
72
|
+
for info, provider_cls in self._collect_candidates():
|
|
73
|
+
if info["vendor_id"] == vendor_id and info["product_id"] == product_id:
|
|
74
|
+
return self._open(info, provider_cls)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def scancode_to_string(self, scancode):
|
|
78
|
+
return wooting_to_name.get(scancode, str(scancode))
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def razer_scancode_to_hid(scancode):
|
|
82
|
+
result = razer_to_wooting.get(scancode, 0)
|
|
83
|
+
if result == 0: warnings.warn(f"Failed to map Razer scancode to HID: {scancode:#x}")
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def nuphy_scancode_to_hid(scancode):
|
|
88
|
+
result = nuphy_to_wooting.get(scancode, 0)
|
|
89
|
+
if result == 0: warnings.warn(f"Failed to map NuPhy scancode to HID: {scancode:#x}")
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def bytech_scancode_to_hid(scancode):
|
|
94
|
+
result = bytech_to_wooting.get(scancode, 0)
|
|
95
|
+
if result == 0: warnings.warn(f"Failed to map Bytech scancode to HID: {scancode:#x}")
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def drunkdeer_index_to_hid(index):
|
|
100
|
+
return drunkdeer_index_to_hid_scancode(index)
|
analogsense/keymaps.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
KEYS = [
|
|
2
|
+
{"name": "Escape", "wooting": 0x29, "razer": 0x6E, "bytech": 1},
|
|
3
|
+
{"name": "F1", "wooting": 0x3A, "razer": 0x70, "bytech": 2},
|
|
4
|
+
{"name": "F2", "wooting": 0x3B, "razer": 0x71, "bytech": 3},
|
|
5
|
+
{"name": "F3", "wooting": 0x3C, "razer": 0x72, "bytech": 4},
|
|
6
|
+
{"name": "F4", "wooting": 0x3D, "razer": 0x73, "bytech": 5},
|
|
7
|
+
{"name": "F5", "wooting": 0x3E, "razer": 0x74, "bytech": 6},
|
|
8
|
+
{"name": "F6", "wooting": 0x3F, "razer": 0x75, "bytech": 7},
|
|
9
|
+
{"name": "F7", "wooting": 0x40, "razer": 0x76, "bytech": 8},
|
|
10
|
+
{"name": "F8", "wooting": 0x41, "razer": 0x77, "bytech": 9},
|
|
11
|
+
{"name": "F9", "wooting": 0x42, "razer": 0x78, "bytech": 10},
|
|
12
|
+
{"name": "F10", "wooting": 0x43, "razer": 0x79, "bytech": 11},
|
|
13
|
+
{"name": "F11", "wooting": 0x44, "razer": 0x7A, "bytech": 12},
|
|
14
|
+
{"name": "F12", "wooting": 0x45, "razer": 0x7B, "bytech": 13},
|
|
15
|
+
{"name": "`", "wooting": 0x35, "razer": 0x01, "bytech": 14},
|
|
16
|
+
{"name": "1", "wooting": 0x1E, "razer": 0x02, "bytech": 15},
|
|
17
|
+
{"name": "2", "wooting": 0x1F, "razer": 0x03, "bytech": 16},
|
|
18
|
+
{"name": "3", "wooting": 0x20, "razer": 0x04, "bytech": 17},
|
|
19
|
+
{"name": "4", "wooting": 0x21, "razer": 0x05, "bytech": 18},
|
|
20
|
+
{"name": "5", "wooting": 0x22, "razer": 0x06, "bytech": 19},
|
|
21
|
+
{"name": "6", "wooting": 0x23, "razer": 0x07, "bytech": 20},
|
|
22
|
+
{"name": "7", "wooting": 0x24, "razer": 0x08, "bytech": 21},
|
|
23
|
+
{"name": "8", "wooting": 0x25, "razer": 0x09, "bytech": 22},
|
|
24
|
+
{"name": "9", "wooting": 0x26, "razer": 0x0A, "bytech": 23},
|
|
25
|
+
{"name": "0", "wooting": 0x27, "razer": 0x0B, "bytech": 24},
|
|
26
|
+
{"name": "-", "wooting": 0x2D, "razer": 0x0C, "bytech": 25},
|
|
27
|
+
{"name": "=", "wooting": 0x2E, "razer": 0x0D, "bytech": 26},
|
|
28
|
+
{"name": "Backspace", "wooting": 0x2A, "razer": 0x0F, "bytech": 27},
|
|
29
|
+
{"name": "Tab", "wooting": 0x2B, "razer": 0x10, "bytech": 28},
|
|
30
|
+
{"name": "Q", "wooting": 0x14, "razer": 0x11, "bytech": 29},
|
|
31
|
+
{"name": "W", "wooting": 0x1A, "razer": 0x12, "bytech": 30},
|
|
32
|
+
{"name": "E", "wooting": 0x08, "razer": 0x13, "bytech": 31},
|
|
33
|
+
{"name": "R", "wooting": 0x15, "razer": 0x14, "bytech": 32},
|
|
34
|
+
{"name": "T", "wooting": 0x17, "razer": 0x15, "bytech": 33},
|
|
35
|
+
{"name": "Y", "wooting": 0x1C, "razer": 0x16, "bytech": 34},
|
|
36
|
+
{"name": "U", "wooting": 0x18, "razer": 0x17, "bytech": 35},
|
|
37
|
+
{"name": "I", "wooting": 0x0C, "razer": 0x18, "bytech": 36},
|
|
38
|
+
{"name": "O", "wooting": 0x12, "razer": 0x19, "bytech": 37},
|
|
39
|
+
{"name": "P", "wooting": 0x13, "razer": 0x1A, "bytech": 38},
|
|
40
|
+
{"name": "[", "wooting": 0x2F, "razer": 0x1B, "bytech": 39},
|
|
41
|
+
{"name": "]", "wooting": 0x30, "razer": 0x1C, "bytech": 40},
|
|
42
|
+
{"name": "Enter", "wooting": 0x28, "razer": 0x2B, "bytech": 54},
|
|
43
|
+
{"name": "Caps Lock", "wooting": 0x39, "razer": 0x1E, "bytech": 42},
|
|
44
|
+
{"name": "A", "wooting": 0x04, "razer": 0x1F, "bytech": 43},
|
|
45
|
+
{"name": "S", "wooting": 0x16, "razer": 0x20, "bytech": 44},
|
|
46
|
+
{"name": "D", "wooting": 0x07, "razer": 0x21, "bytech": 45},
|
|
47
|
+
{"name": "F", "wooting": 0x09, "razer": 0x22, "bytech": 46},
|
|
48
|
+
{"name": "G", "wooting": 0x0A, "razer": 0x23, "bytech": 47},
|
|
49
|
+
{"name": "H", "wooting": 0x0B, "razer": 0x24, "bytech": 48},
|
|
50
|
+
{"name": "J", "wooting": 0x0D, "razer": 0x25, "bytech": 49},
|
|
51
|
+
{"name": "K", "wooting": 0x0E, "razer": 0x26, "bytech": 50},
|
|
52
|
+
{"name": "L", "wooting": 0x0F, "razer": 0x27, "bytech": 51},
|
|
53
|
+
{"name": ";", "wooting": 0x33, "razer": 0x28, "bytech": 52},
|
|
54
|
+
{"name": "'", "wooting": 0x34, "razer": 0x29, "bytech": 53},
|
|
55
|
+
{"name": "Backslash", "wooting": 0x31, "razer": 0x2A, "bytech": 41},
|
|
56
|
+
{"name": "Left Shift", "wooting": 0xE1, "razer": 0x2C, "nuphy": 0x200, "bytech": 55},
|
|
57
|
+
{"name": "Intl Backslash", "wooting": 0x64, "razer": 0x2D},
|
|
58
|
+
{"name": "Z", "wooting": 0x1D, "razer": 0x2E, "bytech": 56},
|
|
59
|
+
{"name": "X", "wooting": 0x1B, "razer": 0x2F, "bytech": 57},
|
|
60
|
+
{"name": "C", "wooting": 0x06, "razer": 0x30, "bytech": 58},
|
|
61
|
+
{"name": "V", "wooting": 0x19, "razer": 0x31, "bytech": 59},
|
|
62
|
+
{"name": "B", "wooting": 0x05, "razer": 0x32, "bytech": 60},
|
|
63
|
+
{"name": "N", "wooting": 0x11, "razer": 0x33, "bytech": 61},
|
|
64
|
+
{"name": "M", "wooting": 0x10, "razer": 0x34, "bytech": 62},
|
|
65
|
+
{"name": ",", "wooting": 0x36, "razer": 0x35, "bytech": 63},
|
|
66
|
+
{"name": ".", "wooting": 0x37, "razer": 0x36, "bytech": 64},
|
|
67
|
+
{"name": "/", "wooting": 0x38, "razer": 0x37, "bytech": 65},
|
|
68
|
+
{"name": "Right Shift", "wooting": 0xE5, "razer": 0x39, "nuphy": 0x2000, "bytech": 66},
|
|
69
|
+
{"name": "Left Ctrl", "wooting": 0xE0, "razer": 0x3A, "nuphy": 0x100, "bytech": 67},
|
|
70
|
+
{"name": "Left Meta", "wooting": 0xE3, "razer": 0x7F, "nuphy": 0x800, "bytech": 68},
|
|
71
|
+
{"name": "Left Alt", "wooting": 0xE2, "razer": 0x3C, "nuphy": 0x400, "bytech": 69},
|
|
72
|
+
{"name": "Space", "wooting": 0x2C, "razer": 0x3D, "bytech": 70},
|
|
73
|
+
{"name": "Right Alt", "wooting": 0xE6, "razer": 0x3E, "nuphy": 0x4000, "bytech": 71},
|
|
74
|
+
{"name": "Right Meta", "wooting": 0xE7, "nuphy": 0x8000},
|
|
75
|
+
{"name": "Fn", "wooting": 0x409, "razer": 0x3B, "nuphy": 0xFF05, "bytech": 72},
|
|
76
|
+
{"name": "Context Menu", "wooting": 0x65, "razer": 0x81},
|
|
77
|
+
{"name": "Right Ctrl", "wooting": 0xE4, "razer": 0x40, "nuphy": 0x1000, "bytech": 73},
|
|
78
|
+
{"name": "Print Screen", "wooting": 0x46, "razer": 0x7C},
|
|
79
|
+
{"name": "Pause", "wooting": 0x48, "razer": 0x7D},
|
|
80
|
+
{"name": "Scroll Lock", "wooting": 0x47, "razer": 0x7E},
|
|
81
|
+
{"name": "Insert", "wooting": 0x49, "razer": 0x4B},
|
|
82
|
+
{"name": "Home", "wooting": 0x4A, "razer": 0x50, "bytech": 100},
|
|
83
|
+
{"name": "Page Up", "wooting": 0x4B, "razer": 0x55, "bytech": 102},
|
|
84
|
+
{"name": "Delete", "wooting": 0x4C, "razer": 0x4C, "bytech": 99},
|
|
85
|
+
{"name": "End", "wooting": 0x4D, "razer": 0x51},
|
|
86
|
+
{"name": "Page Down", "wooting": 0x4E, "razer": 0x56, "bytech": 103},
|
|
87
|
+
{"name": "Up Arrow", "wooting": 0x52, "razer": 0x53, "bytech": 74},
|
|
88
|
+
{"name": "Left Arrow", "wooting": 0x50, "razer": 0x4F, "bytech": 76},
|
|
89
|
+
{"name": "Down Arrow", "wooting": 0x51, "razer": 0x54, "bytech": 75},
|
|
90
|
+
{"name": "Right Arrow", "wooting": 0x4F, "razer": 0x59, "bytech": 77},
|
|
91
|
+
{"name": "Num Lock", "wooting": 0x53, "razer": 0x5A},
|
|
92
|
+
{"name": "Numpad /", "wooting": 0x54, "razer": 0x5F},
|
|
93
|
+
{"name": "Numpad *", "wooting": 0x55, "razer": 0x64},
|
|
94
|
+
{"name": "Numpad -", "wooting": 0x56, "razer": 0x69},
|
|
95
|
+
{"name": "Numpad 7", "wooting": 0x5F, "razer": 0x5B},
|
|
96
|
+
{"name": "Numpad 8", "wooting": 0x60, "razer": 0x60},
|
|
97
|
+
{"name": "Numpad 9", "wooting": 0x61, "razer": 0x65},
|
|
98
|
+
{"name": "Numpad +", "wooting": 0x57, "razer": 0x6A},
|
|
99
|
+
{"name": "Numpad 4", "wooting": 0x5C, "razer": 0x5C},
|
|
100
|
+
{"name": "Numpad 5", "wooting": 0x5D, "razer": 0x61},
|
|
101
|
+
{"name": "Numpad 6", "wooting": 0x5E, "razer": 0x66},
|
|
102
|
+
{"name": "Numpad 1", "wooting": 0x59, "razer": 0x5D},
|
|
103
|
+
{"name": "Numpad 2", "wooting": 0x5A, "razer": 0x62},
|
|
104
|
+
{"name": "Numpad 3", "wooting": 0x5B, "razer": 0x67},
|
|
105
|
+
{"name": "Numpad Enter", "wooting": 0x58, "razer": 0x6C},
|
|
106
|
+
{"name": "Numpad 0", "wooting": 0x62, "razer": 0x63},
|
|
107
|
+
{"name": "Numpad .", "wooting": 0x63, "razer": 0x68},
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
wooting_to_name = {k["wooting"]: k["name"] for k in KEYS}
|
|
111
|
+
razer_to_wooting = {k["razer"]: k["wooting"] for k in KEYS if "razer" in k}
|
|
112
|
+
nuphy_to_wooting = {k.get("nuphy", k["wooting"]): k["wooting"] for k in KEYS}
|
|
113
|
+
bytech_to_wooting = {k["bytech"]: k["wooting"] for k in KEYS if "bytech" in k}
|
|
114
|
+
|
|
115
|
+
KEY_NONE = 0x00
|
|
116
|
+
KEY_A = 0x04
|
|
117
|
+
KEY_B = 0x05
|
|
118
|
+
KEY_C = 0x06
|
|
119
|
+
KEY_D = 0x07
|
|
120
|
+
KEY_E = 0x08
|
|
121
|
+
KEY_F = 0x09
|
|
122
|
+
KEY_G = 0x0A
|
|
123
|
+
KEY_H = 0x0B
|
|
124
|
+
KEY_I = 0x0C
|
|
125
|
+
KEY_J = 0x0D
|
|
126
|
+
KEY_K = 0x0E
|
|
127
|
+
KEY_L = 0x0F
|
|
128
|
+
KEY_M = 0x10
|
|
129
|
+
KEY_N = 0x11
|
|
130
|
+
KEY_O = 0x12
|
|
131
|
+
KEY_P = 0x13
|
|
132
|
+
KEY_Q = 0x14
|
|
133
|
+
KEY_R = 0x15
|
|
134
|
+
KEY_S = 0x16
|
|
135
|
+
KEY_T = 0x17
|
|
136
|
+
KEY_U = 0x18
|
|
137
|
+
KEY_V = 0x19
|
|
138
|
+
KEY_W = 0x1A
|
|
139
|
+
KEY_X = 0x1B
|
|
140
|
+
KEY_Y = 0x1C
|
|
141
|
+
KEY_Z = 0x1D
|
|
142
|
+
KEY_1 = 0x1E
|
|
143
|
+
KEY_2 = 0x1F
|
|
144
|
+
KEY_3 = 0x20
|
|
145
|
+
KEY_4 = 0x21
|
|
146
|
+
KEY_5 = 0x22
|
|
147
|
+
KEY_6 = 0x23
|
|
148
|
+
KEY_7 = 0x24
|
|
149
|
+
KEY_8 = 0x25
|
|
150
|
+
KEY_9 = 0x26
|
|
151
|
+
KEY_0 = 0x27
|
|
152
|
+
KEY_ENTER = 0x28
|
|
153
|
+
KEY_ESCAPE = 0x29
|
|
154
|
+
KEY_BACKSPACE = 0x2A
|
|
155
|
+
KEY_TAB = 0x2B
|
|
156
|
+
KEY_SPACE = 0x2C
|
|
157
|
+
KEY_MINUS = 0x2D
|
|
158
|
+
KEY_EQUALS = 0x2E
|
|
159
|
+
KEY_BRACKET_LEFT = 0x2F
|
|
160
|
+
KEY_BRACKET_RIGHT = 0x30
|
|
161
|
+
KEY_BACKSLASH = 0x31
|
|
162
|
+
KEY_INTL_HASH = 0x32
|
|
163
|
+
KEY_SEMICOLON = 0x33
|
|
164
|
+
KEY_QUOTE = 0x34
|
|
165
|
+
KEY_BACKQUOTE = 0x35
|
|
166
|
+
KEY_COMMA = 0x36
|
|
167
|
+
KEY_PERIOD = 0x37
|
|
168
|
+
KEY_SLASH = 0x38
|
|
169
|
+
KEY_CAPS_LOCK = 0x39
|
|
170
|
+
KEY_F1 = 0x3A
|
|
171
|
+
KEY_F2 = 0x3B
|
|
172
|
+
KEY_F3 = 0x3C
|
|
173
|
+
KEY_F4 = 0x3D
|
|
174
|
+
KEY_F5 = 0x3E
|
|
175
|
+
KEY_F6 = 0x3F
|
|
176
|
+
KEY_F7 = 0x40
|
|
177
|
+
KEY_F8 = 0x41
|
|
178
|
+
KEY_F9 = 0x42
|
|
179
|
+
KEY_F10 = 0x43
|
|
180
|
+
KEY_F11 = 0x44
|
|
181
|
+
KEY_F12 = 0x45
|
|
182
|
+
KEY_PRINT_SCREEN = 0x46
|
|
183
|
+
KEY_SCROLL_LOCK = 0x47
|
|
184
|
+
KEY_PAUSE = 0x48
|
|
185
|
+
KEY_INSERT = 0x49
|
|
186
|
+
KEY_HOME = 0x4A
|
|
187
|
+
KEY_PAGE_UP = 0x4B
|
|
188
|
+
KEY_DEL = 0x4C
|
|
189
|
+
KEY_END = 0x4D
|
|
190
|
+
KEY_PAGE_DOWN = 0x4E
|
|
191
|
+
KEY_ARROW_RIGHT = 0x4F
|
|
192
|
+
KEY_ARROW_LEFT = 0x50
|
|
193
|
+
KEY_ARROW_DOWN = 0x51
|
|
194
|
+
KEY_ARROW_UP = 0x52
|
|
195
|
+
KEY_NUM_LOCK = 0x53
|
|
196
|
+
KEY_NUMPAD_DIVIDE = 0x54
|
|
197
|
+
KEY_NUMPAD_MULTIPLY = 0x55
|
|
198
|
+
KEY_NUMPAD_SUBTRACT = 0x56
|
|
199
|
+
KEY_NUMPAD_ADD = 0x57
|
|
200
|
+
KEY_NUMPAD_ENTER = 0x58
|
|
201
|
+
KEY_NUMPAD1 = 0x59
|
|
202
|
+
KEY_NUMPAD2 = 0x5A
|
|
203
|
+
KEY_NUMPAD3 = 0x5B
|
|
204
|
+
KEY_NUMPAD4 = 0x5C
|
|
205
|
+
KEY_NUMPAD5 = 0x5D
|
|
206
|
+
KEY_NUMPAD6 = 0x5E
|
|
207
|
+
KEY_NUMPAD7 = 0x5F
|
|
208
|
+
KEY_NUMPAD8 = 0x60
|
|
209
|
+
KEY_NUMPAD9 = 0x61
|
|
210
|
+
KEY_NUMPAD0 = 0x62
|
|
211
|
+
KEY_NUMPAD_DECIMAL = 0x63
|
|
212
|
+
KEY_INTL_BACKSLASH = 0x64
|
|
213
|
+
KEY_CTX = 0x65
|
|
214
|
+
KEY_POWER = 0x66
|
|
215
|
+
KEY_NUMPAD_EQUAL = 0x67
|
|
216
|
+
KEY_F13 = 0x68
|
|
217
|
+
KEY_F14 = 0x69
|
|
218
|
+
KEY_F15 = 0x6A
|
|
219
|
+
KEY_F16 = 0x6B
|
|
220
|
+
KEY_F17 = 0x6C
|
|
221
|
+
KEY_F18 = 0x6D
|
|
222
|
+
KEY_F19 = 0x6E
|
|
223
|
+
KEY_F20 = 0x6F
|
|
224
|
+
KEY_F21 = 0x70
|
|
225
|
+
KEY_F22 = 0x71
|
|
226
|
+
KEY_F23 = 0x72
|
|
227
|
+
KEY_F24 = 0x73
|
|
228
|
+
KEY_OPEN = 0x74
|
|
229
|
+
KEY_HELP = 0x75
|
|
230
|
+
KEY_SELECT = 0x77
|
|
231
|
+
KEY_AGAIN = 0x79
|
|
232
|
+
KEY_UNDO = 0x7A
|
|
233
|
+
KEY_CUT = 0x7B
|
|
234
|
+
KEY_COPY = 0x7C
|
|
235
|
+
KEY_PASTE = 0x7D
|
|
236
|
+
KEY_FIND = 0x7E
|
|
237
|
+
KEY_VOLUME_MUTE = 0x7F
|
|
238
|
+
KEY_VOLUME_UP = 0x80
|
|
239
|
+
KEY_VOLUME_DOWN = 0x81
|
|
240
|
+
KEY_NUMPAD_COMMA = 0x85
|
|
241
|
+
KEY_INTL_RO = 0x87
|
|
242
|
+
KEY_KANA_MODE = 0x88
|
|
243
|
+
KEY_INTL_YEN = 0x89
|
|
244
|
+
KEY_CONVERT = 0x8A
|
|
245
|
+
KEY_NON_CONVERT = 0x8B
|
|
246
|
+
KEY_LANG1 = 0x90
|
|
247
|
+
KEY_LANG2 = 0x91
|
|
248
|
+
KEY_LANG3 = 0x92
|
|
249
|
+
KEY_LANG4 = 0x93
|
|
250
|
+
KEY_LCTRL = 0xE0
|
|
251
|
+
KEY_LSHIFT = 0xE1
|
|
252
|
+
KEY_LALT = 0xE2
|
|
253
|
+
KEY_LMETA = 0xE3
|
|
254
|
+
KEY_RCTRL = 0xE4
|
|
255
|
+
KEY_RSHIFT = 0xE5
|
|
256
|
+
KEY_RALT = 0xE6
|
|
257
|
+
KEY_RMETA = 0xE7
|
|
258
|
+
KEY_OEM_1 = 0x403
|
|
259
|
+
KEY_OEM_2 = 0x404
|
|
260
|
+
KEY_OEM_3 = 0x405
|
|
261
|
+
KEY_FN = 0x409
|
analogsense/layouts.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from .keymaps import *
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def layout_rows(layout): return layout[0]
|
|
5
|
+
def layout_cols(layout): return layout[1]
|
|
6
|
+
def layout_size(layout): return layout[0] * layout[1]
|
|
7
|
+
def layout_key(layout, i): return layout[2 + i]
|
|
8
|
+
def layout_index_to_row(layout, i): return i // layout_cols(layout)
|
|
9
|
+
def layout_index_to_col(layout, i): return i % layout_cols(layout)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
LAYOUT_KEYCHRON_Q1_HE = (6, 15,
|
|
13
|
+
KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_DEL, KEY_NONE,
|
|
14
|
+
KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_PAGE_UP,
|
|
15
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_PAGE_DOWN,
|
|
16
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_ENTER, KEY_HOME, KEY_NONE,
|
|
17
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_NONE, KEY_SLASH, KEY_RSHIFT, KEY_ARROW_UP,
|
|
18
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RMETA, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
LAYOUT_KEYCHRON_Q3_HE = (6, 16,
|
|
22
|
+
KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_OEM_1, KEY_OEM_2,
|
|
23
|
+
KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_INSERT, KEY_HOME,
|
|
24
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_DEL, KEY_END,
|
|
25
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_NONE, KEY_ENTER, KEY_PAGE_UP, KEY_PAGE_DOWN,
|
|
26
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_NONE, KEY_SLASH, KEY_RSHIFT, KEY_NONE, KEY_ARROW_UP,
|
|
27
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RALT, KEY_RMETA, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
LAYOUT_KEYCHRON_Q5_HE = (6, 19,
|
|
31
|
+
KEY_ESCAPE, KEY_NONE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_DEL, KEY_OEM_1, KEY_OEM_2, KEY_OEM_3, KEY_NONE,
|
|
32
|
+
KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_PAGE_UP, KEY_NUM_LOCK, KEY_NUMPAD_DIVIDE, KEY_NUMPAD_MULTIPLY, KEY_NUMPAD_SUBTRACT,
|
|
33
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_PAGE_DOWN, KEY_NUMPAD7, KEY_NUMPAD8, KEY_NUMPAD9, KEY_NUMPAD_ADD,
|
|
34
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_ENTER, KEY_HOME, KEY_NONE, KEY_NUMPAD4, KEY_NUMPAD5, KEY_NUMPAD6, KEY_NONE,
|
|
35
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_NONE, KEY_SLASH, KEY_RSHIFT, KEY_ARROW_UP, KEY_NUMPAD1, KEY_NUMPAD2, KEY_NUMPAD3, KEY_NUMPAD_ENTER,
|
|
36
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RMETA, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT, KEY_NONE, KEY_NUMPAD0, KEY_NUMPAD_DECIMAL, KEY_NONE,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
LAYOUT_KEYCHRON_K2_HE = (6, 16,
|
|
40
|
+
KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_PRINT_SCREEN, KEY_DEL, KEY_OEM_2,
|
|
41
|
+
KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_PAGE_UP, KEY_NONE,
|
|
42
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_PAGE_DOWN, KEY_NONE,
|
|
43
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_ENTER, KEY_HOME, KEY_NONE, KEY_NONE,
|
|
44
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_RSHIFT, KEY_ARROW_UP, KEY_END, KEY_NONE,
|
|
45
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RALT, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT, KEY_NONE,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
LAYOUT_LEMOKEY_P1_HE = (6, 15,
|
|
49
|
+
KEY_ESCAPE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_DEL, KEY_NONE,
|
|
50
|
+
KEY_BACKQUOTE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_HOME,
|
|
51
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_PAGE_UP,
|
|
52
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_ENTER, KEY_PAGE_DOWN, KEY_NONE,
|
|
53
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_NONE, KEY_SLASH, KEY_RSHIFT, KEY_ARROW_UP,
|
|
54
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RMETA, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
LAYOUT_MADLIONS_MAD60HE = [
|
|
58
|
+
KEY_ESCAPE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE,
|
|
59
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH,
|
|
60
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_NONE, KEY_ENTER,
|
|
61
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_NONE, KEY_RSHIFT,
|
|
62
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RMETA, KEY_RALT, KEY_CTX, KEY_RCTRL, KEY_FN,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
LAYOUT_MADLIONS_MAD68HE = [
|
|
66
|
+
KEY_ESCAPE, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_INSERT,
|
|
67
|
+
KEY_TAB, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P, KEY_BRACKET_LEFT, KEY_BRACKET_RIGHT, KEY_BACKSLASH, KEY_DEL,
|
|
68
|
+
KEY_CAPS_LOCK, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_SEMICOLON, KEY_QUOTE, KEY_NONE, KEY_ENTER, KEY_PAGE_UP,
|
|
69
|
+
KEY_LSHIFT, KEY_NONE, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, KEY_PERIOD, KEY_SLASH, KEY_RSHIFT, KEY_ARROW_UP, KEY_PAGE_DOWN,
|
|
70
|
+
KEY_LCTRL, KEY_LMETA, KEY_LALT, KEY_NONE, KEY_NONE, KEY_NONE, KEY_SPACE, KEY_NONE, KEY_NONE, KEY_RALT, KEY_FN, KEY_RCTRL, KEY_ARROW_LEFT, KEY_ARROW_DOWN, KEY_ARROW_RIGHT,
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
_DRUNKDEER_MAP = {
|
|
74
|
+
(0*21)+0: 0x29, (0*21)+2: 0x3A, (0*21)+3: 0x3B, (0*21)+4: 0x3C,
|
|
75
|
+
(0*21)+5: 0x3D, (0*21)+6: 0x3E, (0*21)+7: 0x3F, (0*21)+8: 0x40,
|
|
76
|
+
(0*21)+9: 0x41, (0*21)+10: 0x42, (0*21)+11: 0x43, (0*21)+12: 0x44,
|
|
77
|
+
(0*21)+13: 0x45, (0*21)+14: 0x4C,
|
|
78
|
+
(1*21)+0: 0x35, (1*21)+1: 0x1E, (1*21)+2: 0x1F, (1*21)+3: 0x20,
|
|
79
|
+
(1*21)+4: 0x21, (1*21)+5: 0x22, (1*21)+6: 0x23, (1*21)+7: 0x24,
|
|
80
|
+
(1*21)+8: 0x25, (1*21)+9: 0x26, (1*21)+10: 0x27, (1*21)+11: 0x2D,
|
|
81
|
+
(1*21)+12: 0x2E, (1*21)+13: 0x2A, (1*21)+15: 0x4A,
|
|
82
|
+
(2*21)+0: 0x2B, (2*21)+1: 0x14, (2*21)+2: 0x1A, (2*21)+3: 0x08,
|
|
83
|
+
(2*21)+4: 0x15, (2*21)+5: 0x17, (2*21)+6: 0x1C, (2*21)+7: 0x18,
|
|
84
|
+
(2*21)+8: 0x0C, (2*21)+9: 0x12, (2*21)+10: 0x13, (2*21)+11: 0x2F,
|
|
85
|
+
(2*21)+12: 0x30, (2*21)+13: 0x31, (2*21)+15: 0x4B,
|
|
86
|
+
(3*21)+0: 0x39, (3*21)+1: 0x04, (3*21)+2: 0x16, (3*21)+3: 0x07,
|
|
87
|
+
(3*21)+4: 0x09, (3*21)+5: 0x0A, (3*21)+6: 0x0B, (3*21)+7: 0x0D,
|
|
88
|
+
(3*21)+8: 0x0E, (3*21)+9: 0x0F, (3*21)+10: 0x33, (3*21)+11: 0x34,
|
|
89
|
+
(3*21)+13: 0x28, (3*21)+15: 0x4E,
|
|
90
|
+
(4*21)+0: 0xE1, (4*21)+2: 0x1D, (4*21)+3: 0x1B, (4*21)+4: 0x06,
|
|
91
|
+
(4*21)+5: 0x19, (4*21)+6: 0x05, (4*21)+7: 0x11, (4*21)+8: 0x10,
|
|
92
|
+
(4*21)+9: 0x36, (4*21)+10: 0x37, (4*21)+11: 0x38, (4*21)+13: 0xE5,
|
|
93
|
+
(4*21)+14: 0x52, (4*21)+15: 0x4D,
|
|
94
|
+
(5*21)+0: 0xE0, (5*21)+1: 0xE3, (5*21)+2: 0xE2, (5*21)+6: 0x2C,
|
|
95
|
+
(5*21)+10: 0xE6, (5*21)+11: 0x409,(5*21)+12: 0x65,
|
|
96
|
+
(5*21)+14: 0x50, (5*21)+15: 0x51, (5*21)+16: 0x4F,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def drunkdeer_index_to_hid_scancode(i):
|
|
100
|
+
import warnings
|
|
101
|
+
sc = _DRUNKDEER_MAP.get(i, 0)
|
|
102
|
+
if sc == 0:
|
|
103
|
+
warnings.warn(f"Failed to map DrunkDeer key to HID scancode: {i}")
|
|
104
|
+
return sc
|
analogsense/providers.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from .keymaps import razer_to_wooting, nuphy_to_wooting, bytech_to_wooting, KEY_NONE
|
|
8
|
+
from .layouts import (
|
|
9
|
+
LAYOUT_KEYCHRON_Q1_HE, LAYOUT_KEYCHRON_Q3_HE, LAYOUT_KEYCHRON_Q5_HE,
|
|
10
|
+
LAYOUT_KEYCHRON_K2_HE, LAYOUT_LEMOKEY_P1_HE,
|
|
11
|
+
LAYOUT_MADLIONS_MAD60HE, LAYOUT_MADLIONS_MAD68HE,
|
|
12
|
+
layout_key, layout_size, layout_index_to_row, layout_index_to_col,
|
|
13
|
+
drunkdeer_index_to_hid_scancode,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
ActiveKey = dict
|
|
17
|
+
Handler = Callable[[list[ActiveKey]], None]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AsProvider:
|
|
21
|
+
FILTERS = []
|
|
22
|
+
|
|
23
|
+
def __init__(self, dev):
|
|
24
|
+
self.dev = dev
|
|
25
|
+
self._buffer: dict[int, float] = {}
|
|
26
|
+
self._thread: threading.Thread | None = None
|
|
27
|
+
self._running = False
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def product_name(self):
|
|
31
|
+
return getattr(self.dev, "product_string", "Unknown")
|
|
32
|
+
|
|
33
|
+
def forget(self):
|
|
34
|
+
self.stop_listening()
|
|
35
|
+
try:
|
|
36
|
+
self.dev.close()
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
def _buffer_to_active_keys(self):
|
|
41
|
+
return [{"scancode": sc, "value": v} for sc, v in self._buffer.items()]
|
|
42
|
+
|
|
43
|
+
def start_listening(self, handler: Handler):
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
def stop_listening(self):
|
|
47
|
+
self._running = False
|
|
48
|
+
if self._thread:
|
|
49
|
+
self._thread.join(timeout=2)
|
|
50
|
+
self._thread = None
|
|
51
|
+
|
|
52
|
+
def _read_loop(self, handler: Handler):
|
|
53
|
+
while self._running:
|
|
54
|
+
try:
|
|
55
|
+
data = self.dev.read(64, timeout_ms=100)
|
|
56
|
+
if data:
|
|
57
|
+
self._handle_report(bytes(data), handler)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
warnings.warn(f"HID read error: {e}")
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
63
|
+
raise NotImplementedError
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class AsProviderWootingV1(AsProvider):
|
|
67
|
+
FILTERS = [
|
|
68
|
+
{"usage_page": 0xFF54, "vendor_id": 0x31E3},
|
|
69
|
+
{"usage_page": 0xFF54, "vendor_id": 0x03EB, "product_id": 0xFF01},
|
|
70
|
+
{"usage_page": 0xFF54, "vendor_id": 0x03EB, "product_id": 0xFF02},
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def start_listening(self, handler: Handler):
|
|
74
|
+
self._running = True
|
|
75
|
+
self._prev_scancodes: set[int] = set()
|
|
76
|
+
self._thread = threading.Thread(target=self._read_loop, args=(handler,), daemon=True)
|
|
77
|
+
self._thread.start()
|
|
78
|
+
|
|
79
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
80
|
+
active_keys = []
|
|
81
|
+
current_scancodes = set()
|
|
82
|
+
i = 0
|
|
83
|
+
while i < len(data):
|
|
84
|
+
if i + 2 > len(data): break
|
|
85
|
+
scancode = (data[i] << 8) | data[i + 1]
|
|
86
|
+
i += 2
|
|
87
|
+
if scancode == 0: break
|
|
88
|
+
if i >= len(data): break
|
|
89
|
+
value = data[i]
|
|
90
|
+
i += 1
|
|
91
|
+
active_keys.append({"scancode": scancode, "value": value / 255})
|
|
92
|
+
current_scancodes.add(scancode)
|
|
93
|
+
for released in self._prev_scancodes - current_scancodes:
|
|
94
|
+
active_keys.append({"scancode": released, "value": 0.0})
|
|
95
|
+
self._prev_scancodes = current_scancodes
|
|
96
|
+
handler(active_keys)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class AsProviderWootingV2(AsProvider):
|
|
100
|
+
FILTERS = [
|
|
101
|
+
{"usage_page": 0xFF53, "vendor_id": 0x31E3},
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
def start_listening(self, handler: Handler):
|
|
105
|
+
self._running = True
|
|
106
|
+
self._prev_scancodes: set[int] = set()
|
|
107
|
+
self._thread = threading.Thread(target=self._read_loop, args=(handler,), daemon=True)
|
|
108
|
+
self._thread.start()
|
|
109
|
+
|
|
110
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
111
|
+
active_keys = []
|
|
112
|
+
current_scancodes = set()
|
|
113
|
+
i = 0
|
|
114
|
+
while i + 4 <= len(data):
|
|
115
|
+
keycode = data[i + 1]
|
|
116
|
+
packed = data[i + 2]
|
|
117
|
+
value_hi = data[i + 3]
|
|
118
|
+
key_ns = (packed >> 2) & 0xF
|
|
119
|
+
value_lo = (packed >> 6) & 0x3
|
|
120
|
+
scancode = (key_ns << 8) | keycode
|
|
121
|
+
value = (value_hi << 2) | value_lo
|
|
122
|
+
i += 4
|
|
123
|
+
if scancode == 0: break
|
|
124
|
+
if value == 0: continue
|
|
125
|
+
active_keys.append({"scancode": scancode, "value": value / 1023})
|
|
126
|
+
current_scancodes.add(scancode)
|
|
127
|
+
for released in self._prev_scancodes - current_scancodes:
|
|
128
|
+
active_keys.append({"scancode": released, "value": 0.0})
|
|
129
|
+
self._prev_scancodes = current_scancodes
|
|
130
|
+
handler(active_keys)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class AsProviderRazerHuntsman(AsProvider):
|
|
134
|
+
FILTERS = [
|
|
135
|
+
{"vendor_id": 0x1532, "product_id": 0x0266},
|
|
136
|
+
{"vendor_id": 0x1532, "product_id": 0x0282},
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
def start_listening(self, handler: Handler):
|
|
140
|
+
self._running = True
|
|
141
|
+
self._prev_scancodes: set[int] = set()
|
|
142
|
+
self._thread = threading.Thread(target=self._read_loop, args=(handler,), daemon=True)
|
|
143
|
+
self._thread.start()
|
|
144
|
+
|
|
145
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
146
|
+
if data[0] != 7: return
|
|
147
|
+
active_keys = []
|
|
148
|
+
current_scancodes = set()
|
|
149
|
+
i = 1
|
|
150
|
+
while i < len(data):
|
|
151
|
+
scancode = data[i]; i += 1
|
|
152
|
+
if scancode == 0: break
|
|
153
|
+
value = data[i]; i += 1
|
|
154
|
+
hid_sc = _razer_to_hid(scancode)
|
|
155
|
+
if hid_sc:
|
|
156
|
+
active_keys.append({"scancode": hid_sc, "value": value / 255})
|
|
157
|
+
current_scancodes.add(hid_sc)
|
|
158
|
+
for released in self._prev_scancodes - current_scancodes:
|
|
159
|
+
active_keys.append({"scancode": released, "value": 0.0})
|
|
160
|
+
self._prev_scancodes = current_scancodes
|
|
161
|
+
handler(active_keys)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class AsProviderRazerHuntsmanV3(AsProvider):
|
|
165
|
+
FILTERS = [
|
|
166
|
+
{"vendor_id": 0x1532, "product_id": 0x02A6},
|
|
167
|
+
{"vendor_id": 0x1532, "product_id": 0x02A7},
|
|
168
|
+
{"vendor_id": 0x1532, "product_id": 0x02B0},
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
def start_listening(self, handler: Handler):
|
|
172
|
+
self._running = True
|
|
173
|
+
self._prev_scancodes: set[int] = set()
|
|
174
|
+
self._thread = threading.Thread(target=self._read_loop, args=(handler,), daemon=True)
|
|
175
|
+
self._thread.start()
|
|
176
|
+
|
|
177
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
178
|
+
if data[0] != 11: return
|
|
179
|
+
active_keys = []
|
|
180
|
+
current_scancodes = set()
|
|
181
|
+
i = 1
|
|
182
|
+
while i + 2 < len(data):
|
|
183
|
+
scancode = data[i]; i += 1
|
|
184
|
+
if scancode == 0: break
|
|
185
|
+
value = data[i]; i += 1
|
|
186
|
+
_unused = data[i]; i += 1
|
|
187
|
+
hid_sc = _razer_to_hid(scancode)
|
|
188
|
+
if hid_sc:
|
|
189
|
+
active_keys.append({"scancode": hid_sc, "value": value / 255})
|
|
190
|
+
current_scancodes.add(hid_sc)
|
|
191
|
+
for released in self._prev_scancodes - current_scancodes:
|
|
192
|
+
active_keys.append({"scancode": released, "value": 0.0})
|
|
193
|
+
self._prev_scancodes = current_scancodes
|
|
194
|
+
handler(active_keys)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class AsProviderNuphy(AsProvider):
|
|
198
|
+
FILTERS = [
|
|
199
|
+
{"vendor_id": 0x19F5},
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
def start_listening(self, handler: Handler):
|
|
203
|
+
self._running = True
|
|
204
|
+
self._thread = threading.Thread(target=self._read_loop, args=(handler,), daemon=True)
|
|
205
|
+
self._thread.start()
|
|
206
|
+
|
|
207
|
+
def _handle_report(self, data: bytes, handler: Handler):
|
|
208
|
+
if data[0] != 0xA0: return
|
|
209
|
+
raw_sc = (data[2] << 8) | data[3]
|
|
210
|
+
hid_sc = _nuphy_to_hid(raw_sc)
|
|
211
|
+
if hid_sc == 0: return
|
|
212
|
+
if data[7] == 0:
|
|
213
|
+
self._buffer.pop(hid_sc, None)
|
|
214
|
+
else:
|
|
215
|
+
self._buffer[hid_sc] = data[7] / 200
|
|
216
|
+
handler(self._buffer_to_active_keys())
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class AsProviderDrunkdeer(AsProvider):
|
|
220
|
+
FILTERS = [
|
|
221
|
+
{"usage_page": 0xFF00, "vendor_id": 0x352D},
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
_POLL_PAYLOAD = bytes([
|
|
225
|
+
0xB6, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00,
|
|
226
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
227
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
228
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
229
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
230
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
231
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
232
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
233
|
+
])
|
|
234
|
+
|
|
235
|
+
def start_listening(self, handler: Handler):
|
|
236
|
+
self._running = True
|
|
237
|
+
self._active_keys = []
|
|
238
|
+
self._thread = threading.Thread(target=self._poll_loop, args=(handler,), daemon=True)
|
|
239
|
+
self._thread.start()
|
|
240
|
+
|
|
241
|
+
def _poll_loop(self, handler: Handler):
|
|
242
|
+
self.dev.write(b"\x04" + self._POLL_PAYLOAD)
|
|
243
|
+
while self._running:
|
|
244
|
+
try:
|
|
245
|
+
data = self.dev.read(64, timeout_ms=100)
|
|
246
|
+
if not data: continue
|
|
247
|
+
data = bytes(data)
|
|
248
|
+
n = data[3]
|
|
249
|
+
if n == 0:
|
|
250
|
+
self._active_keys = []
|
|
251
|
+
for i in range(4, len(data)):
|
|
252
|
+
value = data[i]
|
|
253
|
+
if value != 0:
|
|
254
|
+
sc = drunkdeer_index_to_hid_scancode(n * (64 - 5) + (i - 4))
|
|
255
|
+
self._active_keys.append({"scancode": sc, "value": value / 40})
|
|
256
|
+
if n == 2:
|
|
257
|
+
handler(list(self._active_keys))
|
|
258
|
+
self._active_keys = []
|
|
259
|
+
except Exception as e:
|
|
260
|
+
warnings.warn(f"DrunkDeer read error: {e}")
|
|
261
|
+
break
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class AsProviderKeychron(AsProvider):
|
|
265
|
+
FILTERS = [
|
|
266
|
+
{"vendor_id": 0x3434, "product_id": 0x0B10},
|
|
267
|
+
{"vendor_id": 0x3434, "product_id": 0x0B11},
|
|
268
|
+
{"vendor_id": 0x3434, "product_id": 0x0B12},
|
|
269
|
+
{"vendor_id": 0x3434, "product_id": 0x0B30},
|
|
270
|
+
{"vendor_id": 0x3434, "product_id": 0x0B50},
|
|
271
|
+
{"vendor_id": 0x3434, "product_id": 0x0E20},
|
|
272
|
+
{"vendor_id": 0x3434, "product_id": 0x0E21},
|
|
273
|
+
{"vendor_id": 0x3434, "product_id": 0x0E22},
|
|
274
|
+
{"vendor_id": 0x362D, "product_id": 0x0610},
|
|
275
|
+
]
|
|
276
|
+
|
|
277
|
+
_INIT_PAYLOAD = bytes([
|
|
278
|
+
0xA9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
279
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
280
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
281
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
282
|
+
])
|
|
283
|
+
|
|
284
|
+
def _pick_layout(self):
|
|
285
|
+
pid = self.dev.product_id
|
|
286
|
+
if pid in (0x0B10, 0x0B11, 0x0B12): return LAYOUT_KEYCHRON_Q1_HE
|
|
287
|
+
if pid == 0x0B30: return LAYOUT_KEYCHRON_Q3_HE
|
|
288
|
+
if pid == 0x0B50: return LAYOUT_KEYCHRON_Q5_HE
|
|
289
|
+
if pid == 0x0610: return LAYOUT_LEMOKEY_P1_HE
|
|
290
|
+
return LAYOUT_KEYCHRON_K2_HE
|
|
291
|
+
|
|
292
|
+
def start_listening(self, handler: Handler):
|
|
293
|
+
self._running = True
|
|
294
|
+
self.layout = self._pick_layout()
|
|
295
|
+
self._thread = threading.Thread(target=self._keychron_loop, args=(handler,), daemon=True)
|
|
296
|
+
self._thread.start()
|
|
297
|
+
|
|
298
|
+
def _build_single_key_request(self, index):
|
|
299
|
+
key = layout_key(self.layout, index)
|
|
300
|
+
if key == KEY_NONE: return None
|
|
301
|
+
row = layout_index_to_row(self.layout, index)
|
|
302
|
+
col = layout_index_to_col(self.layout, index)
|
|
303
|
+
buf = bytearray(32)
|
|
304
|
+
buf[0] = 0xA9; buf[1] = 0x30; buf[2] = row; buf[3] = col
|
|
305
|
+
return bytes(buf)
|
|
306
|
+
|
|
307
|
+
def _build_all_keys_request(self):
|
|
308
|
+
buf = bytearray(32)
|
|
309
|
+
buf[0] = 0xA9; buf[1] = 0x31
|
|
310
|
+
return bytes(buf)
|
|
311
|
+
|
|
312
|
+
def _keychron_loop(self, handler: Handler):
|
|
313
|
+
self.dev.write(b"\x00" + self._INIT_PAYLOAD)
|
|
314
|
+
data = bytes(self.dev.read(64, timeout_ms=500) or [])
|
|
315
|
+
if not data: return
|
|
316
|
+
am_version = data[2] if len(data) > 2 else 0
|
|
317
|
+
has_full_report = len(data) > 31 and data[31] == 0x45
|
|
318
|
+
self._buffer = {}
|
|
319
|
+
if has_full_report:
|
|
320
|
+
self._run_full_mode(handler)
|
|
321
|
+
else:
|
|
322
|
+
self._run_single_mode(handler, am_version)
|
|
323
|
+
|
|
324
|
+
def _run_full_mode(self, handler: Handler):
|
|
325
|
+
chunk_index = 0
|
|
326
|
+
self.dev.write(b"\x00" + self._build_all_keys_request())
|
|
327
|
+
while self._running:
|
|
328
|
+
data = bytes(self.dev.read(64, timeout_ms=200) or [])
|
|
329
|
+
if not data: continue
|
|
330
|
+
for i in range(30):
|
|
331
|
+
li = chunk_index * 30 + i
|
|
332
|
+
if li < layout_size(self.layout):
|
|
333
|
+
key = layout_key(self.layout, li)
|
|
334
|
+
if key != KEY_NONE:
|
|
335
|
+
travel = data[2 + i] if 2 + i < len(data) else 0
|
|
336
|
+
if travel >= 5:
|
|
337
|
+
self._buffer[key] = min(travel / 235, 1.0)
|
|
338
|
+
else:
|
|
339
|
+
self._buffer.pop(key, None)
|
|
340
|
+
chunk_index += 1
|
|
341
|
+
if chunk_index == 4:
|
|
342
|
+
handler(self._buffer_to_active_keys())
|
|
343
|
+
chunk_index = 0
|
|
344
|
+
self.dev.write(b"\x00" + self._build_all_keys_request())
|
|
345
|
+
|
|
346
|
+
def _run_single_mode(self, handler: Handler, am_version: int):
|
|
347
|
+
value_offset = 6 if am_version >= 4 else 3
|
|
348
|
+
index = 0
|
|
349
|
+
while True:
|
|
350
|
+
req = self._build_single_key_request(index)
|
|
351
|
+
if req:
|
|
352
|
+
self.dev.write(b"\x00" + req)
|
|
353
|
+
break
|
|
354
|
+
index = (index + 1) % layout_size(self.layout)
|
|
355
|
+
while self._running:
|
|
356
|
+
data = bytes(self.dev.read(64, timeout_ms=200) or [])
|
|
357
|
+
if not data: continue
|
|
358
|
+
key = layout_key(self.layout, index)
|
|
359
|
+
travel = data[value_offset] if value_offset < len(data) else 0
|
|
360
|
+
if travel >= 5:
|
|
361
|
+
self._buffer[key] = min(travel / 235, 1.0)
|
|
362
|
+
else:
|
|
363
|
+
self._buffer.pop(key, None)
|
|
364
|
+
handler(self._buffer_to_active_keys())
|
|
365
|
+
while True:
|
|
366
|
+
index = (index + 1) % layout_size(self.layout)
|
|
367
|
+
req = self._build_single_key_request(index)
|
|
368
|
+
if req:
|
|
369
|
+
self.dev.write(b"\x00" + req)
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class AsProviderMadlions(AsProvider):
|
|
374
|
+
FILTERS = [
|
|
375
|
+
{"vendor_id": 0x373B, "product_id": 0x1053},
|
|
376
|
+
{"vendor_id": 0x373B, "product_id": 0x1055},
|
|
377
|
+
{"vendor_id": 0x373B, "product_id": 0x1056},
|
|
378
|
+
{"vendor_id": 0x373B, "product_id": 0x105D},
|
|
379
|
+
{"vendor_id": 0x373B, "product_id": 0x1058},
|
|
380
|
+
{"vendor_id": 0x373B, "product_id": 0x1059},
|
|
381
|
+
{"vendor_id": 0x373B, "product_id": 0x105A},
|
|
382
|
+
{"vendor_id": 0x373B, "product_id": 0x105C},
|
|
383
|
+
{"vendor_id": 0x373B, "product_id": 0x10A7},
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
_INIT_PAYLOAD = bytes([
|
|
387
|
+
0x02, 0x96, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x04,
|
|
388
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
389
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
390
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
391
|
+
])
|
|
392
|
+
|
|
393
|
+
_MAD60HE_PIDS = {0x1053, 0x1055, 0x1056, 0x105D}
|
|
394
|
+
|
|
395
|
+
def start_listening(self, handler: Handler):
|
|
396
|
+
pid = self.dev.product_id
|
|
397
|
+
self.layout = LAYOUT_MADLIONS_MAD60HE if pid in self._MAD60HE_PIDS else LAYOUT_MADLIONS_MAD68HE
|
|
398
|
+
self._running = True
|
|
399
|
+
self._thread = threading.Thread(target=self._madlions_loop, args=(handler,), daemon=True)
|
|
400
|
+
self._thread.start()
|
|
401
|
+
|
|
402
|
+
def _madlions_loop(self, handler: Handler):
|
|
403
|
+
payload = bytearray(self._INIT_PAYLOAD)
|
|
404
|
+
self.dev.write(b"\x00" + bytes(payload))
|
|
405
|
+
offset = 0
|
|
406
|
+
self._buffer = {}
|
|
407
|
+
while self._running:
|
|
408
|
+
data = bytes(self.dev.read(64, timeout_ms=200) or [])
|
|
409
|
+
if not data: continue
|
|
410
|
+
for i in range(4):
|
|
411
|
+
idx = offset + i
|
|
412
|
+
if idx < len(self.layout):
|
|
413
|
+
key = self.layout[idx]
|
|
414
|
+
pos = 7 + i * 5 + 3
|
|
415
|
+
travel = (data[pos] << 8) | data[pos + 1] if pos + 1 < len(data) else 0
|
|
416
|
+
if travel == 0:
|
|
417
|
+
self._buffer.pop(key, None)
|
|
418
|
+
else:
|
|
419
|
+
self._buffer[key] = travel / 350
|
|
420
|
+
handler(self._buffer_to_active_keys())
|
|
421
|
+
offset = (offset + 4) % len(self.layout)
|
|
422
|
+
payload[6] = offset
|
|
423
|
+
self.dev.write(b"\x00" + bytes(payload))
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class AsProviderBytech(AsProvider):
|
|
427
|
+
FILTERS = [
|
|
428
|
+
{"vendor_id": 0x372E, "product_id": 0x105B},
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
def _build_payload(self, cmd, sub):
|
|
432
|
+
buf = bytearray(63)
|
|
433
|
+
buf[0] = cmd
|
|
434
|
+
buf[1] = sub
|
|
435
|
+
total = 9 + sum(buf[:-1])
|
|
436
|
+
buf[-1] = 255 - (total % 256)
|
|
437
|
+
return bytes(buf)
|
|
438
|
+
|
|
439
|
+
def start_listening(self, handler: Handler):
|
|
440
|
+
self._running = True
|
|
441
|
+
self._thread = threading.Thread(target=self._bytech_loop, args=(handler,), daemon=True)
|
|
442
|
+
self._thread.start()
|
|
443
|
+
|
|
444
|
+
def _bytech_loop(self, handler: Handler):
|
|
445
|
+
payload = self._build_payload(0x97, 0x00)
|
|
446
|
+
self._buffer = {}
|
|
447
|
+
self.dev.write(b"\x09" + payload)
|
|
448
|
+
last_poll = time.monotonic()
|
|
449
|
+
while self._running:
|
|
450
|
+
now = time.monotonic()
|
|
451
|
+
if now - last_poll >= 1.0:
|
|
452
|
+
self.dev.write(b"\x09" + payload)
|
|
453
|
+
last_poll = now
|
|
454
|
+
data = bytes(self.dev.read(64, timeout_ms=100) or [])
|
|
455
|
+
if not data: continue
|
|
456
|
+
if data[0] == 0x97 and data[1] == 0x01:
|
|
457
|
+
self.dev.write(b"\x09" + payload)
|
|
458
|
+
last_poll = time.monotonic()
|
|
459
|
+
self._buffer = {}
|
|
460
|
+
count = data[5]
|
|
461
|
+
for i in range(0, count, 4):
|
|
462
|
+
base = 6 + i
|
|
463
|
+
if base + 3 >= len(data): break
|
|
464
|
+
pos = (data[base] << 8) | data[base + 1]
|
|
465
|
+
distance = (data[base + 2] << 8) | data[base + 3]
|
|
466
|
+
sc = _bytech_to_hid(pos)
|
|
467
|
+
if sc and distance > 10:
|
|
468
|
+
self._buffer[sc] = min(distance / 355, 1.0)
|
|
469
|
+
handler(self._buffer_to_active_keys())
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _razer_to_hid(sc):
|
|
473
|
+
result = razer_to_wooting.get(sc, 0)
|
|
474
|
+
if result == 0:
|
|
475
|
+
warnings.warn(f"Failed to map Razer scancode to HID scancode: {sc:#x}")
|
|
476
|
+
return result
|
|
477
|
+
|
|
478
|
+
def _nuphy_to_hid(sc):
|
|
479
|
+
result = nuphy_to_wooting.get(sc, 0)
|
|
480
|
+
if result == 0:
|
|
481
|
+
warnings.warn(f"Failed to map NuPhy scancode to HID scancode: {sc:#x}")
|
|
482
|
+
return result
|
|
483
|
+
|
|
484
|
+
def _bytech_to_hid(sc):
|
|
485
|
+
result = bytech_to_wooting.get(sc, 0)
|
|
486
|
+
if result == 0:
|
|
487
|
+
warnings.warn(f"Failed to map Bytech scancode to HID scancode: {sc:#x}")
|
|
488
|
+
return result
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
ALL_PROVIDERS = [
|
|
492
|
+
AsProviderWootingV1,
|
|
493
|
+
AsProviderWootingV2,
|
|
494
|
+
AsProviderRazerHuntsman,
|
|
495
|
+
AsProviderRazerHuntsmanV3,
|
|
496
|
+
AsProviderNuphy,
|
|
497
|
+
AsProviderDrunkdeer,
|
|
498
|
+
AsProviderKeychron,
|
|
499
|
+
AsProviderMadlions,
|
|
500
|
+
AsProviderBytech,
|
|
501
|
+
]
|
analogsense/setup.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
analogsense/__init__.py,sha256=G-tS3oKD0rIxFwQONJ_kweFrBOhqJwtWiT58VLSpcU0,107
|
|
2
|
+
analogsense/analogsense.py,sha256=CqCxvbAS3bJn1pC_KBgZeuIFcghip2fm19POxq1J1Ns,3703
|
|
3
|
+
analogsense/keymaps.py,sha256=m9s4KvTZ1wR2V2mzV203jzaZoi2BwNWPsxfJCjBHm1Q,12444
|
|
4
|
+
analogsense/layouts.py,sha256=DxtJppA-T3Nk3upNH6t_q8zi-xEHlKEjyrKyaKFdD6Q,10576
|
|
5
|
+
analogsense/providers.py,sha256=XGvoh2VtljIDv0St7KU_0EYU9f2nPQNPGqi-qpXi9FA,18149
|
|
6
|
+
analogsense/setup.py,sha256=L4I33E3NDrlmVHKqbG9g8B7sN-EoBurxHw0RlJw8Imo,254
|
|
7
|
+
analogsensepy-1.0.0.dist-info/licenses/LICENSE,sha256=fAFqrUI7nATht2AD_lCdHsjdILfCUPPLJwSAHoqIAO4,1101
|
|
8
|
+
analogsensepy-1.0.0.dist-info/METADATA,sha256=UQwWJwPRjJaVtbZa1aUOmf7I9NZM5pi85c0RcnwuTXY,144
|
|
9
|
+
analogsensepy-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
analogsensepy-1.0.0.dist-info/top_level.txt,sha256=3aX3K98ppnKo2OL58wDPPu0N30lTBN-b88aFDXw8lS4,12
|
|
11
|
+
analogsensepy-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Calamity, Inc.
|
|
4
|
+
Copyright (c) 2025 Deana Brcka
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
analogsense
|