makcu 0.1.1__tar.gz → 0.1.3__tar.gz

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.
Files changed (36) hide show
  1. makcu-0.1.3/MANIFEST.in +7 -0
  2. {makcu-0.1.1 → makcu-0.1.3}/PKG-INFO +7 -12
  3. {makcu-0.1.1 → makcu-0.1.3}/README.md +7 -12
  4. {makcu-0.1.1 → makcu-0.1.3}/makcu/__init__.py +2 -2
  5. {makcu-0.1.1 → makcu-0.1.3}/makcu/__main__.py +26 -9
  6. makcu-0.1.3/makcu/conftest.py +22 -0
  7. {makcu-0.1.1 → makcu-0.1.3}/makcu/connection.py +47 -53
  8. {makcu-0.1.1 → makcu-0.1.3}/makcu/controller.py +36 -57
  9. {makcu-0.1.1 → makcu-0.1.3}/makcu/mouse.py +14 -32
  10. makcu-0.1.3/makcu/test_suite.py +100 -0
  11. {makcu-0.1.1 → makcu-0.1.3}/makcu.egg-info/PKG-INFO +7 -12
  12. makcu-0.1.3/makcu.egg-info/SOURCES.txt +18 -0
  13. {makcu-0.1.1 → makcu-0.1.3}/pyproject.toml +6 -2
  14. makcu-0.1.3/pytest.ini +4 -0
  15. makcu-0.1.1/makcu/utils.py +0 -7
  16. makcu-0.1.1/makcu.egg-info/SOURCES.txt +0 -29
  17. makcu-0.1.1/tests/test_button_mask.py +0 -7
  18. makcu-0.1.1/tests/test_capture_right_clicks.py +0 -21
  19. makcu-0.1.1/tests/test_controller_behavior.py +0 -7
  20. makcu-0.1.1/tests/test_device_info.py +0 -8
  21. makcu-0.1.1/tests/test_firmware_version.py +0 -7
  22. makcu-0.1.1/tests/test_get_button_states.py +0 -16
  23. makcu-0.1.1/tests/test_get_raw_mask.py +0 -14
  24. makcu-0.1.1/tests/test_is_button_pressed.py +0 -13
  25. makcu-0.1.1/tests/test_lock_state.py +0 -22
  26. makcu-0.1.1/tests/test_middle_click.py +0 -6
  27. makcu-0.1.1/tests/test_port_connection.py +0 -4
  28. makcu-0.1.1/tests/test_press_and_release.py +0 -14
  29. makcu-0.1.1/tests/test_reset_all.py +0 -20
  30. makcu-0.1.1/tests/test_set_fallback_port.py +0 -3
  31. {makcu-0.1.1 → makcu-0.1.3}/LICENSE +0 -0
  32. {makcu-0.1.1 → makcu-0.1.3}/makcu/enums.py +0 -0
  33. {makcu-0.1.1 → makcu-0.1.3}/makcu/errors.py +0 -0
  34. {makcu-0.1.1 → makcu-0.1.3}/makcu.egg-info/dependency_links.txt +0 -0
  35. {makcu-0.1.1 → makcu-0.1.3}/makcu.egg-info/top_level.txt +0 -0
  36. {makcu-0.1.1 → makcu-0.1.3}/setup.cfg +0 -0
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include pytest.ini
5
+
6
+ recursive-include makcu *.py
7
+ prune makcu/tests
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makcu
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Python library to interact with Makcu devices.
5
5
  Author: SleepyTotem
6
6
  License: GPL
@@ -65,7 +65,7 @@ python -m makcu --runtest
65
65
  ```python
66
66
  from makcu import create_controller, MouseButton
67
67
 
68
- makcu = create_controller()
68
+ makcu = create_controller("COM1") # Fallback port
69
69
  makcu.click(MouseButton.LEFT)
70
70
  makcu.move(100, 50)
71
71
  makcu.scroll(-1)
@@ -85,7 +85,7 @@ makcu = create_controller(debug=True, send_init=True)
85
85
  #### Set fallback port manually
86
86
 
87
87
  ```python
88
- makcu.set_port("COM4") # use before create_controller()
88
+ makcu = create_controller("COM4") # Optional fallback com port
89
89
  ```
90
90
 
91
91
  ---
@@ -189,17 +189,9 @@ def on_button_event(button, pressed):
189
189
  makcu.set_button_callback(on_button_event)
190
190
  ```
191
191
 
192
- ### Configure Debounce
193
-
194
- ```python
195
- makcu.set_callback_debounce_time(100) # milliseconds
196
- ```
197
-
198
192
  ---
199
193
 
200
- ## Click Capturing
201
-
202
- ### ❌ **Currently broken (Pending Firmware Update)**
194
+ ## Click Capturing (Pending Firmware Update)
203
195
 
204
196
  Click capturing will allow you to detect and count click events in software.
205
197
 
@@ -248,6 +240,8 @@ if makcu.is_button_pressed(MouseButton.RIGHT):
248
240
  ### Send raw serial commands
249
241
 
250
242
  ```python
243
+ from makcu import create_controller
244
+ makcu = create_controller()
251
245
  response = makcu.transport.send_command("km.version()", expect_response=True)
252
246
  print(response)
253
247
  ```
@@ -313,3 +307,4 @@ Please open an issue on the project repository and I will get to it asap
313
307
  ## 🌐 Links
314
308
 
