makcu 0.1.3__py3-none-any.whl → 0.2.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.
makcu/controller.py CHANGED
@@ -1,176 +1,421 @@
1
+ import asyncio
1
2
  import random
2
3
  import time
4
+ from typing import Optional, Dict, Callable, Union, List, Any
5
+ from concurrent.futures import ThreadPoolExecutor
3
6
  from .mouse import Mouse
4
7
  from .connection import SerialTransport
5
8
  from .errors import MakcuConnectionError
6
9
  from .enums import MouseButton
7
10
 
8
11
  class MakcuController:
9
- def __init__(self, fallback_com_port, debug=False, send_init=True):
10
- self.transport = SerialTransport(fallback_com_port, debug=debug, send_init=send_init)
12
+ """Ultra-optimized MakcuController for gaming performance"""
13
+
14
+ # Pre-computed lock mappings for O(1) access
15
+ _BUTTON_LOCK_MAP = {
16
+ MouseButton.LEFT: 'lock_left',
17
+ MouseButton.RIGHT: 'lock_right',
18
+ MouseButton.MIDDLE: 'lock_middle',
19
+ MouseButton.MOUSE4: 'lock_side1',
20
+ MouseButton.MOUSE5: 'lock_side2',
21
+ }
22
+
23
+ def __init__(self, fallback_com_port: str = "", debug: bool = False,
24
+ send_init: bool = True, auto_reconnect: bool = True,
25
+ override_port: bool = False) -> None:
26
+ self.transport = SerialTransport(
27
+ fallback_com_port,
28
+ debug=debug,
29
+ send_init=send_init,
30
+ auto_reconnect=auto_reconnect,
31
+ override_port=override_port
32
+ )
11
33
  self.mouse = Mouse(self.transport)
34
+ self._executor = ThreadPoolExecutor(max_workers=1)
35
+ self._connection_callbacks: List[Callable[[bool], None]] = []
36
+
37
+ # Cache connection state to avoid repeated checks
38
+ self._connected = False
12
39
 
13
- def connect(self):
40
+ # Connection management
41
+ def connect(self) -> None:
42
+ """Connect with state caching"""
14
43
  self.transport.connect()
44
+ self._connected = True
45
+ self._notify_connection_change(True)
15
46
 
16
- def disconnect(self):
47
+ def disconnect(self) -> None:
48
+ """Disconnect with cleanup"""
17
49
  self.transport.disconnect()
50
+ self._connected = False
51
+ self._notify_connection_change(False)
52
+ self._executor.shutdown(wait=False)
18
53
 
19
- def is_connected(self):
20
- return self.transport.is_connected()
54
+ def is_connected(self) -> bool:
55
+ """Fast cached connection check"""
56
+ return self._connected and self.transport.is_connected()
21
57
 
22
- def _check_connection(self):
23
- if not self.transport.serial or not self.transport.serial.is_open:
58
+ def _check_connection(self) -> None:
59
+ """Inline connection check for speed"""
60
+ if not self._connected:
24
61
  raise MakcuConnectionError("Not connected")
25
62
 
26
- def click(self, button: MouseButton):
27
- self._check_connection()
28
- self._send_button_command(button, 1)
29
- self._send_button_command(button, 0)
63
+ def _notify_connection_change(self, connected: bool) -> None:
64
+ """Notify callbacks with error suppression"""
65
+ for callback in self._connection_callbacks:
66
+ try:
67
+ callback(connected)
68
+ except Exception:
69
+ pass # Silently ignore for performance
70
+
71
+ # Optimized mouse operations
72
+ def click(self, button: MouseButton) -> None:
73
+ """Optimized click - direct command"""
74
+ if not self._connected:
75
+ raise MakcuConnectionError("Not connected")
76
+ self.mouse.press(button)
77
+ self.mouse.release(button)
30
78
 
31
- def move(self, dx: int, dy: int):
32
- self._check_connection()
79
+ def double_click(self, button: MouseButton) -> None:
80
+ """Double click with minimal delay"""
81
+ if not self._connected:
82
+ raise MakcuConnectionError("Not connected")
83
+ self.mouse.press(button)
84
+ self.mouse.release(button)
85
+ # 1ms delay for double click recognition
86
+ time.sleep(0.001)
87
+ self.mouse.press(button)
88
+ self.mouse.release(button)
89
+
90
+ def move(self, dx: int, dy: int) -> None:
91
+ """Direct move"""
92
+ if not self._connected:
93
+ raise MakcuConnectionError("Not connected")
33
94
  self.mouse.move(dx, dy)
