PyQt5 обработка нажатия клавиши, если окно не активно

Есть программа на PyQt5, стоит стандартный обработчик нажатия клавиш, но если окно программы не активно, этот обработчик просто не обращает внимание на нажатия клавиш, как это пофиксить?

from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt


class Ui_Settings(object):
    def setupUi(self, Settings):
        Settings.setObjectName("Settings")
        Settings.resize(300, 300)


class Settings(QtWidgets.QWidget, Ui_Settings):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

    def keyPressEvent(self, event):
        if event.nativeVirtualKey() == Qt.Key_F:
            self.test()

    def test(self):
        print("Hello world")


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = Settings()
    MainWindow.show()
    sys.exit(app.exec_())

Ответы (1 шт):

Автор решения: S. Nick

Qt может получить доступ к событиям клавиатуры, только если любое из его окон верхнего уровня имеет фокус клавиатуры. Если окно свернуто или другое окно сфокусировано, вы не будете получать события клавиатуры.

Единственное решение - использовать внешнюю библиотеку, но у них есть ограничения.

Модуль keyboard https://github.com/boppreh/keyboard , похоже, не поддерживает macOS, в то время как pyinput https://github.com/moses-palmer/pynput поддерживает, но для этой ОС требуется root-доступ.

В любом случае вам не следует полагаться на синхронизацию проверенных в данный момент нажатых клавиш, потому что вы наверняка пропустите некоторые события. Хотя обычно можно использовать отдельный поток, который реализует прослушиватель событий (которые обычно блокируют), к счастью, в обоих случаях существуют неблокирующие системы для вызова функций обратного вызова (так что на самом деле вам не нужен отдельный поток).

Ниже приведен пример использования модуля keyboard:

  1. тест 1, Нажмите на кнопку 'start' и понажимайте по клавишам, включая клавиши включенные в логику метода keyboardEventReceived.
  2. тест 2, проверьте сочетание клавиш 'alt+s', 'ctrl+s', 'shift+s'.

import sys
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)

        # тест 2
        keyboard.add_hotkey('alt+s', self.onShow, suppress=False)    # Показать интерфейс
        keyboard.add_hotkey('ctrl+s', self.onHide, suppress=False)   # Скрыть интерфейс
        keyboard.add_hotkey('shift+s', self.onQuit, suppress=False)  # Выйти из программы

    # тест 1
    def keyboardEventReceived(self, event):
        if event.event_type == 'down':
            print(f'event.name -> {event.name}')
            if event.name == 'f3':
                print('F3 pressed')
            elif event.name == 'f4':
                print('F4 pressed')
            elif event.name == 'A':
                print('A pressed')
            elif event.name == 'z':
                print('z pressed')
                
    # тест 1            
    def setGrabbing(self, enable):
        if enable:
            self.button.setText('stop')
            # on_press возвращает hook, который может быть использован для "отключения" 
            # функции обратного вызова позже, если это потребуется
            self.hook = keyboard.on_press(self.keyboardEventReceived)
            self.showMinimized()
        else:
            self.button.setText('start')
            keyboard.unhook(self.hook)
            
### # тест 2
    def onShow(self):                                   # тест 2
        self.show()
        self.showNormal()

    def onHide(self):                                   # тест 2
        self.hide()

    def onQuit(self):                                   # тест 2
        keyboard.unhook_all_hotkeys()                   #  Отменить все горячие клавиши
        QtWidgets.QApplication.instance().quit()
           

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = KeyGrabber()                            
    w.show()
    sys.exit(app.exec_()) 

введите сюда описание изображения

→ Ссылка