用 Pythoncurses 编程

  • Author

    • 上午。埃里克·雷蒙德·库奇林
  • Release

    • 2.03

Abstract

本文档介绍了如何使用curses扩展模块控制显示,并使用 Python 2.x 编写文本模式程序。

什么是 curses?

curses 库为基于文本的终端提供了独立于终端的屏幕绘画和键盘处理Function;此类终端包括 VT100,Linux 控制台以及 X11 程序(如 xterm 和 rxvt)提供的模拟终端。显示终端支持各种控制代码以执行常见的操作,例如移动光标,滚动屏幕和擦除区域。不同的终端使用相差很大的代码,并且常常有自己的小怪癖。

在 X 显示器的世界里,人们可能会问“为什么要打扰”?字符单元显示终端确实是一种过时的技术,但是在某些领域,能够用它们来做花哨的事情仍然很有价值。一个是在不带 X 服务器的小型或嵌入式 Unix 上。另一个是针对 OS 安装程序和内核配置程序之类的工具,这些工具可能必须在 X 可用之前运行。

curses 库隐藏了不同终端的所有详细信息,并为程序员提供了一个包含多个不重叠窗口的显示抽象。窗口的内容可以pass各种方式更改-添加文本,擦除文本,更改其外观-并且 curses 库将自动确定需要向终端发送哪些控制代码以产生正确的输出。

curses 库最初是为 BSD Unix 编写的。 AT&T 的 Unix 的后来的 System V 版本增加了许多增强Function和新Function。 BSD curses 不再维护,已被 ncurses 取代,ncurses 是 AT&T 接口的开源实现。如果您使用的是 Linux 或 FreeBSD 等开源 Unix,则您的系统几乎肯定会使用 ncurses。由于大多数当前的商业 Unix 版本都基于 System V 代码,因此这里描述的所有Function可能都可用。但是,某些专有 Unix 所带来的较早版本的 curses 可能不支持所有Function。

没有人为 curses 模块提供 Windows 端口。在 Windows 平台上,请try使用 Fredrik Lundh 编写的 Console 模块。控制台模块提供光标可寻址的文本输出,以及对鼠标和键盘 Importing 的全面支持,并且可从http://effbot.org/zone/console-index.htm获得。

Python curses 模块

您的 Python 模块是对 curses 提供的 C 函数的相当简单的包装;如果您已经熟悉 C 语言中的 curses 编程,那么将这些知识转移到 Python 上真的很容易。最大的区别在于,Python 界面pass将不同的 C 函数(例如addstr()mvaddstr()mvwaddstr())合并为单个addstr()方法,使事情变得更简单。稍后,您将看到更详细的内容。

本 HOWTO 只是使用 curses 和 Python 编写文本模式程序的简介。它并没有试图成为 curses API 的完整指南。为此,请参见有关 ncurses 的 Python 库指南部分,以及有关 ncurses 的 C 手册页。但是,它将为您提供基本的想法。

开始和结束 curses 申请

在做任何事情之前,必须先初始化 curses。这是pass调用initscr()函数来完成的,该函数将确定终端类型,将所需的所有设置代码发送到终端,并创建各种内部数据结构。如果成功,则initscr()返回代表整个屏幕的窗口对象;通常在对应的 C 变量的名称之后称为stdscr

import curses
stdscr = curses.initscr()

通常,curses 应用程序会关闭自动向屏幕显示按键的Function,以便能够读取按键并仅在某些情况下显示它们。这需要调用noecho()函数。

curses.noecho()

应用程序通常还需要立即对键做出反应,而无需按下 Enter 键。与通常的缓冲 Importing 模式相反,这称为 cbreak 模式。

curses.cbreak()

终端通常以多字节转义序列的形式返回特殊键,例如光标键或导航键,例如 Page Up 和 Home。尽管您可以编写应用程序来期望这样的序列并进行相应的处理,但是 curses 可以为您完成,返回一个特殊值,例如curses.KEY_LEFT。要获得 curses 来完成这项工作,您必须启用键盘模式。

