urkit 0.4.3__tar.gz → 0.4.5__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.
- {urkit-0.4.3 → urkit-0.4.5}/PKG-INFO +1 -1
- {urkit-0.4.3 → urkit-0.4.5}/pyproject.toml +1 -1
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/__init__.py +1 -1
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/teach.py +91 -44
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/terminal.py +126 -37
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/PKG-INFO +1 -1
- {urkit-0.4.3 → urkit-0.4.5}/README.md +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/setup.cfg +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/__main__.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/colors.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/init.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/points.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/config.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/connection.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/exceptions.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/geometry.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/base.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/digital.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/robotiq.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/io.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/motion.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/points.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/robot.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit/telemetry.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_exceptions.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_geometry.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper_factory.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper_presets.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_move_sequence.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_points.py +0 -0
- {urkit-0.4.3 → urkit-0.4.5}/tests/test_robot_integration.py +0 -0
|
@@ -26,7 +26,9 @@ import yaml
|
|
|
26
26
|
from urkit.cli.colors import blue, cyan, dim, green, red, yellow
|
|
27
27
|
from urkit.cli.terminal import (
|
|
28
28
|
RawTerminal,
|
|
29
|
+
input_active,
|
|
29
30
|
read_burst,
|
|
31
|
+
start_interrupt_watcher,
|
|
30
32
|
wait_input,
|
|
31
33
|
warn_if_windows,
|
|
32
34
|
)
|
|
@@ -442,13 +444,11 @@ def _draw_help() -> None:
|
|
|
442
444
|
|
|
443
445
|
# Raw terminal shared by the main loop, the SIGINT handler, and the
|
|
444
446
|
# exit path. All terminal setup goes through the cross-platform shim.
|
|
445
|
-
|
|
446
|
-
# (echo on, canonical off); submenus and prompts use echo=False.
|
|
447
|
-
_raw_terminal = RawTerminal(echo=True)
|
|
447
|
+
_raw_terminal = RawTerminal()
|
|
448
448
|
|
|
449
449
|
|
|
450
450
|
def _configure_terminal() -> None:
|
|
451
|
-
"""Enter raw terminal mode (no canonical, echo
|
|
451
|
+
"""Enter raw terminal mode (no canonical, no echo)."""
|
|
452
452
|
_raw_terminal.enable()
|
|
453
453
|
|
|
454
454
|
|
|
@@ -477,6 +477,7 @@ def _read_input(prompt: str, max_len: int = 30) -> str | None:
|
|
|
477
477
|
sys.stdout.flush()
|
|
478
478
|
|
|
479
479
|
name = ""
|
|
480
|
+
input_active.set()
|
|
480
481
|
try:
|
|
481
482
|
while True:
|
|
482
483
|
if not wait_input(0.1):
|
|
@@ -513,6 +514,7 @@ def _read_input(prompt: str, max_len: int = 30) -> str | None:
|
|
|
513
514
|
break
|
|
514
515
|
finally:
|
|
515
516
|
raw.disable()
|
|
517
|
+
input_active.clear()
|
|
516
518
|
|
|
517
519
|
sys.stdout.write("\r\033[K\n")
|
|
518
520
|
sys.stdout.flush()
|
|
@@ -626,7 +628,11 @@ def _filter_select_points(
|
|
|
626
628
|
|
|
627
629
|
# Wait for input, then read the full burst (e.g. the
|
|
628
630
|
# 3-byte \x1b[A arrow-key sequence) in one go.
|
|
629
|
-
|
|
631
|
+
input_active.set()
|
|
632
|
+
try:
|
|
633
|
+
raw_bytes = read_burst(0.1)
|
|
634
|
+
finally:
|
|
635
|
+
input_active.clear()
|
|
630
636
|
if not raw_bytes:
|
|
631
637
|
if _cli_monitor and _cli_monitor.fault_detected:
|
|
632
638
|
raise URKitConnectionError(
|
|
@@ -741,11 +747,15 @@ def _submenu_goto_point(
|
|
|
741
747
|
robot, name, mode_label, start_pose, target_pose, is_cartesian, max_progress
|
|
742
748
|
)
|
|
743
749
|
max_progress = pct
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
if
|
|
747
|
-
|
|
748
|
-
|
|
750
|
+
input_active.set()
|
|
751
|
+
try:
|
|
752
|
+
if wait_input(0.05):
|
|
753
|
+
key = read_burst(0.0).decode("ascii", errors="replace")
|
|
754
|
+
if " " in key: # Space to cancel
|
|
755
|
+
cancelled = True
|
|
756
|
+
break
|
|
757
|
+
finally:
|
|
758
|
+
input_active.clear()
|
|
749
759
|
|
|
750
760
|
# Clear moving screen
|
|
751
761
|
sys.stdout.write("\033[2J\033[1;1H")
|
|
@@ -905,7 +915,11 @@ def _submenu_orient_tcp(
|
|
|
905
915
|
sys.stdout.flush()
|
|
906
916
|
needs_redraw = False
|
|
907
917
|
|
|
908
|
-
|
|
918
|
+
input_active.set()
|
|
919
|
+
try:
|
|
920
|
+
raw_bytes = read_burst(0.1)
|
|
921
|
+
finally:
|
|
922
|
+
input_active.clear()
|
|
909
923
|
if not raw_bytes:
|
|
910
924
|
if _cli_monitor and _cli_monitor.fault_detected:
|
|
911
925
|
raise URKitConnectionError(
|
|
@@ -1024,7 +1038,11 @@ def _submenu_freedrive_axes(
|
|
|
1024
1038
|
sys.stdout.flush()
|
|
1025
1039
|
needs_redraw = False
|
|
1026
1040
|
|
|
1027
|
-
|
|
1041
|
+
input_active.set()
|
|
1042
|
+
try:
|
|
1043
|
+
raw_bytes = read_burst(0.1)
|
|
1044
|
+
finally:
|
|
1045
|
+
input_active.clear()
|
|
1028
1046
|
if not raw_bytes:
|
|
1029
1047
|
continue
|
|
1030
1048
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
@@ -1185,12 +1203,12 @@ def _teach_pendant(
|
|
|
1185
1203
|
print("Error: stdin is not a terminal. Run from an interactive shell.")
|
|
1186
1204
|
sys.exit(1)
|
|
1187
1205
|
|
|
1188
|
-
#
|
|
1189
|
-
#
|
|
1190
|
-
#
|
|
1191
|
-
#
|
|
1206
|
+
# Best-effort robot stop and terminal restore, then exit.
|
|
1207
|
+
# Runs from the SIGINT handler (Unix Ctrl+C) and from the
|
|
1208
|
+
# interrupt watcher (Windows Ctrl+C / fault while the main thread
|
|
1209
|
+
# is blocked in a robot call). os._exit() works even when blocked
|
|
1192
1210
|
# in C library code.
|
|
1193
|
-
def
|
|
1211
|
+
def _interrupt_exit(reason: str) -> None:
|
|
1194
1212
|
try:
|
|
1195
1213
|
if state["freedrive"]:
|
|
1196
1214
|
robot.disable_freedrive()
|
|
@@ -1209,9 +1227,16 @@ def _teach_pendant(
|
|
|
1209
1227
|
_restore_terminal()
|
|
1210
1228
|
except Exception:
|
|
1211
1229
|
pass
|
|
1212
|
-
sys.stderr.write("\
|
|
1230
|
+
sys.stderr.write(f"\n{reason}\n")
|
|
1213
1231
|
os._exit(0)
|
|
1214
1232
|
|
|
1233
|
+
# SIGINT handler: restore terminal and exit when Ctrl+C pressed.
|
|
1234
|
+
# On Unix, raw mode keeps ISIG enabled, so Ctrl+C generates SIGINT.
|
|
1235
|
+
# On Windows, Ctrl+C arrives as a raw 0x03 byte instead (handled by
|
|
1236
|
+
# the main loop's exit key path and the interrupt watcher).
|
|
1237
|
+
def _sigint_handler(signum: int, frame: object) -> None:
|
|
1238
|
+
_interrupt_exit("Interrupted.")
|
|
1239
|
+
|
|
1215
1240
|
signal.signal(signal.SIGINT, _sigint_handler)
|
|
1216
1241
|
|
|
1217
1242
|
# Connection watchdog: detects faults and interrupts blocking calls.
|
|
@@ -1227,6 +1252,21 @@ def _teach_pendant(
|
|
|
1227
1252
|
global _cli_monitor
|
|
1228
1253
|
_cli_monitor = monitor # used by _read_input and _filter_select_points
|
|
1229
1254
|
|
|
1255
|
+
# Windows escape hatch: no SIGINT delivery and no SIGALRM, so while
|
|
1256
|
+
# the main thread is blocked in a long RTDE call (e.g. a move while
|
|
1257
|
+
# a key is held) neither Ctrl+C (a console byte) nor a detected
|
|
1258
|
+
# fault can reach it. The watcher fires the same cleanup and exit.
|
|
1259
|
+
def _watcher_fault_reason() -> str | None:
|
|
1260
|
+
if monitor.fault_detected:
|
|
1261
|
+
return (
|
|
1262
|
+
f"Robot fault detected: "
|
|
1263
|
+
f"{monitor._reason or 'RTDE connection lost'}. "
|
|
1264
|
+
"RTDE connection lost."
|
|
1265
|
+
)
|
|
1266
|
+
return None
|
|
1267
|
+
|
|
1268
|
+
start_interrupt_watcher(_interrupt_exit, _watcher_fault_reason)
|
|
1269
|
+
|
|
1230
1270
|
try:
|
|
1231
1271
|
try:
|
|
1232
1272
|
_configure_terminal()
|
|
@@ -1241,33 +1281,40 @@ def _teach_pendant(
|
|
|
1241
1281
|
command_handled = False
|
|
1242
1282
|
|
|
1243
1283
|
# --- Wait for a key ---
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1284
|
+
# input_active tells the Windows interrupt watcher to
|
|
1285
|
+
# stay out of the console queue while we read.
|
|
1286
|
+
input_active.set()
|
|
1287
|
+
try:
|
|
1288
|
+
while True:
|
|
1289
|
+
if wait_input(0.05):
|
|
1290
|
+
break
|
|
1291
|
+
|
|
1292
|
+
# Periodic refresh while in freedrive — robot may be moving by hand
|
|
1293
|
+
if state["freedrive"]:
|
|
1294
|
+
now = time.monotonic()
|
|
1295
|
+
if now - last_refresh >= _FREEDRIVE_REFRESH_S:
|
|
1296
|
+
last_refresh = now
|
|
1297
|
+
_draw_screen(robot, state, messages, expert_mode)
|
|
1298
|
+
|
|
1299
|
+
# Check for fault detected by watchdog thread
|
|
1300
|
+
if monitor.fault_detected:
|
|
1301
|
+
raise URKitConnectionError(
|
|
1302
|
+
f"Robot fault detected: {monitor._reason or 'RTDE connection lost'}. "
|
|
1303
|
+
"RTDE connection lost."
|
|
1304
|
+
)
|
|
1261
1305
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1306
|
+
# --- Read key, draining buffered repeats ---
|
|
1307
|
+
# When a key is held, the terminal auto-repeats it into the
|
|
1308
|
+
# input buffer. Reading only one character leaves stale
|
|
1309
|
+
# repeats behind — when the user switches from one
|
|
1310
|
+
# movement key to another, the last buffered copy of the
|
|
1311
|
+
# old key fires one final move in the wrong direction
|
|
1312
|
+
# before the new key is read.
|
|
1313
|
+
# Fix: drain all immediately available bytes
|
|
1314
|
+
# (non-blocking) and use the last one.
|
|
1315
|
+
key_text = read_burst(0.0).decode("ascii", errors="replace")
|
|
1316
|
+
finally:
|
|
1317
|
+
input_active.clear()
|
|
1271
1318
|
key = key_text[-1].lower() if key_text else ""
|
|
1272
1319
|
if not key:
|
|
1273
1320
|
continue
|
|
@@ -9,8 +9,8 @@ The CLIs need three things from the platform:
|
|
|
9
9
|
3. On Windows only: ANSI escape processing on stdout so colors and
|
|
10
10
|
cursor codes render.
|
|
11
11
|
|
|
12
|
-
Unix backend: termios +
|
|
13
|
-
|
|
12
|
+
Unix backend: termios + select, the same syscalls the CLIs used to
|
|
13
|
+
issue directly.
|
|
14
14
|
|
|
15
15
|
Windows backend: msvcrt with console input mode changed via kernel32.
|
|
16
16
|
The console does not emit ANSI sequences for special keys, so the
|
|
@@ -30,15 +30,15 @@ from __future__ import annotations
|
|
|
30
30
|
import ctypes
|
|
31
31
|
import os
|
|
32
32
|
import sys
|
|
33
|
+
import threading
|
|
33
34
|
import time
|
|
34
|
-
from typing import Any
|
|
35
|
+
from typing import Any, Callable
|
|
35
36
|
|
|
36
37
|
if sys.platform == "win32":
|
|
37
38
|
import msvcrt
|
|
38
39
|
else:
|
|
39
40
|
import select
|
|
40
41
|
import termios
|
|
41
|
-
import tty
|
|
42
42
|
|
|
43
43
|
if sys.platform == "win32":
|
|
44
44
|
# CDLL is used instead of WinDLL because ctypes.WinDLL is only
|
|
@@ -78,18 +78,35 @@ if sys.platform == "win32":
|
|
|
78
78
|
0x53: "\x1b[3~", # Delete
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
_WIN_ERROR_TEXT = {
|
|
82
|
+
5: "access denied",
|
|
83
|
+
6: "invalid handle",
|
|
84
|
+
87: "invalid parameter",
|
|
85
|
+
1225: "session disconnected",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
def _win_error_detail() -> str:
|
|
89
|
+
code = ctypes.get_last_error()
|
|
90
|
+
return f"Win32 error {code} ({_WIN_ERROR_TEXT.get(code, 'unknown')})"
|
|
91
|
+
|
|
81
92
|
def _console_mode(handle: int) -> int:
|
|
82
93
|
mode = ctypes.c_uint32()
|
|
83
94
|
if not _kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
84
95
|
raise OSError(
|
|
85
|
-
"GetConsoleMode failed (stdin is
|
|
86
|
-
"
|
|
96
|
+
f"GetConsoleMode failed ({_win_error_detail()}). stdin is "
|
|
97
|
+
"not a usable console handle; run urkit from an "
|
|
98
|
+
"interactive shell such as cmd or PowerShell."
|
|
87
99
|
)
|
|
88
100
|
return mode.value
|
|
89
101
|
|
|
90
|
-
def _set_console_mode(handle: int, mode: int) -> None:
|
|
102
|
+
def _set_console_mode(handle: int, mode: int, context: str) -> None:
|
|
91
103
|
if not _kernel32.SetConsoleMode(handle, mode):
|
|
92
|
-
raise OSError(
|
|
104
|
+
raise OSError(
|
|
105
|
+
f"SetConsoleMode failed while {context} "
|
|
106
|
+
f"({_win_error_detail()}, requested mode "
|
|
107
|
+
f"0x{mode:08x}). The Windows console refused the "
|
|
108
|
+
"terminal mode change."
|
|
109
|
+
)
|
|
93
110
|
|
|
94
111
|
def _enable_ansi_output() -> None:
|
|
95
112
|
"""Enable ANSI escape processing on stdout.
|
|
@@ -102,12 +119,82 @@ if sys.platform == "win32":
|
|
|
102
119
|
handle = _kernel32.GetStdHandle(_STD_OUTPUT_HANDLE)
|
|
103
120
|
mode = _console_mode(handle)
|
|
104
121
|
_set_console_mode(
|
|
105
|
-
handle,
|
|
122
|
+
handle,
|
|
123
|
+
mode | _ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
|
124
|
+
"enabling ANSI output",
|
|
106
125
|
)
|
|
107
126
|
except OSError:
|
|
108
127
|
pass
|
|
109
128
|
|
|
110
129
|
|
|
130
|
+
input_active = threading.Event()
|
|
131
|
+
"""Set while CLI code is actively reading stdin (input-wait loops, text
|
|
132
|
+
prompts, menus). The Windows interrupt watcher stays out of the console
|
|
133
|
+
input queue while this is set, so it can never steal a keystroke from
|
|
134
|
+
the main reader. Callers set it around their read sections and clear it
|
|
135
|
+
afterwards."""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def start_interrupt_watcher(
|
|
139
|
+
on_fire: Callable[[str], None],
|
|
140
|
+
fault_reason: Callable[[], str | None],
|
|
141
|
+
) -> threading.Thread | None:
|
|
142
|
+
"""Start the Windows-only interrupt watcher. Returns None elsewhere.
|
|
143
|
+
|
|
144
|
+
On Windows, while the main thread is blocked in a long RTDE call
|
|
145
|
+
(not reading input) nothing can interrupt it: Ctrl+C is a raw
|
|
146
|
+
console byte that only the input loops read, and there is no
|
|
147
|
+
SIGALRM. This daemon thread covers that gap. It polls, only while
|
|
148
|
+
input_active is clear:
|
|
149
|
+
|
|
150
|
+
- fault_reason() for a connection/robot fault
|
|
151
|
+
- the console for a Ctrl+C (0x03) byte; any other byte is pushed
|
|
152
|
+
back with ungetch() so the main reader still sees it, and the
|
|
153
|
+
watcher backs off to avoid interfering with resumed input
|
|
154
|
+
|
|
155
|
+
When either fires it calls on_fire(reason) exactly once; the
|
|
156
|
+
caller should clean up (stop robot, restore terminal) and exit.
|
|
157
|
+
"""
|
|
158
|
+
if sys.platform != "win32":
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
# Only reachable on Windows (guard above), so this never runs on
|
|
162
|
+
# Linux. Local import because mypy prunes the top-level platform
|
|
163
|
+
# guard and cannot see the module inside this closure.
|
|
164
|
+
import msvcrt
|
|
165
|
+
|
|
166
|
+
def _run() -> None:
|
|
167
|
+
backoff = 0.0
|
|
168
|
+
while True:
|
|
169
|
+
if backoff > 0:
|
|
170
|
+
time.sleep(backoff)
|
|
171
|
+
backoff = 0.0
|
|
172
|
+
if input_active.is_set():
|
|
173
|
+
time.sleep(0.05)
|
|
174
|
+
continue
|
|
175
|
+
reason = fault_reason()
|
|
176
|
+
if reason is not None:
|
|
177
|
+
on_fire(reason)
|
|
178
|
+
return
|
|
179
|
+
if not msvcrt.kbhit():
|
|
180
|
+
time.sleep(0.05)
|
|
181
|
+
continue
|
|
182
|
+
byte = msvcrt.getch()
|
|
183
|
+
if byte == b"\x03":
|
|
184
|
+
on_fire("Interrupted.")
|
|
185
|
+
return
|
|
186
|
+
# Not Ctrl+C: give the byte back and back off so the main
|
|
187
|
+
# reader (resuming from a blocking call) processes it.
|
|
188
|
+
msvcrt.ungetch(byte[0])
|
|
189
|
+
backoff = 0.2
|
|
190
|
+
|
|
191
|
+
watcher = threading.Thread(
|
|
192
|
+
target=_run, daemon=True, name="interrupt-watcher"
|
|
193
|
+
)
|
|
194
|
+
watcher.start()
|
|
195
|
+
return watcher
|
|
196
|
+
|
|
197
|
+
|
|
111
198
|
def warn_if_windows() -> None:
|
|
112
199
|
"""Print a notice when the interactive CLI runs on Windows.
|
|
113
200
|
|
|
@@ -136,15 +223,7 @@ class RawTerminal:
|
|
|
136
223
|
raw.disable() # idempotent, safe to call multiple times
|
|
137
224
|
"""
|
|
138
225
|
|
|
139
|
-
def __init__(self
|
|
140
|
-
"""Args:
|
|
141
|
-
echo: Keep terminal echo enabled (cbreak semantics).
|
|
142
|
-
The main teach loop uses echo=True to match the
|
|
143
|
-
pre-shim behavior; menus and input prompts use the
|
|
144
|
-
default echo=False because they display characters
|
|
145
|
-
themselves.
|
|
146
|
-
"""
|
|
147
|
-
self._echo = echo
|
|
226
|
+
def __init__(self) -> None:
|
|
148
227
|
self._enabled = False
|
|
149
228
|
self._old_settings: list[Any] | None = None
|
|
150
229
|
self._old_mode: int | None = None
|
|
@@ -156,29 +235,37 @@ class RawTerminal:
|
|
|
156
235
|
return
|
|
157
236
|
if sys.platform == "win32":
|
|
158
237
|
self._stdin_handle = _kernel32.GetStdHandle(_STD_INPUT_HANDLE)
|
|
238
|
+
if not self._stdin_handle:
|
|
239
|
+
raise OSError(
|
|
240
|
+
"stdin is not attached to a Windows console; run "
|
|
241
|
+
"urkit from an interactive shell such as cmd or "
|
|
242
|
+
"PowerShell."
|
|
243
|
+
)
|
|
159
244
|
self._old_mode = _console_mode(self._stdin_handle)
|
|
160
|
-
# Disable processed input
|
|
161
|
-
# as raw byte 0x03 (handled by the CLIs) instead
|
|
162
|
-
# KeyboardInterrupt, which would skip terminal restore.
|
|
163
|
-
# Echo is
|
|
164
|
-
|
|
165
|
-
|
|
245
|
+
# Disable processed input, line input, and echo. Ctrl+C
|
|
246
|
+
# arrives as raw byte 0x03 (handled by the CLIs) instead
|
|
247
|
+
# of KeyboardInterrupt, which would skip terminal restore.
|
|
248
|
+
# Echo is always off: some consoles refuse raw mode with
|
|
249
|
+
# echo still enabled, and the CLIs render input themselves.
|
|
250
|
+
_set_console_mode(
|
|
251
|
+
self._stdin_handle,
|
|
252
|
+
self._old_mode
|
|
253
|
+
& ~(
|
|
254
|
+
_ENABLE_PROCESSED_INPUT
|
|
255
|
+
| _ENABLE_LINE_INPUT
|
|
256
|
+
| _ENABLE_ECHO_INPUT
|
|
257
|
+
),
|
|
258
|
+
"entering raw mode",
|
|
166
259
|
)
|
|
167
|
-
if not self._echo:
|
|
168
|
-
mode &= ~_ENABLE_ECHO_INPUT
|
|
169
|
-
_set_console_mode(self._stdin_handle, mode)
|
|
170
260
|
_enable_ansi_output()
|
|
171
261
|
else:
|
|
172
262
|
self._old_settings = termios.tcgetattr(sys.stdin)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
settings = termios.tcgetattr(sys.stdin)
|
|
180
|
-
settings[3] = settings[3] & ~(termios.ICANON | termios.ECHO)
|
|
181
|
-
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
|
|
263
|
+
# No canonical mode, no echo; ISIG stays on so Ctrl+C
|
|
264
|
+
# still raises KeyboardInterrupt. Same state as the
|
|
265
|
+
# pre-shim menus and input prompts.
|
|
266
|
+
settings = termios.tcgetattr(sys.stdin)
|
|
267
|
+
settings[3] = settings[3] & ~(termios.ICANON | termios.ECHO)
|
|
268
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
|
|
182
269
|
self._enabled = True
|
|
183
270
|
|
|
184
271
|
def disable(self) -> None:
|
|
@@ -188,7 +275,9 @@ class RawTerminal:
|
|
|
188
275
|
if sys.platform == "win32":
|
|
189
276
|
if self._stdin_handle is None or self._old_mode is None:
|
|
190
277
|
raise RuntimeError("RawTerminal disabled before enable()")
|
|
191
|
-
_set_console_mode(
|
|
278
|
+
_set_console_mode(
|
|
279
|
+
self._stdin_handle, self._old_mode, "restoring terminal"
|
|
280
|
+
)
|
|
192
281
|
else:
|
|
193
282
|
if self._old_settings is None:
|
|
194
283
|
raise RuntimeError("RawTerminal disabled before enable()")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|