urkit 0.4.2__tar.gz → 0.4.3__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.3}/PKG-INFO +1 -1
- {urkit-0.4.2 → urkit-0.4.3}/pyproject.toml +1 -1
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/__init__.py +1 -1
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/points.py +21 -11
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/teach.py +93 -56
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/terminal.py +48 -19
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/PKG-INFO +1 -1
- {urkit-0.4.2 → urkit-0.4.3}/README.md +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/setup.cfg +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/__main__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/__init__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/colors.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/connection_monitor.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/init.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/config.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/connection.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/exceptions.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/geometry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/__init__.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/base.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/digital.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/presets.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/robotiq.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/robotiq_preamble.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/io.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/motion.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/points.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/robot.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit/telemetry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/SOURCES.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/dependency_links.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/entry_points.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/requires.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/top_level.txt +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_exceptions.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_geometry.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper_factory.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper_presets.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_move_sequence.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/tests/test_points.py +0 -0
- {urkit-0.4.2 → urkit-0.4.3}/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
|
|
@@ -442,11 +442,13 @@ def _draw_help() -> None:
|
|
|
442
442
|
|
|
443
443
|
# Raw terminal shared by the main loop, the SIGINT handler, and the
|
|
444
444
|
# exit path. All terminal setup goes through the cross-platform shim.
|
|
445
|
-
|
|
445
|
+
# echo=True matches the pre-shim cbreak behavior of the main loop
|
|
446
|
+
# (echo on, canonical off); submenus and prompts use echo=False.
|
|
447
|
+
_raw_terminal = RawTerminal(echo=True)
|
|
446
448
|
|
|
447
449
|
|
|
448
450
|
def _configure_terminal() -> None:
|
|
449
|
-
"""Enter raw terminal mode (no canonical,
|
|
451
|
+
"""Enter raw terminal mode (no canonical, echo on)."""
|
|
450
452
|
_raw_terminal.enable()
|
|
451
453
|
|
|
452
454
|
|
|
@@ -583,36 +585,44 @@ def _filter_select_points(
|
|
|
583
585
|
raw.enable()
|
|
584
586
|
|
|
585
587
|
try:
|
|
588
|
+
needs_redraw = True
|
|
586
589
|
while True:
|
|
587
|
-
#
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
cursor
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
590
|
+
# Redraw only when state changed. Redrawing every poll
|
|
591
|
+
# iteration (10x/s) is invisible on Linux terminals but
|
|
592
|
+
# flickers visibly on the Windows console.
|
|
593
|
+
if needs_redraw:
|
|
594
|
+
# Clear screen and draw
|
|
595
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
596
|
+
sys.stdout.write(cyan(f" === {title} ===") + "\n")
|
|
597
|
+
sys.stdout.write(dim(" Arrows navigate · Type to filter · Enter select · ESC cancel") + "\n\n")
|
|
598
|
+
sys.stdout.write(f" {blue('Search:')} {filter_str}\n")
|
|
599
|
+
sys.stdout.write(" " + dim("─" * 60) + "\n")
|
|
600
|
+
|
|
601
|
+
filtered = [p for p in all_points if filter_str == "" or filter_str.lower() in p.lower()]
|
|
602
|
+
|
|
603
|
+
# Clamp cursor to valid range
|
|
604
|
+
if not filtered:
|
|
605
|
+
cursor = 0
|
|
606
|
+
elif cursor >= len(filtered):
|
|
607
|
+
cursor = len(filtered) - 1
|
|
608
|
+
|
|
609
|
+
if not filtered:
|
|
610
|
+
sys.stdout.write(yellow(f" No points match '{filter_str}'") + "\n")
|
|
611
|
+
sys.stdout.write("\n " + dim("Type to search for points...") + "\n")
|
|
612
|
+
else:
|
|
613
|
+
sys.stdout.write(blue(" Matching points:") + "\n")
|
|
614
|
+
for i, p in enumerate(filtered):
|
|
615
|
+
marker = green("►") if i == cursor else " "
|
|
616
|
+
highlighted = _highlight_match(p, filter_str)
|
|
617
|
+
sys.stdout.write(f" {marker} {highlighted}\n")
|
|
618
|
+
|
|
619
|
+
sel = green("'" + filtered[cursor] + "'")
|
|
620
|
+
sys.stdout.write(f"\n {dim('Enter')} to select {sel}\n")
|
|
621
|
+
|
|
622
|
+
sys.stdout.flush()
|
|
623
|
+
needs_redraw = False
|
|
605
624
|
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()
|
|
625
|
+
filtered = [p for p in all_points if filter_str == "" or filter_str.lower() in p.lower()]
|
|
616
626
|
|
|
617
627
|
# Wait for input, then read the full burst (e.g. the
|
|
618
628
|
# 3-byte \x1b[A arrow-key sequence) in one go.
|
|
@@ -628,6 +638,7 @@ def _filter_select_points(
|
|
|
628
638
|
# Decode and parse sequentially, handling multi-byte escape sequences
|
|
629
639
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
630
640
|
i = 0
|
|
641
|
+
changed = False
|
|
631
642
|
while i < len(text):
|
|
632
643
|
ch = text[i]
|
|
633
644
|
|
|
@@ -637,8 +648,10 @@ def _filter_select_points(
|
|
|
637
648
|
key = text[i + 2]
|
|
638
649
|
if key == "A":
|
|
639
650
|
cursor = max(0, cursor - 1)
|
|
651
|
+
changed = True
|
|
640
652
|
elif key == "B":
|
|
641
653
|
cursor = min(len(filtered) - 1, cursor + 1)
|
|
654
|
+
changed = True
|
|
642
655
|
# Unrecognized CSI sequences are ignored
|
|
643
656
|
i += 3
|
|
644
657
|
continue
|
|
@@ -652,12 +665,15 @@ def _filter_select_points(
|
|
|
652
665
|
return None
|
|
653
666
|
elif ch == "\x08" or ch == "\x7f":
|
|
654
667
|
filter_str = filter_str[:-1]
|
|
668
|
+
changed = True
|
|
655
669
|
elif ch == "\x03":
|
|
656
670
|
return None
|
|
657
671
|
elif ch.isprintable() and len(filter_str) < 30:
|
|
658
672
|
filter_str += ch
|
|
673
|
+
changed = True
|
|
659
674
|
|
|
660
675
|
i += 1
|
|
676
|
+
needs_redraw = changed
|
|
661
677
|
finally:
|
|
662
678
|
raw.disable()
|
|
663
679
|
|
|
@@ -869,19 +885,25 @@ def _submenu_orient_tcp(
|
|
|
869
885
|
raw.enable()
|
|
870
886
|
|
|
871
887
|
try:
|
|
888
|
+
needs_redraw = True
|
|
872
889
|
while True:
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
sys.stdout.write(
|
|
890
|
+
# Redraw only when the cursor moved. Redrawing every poll
|
|
891
|
+
# iteration (10x/s) is invisible on Linux terminals but
|
|
892
|
+
# flickers visibly on the Windows console.
|
|
893
|
+
if needs_redraw:
|
|
894
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
895
|
+
sys.stdout.write(cyan(" === ORIENT TCP ===") + "\n")
|
|
896
|
+
sys.stdout.write(dim(" Arrows navigate · Enter select · ESC cancel") + "\n\n")
|
|
897
|
+
sys.stdout.write(blue(" Point tool Z toward:") + "\n")
|
|
898
|
+
sys.stdout.write(" " + dim("─" * 60) + "\n")
|
|
899
|
+
|
|
900
|
+
for i, (label, _direction) in enumerate(_TCP_ORIENT_OPTIONS):
|
|
901
|
+
marker = green("►") if i == cursor else " "
|
|
902
|
+
sys.stdout.write(f" {marker} {label}\n")
|
|
882
903
|
|
|
883
|
-
|
|
884
|
-
|
|
904
|
+
sys.stdout.write(f"\n {dim('Enter')} to orient {green(_TCP_ORIENT_OPTIONS[cursor][0])}\n")
|
|
905
|
+
sys.stdout.flush()
|
|
906
|
+
needs_redraw = False
|
|
885
907
|
|
|
886
908
|
raw_bytes = read_burst(0.1)
|
|
887
909
|
if not raw_bytes:
|
|
@@ -894,6 +916,7 @@ def _submenu_orient_tcp(
|
|
|
894
916
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
895
917
|
i = 0
|
|
896
918
|
selected = None
|
|
919
|
+
moved = False
|
|
897
920
|
while i < len(text):
|
|
898
921
|
ch = text[i]
|
|
899
922
|
if ch == "\x1b":
|
|
@@ -901,8 +924,10 @@ def _submenu_orient_tcp(
|
|
|
901
924
|
key = text[i + 2]
|
|
902
925
|
if key == "A":
|
|
903
926
|
cursor = max(0, cursor - 1)
|
|
927
|
+
moved = True
|
|
904
928
|
elif key == "B":
|
|
905
929
|
cursor = min(len(_TCP_ORIENT_OPTIONS) - 1, cursor + 1)
|
|
930
|
+
moved = True
|
|
906
931
|
i += 3
|
|
907
932
|
continue
|
|
908
933
|
else:
|
|
@@ -913,6 +938,7 @@ def _submenu_orient_tcp(
|
|
|
913
938
|
return None
|
|
914
939
|
i += 1
|
|
915
940
|
|
|
941
|
+
needs_redraw = moved
|
|
916
942
|
if selected is not None:
|
|
917
943
|
break
|
|
918
944
|
finally:
|
|
@@ -975,22 +1001,28 @@ def _submenu_freedrive_axes(
|
|
|
975
1001
|
raw.enable()
|
|
976
1002
|
|
|
977
1003
|
try:
|
|
1004
|
+
needs_redraw = True
|
|
978
1005
|
while True:
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1006
|
+
# Redraw only when the cursor moved or an axis toggled.
|
|
1007
|
+
# Redrawing every poll iteration (10x/s) is invisible on
|
|
1008
|
+
# Linux terminals but flickers visibly on the Windows console.
|
|
1009
|
+
if needs_redraw:
|
|
1010
|
+
sys.stdout.write("\033[2J\033[1;1H")
|
|
1011
|
+
sys.stdout.write(cyan(" === FREEDRIVE AXES ===") + "\n")
|
|
1012
|
+
sys.stdout.write(dim(" Arrows navigate · Space toggle · Enter apply · ESC cancel") + "\n\n")
|
|
1013
|
+
sys.stdout.write(" " + dim("─" * 40) + "\n")
|
|
1014
|
+
|
|
1015
|
+
for i, label in enumerate(_FD_AXIS_LABELS):
|
|
1016
|
+
marker = green("►") if i == cursor else " "
|
|
1017
|
+
state_str = green("ON") if axes[i] else red("OFF")
|
|
1018
|
+
sys.stdout.write(f" {marker} {label:<6} {state_str}\n")
|
|
1019
|
+
|
|
1020
|
+
sys.stdout.write(" " + dim("─" * 40) + "\n")
|
|
1021
|
+
active = sum(axes)
|
|
1022
|
+
sys.stdout.write(f"\n {blue('Active axes:')} {green(str(active))} / 6\n")
|
|
1023
|
+
sys.stdout.write(f" {dim('Enter')} to apply · {dim('ESC')} to cancel\n")
|
|
1024
|
+
sys.stdout.flush()
|
|
1025
|
+
needs_redraw = False
|
|
994
1026
|
|
|
995
1027
|
raw_bytes = read_burst(0.1)
|
|
996
1028
|
if not raw_bytes:
|
|
@@ -998,6 +1030,7 @@ def _submenu_freedrive_axes(
|
|
|
998
1030
|
text = raw_bytes.decode("ascii", errors="replace")
|
|
999
1031
|
i = 0
|
|
1000
1032
|
applied = False
|
|
1033
|
+
changed = False
|
|
1001
1034
|
while i < len(text):
|
|
1002
1035
|
ch = text[i]
|
|
1003
1036
|
if ch == "\x1b":
|
|
@@ -1005,20 +1038,24 @@ def _submenu_freedrive_axes(
|
|
|
1005
1038
|
key = text[i + 2]
|
|
1006
1039
|
if key == "A":
|
|
1007
1040
|
cursor = max(0, cursor - 1)
|
|
1041
|
+
changed = True
|
|
1008
1042
|
elif key == "B":
|
|
1009
1043
|
cursor = min(5, cursor + 1)
|
|
1044
|
+
changed = True
|
|
1010
1045
|
i += 3
|
|
1011
1046
|
continue
|
|
1012
1047
|
else:
|
|
1013
1048
|
return # ESC → cancel
|
|
1014
1049
|
if ch == " ":
|
|
1015
1050
|
axes[cursor] = 1 - axes[cursor]
|
|
1051
|
+
changed = True
|
|
1016
1052
|
elif ch == "\n" or ch == "\r":
|
|
1017
1053
|
applied = True
|
|
1018
1054
|
elif ch == "\x03":
|
|
1019
1055
|
return
|
|
1020
1056
|
i += 1
|
|
1021
1057
|
|
|
1058
|
+
needs_redraw = changed
|
|
1022
1059
|
if applied:
|
|
1023
1060
|
break
|
|
1024
1061
|
finally:
|
|
@@ -45,6 +45,15 @@ if sys.platform == "win32":
|
|
|
45
45
|
# exposed in the Windows type stubs; on 64-bit Windows both use
|
|
46
46
|
# the same calling convention.
|
|
47
47
|
_kernel32 = ctypes.CDLL("kernel32", use_last_error=True)
|
|
48
|
+
# Declare prototypes. The default restype is c_int, which would
|
|
49
|
+
# truncate 64-bit HANDLE values from GetStdHandle.
|
|
50
|
+
_kernel32.GetStdHandle.argtypes = [ctypes.c_int]
|
|
51
|
+
_kernel32.GetStdHandle.restype = ctypes.c_void_p
|
|
52
|
+
_kernel32.GetConsoleMode.argtypes = [
|
|
53
|
+
ctypes.c_void_p,
|
|
54
|
+
ctypes.POINTER(ctypes.c_uint32),
|
|
55
|
+
]
|
|
56
|
+
_kernel32.SetConsoleMode.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
|
|
48
57
|
_STD_INPUT_HANDLE = -10
|
|
49
58
|
_STD_OUTPUT_HANDLE = -11
|
|
50
59
|
_ENABLE_PROCESSED_INPUT = 0x0001
|
|
@@ -52,9 +61,10 @@ if sys.platform == "win32":
|
|
|
52
61
|
_ENABLE_ECHO_INPUT = 0x0004
|
|
53
62
|
_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
|
54
63
|
|
|
55
|
-
# msvcrt extended-key scan codes
|
|
56
|
-
#
|
|
57
|
-
#
|
|
64
|
+
# msvcrt extended-key scan codes after the 0xE0 prefix byte, mapped
|
|
65
|
+
# to the ANSI CSI sequences a Unix terminal emits for the same key.
|
|
66
|
+
# Keys carrying the 0x00 prefix are not translated: their scan codes
|
|
67
|
+
# overlap with arrow keys, and the CLIs don't use them.
|
|
58
68
|
_EXTENDED_KEYS: dict[int, str] = {
|
|
59
69
|
0x48: "\x1b[A", # Up
|
|
60
70
|
0x50: "\x1b[B", # Down
|
|
@@ -126,7 +136,15 @@ class RawTerminal:
|
|
|
126
136
|
raw.disable() # idempotent, safe to call multiple times
|
|
127
137
|
"""
|
|
128
138
|
|
|
129
|
-
def __init__(self) -> None:
|
|
139
|
+
def __init__(self, echo: bool = False) -> None:
|
|
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
|
|
130
148
|
self._enabled = False
|
|
131
149
|
self._old_settings: list[Any] | None = None
|
|
132
150
|
self._old_mode: int | None = None
|
|
@@ -139,22 +157,28 @@ class RawTerminal:
|
|
|
139
157
|
if sys.platform == "win32":
|
|
140
158
|
self._stdin_handle = _kernel32.GetStdHandle(_STD_INPUT_HANDLE)
|
|
141
159
|
self._old_mode = _console_mode(self._stdin_handle)
|
|
142
|
-
# Disable processed input
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
& ~(
|
|
149
|
-
_ENABLE_PROCESSED_INPUT
|
|
150
|
-
| _ENABLE_LINE_INPUT
|
|
151
|
-
| _ENABLE_ECHO_INPUT
|
|
152
|
-
),
|
|
160
|
+
# Disable processed input and line input. Ctrl+C arrives
|
|
161
|
+
# as raw byte 0x03 (handled by the CLIs) instead of
|
|
162
|
+
# KeyboardInterrupt, which would skip terminal restore.
|
|
163
|
+
# Echo is kept only when echo=True.
|
|
164
|
+
mode = self._old_mode & ~(
|
|
165
|
+
_ENABLE_PROCESSED_INPUT | _ENABLE_LINE_INPUT
|
|
153
166
|
)
|
|
167
|
+
if not self._echo:
|
|
168
|
+
mode &= ~_ENABLE_ECHO_INPUT
|
|
169
|
+
_set_console_mode(self._stdin_handle, mode)
|
|
154
170
|
_enable_ansi_output()
|
|
155
171
|
else:
|
|
156
172
|
self._old_settings = termios.tcgetattr(sys.stdin)
|
|
157
|
-
|
|
173
|
+
if self._echo:
|
|
174
|
+
# cbreak: no canonical mode, echo and signals stay on
|
|
175
|
+
tty.setcbreak(sys.stdin.fileno())
|
|
176
|
+
else:
|
|
177
|
+
# No canonical mode, no echo (matches the menus' and
|
|
178
|
+
# input prompts' pre-shim termios setup)
|
|
179
|
+
settings = termios.tcgetattr(sys.stdin)
|
|
180
|
+
settings[3] = settings[3] & ~(termios.ICANON | termios.ECHO)
|
|
181
|
+
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, settings)
|
|
158
182
|
self._enabled = True
|
|
159
183
|
|
|
160
184
|
def disable(self) -> None:
|
|
@@ -188,13 +212,18 @@ def _win_read_pending() -> bytes:
|
|
|
188
212
|
out = bytearray()
|
|
189
213
|
while msvcrt.kbhit():
|
|
190
214
|
byte = msvcrt.getch()
|
|
191
|
-
if byte
|
|
192
|
-
|
|
215
|
+
if byte == b"\xe0":
|
|
216
|
+
# getch() returns bytes; the scan code dict is keyed
|
|
217
|
+
# by int, so take the single byte's ordinal
|
|
218
|
+
code = msvcrt.getch()[0]
|
|
193
219
|
seq = _EXTENDED_KEYS.get(code)
|
|
194
220
|
if seq is None:
|
|
195
|
-
# Unrecognized
|
|
221
|
+
# Unrecognized extended key: drop it
|
|
196
222
|
continue
|
|
197
223
|
out += seq.encode("ascii")
|
|
224
|
+
elif byte == b"\x00":
|
|
225
|
+
# Untranslated extended prefix: consume the scan code
|
|
226
|
+
msvcrt.getch()
|
|
198
227
|
else:
|
|
199
228
|
out += byte
|
|
200
229
|
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
|