stdscr.keypad(1)

终止 curses 应用程序比启动应用程序容易得多。你需要打电话

curses.nocbreak(); stdscr.keypad(0); curses.echo()

反转对 curses 友好的终端设置。然后调用endwin()函数将终端恢复到其原始操作模式。

curses.endwin()

调试 curses 应用程序时,常见的问题是在应用程序死机时弄乱了终端,而没有将终端恢复到以前的状态。在 Python 中,这通常发生在您的代码有错误并引发未捕获的异常时。例如,键入键时,键不再在屏幕上回显,这使使用 Shell 变得困难。

在 Python 中,您可以pass导入curses.wrapper()函数来避免这些复杂性并使调试更加容易。它需要调用并进行上述初始化,如果存在颜色支持,则还要初始化颜色。然后,它将运行您提供的可调用对象,并finally进行适当的初始化。可调用对象在 try-catch 子句中调用,该子句捕获异常,执行 curses 反初始化,然后向上传递异常。因此,您的终端不会因为异常而处于有趣的状态。

Windows 和 Pads

Windows 是 curses 的基本抽象。窗口对象代表屏幕的矩形区域,并支持各种方法来显示文本,擦除文本,允许用户 Importing 字符串等。

initscr()函数返回的stdscr对象是一个覆盖整个屏幕的窗口对象。许多程序可能只需要一个窗口,但是您可能希望将屏幕分成较小的窗口,以便分别重绘或清除它们。 newwin()函数创建给定大小的新窗口,并返回新的窗口对象。

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

关于 curses 中使用的坐标系的一句话:坐标始终以* y,x 的 Sequences 传递,并且窗口的左上角为坐标(0,0)。这 break 了处理坐标的通用约定,通常以 x *坐标为第一位。不幸的是,这与大多数其他计算机应用程序有所不同,但是自从第一次编写以来,这一直是 curses 的一部分,现在改变现状为时已晚。

当您调用显示或删除文本的方法时,效果不会立即显示在显示屏上。这是因为 curses 最初是在考虑 300 波特的慢速终端连接的情况下编写的;使用这些终端,尽量减少重新绘制屏幕所需的时间非常重要。这使 curses 可以累积对屏幕的更改,并以最有效的方式显示它们。例如,如果您的程序在窗口中显示了一些字符,然后清除了该窗口,则无需发送原始字符,因为它们将永远不可见。

因此,curses 要求您使用窗口对象的refresh()方法明确地告诉它重绘窗口。实际上,这并不会使编程复杂得多。大多数程序都会进行一系列活动,然后暂停以 await 用户方面的按键或其他操作。您所要做的就是确保在暂停 await 用户 Importing 之前已重绘了屏幕,只需简单地调用其他相关窗口的stdscr.refresh()refresh()方法即可。

垫子是窗户的特例;它可以大于实际显示屏,并且一次只显示其中一部分。创建垫板仅需要垫板的高度和宽度,而刷新垫板则需要提供将在其中显示垫板子部分的屏幕区域的坐标。

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

#  Displays a section of the pad in the middle of the screen
pad.refresh(0,0, 5,5, 20,75)

refresh()呼叫在屏幕上以矩形显示从坐标(5,5)延伸到坐标(20,75)的垫的一部分;显示部分的左上角是打击板上的坐标(0,0)。除此以外,垫完全类似于普通的窗户并支持相同的方法。

如果屏幕上有多个窗口和垫,则有更有效的处理方法,可以防止刷新时屏幕闪烁。使用每个窗口的noutrefresh()方法来更新表示屏幕所需状态的数据结构;然后使用doupdate()一次性更改物理屏幕以使其与所需状态匹配。普通的refresh()方法将doupdate()作为其最后动作。

Displaying Text

