Програмування Curses на Python

Автор:

A.M. Kuchling, Eric S. Raymond

Реліз:

2.04

Що таке прокляття?

Бібліотека curses надає незалежне від терміналу засіб малювання екрану та керування клавіатурою для текстових терміналів; такі термінали включають VT100s, консоль Linux і імітований термінал, що надається різними програмами. Дисплейні термінали підтримують різні керуючі коди для виконання типових операцій, таких як переміщення курсору, прокручування екрана та стирання областей. Різні термінали використовують дуже різні коди та часто мають власні незначні особливості.

У світі графічних дисплеїв хтось може запитати «навіщо турбуватися»? Це правда, що дисплеї з символьними комірками є застарілою технологією, але є ніші, в яких можливість робити з ними фантастичні речі все ще цінна. Одна ніша займає малогабаритні або вбудовані системи Unix, на яких не працює X-сервер. Інші інструменти, такі як інсталятори ОС і конфігуратори ядра, які, можливо, доведеться запустити, перш ніж буде доступна будь-яка графічна підтримка.

The curses library provides fairly basic functionality, providing the programmer with an abstraction of a display containing multiple non-overlapping windows of text. The contents of a window can be changed in various ways—adding text, erasing it, changing its appearance—and the curses library will figure out what control codes need to be sent to the terminal to produce the right output. curses doesn’t provide many user-interface concepts such as buttons, checkboxes, or dialogs; if you need such features, consider a user interface library such as Urwid.

Бібліотека curses спочатку була написана для BSD Unix; пізніші версії System V Unix від AT&T додали багато вдосконалень і нових функцій. BSD curses більше не підтримується, їх замінив ncurses, який є реалізацією інтерфейсу AT&T з відкритим кодом. Якщо ви використовуєте Unix з відкритим кодом, наприклад Linux або FreeBSD, ваша система майже напевно використовує ncurses. Оскільки більшість сучасних комерційних версій Unix базується на коді System V, усі описані тут функції, ймовірно, будуть доступні. Однак старіші версії curses, які містяться в деяких пропрієтарних системах Unix, можуть не підтримувати все.

The Windows version of Python doesn’t include the curses module. A ported version called UniCurses is available.

Модуль проклинань Python

The Python module is a fairly simple wrapper over the C functions provided by curses; if you’re already familiar with curses programming in C, it’s really easy to transfer that knowledge to Python. The biggest difference is that the Python interface makes things simpler by merging different C functions such as addstr(), mvaddstr(), and mvwaddstr() into a single addstr() method. You’ll see this covered in more detail later.

Цей HOWTO є вступом до написання програм у текстовому режимі за допомогою curses і Python. Він не намагається бути повним посібником з curses API; для цього дивіться розділ посібника з бібліотеки Python про ncurses і сторінки посібника C для ncurses. Однак це дасть вам основні ідеї.

Запуск і завершення програми curses

Before doing anything, curses must be initialized. This is done by calling the initscr() function, which will determine the terminal type, send any required setup codes to the terminal, and create various internal data structures. If successful, initscr() returns a window object representing the entire screen; this is usually called stdscr after the name of the corresponding C variable.

import curses
stdscr = curses.initscr()

Зазвичай програми curses вимикають автоматичне відлуння клавіш на екрані, щоб мати можливість читати клавіші та відображати їх лише за певних обставин. Для цього потрібно викликати функцію noecho().

curses.noecho()

Додаткам також зазвичай потрібно миттєво реагувати на натискання клавіш, не вимагаючи натискання клавіші Enter; це називається режимом cbreak, на відміну від звичайного режиму буферизованого введення.

curses.cbreak()

Термінали зазвичай повертають спеціальні клавіші, такі як клавіші керування курсором або навігаційні клавіші, такі як Page Up і Home, як багатобайтову escape-послідовність. Хоча ви можете написати свою програму так, щоб очікувати такі послідовності та обробляти їх відповідно, curses може зробити це за вас, повертаючи спеціальне значення, наприклад curses.KEY_LEFT. Щоб змусити прокляття виконувати роботу, вам потрібно буде ввімкнути режим клавіатури.

stdscr.keypad(True)

