makcu 0.2.0__py3-none-any.whl → 2.1.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 +13 -29
- makcu/conftest.py +0 -2
- makcu/connection.py +57 -83
- makcu/controller.py +27 -71
- makcu/errors.py +0 -5
- makcu/makcu.pyi +1 -3
- makcu/mouse.py +21 -41
- makcu/test_suite.py +8 -22
- {makcu-0.2.0.dist-info → makcu-2.1.1.dist-info}/METADATA +10 -10
- makcu-2.1.1.dist-info/RECORD +16 -0
- makcu-0.2.0.dist-info/RECORD +0 -16
- {makcu-0.2.0.dist-info → makcu-2.1.1.dist-info}/WHEEL +0 -0
- {makcu-0.2.0.dist-info → makcu-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {makcu-0.2.0.dist-info → makcu-2.1.1.dist-info}/top_level.txt +0 -0
makcu/controller.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
import random
|
3
3
|
import time
|
4
|
-
from typing import Optional, Dict, Callable, Union, List
|
4
|
+
from typing import Optional, Dict, Callable, Union, List
|
5
5
|
from concurrent.futures import ThreadPoolExecutor
|
6
6
|
from .mouse import Mouse
|
7
7
|
from .connection import SerialTransport
|
@@ -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/makcu.pyi
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import List
|
2
2
|
from .controller import MakcuController
|
3
|
-
from .enums import MouseButton
|
4
|
-
from .errors import MakcuError, MakcuConnectionError, MakcuCommandError, MakcuTimeoutError, MakcuResponseError
|
5
3
|
|
6
4
|
__version__: str
|
7
5
|
__all__: List[str]
|
makcu/mouse.py
CHANGED
@@ -1,19 +1,16 @@
|
|
1
|
-
from typing import Dict,
|
1
|
+
from typing import Dict, Union
|
2
2
|
from .enums import MouseButton
|
3
3
|
from .connection import SerialTransport
|
4
4
|
from .errors import MakcuCommandError
|
5
5
|
from serial.tools import list_ports
|
6
|
-
import time
|
7
6
|
|
8
|
-
# Create a mock enum-like object for axis locks
|
9
7
|
class AxisButton:
|
10
8
|
def __init__(self, name: str) -> None:
|
11
9
|
self.name = name
|
12
10
|
|
13
11
|
class Mouse:
|
14
|
-
"""Ultra-optimized mouse control for gaming performance"""
|
15
12
|
|
16
|
-
|
13
|
+
|
17
14
|
_BUTTON_COMMANDS = {
|
18
15
|
MouseButton.LEFT: "left",
|
19
16
|
MouseButton.RIGHT: "right",
|
@@ -22,7 +19,7 @@ class Mouse:
|
|
22
19
|
MouseButton.MOUSE5: "ms2",
|
23
20
|
}
|
24
21
|
|
25
|
-
|
22
|
+
|
26
23
|
_PRESS_COMMANDS = {}
|
27
24
|
_RELEASE_COMMANDS = {}
|
28
25
|
_LOCK_COMMANDS = {}
|
@@ -31,21 +28,20 @@ class Mouse:
|
|
31
28
|
|
32
29
|
def __init__(self, transport: SerialTransport) -> None:
|
33
30
|
self.transport = transport
|
34
|
-
self._lock_states_cache: int = 0
|
31
|
+
self._lock_states_cache: int = 0
|
35
32
|
self._cache_valid = False
|
36
33
|
|
37
|
-
|
34
|
+
|
38
35
|
self._init_command_cache()
|
39
36
|
|
40
37
|
def _init_command_cache(self) -> None:
|
41
|
-
|
42
|
-
# Button press/release commands
|
38
|
+
|
43
39
|
for button, cmd in self._BUTTON_COMMANDS.items():
|
44
40
|
self._PRESS_COMMANDS[button] = f"km.{cmd}(1)"
|
45
41
|
self._RELEASE_COMMANDS[button] = f"km.{cmd}(0)"
|
46
42
|
|
47
|
-
|
48
|
-
|
43
|
+
|
44
|
+
|
49
45
|
lock_targets = [
|
50
46
|
("LEFT", "ml", 0),
|
51
47
|
("RIGHT", "mr", 1),
|
@@ -62,55 +58,46 @@ class Mouse:
|
|
62
58
|
self._LOCK_QUERY_COMMANDS[name] = (f"km.lock_{cmd}()", bit)
|
63
59
|
|
64
60
|
def _send_button_command(self, button: MouseButton, state: int) -> None:
|
65
|
-
"""Optimized button command sending"""
|
66
61
|
if button not in self._BUTTON_COMMANDS:
|
67
62
|
raise MakcuCommandError(f"Unsupported button: {button}")
|
68
63
|
|
69
|
-
|
64
|
+
|
70
65
|
cmd = self._PRESS_COMMANDS[button] if state else self._RELEASE_COMMANDS[button]
|
71
66
|
self.transport.send_command(cmd)
|
72
67
|
|
73
68
|
def press(self, button: MouseButton) -> None:
|
74
|
-
"""Press with pre-computed command"""
|
75
69
|
self.transport.send_command(self._PRESS_COMMANDS[button])
|
76
70
|
|
77
71
|
def release(self, button: MouseButton) -> None:
|
78
|
-
"""Release with pre-computed command"""
|
79
72
|
self.transport.send_command(self._RELEASE_COMMANDS[button])
|
80
73
|
|
81
74
|
def move(self, x: int, y: int) -> None:
|
82
|
-
"""Move command"""
|
83
75
|
self.transport.send_command(f"km.move({x},{y})")
|
84
76
|
|
85
77
|
def click(self, button: MouseButton) -> None:
|
86
|
-
"""Optimized click with cached commands"""
|
87
78
|
if button not in self._BUTTON_COMMANDS:
|
88
79
|
raise MakcuCommandError(f"Unsupported button: {button}")
|
89
80
|
|
90
|
-
|
81
|
+
|
91
82
|
press_cmd = self._PRESS_COMMANDS[button]
|
92
83
|
release_cmd = self._RELEASE_COMMANDS[button]
|
93
84
|
|
94
|
-
|
95
|
-
transport = self.transport
|
85
|
+
|
86
|
+
transport = self.transport
|
96
87
|
transport.send_command(press_cmd)
|
97
88
|
transport.send_command(release_cmd)
|
98
89
|
|
99
90
|
def move_smooth(self, x: int, y: int, segments: int) -> None:
|
100
|
-
"""Smooth movement"""
|
101
91
|
self.transport.send_command(f"km.move({x},{y},{segments})")
|
102
92
|
|
103
93
|
def move_bezier(self, x: int, y: int, segments: int, ctrl_x: int, ctrl_y: int) -> None:
|
104
|
-
"""Bezier movement"""
|
105
94
|
self.transport.send_command(f"km.move({x},{y},{segments},{ctrl_x},{ctrl_y})")
|
106
95
|
|
107
96
|
def scroll(self, delta: int) -> None:
|
108
|
-
"""Scroll command"""
|
109
97
|
self.transport.send_command(f"km.wheel({delta})")
|
110
98
|
|
111
|
-
|
99
|
+
|
112
100
|
def _set_lock(self, name: str, lock: bool) -> None:
|
113
|
-
"""Generic lock setter with cache update"""
|
114
101
|
if lock:
|
115
102
|
cmd, bit = self._LOCK_COMMANDS[name]
|
116
103
|
else:
|
@@ -118,7 +105,7 @@ class Mouse:
|
|
118
105
|
|
119
106
|
self.transport.send_command(cmd)
|
120
107
|
|
121
|
-
|
108
|
+
|
122
109
|
if lock:
|
123
110
|
self._lock_states_cache |= (1 << bit)
|
124
111
|
else:
|
@@ -147,15 +134,12 @@ class Mouse:
|
|
147
134
|
self._set_lock("Y", lock)
|
148
135
|
|
149
136
|
def spoof_serial(self, serial: str) -> None:
|
150
|
-
"""Set custom serial"""
|
151
137
|
self.transport.send_command(f"km.serial('{serial}')")
|
152
138
|
|
153
139
|
def reset_serial(self) -> None:
|
154
|
-
"""Reset serial"""
|
155
140
|
self.transport.send_command("km.serial(0)")
|
156
141
|
|
157
142
|
def get_device_info(self) -> Dict[str, Union[str, bool]]:
|
158
|
-
"""Ultra-fast device info with minimal port scanning"""
|
159
143
|
port_name = self.transport.port
|
160
144
|
is_connected = self.transport.is_connected()
|
161
145
|
|
@@ -184,24 +168,21 @@ class Mouse:
|
|
184
168
|
info["vid"] = f"0x{port.vid:04x}"
|
185
169
|
if port.pid is not None:
|
186
170
|
info["pid"] = f"0x{port.pid:04x}"
|
187
|
-
break
|
171
|
+
break
|
188
172
|
except Exception:
|
189
173
|
pass
|
190
174
|
|
191
175
|
return info
|
192
176
|
|
193
177
|
def get_firmware_version(self) -> str:
|
194
|
-
"""Get firmware version"""
|
195
178
|
response = self.transport.send_command("km.version()", expect_response=True, timeout=0.1)
|
196
179
|
return response or ""
|
197
180
|
|
198
181
|
def _invalidate_cache(self) -> None:
|
199
|
-
"""Invalidate cache"""
|
200
182
|
self._cache_valid = False
|
201
183
|
|
202
184
|
def get_all_lock_states(self) -> Dict[str, bool]:
|
203
|
-
|
204
|
-
# Return cache if valid
|
185
|
+
|
205
186
|
if self._cache_valid:
|
206
187
|
return {
|
207
188
|
"X": bool(self._lock_states_cache & (1 << 5)),
|
@@ -213,7 +194,7 @@ class Mouse:
|
|
213
194
|
"MOUSE5": bool(self._lock_states_cache & (1 << 4)),
|
214
195
|
}
|
215
196
|
|
216
|
-
|
197
|
+
|
217
198
|
states = {}
|
218
199
|
targets = ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
|
219
200
|
|
@@ -225,7 +206,7 @@ class Mouse:
|
|
225
206
|
is_locked = result.strip() == '1'
|
226
207
|
states[target] = is_locked
|
227
208
|
|
228
|
-
|
209
|
+
|
229
210
|
if is_locked:
|
230
211
|
self._lock_states_cache |= (1 << bit)
|
231
212
|
else:
|
@@ -239,16 +220,15 @@ class Mouse:
|
|
239
220
|
return states
|
240
221
|
|
241
222
|
def is_locked(self, button: Union[MouseButton, AxisButton]) -> bool:
|
242
|
-
"""Check lock state with cache"""
|
243
223
|
try:
|
244
224
|
target_name = button.name if hasattr(button, 'name') else str(button)
|
245
225
|
|
246
|
-
|
226
|
+
|
247
227
|
if self._cache_valid and target_name in self._LOCK_QUERY_COMMANDS:
|
248
228
|
_, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
249
229
|
return bool(self._lock_states_cache & (1 << bit))
|
250
230
|
|
251
|
-
|
231
|
+
|
252
232
|
cmd, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
253
233
|
result = self.transport.send_command(cmd, expect_response=True, timeout=0.05)
|
254
234
|
|
@@ -258,7 +238,7 @@ class Mouse:
|
|
258
238
|
result = result.strip()
|
259
239
|
is_locked = result == '1'
|
260
240
|
|
261
|
-
|
241
|
+
|
262
242
|
if is_locked:
|
263
243
|
self._lock_states_cache |= (1 << bit)
|
264
244
|
else:
|