使用 Python 创建一个 HUD

Create a HUD using Python

我正在尝试使用 Python 为第三方应用程序编写 HUD。

我已经能够确保当第三方应用程序移动时我的 HUD 跟随第三方应用程序,但我很难确保 HUD 始终出现在第三方应用程序的顶部并在移动时消失它被最小化或当其他应用程序进入前台时。

我已经尝试了 Window API (SetWindowPos, BringWindowToTop and SetForegroundWindow) 的一些功能,但是其中 none 给了我预期的结果:HUD 总是落后于第三个-派对申请。

这是一个简单的代码示例来说明我的问题:

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *

import ctypes
import ctypes.wintypes

user32 =  ctypes.windll.user32
HUD_WIDTH = 100
HUD_HEIGHT = 50 
WINDOW_NAME = "Window name" #Replace this by the name of an opened window 

EVENT_OBJECT_LOCATIONCHANGE = 0x800B
EVENT_SYSTEM_FOREGROUND = 0x0003
WINEVENT_OUTOFCONTEXT = 0x0000
EVENT_SYSTEM_MINIMIZEEND = 0x0017

class Hud(QLabel):
    def __init__(self,window_handle):
        super().__init__()
        self.initUI()
        self.window_handle = window_handle
        rect = ctypes.wintypes.RECT()
        user32.GetWindowRect(window_handle,ctypes.byref(rect))
        self.window_x = rect.left
        self.window_y = rect.top
        self.setFollowWindow()

def initUI(self):
    self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)   
    self.resize(HUD_WIDTH,HUD_HEIGHT)   
    p = self.palette()
    p.setColor(self.backgroundRole(), Qt.black)
    p.setColor(self.foregroundRole(),Qt.white)
    self.setPalette(p)  
    self.setWindowOpacity(0.7)
    self.setText("Hello")
    self.setAlignment(Qt.AlignCenter)
    self.show()

def mousePressEvent(self,event):
    self.click_x_coordinate = event.x()
    self.click_y_coordinate = event.y()

def mouseMoveEvent(self,event):
    self.move(event.globalX()-self.click_x_coordinate,event.globalY()-self.click_y_coordinate)

def setFollowWindow(self):
    WinEventProcType = ctypes.WINFUNCTYPE(
        None, 
        ctypes.wintypes.HANDLE,
        ctypes.wintypes.DWORD,
        ctypes.wintypes.HWND,
        ctypes.wintypes.LONG,
        ctypes.wintypes.LONG,
        ctypes.wintypes.DWORD,
        ctypes.wintypes.DWORD
    )
    def callback(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime):
        if idObject != -9: #Avoids mouse events
            if event == EVENT_OBJECT_LOCATIONCHANGE and hwnd == self.window_handle:
                rect = ctypes.wintypes.RECT()
                user32.GetWindowRect(hwnd,ctypes.byref(rect))
                self.move(self.x() + rect.left - self.window_x,self.y() + rect.top - self.window_y)
                self.window_x = rect.left
                self.window_y = rect.top 
            elif event == EVENT_SYSTEM_FOREGROUND or event == EVENT_SYSTEM_MINIMIZEEND:
                if hwnd == self.window_handle:
                    user32.SetWindowPos(self.winId().__int__(),-1,self.x(),self.y(),self.width(),
                            self.height())

    self.WinEventProc = WinEventProcType(callback)        
    user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE
    self.hook = user32.SetWinEventHook(
        EVENT_SYSTEM_FOREGROUND,
        EVENT_OBJECT_LOCATIONCHANGE,
        0,
        self.WinEventProc,
        0,
        0,
        WINEVENT_OUTOFCONTEXT
    )
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.initUI()
    def initUI(self):
        self.launch_hud_button = QPushButton("Launch HUD",self)
        self.launch_hud_button.clicked.connect(self.launchHud)
        self.show()
    def launchHud(self):
        window_handle = user32.FindWindowW(0,WINDOW_NAME)
        if window_handle != 0 :
            self.hud = Hud(window_handle)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    sys.exit(app.exec_())

我还尝试使用 SetParent 将第三方应用程序设置为我的 HUD 的父应用程序,但是这样做时,我的 HUD 根本不显示。

您有什么建议可以使用 Window API 或其他方法使其工作吗?

试一试:

# ... 
def launchHud(self):
    window_handle = user32.FindWindowW(0,WINDOW_NAME)
    if window_handle != 0 :
        self.hud = Hud(window_handle)

        self.hud.setWindowFlags(self.windowFlags()     |     # <---
                                Qt.FramelessWindowHint |     # <---
                                Qt.WindowStaysOnTopHint)     # <---
        self.hud.show()                                      # <--- !!!
# ...

我终于设法确保我的 HUD 始终保持在 SetParent 的给定 third-party 应用程序的顶部,方法是将 HUD 父级 class 设置为 QMainWindow QLabel 并删除对 setWindowOpacity 的调用。以下代码对我有用:

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *

import ctypes
import ctypes.wintypes

user32 =  ctypes.windll.user32
HUD_WIDTH = 100
HUD_HEIGHT = 50 
WINDOW_NAME = "Window name" #Replace this by the name of an opened window 

class Hud(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.hwnd = self.winId().__int__()


    def initUI(self):
        self.setWindowFlags(Qt.FramelessWindowHint)   
        self.resize(HUD_WIDTH,HUD_HEIGHT)   
        self.textArea = QLabel()
        self.setCentralWidget(self.textArea);  
        self.setStyleSheet("background-color: rgb(0, 0, 0);color: rgb(255,255,255);")
        self.textArea.setText("Hello")
        self.textArea.setAlignment(Qt.AlignCenter)
        self.show()


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow,self).__init__()
        self.initUI()
    def initUI(self):
        self.launch_hud_button = QPushButton("Launch HUD",self)
        self.launch_hud_button.clicked.connect(self.launchHud)
        self.show()
    def launchHud(self):
        window_handle = user32.FindWindowW(0,WINDOW_NAME)
        if window_handle != 0 :
            self.hud = Hud()
            user32.SetParent(self.hud.hwnd,window_handle)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main_window = MainWindow()
    sys.exit(app.exec_())  

如果有人知道为什么 QMainWindow 适用于 SetParent 而不是其他 Qt class 像 QLabel,或者为什么修改 window 不透明度导致不可见的 HUD(对 SetParent 的调用不会 return 0 所以我假设 HUD 实际上被设置为我的 third-party 应用程序的子级但被绘制完全透明)。