addstr导致getstr返回信号

时间:2012-07-03 03:13:27

标签: python ncurses curses

我有一个重写的python curses代码基本上有两个'线程'。它们不是真正的线程 - 一个主要的子寡妇处理函数,第二个是不同的子窗口处理函数,在计时器上执行。我遇到了一个有趣的效果:

  • 主窗口代码正在使用getstr()等待用户的输入。
  • 同时会出现定时器中断,中断代码会在不同的子寡妇中输出内容。
  • 计时器功能的输出将导致getstr()以空输入返回。

可能导致这种影响的原因是什么? 除了检查返回字符串之外,有什么方法可以避免这种影响吗?

<小时/> 用于重现问题的示例代码:

#!/usr/bin/env python
# Simple code to show timer updates

import curses
import os, signal, sys, time, traceback
import math

UPDATE_INTERVAL = 2
test_bed_windows = []
global_count = 0

def signal_handler(signum, frame):
    global test_bed_windows
    global global_count

    if (signum == signal.SIGALRM):
        # Update all the test bed windows
        # restart the timer.
        signal.alarm(UPDATE_INTERVAL)
        global_count += 1

        for tb_window in test_bed_windows:
            tb_window.addstr(1, 1, "Upd: {0}.{1}".format(global_count, test_bed_windows.index(tb_window)))
            tb_window.refresh()
    else:
        print("unexpected signal: {0}".format(signam))
        pass

def main(stdscr):
    # window setup
    screen_y, screen_x = stdscr.getmaxyx()
    stdscr.box()

    # print version
    version_str = " Timer Demo v:0 "
    stdscr.addstr(0, screen_x - len(version_str) - 1, version_str)
    stdscr.refresh()

    window = stdscr.subwin(screen_y-2,screen_x-2,1,1)

    for i in range(3):
        subwin = window.derwin(3,12,1,2 + (15*i))

        test_bed_windows.append(subwin)
        subwin.box()
        subwin.refresh()

    signal.signal(signal.SIGALRM, signal_handler)
    signal.alarm(UPDATE_INTERVAL)

    # Output the prompt and wait for the input:
    window.addstr(12, 1, "Enter Q/q to exit\n")
    window.refresh()

    the_prompt = "Enter here> "
    while True:
        window.addstr(the_prompt)
        window.refresh()

        curses.echo()
        selection = window.getstr()
        curses.noecho()

        if selection == '':
            continue
        elif selection.upper() == 'Q':
            break
        else:
            window.addstr("Entered: {0}".format(selection))
            window.refresh()


if __name__ == '__main__':
    curses.wrapper(main)

2 个答案:

答案 0 :(得分:0)

我怀疑这不是写子窗口导致getstr()返回空字符串,而是报警信号本身。 (从信号处理程序中写入窗口也可能没有明确定义,但这是一个单独的问题。)

C ++ Curses,Python的curses模块通常构建在它之上,当任何信号(除了内部处理的信号之外)进入时,它将从大多数阻塞输入调用返回。在C中有一个已定义的API情况(函数返回-1并将errno设置为EINTER)。

Python模块表示如果curses函数返回错误,它将引发异常。我不确定为什么在这种情况下它没有这样做。

编辑:一种可能的解决方案是使用比curses更加程序员友好的控制台UI库。 Urwid出现(在我简要浏览手册中)以支持事件驱动的UI更新(包括计时器更新),同时处理键盘输入。学习它可能比处理信号和诅咒之间粗略和记录不良的相互作用更容易。

编辑:来自getch()的手册页:

  

在SVr4和XSI Curses文档中未指定处理信号存在时getch和朋友的行为。在历史curses实现中,它取决于操作系统的处理信号接收的实现是否中断正在进行的read(2)调用,以及(在某些实现中)取决于输入超时或非阻塞模式是否已经变化集。

     

关注可移植性的程序员应该为以下两种情况之一做好准备:(a)信号接收不会中断getch; (b)信号接收中断getch并使其返回ERR,并将errno设置为EINTR。 ncurses 实施下,处理过的信号永远不会中断 getch

我尝试使用 getch 而不是 getstr ,它确实在信号上返回-1。如果由 getstr 实现,那么(负返回值)将解决此问题。 所以现在选项是(1)编写自己的 getstr 并进行错误处理或(2)使用Urwid。这可能是Python库的错误吗?

答案 1 :(得分:0)

这仅供参考。 因此,为了以最简单的方式解决这个问题,我只是编写了自己的函数来执行getstr()所做的事情。它不会在出错时退出。欢迎所有评论,更正,优化。

'''
Function to read a string from the current cursor position.
It supports some simple editing: Ctrl+A, Ctrl+E, Backspace, Del, Home, End,
'''
def getstr(window, prompt = "> ", end_on_error = False):
    result = ""
    starty, startx = window.getyx()
    window.move(starty, 0)
    window.deleteln()
    window.addstr(prompt)
    window.refresh()
    window.keypad(True)

    starty, startx = window.getyx()
    endy, endx = window.getyx()
    maxy, maxx = window.getmaxyx()
    while True:
        try:
            selection = -1
            while (selection < 0 and end_on_error == False):
                selection = window.getch()
        except:
            e = sys.exc_info()[0]
            window.addstr("<p>Error: %s</p>" % e)
            break

        if (selection == curses.KEY_ENTER or selection == ord('\n')):
            break
        elif (selection == curses.KEY_HOME or selection == 1):
            window.move(starty, startx)
            continue
        elif (selection == curses.KEY_END or selection == 5):
            window.move(endy, endx)
            continue
        elif (selection == curses.KEY_DC):
            cy, cx = window.getyx()
            window.delch()
            result = result[:(cx - startx)] + result[(cx - startx + 1):]
            endx -= 1
            continue
        elif (selection == curses.KEY_LEFT):
            cy, cx = window.getyx()
            if (cx > startx):
                window.move(cy, cx-1)
            continue
        elif (selection == curses.KEY_RIGHT):
            cy, cx = window.getyx()
            if (cx < endx):
                window.move(cy, cx+1)
            continue
        elif (selection == curses.KEY_BACKSPACE or selection == 127):
            cy, cx = window.getyx()
            if (cx == startx):
                # no more to backspace
                continue
            else:
                window.move(cy, cx-1)
                window.delch()
                endx -= 1
                cx -= 1
                result = result[:(cx - startx)] + result[(cx - startx + 1):]
                continue
        else:
            endy, endx = window.getyx()
            if (selection < 256 and endx+1 < maxx):
                result = result[:(endx - startx)] + chr(selection) + result[(endx - startx):]
                window.addstr(result[(endx - startx):])
                window.move(endy, endx+1)
                endy, endx = window.getyx()


    window.keypad(False)
    output(result)
    return result