在 PyQt5 中检测外部键盘事件

2022-01-12 00:00:00 python pyqt5 keyboard-events keylogger

问题描述

如何在 PyQT5 中实现关键监听器?即使应用在后台,我也想检测按键.

How can I implement a key listener in PyQT5? I want to detect keypresses even when the app is in background.

from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
import sys


class Window(QWidget):
    
    ...
       

    def keyPressEvent(self, e): # doesnt work when app is in background
        if e.key() == Qt.Key_F3:
            print(1)
        elif e.key() == Qt.Key_F4:
            print(0)

   ...

        
App = QApplication(sys.argv)
App.setStyle('Fusion')
window = Window()
sys.exit(App.exec())



解决方案

Qt 只有在其顶层窗口有键盘焦点时才能访问键盘事件.如果窗口被最小化或另一个窗口获得焦点,您将不会收到键盘事件.

Qt can access keyboard events only if any of its top level window has keyboard focus. If the window is minimized or another window takes focus, you will not receive keyboard events.

唯一的解决方案是使用外部库,但它们有局限性.

The only solution is to use an external library, but they have limitations.

keyboard 模块似乎不支持 macOS,而 pyinput 可以,但需要该操作系统的 root 访问权限.我不知道有任何其他方法可以不受限制地支持所有三个平台.

The keyboard module does not seem to support macOS, while pyinput does, but requires root access for that OS. I don't know of any other ways that support all three platforms without limitations.

在任何情况下,您都不应该依赖于对当前按键的定时检查,因为您最终肯定会错过一些事件.
虽然通常会使用一个单独的线程来实现事件侦听器(通常是阻塞的),但幸运的是在这两种情况下都有非阻塞系统来调用回调函数(因此您实际上不需要单独的线程).

In any case, you should not rely on timed checking of the currently pressed keys, because you'll certainly end up missing some events.
While normally one would use a separate thread that implements the event listener (which are normally blocking), luckily in both cases there are non blocking systems to call callback functions (so you don't actually need a separate thread).

以下是使用 keyboard 模块的基本示例:

The following is a basic example using the keyboard module:

from PyQt5 import QtCore, QtWidgets
import keyboard

class KeyGrabber(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QVBoxLayout(self)
        self.button = QtWidgets.QPushButton('start')
        layout.addWidget(self.button)
        self.button.setCheckable(True)
        self.button.toggled.connect(self.setGrabbing)

    def keyboardEventReceived(self, event):
        if event.event_type == 'down':
            if event.name == 'f3':
                print('F3 pressed')
            elif event.name == 'f4':
                print('F4 pressed')

    def setGrabbing(self, enable):
        if enable:
            self.button.setText('stop')
            # on_press returns a hook that can be used to "disconnect" the callback
            # function later, if required
            self.hook = keyboard.on_press(self.keyboardEventReceived)
            self.showMinimized()
        else:
            self.button.setText('start')
            keyboard.unhook(self.hook)

相关文章