如何使用带有 Win32 API 和 PySide6 的 Python 在单独的线程中监听热键?

问题描述 投票:0回答:1

我正在使用 Win32 API 和 PySide6 在 Python 中为 Windows 设置热键系统。我想在 HotkeyManager 类中注册热键并在单独的线程中侦听它们,以便 GUI 保持响应。但是,当我将侦听逻辑移至线程时,无法正确检测到热键事件。

这是不使用线程的代码,其中热键在主线程上注册和检测:

from threading import Thread
from typing import Callable, Dict
from win32gui import RegisterHotKey, UnregisterHotKey, GetMessage
from win32con import VK_NUMPAD0, MOD_NOREPEAT

class HotkeyManager:
    def __init__(self):
        self.hotkey_id = 1
        self._callbacks: Dict[int, Callable] = {}

    def register_hotkey(self, key_code: int, callback: Callable):
        self._callbacks[self.hotkey_id] = callback
        RegisterHotKey(0, self.hotkey_id, MOD_NOREPEAT, key_code)
        self.hotkey_id += 1

    def listen(self):
        while True:
            print("Listener started.")
            msg = GetMessage(None, 0, 0)
            hotkey_id = msg[1]
            if hotkey_id in self._callbacks:
                self._callbacks[hotkey_id]()

在主代码中,此设置按预期工作:

from PySide6 import QtWidgets
from win32con import VK_NUMPAD0

def on_press():
    print("Numpad 0 pressed!")

app = QtWidgets.QApplication([])
manager = HotkeyManager()
manager.register_hotkey(VK_NUMPAD0, on_press)
manager.listen()

# Initialize window
widget = QtWidgets.QMainWindow()
widget.show()
app.exec()

但是,当我尝试将listen()方法移至单独的线程时,热键无法正确响应:

class HotkeyManager:
    def listen(self):
        def run():
            while True:
                print("Listener started.")
                msg = GetMessage(None, 0, 0)
                hotkey_id = msg[1]
                if hotkey_id in self._callbacks:
                    self._callbacks[hotkey_id]()
        
        thread = Thread(target=run, daemon=True)
        thread.start()

如何在单独的线程中正确监听热键而不丢失功能?看来该问题可能是由于热键在主线程上注册而侦听逻辑在辅助线程中运行所致。我怎样才能解决这个问题,让一切按预期进行?

python python-multithreading pywin32 pywinauto
1个回答
0
投票

关于声明:“这是不使用线程即可工作的代码”,问题中的代码没有任何实际工作的内容。详细说一下:

这种对 WM_HOTKEY 进行线程监控的方式似乎有点笨拙,因为窗口有自己的消息循环,如果让我选择,我会尝试在窗口的消息处理函数中处理这些消息,但浅薄的谷歌搜索没有发现任何有用的信息。
发布基于您的代码的解决方案。这个想法是将热键注册和侦听包装在要在线程中执行的函数中。

code00.py

#!/usr/bin/env python

import sys
import threading
import typing

import win32con as wcon
import win32gui as wgui
from PySide6 import QtWidgets

MOD_NOREPEAT = 0x4000


class HotkeyManager:
    def __init__(self):
        self.hotkey_id = 1
        self._callbacks: typing.Dict[int, typing.Callable] = {}
        self.threads = []

    def __del__(self):
        self.clear()

    def register_hotkey(self, key_code: int, callback: typing.Callable):
        t = threading.Thread(target=self._register_hotkey, args=(key_code, callback), daemon=True)
        self.threads.append(t)
        t.start()

    def _register_hotkey(self, key_code: int, callback: typing.Callable):
        self._callbacks[self.hotkey_id] = callback
        wgui.RegisterHotKey(None, self.hotkey_id, MOD_NOREPEAT, key_code)
        self.hotkey_id += 1
        self._listen()

    def _listen(self):
        print(f"Listener started ({threading.get_ident()}, {threading.get_native_id()})")
        while True:
            res = wgui.GetMessage(None, 0, 0)
            print(f" GetMessage returned: {res}")  # @TODO - cfati: Check what it returns!
            rc, msg = res
            hotkey_id = msg[2]
            if hotkey_id in self._callbacks:
                self._callbacks[hotkey_id]()

    def clear(self):
        for hkid in self._callbacks:
            wgui.UnregisterHotKey(None, hkid)
        self._callbacks = {}


def on_press_np0():
    print(f"NumPad0 pressed!")


def main(*argv):
    print(f"Main thread ({threading.get_ident()}, {threading.get_native_id()})")
    app = QtWidgets.QApplication([])
    widget = QtWidgets.QMainWindow()
    widget.setWindowTitle("SO q079161068")
    widget.show()
    print("HotKeys")
    hkm = HotkeyManager()
    hkm.register_hotkey(wcon.VK_NUMPAD0, on_press_np0)
    print("App mesage loop")
    app.exec()


if __name__ == "__main__":
    print(
        "Python {:s} {:03d}bit on {:s}\n".format(
            " ".join(elem.strip() for elem in sys.version.split("\n")),
            64 if sys.maxsize > 0x100000000 else 32,
            sys.platform,
        )
    )
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

输出

[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q079161068]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32

Main thread (31388, 31388)
HotKeys
App mesage loop
Listener started (13152, 13152)
 GetMessage returned: [1, (0, 786, 1, 6291456, 513345140, (0, 0))]
NumPad0 pressed!
 GetMessage returned: [1, (0, 786, 1, 6291456, 513346593, (1919, 0))]
NumPad0 pressed!
 GetMessage returned: [1, (0, 786, 1, 6291456, 513347734, (1919, 1079))]
NumPad0 pressed!
 GetMessage returned: [1, (0, 786, 1, 6291456, 513349359, (0, 1079))]
NumPad0 pressed!
 GetMessage returned: [1, (0, 786, 1, 6291456, 513353093, (938, 561))]
NumPad0 pressed!
 GetMessage returned: [1, (0, 786, 1, 6291456, 513355781, (1621, 305))]
NumPad0 pressed!

Done.

同时发布截图:

img00

请注意,当线程运行时 NumPad 0 击键不会到达其他窗口。

© www.soinside.com 2019 - 2024. All rights reserved.