Search code examples
pythonpython-3.xpyqtkeyboardpyqt5

How to show a QMenu using global keyboard shortcut in PyQt5?


I was trying to show a QMenu instance by an hotkey(e.g. "F1") by PyQt5, then I found this package keyboard.

Trying to use it like: keyboard.add_hotkey('F1', self.show_menu, suppress=True)

Then I got these code:

import sys

import keyboard

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


class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__(flags=Qt.WindowStaysOnTopHint)

        self.menu = QMenu('Menu')
        self.menu.addAction(QAction('menu1', self.menu))
        self.menu.addAction(QAction('menu2', self.menu))
        self.menu.addAction(QAction('menu3', self.menu))

        self.show_menu()  # this works well

        keyboard.add_hotkey('F1', self.show_menu, suppress=True)  # this hotkey works but not showing the menu

    def show_menu(self):
        print('111')
        self.menu.popup(QCursor().pos())
        print('222')


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)
    win = MainWindow()
    # win.show()
    sys.exit(app.exec_())

Actually, the calling to method self.show_menu in __init__ works well, a menu can popup as expected.

But the QUESTION is that, when I press the hotkey "F1", "111" and "222" would be printed, but the menu, won't appear.

Is there anything wrong, or I can make it in some other ways? Please tell me, thank you.


Solution

  • The callback associated with add_hotkey is executed in a secondary thread, and in the OP code the callback is the show_menu method that modifies the GUI which is prohibited by Qt. The solution is to use signals:

    import sys
    
    import keyboard
    
    from PyQt5.QtCore import Qt, QObject, pyqtSignal
    from PyQt5.QtGui import QCursor
    from PyQt5.QtWidgets import QAction, QApplication, QMainWindow, QMenu
    
    
    class KeyBoardManager(QObject):
        F1Signal = pyqtSignal()
    
        def start(self):
            keyboard.add_hotkey("F1", self.F1Signal.emit, suppress=True)
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__(flags=Qt.WindowStaysOnTopHint)
    
            self.menu = QMenu("Menu")
            self.menu.addAction(QAction("menu1", self.menu))
            self.menu.addAction(QAction("menu2", self.menu))
            self.menu.addAction(QAction("menu3", self.menu))
    
            manager = KeyBoardManager(self)
            manager.F1Signal.connect(self.show_menu)
            manager.start()
    
        def show_menu(self):
            print("111")
            self.menu.popup(QCursor.pos())
            print("222")
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        app.setQuitOnLastWindowClosed(False)
        win = MainWindow()
        # win.show()
        sys.exit(app.exec_())