从 C 程序员的角度来看,curses 有时看起来像是一堆曲折的函数迷宫,它们之间有着微妙的不同。例如,addstr()stdscr窗口中的当前光标位置显示一个字符串,而mvaddstr()在显示该字符串之前先移至给定的 y,x 坐标。 waddstr()就像addstr()一样,但是允许指定要使用的窗口,而不是默认情况下使用stdscrmvwaddstr()也类似。

幸运的是,Python 接口隐藏了所有这些细节。 stdscr是任何其他窗口对象,而addstr()等方法则接受多个参数形式。通常有四种不同的形式。

FormDescription
* str ch *在当前位置显示字符串* str 或字符 ch *
* str ch attr *在当前位置使用属性* attr 显示字符串 str 或字符 ch *
* y x str ch *移动到窗口内的* y,x 位置,并显示 str ch *
* y x str ch attr *使用属性* attr 移至窗口内的 y,x 位置并显示 str ch *

属性允许以突出显示的形式显示文本,例如以粗体,下划线,反向代码或彩色显示。下一部分将对它们进行详细说明。

addstr()函数采用 Python 字符串作为要显示的值,而addch()函数采用字符,该字符可以是长度为 1 的 Python 字符串或整数。如果是字符串,则只能显示 0 到 255 之间的字符。SVr4 curses 为扩展字符提供常量。这些常量是大于 255 的整数。例如,ACS_PLMINUS是/符号,而ACS_ULCORNER是框的左上角(方便绘制边框)。

Windows 会记住上次操作后光标留在的位置,因此,如果Ellipsis* y,x *坐标,则在上次操作break的地方都会显示字符串或字符。您也可以使用move(y,x)方法移动光标。由于某些终端始终显示闪烁的光标,因此您可能要确保将光标定位在不会分散注意力的某个位置。使光标在某些明显随机的位置闪烁可能会造成混淆。

如果您的应用程序根本不需要闪烁的光标,则可以调用curs_set(0)使其不可见。同样,为了与较早版本的 curses 兼容,提供了leaveok(bool)函数。当* bool *为 true 时,curses 库将try抑制闪烁的光标,而您无需担心将其放置在奇数个位置。

属性和颜色

字符可以以不同的方式显示。基于文本的应用程序中的状态行通常以反向视频显示;文本查看器可能需要突出显示某些单词。 curses pass允许您为屏幕上的每个单元格指定一个属性来支持这一点。

属性是一个整数,每个位代表一个不同的属性。您可以try显示设置了多个属性位的文本,但是 curses 不能保证所有可能的组合都可用,或者它们在视觉上完全不同。这取决于所用终端的能力,因此坚持此处列出的最常用的属性是最安全的。

AttributeDescription
A_BLINKBlinking text
A_BOLD超亮或粗体 Literals
A_DIM半亮文本
A_REVERSEReverse-video text
A_STANDOUT最好的突出显示模式
A_UNDERLINEUnderlined text

因此,要在屏幕顶部显示反向视频状态行,可以编写以下代码:

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

curses 库还支持提供这些颜色的终端上的颜色。最常见的此类终端可能是 Linux 控制台,其后是 color xterms。

若要使用颜色,必须在调用initscr()之后立即调用start_color()函数,以初始化默认颜色集(curses.wrapper.wrapper()函数自动执行此操作)。完成后,如果使用中的终端可以实际显示颜色,则has_colors()函数将返回 TRUE。 (注意:Curses 使用美国拼写“颜色”,而不是加拿大/英国拼写“颜色”.如果您习惯了英国拼写,则由于这些Function,您必须辞职以使其拼写错误.)

curses 库维护有限数量的颜色对,其中包含前景色(或文本)和背景色。您可以使用color_pair()函数获得与颜色对相对应的属性值。这可以与其他属性(例如A_REVERSE)进行按位或运算,但同样,不能保证此类组合在所有终端上都有效。

一个示例,它使用颜色对 1 显示一行文本:

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

如前所述,颜色对由前景色和背景色组成。 start_color()激活颜色模式时会初始化 8 种基本颜色。它们是:0:黑色,1:红色,2:绿色,3:黄色,4:蓝色,5:洋红色,6:青色和 7:白色。 curses 模块为以下每种颜色定义命名常量:curses.COLOR_BLACKcurses.COLOR_RED等。

