makcu 0.2.0__py3-none-any.whl → 0.2.1__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.
- makcu/__init__.py +0 -18
- makcu/__main__.py +12 -28
- makcu/conftest.py +0 -1
- makcu/connection.py +55 -79
- makcu/controller.py +26 -70
- makcu/errors.py +0 -5
- makcu/mouse.py +21 -39
- makcu/test_suite.py +8 -22
- {makcu-0.2.0.dist-info → makcu-0.2.1.dist-info}/METADATA +1 -1
- makcu-0.2.1.dist-info/RECORD +16 -0
- makcu-0.2.0.dist-info/RECORD +0 -16
- {makcu-0.2.0.dist-info → makcu-0.2.1.dist-info}/WHEEL +0 -0
- {makcu-0.2.0.dist-info → makcu-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {makcu-0.2.0.dist-info → makcu-0.2.1.dist-info}/top_level.txt +0 -0
makcu/controller.py
CHANGED
@@ -9,9 +9,8 @@ from .errors import MakcuConnectionError
|
|
9
9
|
from .enums import MouseButton
|
10
10
|
|
11
11
|
class MakcuController:
|
12
|
-
"""Ultra-optimized MakcuController for gaming performance"""
|
13
12
|
|
14
|
-
|
13
|
+
|
15
14
|
_BUTTON_LOCK_MAP = {
|
16
15
|
MouseButton.LEFT: 'lock_left',
|
17
16
|
MouseButton.RIGHT: 'lock_right',
|
@@ -34,93 +33,80 @@ class MakcuController:
|
|
34
33
|
self._executor = ThreadPoolExecutor(max_workers=1)
|
35
34
|
self._connection_callbacks: List[Callable[[bool], None]] = []
|
36
35
|
|
37
|
-
|
36
|
+
|
38
37
|
self._connected = False
|
39
38
|
|
40
|
-
|
39
|
+
|
41
40
|
def connect(self) -> None:
|
42
|
-
"""Connect with state caching"""
|
43
41
|
self.transport.connect()
|
44
42
|
self._connected = True
|
45
43
|
self._notify_connection_change(True)
|
46
44
|
|
47
45
|
def disconnect(self) -> None:
|
48
|
-
"""Disconnect with cleanup"""
|
49
46
|
self.transport.disconnect()
|
50
47
|
self._connected = False
|
51
48
|
self._notify_connection_change(False)
|
52
49
|
self._executor.shutdown(wait=False)
|
53
50
|
|
54
51
|
def is_connected(self) -> bool:
|
55
|
-
"""Fast cached connection check"""
|
56
52
|
return self._connected and self.transport.is_connected()
|
57
53
|
|
58
54
|
def _check_connection(self) -> None:
|
59
|
-
"""Inline connection check for speed"""
|
60
55
|
if not self._connected:
|
61
56
|
raise MakcuConnectionError("Not connected")
|
62
57
|
|
63
58
|
def _notify_connection_change(self, connected: bool) -> None:
|
64
|
-
"""Notify callbacks with error suppression"""
|
65
59
|
for callback in self._connection_callbacks:
|
66
60
|
try:
|
67
61
|
callback(connected)
|
68
62
|
except Exception:
|
69
|
-
pass
|
63
|
+
pass
|
64
|
+
|
70
65
|
|
71
|
-
# Optimized mouse operations
|
72
66
|
def click(self, button: MouseButton) -> None:
|
73
|
-
"""Optimized click - direct command"""
|
74
67
|
if not self._connected:
|
75
68
|
raise MakcuConnectionError("Not connected")
|
76
69
|
self.mouse.press(button)
|
77
70
|
self.mouse.release(button)
|
78
71
|
|
79
72
|
def double_click(self, button: MouseButton) -> None:
|
80
|
-
"""Double click with minimal delay"""
|
81
73
|
if not self._connected:
|
82
74
|
raise MakcuConnectionError("Not connected")
|
83
75
|
self.mouse.press(button)
|
84
76
|
self.mouse.release(button)
|
85
|
-
|
77
|
+
|
86
78
|
time.sleep(0.001)
|
87
79
|
self.mouse.press(button)
|
88
80
|
self.mouse.release(button)
|
89
81
|
|
90
82
|
def move(self, dx: int, dy: int) -> None:
|
91
|
-
"""Direct move"""
|
92
83
|
if not self._connected:
|
93
84
|
raise MakcuConnectionError("Not connected")
|
94
85
|
self.mouse.move(dx, dy)
|
95
86
|
|
96
87
|
def scroll(self, delta: int) -> None:
|
97
|
-
"""Direct scroll"""
|
98
88
|
if not self._connected:
|
99
89
|
raise MakcuConnectionError("Not connected")
|
100
90
|
self.mouse.scroll(delta)
|
101
91
|
|
102
92
|
def press(self, button: MouseButton) -> None:
|
103
|
-
"""Direct press"""
|
104
93
|
if not self._connected:
|
105
94
|
raise MakcuConnectionError("Not connected")
|
106
95
|
self.mouse.press(button)
|
107
96
|
|
108
97
|
def release(self, button: MouseButton) -> None:
|
109
|
-
"""Direct release"""
|
110
98
|
if not self._connected:
|
111
99
|
raise MakcuConnectionError("Not connected")
|
112
100
|
self.mouse.release(button)
|
113
101
|
|
114
|
-
|
102
|
+
|
115
103
|
def move_smooth(self, dx: int, dy: int, segments: int = 10) -> None:
|
116
|
-
"""Smooth movement"""
|
117
104
|
if not self._connected:
|
118
105
|
raise MakcuConnectionError("Not connected")
|
119
106
|
self.mouse.move_smooth(dx, dy, segments)
|
120
107
|
|
121
108
|
def move_bezier(self, dx: int, dy: int, segments: int = 20,
|
122
109
|
ctrl_x: Optional[int] = None, ctrl_y: Optional[int] = None) -> None:
|
123
|
-
"""Bezier curve movement"""
|
124
110
|
if not self._connected:
|
125
111
|
raise MakcuConnectionError("Not connected")
|
126
112
|
if ctrl_x is None:
|
@@ -129,9 +115,8 @@ class MakcuController:
|
|
129
115
|
ctrl_y = dy // 2
|
130
116
|
self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
|
131
117
|
|
132
|
-
|
118
|
+
|
133
119
|
def lock(self, target: Union[MouseButton, str]) -> None:
|
134
|
-
"""Lock with fast lookup"""
|
135
120
|
if not self._connected:
|
136
121
|
raise MakcuConnectionError("Not connected")
|
137
122
|
|
@@ -149,7 +134,6 @@ class MakcuController:
|
|
149
134
|
raise ValueError(f"Invalid lock target: {target}")
|
150
135
|
|
151
136
|
def unlock(self, target: Union[MouseButton, str]) -> None:
|
152
|
-
"""Unlock with fast lookup"""
|
153
137
|
if not self._connected:
|
154
138
|
raise MakcuConnectionError("Not connected")
|
155
139
|
|
@@ -166,7 +150,7 @@ class MakcuController:
|
|
166
150
|
else:
|
167
151
|
raise ValueError(f"Invalid unlock target: {target}")
|
168
152
|
|
169
|
-
|
153
|
+
|
170
154
|
def lock_left(self, lock: bool) -> None:
|
171
155
|
if not self._connected:
|
172
156
|
raise MakcuConnectionError("Not connected")
|
@@ -203,105 +187,89 @@ class MakcuController:
|
|
203
187
|
self.mouse.lock_y(lock)
|
204
188
|
|
205
189
|
def lock_mouse_x(self, lock: bool) -> None:
|
206
|
-
"""Alias for lock_x"""
|
207
190
|
self.lock_x(lock)
|
208
191
|
|
209
192
|
def lock_mouse_y(self, lock: bool) -> None:
|
210
|
-
"""Alias for lock_y"""
|
211
193
|
self.lock_y(lock)
|
212
194
|
|
213
195
|
def is_locked(self, button: MouseButton) -> bool:
|
214
|
-
"""Check lock state"""
|
215
196
|
if not self._connected:
|
216
197
|
raise MakcuConnectionError("Not connected")
|
217
198
|
return self.mouse.is_locked(button)
|
218
199
|
|
219
200
|
def get_all_lock_states(self) -> Dict[str, bool]:
|
220
|
-
"""Get all lock states"""
|
221
201
|
if not self._connected:
|
222
202
|
raise MakcuConnectionError("Not connected")
|
223
203
|
return self.mouse.get_all_lock_states()
|
224
204
|
|
225
|
-
|
205
|
+
|
226
206
|
def spoof_serial(self, serial: str) -> None:
|
227
|
-
"""Spoof device serial"""
|
228
207
|
if not self._connected:
|
229
208
|
raise MakcuConnectionError("Not connected")
|
230
209
|
self.mouse.spoof_serial(serial)
|
231
210
|
|
232
211
|
def reset_serial(self) -> None:
|
233
|
-
"""Reset device serial"""
|
234
212
|
if not self._connected:
|
235
213
|
raise MakcuConnectionError("Not connected")
|
236
214
|
self.mouse.reset_serial()
|
237
215
|
|
238
216
|
def get_device_info(self) -> Dict[str, str]:
|
239
|
-
"""Get device information"""
|
240
217
|
if not self._connected:
|
241
218
|
raise MakcuConnectionError("Not connected")
|
242
219
|
return self.mouse.get_device_info()
|
243
220
|
|
244
221
|
def get_firmware_version(self) -> str:
|
245
|
-
"""Get firmware version"""
|
246
222
|
if not self._connected:
|
247
223
|
raise MakcuConnectionError("Not connected")
|
248
224
|
return self.mouse.get_firmware_version()
|
249
225
|
|
250
|
-
|
226
|
+
|
251
227
|
def get_button_mask(self) -> int:
|
252
|
-
"""Get current button mask"""
|
253
228
|
if not self._connected:
|
254
229
|
raise MakcuConnectionError("Not connected")
|
255
230
|
return self.transport.get_button_mask()
|
256
231
|
|
257
232
|
def get_button_states(self) -> Dict[str, bool]:
|
258
|
-
"""Get current button states"""
|
259
233
|
if not self._connected:
|
260
234
|
raise MakcuConnectionError("Not connected")
|
261
235
|
return self.transport.get_button_states()
|
262
236
|
|
263
237
|
def is_pressed(self, button: MouseButton) -> bool:
|
264
|
-
"""Check if button is pressed"""
|
265
238
|
if not self._connected:
|
266
239
|
raise MakcuConnectionError("Not connected")
|
267
240
|
return self.transport.get_button_states().get(button.name.lower(), False)
|
268
241
|
|
269
242
|
def enable_button_monitoring(self, enable: bool = True) -> None:
|
270
|
-
"""Enable/disable button monitoring"""
|
271
243
|
if not self._connected:
|
272
244
|
raise MakcuConnectionError("Not connected")
|
273
245
|
self.transport.enable_button_monitoring(enable)
|
274
246
|
|
275
247
|
def set_button_callback(self, callback: Optional[Callable[[MouseButton, bool], None]]) -> None:
|
276
|
-
"""Set button event callback"""
|
277
248
|
if not self._connected:
|
278
249
|
raise MakcuConnectionError("Not connected")
|
279
250
|
self.transport.set_button_callback(callback)
|
280
251
|
|
281
|
-
|
252
|
+
|
282
253
|
def on_connection_change(self, callback: Callable[[bool], None]) -> None:
|
283
|
-
"""Register connection status change callback"""
|
284
254
|
self._connection_callbacks.append(callback)
|
285
255
|
|
286
256
|
def remove_connection_callback(self, callback: Callable[[bool], None]) -> None:
|
287
|
-
"""Remove connection status callback"""
|
288
257
|
if callback in self._connection_callbacks:
|
289
258
|
self._connection_callbacks.remove(callback)
|
290
259
|
|
291
|
-
|
260
|
+
|
292
261
|
def click_human_like(self, button: MouseButton, count: int = 1,
|
293
262
|
profile: str = "normal", jitter: int = 0) -> None:
|
294
|
-
"""Human-like clicking optimized for gaming"""
|
295
263
|
if not self._connected:
|
296
264
|
raise MakcuConnectionError("Not connected")
|
297
265
|
|
298
|
-
|
266
|
+
|
299
267
|
timing_profiles = {
|
300
268
|
"normal": (60, 120, 100, 180),
|
301
269
|
"fast": (30, 60, 50, 100),
|
302
270
|
"slow": (100, 180, 150, 300),
|
303
271
|
"variable": (40, 200, 80, 250),
|
304
|
-
"gaming": (20, 40, 30, 60),
|
272
|
+
"gaming": (20, 40, 30, 60),
|
305
273
|
}
|
306
274
|
|
307
275
|
if profile not in timing_profiles:
|
@@ -324,78 +292,67 @@ class MakcuController:
|
|
324
292
|
|
325
293
|
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
326
294
|
button: MouseButton = MouseButton.LEFT, duration: float = 1.0) -> None:
|
327
|
-
"""Optimized drag operation"""
|
328
295
|
if not self._connected:
|
329
296
|
raise MakcuConnectionError("Not connected")
|
330
297
|
|
331
|
-
|
298
|
+
|
332
299
|
self.move(start_x, start_y)
|
333
|
-
time.sleep(0.02)
|
300
|
+
time.sleep(0.02)
|
334
301
|
|
335
|
-
|
302
|
+
|
336
303
|
self.press(button)
|
337
|
-
time.sleep(0.02)
|
304
|
+
time.sleep(0.02)
|
338
305
|
|
339
|
-
|
306
|
+
|
340
307
|
segments = max(10, int(duration * 30))
|
341
308
|
self.move_smooth(end_x - start_x, end_y - start_y, segments)
|
342
309
|
|
343
|
-
|
344
|
-
time.sleep(0.02)
|
310
|
+
|
311
|
+
time.sleep(0.02)
|
345
312
|
self.release(button)
|
346
313
|
|
347
|
-
|
314
|
+
|
348
315
|
def __enter__(self):
|
349
|
-
"""Context manager entry"""
|
350
316
|
if not self.is_connected():
|
351
317
|
self.connect()
|
352
318
|
return self
|
353
319
|
|
354
320
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
355
|
-
"""Context manager exit"""
|
356
321
|
self.disconnect()
|
357
322
|
|
358
|
-
|
323
|
+
|
359
324
|
async def async_connect(self) -> None:
|
360
|
-
"""Async connect"""
|
361
325
|
loop = asyncio.get_running_loop()
|
362
326
|
await loop.run_in_executor(self._executor, self.connect)
|
363
327
|
|
364
328
|
async def async_disconnect(self) -> None:
|
365
|
-
"""Async disconnect"""
|
366
329
|
loop = asyncio.get_running_loop()
|
367
330
|
await loop.run_in_executor(self._executor, self.disconnect)
|
368
331
|
|
369
332
|
async def async_click(self, button: MouseButton) -> None:
|
370
|
-
"""Async click"""
|
371
333
|
loop = asyncio.get_running_loop()
|
372
334
|
await loop.run_in_executor(self._executor, self.click, button)
|
373
335
|
|
374
336
|
async def async_move(self, dx: int, dy: int) -> None:
|
375
|
-
"""Async move"""
|
376
337
|
loop = asyncio.get_running_loop()
|
377
338
|
await loop.run_in_executor(self._executor, self.move, dx, dy)
|
378
339
|
|
379
340
|
async def async_scroll(self, delta: int) -> None:
|
380
|
-
"""Async scroll"""
|
381
341
|
loop = asyncio.get_running_loop()
|
382
342
|
await loop.run_in_executor(self._executor, self.scroll, delta)
|
383
343
|
|
384
|
-
|
344
|
+
|
385
345
|
async def __aenter__(self):
|
386
|
-
"""Async context manager entry"""
|
387
346
|
await self.async_connect()
|
388
347
|
return self
|
389
348
|
|
390
349
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
391
|
-
"""Async context manager exit"""
|
392
350
|
await self.async_disconnect()
|
393
351
|
|
394
352
|
|
395
|
-
|
353
|
+
|
396
354
|
def create_controller(fallback_com_port: str = "", debug: bool = False,
|
397
355
|
send_init: bool = True, auto_reconnect: bool = True) -> MakcuController:
|
398
|
-
"""Create and connect a controller"""
|
399
356
|
makcu = MakcuController(
|
400
357
|
fallback_com_port,
|
401
358
|
debug=debug,
|
@@ -409,7 +366,6 @@ def create_controller(fallback_com_port: str = "", debug: bool = False,
|
|
409
366
|
async def create_async_controller(fallback_com_port: str = "", debug: bool = False,
|
410
367
|
send_init: bool = True, auto_reconnect: bool = True,
|
411
368
|
override_port: bool = False) -> MakcuController:
|
412
|
-
"""Create and connect a controller asynchronously"""
|
413
369
|
makcu = MakcuController(
|
414
370
|
fallback_com_port,
|
415
371
|
debug=debug,
|
makcu/errors.py
CHANGED
@@ -1,19 +1,14 @@
|
|
1
1
|
class MakcuError(Exception):
|
2
|
-
"""Base exception for all Makcu-related errors."""
|
3
2
|
pass
|
4
3
|
|
5
4
|
class MakcuConnectionError(MakcuError):
|
6
|
-
"""Raised when the device connection fails."""
|
7
5
|
pass
|
8
6
|
|
9
7
|
class MakcuCommandError(MakcuError):
|
10
|
-
"""Raised when a device command is invalid, rejected, or fails."""
|
11
8
|
pass
|
12
9
|
|
13
10
|
class MakcuTimeoutError(MakcuError):
|
14
|
-
"""Raised when the device does not respond in time."""
|
15
11
|
pass
|
16
12
|
|
17
13
|
class MakcuResponseError(MakcuError):
|
18
|
-
"""Raised when the response from the device is malformed or unexpected."""
|
19
14
|
pass
|
makcu/mouse.py
CHANGED
@@ -5,15 +5,14 @@ from .errors import MakcuCommandError
|
|
5
5
|
from serial.tools import list_ports
|
6
6
|
import time
|
7
7
|
|
8
|
-
|
8
|
+
|
9
9
|
class AxisButton:
|
10
10
|
def __init__(self, name: str) -> None:
|
11
11
|
self.name = name
|
12
12
|
|
13
13
|
class Mouse:
|
14
|
-
"""Ultra-optimized mouse control for gaming performance"""
|
15
14
|
|
16
|
-
|
15
|
+
|
17
16
|
_BUTTON_COMMANDS = {
|
18
17
|
MouseButton.LEFT: "left",
|
19
18
|
MouseButton.RIGHT: "right",
|
@@ -22,7 +21,7 @@ class Mouse:
|
|
22
21
|
MouseButton.MOUSE5: "ms2",
|
23
22
|
}
|
24
23
|
|
25
|
-
|
24
|
+
|
26
25
|
_PRESS_COMMANDS = {}
|
27
26
|
_RELEASE_COMMANDS = {}
|
28
27
|
_LOCK_COMMANDS = {}
|
@@ -31,21 +30,20 @@ class Mouse:
|
|
31
30
|
|
32
31
|
def __init__(self, transport: SerialTransport) -> None:
|
33
32
|
self.transport = transport
|
34
|
-
self._lock_states_cache: int = 0
|
33
|
+
self._lock_states_cache: int = 0
|
35
34
|
self._cache_valid = False
|
36
35
|
|
37
|
-
|
36
|
+
|
38
37
|
self._init_command_cache()
|
39
38
|
|
40
39
|
def _init_command_cache(self) -> None:
|
41
|
-
|
42
|
-
# Button press/release commands
|
40
|
+
|
43
41
|
for button, cmd in self._BUTTON_COMMANDS.items():
|
44
42
|
self._PRESS_COMMANDS[button] = f"km.{cmd}(1)"
|
45
43
|
self._RELEASE_COMMANDS[button] = f"km.{cmd}(0)"
|
46
44
|
|
47
|
-
|
48
|
-
|
45
|
+
|
46
|
+
|
49
47
|
lock_targets = [
|
50
48
|
("LEFT", "ml", 0),
|
51
49
|
("RIGHT", "mr", 1),
|
@@ -62,55 +60,46 @@ class Mouse:
|
|
62
60
|
self._LOCK_QUERY_COMMANDS[name] = (f"km.lock_{cmd}()", bit)
|
63
61
|
|
64
62
|
def _send_button_command(self, button: MouseButton, state: int) -> None:
|
65
|
-
"""Optimized button command sending"""
|
66
63
|
if button not in self._BUTTON_COMMANDS:
|
67
64
|
raise MakcuCommandError(f"Unsupported button: {button}")
|
68
65
|
|
69
|
-
|
66
|
+
|
70
67
|
cmd = self._PRESS_COMMANDS[button] if state else self._RELEASE_COMMANDS[button]
|
71
68
|
self.transport.send_command(cmd)
|
72
69
|
|
73
70
|
def press(self, button: MouseButton) -> None:
|
74
|
-
"""Press with pre-computed command"""
|
75
71
|
self.transport.send_command(self._PRESS_COMMANDS[button])
|
76
72
|
|
77
73
|
def release(self, button: MouseButton) -> None:
|
78
|
-
"""Release with pre-computed command"""
|
79
74
|
self.transport.send_command(self._RELEASE_COMMANDS[button])
|
80
75
|
|
81
76
|
def move(self, x: int, y: int) -> None:
|
82
|
-
"""Move command"""
|
83
77
|
self.transport.send_command(f"km.move({x},{y})")
|
84
78
|
|
85
79
|
def click(self, button: MouseButton) -> None:
|
86
|
-
"""Optimized click with cached commands"""
|
87
80
|
if button not in self._BUTTON_COMMANDS:
|
88
81
|
raise MakcuCommandError(f"Unsupported button: {button}")
|
89
82
|
|
90
|
-
|
83
|
+
|
91
84
|
press_cmd = self._PRESS_COMMANDS[button]
|
92
85
|
release_cmd = self._RELEASE_COMMANDS[button]
|
93
86
|
|
94
|
-
|
95
|
-
transport = self.transport
|
87
|
+
|
88
|
+
transport = self.transport
|
96
89
|
transport.send_command(press_cmd)
|
97
90
|
transport.send_command(release_cmd)
|
98
91
|
|
99
92
|
def move_smooth(self, x: int, y: int, segments: int) -> None:
|
100
|
-
"""Smooth movement"""
|
101
93
|
self.transport.send_command(f"km.move({x},{y},{segments})")
|
102
94
|
|
103
95
|
def move_bezier(self, x: int, y: int, segments: int, ctrl_x: int, ctrl_y: int) -> None:
|
104
|
-
"""Bezier movement"""
|
105
96
|
self.transport.send_command(f"km.move({x},{y},{segments},{ctrl_x},{ctrl_y})")
|
106
97
|
|
107
98
|
def scroll(self, delta: int) -> None:
|
108
|
-
"""Scroll command"""
|
109
99
|
self.transport.send_command(f"km.wheel({delta})")
|
110
100
|
|
111
|
-
|
101
|
+
|
112
102
|
def _set_lock(self, name: str, lock: bool) -> None:
|
113
|
-
"""Generic lock setter with cache update"""
|
114
103
|
if lock:
|
115
104
|
cmd, bit = self._LOCK_COMMANDS[name]
|
116
105
|
else:
|
@@ -118,7 +107,7 @@ class Mouse:
|
|
118
107
|
|
119
108
|
self.transport.send_command(cmd)
|
120
109
|
|
121
|
-
|
110
|
+
|
122
111
|
if lock:
|
123
112
|
self._lock_states_cache |= (1 << bit)
|
124
113
|
else:
|
@@ -147,15 +136,12 @@ class Mouse:
|
|
147
136
|
self._set_lock("Y", lock)
|
148
137
|
|
149
138
|
def spoof_serial(self, serial: str) -> None:
|
150
|
-
"""Set custom serial"""
|
151
139
|
self.transport.send_command(f"km.serial('{serial}')")
|
152
140
|
|
153
141
|
def reset_serial(self) -> None:
|
154
|
-
"""Reset serial"""
|
155
142
|
self.transport.send_command("km.serial(0)")
|
156
143
|
|
157
144
|
def get_device_info(self) -> Dict[str, Union[str, bool]]:
|
158
|
-
"""Ultra-fast device info with minimal port scanning"""
|
159
145
|
port_name = self.transport.port
|
160
146
|
is_connected = self.transport.is_connected()
|
161
147
|
|
@@ -184,24 +170,21 @@ class Mouse:
|
|
184
170
|
info["vid"] = f"0x{port.vid:04x}"
|
185
171
|
if port.pid is not None:
|
186
172
|
info["pid"] = f"0x{port.pid:04x}"
|
187
|
-
break
|
173
|
+
break
|
188
174
|
except Exception:
|
189
175
|
pass
|
190
176
|
|
191
177
|
return info
|
192
178
|
|
193
179
|
def get_firmware_version(self) -> str:
|
194
|
-
"""Get firmware version"""
|
195
180
|
response = self.transport.send_command("km.version()", expect_response=True, timeout=0.1)
|
196
181
|
return response or ""
|
197
182
|
|
198
183
|
def _invalidate_cache(self) -> None:
|
199
|
-
"""Invalidate cache"""
|
200
184
|
self._cache_valid = False
|
201
185
|
|
202
186
|
def get_all_lock_states(self) -> Dict[str, bool]:
|
203
|
-
|
204
|
-
# Return cache if valid
|
187
|
+
|
205
188
|
if self._cache_valid:
|
206
189
|
return {
|
207
190
|
"X": bool(self._lock_states_cache & (1 << 5)),
|
@@ -213,7 +196,7 @@ class Mouse:
|
|
213
196
|
"MOUSE5": bool(self._lock_states_cache & (1 << 4)),
|
214
197
|
}
|
215
198
|
|
216
|
-
|
199
|
+
|
217
200
|
states = {}
|
218
201
|
targets = ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
|
219
202
|
|
@@ -225,7 +208,7 @@ class Mouse:
|
|
225
208
|
is_locked = result.strip() == '1'
|
226
209
|
states[target] = is_locked
|
227
210
|
|
228
|
-
|
211
|
+
|
229
212
|
if is_locked:
|
230
213
|
self._lock_states_cache |= (1 << bit)
|
231
214
|
else:
|
@@ -239,16 +222,15 @@ class Mouse:
|
|
239
222
|
return states
|
240
223
|
|
241
224
|
def is_locked(self, button: Union[MouseButton, AxisButton]) -> bool:
|
242
|
-
"""Check lock state with cache"""
|
243
225
|
try:
|
244
226
|
target_name = button.name if hasattr(button, 'name') else str(button)
|
245
227
|
|
246
|
-
|
228
|
+
|
247
229
|
if self._cache_valid and target_name in self._LOCK_QUERY_COMMANDS:
|
248
230
|
_, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
249
231
|
return bool(self._lock_states_cache & (1 << bit))
|
250
232
|
|
251
|
-
|
233
|
+
|
252
234
|
cmd, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
253
235
|
result = self.transport.send_command(cmd, expect_response=True, timeout=0.05)
|
254
236
|
|
@@ -258,7 +240,7 @@ class Mouse:
|
|
258
240
|
result = result.strip()
|
259
241
|
is_locked = result == '1'
|
260
242
|
|
261
|
-
|
243
|
+
|
262
244
|
if is_locked:
|
263
245
|
self._lock_states_cache |= (1 << bit)
|
264
246
|
else:
|