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.
Files changed (42) hide show
  1. {urkit-0.4.2 → urkit-0.4.3}/PKG-INFO +1 -1
  2. {urkit-0.4.2 → urkit-0.4.3}/pyproject.toml +1 -1
  3. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/__init__.py +1 -1
  4. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/points.py +21 -11
  5. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/teach.py +93 -56
  6. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/terminal.py +48 -19
  7. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/PKG-INFO +1 -1
  8. {urkit-0.4.2 → urkit-0.4.3}/README.md +0 -0
  9. {urkit-0.4.2 → urkit-0.4.3}/setup.cfg +0 -0
  10. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/__main__.py +0 -0
  11. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/__init__.py +0 -0
  12. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/colors.py +0 -0
  13. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/connection_monitor.py +0 -0
  14. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/cli/init.py +0 -0
  15. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/config.py +0 -0
  16. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/connection.py +0 -0
  17. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/exceptions.py +0 -0
  18. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/geometry.py +0 -0
  19. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/__init__.py +0 -0
  20. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/base.py +0 -0
  21. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/digital.py +0 -0
  22. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/presets.py +0 -0
  23. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/robotiq.py +0 -0
  24. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/gripper/robotiq_preamble.py +0 -0
  25. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/io.py +0 -0
  26. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/motion.py +0 -0
  27. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/points.py +0 -0
  28. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/robot.py +0 -0
  29. {urkit-0.4.2 → urkit-0.4.3}/src/urkit/telemetry.py +0 -0
  30. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/SOURCES.txt +0 -0
  31. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/dependency_links.txt +0 -0
  32. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/entry_points.txt +0 -0
  33. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/requires.txt +0 -0
  34. {urkit-0.4.2 → urkit-0.4.3}/src/urkit.egg-info/top_level.txt +0 -0
  35. {urkit-0.4.2 → urkit-0.4.3}/tests/test_exceptions.py +0 -0
  36. {urkit-0.4.2 → urkit-0.4.3}/tests/test_geometry.py +0 -0
  37. {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper.py +0 -0
  38. {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper_factory.py +0 -0
  39. {urkit-0.4.2 → urkit-0.4.3}/tests/test_gripper_presets.py +0 -0
  40. {urkit-0.4.2 → urkit-0.4.3}/tests/test_move_sequence.py +0 -0
  41. {urkit-0.4.2 → urkit-0.4.3}/tests/test_points.py +0 -0
  42. {urkit-0.4.2 → urkit-0.4.3}/tests/test_robot_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "urkit"
7
- version = "0.4.2"
7
+ version = "0.4.3"
8
8
  description = "Universal Robots e-Series control toolkit built on ur_rtde"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -24,7 +24,7 @@ Quick start::
24
24
 
25
25
  from __future__ import annotations
26
26
 
27
- __version__ = "0.4.2"
27
+ __version__ = "0.4.3"
28
28
 
29
29
 
30
30
  from urkit.exceptions import (
@@ -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 text.endswith("[A"): # Up arrow - scroll up
255
+ if "[A" in text: # Up arrow - scroll up
256
256
  scroll = max(0, scroll - 1)
257
257
  needs_redraw = True
258
- elif text.endswith("[B"): # Down arrow - scroll down
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
- elif text == "\x7f" or text == "\x08": # Backspace
266
- if filter_str:
267
- filter_str = filter_str[:-1]
268
- scroll = 0
269
- needs_redraw = True
270
- elif text.isprintable():
271
- filter_str += text
272
- scroll = 0
273
- needs_redraw = True
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
- _raw_terminal = RawTerminal()
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, no echo)."""
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
- # Clear screen and draw
588
- sys.stdout.write("\033[2J\033[1;1H")
589
- sys.stdout.write(cyan(f" === {title} ===") + "\n")
590
- sys.stdout.write(dim(" Arrows navigate · Type to filter · Enter select · ESC cancel") + "\n\n")
591
- sys.stdout.write(f" {blue('Search:')} {filter_str}\n")
592
- sys.stdout.write(" " + dim("─" * 60) + "\n")
593
-
594
- filtered = [p for p in all_points if filter_str == "" or filter_str.lower() in p.lower()]
595
-
596
- # Clamp cursor to valid range
597
- if not filtered:
598
- cursor = 0
599
- elif cursor >= len(filtered):
600
- cursor = len(filtered) - 1
601
-
602
- if not filtered:
603
- sys.stdout.write(yellow(f" No points match '{filter_str}'") + "\n")
604
- sys.stdout.write("\n " + dim("Type to search for points...") + "\n")
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
- sys.stdout.write(blue(" Matching points:") + "\n")
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
- sys.stdout.write("\033[2J\033[1;1H")
874
- sys.stdout.write(cyan(" === ORIENT TCP ===") + "\n")
875
- sys.stdout.write(dim(" Arrows navigate · Enter select · ESC cancel") + "\n\n")
876
- sys.stdout.write(blue(" Point tool Z toward:") + "\n")
877
- sys.stdout.write(" " + dim("─" * 60) + "\n")
878
-
879
- for i, (label, _direction) in enumerate(_TCP_ORIENT_OPTIONS):
880
- marker = green("►") if i == cursor else " "
881
- sys.stdout.write(f" {marker} {label}\n")
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
- sys.stdout.write(f"\n {dim('Enter')} to orient {green(_TCP_ORIENT_OPTIONS[cursor][0])}\n")
884
- sys.stdout.flush()
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
- sys.stdout.write("\033[2J\033[1;1H")
980
- sys.stdout.write(cyan(" === FREEDRIVE AXES ===") + "\n")
981
- sys.stdout.write(dim(" Arrows navigate · Space toggle · Enter apply · ESC cancel") + "\n\n")
982
- sys.stdout.write(" " + dim("─" * 40) + "\n")
983
-
984
- for i, label in enumerate(_FD_AXIS_LABELS):
985
- marker = green("►") if i == cursor else " "
986
- state_str = green("ON") if axes[i] else red("OFF")
987
- sys.stdout.write(f" {marker} {label:<6} {state_str}\n")
988
-
989
- sys.stdout.write(" " + dim("" * 40) + "\n")
990
- active = sum(axes)
991
- sys.stdout.write(f"\n {blue('Active axes:')} {green(str(active))} / 6\n")
992
- sys.stdout.write(f" {dim('Enter')} to apply · {dim('ESC')} to cancel\n")
993
- sys.stdout.flush()
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 (after the 0xE0/0x00 prefix byte)
56
- # mapped to the ANSI CSI sequences a Unix terminal emits for the
57
- # same key.
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, line input, and echo. Ctrl+C
143
- # arrives as raw byte 0x03 (handled by the CLIs) instead
144
- # of KeyboardInterrupt, which would skip terminal restore.
145
- _set_console_mode(
146
- self._stdin_handle,
147
- self._old_mode
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
- tty.setcbreak(sys.stdin.fileno())
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 in (b"\xe0", b"\x00"):
192
- code = msvcrt.getch()
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 function key (F1-F12 etc.): drop it
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Universal Robots e-Series control toolkit built on ur_rtde
5
5
  Author: URKit Contributors
6
6
  License: MIT
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