315
309
  - 🔗 [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
310
+ - 🔗 [PyPI Homepage](https://pypi.org/project/makcu/)
@@ -53,7 +53,7 @@ python -m makcu --runtest
53
53
  ```python
54
54
  from makcu import create_controller, MouseButton
55
55
 
56
- makcu = create_controller()
56
+ makcu = create_controller("COM1") # Fallback port
57
57
  makcu.click(MouseButton.LEFT)
58
58
  makcu.move(100, 50)
59
59
  makcu.scroll(-1)
@@ -73,7 +73,7 @@ makcu = create_controller(debug=True, send_init=True)
73
73
  #### Set fallback port manually
74
74
 
75
75
  ```python
76
- makcu.set_port("COM4") # use before create_controller()
76
+ makcu = create_controller("COM4") # Optional fallback com port
77
77
  ```
78
78
 
79
79
  ---
@@ -177,17 +177,9 @@ def on_button_event(button, pressed):
177
177
  makcu.set_button_callback(on_button_event)
178
178
  ```
179
179
 
180
- ### Configure Debounce
181
-
182
- ```python
183
- makcu.set_callback_debounce_time(100) # milliseconds
184
- ```
185
-
186
180
  ---
187
181
 
188
- ## Click Capturing
189
-
190
- ### ❌ **Currently broken (Pending Firmware Update)**
182
+ ## Click Capturing (Pending Firmware Update)
191
183
 
192
184
  Click capturing will allow you to detect and count click events in software.
193
185
 
@@ -236,6 +228,8 @@ if makcu.is_button_pressed(MouseButton.RIGHT):
236
228
  ### Send raw serial commands
237
229
 
238
230
  ```python
231
+ from makcu import create_controller
232
+ makcu = create_controller()
239
233
  response = makcu.transport.send_command("km.version()", expect_response=True)
240
234
  print(response)
241
235
  ```
@@ -300,4 +294,5 @@ Please open an issue on the project repository and I will get to it asap
300
294
 
301
295
  ## 🌐 Links
302
296
 
303
- - 🔗 [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
297
+ - 🔗 [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
298
+ - 🔗 [PyPI Homepage](https://pypi.org/project/makcu/)
@@ -2,8 +2,8 @@ from .controller import MakcuController
2
2
  from .enums import MouseButton
3
3
  from .errors import MakcuError, MakcuConnectionError
4
4
 
5
- def create_controller(debug=False, send_init=True):
6
- makcu = MakcuController(debug=debug, send_init=send_init)
5
+ def create_controller(fallback_com_port="", debug=False, send_init=True):
6
+ makcu = MakcuController(fallback_com_port, debug=debug, send_init=send_init)
7
7
  makcu.connect()
8
8
  return makcu
9
9
 
@@ -1,9 +1,8 @@
1
- # makcu/__main__.py
2
-
3
1
  import sys
4
- import subprocess
5
2
  import webbrowser
6
3
  import os
4
+ from pathlib import Path
5
+ import pytest
7
6
  from makcu import create_controller, MakcuConnectionError
8
7
 
9
8
  def debug_console():
@@ -34,7 +33,7 @@ def debug_console():
34
33
  def test_port(port):
35
34
  try:
36
35
  print(f"Trying to connect to {port} (without init command)...")
37
- controller = create_controller(send_init=False)
36
+ controller = create_controller(fallback_com_port=port, send_init=False)
38
37
  print(f"✅ Successfully connected to {port}")
39
38
  controller.disconnect()
40
39
  except MakcuConnectionError as e:
@@ -44,15 +43,33 @@ def test_port(port):
44
43
 
45
44
  def run_tests():
46
45
  print("🧪 Running Pytest Suite...")
47
- subprocess.run([
48
- sys.executable, "-m", "pytest",
49
- "--html=latest_pytest.html", "--self-contained-html"
46
+
47
+ package_dir = Path(__file__).resolve().parent
48
+ test_file = package_dir / "test_suite.py"
49
+
50
+ result = pytest.main([
51
+ str(test_file),
52
+ "--rootdir", str(package_dir),
53
+ "-v", "--tb=short",
54
+ "--capture=tee-sys",
55
+ "--html=latest_pytest.html",
56
+ "--self-contained-html"
50
57
  ])
51
58
 
52
59
  report_path = os.path.abspath("latest_pytest.html")
53
- print(f"📄 Opening test report: {report_path}")
54
- webbrowser.open(f"file://{report_path}")
60
+ if os.path.exists(report_path):
61
+ print(f"📄 Opening test report: {report_path}")
62
+ webbrowser.open(f"file://{report_path}")
63
+ else:
64
+ print("❌ Report not found. Something went wrong.")
65
+
66
+ if result != 0:
67
+ print("❌ Some tests failed.")
68
+ else:
69
+ print("✅ All tests passed.")
55
70
 
71
+ sys.exit(result)
72
+
56
73
  def main():
57
74
  args = sys.argv[1:]
58
75
 
@@ -0,0 +1,22 @@
1
+ import pytest
2
+ from makcu import create_controller
3
+ import time
4
+
5
+ @pytest.fixture(scope="session")
6
+ def makcu():
7
+ ctrl = create_controller()
8
+ yield ctrl
9
+ ctrl.disconnect()
10
+ time.sleep(0.2)
11
+
12
+ @pytest.fixture(autouse=True)
13
+ def ensure_clean_exit(makcu):
14
+ yield
15
+ makcu.mouse.lock_left(False)
16
+ makcu.mouse.lock_right(False)
17
+ makcu.mouse.lock_middle(False)
18
+ makcu.mouse.lock_side1(False)
19
+ makcu.mouse.lock_side2(False)
20
+ makcu.mouse.lock_x(False)
21
+ makcu.mouse.lock_y(False)
22
+ makcu.enable_button_monitoring(False)
@@ -6,7 +6,6 @@ from .errors import MakcuConnectionError, MakcuTimeoutError
6
6
  from .enums import MouseButton
7
7
 
8
8
  class SerialTransport:
9
- global fallback_com_port
10
9
  baud_change_command = bytearray([0xDE, 0xAD, 0x05, 0x00, 0xA5, 0x00, 0x09, 0x3D, 0x00])
11
10
 
12
11
  button_map = {
@@ -17,7 +16,8 @@ class SerialTransport:
17
16
  4: 'mouse5'
18
17
  }
19
18
 
20
- def __init__(self, debug=False, send_init=True):
19
+ def __init__(self, fallback, debug=False, send_init=True):
20
+ self._fallback_com_port = fallback
21
21
  self._log_messages = []
22
22
  self.debug = debug
23
23
  self.send_init = send_init
@@ -28,7 +28,6 @@ class SerialTransport:
28
28
  self._stop_event = threading.Event()
29
29
  self._listener_thread = None
30
30
  self._button_states = {btn: False for btn in self.button_map.values()}
31
- self._debounce_time_ms = 50
32
31
  self._last_callback_time = {bit: 0 for bit in self.button_map}
33
32
  self._pause_listener = False
34
33
 
@@ -48,6 +47,7 @@ class SerialTransport:
48
47
  self.serial = None
49
48
  self._current_baud = None
50
49
 
50
+
51
51
  def receive_response(self, max_bytes=1024, max_lines=3, sent_command: str = "") -> str:
52
52
  lines = []
53
53
  try:
@@ -69,9 +69,6 @@ class SerialTransport:
69
69
  lines.remove(command_clean)
70
70
  return "\n".join(lines)
71
71
 
72
- def set_debounce_time(self, ms: int):
73
- self._debounce_time_ms = ms
74
-
75
72
  def set_button_callback(self, callback):
76
73
  self._button_callback = callback
77
74
 
@@ -84,22 +81,20 @@ class SerialTransport:
84
81
  print(entry, flush=True)
85
82
 
86
83
  def find_com_port(self):
87
- global fallback_com_port
88
84
  self._log("Searching for CH343 device...")
89
85
 
90
86
  for port in list_ports.comports():
91
- if "USB-Enhanced-SERIAL CH343" in port.description:
87
+ if "VID:PID=1A86:55D3" in port.hwid.upper():
92
88
  self._log(f"Device found: {port.device}")
93
89
  return port.device
94
90
 
95
- if fallback_com_port and "COM" in fallback_com_port:
96
- self._log(f"CH343 not found. Falling back to specified port: {fallback_com_port}")
97
- return fallback_com_port
91
+ if self._fallback_com_port:
92
+ self._log(f"Device not found. Falling back to specified port: {self._fallback_com_port}")
93
+ return self._fallback_com_port
98
94
  else:
99
- self._log("Fallback port is not valid.")
95
+ self._log("Fallback port not specified or invalid.")
100
96
  return None
101
97
 
102
-
103
98
  def _open_serial_port(self, port, baud_rate):
104
99
  try:
105
100
  self._log(f"Trying to open {port} at {baud_rate} baud.")
@@ -113,18 +108,14 @@ class SerialTransport:
113
108
  self._log("Sending baud rate switch command to 4M.")
114
109
  self.serial.write(self.baud_change_command)
115
110
  self.serial.flush()
116
- port = self.serial.name
117
- self.serial.close()
118
- time.sleep(0.1)
119
- self.serial = self._open_serial_port(port, 4000000)
120
- if self.serial:
121
- self._current_baud = 4000000
122
- self._log("Switched to 4M baud successfully.")
123
- return True
124
- else:
125
- self._log("Failed to reopen port at 4M baud.")
111
+ time.sleep(0.05)
112
+ self.serial.baudrate = 4000000
113
+ self._current_baud = 4000000
114
+ self._log("Switched to 4M baud successfully.")
115
+ return True
126
116
  return False
127
117
 
118
+
128
119
  def connect(self):
129
120
  if self._is_connected:
130
121
  self._log("Already connected.")
@@ -162,7 +153,6 @@ class SerialTransport:
162
153
  return self._is_connected
163
154
 
164
155
  def send_command(self, command, expect_response=False):
165
- time.sleep(0.06)
166
156
  if not self._is_connected or not self.serial or not self.serial.is_open:
167
157
  raise MakcuConnectionError("Serial connection not open.")
168
158
  with self._lock:
@@ -179,15 +169,13 @@ class SerialTransport:
179
169
  finally:
180
170
  self._pause_listener = False
181
171
 
172
+
182
173
  def get_button_states(self):
183
174
  return dict(self._button_states)
184
175
 
185
176
  def get_button_mask(self) -> int:
186
- mask = 0
187
- for i, name in self.button_map.items():
188
- if self._button_states.get(name, False):
189
- mask |= (1 << i)
190
- return mask
177
+ return self._last_mask
178
+
191
179
 
192
180
  def enable_button_monitoring(self, enable: bool = True):
193
181
  self.send_command("km.buttons(1)" if enable else "km.buttons(0)")
@@ -224,54 +212,60 @@ class SerialTransport:
224
212
 
225
213
  def _listen(self, debug=False):
226
214
  self._log("Started listener thread")
227
- last_value = None
228
215
  button_states = {i: False for i in self.button_map}
229
216
  self._last_mask = 0
217
+ self._last_callback_time = {bit: 0 for bit in self.button_map}
230
218
 
231
219
  while self._is_connected and not self._stop_event.is_set():
232
220
  if self._pause_listener:
233
221
  time.sleep(0.001)
234
222
  continue
223
+
235
224
  try:
236
225
  byte = self.serial.read(1)
237
- if byte:
238
- value = byte[0]
239
- mask = 0
240
- for bit, name in self.button_map.items():
241
- is_pressed = bool(value & (1 << bit))
242
- if is_pressed != button_states[bit]:
243
- button_states[bit] = is_pressed
244
- if is_pressed:
245
- mask |= (1 << bit)
226
+ if not byte:
227
+ continue
228
+
229
+ value = byte[0]
230
+ byte_str = str(byte)
231
+
232
+ if not byte_str.startswith("b'\\x"):
233
+ continue
246
234
 
247
- for bit, name in self.button_map.items():
248
- self._button_states[name] = button_states[bit]
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
+ if debug:
248
+ print(f"{name} -> {is_pressed}")
249
249
 
250
- now = time.time() * 1000
251
- if self._button_callback and mask != self._last_mask:
250
+ if self._button_callback:
252
251
  for bit, name in self.button_map.items():
253
252
  previous = bool(self._last_mask & (1 << bit))
254
- current = bool(mask & (1 << bit))
253
+ current = bool(value & (1 << bit))
255
254
  if previous != current:
256
- last_time = self._last_callback_time.get(bit, 0)
257
- if now - last_time >= self._debounce_time_ms:
258
- button_enum = self._button_enum_map.get(bit)
259
- if button_enum:
260
- self._button_callback(button_enum, current)
261
- self._last_callback_time[bit] = now
255
+ button_enum = self._button_enum_map.get(bit)
256
+ if button_enum:
257
+ self._button_callback(button_enum, current)
262
258
 
263
- self._last_mask = mask
259
+ self._last_mask = value
264
260
 
265
261
  if debug:
266
262
  pressed = [name for bit, name in self.button_map.items() if button_states[bit]]
267
263
  button_str = ", ".join(pressed) if pressed else "No buttons pressed"
268
264
  self._log(f"Byte: {value} (0x{value:02X}) -> {button_str}")
269
265
 
270
- last_value = value
271
266
  except serial.SerialException as e:
272
267
  if "ClearCommError failed" not in str(e):
273
268
  self._log(f"Serial error during listening: {e}")
274
269
  break
275
- time.sleep(0.0001)
276
270
 
277
271
  self._log("Listener thread exiting")
@@ -6,8 +6,8 @@ from .errors import MakcuConnectionError
6
6
  from .enums import MouseButton
7
7
 
8
8
  class MakcuController:
9
- def __init__(self, debug=False, send_init=True):
10
- self.transport = SerialTransport(debug=debug, send_init=send_init)
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)
11
11
  self.mouse = Mouse(self.transport)
12
12
 
13
13
  def connect(self):
@@ -25,8 +25,8 @@ class MakcuController:
25
25
 
26
26
  def click(self, button: MouseButton):
27
27
  self._check_connection()
28
- self.mouse.press(button)
29
- self.mouse.release(button)
28
+ self._send_button_command(button, 1)
29
+ self._send_button_command(button, 0)
30
30
 
31
31
  def move(self, dx: int, dy: int):
32
32
  self._check_connection()
@@ -72,6 +72,14 @@ class MakcuController:
72
72
  self._check_connection()
73
73
  self.mouse.lock_side2(lock)
74
74
 
75
+ def lock_x(self, lock: bool):
76
+ self._check_connection()
77
+ self.mouse.lock_x(lock)
78
+
79
+ def lock_y(self, lock: bool):
80
+ self._check_connection()
81
+ self.mouse.lock_y(lock)
82
+
75
83
  def spoof_serial(self, serial: str):
76
84
  self._check_connection()
77
85
  self.mouse.spoof_serial(serial)
@@ -92,21 +100,22 @@ class MakcuController:
92
100
  self._check_connection()
93
101
  return self.transport.get_button_mask()
94
102
 
95
- def is_locked(self, target: str) -> bool:
96
- self._check_connection()
97
- return self.mouse.is_locked(target)
98
-
99
103
  def is_button_locked(self, button: MouseButton) -> bool:
100
104
  self._check_connection()
101
105
  return self.mouse.is_button_locked(button)
102
106
 
103
- def capture(self, button: MouseButton):
104
- self._check_connection()
105
- self.mouse.begin_capture(button.name)
107
+ #def capture(self, button: MouseButton):
108
+ # self._check_connection()
109
+ # self.mouse.begin_capture(button.name)
106
110
 
107
- def get_captured_clicks(self, button: MouseButton) -> int:
108
- self._check_connection()
109
- return self.mouse.stop_capturing_clicks(button.name)
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)
110
119
 
111
120
 
112
121
  def click_human_like(self, button: MouseButton, count: int = 1,
@@ -114,15 +123,15 @@ class MakcuController:
114
123
  self._check_connection()
115
124
 
116
125
  timing_profiles = {
117
- "normal": {"min_down": 60, "max_down": 120, "min_wait": 100, "max_wait": 180},
118
- "fast": {"min_down": 30, "max_down": 60, "min_wait": 50, "max_wait": 100},
119
- "slow": {"min_down": 100, "max_down": 180, "min_wait": 150, "max_wait": 300},
126
+ "normal": (60, 120, 100, 180),
127
+ "fast": (30, 60, 50, 100),
128
+ "slow": (100, 180, 150, 300),
120
129
  }
121
130
 
122
131
  if profile not in timing_profiles:
123
132
  raise ValueError(f"Invalid profile: {profile}. Choose from {list(timing_profiles.keys())}")
124
133
 
125
- t = timing_profiles[profile]
134
+ min_down, max_down, min_wait, max_wait = timing_profiles[profile]
126
135
 
127
136
  for _ in range(count):
128
137
  if jitter > 0:
@@ -130,10 +139,10 @@ class MakcuController:
130
139
  dy = random.randint(-jitter, jitter)
131
140
  self.mouse.move(dx, dy)
132
141
 
133
- self.mouse.press(button)
134
- time.sleep(random.uniform(t["min_down"], t["max_down"]) / 1000.0)
142
+ self.press(button)
143
+ time.sleep(random.uniform(min_down, max_down) / 1000.0)
135
144
  self.mouse.release(button)
136
- time.sleep(random.uniform(t["min_wait"], t["max_wait"]) / 1000.0)
145
+ time.sleep(random.uniform(min_wait, max_wait) / 1000.0)
137
146
 
138
147
  def enable_button_monitoring(self, enable: bool = True):
139
148
  self._check_connection()
@@ -147,51 +156,21 @@ class MakcuController:
147
156
  self._check_connection()
148
157
  return self.mouse.get_all_lock_states()
149
158
 
150
- def set_callback_debounce_time(self, ms: int):
151
- self._check_connection()
152
- self.transport.set_debounce_time(ms)
153
-
154
- def start_capturing_clicks(self, button: str):
155
- self._check_connection()
156
- self.transport._capture_counts[button] = 0
157
- self.transport._capture_active[button] = True
158
- self.transport._capture_last_state[button] = None
159
- self.mouse.begin_capture(button)
160
-
161
- def stop_capturing_clicks(self, button: str) -> int:
162
- self._check_connection()
163
- self.transport._capture_active[button] = False
164
- return self.mouse.stop_capturing_clicks(button)
159
+ def _send_button_command(self, button: MouseButton, state: int):
160
+ self.mouse._send_button_command(button, state)
165
161
 
166
162
  def press(self, button: MouseButton):
167
163
  self._check_connection()
168
- self.mouse.press(button)
164
+ self._send_button_command(button, 1)
169
165
 
170
166
  def release(self, button: MouseButton):
171
167
  self._check_connection()
172
- self.mouse.release(button)
173
-
174
- def set_port(self, port: str):
175
- import makcu.connection
176
- makcu.connection.fallback_com_port = port
177
-
168
+ self._send_button_command(button, 0)
169
+
178
170
  def get_button_states(self) -> dict:
179
171
  self._check_connection()
180
172
  return self.transport.get_button_states()
181
173
 
182
174
  def is_button_pressed(self, button: MouseButton) -> bool:
183
175
  self._check_connection()
184
- return self.transport.get_button_states().get(button.name.lower(), False)
185
-
186
- def get_raw_mask(self) -> int:
187
- self._check_connection()
188
- return self.transport.get_button_mask()
189
-
190
- def get_virtual_bit_mapping(self) -> dict:
191
- return {
192
- "LEFT": 0,
193
- "RIGHT": 1,
194
- "MIDDLE": 2,
195
- "MOUSE4": 3,
196
- "MOUSE5": 4
197
- }
176
+ return self.transport.get_button_states().get(button.name.lower(), False)
@@ -6,27 +6,17 @@ class Mouse:
6
6
  def __init__(self, transport):
7
7
  self.transport = transport
8
8
 
9
- def press(self, button: MouseButton):
10
- cmd = {
11
- MouseButton.LEFT: "km.left(1)",
12
- MouseButton.RIGHT: "km.right(1)",
13
- MouseButton.MIDDLE: "km.middle(1)"
14
- }.get(button)
15
- if cmd:
16
- self.transport.send_command(cmd)
17
- else:
18
- raise MakcuCommandError(f"Unsupported button for press(): {button}")
19
-
20
- def release(self, button: MouseButton):
21
- cmd = {
22
- MouseButton.LEFT: "km.left(0)",
23
- MouseButton.RIGHT: "km.right(0)",
24
- MouseButton.MIDDLE: "km.middle(0)"
25
- }.get(button)
26
- if cmd:
27
- self.transport.send_command(cmd)
28
- else:
29
- raise MakcuCommandError(f"Unsupported button for release(): {button}")
9
+ def _send_button_command(self, button: MouseButton, state: int):
10
+ command_map = {
11
+ MouseButton.LEFT: "left",
12
+ MouseButton.RIGHT: "right",
13
+ MouseButton.MIDDLE: "middle",
14
+ MouseButton.MOUSE4: "ms1",
15
+ MouseButton.MOUSE5: "ms2",
16
+ }
17
+ if button not in command_map:
18
+ raise MakcuCommandError(f"Unsupported button: {button}")
19
+ self.transport.send_command(f"km.{command_map[button]}({state})")
30
20
 
31
21
  def move(self, x: int, y: int):
32
22
  self.transport.send_command(f"km.move({x},{y})")
@@ -40,7 +30,9 @@ class Mouse:
40
30
  def scroll(self, delta: int):
41
31
  self.transport.send_command(f"km.wheel({delta})")
42
32
 
43
- def lock_left(self, lock: bool): self.transport.send_command(f"km.lock_ml({int(lock)})")
33
+ def lock_left(self, lock: bool):
34
+ self.transport.send_command(f"km.lock_ml({int(lock)})")
35
+ print(f"km.lock_ml({int(lock)})")
44
36
  def lock_middle(self, lock: bool): self.transport.send_command(f"km.lock_mm({int(lock)})")
45
37
  def lock_right(self, lock: bool): self.transport.send_command(f"km.lock_mr({int(lock)})")
46
38
  def lock_side1(self, lock: bool): self.transport.send_command(f"km.lock_ms1({int(lock)})")
@@ -117,16 +109,6 @@ class Mouse:
117
109
  """
118
110
  return self.transport.read_captured_clicks(button)
119
111
 
120
- def get_captured_clicks_enum(self, button: MouseButton) -> int:
121
- mapping = {
122
- MouseButton.LEFT: "LEFT",
123
- MouseButton.RIGHT: "RIGHT",
124
- MouseButton.MIDDLE: "MIDDLE",
125
- MouseButton.MOUSE4: "MOUSE4",
126
- MouseButton.MOUSE5: "MOUSE5",
127
- }
128
- return self.stop_capturing_clicks(mapping[button])
129
-
130
112
  def get_all_lock_states(self) -> dict:
131
113
  return {
132
114
  target: self.is_locked(target)
@@ -0,0 +1,100 @@
1
+ import pytest, time
2
+ from makcu import MouseButton
3
+
4
+ def test_press_and_release(makcu):
5
+ makcu.press(MouseButton.LEFT)
6
+ makcu.release(MouseButton.LEFT)
7
+
8
+ def test_firmware_version(makcu):
9
+ version = makcu.mouse.get_firmware_version()
10
+ assert version and len(version.strip()) > 0
11
+
12
+ def test_middle_click(makcu):
13
+ makcu.press(MouseButton.MIDDLE)
14
+ makcu.release(MouseButton.MIDDLE)
15
+
16
+ def test_device_info(makcu):
17
+ print("Fetching device info...")
18
+ info = makcu.mouse.get_device_info()
19
+ print(f"Device Info: {info}")
20
+ assert info.get("port")
21
+ assert info.get("isConnected") is True
22
+
23
+ def test_port_connection(makcu):
24
+ assert makcu.is_connected()
25
+
26
+ @pytest.mark.skip(reason="Capture test disabled until firmware supports tracking clicks from software input")
27
+ def test_capture_right_clicks(makcu):
28
+ makcu.mouse.lock_right(True)
29
+ assert makcu.mouse.is_button_locked(MouseButton.RIGHT)
30
+
31
+ makcu.mouse.begin_capture("RIGHT")
32
+ makcu.press(MouseButton.RIGHT)
33
+ makcu.mouse.release(MouseButton.RIGHT)
34
+ makcu.press(MouseButton.RIGHT)
35
+ makcu.mouse.release(MouseButton.RIGHT)
36
+
37
+ makcu.mouse.lock_right(False)
38
+ assert not makcu.mouse.is_button_locked(MouseButton.RIGHT)
39
+
40
+ count = makcu.mouse.stop_capturing_clicks("RIGHT")
41
+ assert count >= 2, f"Expected >=2 captured clicks, got {count}"
42
+
43
+ def test_button_mask(makcu):
44
+ print("Getting button mask...")
45
+ mask = makcu.get_button_mask()
46
+ print(f"Mask value: {mask}")
47
+ assert isinstance(mask, int)
48
+
49
+ def test_get_button_states(makcu):
50
+ states = makcu.get_button_states()
51
+ assert isinstance(states, dict)
52
+ for key in ['left', 'right', 'middle', 'mouse4', 'mouse5']:
53
+ assert key in states
54
+
55
+ def test_lock_state(makcu):
56
+ print("Locking LEFT button...")
57
+ makcu.lock_left(True)
58
+
59
+ time.sleep(0.1)
60
+
61
+ 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)
81
+
82
+ def test_makcu_behavior(makcu):
83
+ makcu.move(25, 25)
84
+ makcu.click(MouseButton.LEFT)
85
+ makcu.scroll(-2)
86
+
87
+ def test_reset_all(makcu):
88
+ makcu.mouse.lock_left(False)
89
+ makcu.mouse.lock_right(False)
90
+ makcu.mouse.lock_middle(False)
91
+ makcu.mouse.lock_side1(False)
92
+ makcu.mouse.lock_side2(False)
93
+ makcu.mouse.lock_x(False)
94
+ makcu.mouse.lock_y(False)
95
+
96
+ states = makcu.mouse.get_all_lock_states()
97
+ assert all(state is False for state in states.values() if state is not None), \
98
+ f"Expected all unlocked, got: {states}"
99
+
100
+ makcu.enable_button_monitoring(False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: makcu
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Python library to interact with Makcu devices.
5
5
  Author: SleepyTotem
6
6
  License: GPL
@@ -65,7 +65,7 @@ python -m makcu --runtest
65
65
  ```python
66
66
  from makcu import create_controller, MouseButton
67
67
 
68
- makcu = create_controller()
68
+ makcu = create_controller("COM1") # Fallback port
69
69
  makcu.click(MouseButton.LEFT)
70
70
  makcu.move(100, 50)
71
71
  makcu.scroll(-1)
@@ -85,7 +85,7 @@ makcu = create_controller(debug=True, send_init=True)
85
85
  #### Set fallback port manually
86
86
 
87
87
  ```python
88
- makcu.set_port("COM4") # use before create_controller()
88
+ makcu = create_controller("COM4") # Optional fallback com port
89
89
  ```
90
90
 
91
91
  ---
@@ -189,17 +189,9 @@ def on_button_event(button, pressed):
189
189
  makcu.set_button_callback(on_button_event)
190
190
  ```
191
191
 
192
- ### Configure Debounce
193
-
194
- ```python
195
- makcu.set_callback_debounce_time(100) # milliseconds
196
- ```
197
-
198
192
  ---
199
193
 
200
- ## Click Capturing
201
-
202
- ### ❌ **Currently broken (Pending Firmware Update)**
194
+ ## Click Capturing (Pending Firmware Update)
203
195
 
204
196
  Click capturing will allow you to detect and count click events in software.
205
197
 
@@ -248,6 +240,8 @@ if makcu.is_button_pressed(MouseButton.RIGHT):
248
240
  ### Send raw serial commands
249
241
 
250
242
  ```python
243
+ from makcu import create_controller
244
+ makcu = create_controller()
251
245
  response = makcu.transport.send_command("km.version()", expect_response=True)
252
246
  print(response)
253
247
  ```
@@ -313,3 +307,4 @@ Please open an issue on the project repository and I will get to it asap
313
307
  ## 🌐 Links
314
308
 
315
309
  - 🔗 [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
310
+ - 🔗 [PyPI Homepage](https://pypi.org/project/makcu/)
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ pytest.ini
6
+ makcu/__init__.py
7
+ makcu/__main__.py
8
+ makcu/conftest.py
9
+ makcu/connection.py
10
+ makcu/controller.py
11
+ makcu/enums.py
12
+ makcu/errors.py
13
+ makcu/mouse.py
14
+ makcu/test_suite.py
15
+ makcu.egg-info/PKG-INFO
16
+ makcu.egg-info/SOURCES.txt
17
+ makcu.egg-info/dependency_links.txt
18
+ makcu.egg-info/top_level.txt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "makcu"
3
- version = "0.1.1"
3
+ version = "0.1.3"
4
4
  description = "Python library to interact with Makcu devices."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.7"
@@ -14,7 +14,11 @@ license = { text = "GPL" }
14
14
 
15
15
  [tool.setuptools]
16
16
  packages = ["makcu"]
17
+ include-package-data = true
17
18
 
18
19
  [build-system]
19
20
  requires = ["setuptools>=61.0"]
20
- build-backend = "setuptools.build_meta"
21
+ build-backend = "setuptools.build_meta"
22
+
23
+ [tool.setuptools.data-files]
24
+ "." = ["tests/*.py"]
makcu-0.1.3/pytest.ini ADDED
@@ -0,0 +1,4 @@
1
+ [pytest]
2
+ python_files = test_suite.py
3
+ testpaths = makcu
4
+ addopts = -v --tb=short --capture=tee-sys --html=latest_pytest.html --self-contained-html
@@ -1,7 +0,0 @@
1
- from serial.tools import list_ports
2
-
3
- def find_makcu_port():
4
- for port in list_ports.comports():
5
- if "CH343" in port.description:
6
- return port.device
7
- return None
@@ -1,29 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- makcu/__init__.py
5
- makcu/__main__.py
6
- makcu/connection.py
7
- makcu/controller.py
8
- makcu/enums.py
9
- makcu/errors.py
10
- makcu/mouse.py
11
- makcu/utils.py
12
- makcu.egg-info/PKG-INFO
13
- makcu.egg-info/SOURCES.txt
14
- makcu.egg-info/dependency_links.txt
15
- makcu.egg-info/top_level.txt
16
- tests/test_button_mask.py
17
- tests/test_capture_right_clicks.py
18
- tests/test_controller_behavior.py
19
- tests/test_device_info.py
20
- tests/test_firmware_version.py
21
- tests/test_get_button_states.py
22
- tests/test_get_raw_mask.py
23
- tests/test_is_button_pressed.py
24
- tests/test_lock_state.py
25
- tests/test_middle_click.py
26
- tests/test_port_connection.py
27
- tests/test_press_and_release.py
28
- tests/test_reset_all.py
29
- tests/test_set_fallback_port.py
@@ -1,7 +0,0 @@
1
- # test_button_mask.py
2
-
3
- def test_button_mask(makcu):
4
- print("Getting button mask...")
5
- mask = makcu.get_button_mask()
6
- print(f"Mask value: {mask}")
7
- assert isinstance(mask, int)
@@ -1,21 +0,0 @@
1
- # test_capture_right_clicks.py
2
- import pytest
3
- from makcu import MouseButton
4
-
5
- @pytest.mark.skip(reason="Capture test disabled until firmware supports tracking clicks from software input")
6
- def test_capture_right_clicks(makcu):
7
- makcu.mouse.lock_right(True)
8
- assert makcu.mouse.is_button_locked(MouseButton.RIGHT)
9
-
10
- makcu.mouse.begin_capture("RIGHT")
11
-
12
- makcu.mouse.press(MouseButton.RIGHT)
13
- makcu.mouse.release(MouseButton.RIGHT)
14
- makcu.mouse.press(MouseButton.RIGHT)
15
- makcu.mouse.release(MouseButton.RIGHT)
16
-
17
- makcu.mouse.lock_right(False)
18
- assert not makcu.mouse.is_button_locked(MouseButton.RIGHT)
19
-
20
- count = makcu.mouse.stop_capturing_clicks("RIGHT")
21
- assert count >= 2, f"Expected >=2 captured clicks, got {count}"
@@ -1,7 +0,0 @@
1
- # test_makcu_behavior.py
2
- from makcu import MouseButton
3
-
4
- def test_makcu_behavior(makcu):
5
- makcu.move(25, 25)
6
- makcu.click(MouseButton.LEFT)
7
- makcu.scroll(-2)
@@ -1,8 +0,0 @@
1
- # tests/test_device_info.py
2
-
3
- def test_device_info(makcu):
4
- print("Fetching device info...")
5
- info = makcu.mouse.get_device_info()
6
- print(f"Device Info: {info}")
7
- assert info.get("port")
8
- assert info.get("isConnected") is True
@@ -1,7 +0,0 @@
1
- # tests/test_firmware_version.py
2
-
3
- def test_firmware_version(makcu):
4
- print("Getting firmware version...")
5
- version = makcu.mouse.get_firmware_version()
6
- print(f"Firmware version: {version}")
7
- assert version and len(version.strip()) > 0
@@ -1,16 +0,0 @@
1
- # test_get_button_states.py
2
-
3
- import pytest
4
- from makcu import create_controller
5
-
6
- @pytest.fixture(scope="module")
7
- def makcu():
8
- ctrl = create_controller()
9
- yield ctrl
10
- ctrl.disconnect()
11
-
12
- def test_get_button_states(makcu):
13
- states = makcu.get_button_states()
14
- assert isinstance(states, dict)
15
- for key in ['left', 'right', 'middle', 'mouse4', 'mouse5']:
16
- assert key in states
@@ -1,14 +0,0 @@
1
- # test_get_raw_mask.py
2
-
3
- import pytest
4
- from makcu import create_controller
5
-
6
- @pytest.fixture(scope="module")
7
- def makcu():
8
- ctrl = create_controller()
9
- yield ctrl
10
- ctrl.disconnect()
11
-
12
- def test_get_raw_mask(makcu):
13
- mask = makcu.get_raw_mask()
14
- assert isinstance(mask, int)
@@ -1,13 +0,0 @@
1
- # test_is_button_pressed.py
2
-
3
- import pytest
4
- from makcu import create_controller, MouseButton
5
-
6
- @pytest.fixture(scope="module")
7
- def makcu():
8
- ctrl = create_controller()
9
- yield ctrl
10
- ctrl.disconnect()
11
-
12
- def test_is_button_pressed(makcu):
13
- assert makcu.is_button_pressed(MouseButton.LEFT) in [True, False]
@@ -1,22 +0,0 @@
1
- # tests/test_lock_state.py
2
- from makcu import MouseButton
3
-
4
- def test_lock_state(makcu):
5
- print("Locking LEFT button...")
6
- makcu.lock_left(True)
7
-
8
- print("Querying lock state while LEFT is locked...")
9
- assert makcu.is_button_locked(MouseButton.LEFT)
10
-
11
- print("Querying all lock states...")
12
- all_states = makcu.get_all_lock_states()
13
- print(f"All lock states: {all_states}")
14
-
15
- assert all_states["LEFT"] is True
16
- assert isinstance(all_states["RIGHT"], bool)
17
-
18
- print("Unlocking LEFT button...")
19
- makcu.lock_left(False)
20
-
21
- print("Rechecking LEFT lock state after unlock...")
22
- assert not makcu.is_button_locked(MouseButton.LEFT)
@@ -1,6 +0,0 @@
1
- # test_middle_click.py
2
- from makcu import MouseButton
3
-
4
- def test_middle_click(makcu):
5
- makcu.mouse.press(MouseButton.MIDDLE)
6
- makcu.mouse.release(MouseButton.MIDDLE)
@@ -1,4 +0,0 @@
1
- # test_port_connection.py
2
-
3
- def test_port_connection(makcu):
4
- assert makcu.is_connected()
@@ -1,14 +0,0 @@
1
- # test_press_and_release.py
2
-
3
- import pytest
4
- from makcu import create_controller, MouseButton
5
-
6
- @pytest.fixture(scope="module")
7
- def makcu():
8
- ctrl = create_controller()
9
- yield ctrl
10
- ctrl.disconnect()
11
-
12
- def test_press_and_release(makcu):
13
- makcu.press(MouseButton.LEFT)
14
- makcu.release(MouseButton.LEFT)
@@ -1,20 +0,0 @@
1
- # test_reset_all.py
2
-
3
- def test_reset_all(makcu):
4
- # Unlock all buttons
5
- makcu.mouse.lock_left(False)
6
- makcu.mouse.lock_right(False)
7
- makcu.mouse.lock_middle(False)
8
- makcu.mouse.lock_side1(False)
9
- makcu.mouse.lock_side2(False)
10
- makcu.mouse.lock_x(False)
11
- makcu.mouse.lock_y(False)
12
-
13
- # Check lock states to confirm everything is clean
14
- states = makcu.mouse.get_all_lock_states()
15
-
16
- assert all(state is False for state in states.values() if state is not None), \
17
- f"Expected all unlocked, got: {states}"
18
-
19
- # Optionally disable monitoring
20
- makcu.enable_button_monitoring(False)
@@ -1,3 +0,0 @@
1
- def test_set_fallback_port(makcu):
2
- makcu.set_port("COM5")
3
- assert True
File without changes
File without changes
File without changes
File without changes