-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmouse_test.py
More file actions
164 lines (138 loc) · 5.32 KB
/
Copy pathmouse_test.py
File metadata and controls
164 lines (138 loc) · 5.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
from __future__ import annotations
import os
import re
import sys
import time
from dataclasses import dataclass
from typing import Optional
from blessed import Terminal
SCRIPT_FILE = os.path.abspath(__file__)
SCRIPT_DIR = os.path.dirname(SCRIPT_FILE)
APP_DIR = os.path.dirname(SCRIPT_DIR) if os.path.basename(SCRIPT_DIR).lower() == "scripts" else SCRIPT_DIR
try:
from pynput import mouse as pynput_mouse
except Exception:
pynput_mouse = None
@dataclass(frozen=True)
class MouseEvent:
button: int
x: int
y: int
pressed: bool
motion: bool
def parse_sgr_mouse(sequence: str) -> Optional[MouseEvent]:
match = re.search(r"\x1b\[<(\d+);(\d+);(\d+)([Mm])", sequence)
if match is None:
return None
code = int(match.group(1))
return MouseEvent(
button=code & 3,
x=int(match.group(2)),
y=int(match.group(3)),
pressed=match.group(4) == "M",
motion=(code & 32) == 32,
)
def enable_mouse() -> str:
# No 1003 any-motion here: it can flood Windows terminals and hide the ESC input.
return "\x1b[?1000h\x1b[?1002h\x1b[?1006h"
def disable_mouse() -> str:
return "\x1b[?1006l\x1b[?1002l\x1b[?1000l"
def collect(term: Terminal, first: object) -> str:
seq = str(first)
if not seq.startswith("\x1b"):
return seq
deadline = time.perf_counter() + 0.025
while time.perf_counter() < deadline:
nxt = term.inkey(timeout=0.001)
if not nxt:
break
seq += str(nxt)
if parse_sgr_mouse(seq) is not None:
break
if re.search(r"\x1b\[[0-9;?]*[A-Za-z~]$", seq):
break
return seq
def main() -> None:
term = Terminal()
events: list[str] = []
native_move_count = 0
last_native_line = "NATIVE MOVE count=0"
log_dir = os.environ.get("TERMINALMC_LOG_DIR", os.path.join(APP_DIR, "log"))
stamp = os.environ.get("TERMINALMC_LOG_STAMP", time.strftime("%Y%m%d_%H%M%S"))
os.makedirs(log_dir, exist_ok=True)
log_path = os.path.join(log_dir, f"mouse_test_events_{stamp}.log")
lang = os.environ.get("TERMINALMC_LANG", "en").lower()
def add_event(text: str) -> None:
events.append(text)
del events[:-18]
with open(log_path, "a", encoding="utf-8") as log_file:
log_file.write(text + "\n")
native_listener = None
if pynput_mouse is not None:
def on_move(x: int, y: int) -> None:
nonlocal native_move_count, last_native_line
native_move_count += 1
if native_move_count % 15 == 0:
last_native_line = f"NATIVE MOVE count={native_move_count} last=({x},{y})"
def on_click(x: int, y: int, button: object, pressed: bool) -> None:
add_event(f"NATIVE CLICK x={x} y={y} button={button} pressed={pressed}")
try:
native_listener = pynput_mouse.Listener(on_move=on_move, on_click=on_click)
native_listener.start()
except Exception as exc:
add_event(f"NATIVE MOUSE unavailable: {exc}")
else:
add_event("NATIVE MOUSE unavailable: pynput not installed")
with term.fullscreen(), term.cbreak(), term.hidden_cursor():
sys.stdout.write(enable_mouse() + "\x1b[2J\x1b[H")
sys.stdout.flush()
try:
while True:
if lang == "it":
lines = [
"TerminalMC - test mouse",
"PREMI ESC PER CHIUDERE IL TEST. Puoi anche premere Q.",
"Muovi il mouse, prova click sinistro e destro.",
"Diagnostica mouse SGR e mouse nativo pynput.",
last_native_line,
"",
]
else:
lines = [
"TerminalMC mouse test",
"PRESS ESC TO CLOSE THIS TEST. You can also press Q.",
"Move the mouse, left-click, right-click.",
"SGR mouse and native pynput mouse diagnostics.",
last_native_line,
"",
]
lines.extend(events[-18:])
sys.stdout.write("\x1b[H" + "\n".join(line.ljust(120) for line in lines))
sys.stdout.flush()
key = term.inkey(timeout=0.03)
if not key:
continue
raw = collect(term, key)
name = key.name or ""
lower = raw.lower()
if raw == "\x1b" or name == "KEY_ESCAPE" or lower == "q":
add_event("EXIT requested by ESC/Q")
break
event = parse_sgr_mouse(raw)
if event is None:
add_event(f"KEY raw={raw.encode('unicode_escape').decode()}")
else:
add_event(
f"SGR MOUSE button={event.button} x={event.x} y={event.y} "
f"pressed={event.pressed} motion={event.motion}"
)
finally:
if native_listener is not None:
try:
native_listener.stop()
except Exception:
pass
sys.stdout.write(disable_mouse() + "\x1b[0m\x1b[2J\x1b[H")
sys.stdout.flush()
if __name__ == "__main__":
main()