Завершити роботу програми curses набагато легше, ніж розпочати її. Вам потрібно зателефонувати:

curses.nocbreak()
stdscr.keypad(False)
curses.echo()

щоб змінити налаштування терміналу, зручні для curses. Потім викличте функцію endwin(), щоб відновити термінал до вихідного режиму роботи.

curses.endwin()

Поширеною проблемою під час налагодження програми curses є псування терміналу, коли програма вимикається без відновлення попереднього стану терміналу. У Python це зазвичай трапляється, коли ваш код має помилки та викликає неперехоплений виняток. Наприклад, клавіші більше не відтворюються на екрані, коли ви їх вводите, що ускладнює використання оболонки.

У Python ви можете уникнути цих ускладнень і значно полегшити налагодження, імпортувавши функцію curses.wrapper() і використовуючи її таким чином:

from curses import wrapper

def main(stdscr):
    # Clear screen
    stdscr.clear()

    # This raises ZeroDivisionError when i == 10.
    for i in range(0, 11):
        v = i-10
        stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))

    stdscr.refresh()
    stdscr.getkey()

wrapper(main)

The wrapper() function takes a callable object and does the initializations described above, also initializing colors if color support is present. wrapper() then runs your provided callable. Once the callable returns, wrapper() will restore the original state of the terminal. The callable is called inside a tryexcept that catches exceptions, restores the state of the terminal, and then re-raises the exception. Therefore your terminal won’t be left in a funny state on exception and you’ll be able to read the exception’s message and traceback.

Вікна та колодки

Вікна є основною абстракцією в curses. Об’єкт вікна представляє прямокутну область екрана та підтримує методи відображення тексту, його стирання, дозволу користувачеві вводити рядки тощо.

Об’єкт stdscr, що повертається функцією initscr(), є об’єктом вікна, що покриває весь екран. Багатьом програмам може знадобитися лише це одне вікно, але ви можете розділити екран на менші вікна, щоб перемалювати або очистити їх окремо. Функція newwin() створює нове вікно заданого розміру, повертаючи новий об’єкт вікна.

begin_x = 20; begin_y = 7
height = 5; width = 40
win = curses.newwin(height, width, begin_y, begin_x)

Зверніть увагу, що система координат, яка використовується в curses, є незвичною. Координати завжди передаються в порядку y,x, а верхній лівий кут вікна є координатою (0,0). Це порушує звичайну угоду щодо обробки координат, де координата x стоїть першою. Це прикра відмінність від більшості інших комп’ютерних програм, але вона була частиною прокляття відтоді, як була написана, і зараз надто пізно щось змінювати.

Ваша програма може визначити розмір екрана за допомогою змінних curses.LINES і curses.COLS для отримання розмірів y і x. Тоді юридичні координати розширюватимуться від (0,0) до (curses.LINES - 1, curses.COLS - 1).

Коли ви викликаєте метод для відображення або стирання тексту, ефект не відразу відображається на дисплеї. Натомість ви повинні викликати метод refresh() об’єктів вікна, щоб оновити екран.

This is because curses was originally written with slow 300-baud terminal connections in mind; with these terminals, minimizing the time required to redraw the screen was very important. Instead curses accumulates changes to the screen and displays them in the most efficient manner when you call refresh(). For example, if your program displays some text in a window and then clears the window, there’s no need to send the original text because they’re never visible.

In practice, explicitly telling curses to redraw a window doesn’t really complicate programming with curses much. Most programs go into a flurry of activity, and then pause waiting for a keypress or some other action on the part of the user. All you have to do is to be sure that the screen has been redrawn before pausing to wait for user input, by first calling stdscr.refresh() or the refresh() method of some other relevant window.

Колодка — окремий випадок вікна; він може бути більшим, ніж фактичний екран дисплея, і одночасно відображатися лише частина панелі. Щоб створити блокнот, потрібно вказати його висоту та ширину, тоді як для оновлення блокнота потрібно вказати координати області на екрані, де відображатиметься його підрозділ.

pad = curses.newpad(100, 100)
# These loops fill the pad with letters; addch() is
# explained in the next section
for y in range(0, 99):
    for x in range(0, 99):
        pad.addch(y,x, ord('a') + (x*x+y*y) % 26)

