makcu 0.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/__init__.py +16 -0
- makcu/__main__.py +80 -0
- makcu/connection.py +277 -0
- makcu/controller.py +197 -0
- makcu/enums.py +8 -0
- makcu/errors.py +19 -0
- makcu/mouse.py +134 -0
- makcu/utils.py +7 -0
- makcu-0.1.1.dist-info/METADATA +315 -0
- makcu-0.1.1.dist-info/RECORD +13 -0
- makcu-0.1.1.dist-info/WHEEL +5 -0
- makcu-0.1.1.dist-info/licenses/LICENSE +674 -0
- makcu-0.1.1.dist-info/top_level.txt +1 -0
makcu/__init__.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
from .controller import MakcuController
|
2
|
+
from .enums import MouseButton
|
3
|
+
from .errors import MakcuError, MakcuConnectionError
|
4
|
+
|
5
|
+
def create_controller(debug=False, send_init=True):
|
6
|
+
makcu = MakcuController(debug=debug, send_init=send_init)
|
7
|
+
makcu.connect()
|
8
|
+
return makcu
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"MakcuController",
|
12
|
+
"MouseButton",
|
13
|
+
"MakcuError",
|
14
|
+
"MakcuConnectionError",
|
15
|
+
"create_controller",
|
16
|
+
]
|
makcu/__main__.py
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# makcu/__main__.py
|
2
|
+
|
3
|
+
import sys
|
4
|
+
import subprocess
|
5
|
+
import webbrowser
|
6
|
+
import os
|
7
|
+
from makcu import create_controller, MakcuConnectionError
|
8
|
+
|
9
|
+
def debug_console():
|
10
|
+
controller = create_controller()
|
11
|
+
transport = controller.transport
|
12
|
+
|
13
|
+
print("๐ง Makcu Debug Console")
|
14
|
+
print("Type a raw command (e.g., km.version()) and press Enter.")
|
15
|
+
print("Type 'exit' or 'quit' to leave.")
|
16
|
+
|
17
|
+
while True:
|
18
|
+
try:
|
19
|
+
cmd = input(">>> ").strip()
|
20
|
+
if cmd.lower() in {"exit", "quit"}:
|
21
|
+
break
|
22
|
+
if not cmd:
|
23
|
+
continue
|
24
|
+
|
25
|
+
response = transport.send_command(cmd, expect_response=True)
|
26
|
+
print(f"{response or '(no response)'}")
|
27
|
+
|
28
|
+
except Exception as e:
|
29
|
+
print(f"โ ๏ธ Error: {e}")
|
30
|
+
|
31
|
+
controller.disconnect()
|
32
|
+
print("Disconnected.")
|
33
|
+
|
34
|
+
def test_port(port):
|
35
|
+
try:
|
36
|
+
print(f"Trying to connect to {port} (without init command)...")
|
37
|
+
controller = create_controller(send_init=False)
|
38
|
+
print(f"โ
Successfully connected to {port}")
|
39
|
+
controller.disconnect()
|
40
|
+
except MakcuConnectionError as e:
|
41
|
+
print(f"โ Failed to connect to {port}: {e}")
|
42
|
+
except Exception as e:
|
43
|
+
print(f"โ Unexpected error: {e}")
|
44
|
+
|
45
|
+
def run_tests():
|
46
|
+
print("๐งช Running Pytest Suite...")
|
47
|
+
subprocess.run([
|
48
|
+
sys.executable, "-m", "pytest",
|
49
|
+
"--html=latest_pytest.html", "--self-contained-html"
|
50
|
+
])
|
51
|
+
|
52
|
+
report_path = os.path.abspath("latest_pytest.html")
|
53
|
+
print(f"๐ Opening test report: {report_path}")
|
54
|
+
webbrowser.open(f"file://{report_path}")
|
55
|
+
|
56
|
+
def main():
|
57
|
+
args = sys.argv[1:]
|
58
|
+
|
59
|
+
if not args:
|
60
|
+
print("Usage:")
|
61
|
+
print(" python -m makcu --debug")
|
62
|
+
print(" python -m makcu --testPort COM3")
|
63
|
+
print(" python -m makcu --runtest")
|
64
|
+
return
|
65
|
+
|
66
|
+
if args[0] == "--debug":
|
67
|
+
debug_console()
|
68
|
+
elif args[0] == "--testPort" and len(args) == 2:
|
69
|
+
test_port(args[1])
|
70
|
+
elif args[0] == "--runtest":
|
71
|
+
run_tests()
|
72
|
+
else:
|
73
|
+
print(f"Unknown command: {' '.join(args)}")
|
74
|
+
print("Usage:")
|
75
|
+
print(" python -m makcu --debug")
|
76
|
+
print(" python -m makcu --testPort COM3")
|
77
|
+
print(" python -m makcu --runtest")
|
78
|
+
|
79
|
+
if __name__ == "__main__":
|
80
|
+
main()
|
makcu/connection.py
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
import serial
|
2
|
+
import threading
|
3
|
+
import time
|
4
|
+
from serial.tools import list_ports
|
5
|
+
from .errors import MakcuConnectionError, MakcuTimeoutError
|
6
|
+
from .enums import MouseButton
|
7
|
+
|
8
|
+
class SerialTransport:
|
9
|
+
global fallback_com_port
|
10
|
+
baud_change_command = bytearray([0xDE, 0xAD, 0x05, 0x00, 0xA5, 0x00, 0x09, 0x3D, 0x00])
|
11
|
+
|
12
|
+
button_map = {
|
13
|
+
0: 'left',
|
14
|
+
1: 'right',
|
15
|
+
2: 'middle',
|
16
|
+
3: 'mouse4',
|
17
|
+
4: 'mouse5'
|
18
|
+
}
|
19
|
+
|
20
|
+
def __init__(self, debug=False, send_init=True):
|
21
|
+
self._log_messages = []
|
22
|
+
self.debug = debug
|
23
|
+
self.send_init = send_init
|
24
|
+
self._button_callback = None
|
25
|
+
self._last_mask = 0
|
26
|
+
self._lock = threading.Lock()
|
27
|
+
self._is_connected = False
|
28
|
+
self._stop_event = threading.Event()
|
29
|
+
self._listener_thread = None
|
30
|
+
self._button_states = {btn: False for btn in self.button_map.values()}
|
31
|
+
self._debounce_time_ms = 50
|
32
|
+
self._last_callback_time = {bit: 0 for bit in self.button_map}
|
33
|
+
self._pause_listener = False
|
34
|
+
|
35
|
+
self._button_enum_map = {
|
36
|
+
0: MouseButton.LEFT,
|
37
|
+
1: MouseButton.RIGHT,
|
38
|
+
2: MouseButton.MIDDLE,
|
39
|
+
3: MouseButton.MOUSE4,
|
40
|
+
4: MouseButton.MOUSE5,
|
41
|
+
}
|
42
|
+
|
43
|
+
self.port = self.find_com_port()
|
44
|
+
if not self.port:
|
45
|
+
raise MakcuConnectionError("Makcu device not found. Please specify a port explicitly.")
|
46
|
+
|
47
|
+
self.baudrate = 115200
|
48
|
+
self.serial = None
|
49
|
+
self._current_baud = None
|
50
|
+
|
51
|
+
def receive_response(self, max_bytes=1024, max_lines=3, sent_command: str = "") -> str:
|
52
|
+
lines = []
|
53
|
+
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)
|
61
|
+
except Exception as e:
|
62
|
+
print(f"[RECV ERROR] {e}")
|
63
|
+
return ""
|
64
|
+
|
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
|
+
def set_debounce_time(self, ms: int):
|
73
|
+
self._debounce_time_ms = ms
|
74
|
+
|
75
|
+
def set_button_callback(self, callback):
|
76
|
+
self._button_callback = callback
|
77
|
+
|
78
|
+
def _log(self, message):
|
79
|
+
timestamp = time.strftime("%H:%M:%S")
|
80
|
+
entry = f"[{timestamp}] {message}"
|
81
|
+
self._log_messages.append(entry)
|
82
|
+
if len(self._log_messages) > 20:
|
83
|
+
self._log_messages.pop(0)
|
84
|
+
print(entry, flush=True)
|
85
|
+
|
86
|
+
def find_com_port(self):
|
87
|
+
global fallback_com_port
|
88
|
+
self._log("Searching for CH343 device...")
|
89
|
+
|
90
|
+
for port in list_ports.comports():
|
91
|
+
if "USB-Enhanced-SERIAL CH343" in port.description:
|
92
|
+
self._log(f"Device found: {port.device}")
|
93
|
+
return port.device
|
94
|
+
|
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
|
98
|
+
else:
|
99
|
+
self._log("Fallback port is not valid.")
|
100
|
+
return None
|
101
|
+
|
102
|
+
|
103
|
+
def _open_serial_port(self, port, baud_rate):
|
104
|
+
try:
|
105
|
+
self._log(f"Trying to open {port} at {baud_rate} baud.")
|
106
|
+
return serial.Serial(port, baud_rate, timeout=0.05)
|
107
|
+
except serial.SerialException:
|
108
|
+
self._log(f"Failed to open {port} at {baud_rate} baud.")
|
109
|
+
return None
|
110
|
+
|
111
|
+
def _change_baud_to_4M(self):
|
112
|
+
if self.serial and self.serial.is_open:
|
113
|
+
self._log("Sending baud rate switch command to 4M.")
|
114
|
+
self.serial.write(self.baud_change_command)
|
115
|
+
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.")
|
126
|
+
return False
|
127
|
+
|
128
|
+
def connect(self):
|
129
|
+
if self._is_connected:
|
130
|
+
self._log("Already connected.")
|
131
|
+
return
|
132
|
+
self.serial = self._open_serial_port(self.port, 115200)
|
133
|
+
if not self.serial:
|
134
|
+
raise MakcuConnectionError(f"Failed to connect to {self.port} at 115200.")
|
135
|
+
self._log(f"Connected to {self.port} at 115200.")
|
136
|
+
if not self._change_baud_to_4M():
|
137
|
+
raise MakcuConnectionError("Failed to switch to 4M baud.")
|
138
|
+
self._is_connected = True
|
139
|
+
if self.send_init:
|
140
|
+
with self._lock:
|
141
|
+
self.serial.write(b"km.buttons(1)\r")
|
142
|
+
self.serial.flush()
|
143
|
+
self._log("Sent init command: km.buttons(1)")
|
144
|
+
|
145
|
+
self._stop_event.clear()
|
146
|
+
self._listener_thread = threading.Thread(target=self._listen, kwargs={"debug": self.debug}, daemon=True)
|
147
|
+
self._listener_thread.start()
|
148
|
+
|
149
|
+
def disconnect(self):
|
150
|
+
if self.send_init:
|
151
|
+
self._stop_event.set()
|
152
|
+
if self._listener_thread:
|
153
|
+
self._listener_thread.join()
|
154
|
+
with self._lock:
|
155
|
+
if self.serial and self.serial.is_open:
|
156
|
+
self.serial.close()
|
157
|
+
self.serial = None
|
158
|
+
self._is_connected = False
|
159
|
+
self._log("Disconnected.")
|
160
|
+
|
161
|
+
def is_connected(self):
|
162
|
+
return self._is_connected
|
163
|
+
|
164
|
+
def send_command(self, command, expect_response=False):
|
165
|
+
time.sleep(0.06)
|
166
|
+
if not self._is_connected or not self.serial or not self.serial.is_open:
|
167
|
+
raise MakcuConnectionError("Serial connection not open.")
|
168
|
+
with self._lock:
|
169
|
+
try:
|
170
|
+
self._pause_listener = True
|
171
|
+
self.serial.reset_input_buffer()
|
172
|
+
self.serial.write(command.encode("ascii") + b"\r\n")
|
173
|
+
self.serial.flush()
|
174
|
+
if expect_response:
|
175
|
+
response = self.receive_response(sent_command=command)
|
176
|
+
if not response:
|
177
|
+
raise MakcuTimeoutError(f"No response from device for command: {command}")
|
178
|
+
return response
|
179
|
+
finally:
|
180
|
+
self._pause_listener = False
|
181
|
+
|
182
|
+
def get_button_states(self):
|
183
|
+
return dict(self._button_states)
|
184
|
+
|
185
|
+
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
|
191
|
+
|
192
|
+
def enable_button_monitoring(self, enable: bool = True):
|
193
|
+
self.send_command("km.buttons(1)" if enable else "km.buttons(0)")
|
194
|
+
|
195
|
+
def catch_button(self, button: str):
|
196
|
+
command = {
|
197
|
+
"LEFT": "km.catch_ml(0)",
|
198
|
+
"RIGHT": "km.catch_mr(0)",
|
199
|
+
"MIDDLE": "km.catch_mm(0)",
|
200
|
+
"MOUSE4": "km.catch_ms1(0)",
|
201
|
+
"MOUSE5": "km.catch_ms2(0)",
|
202
|
+
}.get(button.upper())
|
203
|
+
if command:
|
204
|
+
self.send_command(command)
|
205
|
+
else:
|
206
|
+
raise ValueError(f"Unsupported button: {button}")
|
207
|
+
|
208
|
+
def read_captured_clicks(self, button: str) -> int:
|
209
|
+
command = {
|
210
|
+
"LEFT": "km.catch_ml()",
|
211
|
+
"RIGHT": "km.catch_mr()",
|
212
|
+
"MIDDLE": "km.catch_mm()",
|
213
|
+
"MOUSE4": "km.catch_ms1()",
|
214
|
+
"MOUSE5": "km.catch_ms2()",
|
215
|
+
}.get(button.upper())
|
216
|
+
if command:
|
217
|
+
result = self.send_command(command, expect_response=True)
|
218
|
+
try:
|
219
|
+
return int(result.strip())
|
220
|
+
except Exception:
|
221
|
+
return 0
|
222
|
+
else:
|
223
|
+
raise ValueError(f"Unsupported button: {button}")
|
224
|
+
|
225
|
+
def _listen(self, debug=False):
|
226
|
+
self._log("Started listener thread")
|
227
|
+
last_value = None
|
228
|
+
button_states = {i: False for i in self.button_map}
|
229
|
+
self._last_mask = 0
|
230
|
+
|
231
|
+
while self._is_connected and not self._stop_event.is_set():
|
232
|
+
if self._pause_listener:
|
233
|
+
time.sleep(0.001)
|
234
|
+
continue
|
235
|
+
try:
|
236
|
+
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)
|
246
|
+
|
247
|
+
for bit, name in self.button_map.items():
|
248
|
+
self._button_states[name] = button_states[bit]
|
249
|
+
|
250
|
+
now = time.time() * 1000
|
251
|
+
if self._button_callback and mask != self._last_mask:
|
252
|
+
for bit, name in self.button_map.items():
|
253
|
+
previous = bool(self._last_mask & (1 << bit))
|
254
|
+
current = bool(mask & (1 << bit))
|
255
|
+
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
|
262
|
+
|
263
|
+
self._last_mask = mask
|
264
|
+
|
265
|
+
if debug:
|
266
|
+
pressed = [name for bit, name in self.button_map.items() if button_states[bit]]
|
267
|
+
button_str = ", ".join(pressed) if pressed else "No buttons pressed"
|
268
|
+
self._log(f"Byte: {value} (0x{value:02X}) -> {button_str}")
|
269
|
+
|
270
|
+
last_value = value
|
271
|
+
except serial.SerialException as e:
|
272
|
+
if "ClearCommError failed" not in str(e):
|
273
|
+
self._log(f"Serial error during listening: {e}")
|
274
|
+
break
|
275
|
+
time.sleep(0.0001)
|
276
|
+
|
277
|
+
self._log("Listener thread exiting")
|
makcu/controller.py
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
import random
|
2
|
+
import time
|
3
|
+
from .mouse import Mouse
|
4
|
+
from .connection import SerialTransport
|
5
|
+
from .errors import MakcuConnectionError
|
6
|
+
from .enums import MouseButton
|
7
|
+
|
8
|
+
class MakcuController:
|
9
|
+
def __init__(self, debug=False, send_init=True):
|
10
|
+
self.transport = SerialTransport(debug=debug, send_init=send_init)
|
11
|
+
self.mouse = Mouse(self.transport)
|
12
|
+
|
13
|
+
def connect(self):
|
14
|
+
self.transport.connect()
|
15
|
+
|
16
|
+
def disconnect(self):
|
17
|
+
self.transport.disconnect()
|
18
|
+
|
19
|
+
def is_connected(self):
|
20
|
+
return self.transport.is_connected()
|
21
|
+
|
22
|
+
def _check_connection(self):
|
23
|
+
if not self.transport.serial or not self.transport.serial.is_open:
|
24
|
+
raise MakcuConnectionError("Not connected")
|
25
|
+
|
26
|
+
def click(self, button: MouseButton):
|
27
|
+
self._check_connection()
|
28
|
+
self.mouse.press(button)
|
29
|
+
self.mouse.release(button)
|
30
|
+
|
31
|
+
def move(self, dx: int, dy: int):
|
32
|
+
self._check_connection()
|
33
|
+
self.mouse.move(dx, dy)
|
34
|
+
|
35
|
+
def scroll(self, delta: int):
|
36
|
+
self._check_connection()
|
37
|
+
self.mouse.scroll(delta)
|
38
|
+
|
39
|
+
def move_smooth(self, dx: int, dy: int, segments: int):
|
40
|
+
self._check_connection()
|
41
|
+
self.mouse.move_smooth(dx, dy, segments)
|
42
|
+
|
43
|
+
def move_bezier(self, dx: int, dy: int, segments: int, ctrl_x: int, ctrl_y: int):
|
44
|
+
self._check_connection()
|
45
|
+
self.mouse.move_bezier(dx, dy, segments, ctrl_x, ctrl_y)
|
46
|
+
|
47
|
+
def lock_mouse_x(self, lock: bool):
|
48
|
+
self._check_connection()
|
49
|
+
self.mouse.lock_x(lock)
|
50
|
+
|
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()
|
57
|
+
self.mouse.lock_left(lock)
|
58
|
+
|
59
|
+
def lock_middle(self, lock: bool):
|
60
|
+
self._check_connection()
|
61
|
+
self.mouse.lock_middle(lock)
|
62
|
+
|
63
|
+
def lock_right(self, lock: bool):
|
64
|
+
self._check_connection()
|
65
|
+
self.mouse.lock_right(lock)
|
66
|
+
|
67
|
+
def lock_side1(self, lock: bool):
|
68
|
+
self._check_connection()
|
69
|
+
self.mouse.lock_side1(lock)
|
70
|
+
|
71
|
+
def lock_side2(self, lock: bool):
|
72
|
+
self._check_connection()
|
73
|
+
self.mouse.lock_side2(lock)
|
74
|
+
|
75
|
+
def spoof_serial(self, serial: str):
|
76
|
+
self._check_connection()
|
77
|
+
self.mouse.spoof_serial(serial)
|
78
|
+
|
79
|
+
def reset_serial(self):
|
80
|
+
self._check_connection()
|
81
|
+
self.mouse.reset_serial()
|
82
|
+
|
83
|
+
def get_device_info(self):
|
84
|
+
self._check_connection()
|
85
|
+
return self.mouse.get_device_info()
|
86
|
+
|
87
|
+
def get_firmware_version(self):
|
88
|
+
self._check_connection()
|
89
|
+
return self.mouse.get_firmware_version()
|
90
|
+
|
91
|
+
def get_button_mask(self) -> int:
|
92
|
+
self._check_connection()
|
93
|
+
return self.transport.get_button_mask()
|
94
|
+
|
95
|
+
def is_locked(self, target: str) -> bool:
|
96
|
+
self._check_connection()
|
97
|
+
return self.mouse.is_locked(target)
|
98
|
+
|
99
|
+
def is_button_locked(self, button: MouseButton) -> bool:
|
100
|
+
self._check_connection()
|
101
|
+
return self.mouse.is_button_locked(button)
|
102
|
+
|
103
|
+
def capture(self, button: MouseButton):
|
104
|
+
self._check_connection()
|
105
|
+
self.mouse.begin_capture(button.name)
|
106
|
+
|
107
|
+
def get_captured_clicks(self, button: MouseButton) -> int:
|
108
|
+
self._check_connection()
|
109
|
+
return self.mouse.stop_capturing_clicks(button.name)
|
110
|
+
|
111
|
+
|
112
|
+
def click_human_like(self, button: MouseButton, count: int = 1,
|
113
|
+
profile: str = "normal", jitter: int = 0):
|
114
|
+
self._check_connection()
|
115
|
+
|
116
|
+
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},
|
120
|
+
}
|
121
|
+
|
122
|
+
if profile not in timing_profiles:
|
123
|
+
raise ValueError(f"Invalid profile: {profile}. Choose from {list(timing_profiles.keys())}")
|
124
|
+
|
125
|
+
t = timing_profiles[profile]
|
126
|
+
|
127
|
+
for _ in range(count):
|
128
|
+
if jitter > 0:
|
129
|
+
dx = random.randint(-jitter, jitter)
|
130
|
+
dy = random.randint(-jitter, jitter)
|
131
|
+
self.mouse.move(dx, dy)
|
132
|
+
|
133
|
+
self.mouse.press(button)
|
134
|
+
time.sleep(random.uniform(t["min_down"], t["max_down"]) / 1000.0)
|
135
|
+
self.mouse.release(button)
|
136
|
+
time.sleep(random.uniform(t["min_wait"], t["max_wait"]) / 1000.0)
|
137
|
+
|
138
|
+
def enable_button_monitoring(self, enable: bool = True):
|
139
|
+
self._check_connection()
|
140
|
+
self.transport.enable_button_monitoring(enable)
|
141
|
+
|
142
|
+
def set_button_callback(self, callback):
|
143
|
+
self._check_connection()
|
144
|
+
self.transport.set_button_callback(callback)
|
145
|
+
|
146
|
+
def get_all_lock_states(self) -> dict:
|
147
|
+
self._check_connection()
|
148
|
+
return self.mouse.get_all_lock_states()
|
149
|
+
|
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)
|
165
|
+
|
166
|
+
def press(self, button: MouseButton):
|
167
|
+
self._check_connection()
|
168
|
+
self.mouse.press(button)
|
169
|
+
|
170
|
+
def release(self, button: MouseButton):
|
171
|
+
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
|
+
|
178
|
+
def get_button_states(self) -> dict:
|
179
|
+
self._check_connection()
|
180
|
+
return self.transport.get_button_states()
|
181
|
+
|
182
|
+
def is_button_pressed(self, button: MouseButton) -> bool:
|
183
|
+
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
|
+
}
|
makcu/enums.py
ADDED
makcu/errors.py
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class MakcuError(Exception):
|
2
|
+
"""Base exception for all Makcu-related errors."""
|
3
|
+
pass
|
4
|
+
|
5
|
+
class MakcuConnectionError(MakcuError):
|
6
|
+
"""Raised when the device connection fails."""
|
7
|
+
pass
|
8
|
+
|
9
|
+
class MakcuCommandError(MakcuError):
|
10
|
+
"""Raised when a device command is invalid, rejected, or fails."""
|
11
|
+
pass
|
12
|
+
|
13
|
+
class MakcuTimeoutError(MakcuError):
|
14
|
+
"""Raised when the device does not respond in time."""
|
15
|
+
pass
|
16
|
+
|
17
|
+
class MakcuResponseError(MakcuError):
|
18
|
+
"""Raised when the response from the device is malformed or unexpected."""
|
19
|
+
pass
|