如何在Python中获取按键码

13

我需要知道按下了哪个键,但不需要字符的代码。我想知道当有人按下'A'键时,即使获得的键是'a'或'A',我也要知道,其他所有键也是如此。

我不能使用PyGame或任何其他库(包括Tkinter)。 只能使用Python标准库,并且必须在终端而不是图形界面中完成。

不需要字符代码。我需要知道按键代码。

例如:

ord('a') != ord('A')                      # 97 != 65
someFunction('a') == someFunction('A')    # a_code == A_code

图形界面还是终端用户界面? - iny
Windows还是Linux?请更新问题,而不是添加另一个评论。 - S.Lott
请在您的问题中添加“编辑”标记以显示您增强问题的位置。 - myroslav
TKinter是Python标准库的一部分。它已经随着Python发行多年了。 - SingleNegationElimination
我有一个类似的问题,你能帮我吗?链接:https://stackoverflow.com/questions/63464407/linux-phyton-numpad-key-cant-be-assigned-to-program - Valac
8个回答

26
请查看tty标准模块。它允许使用tty.setcbreak(sys.stdin)从默认的行定向(cooked)模式转换成字符定向(cbreak)模式。从sys.stdin读取单个字符将导致下一个按下的键盘按键(如果它生成代码):
import sys
import tty
tty.setcbreak(sys.stdin)
while True:
    print ord(sys.stdin.read(1))

注意:此解决方案仅适用于Unix(包括Linux)。

编辑:在Windows上尝试使用msvcrt.getche()/getwche()。/我没有地方可以尝试...


编辑2:使用ctypes.windll通过win32低级控制台API(请参见SO示例)使用ReadConsoleInput函数。您应该过滤掉按键-e.EventType==KEY_EVENT,并查找e.Event.KeyEvent.wVirtualKeyCode值。可以在http://www.benryves.com/tutorials/?t=winconsole&c=4找到应用程序示例(不是Python的,只是为了让您有一个想法)。


重复一遍,我不需要字符代码,我需要的是按键码,按下A键或SHIFT+A键应该是相同的。使用ord()函数将返回A键和SHIFT+A键的不同值;而对于箭头等键则无法正常工作。 - Lucas Gabriel Sánchez
我觉得你提到的问题太底层了。这在DOS中是有效的。现在的操作系统采用按键映射的方法,将按键映射到符号,因此很容易切换键盘布局(语言),所以很少有机会涉及到底层按键操作。pygame可以解决这个问题。 - myroslav
代码效果不错,但在我的笔记本上发现了一些问题:1. CTRL+J=10 以及CTRL+M=10。它们是相同的,我不知道为什么;2. CTRL+一些字符与CTRL+ALT+这些字符相同。 - Enze Chi
在 macOS 上,这不会打印 fn、control、option、command 的结果。如果您需要这些按键的键码,请参阅下面我的 wxPython 回答。 - spacether

9

根据你想要实现的目标,或许使用像pygame这样的库可以满足你的需求。Pygame包含比Python标准库更高级的按键处理功能。


谢谢,但我不必使用除标准Python库以外的任何其他库。 - Lucas Gabriel Sánchez
1
好的。我会把这个答案留在这里(不删除),因为它可能对其他来问类似问题的人有用。 - Greg Hewgill
1
对于那些点踩这个帖子的人:原问题没有说明pygame不可接受。 - Greg Hewgill
+1 对于相关帮助的支持是SO的主要优势之一。 - mechanical_meat

5

看看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()

希望这有所帮助。

仅供参考,根据他们的文档,此库在MacOS上运行效果不佳https://pynput.readthedocs.io/en/latest/limitations.html#macos - spacether
无法在控制台中工作:“请确保您正在运行 X 服务器,并正确设置 DISPLAY 环境变量”。 - Putnik
1
虽然这个方法可行,但你只是复制了文档中的示例。这并没有回答问题:“我需要知道密钥代码”。 - dlewin

3
您可能需要使用Tkinter,这是“标准”的Python GUI,已经包含在Python中多年。
由于数据如何进入和退出命令行进程,命令行解决方案可能无法实现。各种GUI程序通过(可能是库封装的)事件流接收用户输入。每个事件都将是事件详细信息的记录。对于按键事件,该记录可能包含任何键码、修改键位域或以某种编码形式的文本字符。字段的名称及其如何命名取决于您调用的事件库。
命令行程序通过字符流接收用户输入。没有办法捕获更低级别的数据。正如myroslav在他的帖子中解释的那样,tty可能处于煮熟模式或未煮熟模式,唯一的区别在于,在煮熟模式下,终端将为您处理(某些)控制字符,例如删除和输入,使进程接收输入行,而不是逐个字符接收。
处理比此更低级别的内容需要(依赖于操作系统)系统调用或打开/dev中的字符设备。Python的标准库没有提供此标准设施。

3

最近我一直在探讨的一个主题。 还有一件需要检查的事情是,在不同的键盘布局中是否得到正确的值。例如,当你切换到法语-PC布局时,它似乎并不像pygame正确地报告数字键代码。我的建议是使用wxPython。它在RawKeyCode属性中公开了平台特定的键码。 以下是一个演示代码示例:

import logging as log
import wx

class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(200,100))

        self.panel = wx.Panel(self, wx.ID_ANY)
        self.panel.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.panel.Bind(wx.EVT_KEY_UP, self.OnKeyDown)
        self.panel.Bind(wx.EVT_CHAR, self.OnKeyDown)
        self.panel.SetFocus()
        self.Show(True)

    def OnKeyDown(self, event=None):
        line_txt = '---------------------'
        print(line_txt)
        for var in ['RawKeyCode', 'KeyCode', 'UnicodeKey']:
            print(var, getattr(event, var))
            if var == 'UnicodeKey':
                print('char', chr(getattr(event, var)))
        print(line_txt)


if __name__ == "__main__":
    app = wx.App(False)
    gui = MainWindow(None, "test")
    app.MainLoop()

例如,当我在我的Mac上按下fn键(仅限于Mac的键)时,我会看到以下记录:
---------------------
RawKeyCode 63
KeyCode 0
UnicodeKey 0
char 
---------------------

这与平台特定的按键代码一致。

0

这个函数将返回大写字符的代码:

def upCcode( ch ):
    if(len(ch) == 1):
        return ord(ch.upper())

这是针对小写字符代码的:

def lowCcode( ch ):
        if(len(ch) == 1):
            return ord(ch.lower())

这种方式更容易,而且您不需要导入外部库。

您需要选择两种方法中的一种作为您在问题中描述的“someFunction”。以下是一个例子:

输出:

# when using upCode():
>> upCcode('a')
65

>> upCcode('A')
65

# when using lowCode():
>> lowCcode('a')
97

>> lowCcode('A')
97

0

显而易见的答案:

someFunction = string.upper

ord('a') != ord('A')                      # 97 != 65
someFunction('a') == someFunction('A')    # a_code == A_code

或者,换句话说:

char_from_user = getch().upper() # read a char converting to uppercase
if char == 'Q':
    # quit
    exit = True # or something
elif char in ['A', 'K']:
    do_something()

这是一个getch函数的实现,可以在Windows和Linux平台上工作,基于此方法

class _Getch(object):
    """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(object):
    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(object):
    def __init__(self):
        import msvcrt

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


getch = _Getch()

0

如果你需要在Windows上工作,建议尝试使用msvcrt


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