# Displays a section of the pad in the middle of the screen.
# (0,0) : coordinate of upper-left corner of pad area to display.
# (5,5) : coordinate of upper-left corner of window area to be filled
#         with pad content.
# (20, 75) : coordinate of lower-right corner of window area to be
#          : filled with pad content.
pad.refresh( 0,0, 5,5, 20,75)

The refresh() call displays a section of the pad in the rectangle extending from coordinate (5,5) to coordinate (20,75) on the screen; the upper left corner of the displayed section is coordinate (0,0) on the pad. Beyond that difference, pads are exactly like ordinary windows and support the same methods.

If you have multiple windows and pads on screen there is a more efficient way to update the screen and prevent annoying screen flicker as each part of the screen gets updated. refresh() actually does two things:

  1. Викликає метод noutrefresh() кожного вікна, щоб оновити базову структуру даних, що представляє потрібний стан екрана.

  2. Викликає функцію doupdate(), щоб змінити фізичний екран відповідно до потрібного стану, записаного в структурі даних.

Instead you can call noutrefresh() on a number of windows to update the data structure, and then call doupdate() to update the screen.

Відображення тексту

From a C programmer’s point of view, curses may sometimes look like a twisty maze of functions, all subtly different. For example, addstr() displays a string at the current cursor location in the stdscr window, while mvaddstr() moves to a given y,x coordinate first before displaying the string. waddstr() is just like addstr(), but allows specifying a window to use instead of using stdscr by default. mvwaddstr() allows specifying both a window and a coordinate.

На щастя, інтерфейс Python приховує всі ці деталі. stdscr є віконним об’єктом, як і будь-який інший, і такі методи, як addstr() приймають кілька форм аргументів. Зазвичай є чотири різні форми.

Форма

опис

str або ch

Відобразити рядок str або символ ch у поточній позиції

str або ch, attr

Відобразити рядок str або символ ch, використовуючи атрибут attr у поточній позиції

y, x, str або ch

Перейдіть до позиції y,x у вікні та відобразіть str або ch

y, x, str або ch, attr

Перейдіть до позиції y,x у вікні та відобразіть str або ch, використовуючи атрибут attr

Атрибути дозволяють відображати текст у виділених формах, наприклад жирним шрифтом, підкресленням, зворотним кодом або кольором. Вони будуть пояснені більш детально в наступному підрозділі.

The addstr() method takes a Python string or bytestring as the value to be displayed. The contents of bytestrings are sent to the terminal as-is. Strings are encoded to bytes using the value of the window’s encoding attribute; this defaults to the default system encoding as returned by locale.getencoding().

Методи addch() приймають символ, який може бути рядком довжини 1, байтовим рядком довжини 1 або цілим числом.

Константи надаються для символів розширення; ці константи є цілими числами, більшими за 255. Наприклад, ACS_PLMINUS — це символ +/-, а ACS_ULCORNER — верхній лівий кут прямокутника (зручно для малювання меж). Ви також можете використовувати відповідний символ Unicode.

Windows запам’ятовує, де був залишений курсор після останньої операції, тому, якщо ви пропустите координати y,x, рядок або символ відображатиметься там, де зупинилася остання операція. Ви також можете перемістити курсор за допомогою методу move(y,x). Оскільки деякі термінали завжди відображають блимаючий курсор, ви можете переконатися, що курсор розташований у певному місці, де він не буде відволікати; блимання курсора в певному, начебто випадковому, місці може бути збентеженим.

Якщо вашій програмі зовсім не потрібен мерехтливий курсор, ви можете викликати curs_set(False), щоб зробити його невидимим. Для сумісності зі старішими версіями curses існує функція leaveok(bool), яка є синонімом curs_set(). Якщо bool має значення true, бібліотека curses намагатиметься придушити миготливий курсор, і вам не потрібно буде турбуватися про те, щоб залишити його в незвичних місцях.

Атрибути та колір

Символи можуть відображатися різними способами. Рядки стану в текстовій програмі зазвичай відображаються у зворотному відео, або програмі перегляду тексту може знадобитися виділити певні слова. curses підтримує це, дозволяючи вам вказати атрибут для кожної клітинки на екрані.

