makcu 2.1.2__py3-none-any.whl → 2.1.3__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 +378 -60
- makcu/__main__.py +387 -387
- makcu/conftest.py +33 -33
- makcu/connection.py +459 -459
- makcu/controller.py +388 -376
- makcu/enums.py +7 -7
- makcu/errors.py +13 -13
- makcu/makcu.pyi +10 -10
- makcu/mouse.py +249 -249
- makcu/test_suite.py +144 -144
- makcu-2.1.3.dist-info/METADATA +32 -0
- makcu-2.1.3.dist-info/RECORD +15 -0
- makcu-2.1.2.dist-info/METADATA +0 -1141
- makcu-2.1.2.dist-info/RECORD +0 -16
- makcu-2.1.2.dist-info/licenses/LICENSE +0 -674
- {makcu-2.1.2.dist-info → makcu-2.1.3.dist-info}/WHEEL +0 -0
- {makcu-2.1.2.dist-info → makcu-2.1.3.dist-info}/top_level.txt +0 -0
makcu/__init__.py
CHANGED
@@ -1,60 +1,378 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
from
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
1
|
+
import asyncio
|
2
|
+
import random
|
3
|
+
import time
|
4
|
+
from typing import Optional, Dict, Callable, Union, List
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
6
|
+
from .mouse import Mouse
|
7
|
+
from .connection import SerialTransport
|
8
|
+
from .errors import MakcuConnectionError
|
9
|
+
from .enums import MouseButton
|
10
|
+
from .controller import maybe_async
|
11
|
+
|
12
|
+
class MakcuController:
|
13
|
+
_BUTTON_LOCK_MAP = {
|
14
|
+
MouseButton.LEFT: 'lock_left',
|
15
|
+
MouseButton.RIGHT: 'lock_right',
|
16
|
+
MouseButton.MIDDLE: 'lock_middle',
|
17
|
+
MouseButton.MOUSE4: 'lock_side1',
|
18
|
+
MouseButton.MOUSE5: 'lock_side2',
|
19
|
+
}
|
20
|
+
|
21
|
+
def __init__(self, fallback_com_port: str = "", debug: bool = False,
|
22
|
+
send_init: bool = True, auto_reconnect: bool = True,
|
23
|
+
override_port: bool = False) -> None:
|
24
|
+
self.transport = SerialTransport(
|
25
|
+
fallback_com_port,
|
26
|
+
debug=debug,
|
27
|
+
send_init=send_init,
|
28
|
+
auto_reconnect=auto_reconnect,
|
29
|
+
override_port=override_port
|
30
|
+
)
|
31
|
+
self.mouse = Mouse(self.transport)
|
32
|
+
self._executor = ThreadPoolExecutor(max_workers=1)
|
33
|
+
self._connection_callbacks: List[Callable[[bool], None]] = []
|
34
|
+
self._connected = False
|
35
|
+
|
36
|
+
async def _run_async(self, func, *args, **kwargs):
|
37
|
+
"""Run a function in the thread pool executor"""
|
38
|
+
loop = asyncio.get_running_loop()
|
39
|
+
return await loop.run_in_executor(self._executor, func, self, *args, **kwargs)
|
40
|
+
|
41
|
+
def _check_connection(self) -> None:
|
42
|
+
if not self._connected:
|
43
|
+
raise MakcuConnectionError("Not connected")
|
44
|
+
|
45
|
+
def _notify_connection_change(self, connected: bool) -> None:
|
46
|
+
for callback in self._connection_callbacks:
|
47
|
+
try:
|
48
|
+
callback(connected)
|
49
|
+
except Exception:
|
50
|
+
pass
|
51
|
+
|
52
|
+
@maybe_async
|
53
|
+
def connect(self) -> None:
|
54
|
+
self.transport.connect()
|
55
|
+
self._connected = True
|
56
|
+
self._notify_connection_change(True)
|
57
|
+
|
58
|
+
@maybe_async
|
59
|
+
def disconnect(self) -> None:
|
60
|
+
self.transport.disconnect()
|
61
|
+
self._connected = False
|
62
|
+
self._notify_connection_change(False)
|
63
|
+
self._executor.shutdown(wait=False)
|
64
|
+
|
65
|
+
@maybe_async
|
66
|
+
def is_connected(self) -> bool:
|
67
|
+
return self._connected and self.transport.is_connected()
|
68
|
+
|
69
|
+
@maybe_async
|
70
|
+
def click(self, button: MouseButton) -> None:
|
71
|
+
self._check_connection()
|
72
|
+
self.mouse.press(button)
|
73
|
+
self.mouse.release(button)
|
74
|
+
|
75
|
+
@maybe_async
|
76
|
+
def double_click(self, button: MouseButton) -> None:
|
77
|
+
self._check_connection()
|
78
|
+
self.mouse.press(button)
|
79
|
+
self.mouse.release(button)
|
80
|
+
time.sleep(0.001)
|
81
|
+
self.mouse.press(button)
|
82
|
+
self.mouse.release(button)
|
83
|
+
|
84
|
+
@maybe_async
|
85
|
+
def move(self, dx: int, dy: int) -> None:
|
86
|
+
self._check_connection()
|
87
|
+
self.mouse.move(dx, dy)
|
88
|
+
|
89
|
+
@maybe_async
|
90
|
+
def scroll(self, delta: int) -> None:
|
91
|
+
self._check_connection()
|
92
|
+
self.mouse.scroll(delta)
|
93
|
+
|
94
|
+
@maybe_async
|
95
|
+
def press(self, button: MouseButton) -> None:
|
96
|
+
self._check_connection()
|
97
|
+
self.mouse.press(button)
|
98
|
+
|
99
|
+
@maybe_async
|
100
|
+
def release(self, button: MouseButton) -> None:
|
101
|
+
self._check_connection()
|
102
|
+
self.mouse.release(button)
|
103
|
+
|
104
|
+
@maybe_async
|
105
|
+
def move_smooth(self, dx: int, dy: int, segments: int = 10) -> None:
|
106
|
+
self._check_connection()
|
107
|
+
self.mouse.move_smooth(dx, dy, segments)
|
108
|
+
|
109
|
+
@maybe_async
|
110
|
+
def move_bezier(self, dx: int, dy: int, segments: int = 20,
|
111
|
+
ctrl_x: Optional[int] = None, ctrl_y: Optional[int] = None) -> None:
|
112
|
+
self._check_connection()
|
113
|
+
if ctrl_x is None:
|
114
|
+
ctrl_x = dx // 2
|
115
|
+
if ctrl_y is None:
|
116
|
+
ctrl_y = dy // 2
|
117
|
+
self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
|
118
|
+
|
119
|
+
@maybe_async
|
120
|
+
def lock(self, target: Union[MouseButton, str]) -> None:
|
121
|
+
self._check_connection()
|
122
|
+
|
123
|
+
if isinstance(target, MouseButton):
|
124
|
+
if target in self._BUTTON_LOCK_MAP:
|
125
|
+
getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(True)
|
126
|
+
else:
|
127
|
+
raise ValueError(f"Unsupported button: {target}")
|
128
|
+
elif target.upper() in ['X', 'Y']:
|
129
|
+
if target.upper() == 'X':
|
130
|
+
self.mouse.lock_x(True)
|
131
|
+
else:
|
132
|
+
self.mouse.lock_y(True)
|
133
|
+
else:
|
134
|
+
raise ValueError(f"Invalid lock target: {target}")
|
135
|
+
|
136
|
+
@maybe_async
|
137
|
+
def unlock(self, target: Union[MouseButton, str]) -> None:
|
138
|
+
self._check_connection()
|
139
|
+
|
140
|
+
if isinstance(target, MouseButton):
|
141
|
+
if target in self._BUTTON_LOCK_MAP:
|
142
|
+
getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(False)
|
143
|
+
else:
|
144
|
+
raise ValueError(f"Unsupported button: {target}")
|
145
|
+
elif target.upper() in ['X', 'Y']:
|
146
|
+
if target.upper() == 'X':
|
147
|
+
self.mouse.lock_x(False)
|
148
|
+
else:
|
149
|
+
self.mouse.lock_y(False)
|
150
|
+
else:
|
151
|
+
raise ValueError(f"Invalid unlock target: {target}")
|
152
|
+
|
153
|
+
@maybe_async
|
154
|
+
def lock_left(self, lock: bool) -> None:
|
155
|
+
self._check_connection()
|
156
|
+
self.mouse.lock_left(lock)
|
157
|
+
|
158
|
+
@maybe_async
|
159
|
+
def lock_middle(self, lock: bool) -> None:
|
160
|
+
self._check_connection()
|
161
|
+
self.mouse.lock_middle(lock)
|
162
|
+
|
163
|
+
@maybe_async
|
164
|
+
def lock_right(self, lock: bool) -> None:
|
165
|
+
self._check_connection()
|
166
|
+
self.mouse.lock_right(lock)
|
167
|
+
|
168
|
+
@maybe_async
|
169
|
+
def lock_side1(self, lock: bool) -> None:
|
170
|
+
self._check_connection()
|
171
|
+
self.mouse.lock_side1(lock)
|
172
|
+
|
173
|
+
@maybe_async
|
174
|
+
def lock_side2(self, lock: bool) -> None:
|
175
|
+
self._check_connection()
|
176
|
+
self.mouse.lock_side2(lock)
|
177
|
+
|
178
|
+
@maybe_async
|
179
|
+
def lock_x(self, lock: bool) -> None:
|
180
|
+
self._check_connection()
|
181
|
+
self.mouse.lock_x(lock)
|
182
|
+
|
183
|
+
@maybe_async
|
184
|
+
def lock_y(self, lock: bool) -> None:
|
185
|
+
self._check_connection()
|
186
|
+
self.mouse.lock_y(lock)
|
187
|
+
|
188
|
+
@maybe_async
|
189
|
+
def lock_mouse_x(self, lock: bool) -> None:
|
190
|
+
self.lock_x(lock)
|
191
|
+
|
192
|
+
@maybe_async
|
193
|
+
def lock_mouse_y(self, lock: bool) -> None:
|
194
|
+
self.lock_y(lock)
|
195
|
+
|
196
|
+
@maybe_async
|
197
|
+
def is_locked(self, button: MouseButton) -> bool:
|
198
|
+
self._check_connection()
|
199
|
+
return self.mouse.is_locked(button)
|
200
|
+
|
201
|
+
@maybe_async
|
202
|
+
def get_all_lock_states(self) -> Dict[str, bool]:
|
203
|
+
self._check_connection()
|
204
|
+
return self.mouse.get_all_lock_states()
|
205
|
+
|
206
|
+
@maybe_async
|
207
|
+
def spoof_serial(self, serial: str) -> None:
|
208
|
+
self._check_connection()
|
209
|
+
self.mouse.spoof_serial(serial)
|
210
|
+
|
211
|
+
@maybe_async
|
212
|
+
def reset_serial(self) -> None:
|
213
|
+
self._check_connection()
|
214
|
+
self.mouse.reset_serial()
|
215
|
+
|
216
|
+
@maybe_async
|
217
|
+
def get_device_info(self) -> Dict[str, str]:
|
218
|
+
self._check_connection()
|
219
|
+
return self.mouse.get_device_info()
|
220
|
+
|
221
|
+
@maybe_async
|
222
|
+
def get_firmware_version(self) -> str:
|
223
|
+
self._check_connection()
|
224
|
+
return self.mouse.get_firmware_version()
|
225
|
+
|
226
|
+
@maybe_async
|
227
|
+
def get_button_mask(self) -> int:
|
228
|
+
self._check_connection()
|
229
|
+
return self.transport.get_button_mask()
|
230
|
+
|
231
|
+
@maybe_async
|
232
|
+
def get_button_states(self) -> Dict[str, bool]:
|
233
|
+
self._check_connection()
|
234
|
+
return self.transport.get_button_states()
|
235
|
+
|
236
|
+
@maybe_async
|
237
|
+
def is_pressed(self, button: MouseButton) -> bool:
|
238
|
+
self._check_connection()
|
239
|
+
return self.transport.get_button_states().get(button.name.lower(), False)
|
240
|
+
|
241
|
+
@maybe_async
|
242
|
+
def enable_button_monitoring(self, enable: bool = True) -> None:
|
243
|
+
self._check_connection()
|
244
|
+
self.transport.enable_button_monitoring(enable)
|
245
|
+
|
246
|
+
@maybe_async
|
247
|
+
def set_button_callback(self, callback: Optional[Callable[[MouseButton, bool], None]]) -> None:
|
248
|
+
self._check_connection()
|
249
|
+
self.transport.set_button_callback(callback)
|
250
|
+
|
251
|
+
@maybe_async
|
252
|
+
def on_connection_change(self, callback: Callable[[bool], None]) -> None:
|
253
|
+
self._connection_callbacks.append(callback)
|
254
|
+
|
255
|
+
@maybe_async
|
256
|
+
def remove_connection_callback(self, callback: Callable[[bool], None]) -> None:
|
257
|
+
if callback in self._connection_callbacks:
|
258
|
+
self._connection_callbacks.remove(callback)
|
259
|
+
|
260
|
+
@maybe_async
|
261
|
+
def click_human_like(self, button: MouseButton, count: int = 1,
|
262
|
+
profile: str = "normal", jitter: int = 0) -> None:
|
263
|
+
self._check_connection()
|
264
|
+
|
265
|
+
timing_profiles = {
|
266
|
+
"normal": (60, 120, 100, 180),
|
267
|
+
"fast": (30, 60, 50, 100),
|
268
|
+
"slow": (100, 180, 150, 300),
|
269
|
+
"variable": (40, 200, 80, 250),
|
270
|
+
"gaming": (20, 40, 30, 60),
|
271
|
+
}
|
272
|
+
|
273
|
+
if profile not in timing_profiles:
|
274
|
+
raise ValueError(f"Invalid profile: {profile}")
|
275
|
+
|
276
|
+
min_down, max_down, min_wait, max_wait = timing_profiles[profile]
|
277
|
+
|
278
|
+
for i in range(count):
|
279
|
+
if jitter > 0:
|
280
|
+
dx = random.randint(-jitter, jitter)
|
281
|
+
dy = random.randint(-jitter, jitter)
|
282
|
+
self.mouse.move(dx, dy)
|
283
|
+
|
284
|
+
self.mouse.press(button)
|
285
|
+
time.sleep(random.uniform(min_down, max_down) / 1000.0)
|
286
|
+
self.mouse.release(button)
|
287
|
+
|
288
|
+
if i < count - 1:
|
289
|
+
time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
|
290
|
+
|
291
|
+
@maybe_async
|
292
|
+
def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
|
293
|
+
button: MouseButton = MouseButton.LEFT, duration: float = 1.0) -> None:
|
294
|
+
self._check_connection()
|
295
|
+
|
296
|
+
# Move to start position
|
297
|
+
self.move(start_x, start_y)
|
298
|
+
time.sleep(0.02)
|
299
|
+
|
300
|
+
# Press button
|
301
|
+
self.press(button)
|
302
|
+
time.sleep(0.02)
|
303
|
+
|
304
|
+
# Smooth move to end position
|
305
|
+
segments = max(10, int(duration * 30))
|
306
|
+
self.move_smooth(end_x - start_x, end_y - start_y, segments)
|
307
|
+
|
308
|
+
# Release button
|
309
|
+
time.sleep(0.02)
|
310
|
+
self.release(button)
|
311
|
+
|
312
|
+
# Context managers for both sync and async
|
313
|
+
def __enter__(self):
|
314
|
+
if not self.is_connected():
|
315
|
+
self.connect()
|
316
|
+
return self
|
317
|
+
|
318
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
319
|
+
self.disconnect()
|
320
|
+
|
321
|
+
async def __aenter__(self):
|
322
|
+
if not await self.is_connected():
|
323
|
+
await self.connect()
|
324
|
+
return self
|
325
|
+
|
326
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
327
|
+
await self.disconnect()
|
328
|
+
|
329
|
+
# Legacy async methods for backward compatibility
|
330
|
+
async def async_connect(self) -> None:
|
331
|
+
"""Legacy method - use connect() instead"""
|
332
|
+
await self.connect()
|
333
|
+
|
334
|
+
async def async_disconnect(self) -> None:
|
335
|
+
"""Legacy method - use disconnect() instead"""
|
336
|
+
await self.disconnect()
|
337
|
+
|
338
|
+
async def async_click(self, button: MouseButton) -> None:
|
339
|
+
"""Legacy method - use click() instead"""
|
340
|
+
await self.click(button)
|
341
|
+
|
342
|
+
async def async_move(self, dx: int, dy: int) -> None:
|
343
|
+
"""Legacy method - use move() instead"""
|
344
|
+
await self.move(dx, dy)
|
345
|
+
|
346
|
+
async def async_scroll(self, delta: int) -> None:
|
347
|
+
"""Legacy method - use scroll() instead"""
|
348
|
+
await self.scroll(delta)
|
349
|
+
|
350
|
+
|
351
|
+
def create_controller(fallback_com_port: str = "", debug: bool = False,
|
352
|
+
send_init: bool = True, auto_reconnect: bool = True,
|
353
|
+
override_port: bool = False) -> MakcuController:
|
354
|
+
"""Create and connect a controller synchronously"""
|
355
|
+
makcu = MakcuController(
|
356
|
+
fallback_com_port,
|
357
|
+
debug=debug,
|
358
|
+
send_init=send_init,
|
359
|
+
auto_reconnect=auto_reconnect,
|
360
|
+
override_port=override_port
|
361
|
+
)
|
362
|
+
makcu.connect()
|
363
|
+
return makcu
|
364
|
+
|
365
|
+
|
366
|
+
async def create_async_controller(fallback_com_port: str = "", debug: bool = False,
|
367
|
+
send_init: bool = True, auto_reconnect: bool = True,
|
368
|
+
override_port: bool = False) -> MakcuController:
|
369
|
+
"""Create and connect a controller asynchronously"""
|
370
|
+
makcu = MakcuController(
|
371
|
+
fallback_com_port,
|
372
|
+
debug=debug,
|
373
|
+
send_init=send_init,
|
374
|
+
auto_reconnect=auto_reconnect,
|
375
|
+
override_port=override_port
|
376
|
+
)
|
377
|
+
await makcu.connect()
|
378
|
+
return makcu
|