2

I want to condition an action on whether or not a modifier key (Ctrl) is pressed. One workaround I have found is to install an event filter and use QApplication.queryKeyboardModifiers() to detect when Ctrl is pressed, and QApplication.keyboardModifiers() to detect when Ctrl is released:

from PySide6.QtCore import Qt, Signal
from PySide6.QtWidgets import QApplication, QMainWindow

class MainWindow(QMainWindow):

    ctrl_signal = Signal(bool)

    def __init__(self):
        QMainWindow.__init__(self)
        self.installEventFilter(self)
        self.ctrl_signal.connect(self.ctrl_slot)

    def eventFilter(self, _object, e):
        if QApplication.queryKeyboardModifiers() == Qt.CTRL: # This runs twice, and only on key press (not release)
            print("Ctrl pressed")
            self.ctrl_signal.emit(True)
        elif QApplication.keyboardModifiers() == Qt.CTRL: # This runs once, but only on release
            print("Ctrl released")
            self.ctrl_signal.emit(False)
        return False

    def ctrl_slot(self, e):
        print("e: ", e)  # Do something

app = QApplication([])
window = MainWindow()
window.show()
app.exec_()

However, I am concerned that this is an unintended use of the .queryKeyboardModifiers() and .keyboardModifiers() functions, and therefore will likely lead to more trouble later on. Is there a proper way to detect when a modifier key is pressed/released in isolation (i.e. without any other keys being pressed)?

Though I am using PySide6, I'll accept answers in C++ or PyQt if they're helpful.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
amateur3057
  • 345
  • 1
  • 2
  • 11

1 Answers1

4

What you are currently doing is to check if the Ctrl key was pressed every time an event happens (for example click, move, resize, etc) and it seems that that is not your goal but only to detect when is the change so you must improve your filter for the Qt.KeyPress or Qt.KeyRelease event. On the other hand, your method will not work if you want to detect when another child widget consumes an event since it will not be propagated to the parent, instead it is better to apply the filter to the QWindow since the keyboard events arrive when it has the focus and does not depend on the logic of the children.

from PySide6.QtCore import Qt, Signal, QObject, QEvent
from PySide6.QtWidgets import QApplication, QMainWindow


class ControlHelper(QObject):
    ctrl_signal = Signal(bool)

    def __init__(self, window):
        super().__init__(window)
        self._window = window

        self.window.installEventFilter(self)

    @property
    def window(self):
        return self._window

    def eventFilter(self, obj, event):
        if obj is self.window:
            if event.type() == QEvent.KeyPress:
                if event.key() == Qt.Key_Control:
                    self.ctrl_signal.emit(True)
            if event.type() == QEvent.KeyRelease:
                if event.key() == Qt.Key_Control:
                    self.ctrl_signal.emit(False)
        return super().eventFilter(obj, event)


class MainWindow(QMainWindow):
    ctrl_signal = Signal(bool)

    def ctrl_slot(self, e):
        print("e: ", e)


app = QApplication([])
window = MainWindow()
window.show()

helper = ControlHelper(window.windowHandle())
helper.ctrl_signal.connect(window.ctrl_slot)

app.exec_()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Is there any way to also detect when a 2nd Ctrl key is pressed? If a user 1) presses Left-Ctrl, 2) presses Right-Ctrl, 3) releases Left-Ctrl, and 4) releases Right-Ctrl, then the current output is "True False False" because step 2) was not detected (ideal output would be "True True False False"). As I intend to use Ctrl to toggle a state, this would cause the function of Ctrl to be reversed/out of sync. – amateur3057 Apr 28 '21 at 22:16
  • 1
    @amateur3057 My keyboard does not have 2 control so I will not be able to test what I will point out: A possible solution is to track the nativeScanCode, for example check what `print(event.nativeScanCode())` you get when pressing the Ctrl on the left and the Ctrl on the right, and then you do the follow-up taking that information. Qt is not oriented to make that distinction since its usefulness is only to know if some key sent the ctrl command – eyllanesc Apr 28 '21 at 22:21
  • The `print(event.nativeScanCode())` prints unique integers for each of the Ctrl keys, but unfortunately does not respond to the 2nd Ctrl key being pressed, either. – amateur3057 Apr 28 '21 at 22:35