Атрибут — це ціле число, кожен біт якого представляє окремий атрибут. Ви можете спробувати відобразити текст з кількома встановленими бітами атрибутів, але curses не гарантує, що всі можливі комбінації доступні, або що всі вони візуально відрізняються. Це залежить від можливостей використовуваного терміналу, тому найбезпечніше дотримуватися найпоширеніших атрибутів, перелічених тут.

Атрибут

опис

A_BLINK

Миготливий текст

A_BOLD

Дуже яскравий або жирний текст

A_DIM

Наполовину яскравий текст

A_REVERSE

Зворотний відеотекст

A_STANDOUT

Найкращий доступний режим підсвічування

A_UNDERLINE

Підкреслений текст

Отже, щоб відобразити рядок стану реверсивного відео у верхньому рядку екрана, ви можете закодувати:

stdscr.addstr(0, 0, "Current mode: Typing mode",
              curses.A_REVERSE)
stdscr.refresh()

Бібліотека curses також підтримує колір на тих терміналах, які його надають. Найпоширенішим таким терміналом, ймовірно, є консоль Linux, за якою слідують кольорові xterms.

Щоб використовувати колір, ви повинні викликати функцію start_color() відразу після виклику initscr(), щоб ініціалізувати набір кольорів за замовчуванням (це робить функція curses.wrapper() автоматично). Після цього функція has_colors() повертає TRUE, якщо термінал, що використовується, справді може відображати колір. (Примітка: curses використовує американське написання «колір» замість канадсько-британського написання «колір». Якщо ви звикли до британського написання, вам доведеться змиритися з орфографічною помилкою заради цих функцій. )

Бібліотека curses підтримує кінцеву кількість пар кольорів, що містять колір переднього плану (або тексту) і колір фону. Ви можете отримати значення атрибута, що відповідає парі кольорів, за допомогою функції color_pair(); це можна об’єднати порозрядним АБО з іншими атрибутами, такими як A_REVERSE, але знову ж таки, такі комбінації не гарантовано працюють на всіх терміналах.

Приклад, який відображає рядок тексту за допомогою пари кольорів 1:

stdscr.addstr("Pretty text", curses.color_pair(1))
stdscr.refresh()

Як я вже говорив раніше, пара кольорів складається з кольору переднього плану та кольору фону. Функція init_pair(n, f, b) змінює визначення пари кольорів n на колір переднього плану f і колір фону b. Пара кольорів 0 жорстко з’єднана з білим на чорному, і її неможливо змінити.

Кольори пронумеровані, і start_color() ініціалізує 8 основних кольорів, коли він активує колірний режим. Це: 0:чорний, 1:червоний, 2:зелений, 3:жовтий, 4:синій, 5:пурпурний, 6:блакитний і 7:білий. Модуль curses визначає іменовані константи для кожного з цих кольорів: curses.COLOR_BLACK, curses.COLOR_RED і так далі.

Давайте все це разом. Щоб змінити колір 1 на червоний текст на білому фоні, ви повинні викликати:

curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)

Коли ви змінюєте пару кольорів, будь-який текст, який уже відображається за допомогою цієї пари кольорів, зміниться на нові кольори. Ви також можете відобразити новий текст у цьому кольорі за допомогою:

stdscr.addstr(0,0, "RED ALERT!", curses.color_pair(1))

Дуже модні термінали можуть змінювати визначення фактичних кольорів на задане значення RGB. Це дозволяє змінити колір 1, який зазвичай червоний, на фіолетовий, синій або будь-який інший колір, який вам подобається. На жаль, консоль Linux не підтримує це, тому я не можу випробувати це й не можу надати жодних прикладів. Ви можете перевірити, чи ваш термінал може зробити це, викликавши can_change_color(), який повертає True, якщо така можливість є. Якщо вам пощастило мати такий талановитий термінал, зверніться до сторінок керівництва вашої системи для отримання додаткової інформації.

Введення користувача

The C curses library offers only very simple input mechanisms. Python’s curses module adds a basic text-input widget. (Other libraries such as Urwid have more extensive collections of widgets.)

