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