有没有办法捕获 Python 中的击键并过滤掉由按键重复生成的击键?

Is there a way to capture keystrokes in Python and filter out those generated by key repeat?

我正在 Python 中尝试一些输入法,并使用 pynput 我有一个简单的按键抓取器,就像在文档中一样:

from pynput.keyboard import Key, Listener

def on_press(key):
    print('{0} pressed'.format(
        key))

def on_release(key):
    print('{0} release'.format(
        key))
    if key == Key.esc:
        # Stop listener
        return False

# Collect events until released
with Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

但是,无论键是由重复键生成还是仅由按下键生成,都会调用 on_press,据我所知,我不知道它是哪个。

有什么方法可以知道吗?我的目标平台是 macOS,但这应该可以在 Windows 上运行,而且我可能希望有一天能移植这个想法。

pynput 很好,但我不介意切换库,如果这意味着我可以得到我需要的东西来工作。

在按键重复事件的情况下(至少在我的 Ubuntu 系统上)额外的按键事件是 generated/simulated 而没有干预按键释放事件:

$ python keypress_vs_repeat.py 
Key.enter release
a'a' pressed
a'a' pressed
a'a' pressed
a'a' pressed
a'a' release

您可以使用该信息来区分真实按键和模拟按键:

keypress_vs_repeat.py

from pynput.keyboard import Key, Listener

currently_pressed_key = None

def on_press(key):
    global currently_pressed_key
    if key == currently_pressed_key:
        print('{0} repeated'.format(key))
    else:
        print('{0} pressed'.format(key))
        currently_pressed_key = key

def on_release(key):
    global currently_pressed_key
    print('{0} release'.format(key))
    currently_pressed_key = None
    if key == Key.esc:
        # Stop listener
        return False

# Collect events until released
with Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

测试(按一次a,按住b):

$ python keypress_vs_repeat.py
Key.enter release
'a' pressed
a'a' release
'b' pressed
b'b' repeated
b'b' repeated
b'b' repeated
b'b' repeated
b'b' repeated
b'b' release
Key.esc pressed
^[Key.esc release
^[$

这适用于 OS X 和 Mint。我没有 Windows 来测试:

class _Getch:
    """Gets a single character from standard input.  Does not echo to the
screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()
    def __call__(self): return self.impl()

class _GetchUnix:
    def __init__(self):
        import tty, sys

    def __call__(self):
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()

getch = _Getch()

测试:

c=getch()
i=1
while c!='q':
    c=getch()
    print c, i
    # if key is held, it does not repeat...
    i+=1

基于this ActiveState recipe