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.
Files changed (42) hide show
  1. {urkit-0.4.3 → urkit-0.4.5}/PKG-INFO +1 -1
  2. {urkit-0.4.3 → urkit-0.4.5}/pyproject.toml +1 -1
  3. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/__init__.py +1 -1
  4. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/teach.py +91 -44
  5. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/terminal.py +126 -37
  6. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/PKG-INFO +1 -1
  7. {urkit-0.4.3 → urkit-0.4.5}/README.md +0 -0
  8. {urkit-0.4.3 → urkit-0.4.5}/setup.cfg +0 -0
  9. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/__main__.py +0 -0
  10. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/__init__.py +0 -0
  11. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/colors.py +0 -0
  12. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/connection_monitor.py +0 -0
  13. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/init.py +0 -0
  14. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/cli/points.py +0 -0
  15. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/config.py +0 -0
  16. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/connection.py +0 -0
  17. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/exceptions.py +0 -0
  18. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/geometry.py +0 -0
  19. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/__init__.py +0 -0
  20. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/base.py +0 -0
  21. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/digital.py +0 -0
  22. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/presets.py +0 -0
  23. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/robotiq.py +0 -0
  24. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/gripper/robotiq_preamble.py +0 -0
  25. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/io.py +0 -0
  26. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/motion.py +0 -0
  27. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/points.py +0 -0
  28. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/robot.py +0 -0
  29. {urkit-0.4.3 → urkit-0.4.5}/src/urkit/telemetry.py +0 -0
  30. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/SOURCES.txt +0 -0
  31. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/dependency_links.txt +0 -0
  32. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/entry_points.txt +0 -0
  33. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/requires.txt +0 -0
  34. {urkit-0.4.3 → urkit-0.4.5}/src/urkit.egg-info/top_level.txt +0 -0
  35. {urkit-0.4.3 → urkit-0.4.5}/tests/test_exceptions.py +0 -0
  36. {urkit-0.4.3 → urkit-0.4.5}/tests/test_geometry.py +0 -0
  37. {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper.py +0 -0
  38. {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper_factory.py +0 -0
  39. {urkit-0.4.3 → urkit-0.4.5}/tests/test_gripper_presets.py +0 -0
  40. {urkit-0.4.3 → urkit-0.4.5}/tests/test_move_sequence.py +0 -0
  41. {urkit-0.4.3 → urkit-0.4.5}/tests/test_points.py +0 -0
  42. {urkit-0.4.3 → urkit-0.4.5}/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.3
3
+ Version: 0.4.5
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.3"
7
+ version = "0.4.5"
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.3"
27
+ __version__ = "0.4.5"
28
28
 
29
29
 
30
30
  from urkit.exceptions import (
@@ -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
- # 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)
447
+ _raw_terminal = RawTerminal()
448
448
 
449
449
 
450
450
  def _configure_terminal() -> None:
451
- """Enter raw terminal mode (no canonical, echo on)."""
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
- raw_bytes = read_burst(0.1)
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
- if wait_input(0.05):
745
- key = read_burst(0.0).decode("ascii", errors="replace")
746
- if " " in key: # Space to cancel
747
- cancelled = True
748
- break
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
- raw_bytes = read_burst(0.1)
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
- raw_bytes = read_burst(0.1)
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
- # SIGINT handler: restore terminal and exit when Ctrl+C pressed.
1189
- # On Unix, raw mode keeps ISIG enabled, so Ctrl+C generates SIGINT.
1190
- # On Windows, Ctrl+C arrives as a raw 0x03 byte instead (handled by
1191
- # the main loop's exit key path). os._exit() works even when blocked
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 _sigint_handler(signum: int, frame: object) -> None:
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("\nInterrupted.\n")
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
- while True:
1245
- if wait_input(0.05):
1246
- break
1247
-
1248
- # Periodic refresh while in freedrive — robot may be moving by hand
1249
- if state["freedrive"]:
1250
- now = time.monotonic()
1251
- if now - last_refresh >= _FREEDRIVE_REFRESH_S:
1252
- last_refresh = now
1253
- _draw_screen(robot, state, messages, expert_mode)
1254
-
1255
- # Check for fault detected by watchdog thread
1256
- if monitor.fault_detected:
1257
- raise URKitConnectionError(
1258
- f"Robot fault detected: {monitor._reason or 'RTDE connection lost'}. "
1259
- "RTDE connection lost."
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
- # --- Read key, draining buffered repeats ---
1263
- # When a key is held, the terminal auto-repeats it into the
1264
- # input buffer. Reading only one character leaves stale repeats
1265
- # behind — when the user switches from one movement key to
1266
- # another, the last buffered copy of the old key fires one final
1267
- # move in the wrong direction before the new key is read.
1268
- # Fix: drain all immediately available bytes
1269
- # (non-blocking) and use the last one.
1270
- key_text = read_burst(0.0).decode("ascii", errors="replace")
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 + tty + select, exactly the syscalls the CLIs
13
- used to issue directly.
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 not a console). "
86
- "Run from an interactive shell."
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("SetConsoleMode failed")
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, mode | _ENABLE_VIRTUAL_TERMINAL_PROCESSING
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, 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
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 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
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
- 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)
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(self._stdin_handle, self._old_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()")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urkit
3
- Version: 0.4.3
3
+ Version: 0.4.5
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
File without changes