34
95
 
35
- def scroll(self, delta: int):
36
- self._check_connection()
96
+ def scroll(self, delta: int) -> None:
97
+ """Direct scroll"""
98
+ if not self._connected:
99
+ raise MakcuConnectionError("Not connected")
37
100
  self.mouse.scroll(delta)
38
101
 
39
- def move_smooth(self, dx: int, dy: int, segments: int):
40
- self._check_connection()
41
- self.mouse.move_smooth(dx, dy, segments)
102
+ def press(self, button: MouseButton) -> None:
103
+ """Direct press"""
104
+ if not self._connected:
105
+ raise MakcuConnectionError("Not connected")
106
+ self.mouse.press(button)
42
107
 
43
- def move_bezier(self, dx: int, dy: int, segments: int, ctrl_x: int, ctrl_y: int):
44
- self._check_connection()
45
- self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
108
+ def release(self, button: MouseButton) -> None:
109
+ """Direct release"""
110
+ if not self._connected:
111
+ raise MakcuConnectionError("Not connected")
112
+ self.mouse.release(button)
46
113
 
47
- def lock_mouse_x(self, lock: bool):
48
- self._check_connection()
49
- self.mouse.lock_x(lock)
114
+ # Advanced movement - unchanged but with connection check optimization
115
+ def move_smooth(self, dx: int, dy: int, segments: int = 10) -> None:
116
+ """Smooth movement"""
117
+ if not self._connected:
118
+ raise MakcuConnectionError("Not connected")
119
+ self.mouse.move_smooth(dx, dy, segments)
50
120
 
51
- def lock_mouse_y(self, lock: bool):
52
- self._check_connection()
53
- self.mouse.lock_y(lock)
121
+ def move_bezier(self, dx: int, dy: int, segments: int = 20,
122
+ ctrl_x: Optional[int] = None, ctrl_y: Optional[int] = None) -> None:
123
+ """Bezier curve movement"""
124
+ if not self._connected:
125
+ raise MakcuConnectionError("Not connected")
126
+ if ctrl_x is None:
127
+ ctrl_x = dx // 2
128
+ if ctrl_y is None:
129
+ ctrl_y = dy // 2
130
+ self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
54
131
 
55
- def lock_left(self, lock: bool):
56
- self._check_connection()
132
+ # Optimized lock API
133
+ def lock(self, target: Union[MouseButton, str]) -> None:
134
+ """Lock with fast lookup"""
135
+ if not self._connected:
136
+ raise MakcuConnectionError("Not connected")
137
+
138
+ if isinstance(target, MouseButton):
139
+ if target in self._BUTTON_LOCK_MAP:
140
+ getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(True)
141
+ else:
142
+ raise ValueError(f"Unsupported button: {target}")
143
+ elif target.upper() in ['X', 'Y']:
144
+ if target.upper() == 'X':
145
+ self.mouse.lock_x(True)
146
+ else:
147
+ self.mouse.lock_y(True)
148
+ else:
149
+ raise ValueError(f"Invalid lock target: {target}")
150
+
151
+ def unlock(self, target: Union[MouseButton, str]) -> None:
152
+ """Unlock with fast lookup"""
153
+ if not self._connected:
154
+ raise MakcuConnectionError("Not connected")
155
+
156
+ if isinstance(target, MouseButton):
157
+ if target in self._BUTTON_LOCK_MAP:
158
+ getattr(self.mouse, self._BUTTON_LOCK_MAP[target])(False)
159
+ else:
160
+ raise ValueError(f"Unsupported button: {target}")
161
+ elif target.upper() in ['X', 'Y']:
162
+ if target.upper() == 'X':
163
+ self.mouse.lock_x(False)
164
+ else:
165
+ self.mouse.lock_y(False)
166
+ else:
167
+ raise ValueError(f"Invalid unlock target: {target}")
168
+
169
+ # Direct lock methods for backward compatibility
170
+ def lock_left(self, lock: bool) -> None:
171
+ if not self._connected:
172
+ raise MakcuConnectionError("Not connected")
57
173
  self.mouse.lock_left(lock)
58
174
 
59
- def lock_middle(self, lock: bool):
60
- self._check_connection()
175
+ def lock_middle(self, lock: bool) -> None:
176
+ if not self._connected:
177
+ raise MakcuConnectionError("Not connected")
61
178
  self.mouse.lock_middle(lock)
