python 空闲时 urwid 超时

python urwid timeout on idle

如果超过 30 秒没有收到用户的输入,有没有办法让 urwid 应用程序在可配置的超时后执行 sys.exit()?

我们正面临网络中断,这导致 SSH 会话被删除,但客户端程序保持 运行 并持有数据库锁,手动终止是目前唯一的选择,因此有此要求。

您可以在主循环中设置一个警报,当它超时时将调用您想要的任何代码。在这里,我调用了一个使用 ExitMainLoop 异常的函数,但 sys.exit() 也可以。

这段代码在键盘输入发生时取消之前的报警(如果有的话),然后设置一个新的报警。只要有钥匙进来,警报就永远不会响。

在内部,截至 2020 年底,对于警报,urwid 似乎使用 Python 的 time.time(),但不能保证每秒只前进一秒。如果系统时钟向前调整(通过 NTP?),警报可能会提前关闭,退出程序。

import urwid

timeout_time=30

def urwid_exit(loop, user_data):
    raise urwid.ExitMainLoop
    
def handle_input(input):
    global txt
    global loop
    
    #don't reset the alarm on a mouse click,  
    #  and don't try to display it (the .set_text would error if given a tuple)
    if not type(input) is tuple:
        if hasattr(handle_input, "last_alarm") and handle_input.last_alarm:
            loop.remove_alarm(handle_input.last_alarm)
        handle_input.last_alarm = loop.set_alarm_in(timeout_time, urwid_exit)
        txt.set_text("key pressed: %s" % input)

txt = urwid.Text("Hello world")
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill, unhandled_input=handle_input)
#call handle input once without any input to get the alarm started
handle_input(None)
loop.run()

StephenK 的答案略有不同,即使用 loop.event_loop.enter_idle(callback) 而不是 unhandled_input。每当 urwid 进入空闲状态时,callback 函数将是 运行,包括在处理按键事件之后。这有点更一般:计时器在 all activity 完成后启动。 (比如说,最后一次按键启动了一个需要很多秒才能完成的动作)

相关文档位于 https://urwid.readthedocs.io/en/latest/reference/main_loop.html

import urwid

timeout = 10
txt = urwid.Text(
        'This program will exit after '
        f'_{timeout}_ seconds of inactivity '
        '(no keypresses, etc)\n',
        align='center'
        )
fill = urwid.Filler(txt, 'top')
loop = urwid.MainLoop(fill)

alarm_handle = None

def alarm_callback(_loop, _user_data):
    raise urwid.ExitMainLoop

def idle_callback():
    global alarm_handle
    loop.remove_alarm(alarm_handle)  # remove old alarm
    alarm_handle = loop.set_alarm_in(timeout, alarm_callback)
    text = txt.get_text()[0] + f"\nAlarm has been reset _{alarm_handle[1]}_ times"
    txt.set_text(text)

loop.event_loop.enter_idle(idle_callback)
loop.run()