makcu 0.1.3__py3-none-any.whl → 0.1.4__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/connection.py CHANGED
@@ -29,7 +29,12 @@ class SerialTransport:
29
29
  self._listener_thread = None
30
30
  self._button_states = {btn: False for btn in self.button_map.values()}
31
31
  self._last_callback_time = {bit: 0 for bit in self.button_map}
32
- self._pause_listener = False
32
+
33
+ self._response_buffer = ""
34
+ self._response_ready = threading.Event()
35
+ self._waiting_for_response = False
36
+ self._response_timeout = 0.01
37
+ self._command_lock = threading.Lock()
33
38
 
34
39
  self._button_enum_map = {
35
40
  0: MouseButton.LEFT,
@@ -47,28 +52,45 @@ class SerialTransport:
47
52
  self.serial = None
48
53
  self._current_baud = None
49
54
 
50
-
51
- def receive_response(self, max_bytes=1024, max_lines=3, sent_command: str = "") -> str:
52
- lines = []
55
+ def receive_response(self, sent_command: str = "") -> str:
53
56
  try:
54
- for _ in range(max_lines):
55
- line = self.serial.readline(max_bytes)
56
- if not line:
57
- break
58
- decoded = line.decode(errors="ignore").strip()
59
- if decoded:
60
- lines.append(decoded)
57
+ if self._response_ready.wait(timeout=self._response_timeout):
58
+ response = self._response_buffer
59
+ self._response_buffer = ""
60
+ self._response_ready.clear()
61
+
62
+ lines = response.strip().split('\n')
63
+ lines = [line.strip() for line in lines if line.strip()]
64
+
65
+ command_clean = sent_command.strip()
66
+ cleaned_lines = []
67
+
68
+ for line in lines:
69
+ if not line:
70
+ continue
71
+ if line == command_clean:
72
+ continue
73
+ if line.startswith('>>> '):
74
+ actual_response = line[4:].strip()
75
+ if actual_response and actual_response != command_clean:
76
+ cleaned_lines.append(actual_response)
77
+ continue
78
+ if line == command_clean:
79
+ continue
80
+ cleaned_lines.append(line)
81
+
82
+ result = "\n".join(cleaned_lines)
83
+ if self.debug:
84
+ self._log(f"Command: {command_clean} -> Response: '{result}'")
85
+ return result
86
+ else:
87
+ return ""
88
+
61
89
  except Exception as e:
62
- print(f"[RECV ERROR] {e}")
90
+ if self.debug:
91
+ self._log(f"Error in receive_response: {e}")
63
92
  return ""
64
93
 
65
- command_clean = sent_command.strip()
66
- if lines:
67
- lines.pop(-1)
68
- if command_clean in lines and len(lines) > 1:
69
- lines.remove(command_clean)
70
- return "\n".join(lines)
71
-
72
94
  def set_button_callback(self, callback):
73
95
  self._button_callback = callback
74
96
 
@@ -115,7 +137,6 @@ class SerialTransport:
115
137
  return True
116
138
  return False
117
139
 
118
-
119
140
  def connect(self):
120
141
  if self._is_connected:
121
142
  self._log("Already connected.")
@@ -155,20 +176,27 @@ class SerialTransport:
155
176
  def send_command(self, command, expect_response=False):
156
177
  if not self._is_connected or not self.serial or not self.serial.is_open:
157
178
  raise MakcuConnectionError("Serial connection not open.")
158
- with self._lock:
179
+
180
+ with self._command_lock:
159
181
  try:
160
- self._pause_listener = True
161
- self.serial.reset_input_buffer()
182
+ if expect_response:
183
+ self._response_buffer = ""
184
+ self._response_ready.clear()
185
+ self._waiting_for_response = True
186
+
162
187
  self.serial.write(command.encode("ascii") + b"\r\n")
163
188
  self.serial.flush()
189
+
164
190
  if expect_response:
165
191
  response = self.receive_response(sent_command=command)
192
+ self._waiting_for_response = False
166
193
  if not response:
167
194
  raise MakcuTimeoutError(f"No response from device for command: {command}")
168
195
  return response
169
- finally:
170
- self._pause_listener = False
171
-
196
+
197
+ except Exception as e:
198
+ self._waiting_for_response = False
199
+ raise
172
200
 
173
201
  def get_button_states(self):
174
202
  return dict(self._button_states)
@@ -176,11 +204,10 @@ class SerialTransport:
176
204
  def get_button_mask(self) -> int:
177
205
  return self._last_mask
178
206
 
179
-
180
207
  def enable_button_monitoring(self, enable: bool = True):
181
208
  self.send_command("km.buttons(1)" if enable else "km.buttons(0)")
182
209
 
183
- def catch_button(self, button: str):
210
+ def catch_button(self, button: MouseButton):
184
211
  command = {
185
212
  "LEFT": "km.catch_ml(0)",
186
213
  "RIGHT": "km.catch_mr(0)",
@@ -193,79 +220,71 @@ class SerialTransport:
193
220
  else:
194
221
  raise ValueError(f"Unsupported button: {button}")
195
222
 
196
- def read_captured_clicks(self, button: str) -> int:
197
- command = {
198
- "LEFT": "km.catch_ml()",
199
- "RIGHT": "km.catch_mr()",
200
- "MIDDLE": "km.catch_mm()",
201
- "MOUSE4": "km.catch_ms1()",
202
- "MOUSE5": "km.catch_ms2()",
203
- }.get(button.upper())
204
- if command:
205
- result = self.send_command(command, expect_response=True)
206
- try:
207
- return int(result.strip())
208
- except Exception:
209
- return 0
210
- else:
211
- raise ValueError(f"Unsupported button: {button}")
223
+ def _is_button_data(self, byte_value):
224
+ return byte_value <= 0b11111 and byte_value not in [0x0D, 0x0A]
225
+
226
+ def _is_ascii_data(self, byte_value):
227
+ return 32 <= byte_value <= 126 or byte_value in [0x0D, 0x0A] # Include CR/LF
212
228
 
213
229
  def _listen(self, debug=False):
214
230
  self._log("Started listener thread")
215
- button_states = {i: False for i in self.button_map}
216
231
  self._last_mask = 0
217
- self._last_callback_time = {bit: 0 for bit in self.button_map}
232
+ ascii_buffer = bytearray()
233
+ response_lines = []
218
234
 
219
235
  while self._is_connected and not self._stop_event.is_set():
220
- if self._pause_listener:
221
- time.sleep(0.001)
222
- continue
223
-
224
236
  try:
225
- byte = self.serial.read(1)
226
- if not byte:
237
+ data = self.serial.read(self.serial.in_waiting or 1)
238
+ if not data:
227
239
  continue
228
240
 
229
- value = byte[0]
230
- byte_str = str(byte)
231
-
232
- if not byte_str.startswith("b'\\x"):
233
- continue
241
+ for byte_val in data:
242
+ if (self._is_button_data(byte_val) and
243
+ not self._waiting_for_response):
244
+ if byte_val != self._last_mask:
245
+ changed_bits = byte_val ^ self._last_mask
246
+ for bit, name in self.button_map.items():
247
+ if changed_bits & (1 << bit):
248
+ is_pressed = bool(byte_val & (1 << bit))
249
+ self._button_states[name] = is_pressed
250
+ button_enum = self._button_enum_map.get(bit)
251
+ if button_enum and self._button_callback:
252
+ self._button_callback(button_enum, is_pressed)
253
+
254
+ self._last_mask = byte_val
234
255
 
235
- if value != self._last_mask:
236
- if byte_str.startswith("b'\\x00"):
237
- for bit, name in self.button_map.items():
238
- button_states[bit] = False
239
- self._button_states[name] = False
240
- if debug:
241
- print(f"{name} -> False")
242
- else:
243
- for bit, name in self.button_map.items():
244
- is_pressed = bool(value & (1 << bit))
245
- button_states[bit] = is_pressed
246
- self._button_states[name] = is_pressed
247
256
  if debug:
248
- print(f"{name} -> {is_pressed}")
249
-
250
- if self._button_callback:
251
- for bit, name in self.button_map.items():
252
- previous = bool(self._last_mask & (1 << bit))
253
- current = bool(value & (1 << bit))
254
- if previous != current:
255
- button_enum = self._button_enum_map.get(bit)
256
- if button_enum:
257
- self._button_callback(button_enum, current)
258
-
259
- self._last_mask = value
260
-
261
- if debug:
262
- pressed = [name for bit, name in self.button_map.items() if button_states[bit]]
263
- button_str = ", ".join(pressed) if pressed else "No buttons pressed"
264
- self._log(f"Byte: {value} (0x{value:02X}) -> {button_str}")
257
+ pressed = [name for _, name in self.button_map.items() if self._button_states[name]]
258
+ button_str = ", ".join(pressed) if pressed else "No buttons pressed"
259
+ self._log(f"Mask: 0x{byte_val:02X} -> {button_str}")
260
+ elif self._is_ascii_data(byte_val):
261
+ if self._waiting_for_response:
262
+ ascii_buffer.append(byte_val)
263
+ if byte_val == 0x0A:
264
+ try:
265
+ line = ascii_buffer.decode('ascii', errors='ignore').strip()
266
+ ascii_buffer.clear()
267
+
268
+ if line:
269
+ response_lines.append(line)
270
+
271
+ if (len(response_lines) >= 2 or
272
+ (len(response_lines) == 1 and not line.startswith('>>> '))):
273
+
274
+ full_response = '\n'.join(response_lines)
275
+ self._response_buffer = full_response
276
+ self._response_ready.set()
277
+ response_lines.clear()
278
+
279
+ except Exception as e:
280
+ self._log(f"Error decoding ASCII response: {e}")
281
+ ascii_buffer.clear()
282
+ response_lines.clear()
265
283
 
266
284
  except serial.SerialException as e:
267
285
  if "ClearCommError failed" not in str(e):
268
286
  self._log(f"Serial error during listening: {e}")
269
287
  break
270
-
288
+ except Exception as e:
289
+ self._log(f"Unexpected error in listener: {e}")
271
290
  self._log("Listener thread exiting")
makcu/controller.py CHANGED
@@ -25,8 +25,8 @@ class MakcuController:
25
25
 
26
26
  def click(self, button: MouseButton):
27
27
  self._check_connection()
28
- self._send_button_command(button, 1)
29
- self._send_button_command(button, 0)
28
+ self.mouse.press(button)
29
+ self.mouse.release(button)
30
30
 
31
31
  def move(self, dx: int, dy: int):
32
32
  self._check_connection()
@@ -100,23 +100,9 @@ class MakcuController:
100
100
  self._check_connection()
101
101
  return self.transport.get_button_mask()
102
102
 
103
- def is_button_locked(self, button: MouseButton) -> bool:
103
+ def is_locked(self, button: MouseButton) -> bool:
104
104
  self._check_connection()
105
- return self.mouse.is_button_locked(button)
106
-
107
- #def capture(self, button: MouseButton):
108
- # self._check_connection()
109
- # self.mouse.begin_capture(button.name)
110
-
111
-
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)
119
-
105
+ return self.mouse.is_locked(button)
120
106
 
121
107
  def click_human_like(self, button: MouseButton, count: int = 1,
122
108
  profile: str = "normal", jitter: int = 0):
@@ -139,11 +125,11 @@ class MakcuController:
139
125
  dy = random.randint(-jitter, jitter)
140
126
  self.mouse.move(dx, dy)
141
127
 
142
- self.press(button)
128
+ self.mouse.press(button)
143
129
  time.sleep(random.uniform(min_down, max_down) / 1000.0)
144
130
  self.mouse.release(button)
145
131
  time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
146
-
132
+
147
133
  def enable_button_monitoring(self, enable: bool = True):
148
134
  self._check_connection()
149
135
  self.transport.enable_button_monitoring(enable)
@@ -155,22 +141,19 @@ class MakcuController:
155
141
  def get_all_lock_states(self) -> dict:
156
142
  self._check_connection()
157
143
  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
144
 
162
145
  def press(self, button: MouseButton):
163
146
  self._check_connection()
164
- self._send_button_command(button, 1)
147
+ self.mouse.press(button)
165
148
 
166
149
  def release(self, button: MouseButton):
167
150
  self._check_connection()
168
- self._send_button_command(button, 0)
169
-
151
+ self.mouse.release(button)
152
+
170
153
  def get_button_states(self) -> dict:
171
154
  self._check_connection()
172
155
  return self.transport.get_button_states()
173
156
 
174
- def is_button_pressed(self, button: MouseButton) -> bool:
157
+ def is_pressed(self, button: MouseButton) -> bool:
175
158
  self._check_connection()
176
159
  return self.transport.get_button_states().get(button.name.lower(), False)
makcu/mouse.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from .enums import MouseButton
2
2
  from .errors import MakcuCommandError
3
3
  from serial.tools import list_ports
4
+ import time
4
5
 
5
6
  class Mouse:
6
7
  def __init__(self, transport):
@@ -18,6 +19,12 @@ class Mouse:
18
19
  raise MakcuCommandError(f"Unsupported button: {button}")
19
20
  self.transport.send_command(f"km.{command_map[button]}({state})")
20
21
 
22
+ def press(self, button: MouseButton):
23
+ self._send_button_command(button, 1)
24
+
25
+ def release(self, button: MouseButton):
26
+ self._send_button_command(button, 0)
27
+
21
28
  def move(self, x: int, y: int):
22
29
  self.transport.send_command(f"km.move({x},{y})")
23
30
 
@@ -30,18 +37,33 @@ class Mouse:
30
37
  def scroll(self, delta: int):
31
38
  self.transport.send_command(f"km.wheel({delta})")
32
39
 
33
- def lock_left(self, lock: bool):
40
+ def lock_left(self, lock: bool):
34
41
  self.transport.send_command(f"km.lock_ml({int(lock)})")
35
- print(f"km.lock_ml({int(lock)})")
36
- def lock_middle(self, lock: bool): self.transport.send_command(f"km.lock_mm({int(lock)})")
37
- def lock_right(self, lock: bool): self.transport.send_command(f"km.lock_mr({int(lock)})")
38
- def lock_side1(self, lock: bool): self.transport.send_command(f"km.lock_ms1({int(lock)})")
39
- def lock_side2(self, lock: bool): self.transport.send_command(f"km.lock_ms2({int(lock)})")
40
- def lock_x(self, lock: bool): self.transport.send_command(f"km.lock_mx({int(lock)})")
41
- def lock_y(self, lock: bool): self.transport.send_command(f"km.lock_my({int(lock)})")
42
+
43
+ def lock_middle(self, lock: bool):
44
+ self.transport.send_command(f"km.lock_mm({int(lock)})")
45
+
46
+ def lock_right(self, lock: bool):
47
+ self.transport.send_command(f"km.lock_mr({int(lock)})")
48
+
49
+ def lock_side1(self, lock: bool):
50
+ self.transport.send_command(f"km.lock_ms1({int(lock)})")
51
+
52
+ def lock_side2(self, lock: bool):
53
+ self.transport.send_command(f"km.lock_ms2({int(lock)})")
54
+
55
+ def lock_x(self, lock: bool):
56
+ self.transport.send_command(f"km.lock_mx({int(lock)})")
57
+
58
+ def lock_y(self, lock: bool):
59
+ self.transport.send_command(f"km.lock_my({int(lock)})")
60
+
42
61
 
43
- def spoof_serial(self, serial: str): self.transport.send_command(f"km.serial('{serial}')")
44
- def reset_serial(self): self.transport.send_command("km.serial(0)")
62
+ def spoof_serial(self, serial: str):
63
+ self.transport.send_command(f"km.serial('{serial}')")
64
+
65
+ def reset_serial(self):
66
+ self.transport.send_command("km.serial(0)")
45
67
 
46
68
  def get_device_info(self) -> dict:
47
69
  port_name = self.transport.port
@@ -66,51 +88,80 @@ class Mouse:
66
88
  def get_firmware_version(self) -> str:
67
89
  return self.transport.send_command("km.version()", expect_response=True)
68
90
 
69
- def is_locked(self, target: str) -> bool:
70
- target = target.upper()
71
- commands = {
72
- "X": "km.lock_mx()",
73
- "Y": "km.lock_my()",
74
- "LEFT": "km.lock_ml()",
75
- "MIDDLE": "km.lock_mm()",
76
- "RIGHT": "km.lock_mr()",
77
- "MOUSE4": "km.lock_ms1()",
78
- "MOUSE5": "km.lock_ms2()",
91
+ def _get_lock_command(self, target: str) -> str:
92
+ command_map = {
93
+ "X": "mx", "Y": "my",
94
+ "LEFT": "ml", "RIGHT": "mr", "MIDDLE": "mm",
95
+ "MOUSE4": "ms1", "MOUSE5": "ms2",
79
96
  }
80
- if target not in commands:
97
+ key = target.upper()
98
+ if key not in command_map:
81
99
  raise ValueError(f"Unsupported lock target: {target}")
100
+ return f"km.lock_{command_map[key]}()"
101
+
102
+ def is_locked(self, button: MouseButton) -> bool:
82
103
  try:
83
- result = self.transport.send_command(commands[target], expect_response=True)
84
- return result.strip() == "1"
85
- except Exception:
86
- return False
104
+ # Sleep for previous command to finish first, hoping to get rid of this soon.
105
+ time.sleep(0.03)
87
106
 
88
- def is_button_locked(self, button: MouseButton) -> bool:
89
- name_map = {
90
- MouseButton.LEFT: "LEFT",
91
- MouseButton.RIGHT: "RIGHT",
92
- MouseButton.MIDDLE: "MIDDLE",
93
- MouseButton.MOUSE4: "MOUSE4",
94
- MouseButton.MOUSE5: "MOUSE5"
95
- }
96
- return self.is_locked(name_map[button])
97
-
98
- def begin_capture(self, button: str):
99
- """
100
- Assumes lock_<button>(1) has already been called.
101
- Sends catch_<button>(0) to begin capturing click cycles.
102
- """
103
- self.transport.catch_button(button)
104
-
105
- def stop_capturing_clicks(self, button: str) -> int:
106
- """
107
- Assumes lock_<button>(0) has already been called.
108
- Returns the total number of clicks since begin_capture.
109
- """
110
- return self.transport.read_captured_clicks(button)
107
+ command = self._get_lock_command(button.name)
108
+ result = self.transport.send_command(command, expect_response=True)
109
+ if hasattr(self.transport, 'debug') and self.transport.debug:
110
+ print(f"Lock status command: {command}")
111
+ print(f"Raw result: '{result}'")
112
+
113
+ result = result.strip()
114
+
115
+ if result in ['1', '0']:
116
+ return result == '1'
117
+
118
+ lines = result.split('\n')
119
+ for line in lines:
120
+ line = line.strip()
121
+ if line in ['1', '0']:
122
+ return line == '1'
123
+
124
+ import re
125
+ numbers = re.findall(r'\b[01]\b', result)
126
+ if numbers:
127
+ return numbers[-1] == '1'
128
+
129
+ if hasattr(self.transport, 'debug') and self.transport.debug:
130
+ print(f"Could not parse lock status from: '{result}'")
131
+
132
+ return False
133
+
134
+ except Exception as e:
135
+ if hasattr(self.transport, 'debug') and self.transport.debug:
136
+ print(f"Error checking lock status: {e}")
137
+ return False
111
138
 
112
139
  def get_all_lock_states(self) -> dict:
113
- return {
114
- target: self.is_locked(target)
115
- for target in ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
116
- }
140
+ """Get all lock states - no timing delays needed with acknowledgment"""
141
+ targets = ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
142
+ state = {}
143
+
144
+ for target in targets:
145
+ try:
146
+ # No sleep needed - each command waits for acknowledgment
147
+ if hasattr(MouseButton, target):
148
+ state[target] = self.is_locked(MouseButton[target])
149
+ else:
150
+ # Handle X and Y axis locks
151
+ if target in ["X", "Y"]:
152
+ # Create a mock enum-like object for axis locks
153
+ class AxisButton:
154
+ def __init__(self, name):
155
+ self.name = name
156
+ state[target] = self.is_locked(AxisButton(target))
157
+ else:
158
+ state[target] = False
159
+ except Exception as e:
160
+ if hasattr(self.transport, 'debug') and self.transport.debug:
161
+ print(f"Error getting lock state for {target}: {e}")
162
+ state[target] = False
163
+
164
+ if hasattr(self.transport, 'debug') and self.transport.debug:
165
+ print(f"All lock states: {state}")
166
+
167
+ return state
makcu/test_suite.py CHANGED
@@ -26,7 +26,7 @@ def test_port_connection(makcu):
26
26
  @pytest.mark.skip(reason="Capture test disabled until firmware supports tracking clicks from software input")
27
27
  def test_capture_right_clicks(makcu):
28
28
  makcu.mouse.lock_right(True)
29
- assert makcu.mouse.is_button_locked(MouseButton.RIGHT)
29
+ assert makcu.mouse.is_locked(MouseButton.RIGHT)
30
30
 
31
31
  makcu.mouse.begin_capture("RIGHT")
32
32
  makcu.press(MouseButton.RIGHT)
@@ -35,7 +35,7 @@ def test_capture_right_clicks(makcu):
35
35
  makcu.mouse.release(MouseButton.RIGHT)
36
36
 
37
37
  makcu.mouse.lock_right(False)
38
- assert not makcu.mouse.is_button_locked(MouseButton.RIGHT)
38
+ assert not makcu.mouse.is_locked(MouseButton.RIGHT)
39
39
 
40
40
  count = makcu.mouse.stop_capturing_clicks("RIGHT")
41
41
  assert count >= 2, f"Expected >=2 captured clicks, got {count}"
@@ -55,29 +55,10 @@ def test_get_button_states(makcu):
55
55
  def test_lock_state(makcu):
56
56
  print("Locking LEFT button...")
57
57
  makcu.lock_left(True)
58
-
59
- time.sleep(0.1)
60
-
61
58
  print("Querying lock state while LEFT is locked...")
62
- assert makcu.is_button_locked(MouseButton.LEFT)
63
-
64
- print("Querying all lock states...")
65
- all_states = makcu.get_all_lock_states()
66
- print(f"All lock states: {all_states}")
67
-
68
- assert all_states["LEFT"] is True
69
- assert isinstance(all_states["RIGHT"], bool)
70
-
71
- makcu.press(MouseButton.LEFT)
72
- makcu.release(MouseButton.LEFT)
73
-
74
- time.sleep(0.1)
75
-
76
- print("Unlocking LEFT button...")
77
- makcu.lock_left(False)
78
-
79
- print("Rechecking LEFT lock state after unlock...")
80
- assert not makcu.is_button_locked(MouseButton.LEFT)
59
+ state = makcu.is_locked(MouseButton.LEFT) # Check state AFTER ensuring it's locked
60
+ print(state)
61
+ assert state # Now assert the current state
81
62
 
82
63
  def test_makcu_behavior(makcu):
83
64
  makcu.move(25, 25)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makcu
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Python library to interact with Makcu devices.
5
5
  Author: SleepyTotem
6
6
  License: GPL
@@ -50,14 +50,6 @@ python -m makcu [command]
50
50
  | `--testPort COM3` | Tests a specific COM port for connectivity |
51
51
  | `--runtest` | Runs all automated tests and opens a test report |
52
52
 
53
- ### Examples
54
-
55
- ```bash
56
- python -m makcu --debug
57
- python -m makcu --testPort COM3
58
- python -m makcu --runtest
59
- ```
60
-
61
53
  ---
62
54
 
63
55
  ## 🧠 Quickstart (Python)
@@ -65,7 +57,7 @@ python -m makcu --runtest
65
57
  ```python
66
58
  from makcu import create_controller, MouseButton
67
59
 
68
- makcu = create_controller("COM1") # Fallback port
60
+ makcu = create_controller("COM1")
69
61
  makcu.click(MouseButton.LEFT)
70
62
  makcu.move(100, 50)
71
63
  makcu.scroll(-1)
@@ -79,20 +71,14 @@ makcu.disconnect()
79
71
  ### 🔧 Initialization
80
72
 
81
73
  ```python
82
- makcu = create_controller(debug=True, send_init=True)
83
- ```
84
-
85
- #### Set fallback port manually
86
-
87
- ```python
88
- makcu = create_controller("COM4") # Optional fallback com port
74
+ makcu = create_controller(fallback_com_port="COM1", debug=True, send_init=True)
89
75
  ```
90
76
 
91
77
  ---
92
78
 
93
79
  ### 🎮 Mouse Control
94
80
 
95
- #### Clicks
81
+ #### Button Actions
96
82
 
97
83
  ```python
98
84
  makcu.click(MouseButton.LEFT)
@@ -103,21 +89,21 @@ makcu.release(MouseButton.RIGHT)
103
89
  #### Movement
104
90
 
105
91
  ```python
106
- makcu.move(dx=30, dy=20)
92
+ makcu.move(30, 20)
107
93
  makcu.move_smooth(100, 40, segments=10)
108
94
  makcu.move_bezier(50, 50, 15, ctrl_x=25, ctrl_y=25)
109
95
  ```
110
96
 
111
- #### Scrolling
97
+ #### Scroll Wheel
112
98
 
113
99
  ```python
114
- makcu.scroll(-3) # Scroll down
115
- makcu.scroll(3) # Scroll up
100
+ makcu.scroll(-3)
101
+ makcu.scroll(3)
116
102
  ```
117
103
 
118
104
  ---
119
105
 
120
- ### 🔒 Locking and Unlocking
106
+ ### 🔒 Locking
121
107
 
122
108
  ```python
123
109
  makcu.lock_left(True)
@@ -129,10 +115,10 @@ makcu.lock_mouse_x(True)
129
115
  makcu.lock_mouse_y(False)
130
116
  ```
131
117
 
132
- #### Lock Status
118
+ #### Lock State Query
133
119
 
134
120
  ```python
135
- makcu.is_button_locked(MouseButton.LEFT)
121
+ makcu.is_locked(MouseButton.LEFT)
136
122
  makcu.get_all_lock_states()
137
123
  ```
138
124
 
@@ -144,7 +130,7 @@ makcu.get_all_lock_states()
144
130
  makcu.click_human_like(
145
131
  button=MouseButton.LEFT,
146
132
  count=5,
147
- profile="normal", # "fast", "slow" also available
133
+ profile="normal", # or "fast", "slow"
148
134
  jitter=3
149
135
  )
150
136
  ```
@@ -155,10 +141,7 @@ makcu.click_human_like(
155
141
 
156
142
  ```python
157
143
  info = makcu.get_device_info()
158
- print(info)
159
-
160
144
  version = makcu.get_firmware_version()
161
- print(version)
162
145
  ```
163
146
 
164
147
  ---
@@ -172,15 +155,15 @@ makcu.reset_serial()
172
155
 
173
156
  ---
174
157
 
175
- ## 🧪 Button Monitoring & Capture
158
+ ## 🧪 Button Monitoring
176
159
 
177
- ### Enable Real-time Monitoring
160
+ ### Enable Monitoring
178
161
 
179
162
  ```python
180
163
  makcu.enable_button_monitoring(True)
181
164
  ```
182
165
 
183
- ### Set Callback Function
166
+ ### Set Event Callback
184
167
 
185
168
  ```python
186
169
  def on_button_event(button, pressed):
@@ -191,66 +174,46 @@ makcu.set_button_callback(on_button_event)
191
174
 
192
175
  ---
193
176
 
194
- ## ❌ Click Capturing (Pending Firmware Update)
195
-
196
- Click capturing will allow you to detect and count click events in software.
177
+ ## ❌ Click Capturing (Pending Firmware Fix)
197
178
 
198
179
  ```python
199
180
  makcu.mouse.lock_right(True)
200
- makcu.capture(MouseButton.RIGHT)
181
+ makcu.mouse.begin_capture("RIGHT")
201
182
 
202
- # User clicks however many times
183
+ # Simulated user input...
203
184
 
204
185
  makcu.mouse.lock_right(False)
205
- count = makcu.get_captured_clicks(MouseButton.RIGHT)
186
+ count = makcu.mouse.stop_capturing_clicks("RIGHT")
206
187
  print(f"Captured clicks: {count}")
207
188
  ```
208
189
 
209
- > ⚠️ This feature is currently broken in firmware. Do not rely on it yet.
190
+ > ⚠️ Not fully supported yet firmware must be updated to complete this feature.
210
191
 
211
192
  ---
212
193
 
213
194
  ## 🔢 Bitmask & Button States
214
195
 
215
- ### Get Bitmask of Active Buttons
216
-
217
196
  ```python
218
197
  mask = makcu.get_button_mask()
219
- print(f"Button mask: {mask}")
220
- ```
221
-
222
- ### Get Raw Button State Map
223
-
224
- ```python
225
198
  states = makcu.get_button_states()
226
- print(states) # {'left': False, 'right': True, ...}
227
- ```
228
-
229
- ### Check if a Specific Button Is Pressed
230
199
 
231
- ```python
232
- if makcu.is_button_pressed(MouseButton.RIGHT):
233
- print("Right button is pressed")
200
+ if makcu.is_pressed(MouseButton.RIGHT):
201
+ print("Right button is currently pressed")
234
202
  ```
235
203
 
236
204
  ---
237
205
 
238
- ## ⚙️ Low-Level Command Access
239
-
240
- ### Send raw serial commands
206
+ ## ⚙️ Low-Level Access
241
207
 
242
208
  ```python
243
- from makcu import create_controller
244
- makcu = create_controller()
245
209
  response = makcu.transport.send_command("km.version()", expect_response=True)
246
- print(response)
247
210
  ```
248
211
 
249
212
  ---
250
213
 
251
214
  ## 🧪 Test Suite
252
215
 
253
- Run all tests and generate HTML report:
216
+ Run full test suite and generate an HTML report:
254
217
 
255
218
  ```bash
256
219
  python -m makcu --runtest
@@ -287,11 +250,11 @@ except MakcuConnectionError as e:
287
250
 
288
251
  ## 🛠️ Developer Notes
289
252
 
290
- - Uses CH343 USB Serial
291
- - Auto-connects to correct port or fallback
292
- - Supports baud rate switching to 4M
293
- - Automatically enables `km.buttons(1)` monitoring if `send_init=True`
294
- - Supports raw button state polling
253
+ - Communicates via CH343 USB serial
254
+ - Automatically finds correct port or uses fallback
255
+ - Switches baud to 4M after initial connect
256
+ - Enables `km.buttons(1)` on init if requested
257
+ - Supports full button state tracking with events
295
258
 
296
259
  ---
297
260
 
@@ -301,10 +264,11 @@ GPL License © SleepyTotem
301
264
 
302
265
  ---
303
266
 
304
- ## Support
305
- Please open an issue on the project repository and I will get to it asap
267
+ ## 🙋 Support
268
+
269
+ Open an issue on GitHub if you encounter bugs or need help.
306
270
 
307
271
  ## 🌐 Links
308
272
 
309
- - 🔗 [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
310
- - 🔗 [PyPI Homepage](https://pypi.org/project/makcu/)
273
+ - [GitHub Repo](https://github.com/SleepyTotem/makcu-py-lib)
274
+ - [PyPI Package](https://pypi.org/project/makcu/)
@@ -0,0 +1,14 @@
1
+ makcu/__init__.py,sha256=hCP6COi14T4C0V35crnbBEzJPa9hnwGb-gDPoxs_H6E,459
2
+ makcu/__main__.py,sha256=wjRtr7V6qd54w43lHmXQldlVffKMW27nkhKa4E5B9t8,2830
3
+ makcu/conftest.py,sha256=TQibb01_1OfzDrDU5u3IDlrfehXyr7E7jx3g0VySZmU,560
4
+ makcu/connection.py,sha256=NDo8cmAnsu_-Njc-CacLFTKXkALvSc7E2BXYOY5aljg,12030
5
+ makcu/controller.py,sha256=wBrGlO_mivd7YFRQJ84BVXGIuu3G7ChRos9fucTYWzM,5115
6
+ makcu/enums.py,sha256=VmvCLmpghVHuTAkvCGMfA14MgWTtFVMfsGQQNnJ58Ts,126
7
+ makcu/errors.py,sha256=4CkQ4gKa7GL5-BO3yOAJMMsy3QlUDDL42S1P1clqV4A,562
8
+ makcu/mouse.py,sha256=PkBowk--SSHo13gRwK3jnORSQVQV1YeIF4NWE_Cm4KU,6388
9
+ makcu/test_suite.py,sha256=qHYklwhVCeZbpndlwUrSGnd2a5wQtJwefjlo_ZWXD-Y,2661
10
+ makcu-0.1.4.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
11
+ makcu-0.1.4.dist-info/METADATA,sha256=tZCMI-5J3s4Sp_zAVlrvKiLbGCcFGDCbRkDzzlilQWs,4828
12
+ makcu-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ makcu-0.1.4.dist-info/top_level.txt,sha256=IRO1UVb5LK_ovjau0g4oObyXQqy00tVEE-yF5lPgw1w,6
14
+ makcu-0.1.4.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- makcu/__init__.py,sha256=hCP6COi14T4C0V35crnbBEzJPa9hnwGb-gDPoxs_H6E,459
2
- makcu/__main__.py,sha256=wjRtr7V6qd54w43lHmXQldlVffKMW27nkhKa4E5B9t8,2830
3
- makcu/conftest.py,sha256=TQibb01_1OfzDrDU5u3IDlrfehXyr7E7jx3g0VySZmU,560
4
- makcu/connection.py,sha256=QObUZ-iNH-LaC7hamgXDH5p2wH_VfJ6OEAv5qJ1RwVE,10208
5
- makcu/controller.py,sha256=u2BeScKqQI19nSXQzRZqNL9rIj7_AyGDRcawdHvQtxM,5776
6
- makcu/enums.py,sha256=VmvCLmpghVHuTAkvCGMfA14MgWTtFVMfsGQQNnJ58Ts,126
7
- makcu/errors.py,sha256=4CkQ4gKa7GL5-BO3yOAJMMsy3QlUDDL42S1P1clqV4A,562
8
- makcu/mouse.py,sha256=vvJ88r9tOLaGT6evHHx_K45wwTa_bxc9c0S6wj7gX6o,4686
9
- makcu/test_suite.py,sha256=kmsLRv00mWLu3cUW5iAWL3QAhdqOL-rUwAWn6Rs1_ME,3104
10
- makcu-0.1.3.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
11
- makcu-0.1.3.dist-info/METADATA,sha256=hIYmeM8Qd9IWNCEfE_tbZ4DHqe-ttU9uvE8247ih2w0,5588
12
- makcu-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- makcu-0.1.3.dist-info/top_level.txt,sha256=IRO1UVb5LK_ovjau0g4oObyXQqy00tVEE-yF5lPgw1w,6
14
- makcu-0.1.3.dist-info/RECORD,,
File without changes