62
179
 
63
- def lock_right(self, lock: bool):
64
- self._check_connection()
180
+ def lock_right(self, lock: bool) -> None:
181
+ if not self._connected:
182
+ raise MakcuConnectionError("Not connected")
65
183
  self.mouse.lock_right(lock)
66
184
 
67
- def lock_side1(self, lock: bool):
68
- self._check_connection()
185
+ def lock_side1(self, lock: bool) -> None:
186
+ if not self._connected:
187
+ raise MakcuConnectionError("Not connected")
69
188
  self.mouse.lock_side1(lock)
70
189
 
71
- def lock_side2(self, lock: bool):
72
- self._check_connection()
190
+ def lock_side2(self, lock: bool) -> None:
191
+ if not self._connected:
192
+ raise MakcuConnectionError("Not connected")
73
193
  self.mouse.lock_side2(lock)
74
194
 
75
- def lock_x(self, lock: bool):
76
- self._check_connection()
195
+ def lock_x(self, lock: bool) -> None:
196
+ if not self._connected:
197
+ raise MakcuConnectionError("Not connected")
77
198
  self.mouse.lock_x(lock)
78
199
 
79
- def lock_y(self, lock: bool):
80
- self._check_connection()
200
+ def lock_y(self, lock: bool) -> None:
201
+ if not self._connected:
202
+ raise MakcuConnectionError("Not connected")
81
203
  self.mouse.lock_y(lock)
82
204
 
83
- def spoof_serial(self, serial: str):
84
- self._check_connection()
205
+ def lock_mouse_x(self, lock: bool) -> None:
206
+ """Alias for lock_x"""
207
+ self.lock_x(lock)
208
+
209
+ def lock_mouse_y(self, lock: bool) -> None:
210
+ """Alias for lock_y"""
211
+ self.lock_y(lock)
212
+
213
+ def is_locked(self, button: MouseButton) -> bool:
214
+ """Check lock state"""
215
+ if not self._connected:
216
+ raise MakcuConnectionError("Not connected")
217
+ return self.mouse.is_locked(button)
218
+
219
+ def get_all_lock_states(self) -> Dict[str, bool]:
220
+ """Get all lock states"""
221
+ if not self._connected:
222
+ raise MakcuConnectionError("Not connected")
223
+ return self.mouse.get_all_lock_states()
224
+
225
+ # Device operations
226
+ def spoof_serial(self, serial: str) -> None:
227
+ """Spoof device serial"""
228
+ if not self._connected:
229
+ raise MakcuConnectionError("Not connected")
85
230
  self.mouse.spoof_serial(serial)
86
231
 
87
- def reset_serial(self):
88
- self._check_connection()
232
+ def reset_serial(self) -> None:
233
+ """Reset device serial"""
234
+ if not self._connected:
235
+ raise MakcuConnectionError("Not connected")
89
236
  self.mouse.reset_serial()
90
237
 
91
- def get_device_info(self):
92
- self._check_connection()
238
+ def get_device_info(self) -> Dict[str, str]:
239
+ """Get device information"""
240
+ if not self._connected:
241
+ raise MakcuConnectionError("Not connected")
93
242
  return self.mouse.get_device_info()
94
243
 
95
- def get_firmware_version(self):
96
- self._check_connection()
244
+ def get_firmware_version(self) -> str:
245
+ """Get firmware version"""
246
+ if not self._connected:
247
+ raise MakcuConnectionError("Not connected")
97
248
  return self.mouse.get_firmware_version()
98
249
 
250
+ # Button monitoring - direct transport access for speed
99
251
  def get_button_mask(self) -> int:
100
- self._check_connection()
252
+ """Get current button mask"""
253
+ if not self._connected:
254
+ raise MakcuConnectionError("Not connected")
101
255
  return self.transport.get_button_mask()
102
256
 
103
- def is_button_locked(self, button: MouseButton) -> bool:
104
- self._check_connection()
105
- return self.mouse.is_button_locked(button)
257
+ def get_button_states(self) -> Dict[str, bool]:
258
+ """Get current button states"""
259
+ if not self._connected:
260
+ raise MakcuConnectionError("Not connected")
261
+ return self.transport.get_button_states()
262
+
263
+ def is_pressed(self, button: MouseButton) -> bool:
264
+ """Check if button is pressed"""
265
+ if not self._connected:
266
+ raise MakcuConnectionError("Not connected")
267
+ return self.transport.get_button_states().get(button.name.lower(), False)
106
268
 
