makcu 0.1.4__py3-none-any.whl → 0.2.0__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 +71 -9
- makcu/__main__.py +254 -37
- makcu/conftest.py +27 -17
- makcu/connection.py +427 -231
- makcu/controller.py +348 -86
- makcu/makcu.pyi +13 -0
- makcu/mouse.py +219 -116
- makcu/py.typed +2 -0
- makcu/test_suite.py +112 -34
- makcu-0.2.0.dist-info/METADATA +1141 -0
- makcu-0.2.0.dist-info/RECORD +16 -0
- makcu-0.1.4.dist-info/METADATA +0 -274
- makcu-0.1.4.dist-info/RECORD +0 -14
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/WHEEL +0 -0
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {makcu-0.1.4.dist-info → makcu-0.2.0.dist-info}/top_level.txt +0 -0
makcu/mouse.py
CHANGED
@@ -1,167 +1,270 @@
|
|
1
|
+
from typing import Dict, Optional, List, Union, Any
|
1
2
|
from .enums import MouseButton
|
3
|
+
from .connection import SerialTransport
|
2
4
|
from .errors import MakcuCommandError
|
3
5
|
from serial.tools import list_ports
|
4
6
|
import time
|
5
7
|
|
8
|
+
# Create a mock enum-like object for axis locks
|
9
|
+
class AxisButton:
|
10
|
+
def __init__(self, name: str) -> None:
|
11
|
+
self.name = name
|
12
|
+
|
6
13
|
class Mouse:
|
7
|
-
|
14
|
+
"""Ultra-optimized mouse control for gaming performance"""
|
15
|
+
|
16
|
+
# Pre-computed command strings for faster access
|
17
|
+
_BUTTON_COMMANDS = {
|
18
|
+
MouseButton.LEFT: "left",
|
19
|
+
MouseButton.RIGHT: "right",
|
20
|
+
MouseButton.MIDDLE: "middle",
|
21
|
+
MouseButton.MOUSE4: "ms1",
|
22
|
+
MouseButton.MOUSE5: "ms2",
|
23
|
+
}
|
24
|
+
|
25
|
+
# Pre-formatted commands cache
|
26
|
+
_PRESS_COMMANDS = {}
|
27
|
+
_RELEASE_COMMANDS = {}
|
28
|
+
_LOCK_COMMANDS = {}
|
29
|
+
_UNLOCK_COMMANDS = {}
|
30
|
+
_LOCK_QUERY_COMMANDS = {}
|
31
|
+
|
32
|
+
def __init__(self, transport: SerialTransport) -> None:
|
8
33
|
self.transport = transport
|
34
|
+
self._lock_states_cache: int = 0 # Bitwise cache
|
35
|
+
self._cache_valid = False
|
36
|
+
|
37
|
+
# Pre-compute all commands for zero-overhead execution
|
38
|
+
self._init_command_cache()
|
39
|
+
|
40
|
+
def _init_command_cache(self) -> None:
|
41
|
+
"""Pre-compute all commands to avoid string formatting during execution"""
|
42
|
+
# Button press/release commands
|
43
|
+
for button, cmd in self._BUTTON_COMMANDS.items():
|
44
|
+
self._PRESS_COMMANDS[button] = f"km.{cmd}(1)"
|
45
|
+
self._RELEASE_COMMANDS[button] = f"km.{cmd}(0)"
|
46
|
+
|
47
|
+
# Lock commands with bitwise indexing
|
48
|
+
# Bit 0-4: buttons, Bit 5: X axis, Bit 6: Y axis
|
49
|
+
lock_targets = [
|
50
|
+
("LEFT", "ml", 0),
|
51
|
+
("RIGHT", "mr", 1),
|
52
|
+
("MIDDLE", "mm", 2),
|
53
|
+
("MOUSE4", "ms1", 3),
|
54
|
+
("MOUSE5", "ms2", 4),
|
55
|
+
("X", "mx", 5),
|
56
|
+
("Y", "my", 6),
|
57
|
+
]
|
58
|
+
|
59
|
+
for name, cmd, bit in lock_targets:
|
60
|
+
self._LOCK_COMMANDS[name] = (f"km.lock_{cmd}(1)", bit)
|
61
|
+
self._UNLOCK_COMMANDS[name] = (f"km.lock_{cmd}(0)", bit)
|
62
|
+
self._LOCK_QUERY_COMMANDS[name] = (f"km.lock_{cmd}()", bit)
|
9
63
|
|
10
|
-
def _send_button_command(self, button: MouseButton, state: int):
|
11
|
-
|
12
|
-
|
13
|
-
MouseButton.RIGHT: "right",
|
14
|
-
MouseButton.MIDDLE: "middle",
|
15
|
-
MouseButton.MOUSE4: "ms1",
|
16
|
-
MouseButton.MOUSE5: "ms2",
|
17
|
-
}
|
18
|
-
if button not in command_map:
|
64
|
+
def _send_button_command(self, button: MouseButton, state: int) -> None:
|
65
|
+
"""Optimized button command sending"""
|
66
|
+
if button not in self._BUTTON_COMMANDS:
|
19
67
|
raise MakcuCommandError(f"Unsupported button: {button}")
|
20
|
-
|
68
|
+
|
69
|
+
# Use pre-computed commands
|
70
|
+
cmd = self._PRESS_COMMANDS[button] if state else self._RELEASE_COMMANDS[button]
|
71
|
+
self.transport.send_command(cmd)
|
21
72
|
|
22
|
-
def press(self, button: MouseButton):
|
23
|
-
|
73
|
+
def press(self, button: MouseButton) -> None:
|
74
|
+
"""Press with pre-computed command"""
|
75
|
+
self.transport.send_command(self._PRESS_COMMANDS[button])
|
24
76
|
|
25
|
-
def release(self, button: MouseButton):
|
26
|
-
|
77
|
+
def release(self, button: MouseButton) -> None:
|
78
|
+
"""Release with pre-computed command"""
|
79
|
+
self.transport.send_command(self._RELEASE_COMMANDS[button])
|
27
80
|
|
28
|
-
def move(self, x: int, y: int):
|
81
|
+
def move(self, x: int, y: int) -> None:
|
82
|
+
"""Move command"""
|
29
83
|
self.transport.send_command(f"km.move({x},{y})")
|
30
84
|
|
31
|
-
def
|
85
|
+
def click(self, button: MouseButton) -> None:
|
86
|
+
"""Optimized click with cached commands"""
|
87
|
+
if button not in self._BUTTON_COMMANDS:
|
88
|
+
raise MakcuCommandError(f"Unsupported button: {button}")
|
89
|
+
|
90
|
+
# Use pre-computed commands for maximum speed
|
91
|
+
press_cmd = self._PRESS_COMMANDS[button]
|
92
|
+
release_cmd = self._RELEASE_COMMANDS[button]
|
93
|
+
|
94
|
+
# Send both commands in rapid succession
|
95
|
+
transport = self.transport # Cache reference
|
96
|
+
transport.send_command(press_cmd)
|
97
|
+
transport.send_command(release_cmd)
|
98
|
+
|
99
|
+
def move_smooth(self, x: int, y: int, segments: int) -> None:
|
100
|
+
"""Smooth movement"""
|
32
101
|
self.transport.send_command(f"km.move({x},{y},{segments})")
|
33
102
|
|
34
|
-
def move_bezier(self, x: int, y: int, segments: int, ctrl_x: int, ctrl_y: int):
|
103
|
+
def move_bezier(self, x: int, y: int, segments: int, ctrl_x: int, ctrl_y: int) -> None:
|
104
|
+
"""Bezier movement"""
|
35
105
|
self.transport.send_command(f"km.move({x},{y},{segments},{ctrl_x},{ctrl_y})")
|
36
106
|
|
37
|
-
def scroll(self, delta: int):
|
107
|
+
def scroll(self, delta: int) -> None:
|
108
|
+
"""Scroll command"""
|
38
109
|
self.transport.send_command(f"km.wheel({delta})")
|
39
110
|
|
40
|
-
|
41
|
-
|
111
|
+
# Optimized lock methods using bitwise cache
|
112
|
+
def _set_lock(self, name: str, lock: bool) -> None:
|
113
|
+
"""Generic lock setter with cache update"""
|
114
|
+
if lock:
|
115
|
+
cmd, bit = self._LOCK_COMMANDS[name]
|
116
|
+
else:
|
117
|
+
cmd, bit = self._UNLOCK_COMMANDS[name]
|
42
118
|
|
43
|
-
|
44
|
-
|
119
|
+
self.transport.send_command(cmd)
|
120
|
+
|
121
|
+
# Update cache
|
122
|
+
if lock:
|
123
|
+
self._lock_states_cache |= (1 << bit)
|
124
|
+
else:
|
125
|
+
self._lock_states_cache &= ~(1 << bit)
|
126
|
+
self._cache_valid = True
|
45
127
|
|
46
|
-
def
|
47
|
-
self.
|
128
|
+
def lock_left(self, lock: bool) -> None:
|
129
|
+
self._set_lock("LEFT", lock)
|
130
|
+
|
131
|
+
def lock_middle(self, lock: bool) -> None:
|
132
|
+
self._set_lock("MIDDLE", lock)
|
48
133
|
|
49
|
-
def
|
50
|
-
self.
|
134
|
+
def lock_right(self, lock: bool) -> None:
|
135
|
+
self._set_lock("RIGHT", lock)
|
51
136
|
|
52
|
-
def
|
53
|
-
self.
|
137
|
+
def lock_side1(self, lock: bool) -> None:
|
138
|
+
self._set_lock("MOUSE4", lock)
|
54
139
|
|
55
|
-
def
|
56
|
-
self.
|
140
|
+
def lock_side2(self, lock: bool) -> None:
|
141
|
+
self._set_lock("MOUSE5", lock)
|
57
142
|
|
58
|
-
def
|
59
|
-
self.
|
143
|
+
def lock_x(self, lock: bool) -> None:
|
144
|
+
self._set_lock("X", lock)
|
60
145
|
|
146
|
+
def lock_y(self, lock: bool) -> None:
|
147
|
+
self._set_lock("Y", lock)
|
61
148
|
|
62
|
-
def spoof_serial(self, serial: str):
|
149
|
+
def spoof_serial(self, serial: str) -> None:
|
150
|
+
"""Set custom serial"""
|
63
151
|
self.transport.send_command(f"km.serial('{serial}')")
|
64
152
|
|
65
|
-
def reset_serial(self):
|
153
|
+
def reset_serial(self) -> None:
|
154
|
+
"""Reset serial"""
|
66
155
|
self.transport.send_command("km.serial(0)")
|
67
156
|
|
68
|
-
def get_device_info(self) ->
|
157
|
+
def get_device_info(self) -> Dict[str, Union[str, bool]]:
|
158
|
+
"""Ultra-fast device info with minimal port scanning"""
|
69
159
|
port_name = self.transport.port
|
70
160
|
is_connected = self.transport.is_connected()
|
161
|
+
|
162
|
+
if not is_connected or not port_name:
|
163
|
+
return {
|
164
|
+
"port": port_name or "Unknown",
|
165
|
+
"description": "Disconnected",
|
166
|
+
"vid": "Unknown",
|
167
|
+
"pid": "Unknown",
|
168
|
+
"isConnected": False
|
169
|
+
}
|
170
|
+
|
71
171
|
info = {
|
72
172
|
"port": port_name,
|
73
|
-
"description": "
|
74
|
-
"vid": "Unknown",
|
173
|
+
"description": "Connected Device",
|
174
|
+
"vid": "Unknown",
|
75
175
|
"pid": "Unknown",
|
76
|
-
"isConnected":
|
176
|
+
"isConnected": True
|
77
177
|
}
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
"
|
83
|
-
|
84
|
-
|
85
|
-
|
178
|
+
|
179
|
+
try:
|
180
|
+
for port in list_ports.comports():
|
181
|
+
if port.device == port_name:
|
182
|
+
info["description"] = port.description or "Connected Device"
|
183
|
+
if port.vid is not None:
|
184
|
+
info["vid"] = f"0x{port.vid:04x}"
|
185
|
+
if port.pid is not None:
|
186
|
+
info["pid"] = f"0x{port.pid:04x}"
|
187
|
+
break # Found our port, exit immediately
|
188
|
+
except Exception:
|
189
|
+
pass
|
190
|
+
|
86
191
|
return info
|
87
192
|
|
88
193
|
def get_firmware_version(self) -> str:
|
89
|
-
|
194
|
+
"""Get firmware version"""
|
195
|
+
response = self.transport.send_command("km.version()", expect_response=True, timeout=0.1)
|
196
|
+
return response or ""
|
90
197
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
if
|
99
|
-
|
100
|
-
|
198
|
+
def _invalidate_cache(self) -> None:
|
199
|
+
"""Invalidate cache"""
|
200
|
+
self._cache_valid = False
|
201
|
+
|
202
|
+
def get_all_lock_states(self) -> Dict[str, bool]:
|
203
|
+
"""Get all lock states with parallel queries for gaming performance"""
|
204
|
+
# Return cache if valid
|
205
|
+
if self._cache_valid:
|
206
|
+
return {
|
207
|
+
"X": bool(self._lock_states_cache & (1 << 5)),
|
208
|
+
"Y": bool(self._lock_states_cache & (1 << 6)),
|
209
|
+
"LEFT": bool(self._lock_states_cache & (1 << 0)),
|
210
|
+
"RIGHT": bool(self._lock_states_cache & (1 << 1)),
|
211
|
+
"MIDDLE": bool(self._lock_states_cache & (1 << 2)),
|
212
|
+
"MOUSE4": bool(self._lock_states_cache & (1 << 3)),
|
213
|
+
"MOUSE5": bool(self._lock_states_cache & (1 << 4)),
|
214
|
+
}
|
215
|
+
|
216
|
+
# Query all states in rapid succession
|
217
|
+
states = {}
|
218
|
+
targets = ["X", "Y", "LEFT", "RIGHT", "MIDDLE", "MOUSE4", "MOUSE5"]
|
219
|
+
|
220
|
+
for target in targets:
|
221
|
+
cmd, bit = self._LOCK_QUERY_COMMANDS[target]
|
222
|
+
try:
|
223
|
+
result = self.transport.send_command(cmd, expect_response=True, timeout=0.05)
|
224
|
+
if result and result.strip() in ['0', '1']:
|
225
|
+
is_locked = result.strip() == '1'
|
226
|
+
states[target] = is_locked
|
227
|
+
|
228
|
+
# Update cache
|
229
|
+
if is_locked:
|
230
|
+
self._lock_states_cache |= (1 << bit)
|
231
|
+
else:
|
232
|
+
self._lock_states_cache &= ~(1 << bit)
|
233
|
+
else:
|
234
|
+
states[target] = False
|
235
|
+
except Exception:
|
236
|
+
states[target] = False
|
237
|
+
|
238
|
+
self._cache_valid = True
|
239
|
+
return states
|
101
240
|
|
102
|
-
def is_locked(self, button: MouseButton) -> bool:
|
241
|
+
def is_locked(self, button: Union[MouseButton, AxisButton]) -> bool:
|
242
|
+
"""Check lock state with cache"""
|
103
243
|
try:
|
104
|
-
|
105
|
-
time.sleep(0.03)
|
106
|
-
|
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}'")
|
244
|
+
target_name = button.name if hasattr(button, 'name') else str(button)
|
112
245
|
|
113
|
-
|
246
|
+
# Check cache first
|
247
|
+
if self._cache_valid and target_name in self._LOCK_QUERY_COMMANDS:
|
248
|
+
_, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
249
|
+
return bool(self._lock_states_cache & (1 << bit))
|
114
250
|
|
115
|
-
|
116
|
-
|
251
|
+
# Query device
|
252
|
+
cmd, bit = self._LOCK_QUERY_COMMANDS[target_name]
|
253
|
+
result = self.transport.send_command(cmd, expect_response=True, timeout=0.05)
|
117
254
|
|
118
|
-
|
119
|
-
|
120
|
-
line = line.strip()
|
121
|
-
if line in ['1', '0']:
|
122
|
-
return line == '1'
|
255
|
+
if not result:
|
256
|
+
return False
|
123
257
|
|
124
|
-
|
125
|
-
|
126
|
-
if numbers:
|
127
|
-
return numbers[-1] == '1'
|
258
|
+
result = result.strip()
|
259
|
+
is_locked = result == '1'
|
128
260
|
|
129
|
-
|
130
|
-
|
261
|
+
# Update cache
|
262
|
+
if is_locked:
|
263
|
+
self._lock_states_cache |= (1 << bit)
|
264
|
+
else:
|
265
|
+
self._lock_states_cache &= ~(1 << bit)
|
131
266
|
|
132
|
-
return
|
267
|
+
return is_locked
|
133
268
|
|
134
|
-
except Exception
|
135
|
-
|
136
|
-
print(f"Error checking lock status: {e}")
|
137
|
-
return False
|
138
|
-
|
139
|
-
def get_all_lock_states(self) -> dict:
|
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
|
269
|
+
except Exception:
|
270
|
+
return False
|
makcu/py.typed
ADDED
makcu/test_suite.py
CHANGED
@@ -1,19 +1,35 @@
|
|
1
|
-
import pytest
|
1
|
+
import pytest
|
2
|
+
import time
|
2
3
|
from makcu import MouseButton
|
3
4
|
|
5
|
+
# Pre-compute test data to avoid runtime overhead
|
6
|
+
TEST_BUTTONS = (MouseButton.LEFT, MouseButton.RIGHT, MouseButton.MIDDLE)
|
7
|
+
BUTTON_STATE_KEYS = ('left', 'right', 'middle', 'mouse4', 'mouse5')
|
8
|
+
MOVE_COORDS = ((10, 0), (0, 10), (-10, 0), (0, -10))
|
9
|
+
|
10
|
+
def test_connect_to_port(makcu):
|
11
|
+
"""Test connection - already connected via fixture"""
|
12
|
+
print("Connecting to port...")
|
13
|
+
makcu.connect()
|
14
|
+
assert makcu.is_connected(), "Failed to connect to the makcu"
|
15
|
+
|
4
16
|
def test_press_and_release(makcu):
|
17
|
+
"""Test basic press/release - minimal overhead"""
|
5
18
|
makcu.press(MouseButton.LEFT)
|
6
19
|
makcu.release(MouseButton.LEFT)
|
7
20
|
|
8
21
|
def test_firmware_version(makcu):
|
22
|
+
"""Test firmware version retrieval"""
|
9
23
|
version = makcu.mouse.get_firmware_version()
|
10
24
|
assert version and len(version.strip()) > 0
|
11
25
|
|
12
26
|
def test_middle_click(makcu):
|
27
|
+
"""Test middle button - direct operations"""
|
13
28
|
makcu.press(MouseButton.MIDDLE)
|
14
29
|
makcu.release(MouseButton.MIDDLE)
|
15
30
|
|
16
31
|
def test_device_info(makcu):
|
32
|
+
"""Test device info - optimized checks"""
|
17
33
|
print("Fetching device info...")
|
18
34
|
info = makcu.mouse.get_device_info()
|
19
35
|
print(f"Device Info: {info}")
|
@@ -21,61 +37,123 @@ def test_device_info(makcu):
|
|
21
37
|
assert info.get("isConnected") is True
|
22
38
|
|
23
39
|
def test_port_connection(makcu):
|
40
|
+
"""Test connection state - cached check"""
|
24
41
|
assert makcu.is_connected()
|
25
42
|
|
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_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_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
43
|
def test_button_mask(makcu):
|
44
|
+
"""Test button mask - direct integer check"""
|
44
45
|
print("Getting button mask...")
|
45
46
|
mask = makcu.get_button_mask()
|
46
47
|
print(f"Mask value: {mask}")
|
47
48
|
assert isinstance(mask, int)
|
48
49
|
|
49
50
|
def test_get_button_states(makcu):
|
51
|
+
"""Test button states - optimized validation"""
|
50
52
|
states = makcu.get_button_states()
|
51
53
|
assert isinstance(states, dict)
|
52
|
-
for key in
|
54
|
+
for key in BUTTON_STATE_KEYS:
|
53
55
|
assert key in states
|
54
56
|
|
55
57
|
def test_lock_state(makcu):
|
58
|
+
"""Test lock functionality - minimal operations"""
|
56
59
|
print("Locking LEFT button...")
|
57
60
|
makcu.lock_left(True)
|
58
61
|
print("Querying lock state while LEFT is locked...")
|
59
|
-
state = makcu.is_locked(MouseButton.LEFT)
|
62
|
+
state = makcu.is_locked(MouseButton.LEFT)
|
60
63
|
print(state)
|
61
|
-
assert state
|
64
|
+
assert state
|
62
65
|
|
63
66
|
def test_makcu_behavior(makcu):
|
67
|
+
"""Test basic behavior - batched operations"""
|
64
68
|
makcu.move(25, 25)
|
65
69
|
makcu.click(MouseButton.LEFT)
|
66
70
|
makcu.scroll(-2)
|
67
71
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
makcu.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
def test_batch_commands(makcu):
|
73
|
+
"""Test batch execution performance"""
|
74
|
+
print("Testing batch command execution (10 commands)...")
|
75
|
+
|
76
|
+
start_time = time.perf_counter()
|
77
|
+
|
78
|
+
# Execute 10 different commands in rapid succession
|
79
|
+
makcu.move(10, 0)
|
80
|
+
makcu.click(MouseButton.LEFT)
|
81
|
+
makcu.move(0, 10)
|
82
|
+
makcu.press(MouseButton.RIGHT)
|
83
|
+
makcu.release(MouseButton.RIGHT)
|
84
|
+
makcu.scroll(-1)
|
85
|
+
makcu.move(-10, 0)
|
86
|
+
makcu.click(MouseButton.MIDDLE)
|
87
|
+
makcu.move(0, -10)
|
88
|
+
makcu.scroll(1)
|
89
|
+
|
90
|
+
end_time = time.perf_counter()
|
91
|
+
elapsed_ms = (end_time - start_time) * 1000
|
92
|
+
|
93
|
+
print(f"Batch execution time: {elapsed_ms:.2f}ms")
|
94
|
+
print(f"Average per command: {elapsed_ms/10:.2f}ms")
|
95
|
+
|
96
|
+
# Assert that 10 commands complete in under 50ms
|
97
|
+
assert elapsed_ms < 50, f"Batch commands took {elapsed_ms:.2f}ms, expected < 50ms"
|
98
|
+
|
99
|
+
# Also test with mouse movements only
|
100
|
+
start_time = time.perf_counter()
|
101
|
+
for _ in range(10):
|
102
|
+
makcu.move(5, 5)
|
103
|
+
end_time = time.perf_counter()
|
104
|
+
|
105
|
+
move_only_ms = (end_time - start_time) * 1000
|
106
|
+
print(f"10 move commands: {move_only_ms:.2f}ms ({move_only_ms/10:.2f}ms per move)")
|
107
|
+
|
108
|
+
def test_rapid_moves(makcu):
|
109
|
+
"""Test rapid movement commands"""
|
110
|
+
start = time.perf_counter_ns()
|
111
|
+
|
112
|
+
# Unrolled loop for 10 moves
|
113
|
+
makcu.move(5, 5)
|
114
|
+
makcu.move(5, 5)
|
115
|
+
makcu.move(5, 5)
|
116
|
+
makcu.move(5, 5)
|
117
|
+
makcu.move(5, 5)
|
118
|
+
makcu.move(5, 5)
|
119
|
+
makcu.move(5, 5)
|
120
|
+
makcu.move(5, 5)
|
121
|
+
makcu.move(5, 5)
|
122
|
+
makcu.move(5, 5)
|
123
|
+
|
124
|
+
elapsed_ms = (time.perf_counter_ns() - start) / 1_000_000
|
125
|
+
print(f"10 rapid moves: {elapsed_ms:.2f}ms")
|
126
|
+
assert elapsed_ms < 30
|
127
|
+
|
128
|
+
def test_button_performance(makcu):
|
129
|
+
"""Test button operation performance"""
|
130
|
+
start = time.perf_counter_ns()
|
131
|
+
|
132
|
+
# Test each button type once
|
133
|
+
for button in TEST_BUTTONS:
|
134
|
+
makcu.press(button)
|
135
|
+
makcu.release(button)
|
136
|
+
|
137
|
+
elapsed_ms = (time.perf_counter_ns() - start) / 1_000_000
|
138
|
+
print(f"Button operations: {elapsed_ms:.2f}ms")
|
139
|
+
assert elapsed_ms < 20
|
140
|
+
|
141
|
+
def test_mixed_operations(makcu):
|
142
|
+
"""Test mixed operation performance"""
|
143
|
+
start = time.perf_counter_ns()
|
144
|
+
|
145
|
+
# Mixed operations without loops
|
146
|
+
makcu.move(20, 20)
|
147
|
+
makcu.press(MouseButton.LEFT)
|
148
|
+
makcu.move(-20, -20)
|
149
|
+
makcu.release(MouseButton.LEFT)
|
150
|
+
makcu.scroll(1)
|
151
|
+
|
152
|
+
elapsed_ms = (time.perf_counter_ns() - start) / 1_000_000
|
153
|
+
print(f"Mixed operations: {elapsed_ms:.2f}ms")
|
154
|
+
assert elapsed_ms < 15
|
80
155
|
|
81
|
-
|
156
|
+
# Skip slow/unnecessary tests
|
157
|
+
@pytest.mark.skip(reason="Capture test disabled until firmware supports tracking clicks from software input")
|
158
|
+
def test_capture_right_clicks(makcu):
|
159
|
+
pass
|