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/mouse.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from .enums import MouseButton
|
|
2
|
+
from .errors import MakcuCommandError
|
|
3
|
+
from serial.tools import list_ports
|
|
4
|
+
|
|
5
|
+
class Mouse:
|
|
6
|
+
def __init__(self, transport):
|
|
7
|
+
self.transport = transport
|
|
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}")
|
|
30
|
+
|
|
31
|
+
def move(self, x: int, y: int):
|
|
32
|
+
self.transport.send_command(f"km.move({x},{y})")
|
|
33
|
+
|
|
34
|
+
def move_smooth(self, x: int, y: int, segments: int):
|
|
35
|
+
self.transport.send_command(f"km.move({x},{y},{segments})")
|
|
36
|
+
|
|
37
|
+
def move_bezier(self, x: int, y: int, segments: int, ctrl_x: int, ctrl_y: int):
|
|
38
|
+
self.transport.send_command(f"km.move({x},{y},{segments},{ctrl_x},{ctrl_y})")
|
|
39
|
+
|
|
40
|
+
def scroll(self, delta: int):
|
|
41
|
+
self.transport.send_command(f"km.wheel({delta})")
|
|
42
|
+
|
|
43
|
+
def lock_left(self, lock: bool): self.transport.send_command(f"km.lock_ml({int(lock)})")
|
|
44
|
+
def lock_middle(self, lock: bool): self.transport.send_command(f"km.lock_mm({int(lock)})")
|
|
45
|
+
def lock_right(self, lock: bool): self.transport.send_command(f"km.lock_mr({int(lock)})")
|
|
46
|
+
def lock_side1(self, lock: bool): self.transport.send_command(f"km.lock_ms1({int(lock)})")
|
|
47
|
+
def lock_side2(self, lock: bool): self.transport.send_command(f"km.lock_ms2({int(lock)})")
|
|
48
|
+
def lock_x(self, lock: bool): self.transport.send_command(f"km.lock_mx({int(lock)})")
|
|
49
|
+
def lock_y(self, lock: bool): self.transport.send_command(f"km.lock_my({int(lock)})")
|
|
50
|
+
|
|
51
|
+
def spoof_serial(self, serial: str): self.transport.send_command(f"km.serial('{serial}')")
|
|
52
|
+
def reset_serial(self): self.transport.send_command("km.serial(0)")
|
|
53
|
+
|
|
54
|
+
def get_device_info(self) -> dict:
|
|
55
|
+
port_name = self.transport.port
|
|
56
|
+
is_connected = self.transport.is_connected()
|
|
57
|
+
info = {
|
|
58
|
+
"port": port_name,
|
|
59
|
+
"description": "Unknown",
|
|
60
|
+
"vid": "Unknown",
|
|
61
|
+
"pid": "Unknown",
|
|
62
|
+
"isConnected": is_connected
|
|
63
|
+
}
|
|
64
|
+
for port in list_ports.comports():
|
|
65
|
+
if port.device == port_name:
|
|
66
|
+
info.update({
|
|
67
|
+
"description": port.description,
|
|
68
|
+
"vid": hex(port.vid) if port.vid is not None else "Unknown",
|
|
69
|
+
"pid": hex(port.pid) if port.pid is not None else "Unknown"
|
|
70
|
+
})
|
|
71
|
+
break
|
|
72
|
+
return info
|
|
73
|
+
|
|
74
|
+
def get_firmware_version(self) -> str:
|
|
75
|
+
return self.transport.send_command("km.version()", expect_response=True)
|
|
76
|
+
|
|
77
|
+
def is_locked(self, target: str) -> bool:
|
|
78
|
+
target = target.upper()
|
|
79
|
+
commands = {
|
|
80
|
+
"X": "km.lock_mx()",
|
|
81
|
+
"Y": "km.lock_my()",
|
|
82
|
+
"LEFT": "km.lock_ml()",
|
|
83
|
+
"MIDDLE": "km.lock_mm()",
|
|
84
|
+
"RIGHT": "km.lock_mr()",
|
|
85
|
+
"MOUSE4": "km.lock_ms1()",
|
|
86
|
+
"MOUSE5": "km.lock_ms2()",
|
|
87
|
+
}
|
|
88
|
+
if target not in commands:
|
|
89
|
+
raise ValueError(f"Unsupported lock target: {target}")
|
|
90
|
+
try:
|
|
91
|
+
result = self.transport.send_command(commands[target], expect_response=True)
|
|
92
|
+
return result.strip() == "1"
|
|
93
|
+
except Exception:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def is_button_locked(self, button: MouseButton) -> bool:
|
|
97
|
+
name_map = {
|
|
98
|
+
MouseButton.LEFT: "LEFT",
|
|
99
|
+
MouseButton.RIGHT: "RIGHT",
|
|
100
|
+
MouseButton.MIDDLE: "MIDDLE",
|
|
101
|
+
MouseButton.MOUSE4: "MOUSE4",
|
|
102
|
+
MouseButton.MOUSE5: "MOUSE5"
|
|
103
|
+
}
|
|
104
|
+
return self.is_locked(name_map[button])
|
|
105
|
+
|
|
106
|
+
def begin_capture(self, button: str):
|
|
107
|
+
"""
|
|
108
|
+
Assumes lock_<button>(1) has already been called.
|
|
109
|
+
Sends catch_<button>(0) to begin capturing click cycles.
|
|
110
|
+
"""
|
|
111
|
+
self.transport.catch_button(button)
|
|
112
|
+
|
|
113
|
+
def stop_capturing_clicks(self, button: str) -> int:
|
|
114
|
+
"""
|
|
115
|
+
Assumes lock_<button>(0) has already been called.
|
|
116
|
+
Returns the total number of clicks since begin_capture.
|
|
117
|
+
"""
|
|
118
|
+
return self.transport.read_captured_clicks(button)
|
|
119
|
+
|
|
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
|
+
def get_all_lock_states(self) -> dict:
|
|
131
|
+
return {
|
|
132
|
+
target: self.is_locked(target)
|
|
133
|
+
for target in ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
|
|
134
|
+
}
|
makcu/utils.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makcu
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Python library to interact with Makcu devices.
|
|
5
|
+
Author: SleepyTotem
|
|
6
|
+
License: GPL
|
|
7
|
+
Project-URL: Homepage, https://github.com/SleepyTotem/makcu-py-lib
|
|
8
|
+
Requires-Python: >=3.7
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# ๐ฑ๏ธ Makcu Python Library
|
|
14
|
+
|
|
15
|
+
Makcu Py Lib is a Python library for controlling Makcu devices โ enabling software-driven mouse input, movement simulation, locking, monitoring, and more.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ๐ฆ Installation
|
|
20
|
+
|
|
21
|
+
### โ
Recommended: PyPI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install makcu
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### ๐งช Alternative: Install from Source
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/SleepyTotem/makcu-py-lib
|
|
31
|
+
cd makcu-py-lib
|
|
32
|
+
pip install .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## ๐ Command-Line Usage
|
|
38
|
+
|
|
39
|
+
After installation, use:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m makcu [command]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Available Commands
|
|
46
|
+
|
|
47
|
+
| Command | Description |
|
|
48
|
+
|---------|-------------|
|
|
49
|
+
| `--debug` | Opens interactive console to send raw `km.*` commands |
|
|
50
|
+
| `--testPort COM3` | Tests a specific COM port for connectivity |
|
|
51
|
+
| `--runtest` | Runs all automated tests and opens a test report |
|
|
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
|
+
---
|
|
62
|
+
|
|
63
|
+
## ๐ง Quickstart (Python)
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from makcu import create_controller, MouseButton
|
|
67
|
+
|
|
68
|
+
makcu = create_controller()
|
|
69
|
+
makcu.click(MouseButton.LEFT)
|
|
70
|
+
makcu.move(100, 50)
|
|
71
|
+
makcu.scroll(-1)
|
|
72
|
+
makcu.disconnect()
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## ๐งฉ API Reference
|
|
78
|
+
|
|
79
|
+
### ๐ง Initialization
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
makcu = create_controller(debug=True, send_init=True)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Set fallback port manually
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
makcu.set_port("COM4") # use before create_controller()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### ๐ฎ Mouse Control
|
|
94
|
+
|
|
95
|
+
#### Clicks
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
makcu.click(MouseButton.LEFT)
|
|
99
|
+
makcu.press(MouseButton.RIGHT)
|
|
100
|
+
makcu.release(MouseButton.RIGHT)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Movement
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
makcu.move(dx=30, dy=20)
|
|
107
|
+
makcu.move_smooth(100, 40, segments=10)
|
|
108
|
+
makcu.move_bezier(50, 50, 15, ctrl_x=25, ctrl_y=25)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Scrolling
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
makcu.scroll(-3) # Scroll down
|
|
115
|
+
makcu.scroll(3) # Scroll up
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### ๐ Locking and Unlocking
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
makcu.lock_left(True)
|
|
124
|
+
makcu.lock_right(True)
|
|
125
|
+
makcu.lock_middle(False)
|
|
126
|
+
makcu.lock_side1(True)
|
|
127
|
+
makcu.lock_side2(False)
|
|
128
|
+
makcu.lock_mouse_x(True)
|
|
129
|
+
makcu.lock_mouse_y(False)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Lock Status
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
makcu.is_button_locked(MouseButton.LEFT)
|
|
136
|
+
makcu.get_all_lock_states()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### ๐ค Human-like Click Simulation
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
makcu.click_human_like(
|
|
145
|
+
button=MouseButton.LEFT,
|
|
146
|
+
count=5,
|
|
147
|
+
profile="normal", # "fast", "slow" also available
|
|
148
|
+
jitter=3
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### ๐ Device Info & Firmware
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
info = makcu.get_device_info()
|
|
158
|
+
print(info)
|
|
159
|
+
|
|
160
|
+
version = makcu.get_firmware_version()
|
|
161
|
+
print(version)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### ๐ Serial Spoofing
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
makcu.spoof_serial("FAKE123456")
|
|
170
|
+
makcu.reset_serial()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## ๐งช Button Monitoring & Capture
|
|
176
|
+
|
|
177
|
+
### Enable Real-time Monitoring
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
makcu.enable_button_monitoring(True)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Set Callback Function
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
def on_button_event(button, pressed):
|
|
187
|
+
print(f"{button.name} is {'pressed' if pressed else 'released'}")
|
|
188
|
+
|
|
189
|
+
makcu.set_button_callback(on_button_event)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Configure Debounce
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
makcu.set_callback_debounce_time(100) # milliseconds
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Click Capturing
|
|
201
|
+
|
|
202
|
+
### โ **Currently broken (Pending Firmware Update)**
|
|
203
|
+
|
|
204
|
+
Click capturing will allow you to detect and count click events in software.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
makcu.mouse.lock_right(True)
|
|
208
|
+
makcu.capture(MouseButton.RIGHT)
|
|
209
|
+
|
|
210
|
+
# User clicks however many times
|
|
211
|
+
|
|
212
|
+
makcu.mouse.lock_right(False)
|
|
213
|
+
count = makcu.get_captured_clicks(MouseButton.RIGHT)
|
|
214
|
+
print(f"Captured clicks: {count}")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
> โ ๏ธ This feature is currently broken in firmware. Do not rely on it yet.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## ๐ข Bitmask & Button States
|
|
222
|
+
|
|
223
|
+
### Get Bitmask of Active Buttons
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
mask = makcu.get_button_mask()
|
|
227
|
+
print(f"Button mask: {mask}")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Get Raw Button State Map
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
states = makcu.get_button_states()
|
|
234
|
+
print(states) # {'left': False, 'right': True, ...}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Check if a Specific Button Is Pressed
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
if makcu.is_button_pressed(MouseButton.RIGHT):
|
|
241
|
+
print("Right button is pressed")
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## โ๏ธ Low-Level Command Access
|
|
247
|
+
|
|
248
|
+
### Send raw serial commands
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
response = makcu.transport.send_command("km.version()", expect_response=True)
|
|
252
|
+
print(response)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## ๐งช Test Suite
|
|
258
|
+
|
|
259
|
+
Run all tests and generate HTML report:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
python -m makcu --runtest
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## ๐ Enumerations
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from makcu import MouseButton
|
|
271
|
+
|
|
272
|
+
MouseButton.LEFT
|
|
273
|
+
MouseButton.RIGHT
|
|
274
|
+
MouseButton.MIDDLE
|
|
275
|
+
MouseButton.MOUSE4
|
|
276
|
+
MouseButton.MOUSE5
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## ๐งฏ Exception Handling
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from makcu import MakcuError, MakcuConnectionError
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
makcu = create_controller()
|
|
288
|
+
except MakcuConnectionError as e:
|
|
289
|
+
print("Connection failed:", e)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## ๐ ๏ธ Developer Notes
|
|
295
|
+
|
|
296
|
+
- Uses CH343 USB Serial
|
|
297
|
+
- Auto-connects to correct port or fallback
|
|
298
|
+
- Supports baud rate switching to 4M
|
|
299
|
+
- Automatically enables `km.buttons(1)` monitoring if `send_init=True`
|
|
300
|
+
- Supports raw button state polling
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## ๐ License
|
|
305
|
+
|
|
306
|
+
GPL License ยฉ SleepyTotem
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Support
|
|
311
|
+
Please open an issue on the project repository and I will get to it asap
|
|
312
|
+
|
|
313
|
+
## ๐ Links
|
|
314
|
+
|
|
315
|
+
- ๐ [Project Homepage](https://github.com/SleepyTotem/makcu-py-lib)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
makcu/__init__.py,sha256=gBIXKg-TXZg3fTrWeUi97z1ezWF_pleFDbw9f6NpjKs,418
|
|
2
|
+
makcu/__main__.py,sha256=tptSrqmMTrkfe23JsJT_znNXUX2orseCigEnVCeB-B0,2346
|
|
3
|
+
makcu/connection.py,sha256=-lHPzMifqOOhG0ZfYGrM7SNmifBppbWYbdrm1IY5ZBY,10635
|
|
4
|
+
makcu/controller.py,sha256=oFp1qrUW4xqr5FYT3SXTvHIQJxxGF2v5ysqIpZk2Dsk,6542
|
|
5
|
+
makcu/enums.py,sha256=VmvCLmpghVHuTAkvCGMfA14MgWTtFVMfsGQQNnJ58Ts,126
|
|
6
|
+
makcu/errors.py,sha256=4CkQ4gKa7GL5-BO3yOAJMMsy3QlUDDL42S1P1clqV4A,562
|
|
7
|
+
makcu/mouse.py,sha256=KYse0Bm6rEyF8v7ujtRWDXGeUZQU5VAzKO9SxXY_O9g,5284
|
|
8
|
+
makcu/utils.py,sha256=-xr6mkYITpWyAIFxHeTsZQO1V__91Sm0aFho5iWHegc,191
|
|
9
|
+
makcu-0.1.1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
10
|
+
makcu-0.1.1.dist-info/METADATA,sha256=Z08RUlzyhGdKSoMUrMHpvgfZy0yc_nO7u-_j1meazpU,5563
|
|
11
|
+
makcu-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
makcu-0.1.1.dist-info/top_level.txt,sha256=IRO1UVb5LK_ovjau0g4oObyXQqy00tVEE-yF5lPgw1w,6
|
|
13
|
+
makcu-0.1.1.dist-info/RECORD,,
|