init_pair(n, f, b)函数将颜色对* n *的定义更改为前景色 f 和背景色 b。颜色对 0 硬连线为黑底白字,无法更改。

让我们将所有这些放在一起。要将颜色 1 更改为白色背景上的红色文本,请调用:

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

更改颜色对时,已经使用该颜色对显示的任何文本都将更改为新颜色。您还可以使用以下颜色显示新文本:

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

非常漂亮的终端可以将实际颜色的定义更改为给定的 RGB 值。这使您可以将通常为红色的颜色 1 更改为紫色或蓝色或您喜欢的任何其他颜色。不幸的是,Linux 控制台不支持此Function,因此我无法try它,并且无法提供任何示例。您可以pass调用can_change_color()来检查终端是否可以执行此操作,如果存在该Function,则返回 TRUE。如果您有幸拥有如此出色的终端机,请查阅系统的手册页以获取更多信息。

User Input

curses 库本身仅提供非常简单的 Importing 机制。 Python 的支持增加了一个文本 Importing 小部件,弥补了其中的不足。

获取窗口 Importing 的最常见方法是使用其getch()方法。 getch()暂停并 await 用户敲击按键,如果echo()较早被调用,则显示该按键。您可以有选择地指定在暂停之前光标应移动到的坐标。

可以使用方法nodelay()更改此行为。在nodelay(1)之后,窗口的getch()变为非阻塞状态,并且在没有任何 Importing 准备就绪时返回curses.ERR(值-1)。还有一个halfdelay()函数,可用于(实际上)在每个getch()上设置一个计时器;如果在指定的延迟(以十分之一秒为单位)内没有 Importing 变为可用,则 curses 会引发异常。

getch()方法返回一个整数;如果介于 0 和 255 之间,则表示所按下键的 ASCII 码。大于 255 的值是特殊键,例如 Page Up,Home 或光标键。您可以将返回的值与curses.KEY_PPAGEcurses.KEY_HOMEcurses.KEY_LEFT等常量进行比较。通常,程序的主循环如下所示:

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

curses.ascii模块提供了采用整数或 1 个字符的字符串参数的 ASCII 类成员资格函数;这些对于为命令解释器编写更具可读性的测试可能很有用。它还提供采用整数或 1 个字符的字符串参数并返回相同类型的转换函数。例如,curses.ascii.ctrl()返回与其参数相对应的控制字符。

还有一种方法可以检索整个字符串getstr()。它很少使用,因为它的Function非常有限。唯一可用的编辑键是 Backspace 键和 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)

Python curses.textpad模块提供了更好的Function。有了它,您可以将窗口变成支持类似 Emacs 的一组键绑定的文本框。 Textbox类的各种方法都支持使用 Importing 验证进行编辑,并在有或没有尾随空格的情况下收集编辑结果。有关详细信息,请参见curses.textpad上的库文档。

有关更多信息

本 HOWTO 未涵盖一些高级主题,例如屏幕抓取或从 xterm 实例捕获鼠标事件。但是 curses 模块的 Python 库页面现在已经很完整了。您应该接下来浏览它。

如果您不确定任何 ncurses 入口点的详细行为,请查阅您的 curses 实现的手册页,无论是 ncurses 还是专有的 Unix 供应商。手册页将记录所有怪癖,并提供所有可用Function,属性和ACS_*字符的完整列表。

由于 curses API 太大,因此 Python 界面不支持某些Function,这不是因为它们难以实现,而是因为现在还没有人需要它们。随时添加它们,然后提交补丁。另外,我们还不支持与 ncurses 关联的菜单库。随时添加。

如果您编写了一个有趣的 Servlets,请随时将其贡献为另一个演示。我们总是可以使用更多它们!

ncurses 常见问题解答:http://invisible-island.net/ncurses/ncurses.faq.html