107
- #def capture(self, button: MouseButton):
108
- # self._check_connection()
109
- # self.mouse.begin_capture(button.name)
269
+ def enable_button_monitoring(self, enable: bool = True) -> None:
270
+ """Enable/disable button monitoring"""
271
+ if not self._connected:
272
+ raise MakcuConnectionError("Not connected")
273
+ self.transport.enable_button_monitoring(enable)
110
274
 
275
+ def set_button_callback(self, callback: Optional[Callable[[MouseButton, bool], None]]) -> None:
276
+ """Set button event callback"""
277
+ if not self._connected:
278
+ raise MakcuConnectionError("Not connected")
279
+ self.transport.set_button_callback(callback)
111
280
 
112
- #def stop_capturing_clicks(self, button: str) -> int:
113
- # self._check_connection()
114
- # return self.mouse.stop_capturing_clicks(button)
115
-
116
- #def get_captured_clicks(self, button: MouseButton) -> int:
117
- # self._check_connection()
118
- # return self.mouse.stop_capturing_clicks(button.name)
281
+ # Connection management
282
+ def on_connection_change(self, callback: Callable[[bool], None]) -> None:
283
+ """Register connection status change callback"""
284
+ self._connection_callbacks.append(callback)
119
285
 
286
+ def remove_connection_callback(self, callback: Callable[[bool], None]) -> None:
287
+ """Remove connection status callback"""
288
+ if callback in self._connection_callbacks:
289
+ self._connection_callbacks.remove(callback)
120
290
 
291
+ # Human-like interactions with optimized timing
121
292
  def click_human_like(self, button: MouseButton, count: int = 1,
122
- profile: str = "normal", jitter: int = 0):
123
- self._check_connection()
293
+ profile: str = "normal", jitter: int = 0) -> None:
294
+ """Human-like clicking optimized for gaming"""
295
+ if not self._connected:
296
+ raise MakcuConnectionError("Not connected")
124
297
 
298
+ # Pre-computed timing profiles (in milliseconds)
125
299
  timing_profiles = {
126
300
  "normal": (60, 120, 100, 180),
127
301
  "fast": (30, 60, 50, 100),
128
302
  "slow": (100, 180, 150, 300),
303
+ "variable": (40, 200, 80, 250),
304
+ "gaming": (20, 40, 30, 60), # New gaming profile
129
305
  }
130
306
 
131
307
  if profile not in timing_profiles:
132
- raise ValueError(f"Invalid profile: {profile}. Choose from {list(timing_profiles.keys())}")
308
+ raise ValueError(f"Invalid profile: {profile}")
133
309
 
134
310
  min_down, max_down, min_wait, max_wait = timing_profiles[profile]
135
311
 
136
- for _ in range(count):
312
+ for i in range(count):
137
313
  if jitter > 0:
138
314
  dx = random.randint(-jitter, jitter)
139
315
  dy = random.randint(-jitter, jitter)
140
316
  self.mouse.move(dx, dy)
141
317
 
142
- self.press(button)
318
+ self.mouse.press(button)
143
319
  time.sleep(random.uniform(min_down, max_down) / 1000.0)
144
320
  self.mouse.release(button)
145
- time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
146
-
147
- def enable_button_monitoring(self, enable: bool = True):
148
- self._check_connection()
149
- self.transport.enable_button_monitoring(enable)
150
-
151
- def set_button_callback(self, callback):
152
- self._check_connection()
153
- self.transport.set_button_callback(callback)
154
-
155
- def get_all_lock_states(self) -> dict:
156
- self._check_connection()
157
- return self.mouse.get_all_lock_states()
158
-
159
- def _send_button_command(self, button: MouseButton, state: int):
160
- self.mouse._send_button_command(button, state)
161
-
162
- def press(self, button: MouseButton):
163
- self._check_connection()
164
- self._send_button_command(button, 1)
165
-
166
- def release(self, button: MouseButton):
167
- self._check_connection()
168
- self._send_button_command(button, 0)
321
+
322
+ if i < count - 1:
323
+ time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
324
+
325
+ def drag(self, start_x: int, start_y: int, end_x: int, end_y: int,
326
+ button: MouseButton = MouseButton.LEFT, duration: float = 1.0) -> None:
327
+ """Optimized drag operation"""
328
+ if not self._connected:
329
+ raise MakcuConnectionError("Not connected")
169
330
 
