You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何设置全局热键以避免pyautogui操作干扰其响应?

问题解决:全局热键在pyautogui按住Shift时响应异常

问题根源分析

  1. 热键注册的进程错误:创建子进程时直接调用registerGlobalHotkey()(带括号),导致热键实际在主进程注册,子进程并未真正启动监听。同时Qt信号无法跨进程传递,self.requestRestart.emit()在子进程中无法触发主进程的重启逻辑。
  2. 主进程阻塞:dropItems函数中的循环和time.sleep会阻塞主进程的事件循环,导致键盘钩子无法及时处理热键事件。
  3. 模拟按键冲突:pyautogui的keyDown('shift')会持续占用键盘模拟通道,干扰keyboard库的全局钩子对物理按键的检测。

解决方案

1. 修复热键的跨进程通信

使用multiprocessing.Queue实现子进程与主进程的通信,子进程触发热键后向队列发送重启信号,主进程定期监听队列并执行重启。

2. 将自动化操作移至Qt线程

避免阻塞主进程的事件循环,确保热键钩子能及时响应。

3. 替换pyautogui为pynput(可选)

pynput的键盘模拟与keyboard库的钩子兼容性更好,减少按键冲突。

修正后的代码示例

主界面代码

from multiprocessing import Process, Queue
import keyboard
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import pyqtSignal, QProcess, QTimer
import sys

class PrimaryInterface(QMainWindow):
    requestRestart = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("-----")
        self.resize(450, 300)
        self.networkManager = QNetworkAccessManager(self)
        self.setupUI()
        self.checkVersionUpdate()

        # 创建跨进程通信队列
        self.restart_queue = Queue()
        # 启动热键监听子进程,传递队列
        self.listener_process = Process(target=self.registerGlobalHotkey, args=(self.restart_queue,))
        self.listener_process.start()

        # 用QTimer定期检查队列
        self.queue_check_timer = QTimer(self)
        self.queue_check_timer.timeout.connect(self.check_restart_queue)
        self.queue_check_timer.start(100)  # 每100ms检查一次

        self.requestRestart.connect(self.restartApplication)

    def registerGlobalHotkey(self, restart_queue):
        # 子进程中注册热键,触发时向队列发送信号
        def trigger_restart():
            restart_queue.put("restart")
        keyboard.add_hotkey('ctrl+shift+x', trigger_restart)
        # 子进程保持运行,监听热键
        keyboard.wait()

    def check_restart_queue(self):
        if not self.restart_queue.empty():
            msg = self.restart_queue.get()
            if msg == "restart":
                self.requestRestart.emit()

    def restartApplication(self):
        self.cleanupBeforeExit()
        QApplication.quit()
        QProcess.startDetached(sys.executable, sys.argv)

    def cleanupBeforeExit(self):
        # 终止子进程并清理热键
        if self.listener_process.is_alive():
            self.listener_process.terminate()
            self.listener_process.join()
        keyboard.unhook_all_hotkeys()
        self.networkManager.clearAccessCache()

自动化脚本改进(移至Qt线程)

from PyQt5.QtCore import QThread, pyqtSignal
import cv2
import numpy as np
import random
import time
from pynput.keyboard import Controller as KeyboardController

class AutoDropThread(QThread):
    finished = pyqtSignal()

    def __init__(self, gwm):
        super().__init__()
        self.gwm = gwm
        self.keyboard = KeyboardController()
        self._is_running = True

    def stop(self):
        self._is_running = False

    def dropItems(self, gameplay_screen, last_slot_image):
        if last_slot_image is None or not self._is_running:
            print("No last slot image provided or thread stopped.")
            return
        
        inventory_x, inventory_y = 545, 203
        inventory_width, inventory_height = 197, 264
        inventory_img = gameplay_screen[inventory_y:inventory_y+inventory_height, inventory_x:inventory_x+inventory_width]

        if last_slot_image.ndim == 3:
            last_slot_image = cv2.cvtColor(last_slot_image, cv2.COLOR_BGR2GRAY)
        if inventory_img.ndim == 3:
            inventory_img = cv2.cvtColor(inventory_img, cv2.COLOR_BGR2GRAY)

        res = cv2.matchTemplate(inventory_img, last_slot_image, cv2.TM_CCOEFF_NORMED)
        threshold = 0.7 
        loc = np.where(res >= threshold)

        try:
            self.keyboard.press('shift')
            first_slot_processed = False
            for pt in zip(*loc[::-1]):
                if not self._is_running:
                    break
                game_location = (pt[0] + inventory_x + last_slot_image.shape[1]//2, pt[1] + inventory_y + last_slot_image.shape[0]//2)
                if not first_slot_processed:
                    self.gwm.smooth_move_and_random_click(game_location, radius=8, smooth_time_min=0.9, smooth_time_max=1.8)
                    first_slot_processed = True
                else:
                    self.gwm.smooth_move_and_random_click(game_location, radius=8, smooth_time_min=0.1, smooth_time_max=0.3)
                # 短暂休眠但不阻塞线程
                time.sleep(random.uniform(.02, .05))
        finally:
            self.keyboard.release('shift')
        self.finished.emit()

额外注意事项

  • 确保子进程在程序退出时被正确终止,避免残留进程。
  • 使用QTimer检查队列而非阻塞等待,保证主进程事件循环正常运行。
  • 替换pyautogui为pynput后,模拟按键不会干扰keyboard库的全局钩子检测。

内容的提问来源于stack exchange,提问作者Zerx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 09:52:05