Python’s msvcrt module (Windows only) exposes the Microsoft Visual C Runtime’s console I/O and file-management routines. import msvcrt. Keypress: msvcrt.kbhit() → True if a key is waiting; msvcrt.getch() → bytes (1 byte, no echo, no newline wait); msvcrt.getwch() → str (wide char); msvcrt.getche() → bytes (echoes char). Write: msvcrt.putch(b'x') — one byte to stdout without newline; msvcrt.putwch('x') — wide char. Push back: msvcrt.ungetch(b'x'). File mode: msvcrt.setmode(fd, os.O_BINARY) — switch a file descriptor to binary mode; msvcrt.setmode(fd, os.O_TEXT) — text mode. File lock: msvcrt.locking(fd, msvcrt.LK_NBLCK, nbytes) — non-blocking lock; msvcrt.locking(fd, msvcrt.LK_UNLCK, nbytes) — unlock. Handle bridge: handle = msvcrt.get_osfhandle(fd) — C fd → Windows HANDLE; fd = msvcrt.open_osfhandle(handle, os.O_RDONLY) — HANDLE → C fd. Claude Code generates Windows CLI menus, keystroke-driven TUI apps, non-blocking keyboard loops, file-locking utilities, and console progress spinners.
CLAUDE.md for msvcrt
## msvcrt Stack
- Stdlib: import msvcrt, os (Windows only)
- Poll: msvcrt.kbhit() # True if key waiting
- Read: msvcrt.getch() # bytes, no echo
- msvcrt.getche() # bytes, with echo
- msvcrt.getwch() # str wide char
- Write: msvcrt.putch(b'.') # one byte, no newline
- Mode: msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
- Lock: msvcrt.locking(fd, msvcrt.LK_NBLCK, n) # lock n bytes
- msvcrt.locking(fd, msvcrt.LK_UNLCK, n) # unlock
- Handle: msvcrt.get_osfhandle(fd) # fd → Windows HANDLE
msvcrt Console and File Pipeline
# app/msvcrtutil.py — keypress, menu, progress, file locking, handle bridge
from __future__ import annotations
import os
import sys
import time
import threading
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Callable, Iterator
# Only import msvcrt on Windows
_MSVCRT_AVAILABLE = False
if os.name == "nt":
try:
import msvcrt as _ms
_MSVCRT_AVAILABLE = True
except ImportError:
pass
# ─────────────────────────────────────────────────────────────────────────────
# 1. Single-keypress helpers
# ─────────────────────────────────────────────────────────────────────────────
def kbhit() -> bool:
"""
Return True if a keypress is waiting in the input buffer.
Always returns False on non-Windows platforms.
Example:
if kbhit():
ch = getch()
"""
if _MSVCRT_AVAILABLE:
return _ms.kbhit()
return False
def getch() -> bytes | None:
"""
Read one keypress without echo. Returns None on non-Windows.
Extended keys (arrows, F-keys) return b'\\x00' or b'\\xe0' as first byte;
call getch() a second time to get the scan code.
Example:
ch = getch() # b'a', b'\\r' for Enter, etc.
if ch in (b'\\x00', b'\\xe0'):
scan = getch() # extended key scan code
"""
if _MSVCRT_AVAILABLE:
return _ms.getch()
return None
def getche() -> bytes | None:
"""Read one keypress, echo it. Returns None on non-Windows."""
if _MSVCRT_AVAILABLE:
return _ms.getche()
return None
def getwch() -> str | None:
"""Read one wide-character keypress without echo. Returns None on non-Windows."""
if _MSVCRT_AVAILABLE:
return _ms.getwch()
return None
def putch(char: bytes) -> None:
"""
Write one byte to the console without a trailing newline.
Example:
for c in b"Loading...":
putch(bytes([c]))
time.sleep(0.05)
"""
if _MSVCRT_AVAILABLE:
_ms.putch(char[:1])
else:
sys.stdout.write(char.decode("latin-1", errors="replace"))
sys.stdout.flush()
# ─────────────────────────────────────────────────────────────────────────────
# 2. Interactive menu
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class MenuItem:
key: str # single character key
label: str # display label
action: Callable[[], None]
def run_menu(title: str, items: list[MenuItem], quit_key: str = "q") -> None:
"""
Display a keystroke-driven menu and dispatch actions.
Press quit_key to exit. Falls back to input() on non-Windows.
Example:
run_menu("My App", [
MenuItem("1", "Say hello", lambda: print("Hello!")),
MenuItem("2", "Show time", lambda: print(time.ctime())),
])
"""
while True:
print(f"\n{'=' * 40}")
print(f" {title}")
print(f"{'=' * 40}")
for item in items:
print(f" [{item.key}] {item.label}")
print(f" [{quit_key}] Quit")
print("Press a key: ", end="", flush=True)
if _MSVCRT_AVAILABLE:
ch = _ms.getch()
key = ch.decode("latin-1", errors="replace")
else:
key = input().strip()[:1]
print(key)
if key == quit_key:
break
for item in items:
if key == item.key:
item.action()
break
# ─────────────────────────────────────────────────────────────────────────────
# 3. Non-blocking keyboard loop
# ─────────────────────────────────────────────────────────────────────────────
def wait_for_key(timeout_s: float = 5.0, prompt: str = "Press any key...") -> bytes | None:
"""
Wait up to timeout_s seconds for a keypress. Returns the key bytes
or None if timed out. Non-blocking on Windows; blocking fallback elsewhere.
Example:
key = wait_for_key(3.0, "Press a key within 3 seconds: ")
if key is None:
print("Timed out")
"""
print(prompt, end="", flush=True)
if not _MSVCRT_AVAILABLE:
return None # no-op on non-Windows
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
if _ms.kbhit():
ch = _ms.getch()
print()
return ch
time.sleep(0.05)
print()
return None
# ─────────────────────────────────────────────────────────────────────────────
# 4. Console spinner with keypress stop
# ─────────────────────────────────────────────────────────────────────────────
@contextmanager
def console_spinner(message: str = "Working") -> Iterator[None]:
"""
Show a spinner in the console while a block runs.
On Windows, pressing any key stops the spinner early.
Example:
with console_spinner("Processing"):
time.sleep(3)
"""
_stop = threading.Event()
frames = ["|", "/", "-", "\\"]
def _spin() -> None:
idx = 0
while not _stop.is_set():
frame = frames[idx % len(frames)]
sys.stdout.write(f"\r{message}... {frame} ")
sys.stdout.flush()
if _MSVCRT_AVAILABLE and _ms.kbhit():
_ms.getch() # consume the key
_stop.set()
break
time.sleep(0.1)
idx += 1
sys.stdout.write(f"\r{message}... done\n")
sys.stdout.flush()
t = threading.Thread(target=_spin, daemon=True)
t.start()
try:
yield
finally:
_stop.set()
t.join(timeout=0.5)
# ─────────────────────────────────────────────────────────────────────────────
# 5. File mode + byte-range locking
# ─────────────────────────────────────────────────────────────────────────────
def set_binary_mode(fd: int) -> None:
"""
Switch a file descriptor to binary mode (no CR/LF translation).
No-op on non-Windows.
Example:
set_binary_mode(sys.stdout.fileno())
"""
if _MSVCRT_AVAILABLE:
_ms.setmode(fd, os.O_BINARY)
@contextmanager
def locked_region(fd: int, nbytes: int, offset: int = 0) -> Iterator[None]:
"""
Lock a byte region of an open file descriptor for exclusive access,
yield, then unlock. Uses LK_NBLCK (non-blocking, raises OSError if busy).
No-op on non-Windows.
Example:
with open("data.bin", "r+b") as f:
with locked_region(f.fileno(), 128, offset=0):
f.seek(0)
data = f.read(128)
"""
if not _MSVCRT_AVAILABLE:
yield
return
import msvcrt as _ms2
# seek to offset before locking
os.lseek(fd, offset, os.SEEK_SET)
_ms2.locking(fd, _ms2.LK_NBLCK, nbytes)
try:
yield
finally:
os.lseek(fd, offset, os.SEEK_SET)
_ms2.locking(fd, _ms2.LK_UNLCK, nbytes)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== msvcrt demo ===")
print(f" Windows available: {_MSVCRT_AVAILABLE}")
# ── putch ─────────────────────────────────────────────────────────────────
print("\n--- putch progress bar ---")
sys.stdout.write(" [")
for _ in range(20):
putch(b"#")
time.sleep(0.02)
sys.stdout.write("]\n")
sys.stdout.flush()
# ── wait_for_key ──────────────────────────────────────────────────────────
print("\n--- wait_for_key (1s timeout) ---")
key = wait_for_key(1.0, " Press any key within 1s: ")
if key is None:
print(" Timed out (no key pressed or non-Windows)")
else:
print(f" Got key: {key!r}")
# ── console_spinner ───────────────────────────────────────────────────────
print("\n--- console_spinner (1s work) ---")
with console_spinner(" Computing"):
time.sleep(1.0)
# ── set_binary_mode ───────────────────────────────────────────────────────
print("\n--- set_binary_mode ---")
try:
set_binary_mode(sys.stdout.fileno())
print(" stdout switched to binary mode (Windows only)")
except Exception as e:
print(f" set_binary_mode: {e}")
# ── locked_region ─────────────────────────────────────────────────────────
print("\n--- locked_region ---")
import tempfile
with tempfile.NamedTemporaryFile(delete=False, suffix=".bin") as tf:
tf.write(b"\x00" * 256)
tmp_path = tf.name
try:
with open(tmp_path, "r+b") as f:
with locked_region(f.fileno(), 64, offset=0):
f.seek(0)
f.write(b"A" * 64)
print(f" wrote 64 bytes under lock to {tmp_path}")
data = open(tmp_path, "rb").read(64)
print(f" first 8 bytes: {data[:8]!r}")
finally:
os.unlink(tmp_path)
print("\n=== done ===")
For the keyboard (PyPI) alternative — keyboard.read_key() and keyboard.on_press(callback) provide cross-platform hotkey registration, key name resolution, and global keyboard hooks — use keyboard for cross-platform hotkeys, global shortcuts, or remapping keys; use msvcrt for lightweight Windows-only console input loops with zero dependencies. For the readchar (PyPI) alternative — readchar.readchar() and readchar.readkey() abstract over msvcrt.getch() on Windows and tty/termios on Unix — use readchar when you need a cross-platform single-keypress API; use msvcrt directly when you need the full Windows API (locking, setmode, get_osfhandle) or want zero dependencies. The Claude Skills 360 bundle includes msvcrt skill sets covering kbhit()/getch()/getche()/getwch() keypress helpers, putch() console writer, run_menu() keystroke dispatcher, wait_for_key() timed reader, console_spinner() threading context manager, set_binary_mode() fd helper, and locked_region() byte-range file lock. Start with the free tier to try Windows console patterns and msvcrt pipeline code generation.