cpython/Lib/curses/__init__.py
Serhiy Storchaka ac023ea48f
gh-90092: Support multiple terminals in the curses module (GH-151748)
Add the X/Open Curses SCREEN API for driving more than one terminal:
newterm() and set_term(), plus the ncurses extension new_prescr().

A new screen object wraps the C SCREEN.  It exposes the terminal's
standard window as screen.stdscr.  Each window keeps a reference to its
screen (like a subwindow does to its parent window), so the screen is
deleted automatically once it and all of its windows are unreferenced.

The ncurses use_screen()/use_window() locking helpers are exposed as
the screen.use() and window.use() methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:33:02 +00:00

115 lines
3.8 KiB
Python

"""curses
The main package for curses support for Python. Normally used by importing
the package, and perhaps a particular module inside it.
import curses
from curses import textpad
curses.initscr()
...
"""
from _curses import *
import os as _os
import sys as _sys
# Some constants, most notably the ACS_* ones, are only added to the C
# _curses module's dictionary after initscr() is called. (Some
# versions of SGI's curses don't define values for those constants
# until initscr() has been called.) This wrapper function calls the
# underlying C initscr(), and then copies the constants from the
# _curses module to the curses package's dictionary. Don't do 'from
# curses import *' if you'll be needing the ACS_* constants.
def initscr():
import _curses, curses
# we call setupterm() here because it raises an error
# instead of calling exit() in error cases.
setupterm(term=_os.environ.get("TERM", "unknown"),
fd=_sys.__stdout__.fileno())
stdscr = _curses.initscr()
for key, value in _curses.__dict__.items():
if key.startswith('ACS_') or key in ('LINES', 'COLS'):
setattr(curses, key, value)
return stdscr
# newterm() is wrapped for the same reason as initscr(): the ACS_* constants
# and LINES/COLS only become available once a terminal is initialized, and are
# then copied to the curses package's dictionary.
try:
newterm
except NameError:
pass
else:
def newterm(type=None, fd=None, infd=None, /):
import _curses, curses
screen = _curses.newterm(type, fd, infd)
for key, value in _curses.__dict__.items():
if key.startswith('ACS_') or key in ('LINES', 'COLS'):
setattr(curses, key, value)
return screen
# This is a similar wrapper for start_color(), which adds the COLORS and
# COLOR_PAIRS variables which are only available after start_color() is
# called.
def start_color():
import _curses, curses
_curses.start_color()
curses.COLORS = _curses.COLORS
curses.COLOR_PAIRS = _curses.COLOR_PAIRS
# Import Python has_key() implementation if _curses doesn't contain has_key()
try:
has_key
except NameError:
from .has_key import has_key # noqa: F401
# Wrapper for the entire curses-based application. Runs a function which
# should be the rest of your curses-based application. If the application
# raises an exception, wrapper() will restore the terminal to a sane state so
# you can read the resulting traceback.
def wrapper(func, /, *args, **kwds):
"""Wrapper function that initializes curses and calls another function,
restoring normal keyboard/screen behavior on error.
The callable object 'func' is then passed the main window 'stdscr'
as its first argument, followed by any other arguments passed to
wrapper().
"""
try:
# Initialize curses
stdscr = initscr()
# Turn off echoing of keys, and enter cbreak mode,
# where no buffering is performed on keyboard input
noecho()
cbreak()
# In keypad mode, escape sequences for special keys
# (like the cursor keys) will be interpreted and
# a special value like curses.KEY_LEFT will be returned
stdscr.keypad(1)
# Start color, too. Harmless if the terminal doesn't have
# color; user can test with has_color() later on. The try/catch
# works around a minor bit of over-conscientiousness in the curses
# module -- the error return from C start_color() is ignorable,
# unless they are raised by the interpreter due to other issues.
try:
start_color()
except _curses.error:
pass
return func(stdscr, *args, **kwds)
finally:
# Set everything back to normal
if 'stdscr' in locals():
stdscr.keypad(0)
echo()
nocbreak()
endwin()