如何使用python在控制台中获取键盘事件

问题描述 投票:5回答:1

我想用python处理控制台中的键盘事件。运行脚本有一些持久输出流,当管理员触发一个按键事件时,脚本会改变其输出内容。

我用代码完成它如下(按'q'将触发输出更改),但有两个问题

  1. 我的输出中有一个增加的空间。调试后,我发现代码“tty.setraw(fd)”导致,但我不知道如何解决它
  2. ctrl + c不能再工作了(如果#“tty.setraw(fd)”,ctrl + c就可以了)

如果它太复杂,任何其他模块都可以做我想要的?我试过诅咒模块,似乎会冻结窗口输出而无法在mutlithread中进行协调

#!/usr/bin/python
import sys
import select
import tty, termios
import threading
import time

def loop():

    while loop_bool:
        if switch:   
            output = 'aaaa'
        else:
            output = 'bbbb'
        print output
        time.sleep(0.2)


def change():
    global switch
    global loop_bool    
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)

    try: 
        while loop_bool:
            tty.setraw(fd)
            i,o,e = select.select([sys.stdin],[],[],1)
            if len(i)!=0:
                if i[0] == sys.stdin:
                    input = sys.stdin.read(1)

                    if input =='q':
                        if switch:
                            switch = False                                         
                        else: 
                            switch =  True


        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    except KeyboardInterrupt:

        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        loop_bool = False


try:   
    switch = True
    loop_bool = True    
    t1=threading.Thread(target=loop)
    t2=threading.Thread(target=change)        

    t1.start()
    t2.start()

    t1.join(1)
    t2.join(1)
except KeyboardInterrupt:

    loop_bool = False
python
1个回答
1
投票

这可能取决于你所使用的平台,甚至可能是你正在使用的终端模拟器,我不确定它是否能解决你的问题,但......

您应该能够在不调用tty.setraw的情况下获得逐字符输入,只需将“规范模式”设置为关闭,您可以通过屏蔽ICANON的lflag属性中的tcgetattr()位来实现。您可能还需要设置VMIN或VTIME属性,但默认值应该已经正确。

有关详细信息,请参阅linux man page中的“规范和非规范模式”部分,OS X man page中的“非规范模式输入处理”部分,或者如果您使用的是其他平台,请参阅等效部分。

将此作为上下文管理器而不是进行显式清理可能更清晰。特别是因为你现有的代码每次都通过循环执行setraw,并且只在最后恢复;理想情况下,它们应成对配对,并使用with声明保证。 (另外,这样你就不需要在except子句和正常流程中重复自己了。)所以:

@contextlib.contextmanager
def decanonize(fd):
    old_settings = termios.tcgetattr(fd)
    new_settings = old_settings[:]
    new_settings[3] &= ~termios.ICANON
    termios.tcsetattr(fd, termios.TCSAFLUSH, new_settings)
    yield
    termios.tcsetattr(fd, termios.TCSAFLUSH, old_settings)

现在:

def change():
    global switch
    global loop_bool

    with decanonize(sys.stdin.fileno()):
        try:
            while loop_bool:
                i,o,e = select.select([sys.stdin],[],[],1)
                if i and i[0] == sys.stdin:
                    input = sys.stdin.read(1)                    
                    if input =='q':
                        switch = not switch
        except KeyboardInterrupt:
            loop_bool = False

或许你想要with块在较低的水平(在while内,或至少try)。

(PS,我将几行代码转换为等效但更简单的形式,以删除几个级别的嵌套。)

YMMV,但这是我的Mac测试:

Retina:test abarnert$ python termtest.py 
aaaa
aaaa
aaaa
qbbbb
bbbb
bbbb
qaaaa
aaaa
aaaa
^CRetina:test abarnert$

这让我觉得你可能想要关闭输入回声(由new_settings[3] &= ~termios.ECHO做),这意味着你可能想要用更通用的东西替换decanonize函数,用于临时设置或清除任意termios标志。 (另外,如果tcgetattr返回namedtuple而不是list,那将是很好的,所以你可以做new_settings.lflag而不是new_settings[3],或者至少为属性索引提供符号常量。)

同时,根据您的评论,听起来^ C仅在第一或第二秒内有效,并且它与joins中的超时有关。这是有道理的 - 主线程只是启动两个线程,两个join(1)调用,然后完成。所以,2。启动后的几秒钟,它完成了所有的工作 - 并离开了try:块 - 所以KeyboardInterrupt不再有任何方式触发loop_bool = False并发出工作线程退出的信号。

我不确定为什么你首先会在joins上有超时,以及当他们超时时会发生什么,但有三种可能性:

  1. 你不想在^ C之前退出,并且由于任何正当理由而没有超时。所以把它们拿出来。然后主线程将永远等待其他两个线程完成,并且它仍然在try块内,所以^ C应该能够设置loop_bool = False
  2. 该应用程序应该在2秒后正常退出。 (我猜你会更喜欢这对线程上的单个join-any或join-all,并且有2秒的超时,但是因为Python没有任何简单的方法,所以你顺序加入了线程。)在这种情况下,您希望在超时结束时立即设置loop_bool = False。所以只需将except改为finally
  3. 超时总是应该足够慷慨(可能这只是你的真实应用程序的简化版本),如果你超过了超时,这是一个特例。之前的选项可能仍然有效,也可能无效。如果没有,请在两个线程上设置daemon = True,并且当主线程完成时它们将被杀死(不好被要求关闭)。 (请注意,它的工作方式在Windows与Unix上有点不同 - 尽管你可能并不关心这个应用程序的Windows。更重要的是,所有文档都说“整个Python程序退出时没有活着的非留下守护程序线程,“因此你不应指望任何守护程序线程能够进行任何清理,但也不应指望它们不进行任何清理。不要在守护程序线程中做任何可能留下临时文件的事情周围,​​关键的日志消息不成文,等)
© www.soinside.com 2019 - 2024. All rights reserved.