简体   繁体   English

在 PyQt5/Pyside2 中窃取焦点

[英]Steal focus in PyQt5/Pyside2

I am creating a launcher, in the style of Albert , Alfred or uLauncher .我正在创建一个启动器,采用AlbertAlfreduLauncher的风格。 My application runs in the background and shows up when a hotkey is pressed.我的应用程序在后台运行,并在按下热键时显示。 I use pynput to listen to hotkeys.我使用pynput来收听热键。 I cannot use PyQt5 hotkey's feature (can't I?) because I need to listen to keyboard events in the system scope, not only the application's scope.我不能使用 PyQt5 热键的功能(不能吗?),因为我需要监听系统 scope 中的键盘事件,而不仅仅是应用程序的 scope。

When the shortcut is pressed, it calls the show() method of my widget.按下快捷方式时,它会调用我的小部件的 show() 方法。 The only issue is that I can't get the focus back on my window, despite the use of raise_ , setFocus and activateWindow .唯一的问题是,尽管使用了raise_setFocusactivateWindow ,但我无法将焦点重新放在我的 window 上。

I found a (ugly) workaround that consists in openning a QMessageBox (+ tweaking its appearance to make it invisible, but I didn't put that in the example code) and closing it immediately after.我发现了一个(丑陋的)解决方法,它包括打开一个 QMessageBox(+ 调整它的外观以使其不可见,但我没有将它放在示例代码中)并在之后立即关闭它。

When I was working on Linux, that workaround was doing the job, and I was ready to forget how ugly it is for it does the job.当我在研究 Linux 时,该解决方法正在完成这项工作,我已经准备好忘记它是多么丑陋,因为它完成了这项工作。 But I switched to Windows (on which my app must run too), and now this cheeky trick seems to cause freeze then crash of my application.但是我切换到 Windows (我的应用程序也必须在其上运行),现在这个厚颜无耻的技巧似乎导致我的应用程序冻结然后崩溃。 Karma?业力? For sure.当然。

Any ways, my application is useless if it cannot catch focus, so I'm asking two questions, and I'd be happy with only one being solved.无论如何,如果我的应用程序无法抓住焦点,它就毫无用处,所以我问了两个问题,我很高兴只解决一个问题。 :) :)

  • Do you know why showing the QMessageBox causes a crash?你知道为什么显示QMessageBox 会导致崩溃吗?
  • Do you know any other way to get the focus back on my application?你知道任何其他方法可以让我的应用程序重新获得关注吗?

Here is an example code to play with.这是一个可以使用的示例代码。

Thank you very much:)非常感谢:)

EDIT : I just found out that even with deactivating the QMessageBox workaround, the application eventually crashes (after 5, 20, 30 calls of the hotkey).编辑:我刚刚发现即使停用 QMessageBox 解决方法,应用程序最终也会崩溃(在 5、20、30 次热键调用之后)。 So the issue might as well be in the way I bind my shortcut to the GUI, I fear a thread issue, but this is way beyond my knowledge:/所以问题也可能在于我将快捷方式绑定到 GUI 的方式,我担心线程问题,但这超出了我的知识范围:/

import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent
from pynput import keyboard


class Launcher(QLineEdit):
    def __init__(self):
        super().__init__()
        self.setFixedSize(QSize(600, 50))
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setPlaceholderText('Search...')

        self.installEventFilter(self)

        self.set_shortcut('<ctrl>+`')

    def set_shortcut(self, shortcut):
        def for_canonical(f):
            return lambda k: f(listener.canonical(k))

        hotkey = keyboard.HotKey(
            keyboard.HotKey.parse(shortcut),
            self.wake_up)

        listener = keyboard.Listener(
                on_press=for_canonical(hotkey.press),
                on_release=for_canonical(hotkey.release))

        listener.start()

    def wake_up(self):
        print('Waking up')
        self.show()
        self.cheeky_focus_stealer()

    def cheeky_focus_stealer(self):
        self.setFocus()
        self.raise_()
        self.activateWindow()

        # Working of linux, but causes freeze/crash on Windows 10
        message_box = QMessageBox(self)
        message_box.show()
        message_box.hide()

    def eventFilter(self, obj, event):
        if obj is self and event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Escape:
                self.hide()
                return True

        return super().eventFilter(obj, event)


def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)

    window = Launcher()
    window.show()

    app.exec_()


if __name__ == "__main__":
    main()

I found my error, so I'm posting an updated piece of code here for it could be helpful to anyone trying to bind a global hotkey to a function that affects a GUI, aka two different thread communicating.我发现了我的错误,所以我在这里发布了一段更新的代码,因为它可能对任何试图将全局热键绑定到影响 GUI(即两个不同的线程通信)的 function 的人有所帮助。

My mistake was indeed to bind the hotkey triggered action straight to my show() method, which implies that the pynput listenner thread will attempt to communnicate with the QApplication .我的错误确实是将热键触发的操作直接绑定到我的show()方法,这意味着pynput线程将尝试与QApplication通信。

The trick is to use a pyqtSignal() and to ask it to trigger the show() method.诀窍是使用pyqtSignal()并要求它触发show()方法。 The signal itself being trigger by the hotkey.信号本身由热键触发。

After doing that in a clean way, my cheeky_focus_stealer works again, because it is ran from the GUI thread.以干净的方式完成之后,我的cheeky_focus_stealer再次工作,因为它是从GUI 线程运行的。

import sys
from PyQt5.QtWidgets import QLineEdit, QApplication, QMessageBox
from PyQt5.QtCore import QSize, Qt, QEvent, QObject, pyqtSignal
from pynput import keyboard


class Forwarder(QObject):
    signal = pyqtSignal()


class Launcher(QLineEdit):
    def __init__(self):
        super().__init__()
        self.setFixedSize(QSize(600, 50))
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
        self.setPlaceholderText('Search...')

        self.installEventFilter(self)

        self.set_shortcut('<ctrl>+`')

    def set_shortcut(self, shortcut):
        # The forwarder must be parented to the Launcher
        forwarder = Forwarder(parent=self)
        forwarder.signal.connect(self.wake_up)

        def for_canonical(f):
            return lambda k: f(listener.canonical(k))

        hotkey = keyboard.HotKey(
            keyboard.HotKey.parse(shortcut),
            forwarder.signal.emit)

        listener = keyboard.Listener(
                on_press=for_canonical(hotkey.press),
                on_release=for_canonical(hotkey.release))

        listener.start()

    def wake_up(self):
        print('Waking up')
        self.show()
        self.cheeky_focus_stealer()

    def cheeky_focus_stealer(self):
        self.setFocus()
        self.raise_()
        self.activateWindow()

        # Working of linux, but causes freeze/crash on Windows 10
        message_box = QMessageBox(self)
        message_box.show()
        message_box.hide()

    def eventFilter(self, obj, event):
        if obj is self and event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Escape:
                self.hide()
                return True

        return super().eventFilter(obj, event)


def main():
    app = QApplication(sys.argv)
    app.setQuitOnLastWindowClosed(False)

    window = Launcher()
    window.show()

    app.exec_()


if __name__ == "__main__":
    main()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM