Python中的按键监听器?

52

是否有一种在Python中无需使用类似于pygame这样臃肿的模块来实现键盘监听的方法?

例如,当我按下a键时,它应该会在控制台上打印以下内容:

按下了a键!

它还应该监听方向键、空格键和Shift键。


1
不幸的是,在终端中通常无法检测到Shift键是否按下。唯一能获取该信息的时候是当你得到一个受Shift键影响的字符,然后你必须猜测是否按下了Shift键来生成该字符。 - icktoofay
1
箭头键怎么办?我知道在其他一些编程语言中这是可能的(甚至使用Shift键!)叹气 哦,好吧。 - ollien
2
在Linux上,您可以监视“/dev/input/*”并直接提取按键。 - Blender
2
@Blender:除非你是通过SSH运行它... - icktoofay
1
@njk828:箭头键是可能的,但有点棘手,因为它们通常是转义序列。 - icktoofay
显示剩余3条评论
7个回答

59

我正在寻找一种不需要窗口焦点的简单解决方案。Jayk的答案,pynput,对我来说非常完美。这是我如何使用它的示例。

from pynput import keyboard

def on_press(key):
    if key == keyboard.Key.esc:
        return False  # stop listener
    try:
        k = key.char  # single-char keys
    except:
        k = key.name  # other keys
    if k in ['1', '2', 'left', 'right']:  # keys of interest
        # self.keys.append(k)  # store it in global-like variable
        print('Key pressed: ' + k)
        return False  # stop listener; remove this if want more keys

listener = keyboard.Listener(on_press=on_press)
listener.start()  # start to listen on a separate thread
listener.join()  # remove if main thread is polling self.keys

4
pynput的一个显著限制是它依赖于图形环境,因此在Linux上需要X。因此,在远程VPS上它无法工作。 - tombh
1
对于那些想知道的人,你可以在运行循环或GUI时运行此代码。尝试在此处的代码末尾添加 while True: pass - Sylvester Kruin

31

很不幸,要做到这一点并不容易。如果您想要创建某种文本用户界面,您可能需要研究 curses。如果您想像在终端中那样显示内容,但希望输入不同,则必须使用 termios 来处理,不幸的是,这在Python中似乎缺乏文档说明。不过,这两个选项都不是那么简单的。此外,它们不能在Windows下工作;如果您需要在Windows下使用它们,则必须使用 PDCurses 代替 curses 或者使用 pywin32 而不是 termios


我成功地实现了这个功能。它输出您键入的十六进制表示。正如我在您的问题评论中所说,箭头键比较棘手;我认为您会同意这一点。

#!/usr/bin/env python
import sys
import termios
import contextlib


@contextlib.contextmanager
def raw_mode(file):
    old_attrs = termios.tcgetattr(file.fileno())
    new_attrs = old_attrs[:]
    new_attrs[3] = new_attrs[3] & ~(termios.ECHO | termios.ICANON)
    try:
        termios.tcsetattr(file.fileno(), termios.TCSADRAIN, new_attrs)
        yield
    finally:
        termios.tcsetattr(file.fileno(), termios.TCSADRAIN, old_attrs)


def main():
    print 'exit with ^C or ^D'
    with raw_mode(sys.stdin):
        try:
            while True:
                ch = sys.stdin.read(1)
                if not ch or ch == chr(4):
                    break
                print '%02x' % ord(ch),
        except (KeyboardInterrupt, EOFError):
            pass


if __name__ == '__main__':
    main()

1
你能举个例子来说明如何监听箭头键吗?Python文档对此并不是很清楚。 - ollien
@njk828:是的,正如我所说,文档有点稀少。我发布了一些代码,可以将终端置于正确模式并读取原始字符。从那里开始,您可以尝试弄清楚箭头键如何表示。 - icktoofay
@icktoofay 我认为这只能在终端环境下工作,对吧?否则,如果按键事件发生在外部...它将不会被写入标准输入流,对吧? - Prakash Palnati
@Prakash047:没错,它只能在你专注于终端窗口时工作。如果你想在其他地方监听按键,你就得研究一下特定于窗口系统的方法,比如在Linux上使用X11或Wayland。 - icktoofay
谢谢,这也适用于Mac OS。只是对于一些不可打印字符有点麻烦:Esc被读作0x1B,而其他一些字符也被读作0x1B,后面跟着额外的字符,例如F1产生0x1B 0x4F 0x50,箭头向上产生0x1B 0x5B 0x41,删除(在Mac键盘上为Fn-Backspace)产生0x1B 0x5B 0x33 0x7E。 - Lars
@ollien 考虑接受其中一个建议使用更直观的 pynput 库而不是这个库的答案。 - kynan

18

在 Python 中有一种处理按键监听的方式,可以通过 pynput 实现。

命令行:

$ pip install pynput

Python 代码:

from pynput import keyboard
# your code here

11
优秀的推荐,因为这是一个非常易于使用且符合Python风格的软件包,但你可以提供一个更清晰的示例。对于任何想要了解如何使用它的人,请查看文档: https://pypi.python.org/pypi/pynput - rien333

16

以下是在Windows上的操作步骤:

"""

    Display series of numbers in infinite loop
    Listen to key "s" to stop
    Only works on Windows because listening to keys
    is platform dependent

"""

# msvcrt is a windows specific native module
import msvcrt
import time

# asks whether a key has been acquired
def kbfunc():
    #this is boolean for whether the keyboard has bene hit
    x = msvcrt.kbhit()
    if x:
        #getch acquires the character encoded in binary ASCII
        ret = msvcrt.getch()
    else:
        ret = False
    return ret

#begin the counter
number = 1

#infinite loop
while True:

    #acquire the keyboard hit if exists
    x = kbfunc() 

    #if we got a keyboard hit
    if x != False and x.decode() == 's':
        #we got the key!
        #because x is a binary, we need to decode to string
        #use the decode() which is part of the binary object
        #by default, decodes via utf8
        #concatenation auto adds a space in between
        print ("STOPPING, KEY:", x.decode())
        #break loop
        break
    else:
        #prints the number
        print (number)
        #increment, there's no ++ in python
        number += 1
        #wait half a second
        time.sleep(0.5)

我稍微改了一下循环。while True: 换行 x = kbfunc() 换行 if x != False: 换行 print("keypress:", x.decode())。这对我来说很好用。不知道你是否知道如何让终端在失去焦点时仍然能够监听按键事件。 - Musixauce3000

10

键盘

使用这个小型Python库完全控制你的键盘。钩子全局事件,注册热键,模拟按键等等。

在所有键盘上进行全局事件钩子(无论焦点在哪里,都可以捕捉键)。 监听和发送键盘事件。 支持Windows和Linux(需要sudo),具有实验性的OS X支持(感谢@glitchassassin!)。 纯Python,无需编译C模块。 零依赖项。安装和部署简单,只需复制文件即可。 Python 2和3。 支持复杂的热键(例如Ctrl+Shift+M,Ctrl+Space),并且超时可控。 包括高级API(例如记录和播放,添加缩写)。 将键映射为您布局中实际存在的键,并具有完全的国际化支持(例如Ctrl+ç)。 事件自动在单独的线程中捕获,不会阻塞主程序。 经过测试和文档化。 不影响重音死键(我在看你,pyHook)。 鼠标支持可通过项目mouse获取(pip install mouse)。

来自README.md

import keyboard

keyboard.press_and_release('shift+s, space')

keyboard.write('The quick brown fox jumps over the lazy dog.')

# Press PAGE UP then PAGE DOWN to type "foobar".
keyboard.add_hotkey('page up, page down', lambda: keyboard.write('foobar'))

# Blocks until you press esc.
keyboard.wait('esc')

# Record events until 'esc' is pressed.
recorded = keyboard.record(until='esc')
# Then replay back at three times the speed.
keyboard.play(recorded, speed_factor=3)

# Type @@ then press space to replace with abbreviation.
keyboard.add_abbreviation('@@', 'my.long.email@example.com')
# Block forever.
keyboard.wait()

1
为了避免依赖于X,Linux部分读取原始设备文件(/dev/input/input*),但这需要root权限。这对于许多日常工具来说很不方便... - feeela

2

虽然我喜欢使用键盘模块来捕获键盘事件,但我不喜欢它的record()函数,因为它返回一个数组,类似于[KeyboardEvent("A"), KeyboardEvent("~")],我觉得这有点难以阅读。因此,为了记录键盘事件,我喜欢同时使用键盘模块和线程模块,像这样:

import keyboard
import string
from threading import *


# I can't find a complete list of keyboard keys, so this will have to do:
keys = list(string.ascii_lowercase)
"""
Optional code(extra keys):

keys.append("space_bar")
keys.append("backspace")
keys.append("shift")
keys.append("esc")
"""
def listen(key):
    while True:
        keyboard.wait(key)
        print("[+] Pressed",key)
threads = [Thread(target=listen, kwargs={"key":key}) for key in keys]
for thread in threads:
    thread.start()

1
我喜欢使用Pynput。 它有许多选项,可以提供更简单、更优雅的解决方案。
例如:
from pynput import keyboard

def on_activate_a():
    print('A pressed')

def on_activate_b():
    print('B pressed')

def on_activate_c():
    print('C pressed')

def quit():
    print('QUIT')
    h.stop()

with keyboard.GlobalHotKeys({
        'a': on_activate_a,
        'b': on_activate_b,
        'c': on_activate_c,
        '<ctrl>+c': quit}) as h:
    h.join()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接