Існує два способи отримання даних із вікна:

  • getch() оновлює екран, а потім чекає, поки користувач натисне клавішу, відображаючи клавішу, якщо echo() було викликано раніше. Додатково можна вказати координату, до якої слід перемістити курсор перед паузою.

  • getkey() робить те саме, але перетворює ціле число на рядок. Окремі символи повертаються як рядки з 1 символу, а спеціальні клавіші, такі як функціональні клавіші, повертають довші рядки, що містять ім’я ключа, наприклад KEY_UP або ^G.

It’s possible to not wait for the user using the nodelay() window method. After nodelay(True), getch() and getkey() for the window become non-blocking. To signal that no input is ready, getch() returns curses.ERR (a value of -1) and getkey() raises an exception. There’s also a halfdelay() function, which can be used to (in effect) set a timer on each getch(); if no input becomes available within a specified delay (measured in tenths of a second), curses raises an exception.

The getch() method returns an integer; if it’s between 0 and 255, it represents the ASCII code of the key pressed. Values greater than 255 are special keys such as Page Up, Home, or the cursor keys. You can compare the value returned to constants such as curses.KEY_PPAGE, curses.KEY_HOME, or curses.KEY_LEFT. The main loop of your program may look something like this:

while True:
    c = stdscr.getch()
    if c == ord('p'):
        PrintDocument()
    elif c == ord('q'):
        break  # Exit the while loop
    elif c == curses.KEY_HOME:
        x = y = 0

Модуль curses.ascii надає функції приналежності до класу ASCII, які приймають цілі або односимвольні рядкові аргументи; вони можуть бути корисними для написання більш читабельних тестів для таких циклів. Він також надає функції перетворення, які приймають цілі або односимвольні аргументи та повертають той самий тип. Наприклад, curses.ascii.ctrl() повертає керуючий символ, що відповідає його аргументу.

Існує також метод для отримання цілого рядка, getstr(). Використовується не дуже часто, оскільки його функціональність досить обмежена; єдиними доступними клавішами редагування є клавіша повернення та клавіша Enter, яка завершує рядок. За бажанням його можна обмежити фіксованою кількістю символів.

curses.echo()            # Enable echoing of characters

# Get a 15-character string, with the cursor on the top line
s = stdscr.getstr(0,0, 15)

Модуль curses.textpad надає текстове поле, яке підтримує Emacs-подібний набір сполучень клавіш. Різні методи класу Textbox підтримують редагування з перевіркою введених даних і збиранням результатів редагування з пробілами в кінці або без них. Ось приклад:

import curses
from curses.textpad import Textbox, rectangle

def main(stdscr):
    stdscr.addstr(0, 0, "Enter IM message: (hit Ctrl-G to send)")

    editwin = curses.newwin(5,30, 2,1)
    rectangle(stdscr, 1,0, 1+5+1, 1+30+1)
    stdscr.refresh()

    box = Textbox(editwin)

    # Let the user edit until Ctrl-G is struck.
    box.edit()

    # Get resulting contents
    message = box.gather()

Перегляньте документацію бібліотеки на curses.textpad для отримання додаткової інформації.

Для отримання додаткової інформації

Цей HOWTO не охоплює деякі складні теми, такі як читання вмісту екрана чи захоплення подій миші з екземпляра xterm, але сторінка бібліотеки Python для модуля curses тепер досить повна. Ви повинні переглянути його далі.

If you’re in doubt about the detailed behavior of the curses functions, consult the manual pages for your curses implementation, whether it’s ncurses or a proprietary Unix vendor’s. The manual pages will document any quirks, and provide complete lists of all the functions, attributes, and ACS_* characters available to you.

Оскільки API curses дуже великий, деякі функції не підтримуються в інтерфейсі Python. Часто це відбувається не тому, що їх важко реалізувати, а тому, що вони ще нікому не потрібні. Крім того, Python ще не підтримує бібліотеку меню, пов’язану з ncurses. Латки, що додають підтримку для них, будуть вітатися; див. Посібник розробника Python, щоб дізнатися більше про надсилання виправлень у Python.