170
- def get_button_states(self) -> dict:
171
- self._check_connection()
172
- return self.transport.get_button_states()
173
-
174
- def is_button_pressed(self, button: MouseButton) -> bool:
175
- self._check_connection()
176
- return self.transport.get_button_states().get(button.name.lower(), False)
331
+ # Move to start
332
+ self.move(start_x, start_y)
333
+ time.sleep(0.02) # Reduced from 0.05
334
+
335
+ # Press button
336
+ self.press(button)
337
+ time.sleep(0.02) # Reduced from 0.05
338
+
339
+ # Move to end with smooth motion
340
+ segments = max(10, int(duration * 30))
341
+ self.move_smooth(end_x - start_x, end_y - start_y, segments)
342
+
343
+ # Release button
344
+ time.sleep(0.02) # Reduced from 0.05
345
+ self.release(button)
346
+
347
+ # Context manager support
348
+ def __enter__(self):
349
+ """Context manager entry"""
350
+ if not self.is_connected():
351
+ self.connect()
352
+ return self
353
+
354
+ def __exit__(self, exc_type, exc_val, exc_tb):
355
+ """Context manager exit"""
356
+ self.disconnect()
357
+
358
+ # Async methods for forward compatibility
359
+ async def async_connect(self) -> None:
360
+ """Async connect"""
361
+ loop = asyncio.get_running_loop()
362
+ await loop.run_in_executor(self._executor, self.connect)
363
+
364
+ async def async_disconnect(self) -> None:
365
+ """Async disconnect"""
366
+ loop = asyncio.get_running_loop()
367
+ await loop.run_in_executor(self._executor, self.disconnect)
368
+
369
+ async def async_click(self, button: MouseButton) -> None:
370
+ """Async click"""
371
+ loop = asyncio.get_running_loop()
372
+ await loop.run_in_executor(self._executor, self.click, button)
373
+
374
+ async def async_move(self, dx: int, dy: int) -> None:
375
+ """Async move"""
376
+ loop = asyncio.get_running_loop()
377
+ await loop.run_in_executor(self._executor, self.move, dx, dy)
378
+
379
+ async def async_scroll(self, delta: int) -> None:
380
+ """Async scroll"""
381
+ loop = asyncio.get_running_loop()
382
+ await loop.run_in_executor(self._executor, self.scroll, delta)
383
+
384
+ # Async context manager
385
+ async def __aenter__(self):
386
+ """Async context manager entry"""
387
+ await self.async_connect()
388
+ return self
389
+
390
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
391
+ """Async context manager exit"""
392
+ await self.async_disconnect()
393
+
394
+
395
+ # Factory functions with connection optimization
396
+ def create_controller(fallback_com_port: str = "", debug: bool = False,
397
+ send_init: bool = True, auto_reconnect: bool = True) -> MakcuController:
398
+ """Create and connect a controller"""
399
+ makcu = MakcuController(
400
+ fallback_com_port,
401
+ debug=debug,
402
+ send_init=send_init,
403
+ auto_reconnect=auto_reconnect
404
+ )
405
+ makcu.connect()
406
+ return makcu
407
+
408
+
409
+ async def create_async_controller(fallback_com_port: str = "", debug: bool = False,
410
+ send_init: bool = True, auto_reconnect: bool = True,
411
+ override_port: bool = False) -> MakcuController:
412
+ """Create and connect a controller asynchronously"""
413
+ makcu = MakcuController(
414
+ fallback_com_port,
415
+ debug=debug,
416
+ send_init=send_init,
417
+ auto_reconnect=auto_reconnect,
418
+ override_port=override_port
419
+ )
420
+ await makcu.async_connect()
421
+ return makcu
makcu/makcu.pyi ADDED
@@ -0,0 +1,13 @@
1
+ from typing import Optional, Dict, Callable, List, Union
2
+ from .controller import MakcuController
3
+ from .enums import MouseButton
4
+ from .errors import MakcuError, MakcuConnectionError, MakcuCommandError, MakcuTimeoutError, MakcuResponseError
5
+
6
+ __version__: str
7
+ __all__: List[str]
8
+
9
+ def create_controller(
10
+ fallback_com_port: str = "",
11
+ debug: bool = False,
12
+ send_init: bool = True
13
+ ) -> MakcuController: ...