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/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, Any
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
- # Pre-computed lock mappings for O(1) access
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
- # Cache connection state to avoid repeated checks
36
+
38
37
  self._connected = False
39
38
 
40
- # Connection management
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 # Silently ignore for performance
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
- # 1ms delay for double click recognition
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
- # Advanced movement - unchanged but with connection check optimization
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
- # Optimized lock API
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
- # Direct lock methods for backward compatibility
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
- # Device operations
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
- # Button monitoring - direct transport access for speed
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
- # Connection management
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
- # Human-like interactions with optimized timing
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
- # Pre-computed timing profiles (in milliseconds)
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), # New gaming profile
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
- # Move to start
298
+
332
299
  self.move(start_x, start_y)
333
- time.sleep(0.02) # Reduced from 0.05
300
+ time.sleep(0.02)
334
301
 
335
- # Press button
302
+
336
303
  self.press(button)
337
- time.sleep(0.02) # Reduced from 0.05
304
+ time.sleep(0.02)
338
305
 
339
- # Move to end with smooth motion
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
- # Release button
344
- time.sleep(0.02) # Reduced from 0.05
310
+
311
+ time.sleep(0.02)
345
312
  self.release(button)
346
313
 
347
- # Context manager support
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
- # Async methods for forward compatibility
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
- # Async context manager
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
- # Factory functions with connection optimization
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 Optional, Dict, Callable, List, Union
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, Optional, List, Union, Any
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
- # Pre-computed command strings for faster access
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
- # Pre-formatted commands cache
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 # Bitwise cache
31
+ self._lock_states_cache: int = 0
35
32
  self._cache_valid = False
36
33
 
37
- # Pre-compute all commands for zero-overhead execution
34
+
38
35
  self._init_command_cache()
39
36
 
40
37
  def _init_command_cache(self) -> None:
41
- """Pre-compute all commands to avoid string formatting during execution"""
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
- # Lock commands with bitwise indexing
48
- # Bit 0-4: buttons, Bit 5: X axis, Bit 6: Y axis
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
- # Use pre-computed commands
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
- # Use pre-computed commands for maximum speed
81
+
91
82
  press_cmd = self._PRESS_COMMANDS[button]
92
83
  release_cmd = self._RELEASE_COMMANDS[button]
93
84
 
94
- # Send both commands in rapid succession
95
- transport = self.transport # Cache reference
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
- # Optimized lock methods using bitwise cache
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
- # Update cache
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 # Found our port, exit immediately
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
- """Get all lock states with parallel queries for gaming performance"""
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
- # Query all states in rapid succession
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
- # Update cache
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
- # Check cache first
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
- # Query device
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
- # Update cache
241
+
262
242
  if is_locked:
263
243
  self._lock_states_cache |= (1 << bit)
264
244
  else: