urkit 0.4.2__tar.gz → 0.4.4__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.2 → urkit-0.4.4}/PKG-INFO +1 -1
- {urkit-0.4.2 → urkit-0.4.4}/pyproject.toml +1 -1
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/__init__.py +1 -1
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/points.py +21 -11
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/teach.py +89 -54
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/terminal.py +65 -16
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/PKG-INFO +1 -1
- {urkit-0.4.2 → urkit-0.4.4}/README.md +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/setup.cfg +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/__main__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/colors.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/cli/init.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/config.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/connection.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/exceptions.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/geometry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/base.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/digital.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/robotiq.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/io.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/motion.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/points.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/robot.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit/telemetry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_exceptions.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_geometry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_gripper.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_gripper_factory.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_gripper_presets.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_move_sequence.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_points.py +0 -0
- {urkit-0.4.2 → urkit-0.4.4}/tests/test_robot_integration.py +0 -0
|
@@ -252,25 +252,35 @@ def _interactive_points_filter(points_db: Points, all_points: list[str], points_
|
|
|
252
252
|
# that arrives late; wait a bit before treating it as quit.
|
|
253
253
|
if text == "\x1b":
|
|
254
254
|
text = text + read_burst(0.2).decode("ascii", errors="replace")
|
|
255
|
-
if
|
|
255
|
+
if "[A" in text: # Up arrow - scroll up
|
|
256
256
|
scroll = max(0, scroll - 1)
|
|
257
257
|
needs_redraw = True
|
|
258
|
-
elif
|
|
258
|
+
elif "[B" in text: # Down arrow - scroll down
|
|
259
259
|
filtered = [p for p in all_points_sorted if filter_str == "" or filter_str.lower() in p.lower()]
|
|
260
260
|
scroll = min(len(filtered) - 1, scroll + 1) if filtered else 0
|
|
261
261
|
needs_redraw = True
|
|
262
262
|
else:
|
|
263
263
|
# Just ESC (or an unrecognized sequence) — quit
|
|
264
264
|
break
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
265
|
+
else:
|
|
266
|
+
# A burst can mix printable chars and backspaces
|
|
267
|
+
# (fast typing), so process sequentially.
|
|
268
|
+
quit_requested = False
|
|
269
|
+
for ch in text:
|
|
270
|
+
if ch == "\x03": # Ctrl+C (raw byte on Windows)
|
|
271
|
+
quit_requested = True
|
|
272
|
+
break
|
|
273
|
+
elif ch == "\x7f" or ch == "\x08": # Backspace
|
|
274
|
+
if filter_str:
|
|
275
|
+
filter_str = filter_str[:-1]
|
|
276
|
+
scroll = 0
|
|
277
|
+
needs_redraw = True
|
|
278
|
+
elif ch.isprintable():
|
|
279
|
+
filter_str += ch
|
|
280
|
+
scroll = 0
|
|
281
|
+
needs_redraw = True
|
|
282
|
+
if quit_requested:
|
|
283
|
+
break
|
|
274
284
|
|
|
275
285
|
except KeyboardInterrupt:
|
|
276
286
|
# Ctrl+C — exit gracefully without traceback
|
|
@@ -583,36 +583,44 @@ def _filter_select_points(
|
|
|
583
583
|
raw.enable()
|
|
584
584
|
|
|
585
585
|
try:
|
|
586
|
+
needs_redraw = True
|
|
586
587
|
while True:
|
|
587
|
-
#
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
cursor
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
588
|
+
# Redraw only when state changed. Redrawing every poll
|
|
589
|
+
# iteration (10x/s) is invisible on Linux terminals but
|
|
590
|
+
# flickers visibly on the Windows console.
|
|
591
|
+
if needs_redraw:
|
|
592
|
+
# Clear screen and draw
|
|
593
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
594
|
+
sys.stdout.write(cyan(f" === {title} ===") + "\n")
|
|
595
|
+
sys.stdout.write(dim(" Arrows navigate · Type to filter · Enter select · ESC cancel") + "\n\n")
|
|
596
|
+
sys.stdout.write(f" {blue('Search:')} {filter_str}\n")
|
|
597
|
+
sys.stdout.write(" " + dim("─" * 60) + "\n")
|
|
598
|
+
|
|
599
|
+
filtered = [p for p in all_points if filter_str == "" or filter_str.lower() in p.lower()]
|
|
600
|
+
|
|
601
|
+
# Clamp cursor to valid range
|
|
602
|
+
if not filtered:
|
|
603
|
+
cursor = 0
|
|
604
|
+
elif cursor >= len(filtered):
|
|
605
|
+
cursor = len(filtered) - 1
|
|
606
|
+
|
|
607
|
+
if not filtered:
|
|
608
|
+
sys.stdout.write(yellow(f" No points match '{filter_str}'") + "\n")
|
|
609
|
+
sys.stdout.write("\n " + dim("Type to search for points...") + "\n")
|
|
610
|
+
else:
|
|
611
|
+
sys.stdout.write(blue(" Matching points:") + "\n")
|
|
612
|
+
for i, p in enumerate(filtered):
|
|
613
|
+
marker = green("►") if i == cursor else " "
|
|
614
|
+
highlighted = _highlight_match(p, filter_str)
|
|
615
|
+
sys.stdout.write(f" {marker} {highlighted}\n")
|
|
616
|
+
|
|
617
|
+
sel = green("'" + filtered[cursor] + "'")
|
|
618
|
+
sys.stdout.write(f"\n {dim('Enter')} to select {sel}\n")
|
|
619
|
+
|
|
620
|
+
sys.stdout.flush()
|
|
621
|
+
needs_redraw = False
|
|
605
622
|
else:
|
|
606
|
-
|
|
607
|
-
for i, p in enumerate(filtered):
|
|
608
|
-
marker = green("►") if i == cursor else " "
|
|
609
|
-
highlighted = _highlight_match(p, filter_str)
|
|
610
|
-
sys.stdout.write(f" {marker} {highlighted}\n")
|
|
611
|
-
|
|
612
|
-
sel = green("'" + filtered[cursor] + "'")
|
|
613
|
-
sys.stdout.write(f"\n {dim('Enter')} to select {sel}\n")
|
|
614
|
-
|
|
615
|
-
sys.stdout.flush()
|
|
623
|
+
filtered = [p for p in all_points if filter_str == "" or filter_str.lower() in p.lower()]
|
|
616
624
|
|
|
617
625
|
# Wait for input, then read the full burst (e.g. the
|
|
618
626
|
# 3-byte \x1b[A arrow-key sequence) in one go.
|
|
@@ -628,6 +636,7 @@ def _filter_select_points(
|
|
|
628
636
|
# Decode and parse sequentially, handling multi-byte escape sequences
|
|
629
637
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
630
638
|
i = 0
|
|
639
|
+
changed = False
|
|
631
640
|
while i < len(text):
|
|
632
641
|
ch = text[i]
|
|
633
642
|
|
|
@@ -637,8 +646,10 @@ def _filter_select_points(
|
|
|
637
646
|
key = text[i + 2]
|
|
638
647
|
if key == "A":
|
|
639
648
|
cursor = max(0, cursor - 1)
|
|
649
|
+
changed = True
|
|
640
650
|
elif key == "B":
|
|
641
651
|
cursor = min(len(filtered) - 1, cursor + 1)
|
|
652
|
+
changed = True
|
|
642
653
|
# Unrecognized CSI sequences are ignored
|
|
643
654
|
i += 3
|
|
644
655
|
continue
|
|
@@ -652,12 +663,15 @@ def _filter_select_points(
|
|
|
652
663
|
return None
|
|
653
664
|
elif ch == "\x08" or ch == "\x7f":
|
|
654
665
|
filter_str = filter_str[:-1]
|
|
666
|
+
changed = True
|
|
655
667
|
elif ch == "\x03":
|
|
656
668
|
return None
|
|
657
669
|
elif ch.isprintable() and len(filter_str) < 30:
|
|
658
670
|
filter_str += ch
|
|
671
|
+
changed = True
|
|
659
672
|
|
|
660
673
|
i += 1
|
|
674
|
+
needs_redraw = changed
|
|
661
675
|
finally:
|
|
662
676
|
raw.disable()
|
|
663
677
|
|
|
@@ -869,19 +883,25 @@ def _submenu_orient_tcp(
|
|
|
869
883
|
raw.enable()
|
|
870
884
|
|
|
871
885
|
try:
|
|
886
|
+
needs_redraw = True
|
|
872
887
|
while True:
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
sys.stdout.write(
|
|
888
|
+
# Redraw only when the cursor moved. Redrawing every poll
|
|
889
|
+
# iteration (10x/s) is invisible on Linux terminals but
|
|
890
|
+
# flickers visibly on the Windows console.
|
|
891
|
+
if needs_redraw:
|
|
892
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
893
|
+
sys.stdout.write(cyan(" === ORIENT TCP ===") + "\n")
|
|
894
|
+
sys.stdout.write(dim(" Arrows navigate · Enter select · ESC cancel") + "\n\n")
|
|
895
|
+
sys.stdout.write(blue(" Point tool Z toward:") + "\n")
|
|
896
|
+
sys.stdout.write(" " + dim("─" * 60) + "\n")
|
|
897
|
+
|
|
898
|
+
for i, (label, _direction) in enumerate(_TCP_ORIENT_OPTIONS):
|
|
899
|
+
marker = green("►") if i == cursor else " "
|
|
900
|
+
sys.stdout.write(f" {marker} {label}\n")
|
|
882
901
|
|
|
883
|
-
|
|
884
|
-
|
|
902
|
+
sys.stdout.write(f"\n {dim('Enter')} to orient {green(_TCP_ORIENT_OPTIONS[cursor][0])}\n")
|
|
903
|
+
sys.stdout.flush()
|
|
904
|
+
needs_redraw = False
|
|
885
905
|
|
|
886
906
|
raw_bytes = read_burst(0.1)
|
|
887
907
|
if not raw_bytes:
|
|
@@ -894,6 +914,7 @@ def _submenu_orient_tcp(
|
|
|
894
914
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
895
915
|
i = 0
|
|
896
916
|
selected = None
|
|
917
|
+
moved = False
|
|
897
918
|
while i < len(text):
|
|
898
919
|
ch = text[i]
|
|
899
920
|
if ch == "\x1b":
|
|
@@ -901,8 +922,10 @@ def _submenu_orient_tcp(
|
|
|
901
922
|
key = text[i + 2]
|
|
902
923
|
if key == "A":
|
|
903
924
|
cursor = max(0, cursor - 1)
|
|
925
|
+
moved = True
|
|
904
926
|
elif key == "B":
|
|
905
927
|
cursor = min(len(_TCP_ORIENT_OPTIONS) - 1, cursor + 1)
|
|
928
|
+
moved = True
|
|
906
929
|
i += 3
|
|
907
930
|
continue
|
|
908
931
|
else:
|
|
@@ -913,6 +936,7 @@ def _submenu_orient_tcp(
|
|
|
913
936
|
return None
|
|
914
937
|
i += 1
|
|
915
938
|
|
|
939
|
+
needs_redraw = moved
|
|
916
940
|
if selected is not None:
|
|
917
941
|
break
|
|
918
942
|
finally:
|
|
@@ -975,22 +999,28 @@ def _submenu_freedrive_axes(
|
|
|
975
999
|
raw.enable()
|
|
976
1000
|
|
|
977
1001
|
try:
|
|
1002
|
+
needs_redraw = True
|
|
978
1003
|
while True:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1004
|
+
# Redraw only when the cursor moved or an axis toggled.
|
|
1005
|
+
# Redrawing every poll iteration (10x/s) is invisible on
|
|
1006
|
+
# Linux terminals but flickers visibly on the Windows console.
|
|
1007
|
+
if needs_redraw:
|
|
1008
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
1009
|
+
sys.stdout.write(cyan(" === FREEDRIVE AXES ===") + "\n")
|
|
1010
|
+
sys.stdout.write(dim(" Arrows navigate · Space toggle · Enter apply · ESC cancel") + "\n\n")
|
|
1011
|
+
sys.stdout.write(" " + dim("─" * 40) + "\n")
|
|
1012
|
+
|
|
1013
|
+
for i, label in enumerate(_FD_AXIS_LABELS):
|
|
1014
|
+
marker = green("►") if i == cursor else " "
|
|
1015
|
+
state_str = green("ON") if axes[i] else red("OFF")
|
|
1016
|
+
sys.stdout.write(f" {marker} {label:<6} {state_str}\n")
|
|
1017
|
+
|
|
1018
|
+
sys.stdout.write(" " + dim("─" * 40) + "\n")
|
|
1019
|
+
active = sum(axes)
|
|
1020
|
+
sys.stdout.write(f"\n {blue('Active axes:')} {green(str(active))} / 6\n")
|
|
1021
|
+
sys.stdout.write(f" {dim('Enter')} to apply · {dim('ESC')} to cancel\n")
|
|
1022
|
+
sys.stdout.flush()
|
|
1023
|
+
needs_redraw = False
|
|
994
1024
|
|
|
995
1025
|
raw_bytes = read_burst(0.1)
|
|
996
1026
|
if not raw_bytes:
|
|
@@ -998,6 +1028,7 @@ def _submenu_freedrive_axes(
|
|
|
998
1028
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
999
1029
|
i = 0
|
|
1000
1030
|
applied = False
|
|
1031
|
+
changed = False
|
|
1001
1032
|
while i < len(text):
|
|
1002
1033
|
ch = text[i]
|
|
1003
1034
|
if ch == "\x1b":
|
|
@@ -1005,20 +1036,24 @@ def _submenu_freedrive_axes(
|
|
|
1005
1036
|
key = text[i + 2]
|
|
1006
1037
|
if key == "A":
|
|
1007
1038
|
cursor = max(0, cursor - 1)
|
|
1039
|
+
changed = True
|
|
1008
1040
|
elif key == "B":
|
|
1009
1041
|
cursor = min(5, cursor + 1)
|
|
1042
|
+
changed = True
|
|
1010
1043
|
i += 3
|
|
1011
1044
|
continue
|
|
1012
1045
|
else:
|
|
1013
1046
|
return # ESC → cancel
|
|
1014
1047
|
if ch == " ":
|
|
1015
1048
|
axes[cursor] = 1 - axes[cursor]
|
|
1049
|
+
changed = True
|
|
1016
1050
|
elif ch == "\n" or ch == "\r":
|
|
1017
1051
|
applied = True
|
|
1018
1052
|
elif ch == "\x03":
|
|
1019
1053
|
return
|
|
1020
1054
|
i += 1
|
|
1021
1055
|
|
|
1056
|
+
needs_redraw = changed
|
|
1022
1057
|
if applied:
|
|
1023
1058
|
break
|
|
1024
1059
|
finally:
|
|
@@ -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
|
|
@@ -38,13 +38,21 @@ if sys.platform == "win32":
|
|
|
38
38
|
else:
|
|
39
39
|
import select
|
|
40
40
|
import termios
|
|
41
|
-
import tty
|
|
42
41
|
|
|
43
42
|
if sys.platform == "win32":
|
|
44
43
|
# CDLL is used instead of WinDLL because ctypes.WinDLL is only
|
|
45
44
|
# exposed in the Windows type stubs; on 64-bit Windows both use
|
|
46
45
|
# the same calling convention.
|
|
47
46
|
_kernel32 = ctypes.CDLL("kernel32", use_last_error=True)
|
|
47
|
+
# Declare prototypes. The default restype is c_int, which would
|
|
48
|
+
# truncate 64-bit HANDLE values from GetStdHandle.
|
|
49
|
+
_kernel32.GetStdHandle.argtypes = [ctypes.c_int]
|
|
50
|
+
_kernel32.GetStdHandle.restype = ctypes.c_void_p
|
|
51
|
+
_kernel32.GetConsoleMode.argtypes = [
|
|
52
|
+
ctypes.c_void_p,
|
|
53
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
54
|
+
]
|
|
55
|
+
_kernel32.SetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
|
48
56
|
_STD_INPUT_HANDLE = -10
|
|
49
57
|
_STD_OUTPUT_HANDLE = -11
|
|
50
58
|
_ENABLE_PROCESSED_INPUT = 0x0001
|
|
@@ -52,9 +60,10 @@ if sys.platform == "win32":
|
|
|
52
60
|
_ENABLE_ECHO_INPUT = 0x0004
|
|
53
61
|
_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
54
62
|
|
|
55
|
-
# msvcrt extended-key scan codes
|
|
56
|
-
#
|
|
57
|
-
#
|
|
63
|
+
# msvcrt extended-key scan codes after the 0xE0 prefix byte, mapped
|
|
64
|
+
# to the ANSI CSI sequences a Unix terminal emits for the same key.
|
|
65
|
+
# Keys carrying the 0x00 prefix are not translated: their scan codes
|
|
66
|
+
# overlap with arrow keys, and the CLIs don't use them.
|
|
58
67
|
_EXTENDED_KEYS: dict[int, str] = {
|
|
59
68
|
0x48: "\x1b[A", # Up
|
|
60
69
|
0x50: "\x1b[B", # Down
|
|
@@ -68,18 +77,35 @@ if sys.platform == "win32":
|
|
|
68
77
|
0x53: "\x1b[3~", # Delete
|
|
69
78
|
}
|
|
70
79
|
|
|
80
|
+
_WIN_ERROR_TEXT = {
|
|
81
|
+
5: "access denied",
|
|
82
|
+
6: "invalid handle",
|
|
83
|
+
87: "invalid parameter",
|
|
84
|
+
1225: "session disconnected",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def _win_error_detail() -> str:
|
|
88
|
+
code = ctypes.get_last_error()
|
|
89
|
+
return f"Win32 error {code} ({_WIN_ERROR_TEXT.get(code, 'unknown')})"
|
|
90
|
+
|
|
71
91
|
def _console_mode(handle: int) -> int:
|
|
72
92
|
mode = ctypes.c_uint32()
|
|
73
93
|
if not _kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
74
94
|
raise OSError(
|
|
75
|
-
"GetConsoleMode failed (stdin is
|
|
76
|
-
"
|
|
95
|
+
f"GetConsoleMode failed ({_win_error_detail()}). stdin is "
|
|
96
|
+
"not a usable console handle; run urkit from an "
|
|
97
|
+
"interactive shell such as cmd or PowerShell."
|
|
77
98
|
)
|
|
78
99
|
return mode.value
|
|
79
100
|
|
|
80
|
-
def _set_console_mode(handle: int, mode: int) -> None:
|
|
101
|
+
def _set_console_mode(handle: int, mode: int, context: str) -> None:
|
|
81
102
|
if not _kernel32.SetConsoleMode(handle, mode):
|
|
82
|
-
raise OSError(
|
|
103
|
+
raise OSError(
|
|
104
|
+
f"SetConsoleMode failed while {context} "
|
|
105
|
+
f"({_win_error_detail()}, requested mode "
|
|
106
|
+
f"0x{mode:08x}). The Windows console refused the "
|
|
107
|
+
"terminal mode change."
|
|
108
|
+
)
|
|
83
109
|
|
|
84
110
|
def _enable_ansi_output() -> None:
|
|
85
111
|
"""Enable ANSI escape processing on stdout.
|
|
@@ -92,7 +118,9 @@ if sys.platform == "win32":
|
|
|
92
118
|
handle = _kernel32.GetStdHandle(_STD_OUTPUT_HANDLE)
|
|
93
119
|
mode = _console_mode(handle)
|
|
94
120
|
_set_console_mode(
|
|
95
|
-
handle,
|
|
121
|
+
handle,
|
|
122
|
+
mode | _ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
|
123
|
+
"enabling ANSI output",
|
|
96
124
|
)
|
|
97
125
|
except OSError:
|
|
98
126
|
pass
|
|
@@ -138,10 +166,18 @@ class RawTerminal:
|
|
|
138
166
|
return
|
|
139
167
|
if sys.platform == "win32":
|
|
140
168
|
self._stdin_handle = _kernel32.GetStdHandle(_STD_INPUT_HANDLE)
|
|
169
|
+
if not self._stdin_handle:
|
|
170
|
+
raise OSError(
|
|
171
|
+
"stdin is not attached to a Windows console; run "
|
|
172
|
+
"urkit from an interactive shell such as cmd or "
|
|
173
|
+
"PowerShell."
|
|
174
|
+
)
|
|
141
175
|
self._old_mode = _console_mode(self._stdin_handle)
|
|
142
176
|
# Disable processed input, line input, and echo. Ctrl+C
|
|
143
177
|
# arrives as raw byte 0x03 (handled by the CLIs) instead
|
|
144
178
|
# of KeyboardInterrupt, which would skip terminal restore.
|
|
179
|
+
# Echo is always off: some consoles refuse raw mode with
|
|
180
|
+
# echo still enabled, and the CLIs render input themselves.
|
|
145
181
|
_set_console_mode(
|
|
146
182
|
self._stdin_handle,
|
|
147
183
|
self._old_mode
|
|
@@ -150,11 +186,17 @@ class RawTerminal:
|
|
|
150
186
|
| _ENABLE_LINE_INPUT
|
|
151
187
|
| _ENABLE_ECHO_INPUT
|
|
152
188
|
),
|
|
189
|
+
"entering raw mode",
|
|
153
190
|
)
|
|
154
191
|
_enable_ansi_output()
|
|
155
192
|
else:
|
|
156
193
|
self._old_settings = termios.tcgetattr(sys.stdin)
|
|
157
|
-
|
|
194
|
+
# No canonical mode, no echo; ISIG stays on so Ctrl+C
|
|
195
|
+
# still raises KeyboardInterrupt. Same state as the
|
|
196
|
+
# pre-shim menus and input prompts.
|
|
197
|
+
settings = termios.tcgetattr(sys.stdin)
|
|
198
|
+
settings[3] = settings[3] & ~(termios.ICANON | termios.ECHO)
|
|
199
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
|
|
158
200
|
self._enabled = True
|
|
159
201
|
|
|
160
202
|
def disable(self) -> None:
|
|
@@ -164,7 +206,9 @@ class RawTerminal:
|
|
|
164
206
|
if sys.platform == "win32":
|
|
165
207
|
if self._stdin_handle is None or self._old_mode is None:
|
|
166
208
|
raise RuntimeError("RawTerminal disabled before enable()")
|
|
167
|
-
_set_console_mode(
|
|
209
|
+
_set_console_mode(
|
|
210
|
+
self._stdin_handle, self._old_mode, "restoring terminal"
|
|
211
|
+
)
|
|
168
212
|
else:
|
|
169
213
|
if self._old_settings is None:
|
|
170
214
|
raise RuntimeError("RawTerminal disabled before enable()")
|
|
@@ -188,13 +232,18 @@ def _win_read_pending() -> bytes:
|
|
|
188
232
|
out = bytearray()
|
|
189
233
|
while msvcrt.kbhit():
|
|
190
234
|
byte = msvcrt.getch()
|
|
191
|
-
if byte
|
|
192
|
-
|
|
235
|
+
if byte == b"\xe0":
|
|
236
|
+
# getch() returns bytes; the scan code dict is keyed
|
|
237
|
+
# by int, so take the single byte's ordinal
|
|
238
|
+
code = msvcrt.getch()[0]
|
|
193
239
|
seq = _EXTENDED_KEYS.get(code)
|
|
194
240
|
if seq is None:
|
|
195
|
-
# Unrecognized
|
|
241
|
+
# Unrecognized extended key: drop it
|
|
196
242
|
continue
|
|
197
243
|
out += seq.encode("ascii")
|
|
244
|
+
elif byte == b"\x00":
|
|
245
|
+
# Untranslated extended prefix: consume the scan code
|
|
246
|
+
msvcrt.getch()
|
|
198
247
|
else:
|
|
199
248
|
out += byte
|
|
200
249
|
return bytes(out)
|
|
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
|